Rustで複数形式のdocumentを扱うソフトウェアを作っていて、parserをどうするかでしばらく悩んでいました。
Text、Markdown、JSON、CSVくらいなら、それぞれに合ったcrateを選んで自前で組み合わせても、それほど困りません。
ところが対象を広げていくと話が変わります。
- DOCX / DOC
- XLSX / XLS
- PPTX / PPT
- EPUB
- HTML / XML
- archives
- SQLite
- images
- その他いろいろ
このあたりまで扱おうとすると、
自分はdocumentを使うソフトを作っていたはずなのに、いつの間にかdocument parser集合体を保守している
という状態になってきました。
そこで個別parserを自分で束ねる方針をやめ、Xbergを採用することにしました。
Xbergとは
XbergはRust coreで作られている、オープンソースのdocument intelligence engineです。
PDF、Office document、spreadsheet、画像、email、ebook、HTML、structured data、archiveなどを同じextraction pipelineで扱えます。
2026年9月時点の公式documentationでは、107 file formats、140 unique file extensionsを掲げています。
Rustだけでなく複数言語向けbindingがあり、CLIやAPI、MCP serverとして利用することもできます。
ライセンスはMITです。
この記事では、細かいformat数は将来変わる可能性があるため、以降は主に**「100形式超」**と表現します。
なぜ個別parser方式をやめたのか
たとえばOfficeだけでも、
DOCX
DOC
XLSX
XLS
PPTX
PPT
ODT
ODS
ODP
...
と大量にあります。
これを個別crateで組み始めると、
format A
↓
crate A
↓
独自result
format B
↓
crate B
↓
別のresult
format C
↓
native dependency
↓
また別のresult
となります。
必要なのはparserだけではありません。
- dependency management
- error handling
- malformed fileへの対応
- output normalization
- platform差
- security boundary
- update追従
- license確認
までformatごとに増えていきます。
Xbergでは、各形式のextractorを共通のpipelineへまとめて、
さまざまなdocument
↓
Xberg
↓
ExtractionResult
↓
自分のapplication
という境界を作れます。
自分にとって一番大きかったのは、parser選定そのものを製品の中心から外せることでした。
Officeを一度PDFへ変換する方式ではない
XbergではOffice documentも広範囲なdocument extractionの一部として扱われています。
公式documentationでは、Office系にnative XML parserやOLE/CFB parserを使い、spreadsheetもspreadsheetとしてextractします。
つまりXLSXなら、
XLSX
↓
Spreadsheetとして解析
↓
cell / formula / metadata
という方向です。
「Office documentをLibreOfficeでPDF化してからPDF parserへ渡す」という構成ではありません。
これは後で紹介するLiteParseとの大きな違いです。
Rustでは現在xberg crateを使う
検索すると、ここは少し混乱しやすいです。
Xbergの前身はKreuzbergで、Kreuzberg v4系の記事やdocumentationには、
use kreuzberg::{extract_file_sync, ExtractionConfig};
のようなAPI例が残っています。
現在のXberg 1.xではRust packageもxbergです。
この記事を書いている時点で、私が実際に統合・検証しているversionは、
xberg = { version = "=1.1.0", default-features = false, optional = true }
です。
実際のbuildでは、
xberg/tokio-runtime
を有効化しています。
つまりこの記事のコード例は、検索結果から推測して書いたものではなく、crates.ioのupstream Xberg 1.1.0を使ってcompileおよびrun確認したものです。
実際にcompile・runした最小コード
検証に使った最小例はこれです。
use xberg::{extract, ExtractInput, ExtractionConfig};
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let output = extract(
ExtractInput::from_bytes(
b"hello from upstream xberg".to_vec(),
"text/plain",
Some("note.txt".into()),
),
&ExtractionConfig::default(),
)
.await?;
if let Some(document) = output.results.first() {
println!("content={}", document.content);
println!("mime={}", document.mime_type);
} else if let Some(error) = output.errors.first() {
return Err(format!("{}: {}", error.error_type, error.message).into());
} else {
return Err("Xberg returned neither a result nor a per-input error".into());
}
Ok(())
}
Xberg 1.1.0のupstream APIだけを使用しています。
実行結果は、
content=hello from upstream xberg
mime=text/plain
envelope: results=1, errors=0, summary.inputs=1, summary.results=1, summary.errors=0
でした。
なぜresults[0]ではなくresults.first()なのか
Xbergのextract()は、単一inputでもdocumentそのものを直接返すのではなく、ExtractionResultというenvelopeを返します。
構造は概ね、
ExtractionResult
├── results: Vec<ExtractedDocument>
├── errors: Vec<ExtractionErrorItem>
└── summary: ExtractionSummary
です。
成功したdocumentのcontentは、
document.content
で取得できます。
公式exampleではresults[0]を使う例もありますが、記事ではper-input errorも考慮してfirst()を使いました。
単一documentだからといって、必ずpanic可能な添字アクセスを前提にする必要はないためです。
この記事ではAPIを実地確認した
今回の記事を書くにあたり、Xberg 1.1.0について以下を分けて確認しました。
sourceを読んだ
↓
SOURCE VERIFIED
sampleをcompileした
↓
COMPILE VERIFIED
実際にextractした
↓
RUN VERIFIED
既存integration testでも通した
↓
TEST VERIFIED
この確認をしたのには理由があります。
Xbergは現在かなり速いペースで変化しており、少し前の正しい情報が現在も正しいとは限らないためです。
実際、このfact-checkで一つ誤りを見つけました。
PDF backendはpdf_oxideではなかった
当初この記事では、
XbergのPDF backendは
pdf_oxideというpure Rust engineである
と書いていました。
これはXberg 1.0.0のCHANGELOGを根拠にした記述でした。
1.0.0では、Kreuzberg時代のPDFiumを廃止し、pure Rustのpdf_oxideをPDF backendにしたと明記されています。
しかし、今回実際に採用しているXberg 1.1.0のsourceを確認すると状況が変わっていました。
現在のconfigurationでは、
PdfBackend::Native
がdefaultです。
このNative backendはXberg自身のpure Rust PDF engineである、
xberg-native-pdf
を使用します。
さらに1.1.0では、
PdfBackend::Pdfium
もoptional backendとして存在し、Cargo featureで明示的に有効化できます。
つまり現在は、
┌─ Native
PDF ─ backend ┤ └─ xberg-native-pdf
│
└─ Pdfium
└─ optional
という構成です。
「Xberg 1.1.0のPDF backendはpdf_oxide」ではありません。
ここはこの記事を書くために採用versionのsourceまで確認しなければ、そのまま誤情報として公開していたところでした。
一方で、default PDF backendがpure Rustであるという利点自体は残っています。
「Xberg全部がpure Rust dependencyだけ」という意味ではない
PDF default backendがpure Rustだからといって、
Xbergを使えばsystem dependencyが一切なくなる
という意味ではありません。
機能によってdependencyは変わります。
たとえば、
- Tesseract OCR
- ONNX Runtimeを利用する機能
- HEIC / HEIF系
- WordPerfect系
などでは追加dependencyやnative componentが関係します。
そのためRustへ組み込む場合、私は最初から全部入りにするより、必要なfeature closureを明示的に選ぶ方が扱いやすいと思っています。
XbergのRust buildはfeature flagによってformat extractor、OCR、ML、analysis、serverなどを分離できます。
100形式超対応と「自分が動かした形式」は別
ここはこの記事で一番書いておきたかったところです。
Xberg公式は100形式超をsupportしています。
しかし、
Xbergが対応している
=
自分の製品でその形式を品質保証できる
ではありません。
今回の統合作業でも、それがかなり分かりやすく現れました。
upstreamのcapability
Xberg公式documentationでは、
107 file formats
140 unique file extensions
を掲げています。
自分のcandidate buildで見えたcapability
別のisolated buildで、広めのformat featureを有効化したところ、runtimeのlist_supported_formats()から、
97 extension records
75 unique MIME types
が得られました。
これはfeature closureに応じて利用候補として登録されたcapabilityです。
このbuildでは、
pdf
excel
office
hwp
hwpx
iwork
email
html
xml
archives
sqlite
mdx
svg
などを有効化しました。
一方、system dependencyの追加確認が必要なHEICやWordPerfect、OCR / ML stackなどは意図的に外しています。
実際にrunしたformat
では、自分の統合作業で実際にXberg 1.1.0へ食わせてextraction成功を確認したものは何か。
現時点では、
| Format | Fixture | 実行結果 |
|---|---|---|
| Plain text | text/plain |
成功 |
| Markdown | text/markdown |
成功 |
| CSV | text/csv |
成功 |
| JSON | application/json |
成功 |
です。
つまり現在の証拠は、
upstream support
↓
100形式超
candidate capability registry
↓
多数のformatが登録されることを確認
実際のruntime evidence
↓
Text / Markdown / CSV / JSON
という三段階です。
どれも嘘ではありません。
ただし、意味が違います。
「対応している」と「品質保証できる」は別
自分はこの違いを、
SUPPORTED BY LIBRARY
↓
AVAILABLE
↓
QUALIFIED
↓
PRODUCT GUARANTEE
くらいに分けて考えることにしました。
SUPPORTED BY LIBRARY
upstream libraryがそのformatのextractorを持っている。
AVAILABLE
自分のbuildでdependencyやfeatureが閉じ、そのformatを利用可能にできる。
QUALIFIED
実際のfixtureや異常系を含め、自分の用途で必要な検証を通した。
PRODUCT GUARANTEE
その検証結果を根拠に、自分の製品として正式に保証する。
これを分けないと、
libraryのREADMEに対応と書いてあるから、うちの製品でも対応です
という危険な飛躍が起きます。
巨大documentならmemory使用量が問題になるかもしれません。
malformed fileならpartial extractionになるかもしれません。
OCR backendが無ければ取得できない情報があるかもしれません。
「parse成功」と「必要な情報を十分回収できた」も同じではありません。
だから、
library capabilityを広く利用すること
と、
製品として保証すること
は別にした方がいいと思っています。
これはXbergを疑っているという話ではありません。
むしろ、100形式超という広いcapabilityを現実的に活用するために、保証範囲を別管理するという考え方です。
全formatの完全検証が終わるまで一切使わないより、
利用可能
↓
実際に使う
↓
検証を積む
↓
正式保証へ上げる
方が現実的です。
Xbergにも自分の用途では不足する境界がある
Xbergを採用したからといって、自分が必要としていたI/Oやresource boundaryまで全部解決したわけではありません。
現在のupstream Xberg 1.1.0 APIを確認した結果、自分の用途では少なくとも、
- caller-owned retained input
- bounded incremental output
- source span
- provider capability boundary
などについて、追加のadapterや将来的なextensionを検討する余地があります。
ここは重要なので明記しておきます。
これらは現在のXberg標準APIとして存在すると主張しているわけではありません。
実際、今回の統合作業用にXbergのfork branchも用意していますが、記事執筆時点ではfork固有のsource変更はまだなく、upstream v1.1.0 baseそのものです。
現在実際に製品側で使っているdependencyも、forkではなくcrates.ioのupstream Xberg 1.1.0です。
つまり、
Xberg upstream
↓
現状そのまま利用
自分のapplication側
↓
追加のqualification / provider boundary
という段階です。
「自分が欲しい機能」と「Xbergに既にある機能」は混同しないようにしています。
371言語のcode intelligenceも面白い
Xbergはdocumentだけでなくsource codeも扱えます。
Xberg 1.1.0はtree-sitterを利用したcode intelligenceとして、371 programming languagesを掲げています。
単にsource fileをplain textとして読むだけでなく、
- functions
- classes
- imports
- exports
- symbols
- docstrings
- structure
- semantic chunks
などを取得できます。
たとえば、
source tree
↓
Xberg / tree-sitter
↓
function
class
import
symbol
docstring
↓
検索 / RAG / Agent
という用途が考えられます。
大量のdocumentだけでなくsource treeも検索対象へ入れたい場合、個別言語ごとにparserを揃えなくていいのはかなり魅力的です。
LiteParseも調べた
Xbergを探している途中でもう一つ見つけたのが、Rust製document parserのLiteParseです。
日本語では、LiteParseについてはすでにQiitaで紹介記事があります。
最初はXbergとLiteParseを同じカテゴリのlibraryとして比較していたのですが、調べるほど主戦場が違うことが分かりました。
ざっくり整理すると、
| 観点 | Xberg | LiteParse |
|---|---|---|
| 主戦場 | 汎用document extraction | PDF / OCR / spatial extraction |
| 対応形式 | 100形式超 | PDF中心+Office・images等 |
| Office | native extraction中心 | LibreOfficeでPDFへ変換 |
| Spreadsheet | spreadsheetとしてextract | PDFへ変換してparse |
| JSON / XML / Markdown等 | broad formatの一部 | 汎用parser基盤の中心ではない |
| PDF default | xberg-native-pdf / pure Rust | PDFium |
| OCR | 複数backend | 強い |
| bbox / geometry | 対応 | 特に強い |
| Rust | ◎ | ◎ |
| License | MIT | Apache-2.0 |
| 向いている用途 | broad-format extraction | PDF / OCR / layout重視 |
どちらが上という話ではありません。
中心に置いている問題が違います。
LiteParseは「PDF世界へ集めてから深く読む」
LiteParseのmulti-format supportでは、Office documentをLibreOfficeでPDFへ変換してからLiteParseのPDF pipelineへ渡します。
概念的には、
DOCX / XLSX / PPTX
↓
LibreOffice
↓
PDF
↓
LiteParse
です。
これはかなり合理的です。
入力形式ごとに別々のsemantic modelを持つのではなく、PDFへ収束させれば、
- page geometry
- bounding box
- OCR
- layout
- screenshot
- visual structure
を一貫した世界で扱えます。
一方、自分が欲しかったのは、
XLSXをPDFとして読む
より、
XLSXをSpreadsheetとして読む
方だったので、broad-format foundationにはXbergを選びました。
PDFについてはLiteParseがかなり面白い
逆にPDFだけを見ると、LiteParseにはXbergとは違う魅力があります。
特にpage geometryとbounding boxが強く、layout blockだけでなくtable cell単位でもbboxを保持できます。
そしてかなり面白いのがComplexity Detectionです。
full parseする前にcheapなtext-layer-only passを行い、
このpageはnative textで十分
このpageはOCRが必要
このdocumentは重い処理へ送る
というroutingに使えます。
各pageにはneeds_ocrだけでなく、
scanned
no-text
sparse-text
embedded-images
garbled
vector-text
annotation-text
などの理由も付きます。
これは全部のdocumentへ無条件で高コスト処理をかけるよりかなり良い設計です。
自分の用途でも将来的には、
Xberg
↓
通常のbroad extraction
↓
難しいPDFと判定
↓
specialized PDF provider
↓
layout / bbox / OCR-heavy extraction
という構成は十分あり得ると思っています。
その候補としてLiteParseは覚えておきたいlibraryです。
なので自分の整理では、
Xberg
→ 広く読む
LiteParse
→ PDFを深く読む
くらいが一番しっくりきています。
Kreuzberg v4を使っている場合
既存のKreuzbergユーザー向けにも少し注意があります。
Xberg 1.0はKreuzberg v4系の後継として登場し、active developmentはXberg側へ移っています。
一方、Kreuzberg v4はLTS repositoryとして残されていて、2026年末までcritical bugとsecurity fixをbest-effortで提供する方針です。
新機能はXberg側へ入ります。
そのため既存Kreuzberg v4 applicationを即座に捨てる必要はありませんが、新しく調べ始める場合はXberg側を見る方が自然です。
そして検索結果にはKreuzberg時代のAPI例がかなり残っています。
Rustで利用する場合は特に、
この記事はKreuzberg v4なのか
Xberg 1.xなのか
を確認した方がいいと思います。
まとめ
最初は、
Text parser
Markdown parser
JSON parser
CSV parser
PDF parser
DOCX parser
XLSX parser
PPTX parser
...
を一つずつ揃えようとしていました。
しかしformatを増やすたびに、
parser
dependency
failure mode
normalization
resource management
platform差
security
license
upgrade
まで増えていきます。
それを続けると、本来作りたかったソフトウェアよりparser管理の方が大きくなってしまいます。
そこで、
さまざまなdocument
↓
Xberg
↓
自分のapplication boundary
↓
検索 / RAG / Agent / その他
という構成にしました。
parserそのものを作ることが目的ではなく、parseした結果を使って何かを作りたいなら、Xbergはかなり面白い選択肢だと思います。
ただし、
Xbergが対応している
=
自分の製品で品質保証できる
ではありません。
この境界は残した方がいい。
そしてPDF、OCR、layout、bounding boxを深く扱いたいなら、LiteParseのようなspecialized parserにも別の強みがあります。
今回自分が選んだのは、
broad-format foundation
↓
Xberg
でした。
Rustで、
PDFだけじゃなくOfficeもCSVもJSONもHTMLもemailも来るんだけど……
となっている人には、一度調べてみる価値があると思います。