Skip to content

Unwrap CDATA sections in entity replacement text, not in the expanded text - #40

Merged
FlorianRappl merged 1 commit into
AngleSharp:develfrom
michaelthyregod:fix/cdata-in-entities
Nov 1, 2026
Merged

FlorianRappl merged 1 commit into
AngleSharp:develfrom
michaelthyregod:fix/cdata-in-entities

Conversation

@michaelthyregod

Copy link
Copy Markdown
Contributor

Timing: I have a change coming for AngleSharp core, as part of a fix for the AngleSharp.Css project. I'm running its benchmarks before I can open that PR, so this may need to wait for that core change as well.

Not benchmarked yet: I haven't run the benchmarks for this PR. I'll run them once the core change is settled, since it may affect the results.

Builds on #38 (branched from it; until #38 is merged, this PR also shows its commits).

Problem

When a document declared any entity, the fallback expansion replaced <!\[CDATA\[(.*?)\]\]> in the whole text of a character token. That caused two problems:

  • Escaped text was rewritten: a &lt;![CDATA[b]]&gt; c became a b c. Escaped text and CDATA have to stay distinguishable (Preserve CDATA sections as first-class DOM nodes #30).
  • Quadratic time: many unterminated &lt;![CDATA[ made the regex scan to the end from each one. 240 KB took 33 s; without a DOCTYPE the same text takes 43 ms.

Change

The regex exists for XmlValidSa114, where an entity's replacement text holds a CDATA section (<!ENTITY e "<![CDATA[&foo;]]>"> must give &foo;). So:

  • CDATA sections are now unwrapped in each entity's replacement text, once, in a single pass, with the same matching as the regex.
  • That text is used where an entity contributes to element content: in the tokenizer and in nested expansion.
  • Attribute values keep the replacement text as before.

XmlValidSa114 still passes. The 240 KB case takes 13 ms.

Evidence

A differential fuzz of 60,000 parse results against the previous code (entities with CDATA, nesting, escaped text, attributes, strict and recovery modes): 43,711 are identical, and every difference falls into one of these categories:

Category Results Result
Escaped CDATA-looking text in content 15,279 the fix
CDATA section split across entities 398 not well-formed
Recursive entities 325 not well-formed
A CDATA entity whose content references another CDATA entity 287 the previous code left <![CDATA[ markers in the text

New tests in XmlEntityCDataSections. The previous code fails only the escaped-text test. 607 tests pass.

🤖 Generated with Claude Code

@FlorianRappl FlorianRappl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Too many changes.

… text

The fallback expansion replaced <!\[CDATA\[(.*?)\]\]> in the whole text of a
character token whenever the document declared any entity. That stripped
the markers from escaped text such as &lt;![CDATA[b]]&gt; (giving 'b'), and
took quadratic time on many unterminated starts: 240 KB of &lt;![CDATA[
took 33 s.

The CDATA sections of each entity's replacement text are now unwrapped
once, in a single pass, when the entity is registered, and that text is
used where an entity contributes to element content (tokenizer and
nested expansion). Attribute values keep the replacement text as before.

A 60,000-result differential fuzz against the previous code differs only
for escaped CDATA-looking text (the fix), CDATA sections split across
entities, recursive entities, and CDATA entities whose content references
another CDATA entity, where the previous code left CDATA markers behind.
@michaelthyregod

Copy link
Copy Markdown
Contributor Author

#38 is merged now and this is rebased onto devel, so it only shows its own change: 1 commit, 4 files (+138/-14).

@FlorianRappl FlorianRappl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@FlorianRappl
FlorianRappl merged commit 42e35b4 into AngleSharp:devel Nov 1, 2026
5 checks passed
@michaelthyregod
michaelthyregod deleted the fix/cdata-in-entities branch November 3, 2026 06:10
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