
New releaseAug 9, 2026
xmloxide v0.5.0
Чистая реализация libxml2 на Rust
xmloxide
Чистая реализация libxml2 на Rust — де-факто стандартной библиотеки для разбора XML/HTML в мире открытого ПО.
libxml2 перестала официально поддерживаться в декабре 2025 года с известными проблемами безопасности. xmloxide призван стать безопасной по памяти высокопроизводительной заменой, проходящей те же наборы тестов на соответствие.
Возможности
- Безопасность памяти — аренное дерево с нулевым
unsafeв публичном API - Соответствие стандартам — 100% прохождение тестового набора W3C XML Conformance Test Suite (1727/1727 применимых тестов)
- Восстановление после ошибок — разбор некорректного XML с получением используемого дерева, как в libxml2
- Несколько API разбора — DOM-дерево, потоковый SAX2, pull-парсер XmlReader, инкрементальный разбор
- HTML-парсер — устойчивый к ошибкам парсер HTML 4.01 с авто-закрытием и void-элементами
- HTML5-парсер WHATWG — полный токенизатор и построитель дерева по HTML Living Standard (8810/8810 пройденных html5lib-tests)
- Потоковый HTML5 — SAX-подобный API обратных вызовов для HTML5 (
html5::sax), оборачивающий токенизатор без построения DOM-дерева - CSS-селекторы — запросы элементов с привычным CSS-синтаксисом (
css::select) включая комбинаторы, псевдоклассы и быстрый поиск по#id - XPath 1.0+ — полный парсер и вычислитель выражений со всеми базовыми функциями XPath 1.0 и ключевыми функциями XPath 2.0 (
matches(),replace(),tokenize(),upper-case(),lower-case(),abs(),min(),max()и другими) - Валидация — DTD, RelaxNG, XML Schema (XSD) и ISO Schematron (ISO/IEC 19757-3)
- Интеграция с Serde — опциональная возможность
serdeдля (де)сериализации XML в/из типов Rust - Асинхронный разбор — опциональная возможность
asyncдля разбора изtokio::io::AsyncRead - Канонический XML — C14N 1.0 и Exclusive C14N сериализация
- XInclude — обработка включения документов
- XML-каталоги — OASIS XML Catalogs для разрешения URI
- CLI-инструмент
xmllint— командная утилита для разбора, валидации и запросов XML - Zero-copy где возможно — интернирование строк для быстрых сравнений
- Отсутствие глобального состояния — каждый
Documentсамодостаточен иSend + Sync - C/C++ FFI — полный C-API с заголовочным файлом (
include/xmloxide.h) для встраивания в проекты на C/C++ - Минимальные зависимости — только
encoding_rs(библиотека не имеет других зависимостей;clapиспользуется только в CLI)
Быстрый старт
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");
Сериализация
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>");
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
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();
Разбор 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"));
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");
Разбор 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"));
Также поддерживается разбор фрагментов (алгоритм для innerHTML):
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();
Потоковый HTML5 (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"]);
Восстановление после ошибок
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);
}
CLI-инструмент
# Разбор и красивый вывод
xmllint --format document.xml
# Валидация по схеме
xmllint --schema schema.xsd document.xml
xmllint --relaxng schema.rng document.xml
xmllint --schematron schema.sch document.xml
xmllint --dtdvalid schema.dtd document.xml
# XPath-запрос
xmllint --xpath "//title" document.xml
# Канонический XML
xmllint --c14n document.xml
# Разбор HTML
xmllint --html page.html