From 8ba1d9e362a786185c716650de82e406954b2d18 Mon Sep 17 00:00:00 2001 From: developer0hye Date: Sun, 9 Aug 2026 12:41:59 +0900 Subject: [PATCH 1/3] fix: read xf/@xfId so a cellXfs entry resolves against its own style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `CellFormat::set_attributes` read `numFmtId`, `fontId`, `fillId`, `borderId` and every `apply*` flag, but not `xfId`, even though the struct carries a `format_id` field for it and `write_to` writes it back out. `get_format_id()` therefore always returned 0, so `Stylesheet::make_style` paired every `cellXfs` entry with `cellStyleXfs[0]`. That default stand-in can veto the cell's own formatting: `get_style_by_cell_format` lets the referenced style turn a category off, so a workbook whose Normal style declares `applyNumberFormat="0"` — which Excel writes for plenty of templates — lost the number format on **every** cell. A percent cell read back its stored `0.25` instead of `25%`, and dates and currency went the same way. Reading it also stops a round trip from rewriting each `xfId` as 0. Signed-off-by: developer0hye --- src/structs/cell_format.rs | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/structs/cell_format.rs b/src/structs/cell_format.rs index 99f10945..5124c1c0 100644 --- a/src/structs/cell_format.rs +++ b/src/structs/cell_format.rs @@ -330,6 +330,11 @@ impl CellFormat { set_string_from_xml!(self, e, font_id, "fontId"); set_string_from_xml!(self, e, fill_id, "fillId"); set_string_from_xml!(self, e, border_id, "borderId"); + // `xfId` names the cellStyleXfs entry this cellXfs entry inherits + // from. Leaving it unread resolved every cell against cellStyleXfs[0], + // so a workbook whose Normal style sets applyNumberFormat="0" lost + // every number format, and a round trip rewrote each xfId as 0. + set_string_from_xml!(self, e, format_id, "xfId"); set_string_from_xml!(self, e, apply_number_format, "applyNumberFormat"); set_string_from_xml!(self, e, apply_border, "applyBorder"); set_string_from_xml!(self, e, apply_font, "applyFont"); @@ -425,3 +430,56 @@ impl CellFormat { } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn read_xf(xml: &str) -> CellFormat { + let mut reader = Reader::from_reader(std::io::BufReader::new(xml.as_bytes())); + let mut buf = Vec::new(); + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(ref e)) if e.name().into_inner() == b"xf" => { + let mut obj = CellFormat::default(); + obj.set_attributes(&mut reader, e, false); + return obj; + } + Ok(Event::Empty(ref e)) if e.name().into_inner() == b"xf" => { + let mut obj = CellFormat::default(); + obj.set_attributes(&mut reader, e, true); + return obj; + } + Ok(Event::Eof) => panic!("xf element not found"), + _ => (), + } + buf.clear(); + } + } + + // `xfId` names the cellStyleXfs entry a cellXfs entry inherits from. It was + // written back on save but never read, so every cell resolved against + // cellStyleXfs[0]: a workbook whose Normal style carries + // applyNumberFormat="0" lost every number format, and a round trip + // rewrote each xfId as 0. + #[test] + fn reads_the_xf_id() { + let obj = read_xf(r#""#); + assert_eq!(obj.get_format_id(), 6); + assert_eq!(obj.get_number_format_id(), 9); + } + + #[test] + fn an_absent_xf_id_stays_zero() { + let obj = read_xf(r#""#); + assert_eq!(obj.get_format_id(), 0); + } + + #[test] + fn reads_a_non_zero_xf_id_from_a_non_empty_element() { + let obj = read_xf( + r#""#, + ); + assert_eq!(obj.get_format_id(), 42); + } +} From dbf65dbb2606d4827f631da662cf5aa83e571528 Mon Sep 17 00:00:00 2001 From: developer0hye Date: Mon, 10 Aug 2026 04:06:23 +0900 Subject: [PATCH 2/3] build: call format! with parentheses in the csv writer Current nightly clippy denies `nonstandard_macro_braces` for `format!`, so `cargo clippy -- -D warnings` fails on master: error: use of irregular braces for `format!` macro --> src/writer/csv.rs:47:25 That aborts the build matrix before the other toolchains finish, so every open pull request reads as red regardless of its own contents. Switching to parentheses is the fix clippy suggests; rustfmt then wraps the arguments, which the brace form had exempted it from. No behaviour change. Signed-off-by: developer0hye --- src/writer/csv.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/writer/csv.rs b/src/writer/csv.rs index 89cdc523..8bb21a7d 100644 --- a/src/writer/csv.rs +++ b/src/writer/csv.rs @@ -44,7 +44,12 @@ pub fn write_writer( } // wrap_with_char. if option.wrap_with_char() != "" { - value = format! {"{}{}{}", option.wrap_with_char(), value, option.wrap_with_char()}; + value = format!( + "{}{}{}", + option.wrap_with_char(), + value, + option.wrap_with_char() + ); } row_vec.push(value); } From 0dcdf9dd088d8a6297dce2ddc83ab3716897c01d Mon Sep 17 00:00:00 2001 From: developer0hye Date: Mon, 10 Aug 2026 04:06:28 +0900 Subject: [PATCH 3/3] test: read the xf ids through the non-deprecated accessors `get_format_id` and `get_number_format_id` are deprecated since 3.0.0 in favour of `format_id` and `number_format_id`, so the new tests compiled with four `deprecated` warnings. Use the current names. Signed-off-by: developer0hye --- src/structs/cell_format.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/structs/cell_format.rs b/src/structs/cell_format.rs index 5124c1c0..931f2fa2 100644 --- a/src/structs/cell_format.rs +++ b/src/structs/cell_format.rs @@ -465,14 +465,14 @@ mod tests { #[test] fn reads_the_xf_id() { let obj = read_xf(r#""#); - assert_eq!(obj.get_format_id(), 6); - assert_eq!(obj.get_number_format_id(), 9); + assert_eq!(obj.format_id(), 6); + assert_eq!(obj.number_format_id(), 9); } #[test] fn an_absent_xf_id_stays_zero() { let obj = read_xf(r#""#); - assert_eq!(obj.get_format_id(), 0); + assert_eq!(obj.format_id(), 0); } #[test] @@ -480,6 +480,6 @@ mod tests { let obj = read_xf( r#""#, ); - assert_eq!(obj.get_format_id(), 42); + assert_eq!(obj.format_id(), 42); } }