xmldoc: Lightweight XML Document Parser
xmldoc is a lightweight JavaScript library designed for parsing and traversing XML documents. It provides a simple, object-oriented API for interacting with XML structures, backed by the robust `sax` parser. The current stable version is 2.0.3, with releases occurring as needed for bug fixes and feature enhancements, rather than on a strict cadence. A key differentiator is its minimal footprint and direct access to XML elements and attributes, avoiding more complex XPath implementations by default. Since version 2.0, it fully supports TypeScript, offering type definitions for a better developer experience, and is compatible with both CommonJS and ESM environments. It focuses on parsing and basic traversal, making it suitable for applications where full XML DOM manipulation or complex querying (like advanced XPath) is not required.
Common errors
-
TypeError: XmlDocument is not a constructor
cause Attempting to use `require('xmldoc')` directly without destructuring, or `import XmlDocument from 'xmldoc'` in v2.0+ (where `XmlDocument` is a named export).fixFor CommonJS, use `const { XmlDocument } = require('xmldoc');`. For ESM, use `import { XmlDocument } from 'xmldoc';`. -
ReferenceError: GLOBAL is not defined
cause Running an older version of `xmldoc` (specifically pre-0.5.1) in Node.js v6 or later, which deprecated/removed the global `GLOBAL` object.fixUpgrade `xmldoc` to version `0.5.1` or higher to resolve the `GLOBAL` deprecation warning/error in newer Node.js environments. Alternatively, ensure your Node.js version is compatible with the `xmldoc` version in use. -
Output XML is missing comments or has incorrect escaping for '<' or '>' characters when calling toString().
cause Comments are explicitly not preserved by `xmldoc`'s `toString()` method. Incorrect escaping (e.g., for `<` and `>`) was a bug in older versions (pre-0.5.0).fixComments are not preserved by design. If you are experiencing incorrect character escaping, upgrade `xmldoc` to `v0.5.0` or newer, which fixed the incorrect escaping issue.
Warnings
- breaking Prior to v0.3.1, `xmldoc` methods returning optional values (e.g., `firstChild`, `childNamed`, `attr` for non-existent attributes) would return `null`. As of v0.3.1, these methods now consistently return `undefined`.
- breaking Version 2.0 introduced full TypeScript support and updated CommonJS/ESM compatibility. This changed the primary export pattern from potentially a default export (or other patterns in older versions) to named exports for `XmlDocument`. Older `require('xmldoc')` or `import XmlDocument from 'xmldoc'` patterns may no longer work.
- gotcha When converting an `XmlDocument` or `XmlElement` back to an XML string using `toString()`, XML comments are not preserved in the output. This is a design decision of the library.
- gotcha While `xmldoc` generally attempts to maintain the order of child elements, there have been reports (e.g., GitHub Issue #41) where the order of elements might be unexpectedly altered, particularly in specific scenarios or during internal transformations.
Install
-
npm install xmldoc -
yarn add xmldoc -
pnpm add xmldoc
Imports
- XmlDocument
import XmlDocument from 'xmldoc';
import { XmlDocument } from 'xmldoc'; - XmlDocument
const XmlDocument = require('xmldoc');const { XmlDocument } = require('xmldoc'); - XmlElement
import { XmlElement } from 'xmldoc';
Quickstart
import { XmlDocument, XmlElement } from "xmldoc";
const xmlString = `
<bookstore>
<book category="cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
<!-- A comment about Italian cuisine -->
</book>
<book category="children">
<title lang="en">Harry Potter</title>
<author>J.K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<magazine>
<title>National Geographic</title>
</magazine>
</bookstore>
`;
// Parse the XML string
const document = new XmlDocument(xmlString);
// Find all books
const books = document.childrenNamed("book");
console.log(`Found ${books.length} books.`);
// Access attributes and text content of the first book
const firstBook = books[0];
if (firstBook) {
console.log(`First book title: ${firstBook.childNamed("title")?.val} (Lang: ${firstBook.childNamed("title")?.attr.lang})`);
console.log(`First book author: ${firstBook.childNamed("author")?.val}`);
console.log(`First book category: ${firstBook.attr.category}`);
}
// Find a specific element by path
const harryPotterTitle = document.childNamed("bookstore")
?.childrenNamed("book")
.find(book => book.childNamed("title")?.val === "Harry Potter")
?.childNamed("title");
if (harryPotterTitle) {
console.log(`Harry Potter title found: ${harryPotterTitle.val}`);
}
// Convert back to string (note: comments might be lost depending on version)
const serializedXml = document.toString({ compressed: true }); // compressed option for brevity
console.log("Serialized XML (compressed):", serializedXml.substring(0, 100) + "..."); // show first 100 chars