Xmloxide: A Rust Reimplementation of libxml2 Created with AI Agent Assistance

What is Xmloxide?
Xmloxide is a pure Rust reimplementation of libxml2, the widely used C library for parsing, creating, and manipulating XML and HTML documents. The project was created after libxml2 became officially unmaintained in December 2025 with known security issues. The developer used Claude Code to reproduce the library by having the AI agent work against existing test suites until they passed.
Key Features and Capabilities
- 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
- XPath 1.0 — full expression parser and evaluator with all core functions
- Validation — DTD, RelaxNG, and XML Schema (XSD) validation
- 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)
Performance and Compatibility
Performance is similar to libxml2 on most parsing operations and better on serialization. The library passes the compatibility suite as well as the W3C XML Conformance Test Suite.
Code Examples
Basic parsing:
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"));
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 Usage
The xmllint CLI tool provides:
# 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 --dtdvalid
AI Agent Development Context
The developer experimented with having Claude Code reproduce Redis and SQLite before targeting libxml2. The approach involved tasking the AI agent to work on projects until existing test suites passed. This demonstrates how coding agents can quickly iterate given a test suite, potentially addressing legacy code maintenance problems.
📖 Read the full source: HN AI Agents
👀 See Also

Using a Local LLM as a Claude Code Subagent to Reduce Context Usage
A developer shares a method to use Claude Code to delegate tasks to a local LLM via LM Studio's API, keeping file content out of Claude's context. The approach uses a ~120-line Python script with tool-calling to read files locally and return summaries.

One-Command Docker Setup for OpenClaw with Full-Disk Encryption and Monitoring
A Docker setup for OpenClaw that provides full-disk encryption guides, Tini as PID 1, built-in monitoring tools, and data stored as plain files on the host. Deployment requires just two commands: git clone and ./shell.

Claude Cowork mode explained: file-level task execution vs chat and code modes
Claude's Cowork mode operates inside a chosen folder to perform file-level tasks like organizing messy folders, extracting structured data from screenshots, and combining scattered notes into structured documents.

MatchKit: Design System Generator for Claude Code Projects
MatchKit is a tool that generates complete branded design systems for projects built with Claude Code. It extracts brand colors from uploaded logos and generates customizable components, layouts, and design tokens to avoid the generic look common with AI coding tools.