Retour aux mises à jour
New releaseAug 9, 2026

xmloxide v0.5.0

Une réimplémentation en pur Rust de libxml2

Partager

xmloxide

CI crates.io docs.rs License: MIT MSRV

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
  • Plusieurs API d'analyse — arbre DOM, streaming SAX2, pull XmlReader, push/incrémentale
  • 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

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

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

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);

Streaming SAX2

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();

Analyse HTML

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

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)

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 :

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)

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="/page">Link</a>"#, &mut handler);
assert_eq!(handler.hrefs, vec!["/page"]);

Récupération d'erreurs

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

# 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

ModuleDescription
treeArbre DOM basé sur arena (Document, NodeId, NodeKind)
parserAnalyseur récursif descendant XML 1.0 avec récupération d'erreurs
parser::pushAnalyseur push/incrémentale pour entrée par morceaux
htmlAnalyseur HTML 4.01 tolérant aux erreurs
html5Analyseur WHATWG HTML Living Standard (tokeniseur + constructeur d'arbre)
html5::saxAPI streaming de type SAX pour HTML5 (pas d'arbre DOM construit)
cssMoteur de sélecteurs CSS pour interroger les arbres de documents
saxAnalyseur événementiel streaming SAX2
readerAPI d'analyse par pull XmlReader
serialSérialiseurs XML, HTML et HTML5, plus XML canonique (C14N)
xpathAnalyseur et évaluateur d'expressions XPath 1.0+
validation::dtdAnalyse et validation DTD
validation::relaxngValidation de schéma RelaxNG
validation::xsdValidation de schéma XML (XSD)
validation::schematronValidation basée sur des règles ISO Schematron
serde_xml(Dé)sérialisation Serde XML (fonctionnalité serde optionnelle)
async_xmlAnalyse asynchrone via tokio::io::AsyncRead (fonctionnalité async optionnelle)
xincludeInclusion de documents XInclude 1.0
catalogCatalogues XML OASIS pour la résolution d'URI
encodingDétection et transcodage d'encodage de caractères
ffiLiaisons FFI C/C++ (include/xmloxide.h)

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 :

DocumentTaillexmloxidelibxml2Résultat
Flux Atom4,9 Ko26,7 µs (176 Mio/s)25,5 µs (184 Mio/s)~4 % plus lent
Dessin SVG6,3 Ko58,5 µs (103 Mio/s)65,6 µs (92 Mio/s)12 % plus rapide
POM Maven11,5 Ko76,9 µs (142 Mio/s)74,2 µs (148 Mio/s)~4 % plus lent
Page XHTML10,2 Ko69,5 µs (139 Mio/s)61,5 µs (157 Mio/s)~13 % plus lent
Grand (374 Ko)374 Ko2,15 ms (169 Mio/s)2,08 ms (175 Mio/s)~3 % plus lent

Sérialisation :

DocumentTaillexmloxidelibxml2Résultat
Flux Atom4,9 Ko11,3 µs17,5 µs1,5 fois plus rapide
POM Maven11,5 Ko20,1 µs47,5 µs2,4 fois plus rapide
Grand (374 Ko)374 Ko614 µs1397 µs2,3 fois plus rapide

XPath :

Expressionxmloxidelibxml2Résultat
Chemin simple (//entry/title)1,51 µs1,63 µs8 % plus rapide
Prédicat d'attribut (//book[@id])5,91 µs15,99 µs2,7 fois plus rapide
Fonction count()1,09 µs1,67 µs1,5 fois plus rapide
Fonction string()1,32 µs1,77 µs1,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.

# 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
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).

# 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
#include "xmloxide.h"

xmloxide_document *doc = xmloxide_parse_str("<root>Hello</root>");
uint32_t root = xmloxide_doc_root_element(doc);
char *name = xmloxide_node_name(doc, root);   // "root"
char *text = xmloxide_node_text_content(doc, root); // "Hello"

xmloxide_free_string(name);
xmloxide_free_string(text);
xmloxide_free_doc(doc);

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

libxml2xmloxide (Rust)xmloxide (FFI C)
xmlReadMemoryDocument::parse_strxmloxide_parse_str
xmlReadFileDocument::parse_filexmloxide_parse_file
xmlParseDocDocument::parse_bytesxmloxide_parse_bytes
htmlReadMemoryhtml::parse_htmlxmloxide_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
xmlDocGetRootElementdoc.root_element()xmloxide_doc_root_element
xmlNodeGetContentdoc.text_content(id)xmloxide_node_text_content
xmlNodeSetContentdoc.set_text_content(id, s)xmloxide_set_text_content
xmlGetPropdoc.attribute(id, name)xmloxide_node_attribute
xmlSetPropdoc.set_attribute(...)xmloxide_set_attribute
xmlNewNodedoc.create_node(...)xmloxide_create_element
xmlNewTextdoc.create_node(Text{..})xmloxide_create_text
xmlAddChilddoc.append_child(p, c)xmloxide_append_child
xmlAddPrevSiblingdoc.insert_before(ref, c)xmloxide_insert_before
xmlUnlinkNodedoc.remove_node(id)xmloxide_remove_node
xmlCopyNodedoc.clone_node(id, deep)xmloxide_clone_node
xmlGetIDdoc.element_by_id(s)xmloxide_element_by_id
xmlDocDumpMemoryserial::serialize(&doc)xmloxide_serialize
xmlDocDumpFormatMemoryserial::serialize_with_optionsxmloxide_serialize_pretty
htmlDocDumpMemoryserial::html::serialize_htmlxmloxide_serialize_html
xmlC14NDocDumpMemoryserial::c14n::canonicalizexmloxide_canonicalize
xmlXPathEvalExpressionxpath::evaluatexmloxide_xpath_eval
xmlValidateDtdvalidation::dtd::validatexmloxide_validate_dtd
xmlRelaxNGValidateDocvalidation::relaxng::validatexmloxide_validate_relaxng
xmlSchemaValidateDocvalidation::xsd::validate_xsdxmloxide_validate_xsd
(validation Schematron)validation::schematron::validate_schematronxmloxide_validate_schematron
xmlXIncludeProcessxinclude::process_xincludesxmloxide_process_xincludes
xmlLoadCatalogCatalog::parsexmloxide_parse_catalog
callbacks xmlSAX2...trait sax::SaxHandlerxmloxide_sax_parse
xmlTextReaderReadreader::XmlReaderxmloxide_reader_read
xmlCreatePushParserCtxtparser::PushParserxmloxide_push_parser_new
xmlParseChunkPushParser::pushxmloxide_push_parser_push

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é :

# 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

Construction

cargo build
cargo test
cargo clippy --all-targets --all-features -- -D warnings
cargo bench

Version minimale de Rust prise en charge : 1.81

Limitations

  • 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.

Journal des modifications

Voir CHANGELOG.md pour l'historique des versions.

Licence

MIT

Catégories