mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-07 02:51:33 +00:00
XLSX: form controls are read from the drawing <legacyDrawing r:id> names
The reader took the sheet's first VML drawing relationship. A sheet with pictures in its header or footer has a second VML drawing (<legacyDrawingHF>), and when that relationship came first the control shapes were not found: VML-only controls were skipped, and listed controls lost their caption and macro. The drawing is now the one <legacyDrawing r:id> names, as pass-through already does.
This commit is contained in:
1 parent
85c22dd27b
commit
dd97b29dec
3 files changed
+30
-9
No files matched your search
@@ -397,10 +397,11 @@ fn number_of(shape: &El) -> Option<u64> {
|
||||
|
||||
/// Reads a sheet's buttons, scroll bars and spin buttons: each `<control>` with its properties
|
||||
/// part, else its VML shape, and VML shapes the sheet doesn't list (files that only have VML).
|
||||
/// The shapes come from the VML drawing with relationship id `vml_rid` (`<legacyDrawing r:id>`).
|
||||
/// Returns the shape numbers of the controls it read, so pass-through keeps only the others.
|
||||
pub fn read(cx: &mut Ctx<'_>, controls: Option<&El>, rels: &[Rel], sheet: &mut Sheet) -> HashSet<u64> {
|
||||
pub fn read(cx: &mut Ctx<'_>, controls: Option<&El>, vml_rid: Option<&str>, rels: &[Rel], sheet: &mut Sheet) -> HashSet<u64> {
|
||||
let mut read = HashSet::new();
|
||||
let vml = match rels.iter().find(|r| r.kind == "vmlDrawing") {
|
||||
let vml = match vml_rid.and_then(|id| rels.iter().find(|r| r.id == id && r.kind == "vmlDrawing")) {
|
||||
Some(r) => cx.pkg.read_xml(&r.target).ok().flatten(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
@@ -141,7 +141,6 @@ pub fn read_sheet(cx: &mut Ctx<'_>, part: &str, name: &str) -> Result<(Sheet, Ha
|
||||
let mut fit_to_page = false;
|
||||
let mut column_assignments_left = MAX_COLUMN_ASSIGNMENTS;
|
||||
cx.cf_ext_ids.clear();
|
||||
let mut controls: Option<&El> = None;
|
||||
for e in &root.children {
|
||||
match e.name.as_str() {
|
||||
"sheetPr" => {
|
||||
@@ -263,12 +262,11 @@ pub fn read_sheet(cx: &mut Ctx<'_>, part: &str, name: &str) -> Result<(Sheet, Ha
|
||||
}
|
||||
}
|
||||
"extLst" => read_ext(cx, e, &mut sheet),
|
||||
"controls" | "AlternateContent" => controls = crate::controls::controls_of(e).or(controls),
|
||||
"protectedRanges" => read_protected_ranges(e, &mut sheet),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let read_controls = read_form_controls(cx, controls, &rels, &mut sheet);
|
||||
let read_controls = read_form_controls(cx, &root, &rels, &mut sheet);
|
||||
// Comments are related to the sheet without an element in it.
|
||||
for rel in rels.iter().filter(|r| r.kind == "comments") {
|
||||
if let Some(x) = cx.optional_xml(&rel.target)? {
|
||||
@@ -293,10 +291,14 @@ pub fn read_sheet(cx: &mut Ctx<'_>, part: &str, name: &str) -> Result<(Sheet, Ha
|
||||
Ok((sheet, read_controls))
|
||||
}
|
||||
|
||||
/// Buttons, scroll bars and spin buttons: the `<controls>` list with its properties parts, else
|
||||
/// the VML shapes. Returns the VML shape numbers read, so pass-through keeps only the others.
|
||||
fn read_form_controls(cx: &mut Ctx<'_>, controls: Option<&El>, rels: &[Rel], sheet: &mut Sheet) -> HashSet<u64> {
|
||||
if controls.is_some() || rels.iter().any(|r| r.kind == "vmlDrawing") { crate::controls::read(cx, controls, rels, sheet) } else { HashSet::new() }
|
||||
/// Buttons, scroll bars and spin buttons: the `<controls>` list (also inside
|
||||
/// `mc:AlternateContent`) with its properties parts, else the shapes of the VML drawing that
|
||||
/// `<legacyDrawing r:id>` names (not the header and footer drawing of `<legacyDrawingHF>`).
|
||||
/// Returns the VML shape numbers read, so pass-through keeps only the others.
|
||||
fn read_form_controls(cx: &mut Ctx<'_>, root: &El, rels: &[Rel], sheet: &mut Sheet) -> HashSet<u64> {
|
||||
let controls = root.children.iter().filter_map(crate::controls::controls_of).next_back();
|
||||
let vml = root.child("legacyDrawing").and_then(|e| e.attr("id"));
|
||||
if controls.is_some() || vml.is_some() { crate::controls::read(cx, controls, vml, rels, sheet) } else { HashSet::new() }
|
||||
}
|
||||
|
||||
/// A protection password hash: the iterated attributes (`algorithmName`, `hashValue`,
|
||||
|
||||
@@ -315,3 +315,21 @@ fn the_properties_parts_use_the_ctrl_prop_relationship_type() {
|
||||
assert_eq!(types.len(), 4, "{rels}");
|
||||
assert!(types.iter().all(|t| *t == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp"), "{types:?}");
|
||||
}
|
||||
|
||||
/// A sheet with a header and footer drawing and a drawing for its controls: the controls come
|
||||
/// from the drawing `<legacyDrawing r:id>` names, whichever relationship is listed first.
|
||||
#[test]
|
||||
fn controls_come_from_the_legacy_drawing_not_the_header_and_footer_drawing() {
|
||||
let hf = r##"<xml xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"><v:shape id="LH" o:spid="_x0000_s2049" type="#_x0000_t75"><v:imagedata o:title="logo"/></v:shape></xml>"##;
|
||||
let rels = r#"<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" Target="../drawings/vmlDrawingHF1.vml"/><Relationship Id="rId9" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" Target="../drawings/vmlDrawing1.vml"/></Relationships>"#;
|
||||
let bytes = minimal(
|
||||
r#"<sheetData/><legacyDrawing r:id="rId9"/><legacyDrawingHF r:id="rId1"/>"#,
|
||||
&[("xl/worksheets/_rels/sheet1.xml.rels", rels), ("xl/drawings/vmlDrawingHF1.vml", hf), ("xl/drawings/vmlDrawing1.vml", VML_ONLY)],
|
||||
"",
|
||||
"",
|
||||
);
|
||||
let (wb, _) = read_xlsx(&bytes).unwrap();
|
||||
let c = &wb.sheet(0).unwrap().controls;
|
||||
assert_eq!(c.len(), 3, "{c:?}");
|
||||
assert_eq!(c[2].text, "Go");
|
||||
}
|
||||
Reference in new issue
Block a user