Read conventional XLSX shared strings without a relationship

This commit is contained in:
Dylan M. Taylor committed 2026-11-13 22:11:05 +00:00
1 parent b5b8a7750d
commit 6d0b33c0cd
2 files changed
+44 -3

No files matched your search

+9 -3
View File
@@ -188,9 +188,15 @@ impl<'a> Package<'a> {
self.binary_strings = Some(strings.len());
return Ok(strings);
}
let Some(rel) = rels.first("sharedStrings").filter(|r| !r.external) else { return Ok(Vec::new()) };
let Some(bytes) = self.opc.read(&rel.target)? else {
self.warn(format!("missing part {}", rel.target));
let rel = rels.first("sharedStrings").filter(|r| !r.external);
// Some older spreadsheet writers omit the workbook relationship but still emit the
// conventional shared-string part. The pre-OPC reader accepted that layout, so retain
// compatibility by checking the standard part name only when the relationship is absent.
let target = rel.map_or("xl/sharedStrings.xml", |r| r.target.as_str());
let Some(bytes) = self.opc.read(target)? else {
if rel.is_some() {
self.warn(format!("missing part {target}"));
}
return Ok(Vec::new());
};
let mut items = Vec::new();
+35
View File
@@ -168,6 +168,41 @@ fn a_missing_shared_strings_part_is_a_warning() {
assert!(wb.warnings.iter().any(|w| w.contains("missing part xl/sharedStrings.xml")), "{:?}", wb.warnings);
}
#[test]
fn shared_strings_at_the_conventional_path_work_without_a_relationship() {
const REL_NS: &str = "http://schemas.openxmlformats.org/package/2006/relationships";
const DOC_REL: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
const MAIN: &str = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
let root_rels =
format!(r#"<Relationships xmlns="{REL_NS}"><Relationship Id="w" Type="{DOC_REL}/officeDocument" Target="xl/workbook.xml"/></Relationships>"#);
let workbook = format!(r#"<workbook xmlns="{MAIN}" xmlns:r="{DOC_REL}"><sheets><sheet name="S" sheetId="1" r:id="s"/></sheets></workbook>"#);
let workbook_rels = format!(
r#"<Relationships xmlns="{REL_NS}"><Relationship Id="s" Type="{DOC_REL}/worksheet" Target="worksheets/sheet1.xml"/></Relationships>"#
);
let worksheet = format!(r#"<worksheet xmlns="{MAIN}"><sheetData><row r="1"><c r="A1" t="s"><v>0</v></c></row></sheetData></worksheet>"#);
let strings = format!(r#"<sst xmlns="{MAIN}"><si><t>Tea</t></si></sst>"#);
let bytes = zip(&[
("_rels/.rels", root_rels.as_bytes()),
("xl/workbook.xml", workbook.as_bytes()),
("xl/_rels/workbook.xml.rels", workbook_rels.as_bytes()),
("xl/worksheets/sheet1.xml", worksheet.as_bytes()),
("xl/sharedStrings.xml", strings.as_bytes()),
]);
let mut package = Package::open(&bytes, &Limits::default()).unwrap();
let strings = package.shared_strings().unwrap();
assert_eq!(strings.iter().map(|item| item.text.as_ref()).collect::<Vec<_>>(), ["Tea"]);
let workbook = package.workbook().unwrap();
let mut values = Vec::new();
package
.sheet(&workbook.sheets[0], |row| {
values.extend(row.cells.into_iter().map(|cell| cell.value.resolve(&strings, false)));
Ok(())
})
.unwrap();
assert_eq!(values, [Value::text("Tea")]);
}
#[test]
fn limits_are_errors() {
let rows: String = (1..=2000).map(|r| format!(r#"<row r="{r}"><c r="A{r}"><v>{r}</v></c></row>"#)).collect();