diff --git a/concurrency-test/bench.js b/concurrency-test/bench.js new file mode 100644 index 00000000..a1ad8aca --- /dev/null +++ b/concurrency-test/bench.js @@ -0,0 +1,73 @@ +import { XMLParser } from "fast-xml-parser"; +import { performance } from "node:perf_hooks"; + +// Generate a bigger XML payload (tune SIZE if needed) +const ITEMS = 50000; // try 5k, 20k, 50k if needed +const xml = + `` + + Array.from({ length: ITEMS }, (_, i) => `${i}test`).join("") + + ``; + +function parseWithNewParser() { + const parser = new XMLParser(); + return parser.parse(xml); +} + +const sharedParser = new XMLParser(); +function parseWithSharedParser() { + return sharedParser.parse(xml); +} + +async function runScenario({ label, iterations, concurrency, shared }) { + // Create tasks to run in batches of `concurrency` + const parseFn = shared ? parseWithSharedParser : parseWithNewParser; + + const t0 = performance.now(); + + for (let i = 0; i < iterations; i += concurrency) { + const batchSize = Math.min(concurrency, iterations - i); + await Promise.all( + Array.from({ length: batchSize }, () => + Promise.resolve().then(() => parseFn()) + ) + ); + } + + const t1 = performance.now(); + return { label, ms: t1 - t0 }; +} + +function fmt(ms) { + return `${(ms / 1000).toFixed(3)}s`; +} + +async function main() { + const rounds = 3; // average a few runs + const iterations = 50; // increase if results are too noisy + const concurrencies = [1, 2, 5, 10, 25, 50]; + + console.log(`Node: ${process.version}`); + console.log(`ITEMS: ${ITEMS}, iterations: ${iterations}, rounds: ${rounds}\n`); + + for (const shared of [true, false]) { + console.log(shared ? "=== Shared parser instance ===" : "=== New parser per task ==="); + + for (const c of concurrencies) { + const results = []; + for (let r = 0; r < rounds; r++) { + const out = await runScenario({ + label: `c=${c}`, + iterations, + concurrency: c, + shared, + }); + results.push(out.ms); + } + const avg = results.reduce((a, b) => a + b, 0) / results.length; + console.log(`concurrency ${String(c).padStart(2)} avg: ${fmt(avg)} (runs: ${results.map(fmt).join(", ")})`); + } + console.log(""); + } +} + +main(); diff --git a/concurrency-test/test.js b/concurrency-test/test.js new file mode 100644 index 00000000..ac5aafc5 --- /dev/null +++ b/concurrency-test/test.js @@ -0,0 +1,33 @@ +import { XMLParser } from "fast-xml-parser"; + +const parser = new XMLParser(); + +const xml = ` + + ${Array.from({ length: 1000 }) + .map((_, i) => `${i}test`) + .join("")} + +`; + +function syncParse() { + parser.parse(xml); +} + +function asyncParse() { + return Promise.resolve().then(() => parser.parse(xml)); +} + +async function runTest() { + console.time("sync"); + for (let i = 0; i < 1000; i++) syncParse(); + console.timeEnd("sync"); + + console.time("promiseAll"); + await Promise.all( + Array.from({ length: 1000 }, () => asyncParse()) + ); + console.timeEnd("promiseAll"); +} + +runTest(); diff --git a/docs/v4/3.XMLBuilder.md b/docs/v4/3.XMLBuilder.md index 20032e6a..60769770 100644 --- a/docs/v4/3.XMLBuilder.md +++ b/docs/v4/3.XMLBuilder.md @@ -545,3 +545,37 @@ Output [> Next: XmlValidator](./4.XMLValidator.md) + +## Round-tripping XML → JSON → XML (XML declaration) + +If your input XML contains a declaration like ``, the parser may include it in the output JSON as a `"?xml"` property (unless disabled). +When converting the JSON back to XML, you may want to omit this node to avoid invalid output (e.g., “XML declaration allowed only at the start of the document”). + +### Recommended approaches + +**Option A (recommended): ignore the declaration while parsing** +```js +import { XMLParser, XMLBuilder } from "fast-xml-parser"; + +const parser = new XMLParser({ ignoreDeclaration: true }); +const builder = new XMLBuilder(); + +const obj = parser.parse(xmlInput); +const xmlOutput = builder.build(obj); + +**Option B: Remove the XML declaration node before building** + +If you need to preserve the XML declaration during parsing but want to prevent it from being re-emitted during JSON → XML conversion, you can remove the `"?xml"` node before building: + +```js +import { XMLParser, XMLBuilder } from "fast-xml-parser"; + +const parser = new XMLParser(); +const builder = new XMLBuilder(); + +const obj = parser.parse(xmlInput); + +// Remove XML declaration before building +delete obj["?xml"]; + +const xmlOutput = builder.build(obj);