UNPKG

typesxml

Version:

Open source XML library written in TypeScript

681 lines 26.5 kB
"use strict"; /******************************************************************************* * Copyright (c) 2023-2026 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/epl-v10.html * * Contributors: * Maxprograms - initial API and implementation *******************************************************************************/ Object.defineProperty(exports, "__esModule", { value: true }); exports.xmlStringToJsonObject = xmlStringToJsonObject; exports.xmlFileToJsonObject = xmlFileToJsonObject; exports.xmlStreamToJsonObject = xmlStreamToJsonObject; exports.xmlDocumentToJsonObject = xmlDocumentToJsonObject; exports.xmlStringToJsonFile = xmlStringToJsonFile; exports.xmlFileToJsonFile = xmlFileToJsonFile; exports.xmlStreamToJsonFile = xmlStreamToJsonFile; exports.xmlDocumentToJsonFile = xmlDocumentToJsonFile; exports.jsonObjectToXmlDocument = jsonObjectToXmlDocument; exports.jsonStringToXmlDocument = jsonStringToXmlDocument; exports.jsonFileToXmlDocument = jsonFileToXmlDocument; exports.jsonStreamToXmlDocument = jsonStreamToXmlDocument; exports.jsonObjectToXmlFile = jsonObjectToXmlFile; exports.jsonStringToXmlFile = jsonStringToXmlFile; exports.jsonFileToXmlFile = jsonFileToXmlFile; exports.jsonStreamToXmlFile = jsonStreamToXmlFile; const promises_1 = require("node:fs/promises"); const DOMBuilder_js_1 = require("../DOMBuilder.js"); const SAXParser_js_1 = require("../SAXParser.js"); const XMLDocument_js_1 = require("../XMLDocument.js"); const XMLElement_js_1 = require("../XMLElement.js"); const XMLAttribute_js_1 = require("../XMLAttribute.js"); const TextNode_js_1 = require("../TextNode.js"); const CData_js_1 = require("../CData.js"); const XMLComment_js_1 = require("../XMLComment.js"); const ProcessingInstruction_js_1 = require("../ProcessingInstruction.js"); const XMLDeclaration_js_1 = require("../XMLDeclaration.js"); const XMLDocumentType_js_1 = require("../XMLDocumentType.js"); const RESERVED_KEYS = new Set([ "_attributes", "_text", "_cdata", "_comments", "_processingInstructions", "_content" ]); function xmlStringToJsonObject(xml, options) { const { mode = "simple", ...parseOptions } = options ?? {}; const document = parseXmlFromString(xml, parseOptions); if (mode === "roundtrip") { return xmlDocumentToJsonObject(document, { mode: "roundtrip" }); } return xmlDocumentToJsonObject(document); } async function xmlFileToJsonObject(path, options) { const mode = options?.mode ?? "simple"; const encoding = options?.encoding ?? "utf8"; const xmlText = await (0, promises_1.readFile)(path, { encoding }); if (mode === "roundtrip") { return xmlStringToJsonObject(xmlText, { mode: "roundtrip" }); } return xmlStringToJsonObject(xmlText); } async function xmlStreamToJsonObject(stream, options) { const { mode = "simple", ...streamOptions } = options ?? {}; const document = await parseXmlFromStream(stream, streamOptions); if (mode === "roundtrip") { return xmlDocumentToJsonObject(document, { mode: "roundtrip" }); } return xmlDocumentToJsonObject(document); } function xmlDocumentToJsonObject(document, options) { const root = document.getRoot(); if (!root) { throw new Error("XML document does not contain a root element"); } const mode = options?.mode ?? "simple"; const includeOrderedContent = mode === "roundtrip"; const rootValue = elementToJsonValue(root, { includeOrderedContent }); if (mode !== "roundtrip") { return rootValue; } const result = { rootName: root.getName(), root: rootValue }; const declaration = document.getXmlDeclaration(); if (declaration) { const declarationJson = {}; if (declaration.getVersion()) { declarationJson.version = declaration.getVersion(); } if (declaration.getEncoding()) { declarationJson.encoding = declaration.getEncoding(); } const standalone = declaration.getStandalone(); if (standalone !== undefined) { declarationJson.standalone = standalone; } if (Object.keys(declarationJson).length > 0) { result.declaration = declarationJson; } } const docType = document.getDocumentType(); if (docType) { const doctypeJson = { name: docType.name }; const publicId = docType.getPublicId(); if (publicId) { doctypeJson.publicId = publicId; } const systemId = docType.getSystemId(); if (systemId) { doctypeJson.systemId = systemId; } const internalSubset = docType.getInternalSubset(); if (internalSubset) { doctypeJson.internalSubset = internalSubset; } result.doctype = doctypeJson; } const prolog = []; const epilog = []; let seenRoot = false; let docTypeEncountered = false; for (const node of document.contentIterator()) { if (node instanceof XMLDeclaration_js_1.XMLDeclaration) { continue; } if (node instanceof XMLDocumentType_js_1.XMLDocumentType) { docTypeEncountered = true; continue; } if (node instanceof XMLElement_js_1.XMLElement) { if (!seenRoot) { seenRoot = true; } continue; } if (node instanceof XMLComment_js_1.XMLComment) { if (seenRoot) { const entry = { type: "comment", value: node.getValue() }; epilog.push(entry); } else { const entry = { type: "comment", value: node.getValue() }; if (docTypeEncountered) { entry.afterDoctype = true; } prolog.push(entry); } continue; } if (node instanceof ProcessingInstruction_js_1.ProcessingInstruction) { const data = node.getData(); const entryBase = { type: "processingInstruction", target: node.getTarget() }; if (data && data.length > 0) { entryBase.data = data; } if (seenRoot) { epilog.push(entryBase); } else { const entry = { ...entryBase }; if (docTypeEncountered) { entry.afterDoctype = true; } prolog.push(entry); } continue; } if (node instanceof TextNode_js_1.TextNode) { const entryBase = { type: "text", value: node.getValue() }; if (seenRoot) { epilog.push(entryBase); } else { const entry = { ...entryBase }; if (docTypeEncountered) { entry.afterDoctype = true; } prolog.push(entry); } } } if (prolog.length > 0) { result.prolog = prolog; } if (epilog.length > 0) { result.epilog = epilog; } return result; } async function xmlStringToJsonFile(xml, targetPath, options, indent = 2, encoding = "utf8") { const payloadSource = options?.mode === "roundtrip" ? xmlStringToJsonObject(xml, options) : xmlStringToJsonObject(xml, options); const payload = JSON.stringify(payloadSource, null, indent); await (0, promises_1.writeFile)(targetPath, payload, { encoding }); } async function xmlFileToJsonFile(path, targetPath, xmlEncoding = "utf8", indent = 2, jsonEncoding = "utf8", options) { const document = await parseXmlFromFile(path, xmlEncoding); await xmlDocumentToJsonFile(document, targetPath, indent, jsonEncoding, options); } async function xmlStreamToJsonFile(stream, targetPath, options, indent = 2, encoding = "utf8") { const { mode = "simple", ...streamOptions } = options ?? {}; const document = await parseXmlFromStream(stream, streamOptions); if (mode === "roundtrip") { await xmlDocumentToJsonFile(document, targetPath, indent, encoding, { mode: "roundtrip" }); } else { await xmlDocumentToJsonFile(document, targetPath, indent, encoding); } } async function xmlDocumentToJsonFile(document, targetPath, indent = 2, encoding = "utf8", options) { const payloadSource = options?.mode === "roundtrip" ? xmlDocumentToJsonObject(document, options) : xmlDocumentToJsonObject(document, options); const payload = JSON.stringify(payloadSource, null, indent); await (0, promises_1.writeFile)(targetPath, payload, { encoding }); } function jsonObjectToXmlDocument(json, rootElementName) { if (isXmlJsonDocument(json)) { return jsonDocumentToXmlDocument(json); } const resolvedRootName = rootElementName !== undefined ? rootElementName : inferRootElementNameFromSimpleJson(json) ?? "json"; return simpleJsonObjectToXmlDocument(json, resolvedRootName); } function jsonStringToXmlDocument(jsonText, rootElementName = "json") { const parsed = JSON.parse(jsonText); return jsonObjectToXmlDocument(parsed, rootElementName); } async function jsonFileToXmlDocument(path, rootElementName = "json", encoding = "utf8") { const jsonText = await (0, promises_1.readFile)(path, { encoding }); return jsonStringToXmlDocument(jsonText, rootElementName); } async function jsonStreamToXmlDocument(stream, rootElementName = "json", encoding = "utf8") { const jsonText = await readStreamToString(stream, encoding); return jsonStringToXmlDocument(jsonText, rootElementName); } async function jsonObjectToXmlFile(json, targetPath, rootElementName = "json", encoding = "utf8") { const document = jsonObjectToXmlDocument(json, rootElementName); await (0, promises_1.writeFile)(targetPath, document.toString(), { encoding }); } async function jsonStringToXmlFile(jsonText, targetPath, rootElementName = "json", encoding = "utf8") { const document = jsonStringToXmlDocument(jsonText, rootElementName); await (0, promises_1.writeFile)(targetPath, document.toString(), { encoding }); } async function jsonFileToXmlFile(path, targetPath, rootElementName = "json", jsonEncoding = "utf8", xmlEncoding = "utf8") { const document = await jsonFileToXmlDocument(path, rootElementName, jsonEncoding); await (0, promises_1.writeFile)(targetPath, document.toString(), { encoding: xmlEncoding }); } async function jsonStreamToXmlFile(stream, targetPath, rootElementName = "json", jsonEncoding = "utf8", xmlEncoding = "utf8") { const document = await jsonStreamToXmlDocument(stream, rootElementName, jsonEncoding); await (0, promises_1.writeFile)(targetPath, document.toString(), { encoding: xmlEncoding }); } function parseXmlFromString(xml, options) { const builder = new DOMBuilder_js_1.DOMBuilder(); builder.initialize(); const parser = new SAXParser_js_1.SAXParser(); parser.setContentHandler(builder); parser.parseString(xml, options); const document = builder.getDocument(); if (!document) { throw new Error("Unable to parse XML string"); } return document; } async function parseXmlFromFile(path, encoding = "utf8") { const xmlText = await (0, promises_1.readFile)(path, { encoding }); return parseXmlFromString(xmlText); } async function parseXmlFromStream(stream, options) { const builder = new DOMBuilder_js_1.DOMBuilder(); builder.initialize(); const parser = new SAXParser_js_1.SAXParser(); parser.setContentHandler(builder); await parser.parseStream(stream, options); const document = builder.getDocument(); if (!document) { throw new Error("Unable to parse XML stream"); } return document; } function elementToJsonValue(element, config) { const attributes = element.getAttributes(); const childGroups = new Map(); const textPieces = new Array(); const cdataPieces = new Array(); const commentPieces = new Array(); const processingInstructions = new Array(); const orderedContent = config.includeOrderedContent ? new Array() : undefined; const childOccurrences = config.includeOrderedContent ? new Map() : undefined; for (const node of element.getContent()) { if (node instanceof XMLElement_js_1.XMLElement) { const childName = node.getName(); const converted = elementToJsonValue(node, config); let bucket = childGroups.get(childName); if (!bucket) { bucket = new Array(); childGroups.set(childName, bucket); } bucket.push(converted); if (orderedContent && childOccurrences) { const occurrence = childOccurrences.get(childName) ?? 0; childOccurrences.set(childName, occurrence + 1); orderedContent.push({ kind: "element", name: childName, occurrence }); } continue; } if (node instanceof TextNode_js_1.TextNode) { textPieces.push(node.getValue()); if (orderedContent) { orderedContent.push({ kind: "text", value: node.getValue() }); } continue; } if (node instanceof CData_js_1.CData) { cdataPieces.push(node.getValue()); if (orderedContent) { orderedContent.push({ kind: "cdata", value: node.getValue() }); } continue; } if (node instanceof XMLComment_js_1.XMLComment) { commentPieces.push(node.getValue()); if (orderedContent) { orderedContent.push({ kind: "comment", value: node.getValue() }); } continue; } if (node instanceof ProcessingInstruction_js_1.ProcessingInstruction) { const entry = { target: node.getTarget() }; const data = node.getData(); if (data && data.length > 0) { entry.data = data; } processingInstructions.push(entry); if (orderedContent) { orderedContent.push({ kind: "processingInstruction", target: node.getTarget(), data }); } } } const hasChildElements = childGroups.size > 0; const concatenatedText = textPieces.join(""); const hasSignificantText = textPieces.length > 0 && (!hasChildElements || textPieces.some((value) => value.trim().length > 0)); const hasAttributes = attributes.length > 0; const hasCData = cdataPieces.length > 0; const hasComments = commentPieces.length > 0; const hasProcessing = processingInstructions.length > 0; if (!hasAttributes && !hasChildElements && !hasCData && !hasComments && !hasProcessing) { if (!hasSignificantText) { return ""; } return concatenatedText; } if (!hasAttributes && !hasComments && !hasProcessing && !hasCData && !hasSignificantText && childGroups.size === 1) { const [childName, values] = [...childGroups.entries()][0]; if (childName === deriveArrayItemName(element.getName())) { return values; } } const result = {}; if (hasAttributes) { const attributeMap = {}; for (const attribute of attributes) { attributeMap[attribute.getName()] = attribute.getValue(); } result._attributes = attributeMap; } if (hasSignificantText) { result._text = concatenatedText; } if (hasCData) { result._cdata = cdataPieces.length === 1 ? cdataPieces[0] : cdataPieces; } if (hasComments) { result._comments = commentPieces.length === 1 ? commentPieces[0] : commentPieces; } if (hasProcessing) { result._processingInstructions = processingInstructions; } for (const [childName, values] of childGroups.entries()) { if (values.length === 1) { result[childName] = values[0]; } else { result[childName] = values; } } if (orderedContent && orderedContent.length > 0) { const actualOrder = orderedContent.map((entry) => { if (entry.kind === "element") { return `element:${entry.name}:${entry.occurrence}`; } return entry.kind; }); const fallbackOrder = new Array(); if (hasSignificantText) { fallbackOrder.push("text"); } if (hasCData) { for (let index = 0; index < cdataPieces.length; index++) { fallbackOrder.push("cdata"); } } if (hasComments) { for (let index = 0; index < commentPieces.length; index++) { fallbackOrder.push("comment"); } } if (hasProcessing) { for (let index = 0; index < processingInstructions.length; index++) { fallbackOrder.push("processingInstruction"); } } for (const [childName, values] of childGroups.entries()) { for (let index = 0; index < values.length; index++) { fallbackOrder.push(`element:${childName}:${index}`); } } const needsOrderedContent = actualOrder.length !== fallbackOrder.length || actualOrder.some((value, index) => value !== fallbackOrder[index]); if (needsOrderedContent) { result._content = orderedContent; } } if (!hasAttributes && !hasSignificantText && !hasCData && !hasComments && !hasProcessing && childGroups.size === 0) { return ""; } return result; } function jsonDocumentToXmlDocument(jsonDoc) { const document = new XMLDocument_js_1.XMLDocument(); if (jsonDoc.declaration) { const version = jsonDoc.declaration.version ?? "1.0"; const encoding = jsonDoc.declaration.encoding ?? "utf-8"; const declaration = new XMLDeclaration_js_1.XMLDeclaration(version, encoding, jsonDoc.declaration.standalone); document.setXmlDeclaration(declaration); } const prologNodes = jsonDoc.prolog ?? []; for (const node of prologNodes) { if (!node.afterDoctype) { appendMiscNode(document, node); } } if (jsonDoc.doctype) { const publicId = jsonDoc.doctype.publicId ?? ""; const systemId = jsonDoc.doctype.systemId ?? ""; const docType = new XMLDocumentType_js_1.XMLDocumentType(jsonDoc.doctype.name, publicId, systemId); if (jsonDoc.doctype.internalSubset) { docType.setInternalSubset(jsonDoc.doctype.internalSubset); } document.setDocumentType(docType); } for (const node of prologNodes) { if (node.afterDoctype) { appendMiscNode(document, node); } } const root = jsonValueToElement(jsonDoc.rootName, jsonDoc.root, jsonDoc.rootName); document.setRoot(root); if (jsonDoc.epilog) { for (const node of jsonDoc.epilog) { appendMiscNode(document, node); } } return document; } function appendMiscNode(document, node) { if (node.type === "comment") { document.addComment(new XMLComment_js_1.XMLComment(node.value)); } else if (node.type === "processingInstruction") { const data = node.data ?? ""; document.addProcessingInstruction(new ProcessingInstruction_js_1.ProcessingInstruction(node.target, data)); } else { document.addTextNode(new TextNode_js_1.TextNode(node.value)); } } function simpleJsonObjectToXmlDocument(value, rootElementName) { const document = new XMLDocument_js_1.XMLDocument(); const root = jsonValueToElement(rootElementName, value, rootElementName); document.setRoot(root); return document; } function inferRootElementNameFromSimpleJson(value) { if (value === null || value === undefined) { return undefined; } if (Array.isArray(value)) { return undefined; } if (typeof value !== "object") { return undefined; } const objectValue = value; const candidateKeys = Object.keys(objectValue).filter((key) => !RESERVED_KEYS.has(key)); if (candidateKeys.length !== 1) { return undefined; } return candidateKeys[0]; } function jsonValueToElement(name, value, contextName) { const element = new XMLElement_js_1.XMLElement(name); if (value === null || value === undefined) { return element; } if (Array.isArray(value)) { const itemName = deriveArrayItemName(contextName); for (const item of value) { const child = jsonValueToElement(itemName, item, itemName); element.addElement(child); } if (value.length === 0) { element.addString(""); } return element; } if (typeof value === "string") { if (value.length > 0) { element.addString(value); } return element; } if (typeof value === "number" || typeof value === "boolean") { element.addString(String(value)); return element; } const objectValue = value; if (objectValue._attributes) { for (const [attrName, attrValue] of Object.entries(objectValue._attributes)) { if (attrValue === null || attrValue === undefined) { continue; } element.setAttribute(new XMLAttribute_js_1.XMLAttribute(attrName, String(attrValue))); } } if (objectValue._content && objectValue._content.length > 0) { appendOrderedContent(element, objectValue, objectValue._content); return element; } if (objectValue._text !== undefined && objectValue._text.length > 0) { element.addString(objectValue._text); } if (objectValue._cdata !== undefined) { const cdataValues = ensureArrayOfStrings(objectValue._cdata); for (const cdata of cdataValues) { element.addCData(new CData_js_1.CData(cdata)); } } if (objectValue._comments !== undefined) { const commentValues = ensureArrayOfStrings(objectValue._comments); for (const comment of commentValues) { element.addComment(new XMLComment_js_1.XMLComment(comment)); } } if (objectValue._processingInstructions) { for (const pi of objectValue._processingInstructions) { const data = pi.data ?? ""; element.addProcessingInstruction(new ProcessingInstruction_js_1.ProcessingInstruction(pi.target, data)); } } for (const [childName, childValue] of Object.entries(objectValue)) { if (RESERVED_KEYS.has(childName)) { continue; } if (childValue === undefined) { continue; } if (Array.isArray(childValue)) { for (const entry of childValue) { const childElement = jsonValueToElement(childName, entry, childName); element.addElement(childElement); } } else { const childElement = jsonValueToElement(childName, childValue, childName); element.addElement(childElement); } } return element; } function appendOrderedContent(element, source, content) { const childCache = new Map(); for (const entry of content) { switch (entry.kind) { case "text": element.addTextNode(new TextNode_js_1.TextNode(entry.value)); break; case "cdata": element.addCData(new CData_js_1.CData(entry.value)); break; case "comment": element.addComment(new XMLComment_js_1.XMLComment(entry.value)); break; case "processingInstruction": { const data = entry.data ?? ""; element.addProcessingInstruction(new ProcessingInstruction_js_1.ProcessingInstruction(entry.target, data)); break; } case "element": { const values = getChildValues(source, entry.name, childCache); const jsonValue = values[entry.occurrence]; if (jsonValue === undefined) { break; } const childElement = jsonValueToElement(entry.name, jsonValue, entry.name); element.addElement(childElement); break; } } } } function getChildValues(source, childName, cache) { let values = cache.get(childName); if (!values) { const rawValue = source[childName]; if (rawValue === undefined) { values = []; } else if (Array.isArray(rawValue)) { if (rawValue.length > 0 && typeof rawValue[0] === "object" && rawValue[0] !== null && "kind" in rawValue[0]) { values = []; } else { values = rawValue; } } else { values = [rawValue]; } cache.set(childName, values); } return values; } function ensureArrayOfStrings(value) { return Array.isArray(value) ? value : [value]; } function deriveArrayItemName(parentName) { if (parentName.length > 3 && parentName.endsWith("ies")) { return parentName.substring(0, parentName.length - 3) + "y"; } if (parentName.length > 1 && parentName.endsWith("s")) { return parentName.substring(0, parentName.length - 1); } return "item"; } function isXmlJsonDocument(value) { if (!value || typeof value !== "object") { return false; } const candidate = value; return typeof candidate.rootName === "string" && candidate.root !== undefined; } async function readStreamToString(stream, encoding) { return new Promise((resolve, reject) => { const chunks = new Array(); stream.setEncoding(encoding); stream.on("data", (chunk) => { chunks.push(chunk); }); stream.once("error", reject); stream.once("end", () => { resolve(chunks.join("")); }); }); } //# sourceMappingURL=JsonConversion.js.map