From d8703162be6a6f447da9bbb094deaa9cc43284cf Mon Sep 17 00:00:00 2001 From: prxnvt <53838599+prxnvt@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:06:51 -0500 Subject: [PATCH] XLSX: form controls find their properties parts through a relationship index Each listed control looked its properties part up with a scan of the sheet's relationships, so a sheet at the 10,000-control cap compared ids a hundred million times. The relationships are indexed by id once, as the VML shapes already were; the first of a repeated id still wins. --- crates/xlsx/src/controls.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/xlsx/src/controls.rs b/crates/xlsx/src/controls.rs index e45c98fe3..cf38d0908 100644 --- a/crates/xlsx/src/controls.rs +++ b/crates/xlsx/src/controls.rs @@ -406,7 +406,7 @@ pub fn read(cx: &mut Ctx<'_>, controls: Option<&El>, vml_rid: Option<&str>, rels None => None, }; let shapes: Vec<&El> = vml.as_ref().map(|v| v.kids("shape").collect()).unwrap_or_default(); - // Looked up once per listed control: an index keeps a sheet of many controls linear. + // Looked up once per listed control: indexes keep a sheet of many controls linear. let mut by_number: HashMap = HashMap::with_capacity(shapes.len()); for s in &shapes { if let Some(n) = number_of(s) { @@ -414,6 +414,12 @@ pub fn read(cx: &mut Ctx<'_>, controls: Option<&El>, vml_rid: Option<&str>, rels } } let shape_of = |n: u64| by_number.get(&n).copied(); + // The relationships likewise: one per control, looked up by id (the first of a repeated id wins, + // as a scan would find it). + let mut rel_by_id: HashMap<&str, &Rel> = HashMap::with_capacity(rels.len()); + for r in rels { + rel_by_id.entry(r.id.as_str()).or_insert(r); + } // Every shape the sheet lists, read here or not. let mut listed: HashSet = HashSet::new(); for ctl in controls.map(controls_in).unwrap_or_default() { @@ -424,7 +430,7 @@ pub fn read(cx: &mut Ctx<'_>, controls: Option<&El>, vml_rid: Option<&str>, rels } let shape = shape_of(n); let vml_props = shape.and_then(props_from_vml); - let prop_part = ctl.attr("id").and_then(|id| rels.iter().find(|r| r.id == id)).map(|r| r.target.clone()); + let prop_part = ctl.attr("id").and_then(|id| rel_by_id.get(id)).map(|r| r.target.clone()); let file_props = match prop_part { Some(t) => cx.pkg.read_xml(&t).ok().flatten().filter(|e| e.name == "formControlPr").map(|e| props_from_ctrl_prop(&e)), None => None,