typescript-xml
v1.8.0
Published
A TypeScript XML parser library
Readme
typescript-xml
typescript-xml is a lightweight and intuitive TypeScript library for parsing, traversing, querying, and building XML documents. It provides a simple DOM-like API that makes working with XML feel natural in modern TypeScript projects.
Features
- Parse Well-Formed XML: Reliably parse any standard-compliant XML string into a navigable tree structure.
- Traverse the Tree: Easily navigate the XML hierarchy with powerful properties like
parentElement,children, andattributes. - Query Elements: Quickly find elements using simple methods like
getElement(name),findElements(name), andfindAllElements(name). - Manipulate the DOM: Full support for adding, removing, and replacing nodes to dynamically modify the XML structure.
- Serialize to String: Convert your XML document back to a string, with options for pretty-printing.
- Build Documents Programmatically: Use a fluent builder API to construct complex XML structures from scratch.
- Full Type Support: Includes strong TypeScript definitions for all major XML node types, including
Element,Text,Comment,CDATA,ProcessingInstruction, andDoctype. - Event-Based (SAX-style) Parsing: Efficiently parse very large XML files by processing events as they occur, without loading the entire document into memory.
- XML Schema (XSD) Validation: Validate XML documents against XSD schemas to ensure structural correctness and data integrity.
- DTD Support: Parse and work with XML documents that include Document Type Definitions (DTDs).
Installation
Install the package using your favorite package manager:
# With npm
npm install typescript-xml
# With pnpm
pnpm add typescript-xmlParsing a Document
To get started, use the static XmlDocument.parse() method to convert an XML string into a document object.
const xmlString = `
<rss version="2.0">
<channel>
<title>My Feed</title>
<item>
<title>Item 1</title>
</item>
</channel>
</rss>
`;
const document = XmlDocument.parse(xmlString);If the input string is not well-formed, the parser will throw an Error.
Traversing the Tree
Once parsed, you can easily navigate the document tree.
Accessing Nodes
Start from the rootElement to begin traversal. From any element, you can access its children, attributes, and parentElement.
// Get the root element: <rss>
const rssElement = document.rootElement;
// Get the <channel> element
const channelElement = rssElement.getElement('channel');
if (channelElement) {
// Get all <item> elements within <channel>
const items = channelElement.findElements('item');
console.log(`Found ${items.length} item(s).`);
// Access the text content of the <title> inside the first item
const firstItemTitle = items[0]?.getElement('title')?.innerText;
console.log('First item title:', firstItemTitle); // 'Item 1'
}Finding Elements
The library provides three methods to locate elements:
getElement(name): Returns the first direct child element with the given name, orundefinedif not found.findElements(name): Finds direct children of the current node with the provided tag name.findAllElements(name): Finds direct and indirect children of the current node with the provided tag name, searching recursively.
Serializing to a String
To convert your XmlDocument (or any XmlNode) back into a string, use the toXmlString() method. This is useful for saving changes or for sending XML data.
// Get the XML as a compact string
const compactXml = document.toXmlString();
// Or get it nicely formatted
const prettyXml = document.toXmlString({ pretty: true });
console.log(prettyXml);Event-Based (SAX-Style) Parsing
For very large XML files, loading the entire document into memory can be inefficient. typescript-xml provides an event-based (SAX-style) parser that emits events as it reads the XML stream. This approach is memory-efficient and ideal for processing large datasets.
Use the XmlSaxParser and provide callbacks for the events you want to handle:
const saxParser = new XmlSaxParser({
onStartElement: (event) => {
console.log(`Start Element: ${event.name}`);
if (event.attributes.length > 0) {
const attrs = event.attributes
.map((attr) => `${attr.name}="${attr.value}"`)
.join(', ');
console.log(` Attributes: ${attrs}`);
}
},
onEndElement: (event) => {
console.log(`End Element: ${event.name}`);
},
onText: (event) => {
const trimmedText = event.value.trim();
if (trimmedText) {
console.log(`Text: "${trimmedText}"`);
}
},
});
const xmlStream = `
<library>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
</book>
</library>
`;
saxParser.parse(xmlStream);Complex Example: Processing a Bookshelf
Here is a more advanced example that showcases combining methods to process a collection of data.
const bookshelfXml = `
<bookshelf>
<book>
<title lang="en">The Hitchhiker's Guide to the Galaxy</title>
<price>12.50</price>
</book>
<book>
<title lang="en">The Lord of the Rings</title>
<price>22.99</price>
</book>
<book>
<title lang="de">Die Verwandlung</title>
<price>9.95</price>
</book>
</bookshelf>
`;
const bookshelfDoc = XmlDocument.parse(bookshelfXml);
const bookshelfRoot = bookshelfDoc.rootElement;
// 1. Find all books and calculate the total price
const allBooks = bookshelfRoot.findAllElements('book');
const totalCost = allBooks
.map(book => {
const priceElement = book.getElement('price');
return priceElement ? parseFloat(priceElement.innerText) : 0;
})
.reduce((sum, price) => sum + price, 0);
console.log(`Total cost: $${totalCost.toFixed(2)}`); // Total cost: $45.44
// 2. Find all English books and log their titles
const englishBooks = bookshelfRoot.findAllElements('book')
.filter(book => {
const title = book.getElement('title');
return title?.getAttribute('lang') === 'en';
});
console.log('English titles:');
englishBooks.forEach(book => {
console.log(`- ${book.getElement('title')?.innerText}`);
});
// 3. Add a new book to the bookshelf
const newBook = new XmlElement('book');
const newTitle = new XmlElement('title');
newTitle.setAttribute('lang', 'en');
newTitle.children.push(new XmlText('Dune'));
const newPrice = new XmlElement('price');
newPrice.children.push(new XmlText('15.00'));
newBook.children.push(newTitle, newPrice);
bookshelfRoot.children.push(newBook);
console.log('\n--- Updated Bookshelf ---');
console.log(bookshelfDoc.toXmlString({ pretty: true }));Building XML
For more complex XML creation, XmlBuilder offers a declarative and fluent API to construct documents programmatically. This is especially useful when building documents from data or when the structure is dynamic.
const builder = new XmlBuilder();
builder.element('items', {
nest: () => {
for (let i = 1; i <= 3; i++) {
builder.element('item', {
nest: () => {
builder.attribute('id', `item-${i}`);
builder.element('name', { nest: `Item #${i}` });
builder.element('price', { nest: (i * 1.5).toFixed(2) });
},
});
}
},
});
const doc = builder.buildDocument();
console.log(doc.toXmlString({ pretty: true, indent: ' ' }));This will produce the following output:
<items>
<item id="item-1">
<name>Item #1</name>
<price>1.50</price>
</item>
<item id="item-2">
<name>Item #2</name>
<price>3.00</price>
</item>
<item id="item-3">
<name>Item #3</name>
<price>4.50</price>
</item>
</items>XML Validation
typescript-xml provides validation capabilities to ensure your XML documents conform to their schemas and document type definitions.
XSD (XML Schema) Validation
You can validate XML documents against XSD schemas using the XsdValidator class:
import { XmlDocument, XsdValidator } from 'typescript-xml';
// Your XSD schema
const xsdSchema = `
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="bookshelf">
<xs:complexType>
<xs:sequence>
<xs:element name="book" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="lang" type="xs:string" use="required" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="price" type="xs:decimal" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="price" type="xs:decimal" minOccurs="0" maxOccurs="1" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
`;
// Your XML document
const xmlDocument = `
<bookshelf>
<book>
<title lang="en">The Hitchhiker's Guide to the Galaxy</title>
<price>12.50</price>
</book>
<price>132.00</price>
</bookshelf>
`;
// Parse and validate
const document = XmlDocument.parse(xmlDocument);
const validator = new XsdValidator(xsdSchema);
try {
validator.validate(document.rootElement);
console.log('✅ XML is valid according to the XSD schema');
} catch (error) {
console.error('❌ Validation failed:', error.message);
}You can also use the document's built-in validation method:
const document = XmlDocument.parse(xmlDocument);
const validator = new XsdValidator(xsdSchema);
try {
document.validate(validator);
console.log('✅ Document is valid');
} catch (error) {
console.error('❌ Validation failed:', error.message);
}DTD Validation
For documents with DTDs, validation can be performed automatically if the document includes a DOCTYPE declaration:
const xmlWithDtd = `
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
`;
const document = XmlDocument.parse(xmlWithDtd);
try {
document.validate(); // Automatically uses the DTD from the document
console.log('✅ Document is valid according to its DTD');
} catch (error) {
console.error('❌ DTD validation failed:', error.message);
}Limitations
typescript-xml is designed for simplicity and ease of use. To maintain its lightweight footprint, it does not currently support:
- 🚫 XPath or XSLT transformations.
- ⚠️ DTD validation is partially implemented (DTDs can be parsed but validation logic is not yet complete).
Validation Support Status
- ✅ XSD (XML Schema) Validation: Fully supported with comprehensive validation of elements, attributes, sequences, choices, and occurrence constraints.
- ⚠️ DTD Validation: DTD parsing is supported, but validation logic is still in development.
Credits
This library is a faithful port of the dart-xml package, created by Lukas Renggli. A huge thank you to him for the original design and implementation.
License
This project is licensed under the ISC License. See the LICENSE file for details.
