UNPKG

steady-xml

Version:

**English** | [中文](./README.zh-CN.md)

621 lines (610 loc) 21.7 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var __defProp = Object.defineProperty; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; // src/shared.ts var toString = Object.prototype.toString; function is(value, type) { return toString.call(value) === `[object ${type}]`; } function isNull(value) { return value === void 0 || value === null; } function isString(value) { return typeof value === "string"; } function isBoolean(value) { return typeof value === "boolean"; } function isArray(value) { return Array.isArray(value); } function isObject(value) { return is(value, "Object"); } // src/node.ts var XmlNodeType; (function(XmlNodeType2) { XmlNodeType2["Root"] = "Root"; XmlNodeType2["Declaration"] = "Declaration"; XmlNodeType2["Comment"] = "Comment"; XmlNodeType2["DocumentType"] = "DocumentType"; XmlNodeType2["Element"] = "Element"; XmlNodeType2["Text"] = "Text"; XmlNodeType2["Instruction"] = "Instruction"; XmlNodeType2["CDATA"] = "CDATA"; })(XmlNodeType || (XmlNodeType = exports.XmlNodeType = {})); var XmlNode = class { constructor(type, parent = null, value = null) { __publicField(this, "name"); __publicField(this, "type"); __publicField(this, "parent"); __publicField(this, "children"); __publicField(this, "attributes"); __publicField(this, "value"); __publicField(this, "selfClosing"); __publicField(this, "prefix"); this.name = ""; this.type = type; this.parent = parent; this.value = value; this.children = null; this.attributes = {}; this.selfClosing = false; this.prefix = ""; } setName(value) { this.name = value; return this; } setType(value) { this.type = value; return this; } setParent(value) { this.parent = value; return this; } setChildren(value) { this.children = value ? Array.from(value) : value; return this; } setAttributes(value) { this.attributes = __spreadValues({}, value); return this; } setValue(value) { this.value = value; return this; } setSelfClosing(value) { this.selfClosing = value; return this; } setPrefix(value) { this.prefix = value; return this; } addAttribute(name, value) { this.attributes[name] = value; return this; } removeAttribute(name) { delete this.attributes[name]; return this; } addChild(childNode) { if (childNode === this) return this; if (!this.children) { this.children = []; } this.children.push(childNode); if (childNode.parent !== this) { childNode.parent = this; } return this; } removeChild(childNode) { if (this.children && this.children.length) { const index = this.children.findIndex((node) => node === childNode); if (~index) { this.children.splice(index, 1); childNode.parent = null; } } return this; } toJsObject() { return { name: this.name || void 0, prefix: this.prefix || void 0, type: this.type, attributes: Object.keys(this.attributes).length ? this.attributes : void 0, value: isNull(this.value) ? void 0 : this.value, selfClosing: this.selfClosing || void 0, children: (this.type === XmlNodeType.Element || this.type === XmlNodeType.Root) && this.children && this.children.length ? this.children.map((child) => child.toJSON()) : void 0 }; } toXmlString(indentChar = " ", newLine = "\n", indentCount = 0) { const indent = indentChar.repeat(indentCount); let xml = ""; switch (this.type) { case XmlNodeType.Root: { xml += this.children && this.children.length ? this.children.map((node) => node.toXmlString(indentChar, newLine, indentCount)).join(newLine) : ""; break; } case XmlNodeType.Element: { if (!this.name) return ""; const name = this.prefix ? `${this.prefix}:${this.name}` : this.name; xml += `${indent}<${name}`; const attributes = buildAttributeString(this.attributes || {}); if (attributes) { xml += ` ${attributes}`; } if (this.children && this.children.length) { xml += `>${newLine}${this.children.map((node) => node.toXmlString(indentChar, newLine, indentCount + 1)).join(newLine)}${newLine}${indent}</${name}>`; } else { xml += this.selfClosing ? " />" : `></${name}>`; } break; } case XmlNodeType.CDATA: { xml += `${indent}<![CDATA[${isNull(this.value) ? "" : this.value}]]>`; break; } case XmlNodeType.Text: { xml += isNull(this.value) ? "" : `${indent}${this.value}`; break; } case XmlNodeType.DocumentType: { xml += isNull(this.value) ? "" : `${indent}<!DOCTYPE ${this.value}>`; break; } case XmlNodeType.Comment: { xml += `${indent}<!-- ${isNull(this.value) ? "" : this.value + " "}-->`; break; } case XmlNodeType.Declaration: { xml += `${indent}<?xml `; if (!this.attributes || isNull(this.attributes.version)) { xml += 'version="1.0" '; } else { const version = parseFloat(this.attributes.version); xml += `version="${Number.isNaN(version) ? "1.0" : version.toFixed(1)}" `; } if (this.attributes) { const _a = this.attributes, { version } = _a, attributes = __objRest(_a, ["version"]); if (attributes) { xml += buildAttributeString(attributes); } } xml += "?>"; break; } case XmlNodeType.Instruction: { xml += isNull(this.value) ? "" : `${indent}<?${this.value}?>`; break; } } return xml; } toJSON() { return this.toJsObject(); } toString() { return this.toXmlString("", ""); } }; function buildAttributeString(attributes) { return Object.keys(attributes).map((key) => { const value = attributes[key]; if (isNull(value)) { return null; } if (isBoolean(value)) { return value ? key : null; } return `${key}="${value}"`; }).filter(Boolean).join(" "); } // src/props.ts var defaultProcessor = (v) => v; var defaultParseProps = { ignoreAttributes: false, parseNodeValue: true, trimValues: true, prefixInName: false, valueProcessor: defaultProcessor, attributeProcessor: defaultProcessor }; var defaultBuildProps = { nameKey: "name", typeKey: "type", valueKey: "value", attributesKey: "attributes", childrenKey: "children", selfClosingKey: "selfClosing", prefixKey: "prefix", trimValues: true, isRoot: false, prefixInName: false, valueProcessor: defaultProcessor, attributeProcessor: defaultProcessor }; var parsePropKeys = Object.keys(defaultParseProps); function normalizeParseProps(props = {}) { const normalizedProps = __spreadValues({}, props); parsePropKeys.forEach((key) => { if (isNull(normalizedProps[key])) { normalizedProps[key] = defaultParseProps[key]; } }); return normalizedProps; } var buildPropKeys = Object.keys(defaultBuildProps); function normalizeBuildProps(props = {}) { const normalizedProps = __spreadValues({}, props); buildPropKeys.forEach((key) => { if (isNull(normalizedProps[key])) { normalizedProps[key] = defaultBuildProps[key]; } }); return normalizedProps; } // src/parser.ts var tagNotClosed = "Tag is not closed."; function parseXmlString(xmlString, props = {}) { const normalizedXml = xmlString.replace(/\r\n?/g, "\n"); const normalizedProps = normalizeParseProps(props); const xmlLength = normalizedXml.length; const rootXmlNode = new XmlNode(XmlNodeType.Root); let currentNode = rootXmlNode; let textData = ""; for (let i = 0; i < xmlLength; i++) { const char = normalizedXml[i]; if (char !== "<") { textData += char; } else { if (normalizedXml[i + 1] === "/") { const endIndex = findEndIndexOrThrow(normalizedXml, ">", i, `Element End ${tagNotClosed}`); let tagName = normalizedXml.substring(i + 2, endIndex); let prefix = ""; if (!normalizedProps.prefixInName) { const prefixIndex = tagName.indexOf(":"); if (~prefixIndex) { prefix = tagName.substring(0, prefixIndex); tagName = tagName.substring(prefixIndex + 1); } } if (currentNode.prefix !== prefix || currentNode.name !== tagName) { throw new Error(`End Tag is incorrect.`); } if (textData) { const textValue = toTextValue(processNodeValue("", XmlNodeType.Text, textData, normalizedProps)); if (textValue) { currentNode.addChild(new XmlNode(XmlNodeType.Text, currentNode, textValue)); } } currentNode = currentNode.parent; textData = ""; i = endIndex; } else if (normalizedXml[i + 1] === "?") { const endIndex = findEndIndexOrThrow(normalizedXml, "?>", i, `Processing Instruction ${tagNotClosed}`); const content = normalizedXml.substring(i + 2, endIndex - 1); if (currentNode) { if (content.startsWith("xml ") && content.includes("version=") && currentNode.type === XmlNodeType.Root) { const childNode = new XmlNode(XmlNodeType.Declaration, currentNode); childNode.attributes = parseAttributes(content.substr(4), XmlNodeType.Declaration, normalizedProps); currentNode.addChild(childNode); } else { currentNode.addChild(new XmlNode(XmlNodeType.Instruction, currentNode, processNodeValue("", XmlNodeType.Instruction, content, normalizedProps))); } } i = endIndex; } else if (normalizedXml.substr(i + 1, 3) === "!--") { const endIndex = findEndIndexOrThrow(normalizedXml, "-->", i, `Comment ${tagNotClosed}`); const content = normalizedXml.substring(i + 4, endIndex - 2); if (currentNode) { currentNode.addChild(new XmlNode(XmlNodeType.Comment, currentNode, processNodeValue("", XmlNodeType.Comment, content, normalizedProps))); } i = endIndex; } else if (normalizedXml.substr(i + 1, 8) === "!DOCTYPE") { let endIndex = findEndIndexOrThrow(normalizedXml, ">", i, `Document Type ${tagNotClosed}`); let content = normalizedXml.substring(i + 9, endIndex); if (content.includes("[")) { endIndex = findEndIndexOrThrow(normalizedXml, "]>", i, `Document Type ${tagNotClosed}`); content = normalizedXml.substring(i + 9, endIndex); } if (currentNode) { currentNode.addChild(new XmlNode(XmlNodeType.DocumentType, currentNode, processNodeValue("", XmlNodeType.DocumentType, content, normalizedProps))); } i = endIndex; } else if (normalizedXml.substr(i + 1, 8) === "![CDATA[") { const endIndex = findEndIndexOrThrow(normalizedXml, "]]>", i, `CDATA Section ${tagNotClosed}`); const content = normalizedXml.substring(i + 9, endIndex - 2); if (currentNode && textData) { const textValue = toTextValue(processNodeValue("", XmlNodeType.Text, textData, normalizedProps)); if (textValue) { currentNode.addChild(new XmlNode(XmlNodeType.Text, currentNode, textValue)); } } currentNode.addChild(new XmlNode(XmlNodeType.CDATA, currentNode, processNodeValue("", XmlNodeType.CDATA, content, normalizedProps))); textData = ""; i = endIndex; } else { let attrBoundary = ""; let content = ""; let endIndex = i + 1; while (endIndex <= xmlLength) { let char2 = normalizedXml[endIndex]; if (attrBoundary) { if (char2 === attrBoundary) attrBoundary = ""; } else if (char2 === '"' || char2 === "'") { attrBoundary = char2; } else if (char2 === " ") { char2 = " "; } else if (char2 === ">") { break; } content += char2; endIndex++; } if (endIndex > xmlLength) { throw new Error(`Element ${tagNotClosed}`); } content = content.trim(); const separatorIndex = content.indexOf(" "); let tagName = content; let prefix = ""; if (~separatorIndex) { tagName = content.substr(0, separatorIndex); content = content.substr(separatorIndex + 1); } else { content = ""; } if (!normalizedProps.prefixInName) { const prefixIndex = tagName.indexOf(":"); if (~prefixIndex) { prefix = tagName.substring(0, prefixIndex); tagName = tagName.substring(prefixIndex + 1); } } if (currentNode && textData) { const textValue = toTextValue(processNodeValue("", XmlNodeType.Text, textData, normalizedProps)); if (textValue) { currentNode.addChild(new XmlNode(XmlNodeType.Text, currentNode, textValue)); } } if (content.length && content.lastIndexOf("/") === content.length - 1) { if (tagName[tagName.length - 1] === "/") { tagName = tagName.substr(0, tagName.length - 1); content = ""; } else { content = content.substr(0, content.length - 1); } const childNode = new XmlNode(XmlNodeType.Element, currentNode); childNode.name = tagName; childNode.selfClosing = true; childNode.prefix = prefix; if (content && !normalizedProps.ignoreAttributes) { childNode.attributes = parseAttributes(content, XmlNodeType.Element, normalizedProps); } currentNode.addChild(childNode); } else { const childNode = new XmlNode(XmlNodeType.Element, currentNode); if (content && !normalizedProps.ignoreAttributes) { childNode.attributes = parseAttributes(content, XmlNodeType.Element, normalizedProps); } childNode.name = tagName; childNode.prefix = prefix; currentNode.addChild(childNode); currentNode = childNode; } textData = ""; i = endIndex; } } } return rootXmlNode; } function findEndIndexOrThrow(value, search, position, error) { const index = value.indexOf(search, position); if (!~index) { throw new Error(error); } return index + search.length - 1; } function processNodeValue(name, type, value, props) { if (value) { if (props.trimValues) { value = value.trim(); } return parseValue(props.valueProcessor(value, type, name), props.parseNodeValue); } return null; } function parseValue(value, shouldParse) { if (shouldParse && isString(value)) { value = value.trim(); if (value === "true") { return true; } else if (value === "false") { return false; } else { return tryToNumber(value); } } else { return isNull(value) ? null : value; } } function tryToNumber(value) { const number = parseFloat(value); return Number.isNaN(number) ? value : number; } function toTextValue(value) { return isNull(value) ? "" : isString(value) ? value : String(value); } var attributeRE = /[^\s=]+\s*(=\s*['"][\s\S]*?['"])?/g; function parseAttributes(content, type, props) { content = content.replace(/\r?\n/g, " "); const matches = content.match(attributeRE) || []; const attributes = {}; for (let i = 0; i < matches.length; i++) { const attrString = matches[i]; let [name, value] = attrString.split("="); name = name.trim(); if (isString(value)) { value = value.substring(1, value.length - 1); if (props.trimValues) { value = value.trim(); } attributes[name] = parseValue(props.attributeProcessor(value, name, type), props.parseNodeValue); } else { attributes[name] = true; } } return attributes; } // src/builder.ts function buildFromJson(json, props = {}) { const normalizedProps = normalizeBuildProps(props); const { nameKey, typeKey, valueKey, attributesKey, childrenKey, selfClosingKey, prefixKey } = normalizedProps; const rootXmlNode = new XmlNode(XmlNodeType.Root); const loopQueue = []; if (normalizedProps.isRoot || json[typeKey] === XmlNodeType.Root) { loopQueue.push(...(json[childrenKey] || []).map((child) => ({ parent: rootXmlNode, child }))); } else { const declarationNdoe = new XmlNode(XmlNodeType.Declaration, rootXmlNode); declarationNdoe.attributes = { version: 1, encoding: "UTF-8", standalone: "yes" }; rootXmlNode.addChild(declarationNdoe); loopQueue.push({ parent: rootXmlNode, child: json }); } while (loopQueue.length) { const { parent, child } = loopQueue.shift(); if (isString(child)) { parent.addChild(new XmlNode(XmlNodeType.Text, parent, processNodeValue2(XmlNodeType.Text, child, "", normalizedProps))); continue; } const name = isString(child[nameKey]) ? child[nameKey] : ""; const type = child[typeKey]; const value = isNull(child[valueKey]) ? null : child[valueKey]; switch (type) { case XmlNodeType.CDATA: { parent.addChild(new XmlNode(XmlNodeType.CDATA, parent, processNodeValue2(type, value, "", normalizedProps))); break; } case XmlNodeType.Text: { parent.addChild(new XmlNode(XmlNodeType.Text, parent, processNodeValue2(type, value, "", normalizedProps))); break; } case XmlNodeType.DocumentType: { parent.addChild(new XmlNode(XmlNodeType.DocumentType, parent, processNodeValue2(type, value, "", normalizedProps))); break; } case XmlNodeType.Comment: { parent.addChild(new XmlNode(XmlNodeType.Comment, parent, processNodeValue2(type, value, "", normalizedProps))); break; } case XmlNodeType.Declaration: { const node = new XmlNode(XmlNodeType.Declaration, parent); const attributes = attributesKey && child[attributesKey]; if (attributes && isObject(attributes)) { Object.keys(attributes).forEach((key) => { node.attributes[key] = normalizedProps.attributeProcessor(attributes[key], key, XmlNodeType.Declaration); }); } if (!node.attributes.version) { node.attributes.version = 1; } else { const version = parseFloat(node.attributes.version); node.attributes.version = Number.isNaN(version) ? 1 : version; } parent.addChild(node); break; } case XmlNodeType.Instruction: { parent.addChild(new XmlNode(XmlNodeType.Instruction, parent, processNodeValue2(type, value, "", normalizedProps))); break; } default: { if (!name) break; let tagName = name; let prefix = ""; if (normalizedProps.prefixInName) { const prefixIndex = tagName.indexOf(":"); if (~prefixIndex) { prefix = tagName.substring(0, prefixIndex); tagName = tagName.substring(prefixIndex + 1); } } else if (prefixKey && isString(child[prefixKey])) { prefix = child[prefixKey]; } const node = new XmlNode(XmlNodeType.Element, parent); const attributes = attributesKey && child[attributesKey]; node.name = tagName; node.prefix = prefix; if (attributes && isObject(attributes)) { Object.keys(attributes).forEach((key) => { node.attributes[key] = normalizedProps.attributeProcessor(attributes[key], key, XmlNodeType.Element); }); } parent.addChild(node); const children = child[childrenKey]; if (isArray(children) && children.length) { node.selfClosing = false; loopQueue.push(...children.map((child2) => ({ parent: node, child: child2 }))); } else { node.selfClosing = Boolean(selfClosingKey && child[selfClosingKey]); } } } } return rootXmlNode; } function processNodeValue2(type, value, name, props) { value = props.valueProcessor(value, type, name); if (isNull(value)) { return null; } if (isString(value)) { return props.trimValues ? value.trim() : value; } return String(value); } exports.XmlNode = XmlNode; exports.XmlNodeType = XmlNodeType; exports.buildFromJson = buildFromJson; exports.parseXmlString = parseXmlString;