Repository navigation
fix(diagrams): preserve literal SVG comments in CDATA (FU-1015) #1835
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a3a1eab
417eec2
9208233
9cf8738
fc1d466
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,21 @@ | ||
| // Separate from export-svg.mjs so tests can import it without Chromium or argv. | ||
| export function stripComments(s) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Before accepting or merging this Tier-B change, obtain and record the required second independent review. The commit’s review disposition names only one non-author reviewer ( AGENTS.md reference: AGENTS.md:L28-L33 Useful? React with 👍 / 👎. |
||
| const parts = []; | ||
| let start = 0; | ||
| // Consume processing instructions and comments before interpreting CDATA. | ||
| for (const match of s.matchAll(/<\?[\s\S]*?\?>|<!\[CDATA\[[\s\S]*?\]\]>|<!--(?:[\s\S]*?-->)?/g)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the input is a valid SVG with an internal DTD subset, such as AGENTS.md reference: AGENTS.md:L110-L112 Useful? React with 👍 / 👎. |
||
| if (match[0].startsWith('<!--')) continue; | ||
| parts.push(stripMarkupComments(s.slice(start, match.index)), match[0]); | ||
| start = match.index + match[0].length; | ||
| } | ||
| parts.push(stripMarkupComments(s.slice(start))); | ||
| return parts.join(''); | ||
| } | ||
|
|
||
| // The optional group also removes an unterminated `<!--`; repeating until the | ||
| // string is stable catches openers that a removed comment re-assembled | ||
| // (`<!<!---->-->`), so none survives (CodeQL js/incomplete-multi-character-sanitization). | ||
| export function stripComments(s) { | ||
| function stripMarkupComments(s) { | ||
| let prev; | ||
| do { prev = s; s = s.replace(/<!--(?:[\s\S]*?-->)?/g, ''); } while (s !== prev); | ||
| return s; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { test } from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
| import { stripComments } from './strip-comments.mjs'; | ||
|
|
||
| test('stripComments treats processing instructions as opaque before CDATA', () => { | ||
| const cases = [ | ||
| [ | ||
| '<svg><?probe <![CDATA[ ?><!-- remove --><![CDATA[x]]></svg>', | ||
| '<svg><?probe <![CDATA[ ?><![CDATA[x]]></svg>', | ||
| ], | ||
| [ | ||
| '<svg><?probe <!-- literal --> ?><![CDATA[<!-- keep -->]]><!-- remove --></svg>', | ||
| '<svg><?probe <!-- literal --> ?><![CDATA[<!-- keep -->]]></svg>', | ||
| ], | ||
| [ | ||
| '<svg><!-- <?probe <![CDATA[ ?> --><![CDATA[<?probe <!-- keep --> ?>]]></svg>', | ||
| '<svg><![CDATA[<?probe <!-- keep --> ?>]]></svg>', | ||
| ], | ||
| ]; | ||
| for (const [input, expected] of cases) assert.equal(stripComments(input), expected); | ||
| }); | ||
|
|
||
| test('stripComments preserves literal comment text in CDATA', () => { | ||
| const cases = [ | ||
| [ | ||
| '<svg><!-- remove --><text><![CDATA[A <!-- literal --> B]]></text><!-- remove too --></svg>', | ||
| '<svg><text><![CDATA[A <!-- literal --> B]]></text></svg>', | ||
| ], | ||
| [ | ||
| '<svg><text><![CDATA[<!-- unfinished]]></text><!-- remove --></svg>', | ||
| '<svg><text><![CDATA[<!-- unfinished]]></text></svg>', | ||
| ], | ||
| [ | ||
| '<svg><text><![CDATA[<!<!---->-->]]></text></svg>', | ||
| '<svg><text><![CDATA[<!<!---->-->]]></text></svg>', | ||
| ], | ||
| [ | ||
| '<svg><!-- <![CDATA[hidden]]> --><text><![CDATA[<!-- keep -->]]></text></svg>', | ||
| '<svg><text><![CDATA[<!-- keep -->]]></text></svg>', | ||
| ], | ||
| [ | ||
| '<svg><text><![CDATA[left <!-- a -->]]><![CDATA[<!-- b --> right]]></text></svg>', | ||
| '<svg><text><![CDATA[left <!-- a -->]]><![CDATA[<!-- b --> right]]></text></svg>', | ||
| ], | ||
| ]; | ||
| for (const [input, expected] of cases) assert.equal(stripComments(input), expected); | ||
| }); | ||
|
|
||
| test('stripComments retains removal behavior outside CDATA', () => { | ||
| const cases = [ | ||
| ['', ''], | ||
| ['<svg/>', '<svg/>'], | ||
| ['<svg><!-- one\n two --><text><!-- literal --></text></svg>', '<svg><text><!-- literal --></text></svg>'], | ||
| ['<!<!---->-->', '>'], | ||
| ['<!<!---->--', ''], | ||
| ['<!--<!-- a -->', ''], | ||
| ['<<!---->!-- x -->', ''], | ||
| ['a<!-- never closed', 'a never closed'], | ||
| ['<![C<!---->DATA[<!<!---->--x]]>', '<![CDATA[x]]>'], | ||
| ]; | ||
| for (const [input, expected] of cases) assert.equal(stripComments(input), expected); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When this implementation lands,
docs/PLAN.md:365will still describe FU-1015 as “Geplant: nicht disponiert,” even though this commit implements its #1581 K1 follow-up. Update the row in this commit so the canonical plan reflects the post-merge state; the repository explicitly treats this mismatch as a finding that must be resolved before acceptance.AGENTS.md reference: AGENTS.md:L139-L139
Useful? React with 👍 / 👎.