Parsers by format
What each parser produces, and where it gives up.
The parser turns a document into typed elements. Seven types come out of it, and they are the same for every format — which is what lets the chunker be format-agnostic.
| Type | What it is |
|---|---|
heading | A section title, with its level and its place in the document's heading tree. |
paragraph | Prose. |
list_item | One bullet or numbered item. |
table | Rows and cells, kept as a table. |
code | A fenced block. |
image_caption | A caption, or a vision model's description of an image. |
footnote | A note at the foot of a page. |
pdfplumber — over pdfminer.six — with layout heuristics on top. Headings are detected from font size, weight and spacing; tables from the page's own ruling geometry; paragraphs from the rest.
The PDF library is a configuration choice, not a dependency of the
architecture. atrium/pdf_backend.py defines a seam over the seven
primitives the parser needs — open, text with geometry, tables, render
a page, embedded images, their bytes — and ATRIUM_PDF_BACKEND
selects the implementation. The default is pdfplumber, which is MIT
licensed. pip install "atrium-rag[fast-pdf]" adds PyMuPDF, which is
faster and AGPL-3.0; AGPL §13 reaches a service offered over a
network, so it is opt-in and never a default.
Page numbers are the real page numbers — the page of the PDF.
# Vision captioning for a scanned or image-only page
VISION_ENABLED=1 VISION_MODEL=minicpm-v4.6:1b atrium serveWhere it gives up
- Text that is baked into a figure is not text. It needs OCR, which is off by default.
- A two-column paper is read in column order, not across the page, but the heuristic can be wrong about where a column ends when a full-width figure cuts through it.
- Encrypted PDFs are rejected rather than guessed at.
DOCX
python-docx. Headings, paragraphs, lists and tables.
Where it gives up
- A
.docxhas no pages. The page numbers in the output are derived from heading boundaries — useful, and not the same thing as paper. - Text boxes, headers and footers are not part of the body flow.
PPTX
python-pptx. One slide becomes one chunk, with the slide's title as
its heading and the notes attached.
Where it gives up
- The visual order of a slide is not the reading order. A slide with two columns of text comes out in shape order.
- Charts and diagrams are not described unless a vision adapter is on.
XLSX and XLS
openpyxl for .xlsx, xlrd for the legacy .xls.
One chunk per sheet, with the header row kept with the data.
Where it gives up
- The sheet is the unit of work, not the row. A sheet with a hundred
thousand rows is one chunk, and it will be too large to embed
usefully. Split it yourself, or use
customchunking. - Merged cells are flattened to their top-left value.
HTML
beautifulsoup4. <h1> and <h2> mark page boundaries; the text
between them is the body.
Where it gives up
- Navigation, asides and footers are not stripped. They end up in the text, and they end up in the embedding.
- Pages are derived from headings, not from any notion of a printed page.
Markdown, plain text, CSV, JSON
Parsed directly. Markdown headings become heading elements; CSV
becomes a table with its header row; JSON is walked for text.
EPUB and RTF
striprtf for RTF; EPUB is unpacked and its spine walked.
Where it gives up
- EPUB is reflowable, so page numbers come from the spine position rather than from paper.
ODT, ODS, ODP
odfpy walks the body tree directly. Pages come from the document's
outline level.
Images
JPG, PNG and WebP go to the vision adapter, which describes them.
pip install "atrium-rag[vision]"
VISION_ENABLED=1 atrium serveWhere it gives up
- It needs a vision model. Without one, an image becomes a placeholder element rather than a description.
- It is slow: one model call per image.
OCR
For scanned pages and image-only documents:
pip install "atrium-rag[ocr]"
OCR_ENABLED=1 OCR_PROVIDER=tesseract OCR_LANG=eng atrium serveTwo providers:
| Provider | What it needs | Good at |
|---|---|---|
tesseract | The Python wrapper and the system binary | English, and many other languages with extra packs |
rapidocr | Nothing but pip — the models ship in the wheel | Chinese and English; mixed-script scans |
The RapidOCR model reads Chinese and English only. The provider is chosen at runtime, not at install time, so switching is a config change and a restart.
Choosing a parser
There is no "best" parser here — there is the one that suits your corpus. The trade is between a layout model that reads more layouts and a heuristic one that needs no GPU, and Atrium is the second kind.
If a format is not listed here, the API still accepts it and the
parser will say so in the job's error field. Add a format by
extending UniversalParser — the element contract is seven types and
the chunker downstream does not need to know what came out of it.
Atrium Cloud
Don't want to run it yourself? Atrium Cloud is coming.
It is not here yet — the list is what there is until it is. The library is free, MIT licensed, and yours either way.