stax-xml
Version:
1,049 lines (1,044 loc) • 32.8 kB
JavaScript
// src/types.ts
var XmlEventType;
((XmlEventType2) => {
XmlEventType2["START_DOCUMENT"] = "START_DOCUMENT";
XmlEventType2["END_DOCUMENT"] = "END_DOCUMENT";
XmlEventType2["START_ELEMENT"] = "START_ELEMENT";
XmlEventType2["END_ELEMENT"] = "END_ELEMENT";
XmlEventType2["CHARACTERS"] = "CHARACTERS";
XmlEventType2["CDATA"] = "CDATA";
XmlEventType2["ERROR"] = "ERROR";
})(XmlEventType ||= {});
// src/StaxXmlParser.ts
class StaxXmlParser {
reader = null;
decoder;
buffer;
bufferLength = 0;
position = 0;
eventQueue = [];
resolveNext = null;
error = null;
isStreamEnded = false;
parserFinished = false;
currentTextBuffer = "";
elementStack = [];
namespaceStack = [];
options;
constructor(xmlStream, options = {}) {
if (!(xmlStream instanceof ReadableStream)) {
throw new Error("xmlStream must be a web standard ReadableStream.");
}
this.options = {
encoding: "utf-8",
autoDecodeEntities: true,
maxBufferSize: 64 * 1024,
enableBufferCompaction: true,
...options
};
this.decoder = new TextDecoder(this.options.encoding);
this.buffer = new Uint8Array(this.options.maxBufferSize || 64 * 1024);
this.reader = xmlStream.getReader();
this._startReading();
this._addEvent({ type: "START_DOCUMENT" /* START_DOCUMENT */ });
}
async _startReading() {
try {
while (true) {
const { done, value } = await this.reader.read();
if (done) {
this.isStreamEnded = true;
this._parseBuffer();
if (!this.parserFinished && this.elementStack.length > 0) {
this._addError(new Error("Unexpected end of document. Not all elements were closed."));
}
if (!this.parserFinished) {
this._flushCharacters();
this._addEvent({ type: "END_DOCUMENT" /* END_DOCUMENT */ });
this.parserFinished = true;
}
if (this.resolveNext && this.eventQueue.length === 0) {
this.resolveNext({ value: undefined, done: true });
this.resolveNext = null;
}
break;
}
this._appendToBuffer(value);
this._parseBuffer();
this._compactBufferIfNeeded();
}
} catch (err) {
this._addError(err);
if (this.resolveNext) {
this.resolveNext({ value: undefined, done: true });
this.resolveNext = null;
}
}
}
_parseBuffer() {
while (this.position < this.bufferLength && !this.parserFinished) {
const ltPos = this._findPattern("<");
if (ltPos === -1) {
if (this.isStreamEnded) {
const remainingText = this._readBuffer();
this.currentTextBuffer += remainingText;
this._flushCharacters();
} else {
break;
}
break;
}
if (ltPos > this.position) {
try {
const textLength = ltPos - this.position;
const text = this._readBuffer(textLength);
this.currentTextBuffer += text;
} catch (error) {
if (!this.isStreamEnded) {
break;
}
throw error;
}
}
this.position = ltPos;
if (this._matchesPattern("<?xml")) {
if (!this._parseXmlDeclaration())
break;
} else if (this._matchesPattern("<!--")) {
if (!this._parseComment())
break;
} else if (this._matchesPattern("<![CDATA[")) {
if (!this._parseCData())
break;
} else if (this._matchesPattern("<?")) {
if (!this._parseProcessingInstruction())
break;
} else if (this._matchesPattern("</")) {
this._flushCharacters();
if (!this._parseEndTag())
break;
} else if (this._matchesPattern("<")) {
this._flushCharacters();
if (!this._parseStartTag())
break;
} else {
if (this.isStreamEnded) {
this._addError(new Error(`Malformed XML near position ${this.position}`));
return;
}
break;
}
this._compactBufferIfNeeded();
}
}
_flushCharacters() {
if (this.currentTextBuffer.length > 0) {
const decodedText = this._unescapeXml(this.currentTextBuffer);
if (decodedText.trim().length > 0) {
this._addEvent({
type: "CHARACTERS" /* CHARACTERS */,
value: decodedText
});
}
this.currentTextBuffer = "";
}
}
_compactBufferIfNeeded() {
if (!this.options.enableBufferCompaction)
return;
const maxSize = this.options.maxBufferSize || 64 * 1024;
if (this.position > 8192 || this.bufferLength > maxSize && this.position > maxSize / 4) {
this._compactBuffer();
}
}
_compactBuffer() {
if (this.position > 0 && this.position < this.bufferLength) {
const remainingLength = this.bufferLength - this.position;
this.buffer.copyWithin(0, this.position, this.bufferLength);
this.bufferLength = remainingLength;
this.position = 0;
}
}
_clearBuffers() {
this.bufferLength = 0;
this.position = 0;
this.currentTextBuffer = "";
}
_addEvent(event) {
this.eventQueue.push(event);
if (this.resolveNext) {
this.resolveNext(this._popNextEvent());
this.resolveNext = null;
}
}
_addError(err) {
if (this.error === null) {
this.error = err;
this._addEvent({ type: "ERROR" /* ERROR */, error: err });
this.parserFinished = true;
this._clearBuffers();
if (this.reader) {
this.reader.releaseLock();
this.reader = null;
}
}
}
_popNextEvent() {
if (this.eventQueue.length > 0) {
return { value: this.eventQueue.shift(), done: false };
}
if (this.parserFinished) {
return { value: undefined, done: true };
}
return null;
}
async next() {
if (this.error) {
throw this.error;
}
const nextEvent = this._popNextEvent();
if (nextEvent) {
return nextEvent;
}
if (this.parserFinished) {
return { value: undefined, done: true };
}
return new Promise((resolve) => {
this.resolveNext = resolve;
});
}
[Symbol.asyncIterator]() {
return this;
}
_unescapeXml(text) {
if (!text) {
return "";
}
if (!this.options.autoDecodeEntities) {
return text;
}
let entityMap = {
"<": "<",
">": ">",
""": '"',
"'": "'",
...this.options.addEntities?.reduce((map, entity) => {
if (entity.entity && entity.value) {
map[entity.entity] = entity.value;
}
return map;
}, {}),
"&": "&"
};
const regex = new RegExp(Object.keys(entityMap).map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g");
return text.replace(regex, (match) => {
if (entityMap[match]) {
return entityMap[match];
} else {
return match;
}
});
}
_parseQualifiedName(qname, namespaces, isAttribute = false) {
const colonIndex = qname.indexOf(":");
if (colonIndex === -1) {
if (isAttribute) {
return {
localName: qname,
prefix: undefined,
uri: undefined
};
} else {
const defaultUri = namespaces.get("");
return {
localName: qname,
prefix: undefined,
uri: defaultUri
};
}
} else {
const prefix = qname.substring(0, colonIndex);
const localName = qname.substring(colonIndex + 1);
const uri = namespaces.get(prefix);
return {
localName,
prefix,
uri
};
}
}
get XmlEventType() {
return XmlEventType;
}
_appendToBuffer(newData) {
const requiredSize = this.bufferLength + newData.length;
if (requiredSize > this.buffer.length) {
const newSize = Math.max(this.buffer.length * 2, requiredSize);
const newBuffer = new Uint8Array(newSize);
newBuffer.set(this.buffer.subarray(0, this.bufferLength));
this.buffer = newBuffer;
}
this.buffer.set(newData, this.bufferLength);
this.bufferLength += newData.length;
}
_readBuffer(length) {
const originalPos = this.position;
const endPos = length ? Math.min(this.position + length, this.bufferLength) : this.bufferLength;
const slice = this.buffer.subarray(this.position, endPos);
try {
const result = this.decoder.decode(slice, { stream: !this.isStreamEnded });
this.position = endPos;
return result;
} catch (error) {
if (!this.isStreamEnded && endPos === this.bufferLength) {
for (let i = 1;i <= 4 && endPos - i > this.position; i++) {
try {
const safeSlice = this.buffer.subarray(this.position, endPos - i);
const result = this.decoder.decode(safeSlice, { stream: true });
this.position = endPos - i;
return result;
} catch {
continue;
}
}
}
this.position = originalPos;
throw error;
}
}
_matchesPattern(pattern) {
const patternBytes = new TextEncoder().encode(pattern);
if (this.position + patternBytes.length > this.bufferLength) {
return false;
}
for (let i = 0;i < patternBytes.length; i++) {
if (this.buffer[this.position + i] !== patternBytes[i]) {
return false;
}
}
return true;
}
_findPattern(pattern) {
const patternBytes = new TextEncoder().encode(pattern);
const searchEnd = this.bufferLength - patternBytes.length + 1;
for (let i = this.position;i < searchEnd; i++) {
let found = true;
for (let j = 0;j < patternBytes.length; j++) {
if (this.buffer[i + j] !== patternBytes[j]) {
found = false;
break;
}
}
if (found) {
return i;
}
}
return -1;
}
_parseXmlDeclaration() {
const endPos = this._findPattern("?>");
if (endPos === -1) {
return false;
}
this.position = endPos + 2;
return true;
}
_parseComment() {
const endPos = this._findPattern("-->");
if (endPos === -1) {
return false;
}
this.position = endPos + 3;
return true;
}
_parseCData() {
const startPos = this.position + 9;
const endPos = this._findPattern("]]>");
if (endPos === -1) {
return false;
}
try {
const cdataContent = this.decoder.decode(this.buffer.subarray(startPos, endPos));
this._addEvent({
type: "CDATA" /* CDATA */,
value: cdataContent
});
this.position = endPos + 3;
return true;
} catch (error) {
if (!this.isStreamEnded) {
return false;
}
throw error;
}
}
_parseProcessingInstruction() {
const endPos = this._findPattern("?>");
if (endPos === -1) {
return false;
}
this.position = endPos + 2;
return true;
}
_parseEndTag() {
const gtPos = this._findPattern(">");
if (gtPos === -1) {
return false;
}
try {
const tagContent = this.decoder.decode(this.buffer.subarray(this.position, gtPos + 1));
const closeTagMatch = tagContent.match(/^<\/([a-zA-Z0-9_:.-]+)\s*>$/);
if (!closeTagMatch) {
this._addError(new Error("Malformed closing tag"));
return true;
}
const tagName = closeTagMatch[1];
if (this.elementStack.length === 0 || this.elementStack[this.elementStack.length - 1] !== tagName) {
this._addError(new Error(`Mismatched closing tag: </${tagName}>. Expected </${this.elementStack[this.elementStack.length - 1] || "nothing"}>`));
return true;
}
const currentNamespaces = this.namespaceStack.length > 0 ? this.namespaceStack[this.namespaceStack.length - 1] : new Map;
const { localName, prefix, uri } = this._parseQualifiedName(tagName, currentNamespaces);
this.elementStack.pop();
this.namespaceStack.pop();
this._addEvent({
type: "END_ELEMENT" /* END_ELEMENT */,
name: tagName,
localName,
prefix,
uri
});
this.position = gtPos + 1;
return true;
} catch (error) {
if (!this.isStreamEnded) {
return false;
}
throw error;
}
}
_parseStartTag() {
const gtPos = this._findPattern(">");
if (gtPos === -1) {
return false;
}
try {
const tagContent = this.decoder.decode(this.buffer.subarray(this.position, gtPos + 1));
const tagMatch = tagContent.match(/^<([a-zA-Z0-9_:.-]+)(\s+[^>]*?)?\s*(\/?)>$/);
if (!tagMatch) {
this._addError(new Error("Malformed start tag"));
return true;
}
const tagName = tagMatch[1];
const attributesString = tagMatch[2] || "";
const isSelfClosing = tagMatch[3] === "/";
const currentNamespaces = new Map;
if (this.namespaceStack.length > 0) {
const parentNamespaces = this.namespaceStack[this.namespaceStack.length - 1];
for (const [prefix2, uri2] of parentNamespaces) {
currentNamespaces.set(prefix2, uri2);
}
}
const attributes = {};
const attributesWithPrefix = {};
const attrMatches = attributesString.matchAll(/([a-zA-Z0-9_:.-]+)="([^"]*)"/g);
for (const match of attrMatches) {
const attrName = match[1];
const attrValue = this._unescapeXml(match[2]);
attributes[attrName] = attrValue;
const attrNamespaceInfo = this._parseQualifiedName(attrName, currentNamespaces, true);
attributesWithPrefix[attrNamespaceInfo.localName] = {
value: attrValue,
prefix: attrNamespaceInfo.prefix,
uri: attrNamespaceInfo.uri
};
if (attrName === "xmlns") {
currentNamespaces.set("", attrValue);
} else if (attrName.startsWith("xmlns:")) {
const prefix2 = attrName.substring(6);
currentNamespaces.set(prefix2, attrValue);
}
}
const { localName, prefix, uri } = this._parseQualifiedName(tagName, currentNamespaces);
this._addEvent({
type: "START_ELEMENT" /* START_ELEMENT */,
name: tagName,
localName,
prefix,
uri,
attributes,
attributesWithPrefix
});
this.position = gtPos + 1;
if (!isSelfClosing) {
this.elementStack.push(tagName);
this.namespaceStack.push(currentNamespaces);
} else {
this._addEvent({
type: "END_ELEMENT" /* END_ELEMENT */,
name: tagName,
localName,
prefix,
uri
});
}
return true;
} catch (error) {
if (!this.isStreamEnded) {
return false;
}
throw error;
}
}
}
var StaxXmlParser_default = StaxXmlParser;
// src/StaxXmlWriter.ts
var defaultOptions = {
encoding: "utf-8",
prettyPrint: false,
indentString: " ",
autoEncodeEntities: true,
namespaces: []
};
class StaxXmlWriter {
xmlString = "";
state = 0 /* INITIAL */;
elementStack = [];
hasTextContentStack = [];
namespaceStack = [];
options;
currentIndentLevel = 0;
needsIndent = false;
entityMap = {};
constructor(options = {}) {
this.options = { ...defaultOptions, ...options };
this.namespaceStack = [new Map];
if (this.options.addEntities && Array.isArray(this.options.addEntities)) {
for (const entity of this.options.addEntities) {
if (entity.entity && entity.value) {
this.entityMap[entity.entity] = entity.value;
}
}
}
}
writeStartDocument(version = "1.0", encoding) {
if (this.state !== 0 /* INITIAL */) {
throw new Error("writeStartDocument can only be called once at the beginning of the document.");
}
this.state = 3 /* AFTER_ELEMENT */;
let declaration = `<?xml version="${version}"`;
if (encoding) {
declaration += ` encoding="${encoding.toUpperCase()}"`;
this.options.encoding = encoding;
} else {
declaration += ` encoding="${this.options.encoding.toUpperCase()}"`;
}
declaration += "?>";
this._write(declaration);
if (this.options.prettyPrint) {
this.needsIndent = true;
}
return this;
}
writeEndDocument() {
if (this.state === 4 /* CLOSED */ || this.state === 5 /* ERROR */) {
return;
}
while (this.elementStack.length > 0) {
this.writeEndElement();
}
this.state = 4 /* CLOSED */;
}
getXmlString() {
return this.xmlString;
}
writeStartElement(localName, options) {
if (this.state === 4 /* CLOSED */ || this.state === 5 /* ERROR */) {
throw new Error("Cannot writeStartElement: Writer is closed or in error state.");
}
this._closeStartElementTag();
const prefix = options?.prefix;
const uri = options?.uri;
const attributes = options?.attributes;
const selfClosing = options?.selfClosing ?? false;
this._writeIndent();
const tagName = prefix ? `${prefix}:${localName}` : localName;
this._write(`<${tagName}`);
const currentNamespaces = new Map(this.namespaceStack[this.namespaceStack.length - 1]);
if (prefix && uri) {
this._write(` xmlns:${prefix}="${this._escapeXml(uri)}"`);
currentNamespaces.set(prefix, uri);
}
if (attributes) {
for (const [key, value] of Object.entries(attributes)) {
if (typeof value === "string") {
this._write(` ${key}="${this._escapeXml(value)}"`);
} else {
const attrPrefix = value.prefix;
const attrValue = value.value;
if (attrPrefix) {
if (!currentNamespaces.has(attrPrefix)) {
throw new Error(`Namespace prefix '${attrPrefix}' is not defined for attribute '${key}'`);
}
this._write(` ${attrPrefix}:${key}="${this._escapeXml(attrValue)}"`);
} else {
this._write(` ${key}="${this._escapeXml(attrValue)}"`);
}
}
}
}
if (selfClosing) {
this._write("/>");
this.state = 3 /* AFTER_ELEMENT */;
this._writeNewline();
return this;
}
this.elementStack.push({
localName,
prefix
});
this.hasTextContentStack.push(false);
this.namespaceStack.push(currentNamespaces);
this.state = 1 /* START_ELEMENT_OPEN */;
this.currentIndentLevel++;
return this;
}
writeAttribute(localName, value, prefix) {
if (this.state !== 1 /* START_ELEMENT_OPEN */) {
throw new Error("writeAttribute can only be called after writeStartElement.");
}
let attrName = prefix ? `${prefix}:${localName}` : localName;
let attr = ` ${attrName}="${this._escapeXml(value)}"`;
this._write(attr);
return this;
}
writeNamespace(prefix, uri) {
if (this.state !== 1 /* START_ELEMENT_OPEN */) {
throw new Error("writeNamespace can only be called after writeStartElement.");
}
const currentNamespaces = this.namespaceStack[this.namespaceStack.length - 1];
if (prefix) {
this._write(` xmlns:${prefix}="${this._escapeXml(uri)}"`);
currentNamespaces.set(prefix, uri);
} else {
this._write(` xmlns="${this._escapeXml(uri)}"`);
currentNamespaces.set("", uri);
}
return this;
}
writeCharacters(text) {
if (this.state === 4 /* CLOSED */ || this.state === 5 /* ERROR */) {
throw new Error("Cannot writeCharacters: Writer is closed or in error state.");
}
this._closeStartElementTag();
this._write(this._escapeXml(text));
this.state = 2 /* IN_ELEMENT */;
if (this.hasTextContentStack.length > 0) {
this.hasTextContentStack[this.hasTextContentStack.length - 1] = true;
}
this.needsIndent = false;
return this;
}
writeCData(cdata) {
if (this.state === 4 /* CLOSED */ || this.state === 5 /* ERROR */) {
throw new Error("Cannot writeCData: Writer is closed or in error state.");
}
this._closeStartElementTag();
if (cdata.includes("]]>")) {
throw new Error('CDATA section cannot contain "]]>" sequence.');
}
this._write(`<![CDATA[${cdata}]]>`);
this.state = 2 /* IN_ELEMENT */;
if (this.hasTextContentStack.length > 0) {
this.hasTextContentStack[this.hasTextContentStack.length - 1] = true;
}
this.needsIndent = false;
return this;
}
writeComment(comment) {
if (this.state === 4 /* CLOSED */ || this.state === 5 /* ERROR */) {
throw new Error("Cannot writeComment: Writer is closed or in error state.");
}
this._closeStartElementTag();
if (comment.includes("--")) {
throw new Error('XML comment cannot contain "--" sequence.');
}
this._writeIndent();
this._write(`<!-- ${comment} -->`);
this.state = 3 /* AFTER_ELEMENT */;
this._writeNewline();
return this;
}
writeProcessingInstruction(target, data) {
if (this.state === 4 /* CLOSED */ || this.state === 5 /* ERROR */) {
throw new Error("Cannot writeProcessingInstruction: Writer is closed or in error state.");
}
this._closeStartElementTag();
let pi = `<?${target}`;
if (data) {
if (data.includes("?>")) {
throw new Error('Processing instruction data cannot contain "?>" sequence.');
}
pi += ` ${data}`;
}
pi += "?>";
this._writeIndent();
this._write(pi);
this.state = 3 /* AFTER_ELEMENT */;
this._writeNewline();
return this;
}
writeEndElement() {
if (this.elementStack.length === 0) {
throw new Error("No open element to close.");
}
if (this.state === 4 /* CLOSED */ || this.state === 5 /* ERROR */) {
throw new Error("Cannot writeEndElement: Writer is closed or in error state.");
}
this.currentIndentLevel--;
const hasTextContent = this.hasTextContentStack.pop() || false;
if (!hasTextContent && this.state !== 1 /* START_ELEMENT_OPEN */) {
this._writeIndent();
}
this._closeStartElementTag();
const elementInfo = this.elementStack.pop();
this.namespaceStack.pop();
const closingTagName = elementInfo.prefix ? `${elementInfo.prefix}:${elementInfo.localName}` : elementInfo.localName;
this._write(`</${closingTagName}>`);
this.state = 3 /* AFTER_ELEMENT */;
if (this.options.prettyPrint) {
this.needsIndent = true;
}
return this;
}
setPrettyPrint(enabled) {
this.options.prettyPrint = enabled;
return this;
}
setIndentString(indentString) {
this.options.indentString = indentString;
return this;
}
isPrettyPrintEnabled() {
return this.options.prettyPrint;
}
getIndentString() {
return this.options.indentString;
}
_closeStartElementTag() {
if (this.state === 1 /* START_ELEMENT_OPEN */) {
this._write(">");
this.state = 2 /* IN_ELEMENT */;
if (this.options.prettyPrint) {
this.needsIndent = true;
}
}
}
_writeIndent() {
if (this.options.prettyPrint && this.needsIndent) {
this.xmlString += `
`;
this.xmlString += this.options.indentString.repeat(this.currentIndentLevel);
this.needsIndent = false;
}
}
_writeNewline() {
if (this.options.prettyPrint) {
this.xmlString += `
`;
this.needsIndent = true;
}
}
_write(chunk) {
if (this.state === 4 /* CLOSED */ || this.state === 5 /* ERROR */)
return;
this.xmlString += chunk;
}
_escapeXml(text) {
if (!text) {
return "";
}
if (!this.options.autoEncodeEntities) {
return text;
}
let entityMap = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
...this.options.addEntities?.reduce((map, entity) => {
if (entity.entity && entity.value) {
map[entity.entity] = entity.value;
}
return map;
}, {})
};
const regex = new RegExp(Object.keys(entityMap).join("|"), "g");
return text.replace(regex, (match) => {
if (entityMap[match]) {
return entityMap[match];
} else {
return match;
}
});
}
}
var StaxXmlWriter_default = StaxXmlWriter;
// src/StaxXmlParserSync.ts
class StaxXmlParserSync {
xml;
pos = 0;
elementStack = [];
namespaceStack = [];
options;
xmlnsRegex;
attrRegex;
constructor(xml, options = {}) {
this.xml = xml;
this.options = {
autoDecodeEntities: true,
...options
};
this.namespaceStack.push(new Map);
this.xmlnsRegex = /(xmlns(?::([a-zA-Z0-9_.-]+))?)="([^"]*)"/g;
this.attrRegex = /([a-zA-Z0-9_:.-]+)(?:\s*=\s*"([^"]*)")?/g;
}
*[Symbol.iterator]() {
yield { type: "START_DOCUMENT" /* START_DOCUMENT */ };
while (this.pos < this.xml.length) {
const nextTagOpen = this.xml.indexOf("<", this.pos);
if (nextTagOpen === -1) {
const text = this.xml.substring(this.pos);
if (text.trim().length > 0) {
yield { type: "CHARACTERS" /* CHARACTERS */, value: this._unescapeXml(text.trim()) };
}
this.pos = this.xml.length;
break;
}
if (nextTagOpen > this.pos) {
const text = this.xml.substring(this.pos, nextTagOpen);
if (text.trim().length > 0) {
yield { type: "CHARACTERS" /* CHARACTERS */, value: this._unescapeXml(text.trim()) };
}
}
this.pos = nextTagOpen;
const charAfterAngle = this.xml[this.pos + 1];
switch (charAfterAngle) {
case "/":
yield* this._parseEndTag();
break;
case "!":
yield* this._parseCdataCommentDoctype();
break;
case "?":
yield* this._parseProcessingInstruction();
break;
default:
yield* this._parseStartTag();
break;
}
}
yield { type: "END_DOCUMENT" /* END_DOCUMENT */ };
}
*_parseEndTag() {
const tagClose = this.xml.indexOf(">", this.pos);
if (tagClose === -1)
throw new Error("Unclosed end tag");
const fullTagName = this.xml.substring(this.pos + 2, tagClose).trim();
if (this.elementStack.length === 0) {
throw new Error(`Mismatched closing tag: </${fullTagName}>. No open elements.`);
}
const expectedTagName = this.elementStack[this.elementStack.length - 1];
if (fullTagName !== expectedTagName) {
throw new Error(`Mismatched closing tag: </${fullTagName}>. Expected </${expectedTagName}>.`);
}
this.elementStack.pop();
const currentNamespaces = this.namespaceStack.pop();
const { localName, prefix, uri } = this._parseQualifiedName(fullTagName, currentNamespaces || new Map, false);
yield { type: "END_ELEMENT" /* END_ELEMENT */, name: fullTagName, localName, prefix, uri };
this.pos = tagClose + 1;
}
*_parseCdataCommentDoctype() {
if (this.xml.startsWith("<![CDATA[", this.pos)) {
const cdataEnd = this.xml.indexOf("]]>", this.pos);
if (cdataEnd === -1)
throw new Error("Unclosed CDATA section");
const cdataContent = this.xml.substring(this.pos + 9, cdataEnd);
yield { type: "CDATA" /* CDATA */, value: cdataContent };
this.pos = cdataEnd + 3;
} else if (this.xml.startsWith("<!--", this.pos)) {
const commentEnd = this.xml.indexOf("-->", this.pos);
if (commentEnd === -1)
throw new Error("Unclosed comment");
this.pos = commentEnd + 3;
} else if (this.xml.startsWith("<!DOCTYPE", this.pos)) {
const doctypeEnd = this.xml.indexOf(">", this.pos);
if (doctypeEnd === -1)
throw new Error("Unclosed DOCTYPE declaration");
this.pos = doctypeEnd + 1;
}
}
*_parseProcessingInstruction() {
const piEnd = this.xml.indexOf("?>", this.pos);
if (piEnd === -1)
throw new Error("Unclosed processing instruction");
this.pos = piEnd + 2;
}
*_parseStartTag() {
const tagClose = this._findTagEnd(this.pos + 1);
if (tagClose === -1)
throw new Error("Unclosed start tag");
const tagContent = this.xml.substring(this.pos + 1, tagClose);
let attributes = {};
let attributesWithPrefix = {};
let isSelfClosing = false;
const currentNamespaces = new Map;
if (this.namespaceStack.length > 0) {
const parentNamespaces = this.namespaceStack[this.namespaceStack.length - 1];
for (const [prefix2, uri2] of parentNamespaces) {
currentNamespaces.set(prefix2, uri2);
}
}
let rawTagName;
let rawAttrStr = "";
if (tagContent.endsWith("/")) {
isSelfClosing = true;
const actualTagContent = tagContent.substring(0, tagContent.length - 1).trim();
const spaceIndex = actualTagContent.indexOf(" ");
if (spaceIndex === -1) {
rawTagName = actualTagContent;
} else {
rawTagName = actualTagContent.substring(0, spaceIndex);
rawAttrStr = actualTagContent.substring(spaceIndex + 1);
}
} else {
const spaceIndex = tagContent.indexOf(" ");
if (spaceIndex === -1) {
rawTagName = tagContent;
} else {
rawTagName = tagContent.substring(0, spaceIndex);
rawAttrStr = tagContent.substring(spaceIndex + 1);
}
}
this.xmlnsRegex.lastIndex = 0;
let match;
while ((match = this.xmlnsRegex.exec(rawAttrStr)) !== null) {
const fullAttr = match[1];
const prefix2 = match[2];
const uri2 = match[3];
if (prefix2) {
currentNamespaces.set(prefix2, uri2);
} else {
currentNamespaces.set("", uri2);
}
attributes[fullAttr] = uri2;
attributesWithPrefix[fullAttr] = { value: uri2, localName: prefix2 || "xmlns", prefix: prefix2 ? "xmlns" : undefined, uri: undefined };
}
this.attrRegex.lastIndex = 0;
let attrMatch;
while ((attrMatch = this.attrRegex.exec(rawAttrStr)) !== null) {
const fullAttrName = attrMatch[1];
if (fullAttrName.startsWith("xmlns"))
continue;
const attrValue = attrMatch[2] ? this._unescapeXml(attrMatch[2]) : "true";
attributes[fullAttrName] = attrValue;
const { localName: localName2, prefix: prefix2, uri: uri2 } = this._parseQualifiedName(fullAttrName, currentNamespaces, true);
attributesWithPrefix[fullAttrName] = { value: attrValue, localName: localName2, prefix: prefix2, uri: uri2 };
}
const { localName, prefix, uri } = this._parseQualifiedName(rawTagName, currentNamespaces, false);
yield { type: "START_ELEMENT" /* START_ELEMENT */, name: rawTagName, localName, prefix, uri, attributes, attributesWithPrefix };
this.elementStack.push(rawTagName);
if (!isSelfClosing) {
this.namespaceStack.push(currentNamespaces);
} else {
yield { type: "END_ELEMENT" /* END_ELEMENT */, name: rawTagName, localName, prefix, uri };
this.elementStack.pop();
}
this.pos = tagClose + 1;
}
_findTagEnd(startIndex) {
let i = startIndex;
let inQuote = false;
let quoteChar = "";
while (i < this.xml.length) {
const char = this.xml[i];
if (char === "'" || char === '"') {
if (!inQuote) {
inQuote = true;
quoteChar = char;
} else if (char === quoteChar) {
inQuote = false;
quoteChar = "";
}
} else if (char === ">" && !inQuote) {
return i;
}
i++;
}
return -1;
}
_unescapeXml(text) {
if (!text) {
return "";
}
if (!this.options.autoDecodeEntities) {
return text;
}
let entityMap = {
"<": "<",
">": ">",
""": '"',
"'": "'",
...this.options.addEntities?.reduce((map, entity) => {
if (entity.entity && entity.value) {
map[entity.entity] = entity.value;
}
return map;
}, {}),
"&": "&"
};
const regex = new RegExp(Object.keys(entityMap).map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g");
return text.replace(regex, (match) => {
if (entityMap[match]) {
return entityMap[match];
} else {
return match;
}
});
}
_parseQualifiedName(qname, namespaces, isAttribute = false) {
const colonIndex = qname.indexOf(":");
if (colonIndex === -1) {
if (isAttribute) {
return {
localName: qname,
prefix: undefined,
uri: undefined
};
} else {
const defaultUri = namespaces.get("");
return {
localName: qname,
prefix: undefined,
uri: defaultUri
};
}
} else {
const prefix = qname.substring(0, colonIndex);
const localName = qname.substring(colonIndex + 1);
const uri = namespaces.get(prefix);
return {
localName,
prefix,
uri
};
}
}
}
export {
XmlEventType,
StaxXmlWriter_default as StaxXmlWriter,
StaxXmlParserSync,
StaxXmlParser_default as StaxXmlParser
};