typesxml
Version:
Open source XML library written in TypeScript
188 lines • 9.19 kB
JavaScript
;
/*******************************************************************************
* 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 });
const node_fs_1 = require("node:fs");
const node_os_1 = require("node:os");
const node_path_1 = require("node:path");
const node_stream_1 = require("node:stream");
const DOMBuilder_js_1 = require("../DOMBuilder.js");
const SAXParser_js_1 = require("../SAXParser.js");
const JsonConversion_js_1 = require("../json/JsonConversion.js");
async function runJsonConversionTests() {
const samples = [
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<!--Before root--><!--Second comment-->' +
'<!DOCTYPE note [' +
'<!ENTITY writer "John Doe">' +
'<!ELEMENT note ANY>' +
'<!ELEMENT body ANY>' +
'<!ELEMENT h:header ANY>' +
'<!ELEMENT child ANY>' +
'<!ELEMENT empty EMPTY>' +
']>' +
'<note xmlns:h="http://example.com/h" category="memo">' +
'<h:header><![CDATA[Agenda]]></h:header>' +
'<body>Don\'t forget &writer; on Monday<?pi instructions?></body>' +
'<empty />' +
'</note>',
'<?xml version="1.1" encoding="UTF-8" standalone="yes"?>\n' +
'<root attr1="value1" attr2="value2">' +
'Text node<child>Inner</child>' +
'<child xml:lang="en">More text</child>' +
'<![CDATA[raw <content>]]>' +
'<?processing data?>' +
'<!--After content-->' +
'</root>'
];
for (let index = 0; index < samples.length; index++) {
const label = `Sample ${index + 1}`;
await assertStructuredRoundTrip(samples[index], label);
}
await assertPlainObjectRoundTrip();
await assertRootInference();
await assertFileConversions(samples[0]);
await assertStreamConversions(samples[1]);
console.log("JSON conversion tests passed.");
}
async function assertStructuredRoundTrip(xmlText, label) {
const originalDocument = parseXml(xmlText);
const simpleJson = (0, JsonConversion_js_1.xmlStringToJsonObject)(xmlText);
if (typeof simpleJson !== "object" || simpleJson === null || Array.isArray(simpleJson)) {
throw new Error(`Expected simple conversion to yield object JSON for ${label}`);
}
if ("rootName" in simpleJson || "prolog" in simpleJson || "doctype" in simpleJson) {
throw new Error(`Simple conversion leaked document metadata for ${label}`);
}
const jsonDocument = (0, JsonConversion_js_1.xmlStringToJsonObject)(xmlText, { mode: "roundtrip" });
if (jsonDocument.rootName !== originalDocument.getRoot()?.getName()) {
throw new Error(`Root name mismatch for ${label}`);
}
const rebuiltDocument = (0, JsonConversion_js_1.jsonObjectToXmlDocument)(jsonDocument);
if (!originalDocument.equals(rebuiltDocument)) {
throw new Error(`XML -> JSON -> XML comparison failed for ${label}`);
}
if (jsonDocument.prolog && jsonDocument.prolog.length === 0) {
throw new Error(`Expected prolog entries for ${label}`);
}
if (jsonDocument.declaration && jsonDocument.declaration.version === undefined) {
throw new Error(`Missing declaration details for ${label}`);
}
}
async function assertPlainObjectRoundTrip() {
const source = {
library: "painters",
books: ["DaVinci", "VanGogh", "Rubens"],
prices: [13000, 5000, 20000]
};
const document = (0, JsonConversion_js_1.jsonObjectToXmlDocument)(source, "libraryCatalog");
const simpleRoundTrip = (0, JsonConversion_js_1.xmlDocumentToJsonObject)(document);
if (typeof simpleRoundTrip !== "object" || simpleRoundTrip === null || Array.isArray(simpleRoundTrip)) {
throw new Error("Simple round-trip should yield an object");
}
if ("rootName" in simpleRoundTrip) {
throw new Error("Simple round-trip should not include root metadata");
}
const roundTrip = (0, JsonConversion_js_1.xmlDocumentToJsonObject)(document, { mode: "roundtrip" });
const root = roundTrip.root;
if (!root || Array.isArray(root) || typeof root !== "object") {
throw new Error("Unexpected JSON structure for plain object round-trip");
}
const rootObject = root;
if (rootObject.library !== "painters") {
throw new Error("Library value mismatch after round-trip");
}
const booksEntry = rootObject.books;
if (!Array.isArray(booksEntry) || booksEntry.some((item) => typeof item !== "string") || booksEntry.length !== 3) {
throw new Error("Books array mismatch after round-trip");
}
const pricesEntry = rootObject.prices;
if (!Array.isArray(pricesEntry) || pricesEntry.some((entry) => typeof entry !== "string")) {
throw new Error("Prices array expected string representations");
}
}
async function assertRootInference() {
const source = {
library: {
books: ["DaVinci", "VanGogh", "Rubens"]
}
};
const document = (0, JsonConversion_js_1.jsonObjectToXmlDocument)(source);
const root = document.getRoot();
if (!root || root.getName() !== "library") {
throw new Error("Expected root element name to be inferred from single property");
}
const roundTripJson = (0, JsonConversion_js_1.xmlDocumentToJsonObject)(document);
if (typeof roundTripJson !== "object" || roundTripJson === null || Array.isArray(roundTripJson)) {
throw new Error("Round-tripped JSON should remain an object");
}
if (!("library" in roundTripJson)) {
throw new Error("Round-tripped JSON missing inferred property");
}
}
async function assertFileConversions(xmlText) {
const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "typesxml-json-files-"));
const xmlInputPath = (0, node_path_1.join)(tempDir, "input.xml");
(0, node_fs_1.writeFileSync)(xmlInputPath, xmlText, "utf8");
const jsonPath = (0, node_path_1.join)(tempDir, "output.json");
await (0, JsonConversion_js_1.xmlFileToJsonFile)(xmlInputPath, jsonPath, "utf8", 2, "utf8", { mode: "roundtrip" });
const parsedJson = JSON.parse((0, node_fs_1.readFileSync)(jsonPath, "utf8"));
const rebuiltDocument = (0, JsonConversion_js_1.jsonObjectToXmlDocument)(parsedJson);
const originalDocument = parseXml(xmlText);
if (!originalDocument.equals(rebuiltDocument)) {
throw new Error("File-based XML -> JSON -> XML comparison failed");
}
const rebuiltXmlPath = (0, node_path_1.join)(tempDir, "roundtrip.xml");
await (0, JsonConversion_js_1.jsonFileToXmlFile)(jsonPath, rebuiltXmlPath);
const roundTripDocument = parseXml((0, node_fs_1.readFileSync)(rebuiltXmlPath, "utf8"));
if (!originalDocument.equals(roundTripDocument)) {
throw new Error("JSON file -> XML file comparison failed");
}
const jsonStringPath = (0, node_path_1.join)(tempDir, "string.json");
await (0, JsonConversion_js_1.xmlStringToJsonFile)(xmlText, jsonStringPath, { mode: "roundtrip" });
const jsonDocument = JSON.parse((0, node_fs_1.readFileSync)(jsonStringPath, "utf8"));
await (0, JsonConversion_js_1.jsonObjectToXmlFile)(jsonDocument, (0, node_path_1.join)(tempDir, "from-string.xml"));
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
}
async function assertStreamConversions(xmlText) {
const xmlDocument = parseXml(xmlText);
const xmlStream = node_stream_1.Readable.from([xmlText]);
const jsonFromStream = await (0, JsonConversion_js_1.xmlStreamToJsonObject)(xmlStream, { mode: "roundtrip" });
const rebuiltFromStream = (0, JsonConversion_js_1.jsonObjectToXmlDocument)(jsonFromStream);
if (!xmlDocument.equals(rebuiltFromStream)) {
throw new Error("XML stream round-trip failed");
}
const jsonText = JSON.stringify(jsonFromStream);
const jsonStream = node_stream_1.Readable.from([jsonText]);
const documentFromJsonStream = await (0, JsonConversion_js_1.jsonStreamToXmlDocument)(jsonStream);
if (!xmlDocument.equals(documentFromJsonStream)) {
throw new Error("JSON stream round-trip failed");
}
}
function parseXml(xmlText) {
const parser = new SAXParser_js_1.SAXParser();
const builder = new DOMBuilder_js_1.DOMBuilder();
builder.initialize();
parser.setContentHandler(builder);
parser.setValidating(false);
parser.parseString(xmlText);
const document = builder.getDocument();
if (!document) {
throw new Error("Unable to parse XML text");
}
return document;
}
runJsonConversionTests().catch((error) => {
console.error("JSON conversion tests failed:", error);
process.exitCode = 1;
});
//# sourceMappingURL=JsonConversionTest.js.map