Skip to content

fix(parser): decode numeric character references instead of re-escaping them - #14

Merged
kklimuk merged 2 commits into
mainfrom
claude/triage-issue-12-numeric-char-refs
Oct 8, 2026
Merged

kklimuk merged 2 commits into
mainfrom
claude/triage-issue-12-numeric-char-refs

Conversation

@kklimuk

@kklimuk kklimuk commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

🤖 docx-cli triage

What was wrong

Numeric character references were never decoded on parse, so any save re-escaped their &. docx replace refs.docx Alpha Beta turned <w:lvlText w:val="&#8226;"/> into w:val="&amp;#8226;" and &#8226; &#x2013; done into &amp;#8226; &amp;#x2013; done, so Word showed the reference text instead of •. read also showed \&#8226;.

What changed

  • XmlNode parse sets fast-xml-parser's htmlEntities: {}, which decodes numeric references once (&amp;#8226; still reads as the literal text &#8226;). The empty named-entity table means HTML-only names like &nbsp;, which XML doesn't define, stay literal instead of being silently rewritten. The object form is implemented but typed boolean, hence a narrow cast; a test fails if an upgrade drops it.
  • Build-side escaping moved into the value processors. Attribute values now write tab/LF/CR back out as &#9;/&#10;/&#13;, so a decoded alt-text line break (descr="a&#xA;b") isn't normalized to a space on the next read. Text escaping produces the same bytes as before.
  • The SVG sanitizer's isAllowedUrl now strips tab/CR/LF before checking the scheme, the way browsers do. Without this, java&#9;script: would get past it now that attribute tabs are kept as references.
  • Tests: parser unit tests (decode, decode once, HTML names stay literal, attribute whitespace), CLI invariant tests (body text and numbering.xml lvlText survive a replace), and an SVG sanitizer regression test.

Verification

  • Before: read → \&#8226; \&#x2013; done, and the saved XML had &amp;#8226;. After: read → • – done, the saved files have <w:lvlText w:val="•"/> and • – done, and there's no &amp;#.
  • biome ✅ knip ✅ tsc ✅ bun run test:unit ✅ (1713 tests). The pre-commit hook passed on the latest commit.
  • /simplify: flagged the attribute-whitespace round-trip, now fixed. /code-review xhigh: caught the SVG java<TAB>script: bypass that change opened, and replaced a NUL-marker escaping trick with plain value-processor escaping. Both fixed.

Decisions (settled)

  • Decode numeric references only; HTML named entities stay literal (htmlEntities: {}).
  • Leave the two whitespace gaps (a raw newline/tab written directly into an attribute; &#13; in text). A scan of all 36 repo .docx files (493 parts) found zero instances of either. The case this PR does handle, Word's default alt text descr="…&#xA;&#xA;Description automatically generated", appears 31 times.

Fixes #12

🤖 Generated with Claude Code

https://claude.ai/code/session_012GoxD36rnNfg6uArkVxtJL


Generated by Claude Code

…ng them

A `&#8226;` / `&#x2013;` anywhere in a part (a numbering `w:lvlText`, a run's
text) survived parsing as literal text, so the builder escaped its `&` on the
next save and Word rendered the reference itself. Parse with `htmlEntities`
so references decode once; escape on the build side in the value processors,
writing attribute tab/LF/CR back as references so a decoded alt-text line
break isn't normalized to a space. The SVG sanitizer now strips tab/CR/LF
before classifying a URL scheme, as browsers do.

Fixes #12

Triage-Routine: true
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GoxD36rnNfg6uArkVxtJL
@kklimuk

kklimuk commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

🤖 docx-cli triage

Opened this for #12: numeric character references (&#8226;) now decode on read instead of being re-escaped into literal text on every save. Unit tests pass (1712) and biome/knip/tsc are clean. Two product questions are in the description.

Action needed: approve/merge — review the diff and the two decision checkboxes in the description, then merge #14.


Generated by Claude Code

… literal

`htmlEntities: {}` turns on numeric character-reference decoding with an
empty named-entity table, so `&nbsp;`/`&copy;` (undefined in XML) stay
literal instead of being silently rewritten. Per maintainer decision on #14.

Triage-Routine: true
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GoxD36rnNfg6uArkVxtJL
@kklimuk
kklimuk merged commit 225e28b into main Oct 8, 2026
8 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.

Any write turns numeric character references (&#8226;, &#x2013;) into literal text

2 participants