@eksml/xml 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +588 -0
- package/dist/converters/fromLossless.d.mts +14 -0
- package/dist/converters/fromLossless.d.mts.map +1 -0
- package/dist/converters/fromLossless.mjs +35 -0
- package/dist/converters/fromLossless.mjs.map +1 -0
- package/dist/converters/fromLossy.d.mts +18 -0
- package/dist/converters/fromLossy.d.mts.map +1 -0
- package/dist/converters/fromLossy.mjs +91 -0
- package/dist/converters/fromLossy.mjs.map +1 -0
- package/dist/converters/lossless.d.mts +39 -0
- package/dist/converters/lossless.d.mts.map +1 -0
- package/dist/converters/lossless.mjs +74 -0
- package/dist/converters/lossless.mjs.map +1 -0
- package/dist/converters/lossy.d.mts +42 -0
- package/dist/converters/lossy.d.mts.map +1 -0
- package/dist/converters/lossy.mjs +158 -0
- package/dist/converters/lossy.mjs.map +1 -0
- package/dist/htmlConstants-D6fsKbZ-.mjs +30 -0
- package/dist/htmlConstants-D6fsKbZ-.mjs.map +1 -0
- package/dist/parser-BfdEfWDg.d.mts +95 -0
- package/dist/parser-BfdEfWDg.d.mts.map +1 -0
- package/dist/parser-CYq309aR.mjs +479 -0
- package/dist/parser-CYq309aR.mjs.map +1 -0
- package/dist/parser.d.mts +2 -0
- package/dist/parser.mjs +2 -0
- package/dist/sax.d.mts +64 -0
- package/dist/sax.d.mts.map +1 -0
- package/dist/sax.mjs +70 -0
- package/dist/sax.mjs.map +1 -0
- package/dist/saxEngine-BDnD7ruG.mjs +750 -0
- package/dist/saxEngine-BDnD7ruG.mjs.map +1 -0
- package/dist/utilities/index.d.mts +88 -0
- package/dist/utilities/index.d.mts.map +1 -0
- package/dist/utilities/index.mjs +87 -0
- package/dist/utilities/index.mjs.map +1 -0
- package/dist/writer.d.mts +58 -0
- package/dist/writer.d.mts.map +1 -0
- package/dist/writer.mjs +357 -0
- package/dist/writer.mjs.map +1 -0
- package/dist/xmlParseStream.d.mts +138 -0
- package/dist/xmlParseStream.d.mts.map +1 -0
- package/dist/xmlParseStream.mjs +313 -0
- package/dist/xmlParseStream.mjs.map +1 -0
- package/package.json +100 -0
- package/src/converters/fromLossless.ts +80 -0
- package/src/converters/fromLossy.ts +180 -0
- package/src/converters/lossless.ts +116 -0
- package/src/converters/lossy.ts +274 -0
- package/src/parser.ts +728 -0
- package/src/sax.ts +157 -0
- package/src/saxEngine.ts +1157 -0
- package/src/utilities/escapeRegExp.ts +19 -0
- package/src/utilities/filter.ts +63 -0
- package/src/utilities/getElementById.ts +21 -0
- package/src/utilities/getElementsByClassName.ts +22 -0
- package/src/utilities/htmlConstants.ts +26 -0
- package/src/utilities/index.ts +7 -0
- package/src/utilities/isElementNode.ts +19 -0
- package/src/utilities/isTextNode.ts +19 -0
- package/src/utilities/toContentString.ts +23 -0
- package/src/writer.ts +650 -0
- package/src/xmlParseStream.ts +597 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saxEngine-BDnD7ruG.mjs","names":[],"sources":["../src/saxEngine.ts"],"sourcesContent":["/**\n * saxEngine — a high-performance, synchronous, event-based streaming XML parser.\n *\n * This is an internal module used by `createSaxParser` and `XmlParseStream`.\n * It is not part of the public API.\n *\n * Architecture: single-pass state machine with batch scanning. Each character is\n * consumed exactly once. Within a chunk, hot-path states (text, tag names,\n * attribute names/values, close tags) scan ahead with indexOf / charCodeAt loops\n * to extract tokens via a single substring() rather than per-character +=.\n */\n\n// @generated:char-codes:begin\nconst GT = 62; // >\nconst SLASH = 47; // /\nconst BANG = 33; // !\nconst QUESTION = 63; // ?\nconst EQ = 61; // =\nconst LBRACKET = 91; // [\nconst RBRACKET = 93; // ]\nconst SQUOTE = 39; // '\nconst DQUOTE = 34; // \"\nconst TAB = 9; // \\t\nconst LF = 10; // \\n\nconst CR = 13; // \\r\nconst SPACE = 32; // (space)\nconst DASH = 45; // -\nconst UPPER_C = 67; // C\nconst UPPER_D = 68; // D\nconst UPPER_A = 65; // A\nconst UPPER_T = 84; // T\n// @generated:char-codes:end\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Attributes record emitted with opentag events. */\nexport type Attributes = Record<string, string | null>;\n\n/** Event handlers for the SAX engine. All callbacks are optional. */\nexport interface SaxEngineHandlers {\n /** Fired when an opening tag and its attributes have been fully parsed. */\n onOpenTag?: (tagName: string, attributes: Attributes) => void;\n /** Fired when a closing tag is encountered. */\n onCloseTag?: (tagName: string) => void;\n /** Fired for text content between tags (trimmed; not fired for whitespace-only text). */\n onText?: (text: string) => void;\n /** Fired for CDATA sections. */\n onCdata?: (data: string) => void;\n /** Fired for comments (the full `<!-- ... -->` string). */\n onComment?: (comment: string) => void;\n /** Fired for processing instructions (`<?xml ... ?>`). */\n onProcessingInstruction?: (name: string, body: string) => void;\n /** Fired for DOCTYPE declarations (`<!DOCTYPE html>`, `<!DOCTYPE svg PUBLIC \"...\" \"...\">`). */\n onDoctype?: (tagName: string, attributes: Attributes) => void;\n}\n\n/** Options for the SAX engine. */\nexport interface SaxEngineOptions extends SaxEngineHandlers {\n /** Tag names that are self-closing (void). Default `[]`. */\n selfClosingTags?: string[];\n /** Tag names whose content is raw text. Default `[]`. */\n rawContentTags?: string[];\n /**\n * Maximum allowed size (in characters) for any internal buffer (text,\n * attribute values, comments, CDATA, raw text). When a buffer exceeds\n * this limit a `RangeError` is thrown. Default `undefined` (no limit).\n */\n maxBufferSize?: number;\n}\n\n/** The parser instance returned by `saxEngine()`. */\nexport interface SaxEngineParser {\n /** Feed a chunk of XML to the parser. */\n write(chunk: string): void;\n /** Signal end-of-input and flush any remaining buffered data. */\n close(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Parser states\n// ---------------------------------------------------------------------------\nconst State = {\n TEXT: 0,\n TAG_OPEN: 1,\n OPEN_TAG_NAME: 2,\n OPEN_TAG_BODY: 3,\n ATTR_NAME: 4,\n ATTR_AFTER_NAME: 5,\n ATTR_AFTER_EQ: 6,\n ATTR_VALUE_DQ: 7,\n ATTR_VALUE_SQ: 8,\n ATTR_VALUE_UQ: 9,\n CLOSE_TAG: 10,\n SELF_CLOSING: 11,\n COMMENT_1: 12,\n COMMENT: 13,\n COMMENT_END1: 14,\n COMMENT_END2: 15,\n CDATA_1: 16,\n CDATA_2: 17,\n CDATA_3: 18,\n CDATA_4: 19,\n CDATA_5: 20,\n CDATA_6: 21,\n CDATA: 22,\n CDATA_END1: 23,\n CDATA_END2: 24,\n PI: 25,\n PI_END: 26,\n DOCTYPE: 27,\n DOCTYPE_BRACKET: 28,\n BANG_START: 29,\n RAW_TEXT: 30,\n RAW_END_1: 31,\n RAW_END_2: 32,\n RAW_END_3: 33,\n} as const;\ntype State = (typeof State)[keyof typeof State];\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport function saxEngine(options: SaxEngineOptions = {}): SaxEngineParser {\n const {\n onOpenTag,\n onCloseTag,\n onText,\n onCdata,\n onComment,\n onProcessingInstruction,\n onDoctype,\n selfClosingTags = [],\n rawContentTags = [],\n maxBufferSize,\n } = options;\n\n const voidSet: Set<string> | null =\n selfClosingTags.length > 0 ? new Set(selfClosingTags) : null;\n const rawSet: Set<string> | null =\n rawContentTags.length > 0 ? new Set(rawContentTags) : null;\n\n let state: State = State.TEXT;\n let text = '';\n let tagName = '';\n let attributeName = '';\n let attributeValue = '';\n let attributes: Attributes = Object.create(null);\n let special = '';\n let rawTag = '';\n let rawText = '';\n let rawCloseTagMatchIndex = 0;\n let rawCloseTagTrailing = '';\n\n // --- Emit helpers ---\n\n function trimWhitespace(input: string): string {\n let startIndex = 0;\n let endIndex = input.length - 1;\n while (startIndex <= endIndex) {\n const charCode = input.charCodeAt(startIndex);\n if (\n charCode !== SPACE &&\n charCode !== TAB &&\n charCode !== LF &&\n charCode !== CR\n )\n break;\n startIndex++;\n }\n while (endIndex >= startIndex) {\n const charCode = input.charCodeAt(endIndex);\n if (\n charCode !== SPACE &&\n charCode !== TAB &&\n charCode !== LF &&\n charCode !== CR\n )\n break;\n endIndex--;\n }\n return startIndex === 0 && endIndex === input.length - 1\n ? input\n : input.substring(startIndex, endIndex + 1);\n }\n\n function emitText(): void {\n if (text.length === 0) return;\n if (onText) {\n const trimmed = trimWhitespace(text);\n if (trimmed.length > 0) onText(trimmed);\n }\n text = '';\n }\n\n /**\n * Parse the accumulated DOCTYPE body (everything between `<!` and `>`,\n * excluding internal DTD subsets) and emit an onDoctype event.\n *\n * The body string starts with the declaration keyword (e.g. \"DOCTYPE html ...\")\n * after the `!`. We prepend `!` to form the tagName (e.g. \"!DOCTYPE\"), then\n * parse the remaining space-separated tokens as null-valued attributes.\n * Quoted strings are unquoted and stored as attribute keys.\n */\n function emitDoctype(body: string): void {\n const bodyLength = body.length;\n let i = 0;\n\n // Read the declaration keyword (e.g. \"DOCTYPE\")\n while (i < bodyLength) {\n const charCode = body.charCodeAt(i);\n if (\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR\n )\n break;\n i++;\n }\n const tagName = '!' + body.substring(0, i);\n\n // Parse space-separated tokens as null-valued attributes\n const attributes: Attributes = Object.create(null);\n while (i < bodyLength) {\n const charCode = body.charCodeAt(i);\n // Skip whitespace\n if (\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR\n ) {\n i++;\n continue;\n }\n // Quoted token — capture content without quotes as the key\n if (charCode === DQUOTE || charCode === SQUOTE) {\n const quoteChar = charCode === DQUOTE ? '\"' : \"'\";\n const closeIndex = body.indexOf(quoteChar, i + 1);\n if (closeIndex === -1) {\n // Unclosed quote — take rest as token\n attributes[body.substring(i + 1)] = null;\n break;\n }\n attributes[body.substring(i + 1, closeIndex)] = null;\n i = closeIndex + 1;\n continue;\n }\n // Unquoted token — scan until whitespace\n const tokenStart = i;\n while (i < bodyLength) {\n const tokenCharCode = body.charCodeAt(i);\n if (\n tokenCharCode === SPACE ||\n tokenCharCode === TAB ||\n tokenCharCode === LF ||\n tokenCharCode === CR\n )\n break;\n i++;\n }\n attributes[body.substring(tokenStart, i)] = null;\n }\n\n onDoctype!(tagName, attributes);\n }\n\n /** After we finish parsing an open tag's `>`, handle void/raw transitions. */\n function finishOpenTag(): void {\n if (onOpenTag) onOpenTag(tagName, attributes);\n if (voidSet !== null && voidSet.has(tagName)) {\n if (onCloseTag) onCloseTag(tagName);\n } else if (rawSet !== null && rawSet.has(tagName)) {\n rawTag = tagName;\n rawText = '';\n rawCloseTagMatchIndex = 0;\n state = State.RAW_TEXT;\n }\n }\n\n // --- Inline scan helpers ---\n // These scan ahead within a chunk and return the end index.\n // If the token isn't complete in this chunk, they return -1.\n\n /**\n * Returns true if `charCode` is a tag-name-ending character:\n * `>`, `/`, `=`, or whitespace.\n */\n function isNameEnd(charCode: number): boolean {\n return (\n charCode === GT ||\n charCode === SLASH ||\n charCode === EQ ||\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR\n );\n }\n\n function processChunk(chunk: string): void {\n const chunkLength = chunk.length;\n let i = 0;\n\n while (i < chunkLength) {\n switch (state) {\n // ==================================================================\n // TEXT\n // ==================================================================\n case State.TEXT: {\n const lessThanIndex = chunk.indexOf('<', i);\n if (lessThanIndex === -1) {\n text += i === 0 ? chunk : chunk.substring(i);\n i = chunkLength;\n } else {\n if (lessThanIndex > i) text += chunk.substring(i, lessThanIndex);\n emitText();\n state = State.TAG_OPEN;\n i = lessThanIndex + 1;\n }\n continue;\n }\n\n // ==================================================================\n // TAG_OPEN\n // ==================================================================\n case State.TAG_OPEN: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === SLASH) {\n state = State.CLOSE_TAG;\n tagName = '';\n i++;\n } else if (charCode === BANG) {\n state = State.BANG_START;\n special = '';\n i++;\n } else if (charCode === QUESTION) {\n state = State.PI;\n special = '';\n i++;\n } else {\n state = State.OPEN_TAG_NAME;\n tagName = '';\n attributes = Object.create(null);\n }\n continue;\n }\n\n // ==================================================================\n // OPEN_TAG_NAME — batch scan for end of name\n // ==================================================================\n case State.OPEN_TAG_NAME: {\n // Scan ahead for end of tag name\n let j = i;\n while (j < chunkLength) {\n const charCode = chunk.charCodeAt(j);\n if (isNameEnd(charCode)) break;\n j++;\n }\n // Accumulate what we scanned\n if (j > i) tagName += chunk.substring(i, j);\n if (j >= chunkLength) {\n // Tag name continues in next chunk\n i = chunkLength;\n continue;\n }\n // We hit a terminator\n const charCode = chunk.charCodeAt(j);\n if (charCode === GT) {\n state = State.TEXT;\n i = j + 1;\n finishOpenTag();\n } else if (charCode === SLASH) {\n state = State.SELF_CLOSING;\n i = j + 1;\n } else {\n // whitespace — enter body\n state = State.OPEN_TAG_BODY;\n i = j + 1;\n }\n continue;\n }\n\n // ==================================================================\n // OPEN_TAG_BODY\n // ==================================================================\n case State.OPEN_TAG_BODY: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === GT) {\n state = State.TEXT;\n i++;\n finishOpenTag();\n } else if (charCode === SLASH) {\n state = State.SELF_CLOSING;\n i++;\n } else if (\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR\n ) {\n i++;\n } else {\n state = State.ATTR_NAME;\n attributeName = '';\n // don't advance — first char of attr name\n }\n continue;\n }\n\n // ==================================================================\n // ATTR_NAME — batch scan for end of attr name\n // ==================================================================\n case State.ATTR_NAME: {\n let j = i;\n while (j < chunkLength) {\n const charCode = chunk.charCodeAt(j);\n if (\n charCode === EQ ||\n charCode === GT ||\n charCode === SLASH ||\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR\n )\n break;\n j++;\n }\n if (j > i) attributeName += chunk.substring(i, j);\n if (j >= chunkLength) {\n i = chunkLength;\n continue;\n }\n\n const charCode = chunk.charCodeAt(j);\n if (charCode === EQ) {\n state = State.ATTR_AFTER_EQ;\n i = j + 1;\n } else if (charCode === GT) {\n attributes[attributeName] = null;\n state = State.TEXT;\n i = j + 1;\n finishOpenTag();\n } else if (charCode === SLASH) {\n attributes[attributeName] = null;\n state = State.SELF_CLOSING;\n i = j + 1;\n } else {\n // whitespace\n state = State.ATTR_AFTER_NAME;\n i = j + 1;\n }\n continue;\n }\n\n // ==================================================================\n // ATTR_AFTER_NAME\n // ==================================================================\n case State.ATTR_AFTER_NAME: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === EQ) {\n state = State.ATTR_AFTER_EQ;\n i++;\n } else if (\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR\n ) {\n i++;\n } else if (charCode === GT) {\n attributes[attributeName] = null;\n state = State.TEXT;\n i++;\n finishOpenTag();\n } else if (charCode === SLASH) {\n attributes[attributeName] = null;\n state = State.SELF_CLOSING;\n i++;\n } else {\n // New attribute — boolean (no value)\n attributes[attributeName] = null;\n state = State.ATTR_NAME;\n attributeName = '';\n }\n continue;\n }\n\n // ==================================================================\n // ATTR_AFTER_EQ\n // ==================================================================\n case State.ATTR_AFTER_EQ: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === DQUOTE) {\n state = State.ATTR_VALUE_DQ;\n attributeValue = '';\n i++;\n } else if (charCode === SQUOTE) {\n state = State.ATTR_VALUE_SQ;\n attributeValue = '';\n i++;\n } else if (\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR\n ) {\n i++;\n } else if (charCode === GT) {\n attributes[attributeName] = '';\n state = State.TEXT;\n i++;\n finishOpenTag();\n } else {\n state = State.ATTR_VALUE_UQ;\n attributeValue = '';\n // don't advance — first char of value\n }\n continue;\n }\n\n // ==================================================================\n // ATTR_VALUE_DQ — batch scan for closing \"\n // ==================================================================\n case State.ATTR_VALUE_DQ: {\n const quoteIndex = chunk.indexOf('\"', i);\n if (quoteIndex === -1) {\n attributeValue += i === 0 ? chunk : chunk.substring(i);\n i = chunkLength;\n } else {\n if (quoteIndex > i)\n attributeValue += chunk.substring(i, quoteIndex);\n attributes[attributeName] = attributeValue;\n state = State.OPEN_TAG_BODY;\n i = quoteIndex + 1;\n }\n continue;\n }\n\n // ==================================================================\n // ATTR_VALUE_SQ — batch scan for closing '\n // ==================================================================\n case State.ATTR_VALUE_SQ: {\n const quoteIndex = chunk.indexOf(\"'\", i);\n if (quoteIndex === -1) {\n attributeValue += i === 0 ? chunk : chunk.substring(i);\n i = chunkLength;\n } else {\n if (quoteIndex > i)\n attributeValue += chunk.substring(i, quoteIndex);\n attributes[attributeName] = attributeValue;\n state = State.OPEN_TAG_BODY;\n i = quoteIndex + 1;\n }\n continue;\n }\n\n // ==================================================================\n // ATTR_VALUE_UQ — batch scan for end of unquoted value\n // ==================================================================\n case State.ATTR_VALUE_UQ: {\n let j = i;\n while (j < chunkLength) {\n const charCode = chunk.charCodeAt(j);\n if (\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR ||\n charCode === GT ||\n charCode === SLASH\n )\n break;\n j++;\n }\n if (j > i) attributeValue += chunk.substring(i, j);\n if (j >= chunkLength) {\n i = chunkLength;\n continue;\n }\n\n const charCode = chunk.charCodeAt(j);\n attributes[attributeName] = attributeValue;\n if (charCode === GT) {\n state = State.TEXT;\n i = j + 1;\n finishOpenTag();\n } else if (charCode === SLASH) {\n state = State.SELF_CLOSING;\n i = j + 1;\n } else {\n state = State.OPEN_TAG_BODY;\n i = j + 1;\n }\n continue;\n }\n\n // ==================================================================\n // CLOSE_TAG — batch scan for >\n // ==================================================================\n case State.CLOSE_TAG: {\n const greaterThanIndex = chunk.indexOf('>', i);\n if (greaterThanIndex === -1) {\n tagName += i === 0 ? chunk : chunk.substring(i);\n i = chunkLength;\n } else {\n if (greaterThanIndex > i)\n tagName += chunk.substring(i, greaterThanIndex);\n if (onCloseTag) onCloseTag(trimWhitespace(tagName));\n state = State.TEXT;\n i = greaterThanIndex + 1;\n }\n continue;\n }\n\n // ==================================================================\n // SELF_CLOSING\n // ==================================================================\n case State.SELF_CLOSING: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === GT) {\n state = State.TEXT;\n i++;\n if (onOpenTag) onOpenTag(tagName, attributes);\n if (onCloseTag) onCloseTag(tagName);\n } else {\n state = State.OPEN_TAG_BODY;\n }\n continue;\n }\n\n // ==================================================================\n // BANG_START\n // ==================================================================\n case State.BANG_START: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === DASH) {\n state = State.COMMENT_1;\n special = '<!-';\n i++;\n } else if (charCode === LBRACKET) {\n state = State.CDATA_1;\n i++;\n } else {\n state = State.DOCTYPE;\n special = '';\n // don't advance — first char of declaration body\n }\n continue;\n }\n\n // ==================================================================\n // COMMENT_1\n // ==================================================================\n case State.COMMENT_1: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === DASH) {\n state = State.COMMENT;\n special = '<!--';\n i++;\n } else {\n // Not a comment (malformed <!-X...>) — fall through to DOCTYPE.\n // We've consumed \"<!-\"; the body after \"<!\" is \"-\" plus remainder.\n special = '-';\n state = State.DOCTYPE;\n // don't advance — current char is part of the body\n }\n continue;\n }\n\n // ==================================================================\n // COMMENT — batch scan for -->\n // ==================================================================\n case State.COMMENT: {\n // Batch: scan for '-' which might start '-->'\n const dashIndex = chunk.indexOf('-', i);\n if (dashIndex === -1) {\n special += i === 0 ? chunk : chunk.substring(i);\n i = chunkLength;\n } else {\n if (dashIndex > i) special += chunk.substring(i, dashIndex);\n special += '-';\n state = State.COMMENT_END1;\n i = dashIndex + 1;\n }\n continue;\n }\n\n // ==================================================================\n // COMMENT_END1\n // ==================================================================\n case State.COMMENT_END1: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === DASH) {\n state = State.COMMENT_END2;\n special += '-';\n i++;\n } else {\n state = State.COMMENT;\n special += chunk[i];\n i++;\n }\n continue;\n }\n\n // ==================================================================\n // COMMENT_END2\n // ==================================================================\n case State.COMMENT_END2: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === GT) {\n special += '>';\n if (onComment) onComment(special);\n special = '';\n state = State.TEXT;\n i++;\n } else if (charCode === DASH) {\n special += '-';\n i++;\n } else {\n state = State.COMMENT;\n special += chunk[i];\n i++;\n }\n continue;\n }\n\n // ==================================================================\n // CDATA handshake states\n // These match the sequence <![CDATA[ char by char. On mismatch,\n // fall through to DOCTYPE with the consumed prefix in `special`.\n // ==================================================================\n case State.CDATA_1: {\n // expecting C after <![\n if (chunk.charCodeAt(i) === UPPER_C) {\n state = State.CDATA_2;\n i++;\n } else {\n special = '[';\n state = State.DOCTYPE;\n }\n continue;\n }\n case State.CDATA_2: {\n // expecting D\n if (chunk.charCodeAt(i) === UPPER_D) {\n state = State.CDATA_3;\n i++;\n } else {\n special = '[C';\n state = State.DOCTYPE;\n }\n continue;\n }\n case State.CDATA_3: {\n // expecting A\n if (chunk.charCodeAt(i) === UPPER_A) {\n state = State.CDATA_4;\n i++;\n } else {\n special = '[CD';\n state = State.DOCTYPE;\n }\n continue;\n }\n case State.CDATA_4: {\n // expecting T\n if (chunk.charCodeAt(i) === UPPER_T) {\n state = State.CDATA_5;\n i++;\n } else {\n special = '[CDA';\n state = State.DOCTYPE;\n }\n continue;\n }\n case State.CDATA_5: {\n // expecting A\n if (chunk.charCodeAt(i) === UPPER_A) {\n state = State.CDATA_6;\n i++;\n } else {\n special = '[CDAT';\n state = State.DOCTYPE;\n }\n continue;\n }\n case State.CDATA_6: {\n // expecting [\n if (chunk.charCodeAt(i) === LBRACKET) {\n state = State.CDATA;\n special = '';\n i++;\n } else {\n special = '[CDATA';\n state = State.DOCTYPE;\n }\n continue;\n }\n\n // ==================================================================\n // CDATA — batch scan for ]\n // ==================================================================\n case State.CDATA: {\n const bracketIndex = chunk.indexOf(']', i);\n if (bracketIndex === -1) {\n special += i === 0 ? chunk : chunk.substring(i);\n i = chunkLength;\n } else {\n if (bracketIndex > i) special += chunk.substring(i, bracketIndex);\n state = State.CDATA_END1;\n i = bracketIndex + 1;\n }\n continue;\n }\n\n // ==================================================================\n // CDATA_END1\n // ==================================================================\n case State.CDATA_END1: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === RBRACKET) {\n state = State.CDATA_END2;\n i++;\n } else {\n special += ']' + chunk[i];\n state = State.CDATA;\n i++;\n }\n continue;\n }\n\n // ==================================================================\n // CDATA_END2\n // ==================================================================\n case State.CDATA_END2: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === GT) {\n if (onCdata) onCdata(special);\n special = '';\n state = State.TEXT;\n i++;\n } else if (charCode === RBRACKET) {\n special += ']';\n i++;\n } else {\n special += ']]' + chunk[i];\n state = State.CDATA;\n i++;\n }\n continue;\n }\n\n // ==================================================================\n // PI — batch scan for ?\n // ==================================================================\n case State.PI: {\n const questionMarkIndex = chunk.indexOf('?', i);\n if (questionMarkIndex === -1) {\n special += i === 0 ? chunk : chunk.substring(i);\n i = chunkLength;\n } else {\n if (questionMarkIndex > i)\n special += chunk.substring(i, questionMarkIndex);\n state = State.PI_END;\n i = questionMarkIndex + 1;\n }\n continue;\n }\n\n // ==================================================================\n // PI_END\n // ==================================================================\n case State.PI_END: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === GT) {\n if (onProcessingInstruction) {\n const inner = special;\n let whitespaceIndex = -1;\n for (let j = 0; j < inner.length; j++) {\n const innerCharCode = inner.charCodeAt(j);\n if (\n innerCharCode === SPACE ||\n innerCharCode === TAB ||\n innerCharCode === LF ||\n innerCharCode === CR\n ) {\n whitespaceIndex = j;\n break;\n }\n }\n if (whitespaceIndex === -1) {\n onProcessingInstruction(inner, '');\n } else {\n const instructionName = inner.substring(0, whitespaceIndex);\n let bodyStartIndex = whitespaceIndex + 1;\n while (bodyStartIndex < inner.length) {\n const bodyCharCode = inner.charCodeAt(bodyStartIndex);\n if (\n bodyCharCode !== SPACE &&\n bodyCharCode !== TAB &&\n bodyCharCode !== LF &&\n bodyCharCode !== CR\n )\n break;\n bodyStartIndex++;\n }\n let bodyEndIndex = inner.length - 1;\n while (bodyEndIndex >= bodyStartIndex) {\n const bodyCharCode = inner.charCodeAt(bodyEndIndex);\n if (\n bodyCharCode !== SPACE &&\n bodyCharCode !== TAB &&\n bodyCharCode !== LF &&\n bodyCharCode !== CR\n )\n break;\n bodyEndIndex--;\n }\n onProcessingInstruction(\n instructionName,\n bodyStartIndex <= bodyEndIndex\n ? inner.substring(bodyStartIndex, bodyEndIndex + 1)\n : '',\n );\n }\n }\n special = '';\n state = State.TEXT;\n i++;\n } else {\n special += '?';\n state = State.PI;\n // don't advance — re-check this char for '?' again\n }\n continue;\n }\n\n // ==================================================================\n // DOCTYPE — accumulate body, parse tokens on >\n // ==================================================================\n case State.DOCTYPE: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === GT) {\n if (onDoctype) {\n emitDoctype(special);\n }\n special = '';\n state = State.TEXT;\n i++;\n } else if (charCode === LBRACKET) {\n state = State.DOCTYPE_BRACKET;\n i++;\n } else {\n // Batch scan: find the next > or [ to avoid per-char accumulation\n let j = i;\n while (j < chunkLength) {\n const scanCharCode = chunk.charCodeAt(j);\n if (scanCharCode === GT || scanCharCode === LBRACKET) break;\n j++;\n }\n special += chunk.substring(i, j);\n i = j;\n // If j < chunkLength, the next iteration will handle > or [\n }\n continue;\n }\n\n // ==================================================================\n // DOCTYPE_BRACKET\n // ==================================================================\n case State.DOCTYPE_BRACKET: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === RBRACKET) {\n state = State.DOCTYPE;\n i++;\n } else {\n i++;\n }\n continue;\n }\n\n // ==================================================================\n // RAW_TEXT — batch scan for <\n // ==================================================================\n case State.RAW_TEXT: {\n const lessThanIndex = chunk.indexOf('<', i);\n if (lessThanIndex === -1) {\n rawText += i === 0 ? chunk : chunk.substring(i);\n i = chunkLength;\n } else {\n if (lessThanIndex > i) rawText += chunk.substring(i, lessThanIndex);\n state = State.RAW_END_1;\n i = lessThanIndex + 1;\n }\n continue;\n }\n\n // ==================================================================\n // RAW_END_1\n // ==================================================================\n case State.RAW_END_1: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === SLASH) {\n state = State.RAW_END_2;\n rawCloseTagMatchIndex = 0;\n i++;\n } else {\n rawText += '<';\n state = State.RAW_TEXT;\n // don't advance — re-process this char in RAW_TEXT\n }\n continue;\n }\n\n // ==================================================================\n // RAW_END_2 — matching close tag name\n // ==================================================================\n case State.RAW_END_2: {\n if (rawCloseTagMatchIndex < rawTag.length) {\n if (chunk[i] === rawTag[rawCloseTagMatchIndex]) {\n rawCloseTagMatchIndex++;\n i++;\n } else {\n rawText += '</' + rawTag.substring(0, rawCloseTagMatchIndex);\n state = State.RAW_TEXT;\n // don't advance — re-process this char\n }\n } else {\n const charCode = chunk.charCodeAt(i);\n if (charCode === GT) {\n if (onText && rawText.length > 0) onText(rawText);\n if (onCloseTag) onCloseTag(rawTag);\n rawText = '';\n rawTag = '';\n state = State.TEXT;\n i++;\n } else if (\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR\n ) {\n rawCloseTagTrailing = chunk[i]!;\n state = State.RAW_END_3;\n i++;\n } else {\n rawText += '</' + rawTag;\n state = State.RAW_TEXT;\n // don't advance\n }\n }\n continue;\n }\n\n // ==================================================================\n // RAW_END_3\n // ==================================================================\n case State.RAW_END_3: {\n const charCode = chunk.charCodeAt(i);\n if (charCode === GT) {\n if (onText && rawText.length > 0) onText(rawText);\n if (onCloseTag) onCloseTag(rawTag);\n rawText = '';\n rawTag = '';\n rawCloseTagTrailing = '';\n state = State.TEXT;\n i++;\n } else if (\n charCode === SPACE ||\n charCode === TAB ||\n charCode === LF ||\n charCode === CR\n ) {\n rawCloseTagTrailing += chunk[i];\n i++;\n } else {\n rawText += '</' + rawTag + rawCloseTagTrailing;\n rawCloseTagTrailing = '';\n state = State.RAW_TEXT;\n // don't advance\n }\n continue;\n }\n\n default:\n i++;\n continue;\n }\n }\n }\n\n return {\n write(chunk: string): void {\n if (chunk.length === 0) return;\n processChunk(chunk);\n if (\n maxBufferSize !== undefined &&\n (text.length > maxBufferSize ||\n attributeValue.length > maxBufferSize ||\n special.length > maxBufferSize ||\n rawText.length > maxBufferSize)\n ) {\n const buf =\n text.length > maxBufferSize\n ? 'text'\n : attributeValue.length > maxBufferSize\n ? 'attribute value'\n : special.length > maxBufferSize\n ? 'special'\n : 'raw text';\n throw new RangeError(\n `Buffer overflow: ${buf} buffer exceeded maxBufferSize (${maxBufferSize})`,\n );\n }\n },\n\n close(): void {\n if (state === State.TEXT) {\n emitText();\n } else if (state === State.RAW_END_3) {\n // Full tag name matched + trailing whitespace — treat as valid close\n if (onText && rawText.length > 0) onText(rawText);\n if (onCloseTag) onCloseTag(rawTag);\n rawText = '';\n rawTag = '';\n rawCloseTagTrailing = '';\n state = State.TEXT;\n } else if (\n state === State.RAW_TEXT ||\n state === State.RAW_END_1 ||\n state === State.RAW_END_2\n ) {\n if (state === State.RAW_END_1) {\n rawText += '<';\n } else if (state === State.RAW_END_2) {\n rawText += '</' + rawTag.substring(0, rawCloseTagMatchIndex);\n }\n if (onText && rawText.length > 0) onText(rawText);\n if (onCloseTag) onCloseTag(rawTag);\n rawText = '';\n rawTag = '';\n state = State.TEXT;\n }\n text = '';\n tagName = '';\n attributeName = '';\n attributeValue = '';\n special = '';\n state = State.TEXT;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;AAaA,MAAM,KAAK;AACX,MAAM,QAAQ;AACd,MAAM,OAAO;AACb,MAAM,WAAW;AACjB,MAAM,KAAK;AACX,MAAM,WAAW;AACjB,MAAM,WAAW;AACjB,MAAM,SAAS;AACf,MAAM,SAAS;AACf,MAAM,MAAM;AACZ,MAAM,KAAK;AACX,MAAM,KAAK;AACX,MAAM,QAAQ;AACd,MAAM,OAAO;AACb,MAAM,UAAU;AAChB,MAAM,UAAU;AAChB,MAAM,UAAU;AAChB,MAAM,UAAU;AAqDhB,MAAM,QAAQ;CACZ,MAAM;CACN,UAAU;CACV,eAAe;CACf,eAAe;CACf,WAAW;CACX,iBAAiB;CACjB,eAAe;CACf,eAAe;CACf,eAAe;CACf,eAAe;CACf,WAAW;CACX,cAAc;CACd,WAAW;CACX,SAAS;CACT,cAAc;CACd,cAAc;CACd,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,OAAO;CACP,YAAY;CACZ,YAAY;CACZ,IAAI;CACJ,QAAQ;CACR,SAAS;CACT,iBAAiB;CACjB,YAAY;CACZ,UAAU;CACV,WAAW;CACX,WAAW;CACX,WAAW;CACZ;AAOD,SAAgB,UAAU,UAA4B,EAAE,EAAmB;CACzE,MAAM,EACJ,WACA,YACA,QACA,SACA,WACA,yBACA,WACA,kBAAkB,EAAE,EACpB,iBAAiB,EAAE,EACnB,kBACE;CAEJ,MAAM,UACJ,gBAAgB,SAAS,IAAI,IAAI,IAAI,gBAAgB,GAAG;CAC1D,MAAM,SACJ,eAAe,SAAS,IAAI,IAAI,IAAI,eAAe,GAAG;CAExD,IAAI,QAAe,MAAM;CACzB,IAAI,OAAO;CACX,IAAI,UAAU;CACd,IAAI,gBAAgB;CACpB,IAAI,iBAAiB;CACrB,IAAI,aAAyB,OAAO,OAAO,KAAK;CAChD,IAAI,UAAU;CACd,IAAI,SAAS;CACb,IAAI,UAAU;CACd,IAAI,wBAAwB;CAC5B,IAAI,sBAAsB;CAI1B,SAAS,eAAe,OAAuB;EAC7C,IAAI,aAAa;EACjB,IAAI,WAAW,MAAM,SAAS;AAC9B,SAAO,cAAc,UAAU;GAC7B,MAAM,WAAW,MAAM,WAAW,WAAW;AAC7C,OACE,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,GAEb;AACF;;AAEF,SAAO,YAAY,YAAY;GAC7B,MAAM,WAAW,MAAM,WAAW,SAAS;AAC3C,OACE,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,GAEb;AACF;;AAEF,SAAO,eAAe,KAAK,aAAa,MAAM,SAAS,IACnD,QACA,MAAM,UAAU,YAAY,WAAW,EAAE;;CAG/C,SAAS,WAAiB;AACxB,MAAI,KAAK,WAAW,EAAG;AACvB,MAAI,QAAQ;GACV,MAAM,UAAU,eAAe,KAAK;AACpC,OAAI,QAAQ,SAAS,EAAG,QAAO,QAAQ;;AAEzC,SAAO;;;;;;;;;;;CAYT,SAAS,YAAY,MAAoB;EACvC,MAAM,aAAa,KAAK;EACxB,IAAI,IAAI;AAGR,SAAO,IAAI,YAAY;GACrB,MAAM,WAAW,KAAK,WAAW,EAAE;AACnC,OACE,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,GAEb;AACF;;EAEF,MAAM,UAAU,MAAM,KAAK,UAAU,GAAG,EAAE;EAG1C,MAAM,aAAyB,OAAO,OAAO,KAAK;AAClD,SAAO,IAAI,YAAY;GACrB,MAAM,WAAW,KAAK,WAAW,EAAE;AAEnC,OACE,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,IACb;AACA;AACA;;AAGF,OAAI,aAAa,UAAU,aAAa,QAAQ;IAC9C,MAAM,YAAY,aAAa,SAAS,OAAM;IAC9C,MAAM,aAAa,KAAK,QAAQ,WAAW,IAAI,EAAE;AACjD,QAAI,eAAe,IAAI;AAErB,gBAAW,KAAK,UAAU,IAAI,EAAE,IAAI;AACpC;;AAEF,eAAW,KAAK,UAAU,IAAI,GAAG,WAAW,IAAI;AAChD,QAAI,aAAa;AACjB;;GAGF,MAAM,aAAa;AACnB,UAAO,IAAI,YAAY;IACrB,MAAM,gBAAgB,KAAK,WAAW,EAAE;AACxC,QACE,kBAAkB,SAClB,kBAAkB,OAClB,kBAAkB,MAClB,kBAAkB,GAElB;AACF;;AAEF,cAAW,KAAK,UAAU,YAAY,EAAE,IAAI;;AAG9C,YAAW,SAAS,WAAW;;;CAIjC,SAAS,gBAAsB;AAC7B,MAAI,UAAW,WAAU,SAAS,WAAW;AAC7C,MAAI,YAAY,QAAQ,QAAQ,IAAI,QAAQ;OACtC,WAAY,YAAW,QAAQ;aAC1B,WAAW,QAAQ,OAAO,IAAI,QAAQ,EAAE;AACjD,YAAS;AACT,aAAU;AACV,2BAAwB;AACxB,WAAQ,MAAM;;;;;;;CAYlB,SAAS,UAAU,UAA2B;AAC5C,SACE,aAAa,MACb,aAAa,SACb,aAAa,MACb,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa;;CAIjB,SAAS,aAAa,OAAqB;EACzC,MAAM,cAAc,MAAM;EAC1B,IAAI,IAAI;AAER,SAAO,IAAI,YACT,SAAQ,OAAR;GAIE,KAAK,MAAM,MAAM;IACf,MAAM,gBAAgB,MAAM,QAAQ,KAAK,EAAE;AAC3C,QAAI,kBAAkB,IAAI;AACxB,aAAQ,MAAM,IAAI,QAAQ,MAAM,UAAU,EAAE;AAC5C,SAAI;WACC;AACL,SAAI,gBAAgB,EAAG,SAAQ,MAAM,UAAU,GAAG,cAAc;AAChE,eAAU;AACV,aAAQ,MAAM;AACd,SAAI,gBAAgB;;AAEtB;;GAMF,KAAK,MAAM,UAAU;IACnB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,OAAO;AACtB,aAAQ,MAAM;AACd,eAAU;AACV;eACS,aAAa,MAAM;AAC5B,aAAQ,MAAM;AACd,eAAU;AACV;eACS,aAAa,UAAU;AAChC,aAAQ,MAAM;AACd,eAAU;AACV;WACK;AACL,aAAQ,MAAM;AACd,eAAU;AACV,kBAAa,OAAO,OAAO,KAAK;;AAElC;;GAMF,KAAK,MAAM,eAAe;IAExB,IAAI,IAAI;AACR,WAAO,IAAI,aAAa;AAEtB,SAAI,UADa,MAAM,WAAW,EAAE,CACb,CAAE;AACzB;;AAGF,QAAI,IAAI,EAAG,YAAW,MAAM,UAAU,GAAG,EAAE;AAC3C,QAAI,KAAK,aAAa;AAEpB,SAAI;AACJ;;IAGF,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,IAAI;AACnB,aAAQ,MAAM;AACd,SAAI,IAAI;AACR,oBAAe;eACN,aAAa,OAAO;AAC7B,aAAQ,MAAM;AACd,SAAI,IAAI;WACH;AAEL,aAAQ,MAAM;AACd,SAAI,IAAI;;AAEV;;GAMF,KAAK,MAAM,eAAe;IACxB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,IAAI;AACnB,aAAQ,MAAM;AACd;AACA,oBAAe;eACN,aAAa,OAAO;AAC7B,aAAQ,MAAM;AACd;eAEA,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,GAEb;SACK;AACL,aAAQ,MAAM;AACd,qBAAgB;;AAGlB;;GAMF,KAAK,MAAM,WAAW;IACpB,IAAI,IAAI;AACR,WAAO,IAAI,aAAa;KACtB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,SACE,aAAa,MACb,aAAa,MACb,aAAa,SACb,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,GAEb;AACF;;AAEF,QAAI,IAAI,EAAG,kBAAiB,MAAM,UAAU,GAAG,EAAE;AACjD,QAAI,KAAK,aAAa;AACpB,SAAI;AACJ;;IAGF,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,IAAI;AACnB,aAAQ,MAAM;AACd,SAAI,IAAI;eACC,aAAa,IAAI;AAC1B,gBAAW,iBAAiB;AAC5B,aAAQ,MAAM;AACd,SAAI,IAAI;AACR,oBAAe;eACN,aAAa,OAAO;AAC7B,gBAAW,iBAAiB;AAC5B,aAAQ,MAAM;AACd,SAAI,IAAI;WACH;AAEL,aAAQ,MAAM;AACd,SAAI,IAAI;;AAEV;;GAMF,KAAK,MAAM,iBAAiB;IAC1B,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,IAAI;AACnB,aAAQ,MAAM;AACd;eAEA,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,GAEb;aACS,aAAa,IAAI;AAC1B,gBAAW,iBAAiB;AAC5B,aAAQ,MAAM;AACd;AACA,oBAAe;eACN,aAAa,OAAO;AAC7B,gBAAW,iBAAiB;AAC5B,aAAQ,MAAM;AACd;WACK;AAEL,gBAAW,iBAAiB;AAC5B,aAAQ,MAAM;AACd,qBAAgB;;AAElB;;GAMF,KAAK,MAAM,eAAe;IACxB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,QAAQ;AACvB,aAAQ,MAAM;AACd,sBAAiB;AACjB;eACS,aAAa,QAAQ;AAC9B,aAAQ,MAAM;AACd,sBAAiB;AACjB;eAEA,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,GAEb;aACS,aAAa,IAAI;AAC1B,gBAAW,iBAAiB;AAC5B,aAAQ,MAAM;AACd;AACA,oBAAe;WACV;AACL,aAAQ,MAAM;AACd,sBAAiB;;AAGnB;;GAMF,KAAK,MAAM,eAAe;IACxB,MAAM,aAAa,MAAM,QAAQ,MAAK,EAAE;AACxC,QAAI,eAAe,IAAI;AACrB,uBAAkB,MAAM,IAAI,QAAQ,MAAM,UAAU,EAAE;AACtD,SAAI;WACC;AACL,SAAI,aAAa,EACf,mBAAkB,MAAM,UAAU,GAAG,WAAW;AAClD,gBAAW,iBAAiB;AAC5B,aAAQ,MAAM;AACd,SAAI,aAAa;;AAEnB;;GAMF,KAAK,MAAM,eAAe;IACxB,MAAM,aAAa,MAAM,QAAQ,KAAK,EAAE;AACxC,QAAI,eAAe,IAAI;AACrB,uBAAkB,MAAM,IAAI,QAAQ,MAAM,UAAU,EAAE;AACtD,SAAI;WACC;AACL,SAAI,aAAa,EACf,mBAAkB,MAAM,UAAU,GAAG,WAAW;AAClD,gBAAW,iBAAiB;AAC5B,aAAQ,MAAM;AACd,SAAI,aAAa;;AAEnB;;GAMF,KAAK,MAAM,eAAe;IACxB,IAAI,IAAI;AACR,WAAO,IAAI,aAAa;KACtB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,SACE,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,MACb,aAAa,MACb,aAAa,MAEb;AACF;;AAEF,QAAI,IAAI,EAAG,mBAAkB,MAAM,UAAU,GAAG,EAAE;AAClD,QAAI,KAAK,aAAa;AACpB,SAAI;AACJ;;IAGF,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,eAAW,iBAAiB;AAC5B,QAAI,aAAa,IAAI;AACnB,aAAQ,MAAM;AACd,SAAI,IAAI;AACR,oBAAe;eACN,aAAa,OAAO;AAC7B,aAAQ,MAAM;AACd,SAAI,IAAI;WACH;AACL,aAAQ,MAAM;AACd,SAAI,IAAI;;AAEV;;GAMF,KAAK,MAAM,WAAW;IACpB,MAAM,mBAAmB,MAAM,QAAQ,KAAK,EAAE;AAC9C,QAAI,qBAAqB,IAAI;AAC3B,gBAAW,MAAM,IAAI,QAAQ,MAAM,UAAU,EAAE;AAC/C,SAAI;WACC;AACL,SAAI,mBAAmB,EACrB,YAAW,MAAM,UAAU,GAAG,iBAAiB;AACjD,SAAI,WAAY,YAAW,eAAe,QAAQ,CAAC;AACnD,aAAQ,MAAM;AACd,SAAI,mBAAmB;;AAEzB;;GAMF,KAAK,MAAM;AAET,QADiB,MAAM,WAAW,EAAE,KACnB,IAAI;AACnB,aAAQ,MAAM;AACd;AACA,SAAI,UAAW,WAAU,SAAS,WAAW;AAC7C,SAAI,WAAY,YAAW,QAAQ;UAEnC,SAAQ,MAAM;AAEhB;GAMF,KAAK,MAAM,YAAY;IACrB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,MAAM;AACrB,aAAQ,MAAM;AACd,eAAU;AACV;eACS,aAAa,UAAU;AAChC,aAAQ,MAAM;AACd;WACK;AACL,aAAQ,MAAM;AACd,eAAU;;AAGZ;;GAMF,KAAK,MAAM;AAET,QADiB,MAAM,WAAW,EAAE,KACnB,MAAM;AACrB,aAAQ,MAAM;AACd,eAAU;AACV;WACK;AAGL,eAAU;AACV,aAAQ,MAAM;;AAGhB;GAMF,KAAK,MAAM,SAAS;IAElB,MAAM,YAAY,MAAM,QAAQ,KAAK,EAAE;AACvC,QAAI,cAAc,IAAI;AACpB,gBAAW,MAAM,IAAI,QAAQ,MAAM,UAAU,EAAE;AAC/C,SAAI;WACC;AACL,SAAI,YAAY,EAAG,YAAW,MAAM,UAAU,GAAG,UAAU;AAC3D,gBAAW;AACX,aAAQ,MAAM;AACd,SAAI,YAAY;;AAElB;;GAMF,KAAK,MAAM;AAET,QADiB,MAAM,WAAW,EAAE,KACnB,MAAM;AACrB,aAAQ,MAAM;AACd,gBAAW;AACX;WACK;AACL,aAAQ,MAAM;AACd,gBAAW,MAAM;AACjB;;AAEF;GAMF,KAAK,MAAM,cAAc;IACvB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,IAAI;AACnB,gBAAW;AACX,SAAI,UAAW,WAAU,QAAQ;AACjC,eAAU;AACV,aAAQ,MAAM;AACd;eACS,aAAa,MAAM;AAC5B,gBAAW;AACX;WACK;AACL,aAAQ,MAAM;AACd,gBAAW,MAAM;AACjB;;AAEF;;GAQF,KAAK,MAAM;AAET,QAAI,MAAM,WAAW,EAAE,KAAK,SAAS;AACnC,aAAQ,MAAM;AACd;WACK;AACL,eAAU;AACV,aAAQ,MAAM;;AAEhB;GAEF,KAAK,MAAM;AAET,QAAI,MAAM,WAAW,EAAE,KAAK,SAAS;AACnC,aAAQ,MAAM;AACd;WACK;AACL,eAAU;AACV,aAAQ,MAAM;;AAEhB;GAEF,KAAK,MAAM;AAET,QAAI,MAAM,WAAW,EAAE,KAAK,SAAS;AACnC,aAAQ,MAAM;AACd;WACK;AACL,eAAU;AACV,aAAQ,MAAM;;AAEhB;GAEF,KAAK,MAAM;AAET,QAAI,MAAM,WAAW,EAAE,KAAK,SAAS;AACnC,aAAQ,MAAM;AACd;WACK;AACL,eAAU;AACV,aAAQ,MAAM;;AAEhB;GAEF,KAAK,MAAM;AAET,QAAI,MAAM,WAAW,EAAE,KAAK,SAAS;AACnC,aAAQ,MAAM;AACd;WACK;AACL,eAAU;AACV,aAAQ,MAAM;;AAEhB;GAEF,KAAK,MAAM;AAET,QAAI,MAAM,WAAW,EAAE,KAAK,UAAU;AACpC,aAAQ,MAAM;AACd,eAAU;AACV;WACK;AACL,eAAU;AACV,aAAQ,MAAM;;AAEhB;GAMF,KAAK,MAAM,OAAO;IAChB,MAAM,eAAe,MAAM,QAAQ,KAAK,EAAE;AAC1C,QAAI,iBAAiB,IAAI;AACvB,gBAAW,MAAM,IAAI,QAAQ,MAAM,UAAU,EAAE;AAC/C,SAAI;WACC;AACL,SAAI,eAAe,EAAG,YAAW,MAAM,UAAU,GAAG,aAAa;AACjE,aAAQ,MAAM;AACd,SAAI,eAAe;;AAErB;;GAMF,KAAK,MAAM;AAET,QADiB,MAAM,WAAW,EAAE,KACnB,UAAU;AACzB,aAAQ,MAAM;AACd;WACK;AACL,gBAAW,MAAM,MAAM;AACvB,aAAQ,MAAM;AACd;;AAEF;GAMF,KAAK,MAAM,YAAY;IACrB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,IAAI;AACnB,SAAI,QAAS,SAAQ,QAAQ;AAC7B,eAAU;AACV,aAAQ,MAAM;AACd;eACS,aAAa,UAAU;AAChC,gBAAW;AACX;WACK;AACL,gBAAW,OAAO,MAAM;AACxB,aAAQ,MAAM;AACd;;AAEF;;GAMF,KAAK,MAAM,IAAI;IACb,MAAM,oBAAoB,MAAM,QAAQ,KAAK,EAAE;AAC/C,QAAI,sBAAsB,IAAI;AAC5B,gBAAW,MAAM,IAAI,QAAQ,MAAM,UAAU,EAAE;AAC/C,SAAI;WACC;AACL,SAAI,oBAAoB,EACtB,YAAW,MAAM,UAAU,GAAG,kBAAkB;AAClD,aAAQ,MAAM;AACd,SAAI,oBAAoB;;AAE1B;;GAMF,KAAK,MAAM;AAET,QADiB,MAAM,WAAW,EAAE,KACnB,IAAI;AACnB,SAAI,yBAAyB;MAC3B,MAAM,QAAQ;MACd,IAAI,kBAAkB;AACtB,WAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;OACrC,MAAM,gBAAgB,MAAM,WAAW,EAAE;AACzC,WACE,kBAAkB,SAClB,kBAAkB,OAClB,kBAAkB,MAClB,kBAAkB,IAClB;AACA,0BAAkB;AAClB;;;AAGJ,UAAI,oBAAoB,GACtB,yBAAwB,OAAO,GAAG;WAC7B;OACL,MAAM,kBAAkB,MAAM,UAAU,GAAG,gBAAgB;OAC3D,IAAI,iBAAiB,kBAAkB;AACvC,cAAO,iBAAiB,MAAM,QAAQ;QACpC,MAAM,eAAe,MAAM,WAAW,eAAe;AACrD,YACE,iBAAiB,SACjB,iBAAiB,OACjB,iBAAiB,MACjB,iBAAiB,GAEjB;AACF;;OAEF,IAAI,eAAe,MAAM,SAAS;AAClC,cAAO,gBAAgB,gBAAgB;QACrC,MAAM,eAAe,MAAM,WAAW,aAAa;AACnD,YACE,iBAAiB,SACjB,iBAAiB,OACjB,iBAAiB,MACjB,iBAAiB,GAEjB;AACF;;AAEF,+BACE,iBACA,kBAAkB,eACd,MAAM,UAAU,gBAAgB,eAAe,EAAE,GACjD,GACL;;;AAGL,eAAU;AACV,aAAQ,MAAM;AACd;WACK;AACL,gBAAW;AACX,aAAQ,MAAM;;AAGhB;GAMF,KAAK,MAAM,SAAS;IAClB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,IAAI;AACnB,SAAI,UACF,aAAY,QAAQ;AAEtB,eAAU;AACV,aAAQ,MAAM;AACd;eACS,aAAa,UAAU;AAChC,aAAQ,MAAM;AACd;WACK;KAEL,IAAI,IAAI;AACR,YAAO,IAAI,aAAa;MACtB,MAAM,eAAe,MAAM,WAAW,EAAE;AACxC,UAAI,iBAAiB,MAAM,iBAAiB,SAAU;AACtD;;AAEF,gBAAW,MAAM,UAAU,GAAG,EAAE;AAChC,SAAI;;AAGN;;GAMF,KAAK,MAAM;AAET,QADiB,MAAM,WAAW,EAAE,KACnB,UAAU;AACzB,aAAQ,MAAM;AACd;UAEA;AAEF;GAMF,KAAK,MAAM,UAAU;IACnB,MAAM,gBAAgB,MAAM,QAAQ,KAAK,EAAE;AAC3C,QAAI,kBAAkB,IAAI;AACxB,gBAAW,MAAM,IAAI,QAAQ,MAAM,UAAU,EAAE;AAC/C,SAAI;WACC;AACL,SAAI,gBAAgB,EAAG,YAAW,MAAM,UAAU,GAAG,cAAc;AACnE,aAAQ,MAAM;AACd,SAAI,gBAAgB;;AAEtB;;GAMF,KAAK,MAAM;AAET,QADiB,MAAM,WAAW,EAAE,KACnB,OAAO;AACtB,aAAQ,MAAM;AACd,6BAAwB;AACxB;WACK;AACL,gBAAW;AACX,aAAQ,MAAM;;AAGhB;GAMF,KAAK,MAAM;AACT,QAAI,wBAAwB,OAAO,OACjC,KAAI,MAAM,OAAO,OAAO,wBAAwB;AAC9C;AACA;WACK;AACL,gBAAW,OAAO,OAAO,UAAU,GAAG,sBAAsB;AAC5D,aAAQ,MAAM;;SAGX;KACL,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,SAAI,aAAa,IAAI;AACnB,UAAI,UAAU,QAAQ,SAAS,EAAG,QAAO,QAAQ;AACjD,UAAI,WAAY,YAAW,OAAO;AAClC,gBAAU;AACV,eAAS;AACT,cAAQ,MAAM;AACd;gBAEA,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,IACb;AACA,4BAAsB,MAAM;AAC5B,cAAQ,MAAM;AACd;YACK;AACL,iBAAW,OAAO;AAClB,cAAQ,MAAM;;;AAIlB;GAMF,KAAK,MAAM,WAAW;IACpB,MAAM,WAAW,MAAM,WAAW,EAAE;AACpC,QAAI,aAAa,IAAI;AACnB,SAAI,UAAU,QAAQ,SAAS,EAAG,QAAO,QAAQ;AACjD,SAAI,WAAY,YAAW,OAAO;AAClC,eAAU;AACV,cAAS;AACT,2BAAsB;AACtB,aAAQ,MAAM;AACd;eAEA,aAAa,SACb,aAAa,OACb,aAAa,MACb,aAAa,IACb;AACA,4BAAuB,MAAM;AAC7B;WACK;AACL,gBAAW,OAAO,SAAS;AAC3B,2BAAsB;AACtB,aAAQ,MAAM;;AAGhB;;GAGF;AACE;AACA;;;AAKR,QAAO;EACL,MAAM,OAAqB;AACzB,OAAI,MAAM,WAAW,EAAG;AACxB,gBAAa,MAAM;AACnB,OACE,kBAAkB,KAAA,MACjB,KAAK,SAAS,iBACb,eAAe,SAAS,iBACxB,QAAQ,SAAS,iBACjB,QAAQ,SAAS,gBACnB;IACA,MAAM,MACJ,KAAK,SAAS,gBACV,SACA,eAAe,SAAS,gBACtB,oBACA,QAAQ,SAAS,gBACf,YACA;AACV,UAAM,IAAI,WACR,oBAAoB,IAAI,kCAAkC,cAAc,GACzE;;;EAIL,QAAc;AACZ,OAAI,UAAU,MAAM,KAClB,WAAU;YACD,UAAU,MAAM,WAAW;AAEpC,QAAI,UAAU,QAAQ,SAAS,EAAG,QAAO,QAAQ;AACjD,QAAI,WAAY,YAAW,OAAO;AAClC,cAAU;AACV,aAAS;AACT,0BAAsB;AACtB,YAAQ,MAAM;cAEd,UAAU,MAAM,YAChB,UAAU,MAAM,aAChB,UAAU,MAAM,WAChB;AACA,QAAI,UAAU,MAAM,UAClB,YAAW;aACF,UAAU,MAAM,UACzB,YAAW,OAAO,OAAO,UAAU,GAAG,sBAAsB;AAE9D,QAAI,UAAU,QAAQ,SAAS,EAAG,QAAO,QAAQ;AACjD,QAAI,WAAY,YAAW,OAAO;AAClC,cAAU;AACV,aAAS;AACT,YAAQ,MAAM;;AAEhB,UAAO;AACP,aAAU;AACV,mBAAgB;AAChB,oBAAiB;AACjB,aAAU;AACV,WAAQ,MAAM;;EAEjB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { n as TNode } from "../parser-BfdEfWDg.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/utilities/htmlConstants.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Standard HTML void elements that are self-closing and never have children.
|
|
6
|
+
* Used as the default for `selfClosingTags` when `html: true`.
|
|
7
|
+
*/
|
|
8
|
+
declare const HTML_VOID_ELEMENTS: string[];
|
|
9
|
+
/**
|
|
10
|
+
* HTML elements whose content is raw text (not parsed as markup).
|
|
11
|
+
* Used as the default for `rawContentTags` when `html: true`.
|
|
12
|
+
*/
|
|
13
|
+
declare const HTML_RAW_CONTENT_TAGS: string[];
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/utilities/filter.d.ts
|
|
16
|
+
/**
|
|
17
|
+
* Filter nodes like Array.filter - returns nodes where the filter function returns true
|
|
18
|
+
* @param input - XML string or array of nodes to filter
|
|
19
|
+
* @param predicate - Filter function
|
|
20
|
+
* @param depth - Current depth in the tree (internal use)
|
|
21
|
+
* @param path - Current path in the tree (internal use)
|
|
22
|
+
* @returns Filtered array of nodes
|
|
23
|
+
*/
|
|
24
|
+
declare function filter(input: string | (TNode | string)[], predicate: (node: TNode, index: number, depth: number, path: string) => boolean, depth?: number, path?: string): TNode[];
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/utilities/toContentString.d.ts
|
|
27
|
+
/**
|
|
28
|
+
* Read the text content of a node, useful for mixed content.
|
|
29
|
+
* Example: "this text has some <b>big</b> text and a <a href=''>link</a>"
|
|
30
|
+
* @param domContent - The node(s) to extract text from
|
|
31
|
+
* @returns Concatenated text content
|
|
32
|
+
*/
|
|
33
|
+
declare function toContentString(domContent: TNode | (TNode | string)[] | string): string;
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/utilities/getElementById.d.ts
|
|
36
|
+
/**
|
|
37
|
+
* Find an element by ID attribute
|
|
38
|
+
* @param input - XML string or parsed DOM to search
|
|
39
|
+
* @param id - ID value to find
|
|
40
|
+
* @returns Found node, or undefined if not found
|
|
41
|
+
*/
|
|
42
|
+
declare function getElementById(input: string | (TNode | string)[], id: string): TNode | undefined;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/utilities/getElementsByClassName.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* Find elements by class name
|
|
47
|
+
* @param input - XML string or parsed DOM to search
|
|
48
|
+
* @param className - Class name to find
|
|
49
|
+
* @returns Found nodes
|
|
50
|
+
*/
|
|
51
|
+
declare function getElementsByClassName(input: string | (TNode | string)[], className: string): TNode[];
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/utilities/isTextNode.d.ts
|
|
54
|
+
/**
|
|
55
|
+
* Type guard to check if a node is a text node (string).
|
|
56
|
+
* Useful for filtering and type narrowing when working with mixed node arrays.
|
|
57
|
+
*
|
|
58
|
+
* @param node - The node to check
|
|
59
|
+
* @returns True if the node is a string (text node)
|
|
60
|
+
* @example
|
|
61
|
+
* const parsed = parse('<div>Hello <span>World</span></div>');
|
|
62
|
+
* parsed[0].children.forEach(child => {
|
|
63
|
+
* if (isTextNode(child)) {
|
|
64
|
+
* console.log('Text:', child);
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
*/
|
|
68
|
+
declare function isTextNode(node: TNode | string): node is string;
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/utilities/isElementNode.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* Type guard to check if a node is an element node (TNode object).
|
|
73
|
+
* Useful for filtering and type narrowing when working with mixed node arrays.
|
|
74
|
+
*
|
|
75
|
+
* @param node - The node to check
|
|
76
|
+
* @returns True if the node is a TNode (element node)
|
|
77
|
+
* @example
|
|
78
|
+
* const parsed = parse('<div>Hello <span>World</span></div>');
|
|
79
|
+
* parsed[0].children.forEach(child => {
|
|
80
|
+
* if (isElementNode(child)) {
|
|
81
|
+
* console.log('Element:', child.tagName);
|
|
82
|
+
* }
|
|
83
|
+
* });
|
|
84
|
+
*/
|
|
85
|
+
declare function isElementNode(node: TNode | string): node is TNode;
|
|
86
|
+
//#endregion
|
|
87
|
+
export { HTML_RAW_CONTENT_TAGS, HTML_VOID_ELEMENTS, filter, getElementById, getElementsByClassName, isElementNode, isTextNode, toContentString };
|
|
88
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/utilities/htmlConstants.ts","../../src/utilities/filter.ts","../../src/utilities/toContentString.ts","../../src/utilities/getElementById.ts","../../src/utilities/getElementsByClassName.ts","../../src/utilities/isTextNode.ts","../../src/utilities/isElementNode.ts"],"mappings":";;;;;;AAIA;cAAa,kBAAA;;;;AAqBb;cAAa,qBAAA;;;;;AArBb;;;;;AAqBA;iBCdgB,MAAA,CACd,KAAA,YAAiB,KAAA,cACjB,SAAA,GACE,IAAA,EAAM,KAAA,EACN,KAAA,UACA,KAAA,UACA,IAAA,sBAEF,KAAA,WACA,IAAA,YACC,KAAA;;;;;ADjBH;;;;iBEIgB,eAAA,CACd,UAAA,EAAY,KAAA,IAAS,KAAA;;;;;AFLvB;;;;iBGMgB,cAAA,CACd,KAAA,YAAiB,KAAA,cACjB,EAAA,WACC,KAAA;;;;;AHTH;;;;iBIOgB,sBAAA,CACd,KAAA,YAAiB,KAAA,cACjB,SAAA,WACC,KAAA;;;;;AJVH;;;;;AAqBA;;;;;;;iBKTgB,UAAA,CAAW,IAAA,EAAM,KAAA,YAAiB,IAAA;;;;;ALZlD;;;;;AAqBA;;;;;;;iBMTgB,aAAA,CAAc,IAAA,EAAM,KAAA,YAAiB,IAAA,IAAQ,KAAA"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { n as filter, r as escapeRegExp, t as parse } from "../parser-CYq309aR.mjs";
|
|
2
|
+
import { n as HTML_VOID_ELEMENTS, t as HTML_RAW_CONTENT_TAGS } from "../htmlConstants-D6fsKbZ-.mjs";
|
|
3
|
+
//#region src/utilities/toContentString.ts
|
|
4
|
+
/**
|
|
5
|
+
* Read the text content of a node, useful for mixed content.
|
|
6
|
+
* Example: "this text has some <b>big</b> text and a <a href=''>link</a>"
|
|
7
|
+
* @param domContent - The node(s) to extract text from
|
|
8
|
+
* @returns Concatenated text content
|
|
9
|
+
*/
|
|
10
|
+
function toContentString(domContent) {
|
|
11
|
+
if (Array.isArray(domContent)) {
|
|
12
|
+
let out = "";
|
|
13
|
+
for (let i = 0; i < domContent.length; i++) out += " " + toContentString(domContent[i]);
|
|
14
|
+
return out.trim();
|
|
15
|
+
} else if (typeof domContent === "object" && domContent !== null) return toContentString(domContent.children);
|
|
16
|
+
else return " " + domContent;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/utilities/getElementById.ts
|
|
20
|
+
/**
|
|
21
|
+
* Find an element by ID attribute
|
|
22
|
+
* @param input - XML string or parsed DOM to search
|
|
23
|
+
* @param id - ID value to find
|
|
24
|
+
* @returns Found node, or undefined if not found
|
|
25
|
+
*/
|
|
26
|
+
function getElementById(input, id) {
|
|
27
|
+
if (typeof input === "string") return parse(input, { attrValue: id })[0];
|
|
28
|
+
return filter(input, (node) => node.attributes?.id === id)[0];
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/utilities/getElementsByClassName.ts
|
|
32
|
+
/**
|
|
33
|
+
* Find elements by class name
|
|
34
|
+
* @param input - XML string or parsed DOM to search
|
|
35
|
+
* @param className - Class name to find
|
|
36
|
+
* @returns Found nodes
|
|
37
|
+
*/
|
|
38
|
+
function getElementsByClassName(input, className) {
|
|
39
|
+
const dom = typeof input === "string" ? parse(input) : input;
|
|
40
|
+
const re = new RegExp("(?:^|\\s)" + escapeRegExp(className) + "(?:\\s|$)");
|
|
41
|
+
return filter(dom, (node) => {
|
|
42
|
+
const cls = node.attributes?.class;
|
|
43
|
+
return cls != null && re.test(cls);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/utilities/isTextNode.ts
|
|
48
|
+
/**
|
|
49
|
+
* Type guard to check if a node is a text node (string).
|
|
50
|
+
* Useful for filtering and type narrowing when working with mixed node arrays.
|
|
51
|
+
*
|
|
52
|
+
* @param node - The node to check
|
|
53
|
+
* @returns True if the node is a string (text node)
|
|
54
|
+
* @example
|
|
55
|
+
* const parsed = parse('<div>Hello <span>World</span></div>');
|
|
56
|
+
* parsed[0].children.forEach(child => {
|
|
57
|
+
* if (isTextNode(child)) {
|
|
58
|
+
* console.log('Text:', child);
|
|
59
|
+
* }
|
|
60
|
+
* });
|
|
61
|
+
*/
|
|
62
|
+
function isTextNode(node) {
|
|
63
|
+
return typeof node === "string";
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/utilities/isElementNode.ts
|
|
67
|
+
/**
|
|
68
|
+
* Type guard to check if a node is an element node (TNode object).
|
|
69
|
+
* Useful for filtering and type narrowing when working with mixed node arrays.
|
|
70
|
+
*
|
|
71
|
+
* @param node - The node to check
|
|
72
|
+
* @returns True if the node is a TNode (element node)
|
|
73
|
+
* @example
|
|
74
|
+
* const parsed = parse('<div>Hello <span>World</span></div>');
|
|
75
|
+
* parsed[0].children.forEach(child => {
|
|
76
|
+
* if (isElementNode(child)) {
|
|
77
|
+
* console.log('Element:', child.tagName);
|
|
78
|
+
* }
|
|
79
|
+
* });
|
|
80
|
+
*/
|
|
81
|
+
function isElementNode(node) {
|
|
82
|
+
return typeof node === "object" && node !== null && "tagName" in node;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { HTML_RAW_CONTENT_TAGS, HTML_VOID_ELEMENTS, filter, getElementById, getElementsByClassName, isElementNode, isTextNode, toContentString };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/utilities/toContentString.ts","../../src/utilities/getElementById.ts","../../src/utilities/getElementsByClassName.ts","../../src/utilities/isTextNode.ts","../../src/utilities/isElementNode.ts"],"sourcesContent":["import type { TNode } from '#src/parser.ts';\n\n/**\n * Read the text content of a node, useful for mixed content.\n * Example: \"this text has some <b>big</b> text and a <a href=''>link</a>\"\n * @param domContent - The node(s) to extract text from\n * @returns Concatenated text content\n */\nexport function toContentString(\n domContent: TNode | (TNode | string)[] | string,\n): string {\n if (Array.isArray(domContent)) {\n let out = '';\n for (let i = 0; i < domContent.length; i++) {\n out += ' ' + toContentString(domContent[i]!);\n }\n return out.trim();\n } else if (typeof domContent === 'object' && domContent !== null) {\n return toContentString(domContent.children);\n } else {\n return ' ' + domContent;\n }\n}\n","import type { TNode } from '#src/parser.ts';\nimport { parse } from '#src/parser.ts';\nimport { filter } from '#src/utilities/filter.ts';\n\n/**\n * Find an element by ID attribute\n * @param input - XML string or parsed DOM to search\n * @param id - ID value to find\n * @returns Found node, or undefined if not found\n */\nexport function getElementById(\n input: string | (TNode | string)[],\n id: string,\n): TNode | undefined {\n if (typeof input === 'string') {\n const out = parse(input, { attrValue: id });\n return out[0] as TNode | undefined;\n }\n const matches = filter(input, (node) => node.attributes?.id === id);\n return matches[0];\n}\n","import type { TNode } from '#src/parser.ts';\nimport { parse } from '#src/parser.ts';\nimport { escapeRegExp } from '#src/utilities/escapeRegExp.ts';\nimport { filter } from '#src/utilities/filter.ts';\n\n/**\n * Find elements by class name\n * @param input - XML string or parsed DOM to search\n * @param className - Class name to find\n * @returns Found nodes\n */\nexport function getElementsByClassName(\n input: string | (TNode | string)[],\n className: string,\n): TNode[] {\n const dom = typeof input === 'string' ? parse(input) : input;\n const re = new RegExp('(?:^|\\\\s)' + escapeRegExp(className) + '(?:\\\\s|$)');\n return filter(dom, (node) => {\n const cls = node.attributes?.class;\n return cls != null && re.test(cls);\n });\n}\n","import type { TNode } from '#src/parser.ts';\n\n/**\n * Type guard to check if a node is a text node (string).\n * Useful for filtering and type narrowing when working with mixed node arrays.\n *\n * @param node - The node to check\n * @returns True if the node is a string (text node)\n * @example\n * const parsed = parse('<div>Hello <span>World</span></div>');\n * parsed[0].children.forEach(child => {\n * if (isTextNode(child)) {\n * console.log('Text:', child);\n * }\n * });\n */\nexport function isTextNode(node: TNode | string): node is string {\n return typeof node === 'string';\n}\n","import type { TNode } from '#src/parser.ts';\n\n/**\n * Type guard to check if a node is an element node (TNode object).\n * Useful for filtering and type narrowing when working with mixed node arrays.\n *\n * @param node - The node to check\n * @returns True if the node is a TNode (element node)\n * @example\n * const parsed = parse('<div>Hello <span>World</span></div>');\n * parsed[0].children.forEach(child => {\n * if (isElementNode(child)) {\n * console.log('Element:', child.tagName);\n * }\n * });\n */\nexport function isElementNode(node: TNode | string): node is TNode {\n return typeof node === 'object' && node !== null && 'tagName' in node;\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,gBACd,YACQ;AACR,KAAI,MAAM,QAAQ,WAAW,EAAE;EAC7B,IAAI,MAAM;AACV,OAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,IACrC,QAAO,MAAM,gBAAgB,WAAW,GAAI;AAE9C,SAAO,IAAI,MAAM;YACR,OAAO,eAAe,YAAY,eAAe,KAC1D,QAAO,gBAAgB,WAAW,SAAS;KAE3C,QAAO,MAAM;;;;;;;;;;ACVjB,SAAgB,eACd,OACA,IACmB;AACnB,KAAI,OAAO,UAAU,SAEnB,QADY,MAAM,OAAO,EAAE,WAAW,IAAI,CAAC,CAChC;AAGb,QADgB,OAAO,QAAQ,SAAS,KAAK,YAAY,OAAO,GAAG,CACpD;;;;;;;;;;ACRjB,SAAgB,uBACd,OACA,WACS;CACT,MAAM,MAAM,OAAO,UAAU,WAAW,MAAM,MAAM,GAAG;CACvD,MAAM,KAAK,IAAI,OAAO,cAAc,aAAa,UAAU,GAAG,YAAY;AAC1E,QAAO,OAAO,MAAM,SAAS;EAC3B,MAAM,MAAM,KAAK,YAAY;AAC7B,SAAO,OAAO,QAAQ,GAAG,KAAK,IAAI;GAClC;;;;;;;;;;;;;;;;;;ACJJ,SAAgB,WAAW,MAAsC;AAC/D,QAAO,OAAO,SAAS;;;;;;;;;;;;;;;;;;ACDzB,SAAgB,cAAc,MAAqC;AACjE,QAAO,OAAO,SAAS,YAAY,SAAS,QAAQ,aAAa"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { n as TNode } from "./parser-BfdEfWDg.mjs";
|
|
2
|
+
import { LosslessEntry } from "./converters/lossless.mjs";
|
|
3
|
+
import { LossyValue } from "./converters/lossy.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/writer.d.ts
|
|
6
|
+
/** Options for write. */
|
|
7
|
+
interface WriterOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Pretty-print with indentation. When enabled, each element is placed on
|
|
10
|
+
* its own line with proper nesting indentation.
|
|
11
|
+
*
|
|
12
|
+
* - `true` uses two spaces as the indent.
|
|
13
|
+
* - A string value is used as the indent directly (e.g. `"\t"`, `" "`).
|
|
14
|
+
*
|
|
15
|
+
* Text-only elements are kept inline: `<name>Alice</name>`.
|
|
16
|
+
* Mixed content (text interleaved with elements) is also kept inline to
|
|
17
|
+
* avoid altering whitespace semantics.
|
|
18
|
+
*/
|
|
19
|
+
pretty?: boolean | string;
|
|
20
|
+
/**
|
|
21
|
+
* Encode special characters in text content and attribute values as XML
|
|
22
|
+
* entities. In XML mode (default), `&`, `<`, `>` are encoded in text and
|
|
23
|
+
* `&`, `"`, `'` are encoded in attributes. In HTML mode (`html: true`),
|
|
24
|
+
* the full HTML named entity set is used (e.g. `©`, `é`).
|
|
25
|
+
*
|
|
26
|
+
* Defaults to `false` — text and attribute values are written verbatim.
|
|
27
|
+
*/
|
|
28
|
+
entities?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Enable HTML mode. When enabled:
|
|
31
|
+
* - Void elements (`<br>`, `<img>`, `<hr>`, etc.) are self-closed without
|
|
32
|
+
* a closing tag (e.g. `<br>` instead of `<br></br>` or `<br/>`).
|
|
33
|
+
* - When `entities` is also `true`, uses HTML named entities
|
|
34
|
+
* (e.g. `©` instead of `©`) for encoding.
|
|
35
|
+
*/
|
|
36
|
+
html?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** Input types accepted by `write()`. */
|
|
39
|
+
type WriterInput = TNode | (TNode | string)[] | LossyValue | LossyValue[] | LosslessEntry[];
|
|
40
|
+
/**
|
|
41
|
+
* Serialize a parsed DOM, lossy object, or lossless entry array back to XML.
|
|
42
|
+
*
|
|
43
|
+
* Input format is auto-detected:
|
|
44
|
+
* - `TNode` or `(TNode | string)[]` — DOM tree, serialized directly
|
|
45
|
+
* - `LossyValue` or `LossyValue[]` — converted to DOM via `fromLossy()` first
|
|
46
|
+
* - `LosslessEntry[]` — converted to DOM via `fromLossless()` first
|
|
47
|
+
*
|
|
48
|
+
* @param input - The node(s), lossy object(s), or lossless entries to serialize
|
|
49
|
+
* @param options - Formatting options
|
|
50
|
+
* @returns XML string
|
|
51
|
+
*/
|
|
52
|
+
declare function write(input: TNode | (TNode | string)[], options?: WriterOptions): string;
|
|
53
|
+
declare function write(input: LossyValue | LossyValue[], options?: WriterOptions): string;
|
|
54
|
+
declare function write(input: LosslessEntry[], options?: WriterOptions): string;
|
|
55
|
+
declare function write(input: WriterInput, options?: WriterOptions): string;
|
|
56
|
+
//#endregion
|
|
57
|
+
export { WriterInput, WriterOptions, write };
|
|
58
|
+
//# sourceMappingURL=writer.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.d.mts","names":[],"sources":["../src/writer.ts"],"mappings":";;;;;;UAsDiB,aAAA;EAAA;;;;;;;;;AAiCjB;;EArBE,MAAA;EAsBE;;;;;;;;EAbF,QAAA;EAeE;;;;;AAgBJ;;EAvBE,IAAA;AAAA;;KAIU,WAAA,GACR,KAAA,IACC,KAAA,eACD,UAAA,GACA,UAAA,KACA,aAAA;;;;;;;;;;AAkBJ;;;iBAJgB,KAAA,CACd,KAAA,EAAO,KAAA,IAAS,KAAA,cAChB,OAAA,GAAU,aAAA;AAAA,iBAEI,KAAA,CACd,KAAA,EAAO,UAAA,GAAa,UAAA,IACpB,OAAA,GAAU,aAAA;AAAA,iBAEI,KAAA,CAAM,KAAA,EAAO,aAAA,IAAiB,OAAA,GAAU,aAAA;AAAA,iBACxC,KAAA,CAAM,KAAA,EAAO,WAAA,EAAa,OAAA,GAAU,aAAA"}
|