diff --git a/src/saxes.ts b/src/saxes.ts index c0c2ed61..9e9067c9 100644 --- a/src/saxes.ts +++ b/src/saxes.ts @@ -175,6 +175,7 @@ const FORBIDDEN_BRACKET_BRACKET = 2; * The list of supported events. */ export const EVENTS = [ + "xmldecl", "text", "processinginstruction", "doctype", @@ -190,6 +191,7 @@ export const EVENTS = [ ] as const; const EVENT_NAME_TO_HANDLER_NAME: Record = { + xmldecl: "xmldeclHandler", text: "textHandler", processinginstruction: "piHandler", doctype: "doctypeHandler", @@ -204,6 +206,14 @@ const EVENT_NAME_TO_HANDLER_NAME: Record = { ready: "readyHandler", }; +/** + * Event handler for the + * + * @param text The text data encountered by the parser. + * + */ +export type XMLDeclHandler = (decl: XMLDecl) => void; + /** * Event handler for text data. * @@ -298,6 +308,7 @@ export type ErrorHandler = (err: Error) => void; export type EventName = (typeof EVENTS)[number]; export type EventNameToHandler = { + "xmldecl": XMLDeclHandler; "text": TextHandler; "processinginstruction": PIHandler; "doctype": DoctypeHandler; @@ -347,7 +358,7 @@ export interface SaxesAttributeNS { * prior to the URI being resolvable. This is what is passed to the attribute * event handler. */ -export type SaxesAttributeNSIncomplete = Exclude; +export type SaxesAttributeNSIncomplete = Exclude; /** * This interface defines the structure of attributes when the parser is @@ -612,6 +623,7 @@ export class SaxesParser { private _closed!: boolean; private currentXMLVersion!: string; private readonly stateTable: ((this: SaxesParser) => void)[]; + private xmldeclHandler?: XMLDeclHandler; private textHandler?: TextHandler; private piHandler?: PIHandler; private doctypeHandler?: DoctypeHandler; @@ -1951,6 +1963,8 @@ export class SaxesParser { this.xmlDeclExpects.includes("version")) { this.fail("XML declaration must contain a version."); } + // eslint-disable-next-line no-unused-expressions + this.xmldeclHandler?.(this.xmlDecl); this.name = ""; this.piTarget = this.text = ""; this.state = S_TEXT; diff --git a/test/dtd.ts b/test/dtd.ts index 99e23e2b..f9ce10ac 100644 --- a/test/dtd.ts +++ b/test/dtd.ts @@ -10,6 +10,14 @@ describe("dtd", () => { ]> `, expect: [ + [ + "xmldecl", + { + encoding: "UTF-8", + version: "1.0", + standalone: undefined, + }, + ], ["text", "\n"], ["doctype", ` root [ @@ -30,6 +38,14 @@ describe("dtd", () => { ]> `, expect: [ + [ + "xmldecl", + { + encoding: "UTF-8", + version: "1.0", + standalone: undefined, + }, + ], ["text", "\n"], ["doctype", ` root [ @@ -50,6 +66,14 @@ describe("dtd", () => { ]> `, expect: [ + [ + "xmldecl", + { + encoding: "UTF-8", + version: "1.0", + standalone: undefined, + }, + ], ["text", "\n"], ["doctype", ` root [ "> diff --git a/test/eol-handling.ts b/test/eol-handling.ts index 5172efde..90f50ce3 100644 --- a/test/eol-handling.ts +++ b/test/eol-handling.ts @@ -20,6 +20,14 @@ describe("eol handling", () => { /* eslint-enable linebreak-style */ const expect = [ + [ + "xmldecl", + { + encoding: "utf-8", + version: "1.0", + standalone: undefined, + }, + ], ["text", "\n\n"], ["opentagstart", { name: "moo", attributes: {} }], ["attribute", { @@ -124,6 +132,14 @@ a const xmlDeclEnd = nl.indexOf("?>"); const expect = [ + [ + "xmldecl", + { + encoding: "utf-8", + version: "1.0", + standalone: "no", + }, + ], ["text", "\n"], ["doctype", ` root @@ -200,7 +216,12 @@ abc isSelfClosing: false, }], ["text", "\n"], - ]; + ] as const; + + const fixed11 = + expect.map(x => (x[0] === "xmldecl" ? + [x[0], { ...x[1], version: "1.1" }] : x)); + describe("nl", () => { test({ @@ -266,17 +287,17 @@ abc describe("nel", () => { // We have to switch the EOL characters after the XML declaration. const nel = nl.slice(0, xmlDeclEnd).replace("1.0", "1.1") + - nl.slice(xmlDeclEnd).replace(/\n/g, "\u0085"); + nl.slice(xmlDeclEnd).replace(/\n/g, "\u0085"); test({ name: "one chunk", xml: nel, - expect, + expect: fixed11, }); test({ name: "char-by-char", - expect, + expect: fixed11, fn(parser: SaxesParser): void { for (const x of nel) { parser.write(x); @@ -294,12 +315,12 @@ abc test({ name: "one chunk", xml: ls, - expect, + expect: fixed11, }); test({ name: "char-by-char", - expect, + expect: fixed11, fn(parser: SaxesParser): void { for (const x of ls) { parser.write(x); @@ -318,6 +339,14 @@ abc "error", "2:6: an XML declaration must be at the start of the document.", ], + [ + "xmldecl", + { + encoding: "utf-8", + version: "1.0", + standalone: undefined, + }, + ], ["opentagstart", { name: "doc", attributes: {}, diff --git a/test/testutil.ts b/test/testutil.ts index 3d624843..420989cd 100644 --- a/test/testutil.ts +++ b/test/testutil.ts @@ -3,13 +3,13 @@ import { EventName, EVENTS, SaxesOptions, SaxesParser } from "../build/dist/saxes"; export interface TestOptions { - xml?: string | string[]; + xml?: string | readonly string[]; name: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect: any[]; + expect: readonly any[]; fn?: (parser: SaxesParser<{}>) => void; opt?: SaxesOptions; - events?: EventName[]; + events?: readonly EventName[]; } export function test(options: TestOptions): void { diff --git a/test/xml-declaration.ts b/test/xml-declaration.ts index 27024b87..6cbce5ff 100644 --- a/test/xml-declaration.ts +++ b/test/xml-declaration.ts @@ -19,6 +19,14 @@ describe("xml declaration", () => { xml: "", expect: [ ["error", "1:7: XML declaration must contain a version."], + [ + "xmldecl", + { + encoding: undefined, + version: undefined, + standalone: undefined, + }, + ], ["opentagstart", { name: "root", attributes: {}, ns: {} }], [ "opentag", @@ -55,6 +63,14 @@ describe("xml declaration", () => { xml: "", expect: [ ["error", "1:14: XML declaration is incomplete."], + [ + "xmldecl", + { + encoding: undefined, + version: undefined, + standalone: undefined, + }, + ], ["opentagstart", { name: "root", attributes: {}, ns: {} }], [ "opentag", @@ -91,6 +107,14 @@ describe("xml declaration", () => { xml: "", expect: [ ["error", "1:15: XML declaration is incomplete."], + [ + "xmldecl", + { + encoding: undefined, + version: undefined, + standalone: undefined, + }, + ], ["opentagstart", { name: "root", attributes: {}, ns: {} }], [ "opentag", @@ -128,6 +152,14 @@ describe("xml declaration", () => { expect: [ ["error", "1:15: value must be quoted."], ["error", "1:16: XML declaration is incomplete."], + [ + "xmldecl", + { + encoding: undefined, + version: undefined, + standalone: undefined, + }, + ], ["opentagstart", { name: "root", attributes: {}, ns: {} }], [ "opentag", @@ -164,6 +196,14 @@ describe("xml declaration", () => { xml: "", expect: [ ["error", "1:17: XML declaration is incomplete."], + [ + "xmldecl", + { + encoding: undefined, + version: undefined, + standalone: undefined, + }, + ], ["opentagstart", { name: "root", attributes: {}, ns: {} }], [ "opentag", @@ -200,6 +240,14 @@ describe("xml declaration", () => { xml: "", expect: [ ["error", "1:17: version number must match /^1\\.[0-9]+$/."], + [ + "xmldecl", + { + encoding: undefined, + version: "a", + standalone: undefined, + }, + ], ["opentagstart", { name: "root", attributes: {}, ns: {} }], [ "opentag",