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
JavaScript
;
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