fix(xlsx): fix whitespace trimming bug introduced in #671#673
Conversation
Join all text events within <t> before trimming, instead of trimming each quick-xml Text event separately. This preserves legitimate internal spaces when XML entities split text across events, while still removing leading/trailing ASCII whitespace (space/tab/CR/LF only). Also keeps literal x000D escapes intact during trimming and avoids stripping Unicode spaces such as U+00A0 (NBSP).
| value = value.trim_matches([' ', '\t', '\r', '\n']).to_owned(); | ||
| } | ||
| value = unescape_xml(&value).into_owned(); |
There was a problem hiding this comment.
There are two potential allocations here. It would be better, since this in on the hot path to do it like this:
let value = if preserve_space {
unescape_xml(&value).into_owned()
} else {
// Trim ASCII whitespace (space/tab/CR/LF). Unicode spaces
// such as U+00A0 (NBSP) are preserved.
unescape_xml(value.trim_matches([' ', '\t', '\r', '\n'])).into_owned()
};Apply reviewer feedback from tafia#673: trim the accumulated text via (which operates on a borrow, no allocation) before passing it to , so is called only once. Previously the code called after trimming and then again after unescaping — two heap allocations on the hot path for every <t> element without xml:space="preserve".
There was a problem hiding this comment.
Pull request overview
Fixes a whitespace-trimming regression in XLSX string parsing introduced by enabling per-Text-event trimming, which incorrectly removed internal whitespace when character references caused the XML parser to split text into multiple events.
Changes:
- Accumulate all
Text/CData/entity content for a<t>element into one string, then trim XML whitespace at the boundaries (whenxml:space!="preserve"), and finally unescape Excel_x00HH_escapes. - Add regression tests to ensure internal literal spaces between character references are preserved for both inline strings and shared strings.
- Update the whitespace-trim XLSX fixture to include the new edge case.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/xlsx/mod.rs |
Fixes the trimming logic to operate on the joined <t> content instead of per text event, preventing loss of internal whitespace. |
tests/test.rs |
Adds/updates assertions to cover the regression case (internal space between character references) and adjusts an expectation for trimmed edge-newline behavior. |
tests/whitespace_trim.xlsx |
Updates the fixture workbook used by whitespace-trimming tests to include the new regression scenario. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if !preserve_space { | ||
| xml.config_mut().trim_text(false); | ||
| // Trim only ASCII whitespace, keeping Unicode spaces like U+00A0 | ||
| value = value.trim_matches([' ', '\t', '\r', '\n']).to_owned(); | ||
| } | ||
| value = unescape_xml(&value).into_owned(); |
|
Sorry. I kicked off the GitHub review by accident. It seems to be making a similar comment to mine. In your comment to my review you said it was fixed but I don't see that commit here (which is probably why the agent is also complaining). It looks like you may have put the fix on the wrong branch. |
|
Sorry, I got the wrong branch. It's fixed now. |
|
Merged. Thanks. |
Apologies — #671 introduced a subtle bug. This PR fixes it.
The mistake
#671 used quick-xml's
trim_text(true)to strip whitespace from<t>elements lackingxml:space="preserve". I overlooked thattrim_textoperates on each Text event independently, so when character references split text across multiple events, internal whitespace got wrongly eaten.Concretely, for this XML:
the parser emits three separate Text events:
日期← a single space时间Since
trim_texttrims each event in isolation, event #2 was stripped entirely, yielding"日期时间"instead of the correct"日期 时间".Truly sorry for the sloppy review — I should have tested this edge case.
The fix
_xHHHH_escapes after trimming, keepingx000D(\r) literal during trim so it doesn't get stripped.This also eliminates the
trim_text(true)/trim_text(false)toggle on the shared reader config, which was brittle.Changes
src/xlsx/mod.rs— accumulate raw text, then ASCII-trim the joined result before unescaping.tests/test.rs— added new assertions for internal space between character references in both inline and shared string tests.tests/whitespace_trim.xlsx— updated fixture with the new test case.