Skip DOCTYPE comments/PIs and match root namespaces only as attributes

- The DOCTYPE scan now jumps over whole comments and processing
  instructions found outside a declaration string literal, so a quote or
  bracket inside them no longer leaves the scanner stuck before the root.
- Root namespace repair walks the root's attributes in order, so
  declaration-like text inside a quoted value can no longer fill `seen`
  and cause the real declaration to be dropped as a duplicate.
- Regression tests for both, plus linear-time cases in the isolated
  deadline test.
- CURRENT-STATE: describe the scanner as a boundary check and leave the
  rest of the XML grammar to DOMParser.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-23 12:30:32 +01:00
1 parent 5ca2266f53
commit 6b2aa95cc6
3 files changed
+76 -16

No files matched your search

+28 -11
View File
@@ -1058,7 +1058,17 @@ export function repairKmlRootNamespaces(value, what = 'KML') {
if (quote) {
if (char === quote) quote = null;
} else if (char === '"' || char === "'") quote = char;
else if (char === '[') subsetDepth++;
else if (char === '<' && text.startsWith('<!--', end)) {
// Comments and processing instructions inside the internal subset
// carry no declaration syntax; jump past them as a whole.
const skip = text.indexOf('-->', end + 4);
if (skip < 0) return text;
end = skip + 2;
} else if (char === '<' && text.startsWith('<?', end)) {
const skip = text.indexOf('?>', end + 2);
if (skip < 0) return text;
end = skip + 1;
} else if (char === '[') subsetDepth++;
else if (char === ']') subsetDepth--;
else if (char === '>' && subsetDepth === 0) break;
}
@@ -1098,17 +1108,24 @@ export function repairKmlRootNamespaces(value, what = 'KML') {
msg('That {format} file is not valid XML.'),
{ format: what },
);
// Walk the root's attributes in order so quoted values are never read as
// declarations. Anything the walk cannot tokenize is kept verbatim for the
// XML parser to judge.
const seen = new Set();
let root = text
.slice(start, end)
.replace(
/\sxmlns:([\w.-]+)\s*=\s*(["'])[^"']*\2/g,
(declaration, prefix) => {
if (seen.has(prefix)) return '';
seen.add(prefix);
return declaration;
},
);
const attribute = /(\s+)([^\s=/>"']+)\s*=\s*("[^"]*"|'[^']*')/y;
let position = start + match[0].length;
let root = text.slice(start, position);
for (;;) {
attribute.lastIndex = position;
const found = attribute.exec(text);
if (!found || attribute.lastIndex > end) break;
position = attribute.lastIndex;
const prefix = /^xmlns:([\w.-]+)$/.exec(found[2])?.[1];
if (prefix && seen.has(prefix)) continue;
if (prefix) seen.add(prefix);
root += found[0];
}
root += text.slice(position, end);
if (!seen.has('xsi') && /[<\s]xsi:/.test(text))
root = root.replace(
/(\/?)>$/,