mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-10 04:21:45 +00:00
Merge PR #3238
This commit is contained in:
commit
89d658406e
5 files changed
+46
-4
No files matched your search
@@ -205,6 +205,12 @@ pub struct Workbook {
|
||||
pub calc: CalcSettings,
|
||||
#[serde(default, skip_serializing_if = "is_all")]
|
||||
pub show_objects: ShowObjects,
|
||||
/// Trust Center › Privacy Options › "Remove personal information from file properties on save"
|
||||
/// (`<workbookPr filterPrivacy="1"/>`): kept with the file, and while it is on the author and
|
||||
/// company are left out of the XLSX files GridCraft writes, as Excel leaves them empty. Other
|
||||
/// formats aren't filtered: the workbook's own serialization still holds them.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub remove_personal_info: bool,
|
||||
/// Read and set with [`Workbook::active_sheet`] and [`Workbook::set_active_sheet`].
|
||||
pub active_sheet: ActiveSheet,
|
||||
/// The workbook window's display settings (`<workbookView>`), kept across an open + save.
|
||||
@@ -343,6 +349,7 @@ impl Workbook {
|
||||
date_system: DateSystem::D1900,
|
||||
calc: CalcSettings::default(),
|
||||
show_objects: ShowObjects::All,
|
||||
remove_personal_info: false,
|
||||
active_sheet: ActiveSheet::default(),
|
||||
view: WorkbookView::default(),
|
||||
props: DocProps::default(),
|
||||
|
||||
@@ -200,6 +200,7 @@ fn read_workbook_properties(cx: &mut Ctx<'_>, wb: &mut Workbook, xml: &El) {
|
||||
if p.flag("date1904", false) {
|
||||
cx.date_system = DateSystem::D1904;
|
||||
}
|
||||
wb.remove_personal_info = p.flag("filterPrivacy", false);
|
||||
wb.show_objects = match p.attr("showObjects") {
|
||||
Some("none") => ShowObjects::None,
|
||||
Some("placeholders") => ShowObjects::Placeholders,
|
||||
|
||||
@@ -1714,3 +1714,30 @@ fn r1c1_reference_style_is_kept() {
|
||||
let book = pkg.read_xml("xl/workbook.xml").unwrap().unwrap();
|
||||
assert_eq!(book.child("calcPr").and_then(|c| c.attr("refMode")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_personal_information_is_kept_and_honoured() {
|
||||
// #3089: `<workbookPr filterPrivacy="1"/>` was dropped, so a workbook cleaned of personal
|
||||
// information came back with it switched off, and GridCraft wrote the author and company back.
|
||||
let bytes = super::minimal("<sheetData/>", &[], "", r#"<workbookPr filterPrivacy="1"/>"#);
|
||||
let (mut wb, _) = read_xlsx(&bytes).unwrap();
|
||||
assert!(wb.remove_personal_info);
|
||||
wb.props.author = "Jane Example".into();
|
||||
wb.props.company = "Example Co".into();
|
||||
wb.props.title = "Budget".into();
|
||||
let saved = write_xlsx(&wb).unwrap();
|
||||
let mut pkg = Package::open(&saved).unwrap();
|
||||
let book = pkg.read_xml("xl/workbook.xml").unwrap().unwrap();
|
||||
assert_eq!(book.child("workbookPr").and_then(|p| p.attr("filterPrivacy")), Some("1"));
|
||||
let core = String::from_utf8(pkg.read("docProps/core.xml").unwrap().unwrap()).unwrap();
|
||||
let app = String::from_utf8(pkg.read("docProps/app.xml").unwrap().unwrap()).unwrap();
|
||||
assert!(!core.contains("Jane Example") && !app.contains("Example Co"), "{core}\n{app}");
|
||||
assert!(core.contains("<dc:title>Budget</dc:title>"), "other properties stay: {core}");
|
||||
// Off (the default), nothing changes.
|
||||
wb.remove_personal_info = false;
|
||||
let saved = write_xlsx(&wb).unwrap();
|
||||
let mut pkg = Package::open(&saved).unwrap();
|
||||
let book = pkg.read_xml("xl/workbook.xml").unwrap().unwrap();
|
||||
assert_eq!(book.child("workbookPr").and_then(|p| p.attr("filterPrivacy")), None);
|
||||
assert!(String::from_utf8(pkg.read("docProps/core.xml").unwrap().unwrap()).unwrap().contains("Jane Example"));
|
||||
}
|
||||
@@ -293,6 +293,9 @@ pub fn write_xlsx_named(wb: &Workbook, kind: XlsxKind, book_name: Option<&str>)
|
||||
if wb.date_system == gridcraft_core::DateSystem::D1904 {
|
||||
w.push_str(" date1904=\"1\"");
|
||||
}
|
||||
if wb.remove_personal_info {
|
||||
w.push_str(" filterPrivacy=\"1\"");
|
||||
}
|
||||
w.push_str("/>");
|
||||
if wb.protected_structure {
|
||||
let pw = wb.structure_password.as_ref().map(|h| crate::sheet_write::password_attrs(h, "workbook")).unwrap_or_default();
|
||||
@@ -402,13 +405,16 @@ pub fn write_xlsx_named(wb: &Workbook, kind: XlsxKind, book_name: Option<&str>)
|
||||
|
||||
// Document properties.
|
||||
let p = &wb.props;
|
||||
// "Remove personal information on save": the author and company are not saved (#3089).
|
||||
let private = |v: &String| if wb.remove_personal_info { String::new() } else { v.clone() };
|
||||
let (author, company) = (private(&p.author), private(&p.company));
|
||||
let mut core = format!(
|
||||
"{XML_DECL}<cp:coreProperties xmlns:cp=\"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:dcterms=\"http://purl.org/dc/terms/\" xmlns:dcmitype=\"http://purl.org/dc/dcmitype/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
|
||||
);
|
||||
for (tag, v) in [
|
||||
("dc:title", &p.title),
|
||||
("dc:subject", &p.subject),
|
||||
("dc:creator", &p.author),
|
||||
("dc:creator", &author),
|
||||
("cp:keywords", &p.keywords),
|
||||
("dc:description", &p.description),
|
||||
] {
|
||||
@@ -420,8 +426,8 @@ pub fn write_xlsx_named(wb: &Workbook, kind: XlsxKind, book_name: Option<&str>)
|
||||
out.part("docProps/core.xml", Some("application/vnd.openxmlformats-package.core-properties+xml"), core.into_bytes());
|
||||
let mut app = String::from(XML_DECL);
|
||||
app.push_str("<Properties xmlns=\"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties\" xmlns:vt=\"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes\"><Application>GridCraft</Application>");
|
||||
if !p.company.is_empty() {
|
||||
let _ = write!(app, "<Company>{}</Company>", esc(&p.company));
|
||||
if !company.is_empty() {
|
||||
let _ = write!(app, "<Company>{}</Company>", esc(&company));
|
||||
}
|
||||
app.push_str("</Properties>");
|
||||
out.part("docProps/app.xml", Some("application/vnd.openxmlformats-officedocument.extended-properties+xml"), app.into_bytes());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# File-format parity
|
||||
|
||||
> **Last reviewed:** 2026-11-01 · **Last updated:** 2026-11-03 · **Change:** minor (a workbook's R1C1 reference style, `calcPr refMode`, is kept through open and save; earlier: XLSX page margins are written as their decimals, `0.3` rather than `0.30000001192092896`; earlier: Python cells in `.xlsx` as [MS-XLSX] specifies, `_xlfn._xlws.PY(…)` naming code in the Python part, with cached values, and in GridCraft JSON, with limits on open; earlier: equations in xlsx as Office Math with a marker in `descr` so an edit made in Excel reads back, read back only up to the equation source cap, and a brace with a label and a script on its other side keeps both; Symbol font in PDF; earlier: XLSX reads and writes Page Setup's page order, `pageSetup pageOrder`, and PDF export follows it; earlier: chart data tables: native c:dTable import/export; earlier: XLSX workbook-view selection, normalized tab ratios and sheet-edit regressions; fidelity estimates unchanged; earlier: formulas from Google Sheets' XLSX export open as the original formula instead of `IFERROR(__xludf.DUMMYFUNCTION(…),…)` when GridCraft calculates every function in it, and save as it; `file.exportCsv {values: true}` writes raw values for automation; .xls dialog sheets are skipped instead of read as worksheets; `.xls` import reads each sheet's display options from WINDOW2: right-to-left, gridlines, headings, zeros, formulas; XLSX text boxes and shapes keep their paragraphs' right-to-left direction on save; CSV/TSV/TXT can open with every field kept as text via `file.open {csvAsText}`; formulas over Excel's 8,192 characters, and `%` or call chains past the nesting limit, are parse errors instead of stack overflows when a file is opened; XLSX PivotTables read and write `rowGrandTotals`/`colGrandTotals` the way Excel does, so a table saved with only one grand total opens with the right one; HTML export keeps right-to-left; CSV and text import detects likely Windows-1256 Arabic when invalid UTF-8 has Arabic byte runs; other invalid UTF-8 remains Windows-1252; HTML export places pictures and charts, as inline SVG, at their anchors; earlier: `file.exportCsv {values: true}` writes raw values for automation; .xls dialog sheets are skipped instead of read as worksheets; `.xls` import reads each sheet's display options from WINDOW2: right-to-left, gridlines, headings, zeros, formulas; XLSX text boxes and shapes keep their paragraphs' right-to-left direction on save; CSV/TSV/TXT can open with every field kept as text via `file.open {csvAsText}`; formulas over Excel's 8,192 characters, and `%` or call chains past the nesting limit, are parse errors instead of stack overflows when a file is opened; XLSX PivotTables read and write `rowGrandTotals`/`colGrandTotals` the way Excel does, so a table saved with only one grand total opens with the right one; HTML export keeps right-to-left; CSV and text import detects likely Windows-1256 Arabic when invalid UTF-8 has Arabic byte runs; other invalid UTF-8 remains Windows-1252; HTML export places pictures and charts, as inline SVG, at their anchors; CSV and text open detects each column's date order from values that read only one way, warns when every date is ambiguous, and keeps a column with both kinds as text (#2765)) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-11-01 · **Last updated:** 2026-11-03 · **Change:** minor (`workbookPr filterPrivacy` kept, and honoured on save; earlier: a workbook's R1C1 reference style, `calcPr refMode`, is kept through open and save; earlier: XLSX page margins are written as their decimals, `0.3` rather than `0.30000001192092896`; earlier: Python cells in `.xlsx` as [MS-XLSX] specifies, `_xlfn._xlws.PY(…)` naming code in the Python part, with cached values, and in GridCraft JSON, with limits on open; earlier: equations in xlsx as Office Math with a marker in `descr` so an edit made in Excel reads back, read back only up to the equation source cap, and a brace with a label and a script on its other side keeps both; Symbol font in PDF; earlier: XLSX reads and writes Page Setup's page order, `pageSetup pageOrder`, and PDF export follows it; earlier: chart data tables: native c:dTable import/export; earlier: XLSX workbook-view selection, normalized tab ratios and sheet-edit regressions; fidelity estimates unchanged; earlier: formulas from Google Sheets' XLSX export open as the original formula instead of `IFERROR(__xludf.DUMMYFUNCTION(…),…)` when GridCraft calculates every function in it, and save as it; `file.exportCsv {values: true}` writes raw values for automation; .xls dialog sheets are skipped instead of read as worksheets; `.xls` import reads each sheet's display options from WINDOW2: right-to-left, gridlines, headings, zeros, formulas; XLSX text boxes and shapes keep their paragraphs' right-to-left direction on save; CSV/TSV/TXT can open with every field kept as text via `file.open {csvAsText}`; formulas over Excel's 8,192 characters, and `%` or call chains past the nesting limit, are parse errors instead of stack overflows when a file is opened; XLSX PivotTables read and write `rowGrandTotals`/`colGrandTotals` the way Excel does, so a table saved with only one grand total opens with the right one; HTML export keeps right-to-left; CSV and text import detects likely Windows-1256 Arabic when invalid UTF-8 has Arabic byte runs; other invalid UTF-8 remains Windows-1252; HTML export places pictures and charts, as inline SVG, at their anchors; earlier: `file.exportCsv {values: true}` writes raw values for automation; .xls dialog sheets are skipped instead of read as worksheets; `.xls` import reads each sheet's display options from WINDOW2: right-to-left, gridlines, headings, zeros, formulas; XLSX text boxes and shapes keep their paragraphs' right-to-left direction on save; CSV/TSV/TXT can open with every field kept as text via `file.open {csvAsText}`; formulas over Excel's 8,192 characters, and `%` or call chains past the nesting limit, are parse errors instead of stack overflows when a file is opened; XLSX PivotTables read and write `rowGrandTotals`/`colGrandTotals` the way Excel does, so a table saved with only one grand total opens with the right one; HTML export keeps right-to-left; CSV and text import detects likely Windows-1256 Arabic when invalid UTF-8 has Arabic byte runs; other invalid UTF-8 remains Windows-1252; HTML export places pictures and charts, as inline SVG, at their anchors; CSV and text open detects each column's date order from values that read only one way, warns when every date is ambiguous, and keeps a column with both kinds as text (#2765)) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
|
||||
Every format Excel opens or saves, with GridCraft's read and write support, fidelity and tests.
|
||||
Part of [target-app-parity.md](target-app-parity.md). **File formats: ~50% ready, 80–120 h**
|
||||
@@ -155,6 +155,7 @@ refresh. Counted under features (Get & Transform, ~10% ready), not here.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | minor | XLSX `<workbookPr filterPrivacy="1"/>` (Remove personal information from file properties on save) is read and written back, and while it is on GridCraft saves no author (`dc:creator`) and no company, as Excel leaves them empty (#3089, file part) |
|
||||
| 2026-11-03 | minor | XLSX `<calcPr refMode="R1C1"/>` (a workbook saved in R1C1 reference style) is read into the calculation settings and written back; GridCraft itself still shows A1 references (#2809, file part) |
|
||||
| 2026-11-03 | minor | XLSX `pageMargins` are written as the decimals they were read or set as (`0.3`, as Excel writes them), not as the f64 widening of the stored f32 (`0.30000001192092896`) (#1609 item 7) |
|
||||
| 2026-11-02 | minor | Python cells: `.xlsx` reads and writes `_xlfn._xlws.PY(i,0 or 1)` with the code in the Python part (and reads the string-literal form) with cached values and spills, other formats adopt PY formulas, GridCraft JSON keeps the whole cell, and opening limits code size, result size and spill ranges |
|
||||
|
||||
Reference in new issue
Block a user