Skip to main content

Convert Microsoft Office documents (DOCX, XLSX, PPTX) and PDFs to Markdown

Project description

office2md

Convert Microsoft Office documents to Markdown with intelligent converter selection.

Python 3.8+ License: MIT

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:

  1. Detects the numId used as section headings by analyzing the DOCX numbering XML
  2. Identifies the full family of structural numIds (main sections + sub-sections)
  3. Promotes those paragraphs to real Heading styles with correct hierarchy (##, ###)
  4. Runs Pandoc on the pre-processed DOCX
  5. 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:

![Diagram](./My-Document_images/image1.png)

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.
  • ## Índice is 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) ![](relative.png) GFM pipe tables GitHub, AzDO Wiki, Obsidian
Classic --pandoc-variant classic ![](relative.png) 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. Creates SCHEMA.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 with mkdocs.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 .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

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Commit your changes: git commit -m 'Add my feature'
  4. Push and open a Pull Request

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

office2md-0.5.6.tar.gz (63.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

office2md-0.5.6-py3-none-any.whl (68.1 kB view details)

Uploaded Python 3

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

Hashes for office2md-0.5.6.tar.gz
Algorithm Hash digest
SHA256 69c9621c5b968271c946d8b5c2ee84aa5273c48f3560d209bbd95ecd2e48f98e
MD5 ac1a4afeff82ce8764efb2001e707301
BLAKE2b-256 90ef01801337ecec88e4bebc1595d036414d3794ba34838667e8d1e9f85631ea

See more details on using hashes here.

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

Hashes for office2md-0.5.6-py3-none-any.whl
Algorithm Hash digest
SHA256 66028d3afb4d652a62ee3db84e4c1648d02f9382c1de349d8b9b2baba69e8547
MD5 c13d441216c76a95a8fc1ad242c50cc2
BLAKE2b-256 8b9726bb5f2369ad9b873699e5404d7d4917aca306d03923483cc7f83b291514

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page