Convert Microsoft Office documents (DOCX, XLSX, PPTX) and PDFs to Markdown
Project description
office2md
Convert Microsoft Office documents to Markdown with intelligent converter selection.
Features
- DOCX → Markdown with images, tables, and formatting
- XLSX → Markdown tables (single or all sheets)
- PPTX → Markdown with slide content and speaker notes
- PDF → Markdown using local ML/OCR extraction (via Docling)
- Batch processing with recursive directory support
- Automatic image extraction to a separate folder with sanitized paths
- Normalized DOCX pipeline — recovers section hierarchy from Word documents that use auto-numbered lists instead of real headings
- Optional local-only privacy mode for sensitive documents
- Optional visual QA gate with rendered page diffs and JSON/HTML reports
- Standalone wiki builder for simple, semantic/agent, or MkDocs-published Markdown wikis
Installation
pip install office2md
For best results with DOCX files, also install Pandoc:
# macOS
brew install pandoc
# Ubuntu/Debian
sudo apt-get install pandoc
# Windows
choco install pandoc
For PDF support:
pip install docling
For visual QA:
pip install office2md[visual]
# DOCX visual QA also requires LibreOffice/soffice on PATH.
Quick Start
# Convert a single file (auto-selects best converter)
office2md document.docx
# Specify output path
office2md document.docx -o output/result.md
# Convert all files in a directory
office2md --batch ./input -o ./output
# Recursive batch processing
office2md --batch ./input -o ./output --recursive
# Mirror a source tree into an idempotent Markdown tree for wiki/KB work
office2md --mirror ./input -o ./output --recursive --dry-run
office2md --mirror ./input -o ./output --recursive --overwrite --include docx,pdf
# Build an agent-facing Markdown wiki from converted output
office2md-wiki ./output -o ./knowledge-wiki --title "Knowledge Wiki" --mode semantic
Normalized DOCX Pipeline
Many corporate Word documents are structured using auto-numbered lists instead of real heading styles. When converted with standard tools, these documents lose their section hierarchy — chapters and sub-sections become flat bullet lists.
The office2md converter detects this automatically and handles it transparently — no flags, no extra steps. When you run a batch conversion, each document is analyzed before conversion:
- Document already has real headings → converted directly with Pandoc
- Document uses auto-numbered lists as sections → pre-processed to promote those paragraphs to real headings, then converted with Pandoc
# This is all you need — normalization happens automatically when required
office2md --batch ./input -o ./output
What it does internally
For documents that need normalization:
- Detects the
numIdused as section headings by analyzing the DOCX numbering XML - Identifies the full family of structural numIds (main sections + sub-sections)
- Promotes those paragraphs to real
Headingstyles with correct hierarchy (##,###) - Runs Pandoc on the pre-processed DOCX
- Cleans up TOC links and image paths
For documents that already have proper heading styles (e.g. most technical specs), step 1–4 are skipped entirely — no modification to the original file.
Output structure
output/
├── My-Document.md
├── My-Document_images/
│ ├── image1.png
│ └── image2.png
└── Another-Document.md
Image references in the Markdown use relative paths:

Note: filenames are sanitized to be AzDO Wiki / filesystem-safe: spaces become hyphens, special characters are removed.
Table of Contents
Every converted document gets a [[_TOC_]] tag at the very top, followed by a standard Markdown ## Índice block with anchor links:
[[_TOC_]]
## Índice
- [1. Objetivo](#1-objetivo)
- [2. Descrição da Solução](#2-descrição-da-solução)
...
[[_TOC_]]is rendered natively by Azure DevOps Wiki, Confluence, and GitLab as an auto-generated index. In viewers that don't support it (GitHub, Obsidian, VSCode preview) it appears as harmless literal text and can be deleted.## Índiceis a portable Markdown TOC that works everywhere. Anchor slugs follow the GitHub Slugger algorithm (Unicode-preserving, each space →-), ensuring compatibility with AzDO Wiki, VSCode, and GitHub.
Pandoc Markdown Variant
When using Pandoc (the default converter), you can choose between two output flavors:
# GFM — GitHub Flavored Markdown (default)
office2md --batch ./input -o ./output
# Classic — standard Pandoc Markdown (pipe tables, ATX headings)
office2md --batch ./input -o ./output --pandoc-variant classic
| Variant | Flag | Images | Tables | Best for |
|---|---|---|---|---|
| GFM (default) | (none) |  |
GFM pipe tables | GitHub, AzDO Wiki, Obsidian |
| Classic | --pandoc-variant classic |
 |
Pandoc pipe tables | MkDocs, Sphinx, generic MD |
Converter Selection
DOCX Converters (Priority Order)
| Converter | Quality | Tables | Requirements |
|---|---|---|---|
| Pandoc | ⭐⭐⭐ Best | ✅ Excellent | External binary |
| Mammoth | ⭐⭐ Good | ⚠️ Partial | Pure Python |
| python-docx | ⭐ Basic | ✅ Basic | Pure Python |
By default, office2md automatically selects the best available converter:
Pandoc (if installed) → Mammoth → python-docx
Force a specific converter:
office2md document.docx --use-pandoc # best for complex tables
office2md document.docx --use-mammoth # good pure-Python formatting
office2md document.docx --use-basic # python-docx fallback
PDF Converter
office2md document.pdf --use-docling # ML-based, excellent for scanned docs
Note: Docling is for PDF only. For DOCX, use the default converter or Pandoc.
CLI Reference
office2md [OPTIONS] INPUT
Arguments:
INPUT Input file or directory (with --batch or --mirror)
Options:
-o, --output PATH Output file or directory
-v, --verbose Enable verbose output
DOCX Converter Selection:
--use-pandoc Force Pandoc (best tables)
--use-mammoth Force Mammoth (good formatting)
--use-basic Force python-docx (fallback)
--use-docling Use Docling (PDF only, local ML-based extraction)
--local-only Privacy mode: no LLM/cloud APIs; set offline flags for ML dependencies
Image Options:
--skip-images Skip image extraction
--images-dir PATH Custom directory for images
Visual QA:
--visual-qa Render source/Markdown pages and write visual QA reports
--visual-threshold FLOAT Minimum visual score for the optional QA gate
--quality-report PATH Directory for JSON/HTML quality reports
Batch Processing:
--batch Process directory of files
-r, --recursive Process subdirectories
Mirror Options:
--mirror Mirror origin directory to destination (idempotent)
--overwrite Overwrite existing Markdown files
--dry-run Plan but do not execute conversions
--include EXT Comma-separated extensions (default: docx,pdf)
Format-Specific Options:
--first-sheet-only XLSX: Convert only first sheet
--no-notes PPTX: Skip speaker notes
Batch vs Mirror
Use --batch for quick one-off bulk conversion.
Use --mirror when you need repeatable, idempotent runs — for wikis, knowledge bases, or any pipeline you will run again.
| Feature | --batch |
--mirror |
|---|---|---|
| Skips already-converted files | ❌ | ✅ |
| Dry-run preview | ❌ | ✅ |
Extension filtering (--include) |
❌ | ✅ |
| Per-document asset isolation | ❌ | ✅ |
| Richer reporting | ❌ | ✅ |
# Quick bulk conversion
office2md --batch ./documents -o ./markdown --use-pandoc
# Repeatable mirror with dry-run first
office2md --mirror ./documents -o ./markdown --recursive --dry-run
office2md --mirror ./documents -o ./markdown --recursive --overwrite --include docx,pdf
Wiki Builder CLI
office2md-wiki builds a wiki from a directory of converted Markdown files.
office2md-wiki SOURCE_DIR -o OUTPUT_DIR [OPTIONS]
Options:
-o, --output PATH Output wiki directory
--title TEXT Wiki title
--mode MODE semantic, simple, or published (default: semantic)
--no-overwrite Do not delete the output directory before building
Modes:
semantic— best for AI-agent knowledge bases. CreatesSCHEMA.md,index.md,raw/,products/,concepts/structure.simple— preserves the converted tree and adds navigation indexes (Obsidian-style).published— creates a MkDocs Material site structure withmkdocs.yml.
# Convert first, then build wiki
office2md --mirror ./source-documents -o ./converted-markdown --recursive
office2md-wiki ./converted-markdown -o ./knowledge-wiki --title "Architecture KB" --mode semantic
Full guide: docs/wiki-builder.md.
Privacy and Data Handling
office2md is a local document conversion tool. It does not send documents to OpenAI, Anthropic, Azure OpenAI, or any other cloud API during conversion.
- DOCX uses Pandoc, Mammoth, or
python-docx— all local. - XLSX uses
openpyxl— local. - PPTX uses
python-pptx— local. - PDF uses Docling — local ML/OCR pipeline.
For sensitive documents, use --local-only:
office2md confidential.pdf --use-docling --local-only
office2md --mirror ./documents -o ./markdown --recursive --local-only
--local-only sets HF_HUB_OFFLINE=1, TRANSFORMERS_OFFLINE=1, HF_DATASETS_OFFLINE=1, HF_HUB_DISABLE_TELEMETRY=1, and DO_NOT_TRACK=1. It is a best-effort application-level guard — not an OS firewall. For hard guarantees on regulated data, run in a network-blocked container or VM.
Python API
from office2md import ConverterFactory
# Auto-select converter
converter = ConverterFactory.create("document.docx")
markdown = converter.convert()
converter.save(markdown)
# With options
converter = ConverterFactory.create(
"document.docx",
output_path="output.md",
extract_images=True,
images_dir="./images"
)
markdown = converter.convert()
converter.save(markdown)
Using Specific Converters
from office2md.converters import DocxConverter, PandocConverter
converter = DocxConverter("document.docx", use_pandoc=True)
# Or use Pandoc directly
converter = PandocConverter("document.docx")
markdown = converter.convert()
Mirror via Python API
from office2md.automation import MirrorOptions, execute_mirror
report = execute_mirror(
"./source-docs",
"./markdown",
options=MirrorOptions(overwrite=False),
)
print(report.converted_count, report.failed_count, report.skipped_count)
Mirrored assets are placed beside the generated Markdown:
source-docs/runbooks/app/manual.docx
↓
markdown/runbooks/app/manual.md
markdown/runbooks/app/_assets/manual/image_1.png
Quality Comparison
Compare converters on a specific document:
python scripts/quality_check.py document.docx
🏆 BEST CONVERTER: pandoc (score: 100/100)
Converter Status Time Size Images Headings Tables Issues
pandoc ✅ 3.25s 62,678 30 1 0 0
default ✅ 4.19s 24,338 30 1 0 2
Supported Formats
| Format | Extension | Converter | Notes |
|---|---|---|---|
| Word | .docx |
Pandoc/Mammoth/python-docx | Auto-fallback |
| Excel | .xlsx |
openpyxl | All sheets or first only |
| PowerPoint | .pptx |
python-pptx | With/without notes |
.pdf |
Docling | Requires pip install docling |
Troubleshooting
"Pandoc not available"
# macOS
brew install pandoc
# Ubuntu
sudo apt-get install pandoc
Tables not rendering correctly
Force Pandoc — it handles complex tables best:
office2md document.docx --use-pandoc
Section headings become bullet lists
Your DOCX uses auto-numbered lists instead of real heading styles. Use the Normalized DOCX Pipeline.
Images not showing in Markdown
Check that the _images folder is beside the .md file and that the image paths in the Markdown use relative references (e.g. ./My-Document_images/image1.png). The normalized pipeline handles this automatically.
PDF conversion fails
pip install docling
Development
git clone https://github.com/rodgui/office2md.git
cd office2md
pip install -e ".[dev]"
pytest
pytest --cov=office2md
License
MIT License — see LICENSE for details.
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m 'Add my feature' - Push and open a Pull Request
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file office2md-0.5.6.tar.gz.
File metadata
- Download URL: office2md-0.5.6.tar.gz
- Upload date:
- Size: 63.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
69c9621c5b968271c946d8b5c2ee84aa5273c48f3560d209bbd95ecd2e48f98e
|
|
| MD5 |
ac1a4afeff82ce8764efb2001e707301
|
|
| BLAKE2b-256 |
90ef01801337ecec88e4bebc1595d036414d3794ba34838667e8d1e9f85631ea
|
File details
Details for the file office2md-0.5.6-py3-none-any.whl.
File metadata
- Download URL: office2md-0.5.6-py3-none-any.whl
- Upload date:
- Size: 68.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
66028d3afb4d652a62ee3db84e4c1648d02f9382c1de349d8b9b2baba69e8547
|
|
| MD5 |
c13d441216c76a95a8fc1ad242c50cc2
|
|
| BLAKE2b-256 |
8b9726bb5f2369ad9b873699e5404d7d4917aca306d03923483cc7f83b291514
|