Skip to content

Resolve worksheet namespace prefixes in streaming readers - #371

Merged
MathNya merged 1 commit into
MathNya:masterfrom
developer0hye:fix/worksheet-namespace-prefixes
Oct 1, 2026
Merged

MathNya merged 1 commit into
MathNya:masterfrom
developer0hye:fix/worksheet-namespace-prefixes

Conversation

@developer0hye

Copy link
Copy Markdown
Contributor

Summary

Resolve SpreadsheetML namespace prefixes before dispatching worksheet elements to the existing sub-readers. A worksheet using s:row, s:c and prefixed header/footer elements currently reads successfully but loses its content. The equivalent default-namespace worksheet works.

Related: #370, developer0hye/office2pdf#1803.

A streaming NsReader adapter presents the existing sub-readers with their expected unqualified SpreadsheetML names. It resolves each element in scope, retains qualified foreign names, and gives foreign-default-namespace elements an internal prefix so their local names cannot be confused with worksheet elements. The original stored XML is unchanged. Normal, lazy/lite and callback-streaming reads use the adapter. No dependencies or public APIs change.

Tests

  • Red-first synthetic workbook tests: default namespace passes on 35bbffa4; both s: and spreadsheet: variants lose A1.
  • Text/numeric cells, bold formatting, header/footer text, deferred in-memory reads, lazy file reads, cell streaming, and write/reopen round trips.
  • Nested aliases and prefix rebinding, foreign default namespaces, entity/CDATA preservation, and bounded first-event buffering on a 20,000-row XML input.
  • cargo test --locked: 347 passed; 23 existing ignored tests.
  • cargo clippy --locked --test worksheet_namespaces -- -D warnings: passed, including the library.
  • cargo +nightly-2026-09-11 fmt --all -- --check and git diff --check: passed.

Performance tradeoff

The adapter preserves streaming memory behavior but adds a namespace-resolution/serialization pass before the existing parser. An optimized public-API probe with 20,000 rows / 40,000 cells measured these median reader times across six fresh-process runs per variant and mode, interleaved in alternating order:

Read mode Unchanged upstream Patch
Normal 80.237 ms 81.509 ms
Cell streaming 53.636 ms 71.939 ms

That is approximately 1.6% and 34.1% slower respectively on this fixture. Measurements were on an Apple M2 with 16 GB RAM under variable host load; every repetition, load observation and process resource report was retained. These are host observations, not a performance guarantee. Baseline and patched executables had distinct SHA-256 hashes; the baseline loses all cells on the prefixed benchmark while the patch preserves all 40,000.

Using an adapter keeps namespace handling consistent through nested worksheet readers without changing their shared Reader signatures. The additional streaming-read cost is a tradeoff of that bounded approach.

Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
@MathNya

MathNya commented Sep 23, 2026

Copy link
Copy Markdown
Owner

@developer0hye
Thank you for the PR.
I'll review the changes. It will take a little while.

developer0hye added a commit to developer0hye/office2pdf that referenced this pull request Sep 27, 2026
A worksheet may bind the SpreadsheetML URI to a prefix instead of making
it the default namespace. Excel writes and accepts both spellings, and
the two packages are equivalent XML: identical expanded element names,
attributes and text.

The dependency's worksheet reader matched literal `e.name()` values such
as `row` and `headerFooter`, so `s:row` and `s:headerFooter` matched
nothing. Every sheet element was skipped, the converter reported success,
and the page came out blank — body cells, header and footer all gone.

The fix is a streaming `NsReader` adapter in the dependency
(developer0hye/umya-spreadsheet#14, upstream MathNya/umya-spreadsheet#371)
that resolves each event's namespace and hands the legacy sub-readers the
unqualified local name when, and only when, the URI is SpreadsheetML.
Elements in a foreign namespace take an internal prefix so the legacy
parser cannot mistake their local names for worksheet elements. It buffers
one event, so worksheet streaming is preserved, and the stored source XML
is untouched. Only the lock's `source` line moves; the fork delta over the
previous pin is that adapter, its wiring and its tests.

The regression test pins the rule rather than the reported input: it reads
the committed `s:`-prefixed fixture and four more prefixes generated from
the default-namespace control, including `row:`, which a substring-matching
reader would still get wrong. Both the whole-document and the streaming
parse paths are checked for every body value, the header, and the footer's
three sections with their page-number fields.

The prefixed and default-namespace packages now convert to a
byte-identical PDF, and the default-namespace output is unchanged.

Related: #1803

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
@MathNya

MathNya commented Oct 1, 2026

Copy link
Copy Markdown
Owner

@developer0hye
Thank you for your patience.
I’ll go ahead and merge this.
This fix is necessary.
As for the impact on response times, I’ll address that separately once I find a good solution.

@MathNya
MathNya merged commit c02b9cd into MathNya:master Oct 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants