undoc
High-performance Microsoft Office document extraction to Markdown.
Installation
pip install undoc
Usage
Basic Usage
from undoc import parse_file
# Parse a document
doc = parse_file("document.docx")
# Convert to Markdown
markdown = doc.to_markdown()
print(markdown)
# Convert to plain text
text = doc.to_text()
# Convert to JSON
json_data = doc.to_json()
With Context Manager
from undoc import parse_file
with parse_file("document.xlsx") as doc:
print(doc.to_markdown(frontmatter=True))
print(f"Sections: {doc.section_count}")
print(f"Resources: {doc.resource_count}")
Parse from Bytes
from undoc import parse_bytes
with open("document.pptx", "rb") as f:
data = f.read()
doc = parse_bytes(data)
markdown = doc.to_markdown()
Extract Resources (Images)
from undoc import parse_file
doc = parse_file("document.docx")
# Get all resource IDs
resource_ids = doc.get_resource_ids()
for rid in resource_ids:
# Get resource metadata
info = doc.get_resource_info(rid)
# "primary": an image the document shows. "alternate" (a picture's SVG original) and
# "layer" (an HD Photo effects layer) belong to the primary named by companion_of.
if info["role"] != "primary":
continue
print(f"Resource: {info['filename']} ({info['mime_type']})")
# Get resource binary data
data = doc.get_resource_data(rid)
# Save to file
with open(info['filename'], 'wb') as f:
f.write(data)
Document Metadata
from undoc import parse_file
doc = parse_file("document.docx")
print(f"Title: {doc.title}")
print(f"Author: {doc.author}")
print(f"Sections: {doc.section_count}")
print(f"Resources: {doc.resource_count}")
Render a Slide
A slide of a .pptx renders to PNG from the parsed document, without reading the file again:
from undoc import parse_file
with parse_file("deck.pptx") as doc:
slide = doc.render_section(0, dpi=150)
with open("slide1.png", "wb") as f:
f.write(slide.png)
if any(slide.gaps.values()):
print("not painted:", slide.gaps)
What the renderer cannot paint yet — charts, embedded objects and other graphic frames, custom geometry,
pictures other than PNG and JPEG, text in a script that needs shaping — is left out and counted
in gaps; the rest of the slide is painted. No font is bundled. Text is drawn in the directories you name
(font_dirs), then the system's. A Linux container without fonts draws no text — install a font
package (Noto Sans CJK covers Latin and East Asian text) or pass a font directory — and
reports the runs as gaps. A section the document does not have raises UndocError with
kind == ErrorKind.SECTION_OUT_OF_RANGE; a document that is not a presentation,
UNSUPPORTED_FORMAT.
Handling Failures
UndocError.kind says why a call failed, so you can react to the reason instead of
matching on message text:
from undoc import ErrorKind, UndocError, parse_file
try:
doc = parse_file(path)
print(doc.to_markdown())
except UndocError as err:
if err.kind is ErrorKind.ZIP_ARCHIVE:
print("The file is damaged.")
elif err.kind in (ErrorKind.UNKNOWN_FORMAT, ErrorKind.UNSUPPORTED_FORMAT):
print("Not a supported Office document.")
elif err.kind is ErrorKind.ENCRYPTED:
print("The document is encrypted.")
else:
# Also the right branch for a reason this build has no name for.
print(f"Extraction failed ({err.kind}): {err}")
The numbers behind ErrorKind are a stable ABI contract: a new reason takes the next free
number and existing ones are never renumbered. Always keep a final else — an
unrecognised value arrives as a plain int rather than an ErrorKind, so that a newer
native library stays usable. kind is ErrorKind.OTHER for failures raised by the wrapper
itself, and never ErrorKind.NONE (which means success).
Supported Formats
- DOCX - Microsoft Word documents
- XLSX - Microsoft Excel spreadsheets
- PPTX - Microsoft PowerPoint presentations
Features
- RAG-Ready Output: Structured Markdown optimized for RAG/LLM applications
- High Performance: Native Rust implementation via FFI
- Asset Extraction: Images and embedded resources
- Metadata Preservation: Document properties, styles, formatting
- Cross-Platform: Windows, Linux, macOS (Intel & ARM)
API Reference
Functions
parse_file(path)- Parse document from file pathparse_bytes(data)- Parse document from bytesversion()- Get library version
Undoc Class
Conversion Methods
to_markdown(frontmatter=False, escape_special=True, paragraph_spacing=True, refine=False, *, ...)- Convert to Markdown (escape_special=Falsewrites text without escaping Markdown special characters). Keyword-only, all defaulting to the library's own values:image_path_prefix,include_images,table_fallback("markdown","html"),max_heading_level(1-6),preserve_line_breaks,include_empty_paragraphs,list_marker,cleanup(None,"minimal","standard","aggressive"),revision_handling("accept_all","reject_all","show_markup"),strip_redundant_emphasis_in_headings,emit_page_breaks,include_headers_footers,callout_blockquote,section_markers("none","comment"). A value the library cannot honour raisesUndocErrorwithErrorKind.INVALID_ARGUMENT.to_text()- Convert to plain textto_json(compact=False)- Convert to JSONplain_text()- Get plain text (fast extraction)
Properties
title- Document titleauthor- Document authorsection_count- Number of sectionsresource_count- Number of resources
Tables
get_tables(tsv=False)- Every table as CSV (RFC 4180), or tab-separated withtsv=True, in reading order:{"section", "index", "text"}— the section (a sheet, a slide, a document section), the table's place among that section's tables (from 1), and the text. A table nested in a cell is a table of its own, right after the one that holds it. A merged cell's text is in its top-left position and the positions it covers are empty, so every record has the same number of fields:pandas.read_csv(io.StringIO(t["text"]))reads one.
Resource Methods
get_resource_ids()- List of resource IDsget_resource_info(id)- Resource metadataget_resource_data(id)- Resource binary data
Rendering
render_section(index, dpi=150, font_dirs=(), system_fonts=True)- A slide as PNG, its size, and what was not painted (RenderedSection)
Native library
The package ships the native library for your platform. To load another build of it — one
you compiled from the Rust crate, say — set UNDOC_LIB_PATH to that file. A path that names no file
is an error: importing raises OSError naming the path, rather than quietly loading the
packaged library instead.
License
MIT License - see LICENSE for details.
Metadata
Release files for undoc 0.23.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| undoc-0.23.0.tar.gz | 9.5 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| undoc-0.23.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 19.1 MB
Release files / undoc-0.23.0.tar.gz
| Download URL | undoc-0.23.0.tar.gz |
|---|---|
| Size | 9.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
67657113fc61ff409ab59153017eecf12bc3f81a45aaa00d5ba95e1c5a3f8656
|
|
BLAKE2b-256 checksum How to use checksums |
0ef7f6c1d310df5aa6975363617942286e640c07f31a1ac9b63f0ed8f8b0193d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / undoc-0.23.0-py3-none-any.whl
| Download URL | undoc-0.23.0-py3-none-any.whl |
|---|---|
| Size | 9.6 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
79d9c41706e4b84b3cb980bef8410725e997cbac31baf74f1dbf1423614fea95
|
|
BLAKE2b-256 checksum How to use checksums |
2a1fc6de7c3382293bd10c4de47706d5dafd07b6601fcfbc7b9707340343bb6c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|