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

+9 -4
View File
@@ -10,10 +10,15 @@ lookups preserve the previous analyst cohort and boundary source. Navigation
search keeps a successful geocode, with no search or view-based region recovery.
KML namespace repair locates the opening token once and scans its quoted tag
delimiter once. Malformed or unterminated tags fail before DOM parsing, so a file
of repeated incomplete `<kml` prefixes cannot trigger quadratic regex rescans.
Duplicate namespace repair and missing xsi declarations remain supported; GPX
continues through its existing XML parsing path.
delimiter once, skipping prolog and DOCTYPE comments and processing
instructions whole. The scanner is a boundary check only: an opening KML tag
that never closes, or one with raw markup starting inside it, fails before DOM
parsing, so a file of repeated incomplete `<kml` prefixes cannot trigger
quadratic regex rescans. All other XML grammar, including attribute syntax
inside a complete root tag, is left to DOMParser. Namespace declarations are
recognized only as whole root attributes, never inside quoted values; duplicate
namespace repair and missing xsi declarations remain supported. GPX continues
through its existing XML parsing path.
Terrain height requests accept at most 64 points, matching the browser service's
existing batch size and one bounded upstream chunk. Larger requests return HTTP
+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(
/(\/?)>$/,
+39 -1
View File
@@ -1197,7 +1197,11 @@ test('KML root namespace repair respects quoted delimiters and preserves the doc
test('repeated incomplete KML roots finish within an isolated process deadline', () => {
const source = `import { repairKmlRootNamespaces } from ${JSON.stringify(new URL('./geoFileImport.js', import.meta.url).href)};
try { repairKmlRootNamespaces('<kml '.repeat(1000000)); process.exit(1); }
catch (error) { if (error.name !== 'GeoImportError') throw error; }`;
catch (error) { if (error.name !== 'GeoImportError') throw error; }
try { repairKmlRootNamespaces('<!DOCTYPE kml [' + ${JSON.stringify("<!-- ' --><?pi ' ?>")}.repeat(100000) + ']><kml '); process.exit(1); }
catch (error) { if (error.name !== 'GeoImportError') throw error; }
const wide = '<kml' + ${JSON.stringify(` xmlns:gx="a" note='xmlns:gx="b"'`)}.repeat(20000) + '/>';
if (repairKmlRootNamespaces(wide).split('xmlns:gx="a"').length !== 2) process.exit(1);`;
const child = spawnSync(
process.execPath,
['--input-type=module', '-e', source],
@@ -1224,3 +1228,37 @@ test('KML namespace repair follows a doctype with a quoted root-like entity', ()
input.replace(' xmlns:gx="b"', ''),
);
});
test('KML namespace repair skips doctype comments and processing instructions whole', () => {
for (const prolog of [
"<!DOCTYPE kml [<!-- don't count this quote -->]>",
'<!DOCTYPE kml [<!-- a ] or > here --><!ENTITY x "y">]>',
"<!DOCTYPE kml [<?note it's ] > fine?>]>",
'<!DOCTYPE kml [<!ENTITY x "<!-- \' -->">]>',
]) {
const input = `${prolog}<kml xmlns:gx="a" xmlns:gx="b"><Placemark xsi:type="t"/></kml>`;
assert.equal(
repairKmlRootNamespaces(input),
input.replace(
' xmlns:gx="b">',
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">',
),
prolog,
);
}
const unterminated = '<!DOCTYPE kml [<!-- open <kml xmlns:gx="a">';
assert.equal(repairKmlRootNamespaces(unterminated), unterminated);
});
test('KML namespace repair ignores declaration text inside quoted attribute values', () => {
const input =
'<kml note=\'a > b xmlns:gx="fake"\' xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:gx="dup"><gx:Track/></kml>';
const result = repairKmlRootNamespaces(input);
assert.equal(result, input.replace(' xmlns:gx="dup"', ''));
assert.match(result, /\sxmlns:gx="http:\/\/www.google.com\/kml\/ext\/2.2"/);
const xsi = '<kml note="xmlns:xsi=\'x\'"><Placemark xsi:type="t"/></kml>';
assert.match(
repairKmlRootNamespaces(xsi),
/note="xmlns:xsi='x'" xmlns:xsi="http:\/\/www.w3.org\/2001\/XMLSchema-instance">/,
);
});