libxml2 (https://gitlab.gnome.org/GNOME/libxml2) — 오픈 소스 세계에서 사실상의 표준 XML/HTML 파싱 라이브러리였던 것에 대한 순수 Rust 재구현입니다.
libxml2는 알려진 보안 문제와 함께 2025년 12월부로 공식적으로 유지보수가 중단되었습니다. xmloxide는 동일한 규격 준수 테스트 스위트를 통과하는 메모리 안전하고 고성능의 대체재가 되는 것을 목표로 합니다.
기능 (Features)
-
메모리 안전성 (Memory-safe) — 공개 API에서 unsafe를 사용하지 않은 아레나 기반 트리 구조
-
규격 준수 (Conformant) — W3C XML 규격 준수 테스트 스위트(1727/1727 적용 가능 테스트) 100% 통과율
-
오류 복구 (Error recovery) — libxml2와 마찬가지로 잘못된 XML을 파싱하더라도 사용 가능한 트리를 생성합니다.
-
다중 파싱 API (Multiple parsing APIs) — DOM 트리, SAX2 스트리밍, XmlReader pull, push/증분 처리
-
HTML 파서 — 자동 닫힘 및 void 요소를 포함하는 오류 허용적인 HTML 4.01 파싱
-
WHATWG HTML5 파서 — 완전한 HTML Living Standard 토크나이저 및 트리 빌더 (8810/8810 html5lib-tests 통과)
-
HTML5 스트리밍 — DOM 트리를 구축하지 않고 토크나이저를 래핑하는 HTML5용 SAX 유사 콜백 API (html5::sax)
-
CSS 선택자 (CSS selectors) — 조합자(combinators), 가상 클래스 등을 포함하여 친숙한 CSS 구문으로 요소를 쿼리하고 빠른 #id 검색 지원 (css::select)
-
XPath 1.0+ — 모든 XPath 1.0 핵심 함수와 주요 XPath 2.0 함수(matches(), replace(), tokenize(), upper-case(), lower-case(), abs(), min(), max() 등)를 포함하는 완전한 표현식 파서 및 평가기
-
유효성 검사 (Validation) — DTD, RelaxNG, XML 스키마(XSD), ISO Schematron (ISO/IEC 19757-3) 유효성 검사
-
Serde 통합 — Rust 타입으로의 XML (역)직렬화를 위한 선택적 serde 기능
-
비동기 파싱 (Async parsing) — tokio::io::AsyncRead 소스로부터의 파싱을 위한 선택적 async 기능
-
Canonical XML — C14N 1.0 및 Exclusive C14N 직렬화
-
XInclude — 문서 포함 처리
-
XML Catalogs — URI 해결을 위한 OASIS XML Catalog
-
xmllint CLI — XML 파싱, 유효성 검사 및 쿼리용 명령줄 도구
-
Zero-copy where possible — 빠른 비교를 위한 문자열 인터닝(string interning)
-
No global state — 각 Document는 독립적이며 Send + Sync 지원
-
C/C++ FFI — C/C++ 프로젝트에 임베딩하기 위한 전체 C API 및 헤더 파일 (include/xmloxide.h)
-
Minimal dependencies — encoding_rs만 의존 (라이브러리는 다른 의존성 없음; clap은 CLI 전용)
빠른 시작(Quick Start)
use xmloxide::Document;
let doc = Document::parse_str("<root><child>Hello</child></root>").unwrap();
...
직렬화(Serialization)
use xmloxide::Document;
use xmloxide::serial::serialize;
...
XPath 쿼리(XPath Queries)
use xmloxide::Document;
use xmloxide::xpath::{evaluate, XPathValue};
...
SAX2 스트리밍(SAX2 Streaming)
use xmloxide::sax::{parse_sax, SaxHandler, DefaultHandler};
use xmloxide::parser::ParseOptions;
...
HTML 파싱(HTML Parsing)
use xmloxide::html::parse_html;
let doc = parse_html("<p>Hello <br> World").unwrap();
...
CSS 선택자(CSS Selectors)
use xmloxide::css::select;
use xmloxide::Document;
...
HTML5 파싱 (WHATWG)
use xmloxide::html5::parse_html5;
let doc = parse_html5("<p>Hello <b>world</b>").unwrap();
...
Fragment parsing (내부 HTML(innerHTML)의 기반이 되는 알고리즘)도 지원됩니다:
use xmloxide::html5::{parse_html5_with_options, Html5ParseOptions};
let opts = Html5ParseOptions {
...
HTML5 스트리밍 (SAX 유사)
use xmloxide::html5::sax::{Html5SaxHandler, parse_html5_sax};
struct LinkExtractor { hrefs: Vec<String> }
...
오류 복구(Error Recovery)
use xmloxide::parser::{parse_str_with_options, ParseOptions};
let opts = ParseOptions::default().recover(true);
...
xmllint --format document.xml
...
모듈 개요
| Module | Description |
|---|
tree | Arena 기반 DOM 트리 (Document, NodeId, NodeKind) |
| ... | |
성능
파싱 처리량은 libxml2와 경쟁할 만합니다. 대부분의 문서에서 34% 이내이며, SVG에서는 12% 더 빠릅니다. 직렬화(Serialization)는 아레나 기반 트리 디자인 덕분에 **1.52.4배 더 빠릅니다**. XPath는 모든 벤치마크에서 1.1~2.7배 더 빠릅니다.
파싱:
| Document | Size | xmloxide | libxml2 | Result |
|---|
| Atom feed | 4.9 KB | 26.7 µs (176 MiB/s) | 25.5 µs (184 MiB/s) | ~4% 느림 |
| ... | | | | |
| 직렬화: | | | | |
| Document | Size | xmloxide | libxml2 | Result |
|---|
| Atom feed | 4.9 KB | 11.3 µs | 17.5 µs | 1.5배 빠름 |
| ... | | | | |
| XPath: | | | | |
| Expression | xmloxide | libxml2 | Result |
|---|
Simple path (//entry/title) | 1.51 µs | 1.63 µs | 8% 빠름 |
| ... | | | |
주요 최적화: 빠른 직렬화를 위한 아레나 기반 트리, 문자 유효성 검사를 위한 바이트 레벨 사전 확인(pre-checks), 벌크 텍스트 스캐닝(bulk text scanning), 이름 파싱을 위한 ASCII 빠른 경로(fast paths), 제로 카피 요소 이름 분할(zero-copy element name splitting), 인라인 엔티티 해결(inline entity resolution), 결합된 축 확장(fused axis expansion)을 사용한 XPath // 단계 융합, 인라인 트리 접근자(inlined tree accessors), 자식/후손 축(child/descendant axes)을 위한 이름 테스트 빠른 경로. | | | |
cargo bench --features bench-libxml2 --bench comparison_bench
테스트
- 모든 모듈에 걸쳐 1078개의 단위 테스트(unit tests)
- 전체 C API 표면을 다루는 138개의 FFI 테스트 (SAX, Schematron, CSS 포함)
- libxml2 호환성 스위트: XML 파싱, 네임스페이스, 오류 감지 및 HTML 파싱을 포괄하는 119/119 테스트 통과 (100%)
- W3C XML 준수 테스트 스위트: 적용 가능한 1727개 테스트 중 1727개 통과 (100%)
- html5lib 테스트: 토크나이저 테스트 7032/7032 + 트리 구성 테스트 1778/1778 (100%)
- 실제 XML/HTML 문서, 엣지 케이스 및 오류 복구를 다루는 통합 테스트(Integration tests)
C/C++ FFI
xmloxide는 C/C++ 프로젝트에 임베딩할 수 있는 C 호환 API를 제공합니다 (Chromium, 게임 엔진 또는 현재 libxml2를 사용하는 모든 코드베이스와 같은 경우).
#include "xmloxide.h"
xmloxide_document *doc = xmloxide_parse_str("<root>Hello</root>");
...
트리 탐색 및 변경, XPath 평가, 직렬화(일반 및 예쁘게 출력), HTML/HTML5 파싱, DTD/RelaxNG/XSD/Schematron 유효성 검사, C14N, SAX 스트리밍, XmlReader, push 파서, XML 카탈로그를 포함한 전체 API는 include/xmloxide.h에 선언되어 있습니다.
libxml2에서 마이그레이션하기
| libxml2 | xmloxide (Rust) | xmloxide (C FFI) |
|---|
xmlReadMemory | Document::parse_str | xmloxide_parse_str |
| ... | | |
스레드 안전성: libxml2와 달리, xmloxide는 전역 상태(global state)를 갖지 않습니다. 각 Document는 자체적으로 완결되어 있으며 Send + Sync가 가능합니다. FFI 계층은 마지막 오류 메시지에 대해 스레드 로컬 저장소(thread-local storage)를 사용하므로, 각 스레드는 고유한 오류 상태를 가집니다. 초기화나 정리 함수가 필요하지 않습니다. | | |
퍼징 (Fuzzing)
xmloxide는 보안 테스트를 위한 퍼즈 타겟을 포함하고 있습니다:
cargo install cargo-fuzz
...
빌드하기
cargo build
cargo test
cargo clippy --all-targets --all-features -- -D warnings
...
최소 지원 Rust 버전: 1.81
제한 사항 (Limitations)
최소 지원 Rust 버전: 1.81
제한 사항 (Limitations)
- XML 1.1 미지원 — xmloxide는 XML 1.0 (Fifth Edition)만 구현합니다. XML 1.1은 거의 사용되지 않으며 계획되어 있지 않습니다.
- XSLT 미지원 — XSLT는 별도의 명세(libxslt)이며 범위를 벗어납니다.
- HTML 파서 — HTML 4.01 파서(libxml2의 동작과 일치)와 완전한 WHATWG HTML5 파서를 모두 제공합니다. HTML5 파서는 html5lib-tests를 100% 통과합니다.
- 내부 푸시 파서 버퍼링 — 푸시/증분 파서 API(
PushParser)는 현재 전달된 모든 데이터를 버퍼링하고 finish()에서 전체 파싱을 수행하며, libxml2의 xm처럼 진정한 스트리밍 방식은 아닙니다.