Una reimplementación pura en Rust de libxml2 — la biblioteca de análisis XML/HTML de facto en el mundo del código abierto.
libxml2 quedó oficialmente sin mantenimiento en diciembre de 2025 con problemas de seguridad conocidos. xmloxide aspira a ser un reemplazo seguro en memoria y de alto rendimiento que pase los mismos conjuntos de pruebas de conformidad.
Características
Seguridad en memoria — árbol basado en arena con cero unsafe en la API pública
Conformidad — 100% de tasa de aprobación en el W3C XML Conformance Test Suite (1727/1727 pruebas aplicables)
Recuperación de errores — analiza XML malformado y aún produce un árbol utilizable, al igual que libxml2
Múltiples API de análisis — árbol DOM, SAX2 streaming, XmlReader pull, push/incremental
Analizador HTML — análisis HTML 4.01 tolerante a errores con auto-cierre y elementos vacíos
Analizador HTML5 WHATWG — tokenizador y constructor de árbol completo del HTML Living Standard (8810/8810 pruebas html5lib-tests aprobadas)
Streaming HTML5 — API de callback tipo SAX para HTML5 (html5::sax) que envuelve el tokenizador sin construir un árbol DOM
Selectores CSS — consulta de elementos con sintaxis CSS familiar (css::select) incluyendo combinadores, pseudo-clases y búsqueda rápida por #id
XPath 1.0+ — analizador y evaluador de expresiones completo con todas las funciones principales de XPath 1.0 más funciones clave de XPath 2.0 (matches(), replace(), tokenize(), upper-case(), lower-case(), abs(), min(), max() y más)
Validación — validación DTD, RelaxNG, XML Schema (XSD) e ISO Schematron (ISO/IEC 19757-3)
Integración con Serde — característica opcional serde para (de)serialización XML hacia/desde tipos de Rust
Análisis asíncrono — característica opcional async para analizar desde fuentes tokio::io::AsyncRead
XML Canónico — serialización C14N 1.0 y C14N Exclusivo
XInclude — procesamiento de inclusión de documentos
Catálogos XML — Catálogos XML OASIS para resolución de URI
CLI xmllint — herramienta de línea de comandos para analizar, validar y consultar XML
Cero copia cuando es posible — internado de cadenas para comparaciones rápidas
Sin estado global — cada Document es autocontenido y Send + Sync
FFI C/C++ — API C completa con archivo de cabecera (include/xmloxide.h) para integración en proyectos C/C++
Dependencias mínimas — solo encoding_rs (la biblioteca no tiene otras dependencias; clap es solo para CLI)
Inicio rápido
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");
Serialización
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>");
Consultas 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"));
Selectores 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");
Análisis 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"));
El análisis de fragmentos (el algoritmo detrás de innerHTML) también es compatible:
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 (tipo 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"]);
Recuperación de errores
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);
}
Herramienta CLI
root@kitploit:~
# Analizar y embellecer
xmllint --format document.xml
# Validar contra un esquema
xmllint --schema schema.xsd document.xml
xmllint --relaxng schema.rng document.xml
xmllint --schematron schema.sch document.xml
xmllint --dtdvalid schema.dtd document.xml
# Consulta XPath
xmllint --xpath "//title" document.xml
# XML Canónico
xmllint --c14n document.xml
# Analizar HTML
xmllint --html page.html
Resumen de módulos
Módulo
Descripción
tree
Árbol DOM basado en arena (Document, NodeId, NodeKind)
parser
Analizador descendente recursivo XML 1.0 con recuperación de errores
parser::push
Analizador push/incremental para entrada fragmentada
html
Analizador HTML 4.01 tolerante a errores
html5
Analizador HTML Living Standard WHATWG (tokenizador + constructor de árbol)
html5::sax
API de streaming tipo SAX para HTML5 (sin construcción de árbol DOM)
css
Motor de selectores CSS para consultar árboles de documentos
sax
Analizador SAX2 basado en eventos de streaming
reader
API de análisis basado en pull XmlReader
serial
Serializadores XML, HTML y HTML5, más XML Canónico (C14N)
xpath
Analizador y evaluador de expresiones XPath 1.0+
validation::dtd
Análisis y validación DTD
validation::relaxng
Validación de esquemas RelaxNG
validation::xsd
Validación de esquemas XML (XSD)
validation::schematron
Validación basada en reglas ISO Schematron
serde_xml
(Des)serialización Serde XML (característica opcional serde)
async_xml
Análisis asíncrono mediante tokio::io::AsyncRead (característica opcional async)
xinclude
Inclusión de documentos XInclude 1.0
catalog
Catálogos XML OASIS para resolución de URI
encoding
Rendimiento
El rendimiento de análisis es competitivo con libxml2 — dentro del 3-4% en la mayoría de los documentos, y 12% más rápido en SVG. La serialización es 1.5-2.4 veces más rápida gracias al diseño del árbol basado en arena. XPath es 1.1-2.7 veces más rápido en todos los benchmarks.
Análisis:
Documento
Tamaño
xmloxide
libxml2
Resultado
Feed Atom
4.9 KB
26,7 µs (176 MiB/s)
25,5 µs (184 MiB/s)
~4% más lento
Dibujo SVG
6.3 KB
58,5 µs (103 MiB/s)
65,6 µs (92 MiB/s)
12% más rápido
POM de Maven
11.5 KB
76,9 µs (142 MiB/s)
74,2 µs (148 MiB/s)
~4% más lento
Página XHTML
10.2 KB
69,5 µs (139 MiB/s)
61,5 µs (157 MiB/s)
~13% más lento
Grande (374 KB)
374 KB
2,15 ms (169 MiB/s)
2,08 ms (175 MiB/s)
~3% más lento
Serialización:
Documento
Tamaño
xmloxide
libxml2
Resultado
Feed Atom
4.9 KB
11,3 µs
17,5 µs
1,5x más rápido
POM de Maven
11.5 KB
20,1 µs
47,5 µs
2,4x más rápido
Grande (374 KB)
374 KB
614 µs
1397 µs
2,3x más rápido
XPath:
Expresión
xmloxide
libxml2
Resultado
Ruta simple (//entry/title)
1,51 µs
1,63 µs
8% más rápido
Predicado de atributo (//book[@id])
5,91 µs
15,99 µs
2,7x más rápido
Función count()
1,09 µs
1,67 µs
1,5x más rápido
Función string()
1,32 µs
1,77 µs
1,3x más rápido
Optimizaciones clave: árbol basado en arena para serialización rápida, pre-verificaciones a nivel de byte para validación de caracteres, escaneo de texto por lotes, rutas rápidas ASCII para análisis de nombres, división de nombres de elementos de cero copia, resolución de entidades en línea, fusión de pasos // XPath con expansión de ejes fusionados, accesores de árbol en línea y rutas rápidas de prueba de nombre para ejes hijo/descendiente.
root@kitploit:~
# Ejecutar benchmarks (requiere la biblioteca del sistema libxml2)
cargo bench --features bench-libxml2 --bench comparison_bench
Pruebas
1078 pruebas unitarias en todos los módulos
138 pruebas FFI que cubren toda la superficie de la API C (incluyendo SAX, Schematron y CSS)
Suite de compatibilidad libxml2 — 119/119 pruebas aprobadas (100%) que cubren análisis XML, espacios de nombres, detección de errores y análisis HTML
W3C XML Conformance Test Suite — 1727/1727 pruebas aplicables aprobadas (100%)
html5lib-tests — 7032/7032 pruebas de tokenizador + 1778/1778 pruebas de construcción de árbol (100%)
Pruebas de integración que cubren documentos XML/HTML del mundo real, casos límite y recuperación de errores
root@kitploit:~
cargo test --all-features
FFI C/C++
xmloxide proporciona una API compatible con C para integración en proyectos C/C++ (como Chromium, motores de juegos o cualquier base de código que actualmente use libxml2).
root@kitploit:~
# Compilar bibliotecas compartida + estática (usa el Makefile incluido)
make
# O compilar individualmente:
make shared # .so / .dylib / .dll
make static # .a / .lib
# Compilar y ejecutar el ejemplo en C
make example
La API completa — incluyendo navegación y mutación de árbol, evaluación XPath, serialización (plana y embellecida), análisis HTML/HTML5, validación DTD/RelaxNG/XSD/Schematron, C14N, streaming SAX, XmlReader, analizador push y Catálogos XML — está declarada en include/xmloxide.h.
Migración desde 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
(Análisis HTML5)
html5::parse_html5
—
(Fragmento HTML5 / innerHTML)
html5::parse_html5_with_options
—
(Streaming HTML5)
html5::sax::parse_html5_sax
—
(Selectores CSS / querySelector)
css::select
—
xmlFreeDoc
(liberar 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(...)
Seguridad de hilos: A diferencia de libxml2, xmloxide no tiene estado global. Cada Document es autocontenido y Send + Sync. La capa FFI utiliza almacenamiento local de hilo para el último mensaje de error — cada hilo tiene su propio estado de error. No se necesitan funciones de inicialización o limpieza.
Fuzzing
xmloxide incluye objetivos de fuzzing para pruebas de seguridad:
root@kitploit:~
# Instalar cargo-fuzz (requiere nightly)
cargo install cargo-fuzz
# Ejecutar un objetivo 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
Sin XML 1.1 — xmloxide implementa solo XML 1.0 (Quinta Edición). XML 1.1 rara vez se usa y no está planificado.
Sin XSLT — XSLT es una especificación separada (libxslt) y está fuera del alcance.
Analizadores HTML — se proporcionan tanto un analizador HTML 4.01 (que coincide con el comportamiento de libxml2) como un analizador HTML5 WHATWG completo. El analizador HTML5 pasa el 100% de las pruebas html5lib-tests.
El analizador push almacena en búfer internamente — la API del analizador push/incremental (PushParser) actualmente almacena en búfer todos los datos enviados y realiza el análisis completo en finish(), en lugar de transmitir verdaderamente como xmlParseChunk de libxml2. El streaming SAX (parse_sax para XML, html5::sax::parse_html5_sax para HTML5) está disponible como alternativa para el procesamiento de documentos grandes con restricciones de memoria.
Eje namespace:: de XPath — el eje namespace:: devuelve el nodo del elemento cuando los espacios de nombres en alcance coinciden (en lugar de materializar nodos de espacio de nombres separados), siguiendo el mismo patrón que el eje de atributos.
Contribuciones
Consulte CONTRIBUTING.md para la configuración de desarrollo y las pautas.
Registro de cambios
Consulte CHANGELOG.md para el historial de versiones.
Licencia
MIT
Detección de codificación de caracteres y transcodificación