Repository navigation
fix(parser): preserve named and numeric character references - #1018
Merged
eszlamczyk merged 6 commits intoOct 16, 2026
Merged
Conversation
6 tasks
2 of 6 tasks
eszlamczyk
force-pushed
the
fix/text-character-references
branch
from
October 16, 2026 20:08
5e11543 to
8f557c5
Compare
…b parser appendCodepoint now maps zero, surrogates and anything above U+10FFFF to U+FFFD itself, so the named table and the numeric path share one guard and the UTF-8 encoder can never emit a sequence for an out-of-range value. The numeric path only validates the digits. The committed md4c.js is rebuilt with entity.c linked in; web dropped character references until this artifact caught up with the sources. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Swift package and the Android CMake targets set no C++ standard, so <charconv> is not guaranteed; the standalone iOS package failed to compile. md4c already caps numeric references at 7 decimal or 6 hex digits, so a plain digit loop cannot overflow and needs no library support. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
md4c hands attributes as typed substrings; getAttributeText copied the raw bytes, so `[q](/u?a=1&b=2)` decoded the text but kept `&` in the href. Entity segments now go through the same decoder as text. Include <cstdlib> before the extern "C" block so entity.h's stdlib include is a no-op there, and add a jest test that runs the committed web parser so a stale md4c.js fails CI. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Swift package's annotator and its tests assumed the shared core drops entity references, so a text node that now starts with a decoded entity failed to anchor and the slicer returned nil. The matcher already bridged one decoded scalar; it now bridges two for the handful of entities that decode to a pair, and the tests assert the decoded content with its source range spanning the reference. The entity branch moves into its own method to keep matchRemainder under the complexity limit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The vendored entity.c exported a bare entity_lookup, the same duplicate-symbol class #846 fixed for the parser: an app that also links md4c-html would fail to link. software-mansion-labs/md4c already ships the table as enrmrkd-entity.{c,h} with enrmrkd_entity_lookup, so fetch-md4c.sh syncs that instead and MD4CParser uses the prefixed names. The table itself is unchanged. test-core-parser.sh builds the table into the AST dumper, runs the prefix check over it, and links upstream md4c-html's entity.c into the coexistence binary. The golden dump is re-recorded: the only change is the decoded "&" in commonmark.md's "Term & entity" across its five variants.
eszlamczyk
force-pushed
the
fix/text-character-references
branch
from
October 16, 2026 20:33
8f557c5 to
0e30b3c
Compare
7 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What/Why?
The shared parser drops valid character references because
MD_TEXT_ENTITYis ignored, soTom & Jerryloses its ampersand. Named and numeric references are now decoded after md4c's syntax classification, so encoded punctuation stays literal text and cannot become Markdown syntax. The named lookup is md4c's own HTML5 entity table (upstream it belongs to md4c-html, not the parser), taken from our fork asenrmrkd-entity.{c,h}so it exportsenrmrkd_entity_lookuprather than a bareentity_lookup, and kept in sync byscripts/fetch-md4c.sh.Stacked on #922, which moves the parser to the
ENRMRKD_-prefixed fork; review and merge that first.This carries @Yash-Singh1's #994 (eb70edc) unchanged as the first commit plus one follow-up. #994 comes from an organization-owned fork, which GitHub does not let maintainers push to, so the follow-up lands here. #994 stays open for the discussion there and closes when this merges; both commits keep their authorship.
Closes #993.
Follow-up commits on top of #994
appendCodepointclamps zero, surrogates and anything above U+10FFFF to U+FFFD itself, so the named table and the numeric path share one guard and the UTF-8 encoder can never emit a sequence for an out-of-range value. The numeric path only validates the digits.md4c.jsis rebuilt with the entity table linked in. Web kept dropping references until this artifact caught up with the sources.std::from_chars.Package.swiftand the Android CMake targets set no C++ standard, and the standalone iOS package failed to compile on<charconv>. md4c caps numeric references at 7 decimal or 6 hex digits, so the loop cannot overflow.getAttributeTextcopied the raw bytes, so[q](/u?a=1&b=2)decoded the text but kept&in the href. That was already the case onmain; entity segments now go through the same decoder as text.<cstdlib>is included before theextern "C"block soentity.h's stdlib include is a no-op there.SourceOffsetAnnotatorand its tests assumed entities are dropped from rendered text, so CI'sios-unit-testsfailed once they decode. The matcher now bridges the one or two scalars an entity decodes to, and the tests assert the decoded content with its range spanning the source reference.enrmrkd-entity.{c,h}(enrmrkd_entity_lookup,ENRMRKD_ENTITY). Upstream'sentity.cexports a bareentity_lookup, which would reintroduce Isolate embedded MD4C symbols and parser headers for coexistence #846's duplicate-symbol failure for any app that also links md4c-html.scripts/test-core-parser.shbuilds the table, runs theenrmrkd_prefix check over it, and links upstream md4c-html'sentity.cinto the coexistence binary. The golden dump is re-recorded; its only change is the decoded&incommonmark.md'sTerm & entity.src/web/__tests__/parseMarkdown.test.ts, runs the committed web parser through named, numeric, two-codepoint, literal-preserving, U+FFFD, no-syntax-leak and attribute cases, so a stalemd4c.jsfails CI.Size
md4c's table is the cost of spec-compliant entity handling: about 54 KB per native arch at
-Os, and the web parser goes from 147 KB to 197 KB raw, 57 KB to 77 KB gzipped. A build flag was considered and rejected: entity references are CommonMark behavior, so the default would have to stay on, and the web build is a single committed artifact. If the web size matters later, the table representation can be compacted in a separate change.Testing
Tom & Jerry,© © ©and the two-codepoint⪢̸decode;&foo;,&without a semicolon,`&`and\&stay literal;� � �become U+FFFD.ParserTestson iPhone 17 / iOS 26.5: 18 of 18 pass. Jest web parser suite: 6 of 6 pass.PR Checklist
🤖 Generated with Claude Code