Back to updates
New releaseAug 9, 2026

xmloxide v0.5.0

A pure Rust reimplementation of libxml2

Share

xmloxide

CI crates.io docs.rs License: MIT MSRV

A pure Rust reimplementation of libxml2 — the de facto standard XML/HTML parsing library in the open-source world.

libxml2 became officially unmaintained in December 2025 with known security issues. xmloxide aims to be a memory-safe, high-performance replacement that passes the same conformance test suites.

Features

  • Memory-safe — arena-based tree with zero unsafe in the public API
  • Conformant — 100% pass rate on the W3C XML Conformance Test Suite (1727/1727 applicable tests)
  • Error recovery — parse malformed XML and still produce a usable tree, just like libxml2
  • Multiple parsing APIs — DOM tree, SAX2 streaming, XmlReader pull, push/incremental
  • HTML parser — error-tolerant HTML 4.01 parsing with auto-closing and void elements
  • WHATWG HTML5 parser — full HTML Living Standard tokenizer and tree builder (8810/8810 html5lib-tests passing)
  • HTML5 streaming — SAX-like callback API for HTML5 (html5::sax) that wraps the tokenizer without building a DOM tree
  • CSS selectors — query elements with familiar CSS syntax (css::select) including combinators, pseudo-classes, and fast #id lookup
  • XPath 1.0+ — full expression parser and evaluator with all XPath 1.0 core functions plus key XPath 2.0 functions (matches(), replace(), tokenize(), upper-case(), lower-case(), abs(), min(), max(), and more)
  • Validation — DTD, RelaxNG, XML Schema (XSD), and ISO Schematron (ISO/IEC 19757-3) validation
  • Serde integration — optional serde feature for XML (de)serialization to/from Rust types
  • Async parsing — optional async feature for parsing from tokio::io::AsyncRead sources
  • Canonical XML — C14N 1.0 and Exclusive C14N serialization
  • XInclude — document inclusion processing
  • XML Catalogs — OASIS XML Catalogs for URI resolution
  • xmllint CLI — command-line tool for parsing, validating, and querying XML
  • Zero-copy where possible — string interning for fast comparisons
  • No global state — each Document is self-contained and Send + Sync
  • C/C++ FFI — full C API with header file (include/xmloxide.h) for embedding in C/C++ projects
  • Minimal dependencies — only encoding_rs (library has zero other deps; clap is CLI-only)

Quick Start

use xmloxide::Document;

let doc = Document::parse_str("<root><child>Hello</child></root>").unwrap();
let root = doc.root_element().unwrap();
assert_eq!(doc.node_name(root), Some("root"));
assert_eq!(doc.text_content(root), "Hello");

Serialization

use xmloxide::Document;
use xmloxide::serial::serialize;

let doc = Document::parse_str("<root><child>Hello</child></root>").unwrap();
let xml = serialize(&doc);
assert_eq!(xml, "<root><child>Hello</child></root>");

XPath Queries

use xmloxide::Document;
use xmloxide::xpath::{evaluate, XPathValue};

let doc = Document::parse_str("<library><book><title>Rust</title></book></library>").unwrap();
let root = doc.root_element().unwrap();
let result = evaluate(&doc, root, "count(book)").unwrap();
assert_eq!(result.to_number(), 1.0);

SAX2 Streaming

use xmloxide::sax::{parse_sax, SaxHandler, DefaultHandler};
use xmloxide::parser::ParseOptions;

struct MyHandler;
impl SaxHandler for MyHandler {
    fn start_element(&mut self, name: &str, _: Option<&str>, _: Option<&str>,
                     _: &[(String, String, Option<String>, Option<String>)]) {
        println!("Element: {name}");
    }
}

parse_sax("<root><child/></root>", &ParseOptions::default(), &mut MyHandler).unwrap();

HTML Parsing

use xmloxide::html::parse_html;

