Skip to content

fix(xlsx): fix whitespace trimming bug introduced in #671#673

Merged
jmcnamara merged 2 commits into
tafia:masterfrom
dayongxie:fix/whitespace-trim-joined-text
Jul 6, 2026
Merged

fix(xlsx): fix whitespace trimming bug introduced in #671#673
jmcnamara merged 2 commits into
tafia:masterfrom
dayongxie:fix/whitespace-trim-joined-text

Conversation

@dayongxie

@dayongxie dayongxie commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

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 lacking xml:space="preserve". I overlooked that trim_text operates on each Text event independently, so when character references split text across multiple events, internal whitespace got wrongly eaten.

Concretely, for this XML:

<t>&#26085;&#26399; &#26102;&#38388;</t>

the parser emits three separate Text events:

  1. &#26085;&#26399;
  2. ← a single space
  3. &#26102;&#38388;

Since trim_text trims 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

  • Join all raw text events into a single string first.
  • Trim only ASCII whitespace (U+0020 SPACE, U+0009 TAB, U+000A LF, U+000D CR) from the joined result — Unicode spaces like U+00A0 (NBSP) are preserved.
  • Decode _xHHHH_ escapes after trimming, keeping x000D (\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.

⚠️ Also updated an existing test decode_xml_entities:
Changed the expected value for cell A2 from "\n" to "". A bare &#xA; (newline) without xml:space="preserve" is now correctly trimmed — this is the right behavior per the XML spec, just wasn't accounted for in the original test.

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).
@dayongxie dayongxie changed the title fix(xlsx): trim joined &lt;t&gt; text using ASCII whitespace only fix(xlsx): fix whitespace trimming bug introduced in #671 Jul 3, 2026
Comment thread src/xlsx/mod.rs Outdated
Comment on lines +2914 to +2916
value = value.trim_matches([' ', '\t', '\r', '\n']).to_owned();
}
value = unescape_xml(&value).into_owned();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()
                };

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks!

@jmcnamara jmcnamara self-assigned this Jul 5, 2026
@jmcnamara jmcnamara added next_release awaiting user changes Awaiting changes to a PR to fix requested changes or CI issues. labels Jul 5, 2026
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".

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (when xml: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.

Comment thread src/xlsx/mod.rs Outdated
Comment on lines +2912 to +2916
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();
@jmcnamara

jmcnamara commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

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.

@dayongxie

Copy link
Copy Markdown
Contributor Author

Sorry, I got the wrong branch. It's fixed now.

@jmcnamara jmcnamara merged commit bec70ca into tafia:master Jul 6, 2026
6 checks passed
@jmcnamara

Copy link
Copy Markdown
Collaborator

Merged. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting user changes Awaiting changes to a PR to fix requested changes or CI issues. next_release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants