
New releaseAug 9, 2026
xmloxide v0.5.0
Una reimplementazione in puro Rust di libxml2
xmloxide
Una reimplementazione in Rust puro di libxml2 — la libreria di parsing XML/HTML de facto standard nel mondo open-source.
libxml2 è diventata ufficialmente non mantenuta a dicembre 2025 con problemi di sicurezza noti. xmloxide punta ad essere un sostituto memory-safe e ad alte prestazioni che supera le stesse suite di test di conformità.
Caratteristiche
- Memory-safe — albero basato su arena con zero
unsafenell'API pubblica - Conforme — 100% di superamento della W3C XML Conformance Test Suite (1727/1727 test applicabili)
- Recupero errori — analizza XML malformato e produce comunque un albero utilizzabile, proprio come libxml2
- Multiple API di parsing — DOM tree, SAX2 streaming, XmlReader pull, push/incrementale
- Parser HTML — parsing HTML 4.01 tollerante agli errori con chiusura automatica ed elementi void
- Parser WHATWG HTML5 — tokenizer e tree builder completi dell'HTML Living Standard (8810/8810 html5lib-tests superati)
- Streaming HTML5 — API callback simile a SAX per HTML5 (
html5::sax) che avvolge il tokenizer senza costruire un albero DOM - Selettori CSS — interroga elementi con sintassi CSS familiare (
css::select) inclusi combinatori, pseudo-classi e ricerca rapida per#id - XPath 1.0+ — parser di espressioni ed evaluatore completo con tutte le funzioni principali di XPath 1.0 più funzioni chiave di XPath 2.0 (
matches(),replace(),tokenize(),upper-case(),lower-case(),abs(),min(),max(), e altro) - Validazione — DTD, RelaxNG, XML Schema (XSD) e ISO Schematron (ISO/IEC 19757-3)
- Integrazione Serde — funzionalità opzionale
serdeper (de)serializzazione XML da/verso tipi Rust - Parsing asincrono — funzionalità opzionale
asyncper il parsing da sorgentitokio::io::AsyncRead - XML Canonico — serializzazione C14N 1.0 e Exclusive C14N
- XInclude — elaborazione di inclusione di documenti
- Cataloghi XML — Cataloghi XML OASIS per risoluzione URI
- CLI
xmllint— strumento a riga di comando per analizzare, validare e interrogare XML - Zero-copy dove possibile — string interning per confronti rapidi
- Nessuno stato globale — ogni
Documentè autonomo eSend + Sync - FFI C/C++ — API C completa con file header (
include/xmloxide.h) per l'incorporamento in progetti C/C++ - Dipendenze minime — solo
encoding_rs(la libreria ha zero altre dipendenze;clapè solo per la CLI)
Avvio rapido
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");
Serializzazione
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>");
Query 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!("Elemento: {name}");
}
}
parse_sax("<root><child/></root>", &ParseOptions::default(), &mut MyHandler).unwrap();
Parsing 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"));
Selettori 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");
Parsing 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"));
Anche il parsing di frammenti (l'algoritmo alla base di innerHTML) è supportato:
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 (simile a 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="https://github.com/jonwiggins/xmloxide/blob/main/page">Link</a>"#, &mut handler);
assert_eq!(handler.hrefs, vec!["/page"]);
Recupero errori
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);
}
Strumento CLI
# Analizza e stampa in modo leggibile
xmllint --format document.xml
# Valida rispetto a uno 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
# Query XPath
xmllint --xpath "//title" document.xml
# XML canonico
xmllint --c14n document.xml
# Parsing HTML
xmllint --html page.html