let doc = parse_html("<p>Hello <br> World").unwrap();
let root = doc.root_element().unwrap();
assert_eq!(doc.node_name(root), Some("html"));

CSS Selectors

use xmloxide::css::select;
use xmloxide::Document;

let doc = Document::parse_str(r#"<div><p class="intro">Hello</p><p>World</p></div>"#).unwrap();
let root = doc.root_element().unwrap();
let intros = select(&doc, root, "p.intro").unwrap();
assert_eq!(intros.len(), 1);
assert_eq!(doc.text_content(intros[0]), "Hello");

HTML5 Parsing (WHATWG)

use xmloxide::html5::parse_html5;

let doc = parse_html5("<p>Hello <b>world</b>").unwrap();
let root = doc.root_element().unwrap();
assert_eq!(doc.node_name(root), Some("html"));

Fragment parsing (the algorithm behind innerHTML) is also supported:

use xmloxide::html5::{parse_html5_with_options, Html5ParseOptions};

let opts = Html5ParseOptions {
    scripting: false,
    fragment_context: Some("body".to_string()),
};
let doc = parse_html5_with_options("<p>fragment</p>", &opts).unwrap();

HTML5 Streaming (SAX-like)

use xmloxide::html5::sax::{Html5SaxHandler, parse_html5_sax};

struct LinkExtractor { hrefs: Vec<String> }
impl Html5SaxHandler for LinkExtractor {
    fn start_element(&mut self, name: &str, attrs: &[(String, String)], _sc: bool) {
        if name == "a" {
            if let Some((_, href)) = attrs.iter().find(|(n, _)| n == "href") {
                self.hrefs.push(href.clone());
            }
        }
    }
}

let mut handler = LinkExtractor { hrefs: Vec::new() };
parse_html5_sax(r#"<a href="https://github.com/jonwiggins/xmloxide/blob/main/page">Link</a>"#, &mut handler);
assert_eq!(handler.hrefs, vec!["/page"]);

Error Recovery

use xmloxide::parser::{parse_str_with_options, ParseOptions};

let opts = ParseOptions::default().recover(true);
let doc = parse_str_with_options("<root><unclosed>", &opts).unwrap();
for diag in &doc.diagnostics {
    eprintln!("{}", diag);
}

CLI Tool

# Parse and pretty-print
xmllint --format document.xml

# Validate against a schema
xmllint --schema schema.xsd document.xml
xmllint --relaxng schema.rng document.xml
xmllint --schematron schema.sch document.xml
xmllint --dtdvalid schema.dtd document.xml

# XPath query
xmllint --xpath "//title" document.xml

# Canonical XML
xmllint --c14n document.xml

# Parse HTML
xmllint --html page.html

Module Overview

ModuleDescription
treeArena-based DOM tree (Document, NodeId, NodeKind)
parserXML 1.0 recursive descent parser with error recovery
parser::pushPush/incremental parser for chunked input
htmlError-tolerant HTML 4.01 parser
html5WHATWG HTML Living Standard parser (tokenizer + tree builder)
html5::saxStreaming SAX-like API for HTML5 (no DOM tree built)
cssCSS selector engine for querying document trees
saxSAX2 streaming event-driven parser
readerXmlReader pull-based parsing API
serialXML, HTML, and HTML5 serializers, plus Canonical XML (C14N)
xpathXPath 1.0+ expression parser and evaluator
validation::dtdDTD parsing and validation
validation::relaxngRelaxNG schema validation
validation::xsdXML Schema (XSD) validation
validation::schematronISO Schematron rule-based validation
serde_xmlSerde XML (de)serialization (optional serde feature)
async_xmlAsync parsing via tokio::io::AsyncRead (optional async feature)
xincludeXInclude 1.0 document inclusion
catalogOASIS XML Catalogs for URI resolution
encodingCharacter encoding detection and transcoding
ffiC/C++ FFI bindings (include/xmloxide.h)

Performance

Categories