XLSX: form controls find their VML shapes through an index

Each control listed in <controls> looked for its VML shape by scanning
every shape, so a sheet of 10,000 controls took over 8 s to open. The
shapes are now indexed by number once, and the same sheet opens in
about 0.4 s.
This commit is contained in:
prxnvt committed 2026-10-31 16:11:55 -04:00
1 parent 0e631e20da
commit cc68d71ac2
2 files changed
+30 -2

No files matched your search

+9 -2
View File
@@ -12,7 +12,7 @@
//! list boxes, drop-downs, ActiveX controls) are left to pass-through (`passthrough.rs`), which
//! keeps them unchanged; a sheet can hold both, in one `<controls>` element and one VML drawing.
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::fmt::Write as _;
use gridcraft_core::CellRef;
@@ -404,7 +404,14 @@ pub fn read(cx: &mut Ctx<'_>, controls: Option<&El>, rels: &[Rel], sheet: &mut S
None => None,
};
let shapes: Vec<&El> = vml.as_ref().map(|v| v.kids("shape").collect()).unwrap_or_default();
let shape_of = |n: u64| shapes.iter().copied().find(|s| number_of(s) == Some(n));
// Looked up once per listed control: an index keeps a sheet of many controls linear.
let mut by_number: HashMap<u64, &El> = HashMap::with_capacity(shapes.len());
for s in &shapes {
if let Some(n) = number_of(s) {
by_number.entry(n).or_insert(s);
}
}
let shape_of = |n: u64| by_number.get(&n).copied();
// Every shape the sheet lists, read here or not.
let mut listed: HashSet<u64> = HashSet::new();
for ctl in controls.map(controls_in).unwrap_or_default() {
+21
View File
@@ -282,3 +282,24 @@ fn hostile_form_controls_are_capped_and_repaired() {
let (back, _) = read_xlsx(&write_xlsx(&wb).unwrap()).unwrap();
assert_eq!(back.sheet(0).unwrap().controls.len(), c.len());
}
/// Each listed control finds its VML shape through an index, so a sheet of many controls reads in
/// linear time (it used to scan every shape for every control).
#[test]
#[allow(clippy::disallowed_methods)] // a native-only timing test: the clock is read off wasm
fn many_listed_controls_read_in_linear_time() {
let mut wb = Workbook::default();
if let Some(sh) = wb.sheet_mut(0) {
for i in 0..10_000u32 {
let a = anchor(&format!("B{}", i + 1), 60.0, 18.0);
sh.controls.push(FormControl { id: i + 1, kind: ControlKind::Spin, anchor: a, ..Default::default() });
}
}
let bytes = write_xlsx(&wb).unwrap();
let t0 = std::time::Instant::now();
let (back, _) = read_xlsx(&bytes).unwrap();
let took = t0.elapsed();
assert_eq!(back.sheet(0).unwrap().controls.len(), 10_000);
// About 0.4 s here; scanning every shape for every control took over 8 s.
assert!(took < std::time::Duration::from_secs(3), "{took:?}");
}