Files
rea/src/domain/xmlDocument.ts
T
morluto 623339a7ab refactor(xml): centralize strict document parsing
Own syntax validation and source-located DOM projection together. Preserve explicit consumer policies and remove duplicated warning-message filters from Apktool and SAZ.

Validation: 92 focused checks, 3 selected fake Apktool boundary checks, check:fast and docs:check. No real Apktool or Ghidra execution.

Refs #4694
2026-10-17 15:16:24 +08:00

63 lines
1.8 KiB
TypeScript

import {
DOMParser,
normalizeLineEndings,
onErrorStopParsing,
type Document,
} from "@xmldom/xmldom";
import { SaxesParser } from "saxes";
import { err, ok, type Result } from "./result.js";
export class XmlDocumentParseError extends Error {
readonly _tag = "XmlDocumentParseError";
constructor(
readonly reason: "malformed" | "doctype",
cause?: unknown,
) {
super(
reason === "doctype"
? "XML document types are unsupported"
: `XML is not well-formed${cause instanceof Error ? `: ${cause.message}` : ""}`,
{ cause },
);
}
}
/** One boundary owns strict XML syntax and the source-located DOM projection. */
export const parseXmlDocument = (
source: string,
policy: {
readonly doctype: "allow" | "reject";
readonly lineEndings: "preserve" | "normalize";
},
): Result<Document, XmlDocumentParseError> => {
try {
const syntax = new SaxesParser({
defaultXMLVersion: "1.0",
forceXMLVersion: true,
});
syntax.on("doctype", () => {
if (policy.doctype === "reject")
throw new XmlDocumentParseError("doctype");
});
syntax.write(source).close();
// Saxes owns well-formedness outside DTDs. The DOM parser additionally
// checks DTD syntax and builds source-located nodes; its advisory warnings
// (including literal U+FFFD) are not byte-decoding or syntax failures.
return ok(
new DOMParser({
normalizeLineEndings:
policy.lineEndings === "preserve"
? (text) => text
: normalizeLineEndings,
onError: onErrorStopParsing,
}).parseFromString(source, "application/xml"),
);
} catch (cause: unknown) {
return err(
cause instanceof XmlDocumentParseError
? cause
: new XmlDocumentParseError("malformed", cause),
);
}
};