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.27.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| undoc-0.27.1.tar.gz | 9.6 MB | Without provenance | Details for undoc-0.27.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| undoc-0.27.1-py3-none-any.whl | Python 3 | none | any | Details for undoc-0.27.1-py3-none-any.whl |
Total release size: 19.2 MB
Release files / undoc-0.27.1.tar.gz
| Download URL | undoc-0.27.1.tar.gz |
|---|---|
| Size | 9.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1182029fb92d9ad6cca0bb12a59b06d631259b4f23b45fb2bdbe5d99a6d16e94
|
|
BLAKE2b-256 checksum How to use checksums |
0fc94843ded176b2e551cea07dfeef46e81896db871d4cf3aef6488538324cd4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / undoc-0.27.1-py3-none-any.whl
| Download URL | undoc-0.27.1-py3-none-any.whl |
|---|---|
| Size | 9.6 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
172fc0677117fdaa0181a9f43c8633c33279d2273a7d0f367749d7a3b372043e
|
|
BLAKE2b-256 checksum How to use checksums |
f59b31d065a51849690ca317d29f066d0cce6aca2fcd45fd6b9efb44a1b01c81
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for undoc 0.27.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.