Une réimplémentation pure en Rust de libxml2 — la bibliothèque d'analyse XML/HTML de facto standard dans le monde open-source.
libxml2 est officiellement devenu non maintenu en décembre 2025 avec des problèmes de sécurité connus. xmloxide vise à être un remplacement sûr en mémoire et haute performance qui réussit les mêmes suites de tests de conformité.
Fonctionnalités
Sécurité mémoire — arbre basé sur un arena avec zéro unsafe dans l'API publique
Conforme — taux de réussite de 100 % sur la suite de tests de conformité XML du W3C (1727/1727 tests applicables)
Récupération d'erreurs — analyser du XML mal formé et quand même produire un arbre utilisable, tout comme libxml2
Analyseur HTML — analyse HTML 4.01 tolérante aux erreurs avec fermeture automatique et éléments vides
Analyseur HTML5 WHATWG — tokeniseur et constructeur d'arbre complets du HTML Living Standard (8810/8810 tests html5lib-tests réussis)
Streaming HTML5 — API de rappel de type SAX pour HTML5 (html5::sax) qui encapsule le tokeniseur sans construire d'arbre DOM
Sélecteurs CSS — interroger des éléments avec une syntaxe CSS familière (css::select) incluant combinateurs, pseudo-classes et recherche rapide par #id
XPath 1.0+ — analyseur et évaluateur d'expressions complet avec toutes les fonctions de base XPath 1.0 plus des fonctions clés XPath 2.0 (matches(), replace(), tokenize(), upper-case(), lower-case(), abs(), min(), max(), et plus)
Validation — validation DTD, RelaxNG, Schéma XML (XSD) et Schematron ISO (ISO/IEC 19757-3)
Intégration Serde — fonctionnalité serde optionnelle pour la (dé)sérialisation XML vers/depuis des types Rust
Analyse asynchrone — fonctionnalité async optionnelle pour analyser à partir de sources tokio::io::AsyncRead
XML canonique — sérialisation C14N 1.0 et C14N exclusive
XInclude — traitement d'inclusion de documents
Catalogues XML — catalogues XML OASIS pour la résolution d'URI
CLI xmllint — outil en ligne de commande pour analyser, valider et interroger du XML
Zéro copie quand c'est possible — internement de chaînes pour des comparaisons rapides
Aucun état global — chaque Document est autonome et Send + Sync
FFI C/C++ — API C complète avec fichier d'en-tête (include/xmloxide.h) pour intégration dans des projets C/C++
Dépendances minimales — seulement encoding_rs (la bibliothèque n'a aucune autre dépendance ; clap est uniquement CLI)
Démarrage rapide
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");
Sérialisation
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>");
Requêtes XPath
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"));
Sélecteurs CSS
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");
Analyse HTML5 (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"));
L'analyse de fragments (l'algorithme derrière innerHTML) est également prise en charge :
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();
Streaming HTML5 (type SAX)
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"]);
Récupération d'erreurs
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);
}
Outil CLI
root@kitploit:~
# Analyser et afficher avec une mise en forme
xmllint --format document.xml
# Valider par rapport à un schéma
xmllint --schema schema.xsd document.xml
xmllint --relaxng schema.rng document.xml
xmllint --schematron schema.sch document.xml
xmllint --dtdvalid schema.dtd document.xml
# Requête XPath
xmllint --xpath "//title" document.xml
# XML canonique
xmllint --c14n document.xml
# Analyser HTML
xmllint --html page.html
Aperçu des modules
Module
Description
tree
Arbre DOM basé sur arena (Document, NodeId, NodeKind)
parser
Analyseur récursif descendant XML 1.0 avec récupération d'erreurs
parser::push
Analyseur push/incrémentale pour entrée par morceaux
html
Analyseur HTML 4.01 tolérant aux erreurs
html5
Analyseur WHATWG HTML Living Standard (tokeniseur + constructeur d'arbre)
html5::sax
API streaming de type SAX pour HTML5 (pas d'arbre DOM construit)
css
Moteur de sélecteurs CSS pour interroger les arbres de documents
sax
Analyseur événementiel streaming SAX2
reader
API d'analyse par pull XmlReader
serial
Sérialiseurs XML, HTML et HTML5, plus XML canonique (C14N)
xpath
Analyseur et évaluateur d'expressions XPath 1.0+
validation::dtd
Analyse et validation DTD
validation::relaxng
Validation de schéma RelaxNG
validation::xsd
Validation de schéma XML (XSD)
validation::schematron
Validation basée sur des règles ISO Schematron
serde_xml
(Dé)sérialisation Serde XML (fonctionnalité serde optionnelle)
async_xml
Analyse asynchrone via tokio::io::AsyncRead (fonctionnalité async optionnelle)
xinclude
Inclusion de documents XInclude 1.0
catalog
Catalogues XML OASIS pour la résolution d'URI
encoding
Performances
Le débit d'analyse est compétitif avec libxml2 — à moins de 3–4 % sur la plupart des documents, et 12 % plus rapide sur SVG. La sérialisation est 1,5 à 2,4 fois plus rapide grâce à la conception de l'arbre basé sur arena. XPath est 1,1 à 2,7 fois plus rapide sur tous les benchmarks.
Analyse :
Document
Taille
xmloxide
libxml2
Résultat
Flux Atom
4,9 Ko
26,7 µs (176 Mio/s)
25,5 µs (184 Mio/s)
~4 % plus lent
Dessin SVG
6,3 Ko
58,5 µs (103 Mio/s)
65,6 µs (92 Mio/s)
12 % plus rapide
POM Maven
11,5 Ko
76,9 µs (142 Mio/s)
74,2 µs (148 Mio/s)
~4 % plus lent
Page XHTML
10,2 Ko
69,5 µs (139 Mio/s)
61,5 µs (157 Mio/s)
~13 % plus lent
Grand (374 Ko)
374 Ko
2,15 ms (169 Mio/s)
2,08 ms (175 Mio/s)
~3 % plus lent
Sérialisation :
Document
Taille
xmloxide
libxml2
Résultat
Flux Atom
4,9 Ko
11,3 µs
17,5 µs
1,5 fois plus rapide
POM Maven
11,5 Ko
20,1 µs
47,5 µs
2,4 fois plus rapide
Grand (374 Ko)
374 Ko
614 µs
1397 µs
2,3 fois plus rapide
XPath :
Expression
xmloxide
libxml2
Résultat
Chemin simple (//entry/title)
1,51 µs
1,63 µs
8 % plus rapide
Prédicat d'attribut (//book[@id])
5,91 µs
15,99 µs
2,7 fois plus rapide
Fonction count()
1,09 µs
1,67 µs
1,5 fois plus rapide
Fonction string()
1,32 µs
1,77 µs
1,3 fois plus rapide
Optimisations clés : arbre basé sur arena pour une sérialisation rapide, pré-vérifications au niveau des octets pour la validation des caractères, analyse en bloc du texte, chemins rapides ASCII pour l'analyse des noms, séparation de noms d'éléments sans copie, résolution d'entités en ligne, fusion d'étapes XPath // avec expansion d'axes fusionnés, accesseurs d'arbre inlinés, et chemins rapides de test de noms pour les axes enfants/descendants.
root@kitploit:~
# Exécuter les benchmarks (nécessite la bibliothèque système libxml2)
cargo bench --features bench-libxml2 --bench comparison_bench
Tests
1078 tests unitaires dans tous les modules
138 tests FFI couvrant toute la surface de l'API C (y compris SAX, Schematron et CSS)
Suite de compatibilité libxml2 — 119/119 tests réussis (100 %) couvrant l'analyse XML, les espaces de noms, la détection d'erreurs et l'analyse HTML
Suite de tests de conformité XML du W3C — 1727/1727 tests applicables réussis (100 %)
html5lib-tests — 7032/7032 tests de tokeniseur + 1778/1778 tests de construction d'arbre (100 %)
Tests d'intégration couvrant des documents XML/HTML réels, des cas limites et la récupération d'erreurs
root@kitploit:~
cargo test --all-features
FFI C/C++
xmloxide fournit une API compatible C pour l'intégration dans des projets C/C++ (comme Chromium, les moteurs de jeu, ou toute base de code utilisant actuellement libxml2).
root@kitploit:~
# Construire les bibliothèques partagées + statiques (utilise le Makefile inclus)
make
# Ou construire individuellement :
make shared # .so / .dylib / .dll
make static # .a / .lib
# Construire et exécuter l'exemple C
make example
L'API complète — incluant la navigation et la mutation de l'arbre, l'évaluation XPath, la sérialisation (simple et avec mise en forme), l'analyse HTML/HTML5, la validation DTD/RelaxNG/XSD/Schematron, C14N, le streaming SAX, XmlReader, l'analyseur push et les catalogues XML — est déclarée dans include/xmloxide.h.
Migration depuis libxml2
libxml2
xmloxide (Rust)
xmloxide (FFI C)
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
(analyse HTML5)
html5::parse_html5
—
(fragment HTML5 / innerHTML)
html5::parse_html5_with_options
—
(streaming HTML5)
html5::sax::parse_html5_sax
—
(sélecteurs CSS / querySelector)
css::select
—
xmlFreeDoc
(libérer 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(...)
Sécurité des threads : Contrairement à libxml2, xmloxide n'a pas d'état global. Chaque Document est autonome et Send + Sync. La couche FFI utilise un stockage local au thread pour le dernier message d'erreur — chaque thread a son propre état d'erreur. Aucune fonction d'initialisation ou de nettoyage n'est nécessaire.
Fuzzing
xmloxide inclut des cibles de fuzzing pour les tests de sécurité :
root@kitploit:~
# Installer cargo-fuzz (nécessite nightly)
cargo install cargo-fuzz
# Exécuter une cible de fuzzing
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
Pas de XML 1.1 — xmloxide implémente XML 1.0 (Cinquième Édition) uniquement. XML 1.1 est rarement utilisé et n'est pas prévu.
Pas de XSLT — XSLT est une spécification distincte (libxslt) et est hors du champ d'application.
Analyseurs HTML — un analyseur HTML 4.01 (correspondant au comportement de libxml2) et un analyseur HTML5 WHATWG complet sont fournis. L'analyseur HTML5 réussit 100 % des html5lib-tests.
L'analyseur push met en mémoire tampon en interne — l'API d'analyse push/incrémentale (PushParser) met actuellement en mémoire tampon toutes les données poussées et effectue l'analyse complète lors de finish(), plutôt qu'un véritable streaming comme xmlParseChunk de libxml2. Le streaming SAX (parse_sax pour XML, html5::sax::parse_html5_sax pour HTML5) est disponible comme alternative pour le traitement de grands documents avec mémoire limitée.
Axe XPath namespace:: — l'axe namespace:: renvoie le nœud élément lorsque les espaces de noms en portée correspondent (plutôt que de matérialiser des nœuds d'espace de noms séparés), suivant le même modèle que l'axe d'attribut.
Contribuer
Voir CONTRIBUTING.md pour la configuration de développement et les directives.