Supported formats & how each is handled
doc2mark recognises an input file by its extension and dispatches it to a
dedicated processor. Every processor produces the same
ProcessedDocument (Markdown content plus
DocumentMetadata), so the format you feed in never changes
how you consume the result.
Two ideas are worth keeping in mind while reading this page:
Rule-based extraction is the default. Office, PDF, text, markup, and email files are parsed structurally (no model calls), so they need no OCR credentials. OCR is opt-in: it only runs when you pass
extract_images=Truetogether withocr_images=True(and only for the formats that carry images). See Content-aware OCR policy for exactly how that decision is made, and OCR for configuring a provider.Legacy Office formats need LibreOffice.
.doc,.xls,.ppt,.pps, and.rtfare converted to their modern equivalents by a local LibreOffice (soffice) install before parsing.
How a format is detected
Detection is extension-based (case-insensitive). The loader strips the leading
dot, lowercases it, and matches it against DocumentFormat.
Two aliases are normalised: .htm maps to HTML and .markdown maps to
MARKDOWN (whose canonical extension value is "md"). An unknown extension
raises UnsupportedFormatError.
from doc2mark import UnifiedDocumentLoader
loader = UnifiedDocumentLoader(ocr_provider=None) # no OCR needed
print(loader.supported_formats) # ['docx', 'xlsx', 'pptx', 'doc', ...]
Extension → processor → OCR
The “OCR” column reflects whether OCR can ever be involved. Where it says
Optional, OCR runs only when both extract_images=True and
ocr_images=True are passed and an OCR provider is configured; otherwise
the file is handled purely by rule-based extraction. Where it says Never, the
processor never calls OCR regardless of those flags.
Extensions |
Processor |
OCR |
Extraction approach |
|---|---|---|---|
|
|
Optional |
Rule-based OOXML parsing (advanced Office pipeline). Embedded images can be extracted as base64 and optionally OCR’d. |
|
|
Optional |
Rule-based text/table extraction via the advanced PDF pipeline (PyMuPDF fallback). Page/figure images can be extracted and optionally OCR’d. |
|
|
Optional |
LibreOffice converts to |
|
|
Optional |
Pillow reads the image; text content comes from OCR when requested.
|
|
|
Never |
Plain-text and structured-data parsing only. |
|
|
Never |
HTML/XML/Markdown to Markdown conversion. |
|
|
Never |
RFC 822 header + body extraction (optional; see below). |
Office formats (DOCX, PPTX, XLSX)
Modern Office files are parsed by OfficeProcessor through the advanced
Office pipeline. Paragraphs, headings, lists, and tables are converted to
Markdown with page / slide / sheet markers preserved. Complex tables with
merged cells respect the loader’s table_style ('minimal_html' by
default, or 'markdown_grid' / 'styled_html').
Images. With
extract_images=Truethe embedded media is returned as base64 inProcessedDocument.images; addingocr_images=Trueruns the configured OCR provider over those images (batched).Image-dominant docs. A
.docx/.pptxthat is mostly pictures with little real text (decided from the OOXML structure, no rendering) is routed through the PDF image strategy: LibreOffice renders it to PDF, the PDF processor OCRs whole pages, and the original Office identity is restored in the metadata (metadata.extra['routed_via'] == 'pdf'). This route is gated on OCR being requested and silently falls back to native parsing if anything (including a missing LibreOffice) is unavailable..xlsxnever routes this way.
loader = UnifiedDocumentLoader(ocr_provider=None)
doc = loader.load("report.docx")
print(doc.metadata.format) # DocumentFormat.DOCX
print(doc.metadata.page_count)
If the advanced pipeline is unavailable, the processor falls back to basic python-docx / openpyxl / python-pptx parsing.
PDF
PDFProcessor uses the advanced PDF pipeline (pdf_to_simple_json →
pdf_to_markdown) to extract text and tables; table extraction is always on.
The ocr_images flag is mapped to the pipeline’s use_ocr and only takes
effect together with extract_images=True. When the advanced pipeline cannot
be imported, the processor falls back to PyMuPDF (fitz): per-page text
extraction, and — if OCR is enabled — rendering each page at 300 DPI and OCRing
pages that have no text layer.
loader = UnifiedDocumentLoader(ocr_provider="openai", api_key="sk-...")
doc = loader.load("scanned.pdf", extract_images=True, ocr_images=True)
print(doc.content)
Legacy Office formats (DOC, PPT, PPS, XLS, RTF)
LegacyProcessor shells out to a local LibreOffice (soffice) install to
convert each legacy file to a modern container, then hands the result to
OfficeProcessor:
Source extension |
Converted to |
Backing processor |
|---|---|---|
|
|
Office (Word) |
|
|
Office (Word) |
|
|
Office (PowerPoint) |
|
|
Office (PowerPoint) |
|
|
Office (Excel) |
The original format and filename are restored in the metadata, and the
conversion is recorded under metadata.extra (converted_from /
converted_to). If LibreOffice is not found, processing raises
ProcessingError with installation guidance. Because the
output is a real Office document, the same image extraction / OCR options apply.
Images (PNG, JPG, JPEG, WEBP, TIFF, TIF, BMP, GIF, HEIC, HEIF, AVIF)
ImageProcessor opens the file with Pillow and writes a small Markdown header
describing the image (format, dimensions, mode, size). For standalone images the
only way to recover text is OCR:
ocr_images=True(with a configured provider) transcribes the image; the text is appended under an OCR Extracted Text section and mirrored injson_contentas atext:image_descriptionentry.extract_images=True(the default for this processor) also returns the image re-encoded as PNG base64 inProcessedDocument.images.
.heic / .heif decoding requires the optional pillow-heif package; if
it is not installed those formats simply cannot be opened.
loader = UnifiedDocumentLoader(ocr_provider="openai", api_key="sk-...")
doc = loader.load("receipt.jpg", ocr_images=True)
print(doc.content)
Text & data (TXT, CSV, TSV, JSON, JSONL)
TextProcessor handles plain-text and structured-data files entirely with the
standard library — no OCR, ever:
.txt— read as text (encodingdefaults toutf-8); all-caps short lines are promoted to Markdown headings..csv— the delimiter is auto-detected withcsv.Sniffer(override with thedelimiterargument) and rows become a Markdown table..tsv— same as CSV with a tab delimiter..json— parsed and rendered as nested Markdown; metadata records the top-leveldata_typeanditem_count..jsonl— each non-empty line is parsed as one record; invalid lines are skipped with a warning, and each record is rendered under its own heading.
loader = UnifiedDocumentLoader(ocr_provider=None)
doc = loader.load("data.csv", delimiter=";")
print(doc.metadata.row_count, doc.metadata.column_count)
Markup (HTML, XML, Markdown)
MarkupProcessor converts markup to Markdown without any OCR:
HTML / HTM — parsed with BeautifulSoup and converted via
markdownify(ATX headings,-bullets) when available, falling back to a built-inSimpleHTMLToMarkdownparser. Title, word, link, and image counts are recorded in the metadata.XML — parsed safely with
defusedxmland rendered as a heading tree (tags → headings, attributes → bullet lists, text/tail preserved). Metadata capturesroot_tagandelement_count.Markdown (.md / .markdown) — passed through largely as-is. Leading YAML front-matter is parsed (when PyYAML is installed) into
metadata.frontmatter, and heading / link / image counts are computed.
Email (EML)
EmailProcessor reads RFC 822 .eml files with the standard-library
email package — no OCR. It extracts the From, To, Cc,
Subject, and Date headers and the best body representation, preferring a
text/plain part and otherwise converting the text/html part to Markdown
with the same SimpleHTMLToMarkdown converter used for web pages. The default
Markdown rendering uses the subject as the top-level heading followed by the
remaining headers and the body; TEXT and JSON output formats are also
supported.
Note
.eml support is registered only when the email module and the
DocumentFormat.EML member are both present, so the rest of the loader
keeps working even in builds without it.
Choosing whether OCR runs
For every format above, plain extraction happens with no credentials. OCR is a
deliberate add-on you enable per call via extract_images / ocr_images
(images, Office, PDF, and image-dominant routing). The full decision
procedure — when an OCR provider is built, when it is invoked, and what happens
without credentials — is documented in Content-aware OCR policy.