Volver a actualizaciones
Nuevo releaseAug 9, 2026

xmloxide v0.5.0

Una reimplementación en Rust puro de libxml2

Compartir

xmloxide

CI crates.io docs.rs License: MIT MSRV

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

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

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

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

SAX2 Streaming

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

Análisis 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"));

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

Análisis 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"));

El análisis de fragmentos (el algoritmo detrás de innerHTML) también es compatible:

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)

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

Recuperación de errores

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

# 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óduloDescripción
treeÁrbol DOM basado en arena (Document, NodeId, NodeKind)
parserAnalizador descendente recursivo XML 1.0 con recuperación de errores
parser::pushAnalizador push/incremental para entrada fragmentada
htmlAnalizador HTML 4.01 tolerante a errores
html5Analizador HTML Living Standard WHATWG (tokenizador + constructor de árbol)
html5::saxAPI de streaming tipo SAX para HTML5 (sin construcción de árbol DOM)
cssMotor de selectores CSS para consultar árboles de documentos
saxAnalizador SAX2 basado en eventos de streaming
readerAPI de análisis basado en pull XmlReader
serialSerializadores XML, HTML y HTML5, más XML Canónico (C14N)
xpathAnalizador y evaluador de expresiones XPath 1.0+
validation::dtdAnálisis y validación DTD
validation::relaxngValidación de esquemas RelaxNG
validation::xsdValidación de esquemas XML (XSD)
validation::schematronValidación basada en reglas ISO Schematron
serde_xml(Des)serialización Serde XML (característica opcional serde)
async_xmlAnálisis asíncrono mediante tokio::io::AsyncRead (característica opcional async)
xincludeInclusión de documentos XInclude 1.0
catalogCatálogos XML OASIS para resolución de URI
encodingDetección de codificación de caracteres y transcodificación
ffiEnlaces FFI C/C++ (include/xmloxide.h)

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:

DocumentoTamañoxmloxidelibxml2Resultado
Feed Atom4.9 KB26,7 µs (176 MiB/s)25,5 µs (184 MiB/s)~4% más lento
Dibujo SVG6.3 KB58,5 µs (103 MiB/s)65,6 µs (92 MiB/s)12% más rápido
POM de Maven11.5 KB76,9 µs (142 MiB/s)74,2 µs (148 MiB/s)~4% más lento
Página XHTML10.2 KB69,5 µs (139 MiB/s)61,5 µs (157 MiB/s)~13% más lento
Grande (374 KB)374 KB2,15 ms (169 MiB/s)2,08 ms (175 MiB/s)~3% más lento

Serialización:

DocumentoTamañoxmloxidelibxml2Resultado
Feed Atom4.9 KB11,3 µs17,5 µs1,5x más rápido
POM de Maven11.5 KB20,1 µs47,5 µs2,4x más rápido
Grande (374 KB)374 KB614 µs1397 µs2,3x más rápido

XPath:

Expresiónxmloxidelibxml2Resultado
Ruta simple (//entry/title)1,51 µs1,63 µs8% más rápido
Predicado de atributo (//book[@id])5,91 µs15,99 µs2,7x más rápido
Función count()1,09 µs1,67 µs1,5x más rápido
Función string()1,32 µs1,77 µs1,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.

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

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

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

libxml2xmloxide (Rust)xmloxide (C FFI)
xmlReadMemoryDocument::parse_strxmloxide_parse_str
xmlReadFileDocument::parse_filexmloxide_parse_file
xmlParseDocDocument::parse_bytesxmloxide_parse_bytes
htmlReadMemoryhtml::parse_htmlxmloxide_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
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
(Validación 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

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:

# 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

Compilación

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

Versión mínima de Rust soportada: 1.81

Limitaciones

  • 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

Categorías