parse-xml
A fast, safe, compliant XML parser for Node.js and browsers.
Links
Installation
npm install @rgrove/parse-xml
Or, if you like living dangerously, you can load the minified bundle in a browser via Unpkg and use the parseXml
global.
Features
-
Returns a convenient object tree representing an XML document.
-
Works great in Node.js and browsers.
-
Provides helpful, detailed error messages with context when a document is not well-formed.
-
Mostly conforms to XML 1.0 (Fifth Edition) as a non-validating parser (see below for details).
-
Passes all relevant tests in the XML Conformance Test Suite.
-
Written in TypeScript and compiled to ES2020 JavaScript for Node.js and ES2017 JavaScript for browsers. The browser build is also optimized for minification.
-
Zero dependencies.
Not Features
While this parser is capable of parsing document type declarations (<!DOCTYPE ... >
) and including them in the node tree, it doesn't actually do anything with them. External document type definitions won't be loaded, and the parser won't validate the document against a DTD or resolve custom entity references defined in a DTD.
In addition, the only supported character encoding is UTF-8 because it's not feasible (or useful) to support other character encodings in JavaScript.
Examples
Basic Usage
ESM
import { parseXml } from '@rgrove/parse-xml';
parseXml('<kittens fuzzy="yes">I like fuzzy kittens.</kittens>');
CommonJS
const { parseXml } = require('@rgrove/parse-xml');
parseXml('<kittens fuzzy="yes">I like fuzzy kittens.</kittens>');
The result is an XmlDocument
instance containing the parsed document, with a structure that looks like this (some properties and methods are excluded for clarity; see the API docs for details):
{
type: 'document',
children: [
{
type: 'element',
name: 'kittens',
attributes: {
fuzzy: 'yes'
},
children: [
{
type: 'text',
text: 'I like fuzzy kittens.'
}
],
parent: { ... },
isRootNode: true
}
]
}
All parse-xml objects have toJSON()
methods that return JSON-serializable objects, so you can easily convert an XML document to JSON:
let json = JSON.stringify(parseXml(xml));
Friendly Errors
When something goes wrong, parse-xml throws an error that tells you exactly what happened and shows you where the problem is so you can fix it.
parseXml('<foo><bar>baz</foo>');
Output
Error: Missing end tag for element bar (line 1, column 14)
<foo><bar>baz</foo>
^
In addition to a helpful message, error objects have the following properties:
-
column Number
Column where the error occurred (1-based).
-
excerpt String
Excerpt from the input string that contains the problem.
-
line Number
Line where the error occurred (1-based).
-
pos Number
Character position where the error occurred relative to the beginning of the input (0-based).
Why another XML parser?
There are many XML parsers for Node, and some of them are good. However, most of them suffer from one or more of the following shortcomings:
-
Native dependencies.
-
Loose, non-standard parsing behavior that can lead to unexpected or even unsafe results when given input the author didn't anticipate.
-
Kitchen sink APIs that tightly couple a parser with DOM manipulation functions, a stringifier, or other tooling that isn't directly related to parsing and consuming XML.
-
Stream-based parsing. This is great in the rare case that you need to parse truly enormous documents, but can be a pain to work with when all you want is a node tree.
-
Poor error handling.
-
Too big or too Node-specific to work well in browsers.
parse-xml's goal is to be a small, fast, safe, compliant, non-streaming, non-validating, browser-friendly parser, because I think this is an under-served niche.
I think parse-xml demonstrates that it's not necessary to jettison the spec entirely or to write complex code in order to implement a small, fast XML parser.
Also, it was fun.
Benchmark
Here's how parse-xml's performance stacks up against a few comparable libraries:
- fast-xml-parser, which claims to be the fastest pure JavaScript XML parser
- libxmljs2, which is based on the native libxml library written in C
- xmldoc, which is based on sax-js
While libxmljs2 is faster at parsing medium and large documents, its performance comes at the expense of a large C dependency, no browser support, and a history of security vulnerabilities in the underlying libxml2 library.
In these results, "ops/s" refers to operations per second. Higher is faster.
Node.js v18.14.0 / Darwin arm64
Apple M1 Max
Running "Small document (291 bytes)" suite...
Progress: 100%
@rgrove/parse-xml 4.1.0:
191 553 ops/s, Β±0.10% | fastest
fast-xml-parser 4.1.1:
142 565 ops/s, Β±0.11% | 25.57% slower
libxmljs2 0.31.0 (native):
74 646 ops/s, Β±0.30% | 61.03% slower
xmldoc 1.2.0 (sax-js):
66 823 ops/s, Β±0.09% | slowest, 65.12% slower
Finished 4 cases!
Fastest: @rgrove/parse-xml 4.1.0
Slowest: xmldoc 1.2.0 (sax-js)
Running "Medium document (72081 bytes)" suite...
Progress: 100%
@rgrove/parse-xml 4.1.0:
1 065 ops/s, Β±0.11% | 49.81% slower
fast-xml-parser 4.1.1:
637 ops/s, Β±0.12% | 69.98% slower
libxmljs2 0.31.0 (native):
2 122 ops/s, Β±2.48% | fastest
xmldoc 1.2.0 (sax-js):
444 ops/s, Β±0.36% | slowest, 79.08% slower
Finished 4 cases!
Fastest: libxmljs2 0.31.0 (native)
Slowest: xmldoc 1.2.0 (sax-js)
Running "Large document (1162464 bytes)" suite...
Progress: 100%
@rgrove/parse-xml 4.1.0:
93 ops/s, Β±0.10% | 53.27% slower
fast-xml-parser 4.1.1:
48 ops/s, Β±0.60% | 75.88% slower
libxmljs2 0.31.0 (native):
199 ops/s, Β±1.47% | fastest
xmldoc 1.2.0 (sax-js):
38 ops/s, Β±0.09% | slowest, 80.9% slower
Finished 4 cases!
Fastest: libxmljs2 0.31.0 (native)
Slowest: xmldoc 1.2.0 (sax-js)
See the parse-xml-benchmark repo for instructions on how to run this benchmark yourself.