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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBVyQEwtmEw3mS1MoAiwrK
This commit is contained in:
mtlcl410
2026-10-29 07:21:22 +08:00
co-authored by Claude Opus 5.5
parent ca413b7e9f
commit a03446e8dd
3 changed files with 26 additions and 5 deletions
+10 -4
View File
@@ -259,7 +259,7 @@ impl Layouter<'_> {
let selected: Option<String> =
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<Node> = 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;
}
+1 -1
View File
@@ -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.
+15
View File
@@ -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#"<field w="2in" h="0.3in"><ui><textEdit/></ui></field>
<field name="field" w="2in" h="0.3in"><ui><textEdit/></ui></field>"#;
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:?}");
}