libxml2 の純Rust再実装 — オープンソース世界で事実上の標準XML/HTMLパースライブラリです。
libxml2は2025年12月に公式メンテナンス終了となり、既知のセキュリティ問題を抱えています。xmloxideは、メモリ安全かつ高性能で、同じ適合性テストスイートを通過する代替品を目指します。
unsafeを一切含みませんhtml5::sax)css::select)。コンビネーター、疑似クラス、高速な#idルックアップ対応matches()、replace()、tokenize()、upper-case()、lower-case()、abs()、min()、max()など)をサポートserdeフィーチャーで、Rust型とのXML (逆)シリアライズasyncフィーチャーで、tokio::io::AsyncReadソースからのパースxmllint CLI — XMLのパース、検証、クエリを行うコマンドラインツールDocumentは自己完結型でSend + Syncinclude/xmloxide.h)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>");
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::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();
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"));
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");
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();
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);
}
# パースしてプリティプリント
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
# Canonical XML
xmllint --c14n document.xml
# HTMLパース
xmllint --html page.html
| モジュール | 説明 |
|---|---|
tree | アリーナベースのDOMツリー(Document、NodeId、NodeKind) |
parser | エラーリカバリ機能付きXML 1.0再帰下降パーサー |
parser::push | チャンク入力用のプッシュ/インクリメンタルパーサー |
html | エラー耐性のあるHTML 4.01パーサー |
html5 | WHATWG HTML Living Standardパーサー(トークナイザー+ツリービルダー) |
html5::sax | HTML5用のストリーミングSAXライクAPI(DOMツリーは構築しない) |
css | ドキュメントツリーをクエリするCSSセレクターエンジン |
sax | SAX2ストリーミングイベント駆動パーサー |
reader | XmlReaderプルベースのパースAPI |
serial | XML、HTML、HTML5シリアライザー、およびCanonical XML (C14N) |
xpath | XPath 1.0+式パーサーと評価器 |
validation::dtd | DTDパースと検証 |
validation::relaxng | RelaxNGスキーマ検証 |
validation::xsd | XML Schema (XSD) 検証 |
validation::schematron | ISO Schematronルールベース検証 |
serde_xml | Serde XML (逆)シリアライズ(オプションのserdeフィーチャー) |
async_xml | tokio::io::AsyncRead経由の非同期パース(オプションのasyncフィーチャー) |
xinclude | XInclude 1.0ドキュメントインクルード |
catalog | URI解決のためのOASIS XMLカタログ |
encoding | 文字エンコーディング検出と変換 |
ffi | C/C++ FFIバインディング(include/xmloxide.h) |
パースのスループットはlibxml2と競合します — ほとんどのドキュメントで3-4%以内、SVGでは12%高速です。シリアライズはアリーナベースのツリー設計のおかげで1.5-2.4倍高速です。XPathはすべてのベンチマークで1.1-2.7倍高速です。
パース:
| ドキュメント | サイズ | xmloxide | libxml2 | 結果 |
|---|---|---|---|---|
| Atomフィード | 4.9 KB | 26.7 µs (176 MiB/s) | 25.5 µs (184 MiB/s) | ~4%低速 |
| SVG描画 | 6.3 KB | 58.5 µs (103 MiB/s) | 65.6 µs (92 MiB/s) | 12%高速 |
| Maven POM | 11.5 KB | 76.9 µs (142 MiB/s) | 74.2 µs (148 MiB/s) | ~4%低速 |
| XHTMLページ | 10.2 KB | 69.5 µs (139 MiB/s) | 61.5 µs (157 MiB/s) | ~13%低速 |
| 大(374 KB) | 374 KB | 2.15 ms (169 MiB/s) | 2.08 ms (175 MiB/s) | ~3%低速 |
シリアライズ:
| ドキュメント | サイズ | xmloxide | libxml2 | 結果 |
|---|---|---|---|---|
| Atomフィード | 4.9 KB | 11.3 µs | 17.5 µs | 1.5倍高速 |
| Maven POM | 11.5 KB | 20.1 µs | 47.5 µs | 2.4倍高速 |
| 大(374 KB) | 374 KB | 614 µs | 1397 µs | 2.3倍高速 |
XPath:
| 式 | xmloxide | libxml2 | 結果 |
|---|---|---|---|
単純パス (//entry/title) | 1.51 µs | 1.63 µs | 8%高速 |
属性述語 (//book[@id]) | 5.91 µs | 15.99 µs | 2.7倍高速 |
count()関数 | 1.09 µs | 1.67 µs | 1.5倍高速 |
string()関数 | 1.32 µs | 1.77 µs | 1.3倍高速 |