UNPKG

partial-xml-stream-parser

Version:

A lenient XML stream parser for Node.js and browsers that can handle incomplete or malformed XML data, with depth control, CDATA support for XML serialization and round-trip parsing, wildcard pattern support for stopNodes, and CDATA handling within stopNo

239 lines 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const index_1 = require("../index"); const perf_hooks_1 = require("perf_hooks"); /** * Calculates statistical measures for a list of durations. */ function calculateStats(durations) { if (!durations || durations.length === 0) { return { min: 0, max: 0, median: 0, p95: 0, p99: 0, mean: 0, stdDev: 0 }; } const sorted = [...durations].sort((a, b) => a - b); const N = sorted.length; const mean = durations.reduce((a, b) => a + b, 0) / N; const p95Index = Math.max(0, Math.min(N - 1, Math.floor(N * 0.95))); const p99Index = Math.max(0, Math.min(N - 1, Math.floor(N * 0.99))); const medianIndex = Math.max(0, Math.min(N - 1, Math.floor(N / 2))); return { min: sorted[0], max: sorted[N - 1], median: sorted[medianIndex], p95: sorted[p95Index], p99: sorted[p99Index], mean, stdDev: N > 0 ? Math.sqrt(durations.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b, 0) / N) : 0, }; } /** * Gets an appropriate iteration count for benchmarks based on input size. */ function getIterationCount(size) { if (size <= 10000) return 100; // ~10KB if (size <= 100000) return 20; // ~100KB if (size <= 500000) return 10; // ~500KB return 5; // For very large tests } /** * Runs a benchmark for a given operation and logs time and heap statistics. */ function runParserBenchmark(name, benchmarkFn, xmlInputString) { const inputLength = xmlInputString ? xmlInputString.length : 1000; const iterations = getIterationCount(inputLength); console.log(`\n--- Benchmark: ${name} (${iterations} iterations) ---`); // Warm-up try { benchmarkFn(); } catch (e) { console.error(`Warm-up for "${name}" failed:`, e); return; } if (global.gc) { global.gc(); } const initialHeap = process.memoryUsage().heapUsed; const durations = []; for (let i = 0; i < iterations; i++) { try { const startTime = perf_hooks_1.performance.now(); benchmarkFn(); const endTime = perf_hooks_1.performance.now(); durations.push(endTime - startTime); } catch (e) { console.error(`Iteration ${i + 1} for "${name}" failed:`, e); durations.push(NaN); } } const validDurations = durations.filter((d) => !isNaN(d)); if (global.gc) { global.gc(); } const finalHeap = process.memoryUsage().heapUsed; if (validDurations.length === 0 && durations.length > 0) { console.log(" All iterations failed. No time statistics available."); } else if (validDurations.length < durations.length) { console.log(` ${durations.length - validDurations.length} iterations failed. Statistics from ${validDurations.length} successful iterations:`); } const stats = calculateStats(validDurations); const totalTime = validDurations.reduce((a, b) => a + b, 0); console.log(" Time Metrics:"); console.log(` Total: ${totalTime.toFixed(3)} ms (for ${validDurations.length} successful iterations)`); console.log(` Mean: ${stats.mean.toFixed(3)} ms`); console.log(` Min: ${stats.min.toFixed(3)} ms, Max: ${stats.max.toFixed(3)} ms, Median: ${stats.median.toFixed(3)} ms`); console.log(` P95: ${stats.p95.toFixed(3)} ms, P99: ${stats.p99.toFixed(3)} ms, StdDev: ${stats.stdDev.toFixed(3)} ms`); console.log(" Heap Metrics:"); const toMB = (bytes) => (bytes / 1024 / 1024).toFixed(3); console.log(` Initial: ${toMB(initialHeap)} MB`); console.log(` Final: ${toMB(finalHeap)} MB`); console.log(` Delta: ${toMB(finalHeap - initialHeap)} MB`); } (0, vitest_1.describe)("PartialXMLStreamParser Benchmarks", () => { const simpleXML = '<root><item id="1">Text1</item><item id="2">Text2</item></root>'; const complexXML = ` <data store="main"> <book category="COOKING"> <title lang="en">Everyday Italian</title> <author>Giada De Laurentiis</author> <year>2005</year> <price>30.00</price> </book> <book category="CHILDREN"> <title lang="en">Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book> <book category="WEB"> <title lang="en">Learning XML</title> <author>Erik T. Ray</author> <year>2003</year> <price>39.95</price> </book> <misc type="info">Some additional info here</misc> </data> `; const multiRootXML = "<item>A</item><item>B</item><item>C</item><item>D</item><item>E</item>"; const cdataXML = "<root><data>This is some <CDATA> content with & symbols that should be preserved.</data></root>"; const stopNodeXML = '<root><script type="text/javascript">function greet(){ console.log("hello <world>"); }</script><content>Parse me</content></root>'; const generateLargeXML = (numItems) => { let xml = "<largeRoot>"; for (let i = 0; i < numItems; i++) { xml += `<item id="${i}">`; xml += `<name>Item Name ${i}</name>`; xml += `<value>${Math.random() * 1000}</value>`; xml += `<description>This is a description for item ${i}. It contains some text to make it a bit larger.</description>`; xml += `<nested><subitem attr="sub${i}">Sub Value ${i}</subitem></nested>`; xml += "</item>"; } xml += "</largeRoot>"; return xml; }; const largeXMLString = generateLargeXML(1000); // 1000 items (0, vitest_1.test)("Parse simple XML (single chunk)", () => { runParserBenchmark("Parse simple XML (single chunk)", () => { const parser = new index_1.PartialXMLStreamParser(); parser.parseStream(simpleXML); parser.parseStream(null); }, simpleXML); }); (0, vitest_1.test)("Parse complex XML (single chunk)", () => { runParserBenchmark("Parse complex XML (single chunk)", () => { const parser = new index_1.PartialXMLStreamParser(); parser.parseStream(complexXML); parser.parseStream(null); }, complexXML); }); (0, vitest_1.test)("Parse simple XML (multiple chunks)", () => { runParserBenchmark("Parse simple XML (multiple chunks)", () => { const parser = new index_1.PartialXMLStreamParser(); const chunks = simpleXML.match(/.{1,10}/g) || [simpleXML]; chunks.forEach((chunk) => parser.parseStream(chunk)); parser.parseStream(null); }, simpleXML); }); (0, vitest_1.test)("Parse complex XML (multiple chunks)", () => { runParserBenchmark("Parse complex XML (multiple chunks)", () => { const parser = new index_1.PartialXMLStreamParser(); const chunks = complexXML.match(/.{1,50}/g) || [complexXML]; chunks.forEach((chunk) => parser.parseStream(chunk)); parser.parseStream(null); }, complexXML); }); (0, vitest_1.test)("Parse XML with multiple root elements", () => { runParserBenchmark("Parse XML with multiple root elements", () => { const parser = new index_1.PartialXMLStreamParser(); parser.parseStream(multiRootXML); parser.parseStream(null); }, multiRootXML); }); (0, vitest_1.test)("Parse XML with CDATA", () => { runParserBenchmark("Parse XML with CDATA", () => { const parser = new index_1.PartialXMLStreamParser(); parser.parseStream(cdataXML); parser.parseStream(null); }, cdataXML); }); (0, vitest_1.test)("Parse XML with stop nodes", () => { runParserBenchmark("Parse XML with stop nodes", () => { const parser = new index_1.PartialXMLStreamParser({ stopNodes: ["script"] }); parser.parseStream(stopNodeXML); parser.parseStream(null); }, stopNodeXML); }); (0, vitest_1.test)("Parse XML with alwaysCreateTextNode true", () => { runParserBenchmark("Parse XML with alwaysCreateTextNode true", () => { const parser = new index_1.PartialXMLStreamParser({ alwaysCreateTextNode: true, }); parser.parseStream(complexXML); parser.parseStream(null); }, complexXML); }); (0, vitest_1.test)("Parse XML with parsePrimitives true", () => { runParserBenchmark("Parse XML with parsePrimitives true", () => { const parser = new index_1.PartialXMLStreamParser({ parsePrimitives: true }); parser.parseStream(complexXML); parser.parseStream(null); }, complexXML); }); (0, vitest_1.test)("Parse large XML (1000 items, single chunk)", () => { runParserBenchmark("Parse large XML (1000 items, single chunk)", () => { const parser = new index_1.PartialXMLStreamParser(); parser.parseStream(largeXMLString); parser.parseStream(null); }, largeXMLString); }); (0, vitest_1.test)("Parse large XML (1000 items, multiple chunks)", () => { runParserBenchmark("Parse large XML (1000 items, multiple chunks)", () => { const parser = new index_1.PartialXMLStreamParser(); const chunks = largeXMLString.match(/.{1,1024}/g) || [largeXMLString]; chunks.forEach((chunk) => parser.parseStream(chunk)); parser.parseStream(null); }, largeXMLString); }); (0, vitest_1.test)("Parse very large XML (5000 items, single chunk)", () => { const veryLargeXMLString = generateLargeXML(5000); runParserBenchmark("Parse very large XML (5000 items, single chunk)", () => { const parser = new index_1.PartialXMLStreamParser(); parser.parseStream(veryLargeXMLString); parser.parseStream(null); }, veryLargeXMLString); }); (0, vitest_1.test)("Parse very large XML (5000 items, multiple chunks)", () => { const veryLargeXMLString = generateLargeXML(5000); runParserBenchmark("Parse very large XML (5000 items, multiple chunks)", () => { const parser = new index_1.PartialXMLStreamParser(); const chunks = veryLargeXMLString.match(/.{1,1024}/g) || [veryLargeXMLString]; chunks.forEach((chunk) => parser.parseStream(chunk)); parser.parseStream(null); }, veryLargeXMLString); }); }); //# sourceMappingURL=parser.bench.spec.js.map