X-Ray.Content
X-Ray.Content extracts the content of a document: its text, rendered as plain text, Markdown, HTML, Djot or JSON, plus metadata, tables, images, URIs and per-page content. It reads 97 MIME types across 128 file extensions with 48 extractors. It is meant for search indexes and LLM pipelines, where what matters is what a document says rather than how it is laid out.
It is a native C# port of the Rust xberg extraction
engine, checked against xberg's output on a shared fixture corpus. Extraction is pure managed
code. The optional OCR pass is the one exception, and it is off by default.
curiosity-ai/x-ray-content X-Ray.Content on NuGet
Install
dotnet add package X-Ray.Content
Quick start
using XRay.Content.Core;
var extractor = new Extractor();
var result = extractor.Extract(
ExtractInput.FromUri("quarterly-report.pdf"),
new ExtractionConfig { OutputFormat = OutputFormat.Markdown });
var doc = result.Results[0];
Console.WriteLine(doc.MimeType); // application/pdf
Console.WriteLine(doc.Content); // the rendered Markdown
Console.WriteLine(doc.Metadata.Title);
Console.WriteLine(doc.Tables.Count);
When the file is already in memory, pass the bytes and their MIME type:
using XRay.Content.Core;
var input = ExtractInput.FromBytes(bytes, "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
Extract does not throw for a bad document. A failure is reported in result.Errors with a
type and a code, and an unsupported MIME type comes back as an empty document with a
ProcessingWarning. ExtractAsync takes the same arguments. Both take a CancellationToken as an extra last argument; see Cancellation.
Output formats
ExtractionConfig.OutputFormat selects how ExtractedDocument.Content is rendered. The
extracted document model is the same for every format; only the rendering differs.
| Format | Value |
|---|---|
| Plain text (default) | OutputFormat.Plain |
| Markdown (GFM) | OutputFormat.Markdown |
| HTML | OutputFormat.Html |
| Djot | OutputFormat.Djot |
| JSON section tree | OutputFormat.Json |
| Structured element list | OutputFormat.Structured |
| DocTags | OutputFormat.DocTags |
Related options:
ExtractedDocument.PlainContentholds the plain-text rendering alongsideContent, at no extra cost, for callers that index text and display markup.IncludeDocumentStructure = trueadds the heading tree inExtractedDocument.Document.RenderPagesInOutputFormat = truerenders each entry ofExtractedDocument.Pagesin the chosen output format too, so you can keep the Markdown and know which page each part is on.ResultFormat.ElementBasedreturns the flat element stream instead of one rendered string.
Formats it reads
- Office: Word, Excel, PowerPoint (OOXML and legacy binary), OpenDocument, RTF, WordPerfect, HWP/HWPX, iWork
- PDF: text, geometry, tables, form fields, annotations, embedded files, bookmarks, page labels, XMP, and encrypted documents opened with a password (see PDF)
- Markup and web: HTML, XML, Markdown, MDX, Djot, AsciiDoc, reStructuredText, Org, Typst, LaTeX, DocBook, JATS, OPML
- Email: EML, MSG, PST
- Books: EPUB, FictionBook
- Data: CSV/TSV, DBF, JSON/JSONL, YAML, TOML, Jupyter notebooks
- Academic: BibTeX and citation formats
- Subtitles: WebVTT
- Images: metadata and EXIF (pixels are read only by the OCR pass)
- Photoshop: PSD/PSB layer structure and type-layer text, through X-Ray.Psd; see Photoshop documents
- Archives: ZIP, TAR, 7z, GZip, extracted recursively as child documents
- Source code: identified by extension or shebang
Optional OCR
Off by default. Enabling it is the only way the package loads native code:
using XRay.Content.Core;
var config = new ExtractionConfig
{
Ocr = new OcrOptions { Mode = OcrMode.ScanOnly }, // or OcrMode.AllImages
};
| Mode | What it recognises |
|---|---|
Disabled (default) |
Nothing. No model is loaded and no native library is touched. |
ScanOnly |
PDF pages the scan detector flags; their text is appended to the page. |
AllImages |
Every embedded image above a size threshold, with its text inserted after the image, plus the ScanOnly behaviour. |
OCR only adds text; native text is never replaced. It is never fatal: a missing model, an
undecodable image or a recognition that exceeds PerImageTimeout becomes a
ProcessingWarning. Recognised tables come back as tables in ExtractedDocument.Tables.
The recognizer is PaddleOCR, which brings
SkiaSharp, and PDFium for rasterising scanned pages. Nothing is downloaded during extraction:
point OcrOptions.ModelDirectory at a checkpoint you have already fetched.
More in this section
Differences from xberg
The port aims for output identical to xberg's, with documented exceptions. The two that change
results are the optional OCR pass above, which uses a different engine from xberg's, and
Excel number formatting: numeric cells are rendered through their number format (3.2%,
$31,200) rather than as the raw value, controlled by XRayOptions.ExcelNumberFormats.
The port also adds features xberg does not have. Photoshop files are read by an extractor of their own, since xberg has none. The PDF reader gained annotations, embedded files and password handling, and extraction can be cancelled. None of these change the output for documents that do not use them. Audio and video transcription, embeddings, chunking, keyword extraction and the server modes of xberg are out of scope.
The repository's CLAUDE.md describes the architecture, scope and every deviation in detail.