Eine reine Rust-Neuimplementierung von libxml2 — dem De-facto-Standard für XML/HTML-Parsing-Bibliotheken in der Open-Source-Welt.
libxml2 wurde im Dezember 2025 offiziell für ungepflegt erklärt und weist bekannte Sicherheitslücken auf. xmloxide zielt darauf ab, ein speichersicherer, leistungsstarker Ersatz zu sein, der die gleichen Konformitätstestsuiten besteht.
Funktionen
Speichersicher — Arena-basierter Baum ohne unsafe in der öffentlichen API
Konform — 100% Bestehensquote der W3C-XML-Konformitätstestsuite (1727/1727 anwendbare Tests)
Fehlerbehandlung — fehlerhafte XML-Daten parsen und trotzdem einen nutzbaren Baum erzeugen, genau wie libxml2
Mehrere Parsing-APIs — DOM-Baum, SAX2-Streaming, XmlReader-Pull, Push/inkrementell
HTML-Parser — fehlertolerantes HTML-4.01-Parsen mit automatischem Schließen und void-Elementen
WHATWG-HTML5-Parser — vollständiger HTML Living Standard-Tokenizer und Baumkonstruktor (8810/8810 html5lib-Tests bestanden)
HTML5-Streaming — SAX-ähnliche Callback-API für HTML5 (html5::sax), die den Tokenizer umschließt, ohne einen DOM-Baum zu erstellen
CSS-Selektoren — Abfrage von Elementen mit vertrauter CSS-Syntax (css::select) inklusive Kombinatoren, Pseudoklassen und schnellem #id-Lookup
XPath 1.0+ — vollständiger Ausdrucksparser und -auswerter mit allen Kernfunktionen von XPath 1.0 sowie wichtigen XPath-2.0-Funktionen (matches(), replace(), tokenize(), upper-case(), lower-case(), abs(), min(), max() und mehr)
Validierung — DTD-, RelaxNG-, XML-Schema-(XSD)- und ISO-Schematron-Validierung (ISO/IEC 19757-3)
Serde-Integration — optionales serde-Feature für XML-(De-)Serialisierung von/nach Rust-Typen
Asynchrones Parsen — optionales async-Feature zum Parsen von tokio::io::AsyncRead-Quellen
Kanonisches XML — C14N 1.0 und Exclusive-C14N-Serialisierung
XInclude — Dokumenteninklusionsverarbeitung
XML-Kataloge — OASIS-XML-Kataloge zur URI-Auflösung
xmllint-CLI — Kommandozeilenwerkzeug zum Parsen, Validieren und Abfragen von XML
Zero-Copy wo möglich — String-Interning für schnelle Vergleiche
Kein globaler Zustand — jedes Document ist in sich abgeschlossen und Send + Sync
C/C++-FFI — vollständige C-API mit Header-Datei (include/xmloxide.h) zur Einbettung in C/C++-Projekte
Minimale Abhängigkeiten — nur encoding_rs (die Bibliothek hat keine weiteren Abhängigkeiten; clap ist nur CLI-spezifisch)
Schnellstart
root@kitploit:~
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");
Serialisierung
root@kitploit:~
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-Abfragen
root@kitploit:~
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);
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-Selektoren
root@kitploit:~
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)
root@kitploit:~
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 (der Algorithmus hinter innerHTML) wird ebenfalls unterstützt:
root@kitploit:~
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-ähnlich)
root@kitploit:~
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"]);
Fehlerbehandlung
root@kitploit:~
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-Werkzeug
root@kitploit:~
# Analysieren und hübsch ausgeben
xmllint --format document.xml
# Gegen ein Schema validieren
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-Abfrage
xmllint --xpath "//title" document.xml
# Kanonisches XML
xmllint --c14n document.xml
# HTML parsen
xmllint --html page.html
Asynchrones Parsen über tokio::io::AsyncRead (optionales async-Feature)
xinclude
XInclude-1.0-Dokumenteninklusion
catalog
OASIS-XML-Kataloge zur URI-Auflösung
encoding
Zeichenkodierungserkennung und -transkodierung
Leistung
Der Parsing-Durchsatz ist vergleichbar mit libxml2 — innerhalb von 3–4% bei den meisten Dokumenten und 12 % schneller bei SVG. Die Serialisierung ist 1,5–2,4x schneller dank des arena-basierten Baumdesigns. XPath ist 1,1–2,7x schneller in allen Benchmarks.
Parsing:
Dokument
Größe
xmloxide
libxml2
Ergebnis
Atom-Feed
4,9 KB
26,7 µs (176 MiB/s)
25,5 µs (184 MiB/s)
~4 % langsamer
SVG-Zeichnung
6,3 KB
58,5 µs (103 MiB/s)
65,6 µs (92 MiB/s)
12 % schneller
Maven-POM
11,5 KB
76,9 µs (142 MiB/s)
74,2 µs (148 MiB/s)
~4 % langsamer
XHTML-Seite
10,2 KB
69,5 µs (139 MiB/s)
61,5 µs (157 MiB/s)
~13 % langsamer
Groß (374 KB)
374 KB
2,15 ms (169 MiB/s)
2,08 ms (175 MiB/s)
~3 % langsamer
Serialisierung:
Dokument
Größe
xmloxide
libxml2
Ergebnis
Atom-Feed
4,9 KB
11,3 µs
17,5 µs
1,5x schneller
Maven-POM
11,5 KB
20,1 µs
47,5 µs
2,4x schneller
Groß (374 KB)
374 KB
614 µs
1397 µs
2,3x schneller
XPath:
Ausdruck
xmloxide
libxml2
Ergebnis
Einfacher Pfad (//entry/title)
1,51 µs
1,63 µs
8 % schneller
Attribut-Prädikat (//book[@id])
5,91 µs
15,99 µs
2,7x schneller
count()-Funktion
1,09 µs
1,67 µs
1,5x schneller
string()-Funktion
1,32 µs
1,77 µs
1,3x schneller
Wichtige Optimierungen: Arena-basierter Baum für schnelle Serialisierung, Byte-Level-Vorprüfungen für Zeichenvalidierung, Bulk-Text-Scanning, ASCII-Schnellpfade für Namensparsing, Zero-Copy-Elementnamen-Aufteilung, Inline-Entity-Auflösung, XPath-//-Schrittfusion mit fusionierter Achsenerweiterung, Inline-Baumzugriffe und Namens-Test-Schnellpfade für Kind-/Nachkommenachsen.
Integrationstests decken reale XML/HTML-Dokumente, Randfälle und Fehlerbehandlung ab
root@kitploit:~
cargo test --all-features
C/C++-FFI
xmloxide bietet eine C-kompatible API zur Einbettung in C/C++-Projekte (wie Chromium, Game-Engines oder jede Codebasis, die derzeit libxml2 verwendet).
root@kitploit:~
# Gemeinsame + statische Bibliotheken bauen (verwendet das beiliegende Makefile)
make
# Oder einzeln bauen:
make shared # .so / .dylib / .dll
make static # .a / .lib
# C-Beispiel bauen und ausführen
make example
Die vollständige API — einschließlich Baum-Navigation und -Mutation, XPath-Auswertung, Serialisierung (einfach und hübsch gedruckt), HTML/HTML5-Parsing, DTD/RelaxNG/XSD/Schematron-Validierung, C14N, SAX-Streaming, XmlReader, Push-Parser und XML-Kataloge — ist in include/xmloxide.h deklariert.
Migration von libxml2
libxml2
xmloxide (Rust)
xmloxide (C FFI)
xmlReadMemory
Document::parse_str
xmloxide_parse_str
xmlReadFile
Document::parse_file
xmloxide_parse_file
xmlParseDoc
Document::parse_bytes
xmloxide_parse_bytes
htmlReadMemory
html::parse_html
xmloxide_parse_html
(HTML5-Parsing)
html5::parse_html5
—
(HTML5-Fragment / innerHTML)
html5::parse_html5_with_options
—
(HTML5-Streaming)
html5::sax::parse_html5_sax
—
(CSS-Selektoren / querySelector)
css::select
—
xmlFreeDoc
(drop Document)
xmloxide_free_doc
xmlDocGetRootElement
doc.root_element()
xmloxide_doc_root_element
xmlNodeGetContent
doc.text_content(id)
xmloxide_node_text_content
xmlNodeSetContent
doc.set_text_content(id, s)
xmloxide_set_text_content
xmlGetProp
doc.attribute(id, name)
xmloxide_node_attribute
xmlSetProp
doc.set_attribute(...)
xmloxide_set_attribute
xmlNewNode
doc.create_node(...)
xmloxide_create_element
Thread-Sicherheit: Im Gegensatz zu libxml2 hat xmloxide keinen globalen Zustand. Jedes Document ist in sich abgeschlossen und Send + Sync. Die FFI-Schicht verwendet thread-lokalen Speicher für die letzte Fehlermeldung – jeder Thread hat seinen eigenen Fehlerzustand. Es werden keine Initialisierungs- oder Bereinigungsfunktionen benötigt.
Fuzzing
xmloxide enthält Fuzz-Ziele für Sicherheitstests:
root@kitploit:~
# cargo-fuzz installieren (erfordert nightly)
cargo install cargo-fuzz
# Ein Fuzz-Ziel ausführen
cargo +nightly fuzz run fuzz_xml_parse
cargo +nightly fuzz run fuzz_html_parse
cargo +nightly fuzz run fuzz_html5_parse
cargo +nightly fuzz run fuzz_html5_fragment
cargo +nightly fuzz run fuzz_xpath
cargo +nightly fuzz run fuzz_roundtrip
cargo +nightly fuzz run fuzz_sax
cargo +nightly fuzz run fuzz_reader
cargo +nightly fuzz run fuzz_push
cargo +nightly fuzz run fuzz_validation
cargo +nightly fuzz run fuzz_schematron
Kein XML 1.1 — xmloxide implementiert nur XML 1.0 (Fünfte Edition). XML 1.1 wird selten verwendet und ist nicht geplant.
Kein XSLT — XSLT ist eine separate Spezifikation (libxslt) und außerhalb des Rahmens.
HTML-Parser — Es werden sowohl ein HTML-4.01-Parser (der das Verhalten von libxml2 nachbildet) als auch ein vollständiger WHATWG-HTML5-Parser bereitgestellt. Der HTML5-Parser besteht 100% der html5lib-Tests.
Push-Parser puffert intern — die Push/inkrementelle Parser-API (PushParser) puffert derzeit alle gepushten Daten und führt die vollständige Analyse bei finish() durch, anstatt wirklich zu streamen wie libxml2s xmlParseChunk. SAX-Streaming (parse_sax für XML, html5::sax::parse_html5_sax für HTML5) steht als Alternative für die speicherbeschränkte Verarbeitung großer Dokumente zur Verfügung.
XPath-namespace::-Achse — die namespace::-Achse gibt den Elementknoten zurück, wenn Namensräume im Gültigkeitsbereich übereinstimmen (anstatt separate Namensraumknoten zu materialisieren), und folgt damit dem gleichen Muster wie die Attributachse.
Mitwirken
Siehe CONTRIBUTING.md für Entwicklungseinrichtung und Richtlinien.