XLSX: new workbooks get <fileVersion appName="gridcraft"/> (fixes #1699)

A workbook GridCraft created had no <fileVersion> in workbook.xml; it now
names GridCraft, and keeps it on later saves.

A workbook read from an Excel file still keeps its <fileVersion> as it was,
appName="xl" included. Observed in Excel 16.114: with the calcFeatures
extension present, Excel uses the saved cell values only when appName is
"xl". appName="gridcraft", "Calc" or no appName makes it recalculate
everything on open, which brings back #799 (lookups into closed workbooks
open as #CALC!). The application that last saved the file is recorded in
docProps/app.xml <Application>GridCraft</Application>, which GridCraft
already writes on every save and which Excel's recalculation ignores.
This commit is contained in:
aviiciii
2026-10-17 01:26:43 +05:30
parent f1894c6da6
commit 94af08ad20
2 changed files with 16 additions and 4 deletions
+12 -3
View File
@@ -819,7 +819,16 @@ fn file_version_and_calc_features_survive_a_save() {
assert!(text.contains(&format!("<extLst>{calc_features}</extLst>")), "{text}");
}
// A workbook that had neither gets neither.
let plain = Unpacked::new(&write_xlsx(&gridcraft_model::Workbook::new()).unwrap()).text("xl/workbook.xml");
assert!(!plain.contains("fileVersion") && !plain.contains("extLst"), "{plain}");
// A workbook that had neither gets a <fileVersion> naming GridCraft (#1699), kept on the next
// save, and no calcFeatures: Excel recalculates it on open.
let new = write_xlsx(&gridcraft_model::Workbook::new()).unwrap();
let (reread, _) = read_xlsx(&new).unwrap();
for saved in [new, write_xlsx(&reread).unwrap()] {
let u = Unpacked::new(&saved);
u.assert_valid();
let plain = u.text("xl/workbook.xml");
let root = xml::parse(plain.as_bytes()).unwrap();
assert_eq!(root.children.first().map(|e| e.name.as_str()), Some("fileVersion"), "{plain}");
assert!(plain.contains(r#"<fileVersion appName="gridcraft"/>"#) && !plain.contains("extLst"), "{plain}");
}
}
+4 -1
View File
@@ -314,7 +314,10 @@ pub fn write_xlsx(wb: &Workbook, kind: XlsxKind) -> Result<Vec<u8>, IoError> {
}
let mut w = format!("{XML_DECL}<workbook xmlns=\"{NS_MAIN}\" xmlns:r=\"{NS_REL}\">");
w.push_str(&crate::passthrough::workbook_elements(wb, "fileVersion"));
// A workbook read from a file keeps its <fileVersion> as it was: Excel trusts the saved values
// only when appName is "xl" (#799). One without names GridCraft (#1699), and Excel recalculates it.
let file_version = crate::passthrough::workbook_elements(wb, "fileVersion");
w.push_str(if file_version.is_empty() { "<fileVersion appName=\"gridcraft\"/>" } else { &file_version });
w.push_str(if wb.date_system == gridcraft_core::DateSystem::D1904 { "<workbookPr date1904=\"1\"/>" } else { "<workbookPr/>" });
if wb.protected_structure {
let pw = wb.structure_password.as_ref().map(|h| crate::sheet_write::password_attrs(h, "workbook")).unwrap_or_default();