From a03446e8dd6f8788e0cd083145dc7ea18aa8ae0a Mon Sep 17 00:00:00 2001 From: mtlcl410 <2368091+mtlcl410@users.noreply.github.com> Date: Thu, 29 Oct 2026 07:21:22 +0800 Subject: [PATCH] M12: an unnamed XFA field's made-up name never takes one the template uses An unnamed field or group is named `field` or `group`. When it came before a field the template names `field`, it took that name and the named field became `field_2`. A fallback name now skips the template's own names too, and a field keeps the name the template gives it while no other field has it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CBVyQEwtmEw3mS1MoAiwrK --- crates/xfa/src/layout/draw.rs | 14 ++++++++++---- crates/xfa/src/layout/field.rs | 2 +- crates/xfa/src/tests.rs | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/crates/xfa/src/layout/draw.rs b/crates/xfa/src/layout/draw.rs index b71e5114e8..e3ecc41605 100644 --- a/crates/xfa/src/layout/draw.rs +++ b/crates/xfa/src/layout/draw.rs @@ -259,7 +259,7 @@ impl Layouter<'_> { let selected: Option = self.data.and_then(|d| d.text_at(&data_path(self.tpl, &ctx.som))).map(|t| t.trim().to_string()).filter(|t| !t.is_empty()); let inner = rect.inset(&g.common.margin); - let group = self.unique_name(g.common.name.as_deref().unwrap_or("group")); + let group = self.unique_name(g.common.name.as_deref().unwrap_or("group"), g.common.name.is_none()); let nodes: Vec = g.fields.iter().map(|f| Node::Field(Box::new(f.clone()))).collect(); let placed: Vec<(usize, Rect)> = match g.layout { Layout::Tb => { @@ -319,14 +319,20 @@ impl Layouter<'_> { /// A field name made from `base` that no other field of the form has: the first keeps it, /// later ones get `_2`, `_3`, …, skipping a name another field already has or that the /// template gives another field (a template field named `Cell4_2` keeps it, and the second - /// `Cell4` becomes `Cell4_3`). - pub(super) fn unique_name(&mut self, base: &str) -> String { + /// `Cell4` becomes `Cell4_3`). A `fallback` base, the name an unnamed field or group gets + /// (`field`, `group`), is made up too: it doesn't take a name the template gives another + /// field either, and a template field named `field` still gets it. + pub(super) fn unique_name(&mut self, base: &str, fallback: bool) -> String { let base = clean_name(base); + // The template's own name, while no other field has it. + if !fallback && self.taken.insert(base.clone()) { + return base; + } loop { let n = self.names.entry(base.clone()).or_insert(0); *n = n.saturating_add(1); let candidate = if *n == 1 { base.clone() } else { format!("{base}_{n}") }; - let reserved = *n > 1 && self.template_names.contains(&candidate); + let reserved = (fallback || *n > 1) && self.template_names.contains(&candidate); if !reserved && self.taken.insert(candidate.clone()) { return candidate; } diff --git a/crates/xfa/src/layout/field.rs b/crates/xfa/src/layout/field.rs index cc15f16788..e046ece482 100644 --- a/crates/xfa/src/layout/field.rs +++ b/crates/xfa/src/layout/field.rs @@ -152,7 +152,7 @@ impl Layouter<'_> { (Ui::Button, Some(cap)) => ctx.para.apply(&cap.para).h_align, _ => fctx.para.h_align, }; - let name = self.unique_name(f.common.name.as_deref().unwrap_or("field")); + let name = self.unique_name(f.common.name.as_deref().unwrap_or("field"), f.common.name.is_none()); let som = som_path(); let read_only = matches!(f.access, Access::ReadOnly | Access::Protected); // The data's value wins over the template's default. diff --git a/crates/xfa/src/tests.rs b/crates/xfa/src/tests.rs index e6b562290f..af33e46b12 100644 --- a/crates/xfa/src/tests.rs +++ b/crates/xfa/src/tests.rs @@ -4117,3 +4117,18 @@ fn a_made_up_field_name_never_takes_one_the_template_uses() { assert_eq!(got, Some("Cell4_2"), "{rows:?}: the template's own name is kept ({names:?})"); } } + +/// An unnamed field's made-up name (`field`) doesn't take the name a later field has in the +/// template: the field the template names `field` keeps it, and the unnamed one before it takes +/// the next free name. +#[test] +fn an_unnamed_field_never_takes_a_name_the_template_uses() { + let body = r#" +"#; + let form = layout_xml(&one_area_form(r#"layout="tb""#, body)).unwrap(); + let all = widgets(&form.pages[0]); + // In layout order: the unnamed field, then the one the template names `field`. + let names: Vec<&str> = all.iter().map(|w| w.name.as_str()).collect(); + assert_eq!(names.get(1), Some(&"field"), "{names:?}"); + assert_ne!(names.first(), Some(&"field"), "{names:?}"); +}