Convert markdown/text/HTML to PPTX, DOCX, and XLSX using template-driven deterministic rendering with optional LLM normalization.
Project description
Text2OfficeProcessor — Intelligent Document Converter
Convert Markdown / Text / HTML to PPTX, DOCX, and XLSX using template-driven deterministic rendering with optional LLM normalization.
What It Does
Text2OfficeProcessor takes your content in plain text formats and converts it into professionally formatted office documents using your own templates. The tool preserves all template branding, backgrounds, images, and layouts exactly — it only injects your content at the run level.
| Input | Output |
|---|---|
.md |
.pptx — Template-driven slide decks |
.txt |
.docx — Branded word documents |
.html |
.xlsx — Structured spreadsheets |
Core Design Principles
- Deterministic rendering — no LLM hallucination in the final output. All document construction is 100% programmatic.
- Template preservation — backgrounds, images, fonts, and layouts are never modified.
- LLM as assistant, not author — LLM can normalize and tag content, never write documents.
- Zero magic — every replacement is traceable; every slide is auditable.
Quick Start
Install
pip install text2officeprocessor
The text2officeprocessor command is immediately available. Bundled generic templates are included — no template file needed to get started.
From source (for development or contribution):
git clone https://github.com/sage-khan/text2officeprocessor
cd text2officeprocessor
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt && pip install -e .
Zero-config — try it immediately
No template file needed. The bundled generic template is used automatically:
text2officeprocessor convert --slides-md slides.md --output outputs/presentation.pptx
See what templates are bundled:
text2officeprocessor templates
Generate a PPTX with your own template
text2officeprocessor convert \
--slides-md slides.md \
--template template.pptx \
--output outputs/presentation.pptx \
--type pptx
Generate from raw markdown (full pipeline)
text2officeprocessor convert \
--input content.md \
--template template.pptx \
--output output.pptx \
--type pptx
Generate DOCX
text2officeprocessor convert \
--input content.md \
--template template.docx \
--output output.docx \
--type docx
Generate XLSX
text2officeprocessor convert \
--input report.md \
--output report.xlsx \
--type xlsx
Use a custom rules config
text2officeprocessor convert \
--slides-md slides.md \
--template template.pptx \
--output output.pptx \
--config my-rules.yaml
Batch convert an entire directory
text2officeprocessor batch \
--input-dir ./content/ \
--output-dir ./outputs/ \
--type xlsx
Convert only .md files to PPTX using your own template:
text2officeprocessor batch \
--input-dir ./slides/ \
--output-dir ./outputs/ \
--type pptx \
--template template.pptx \
--pattern "*.md"
Embed a draw.io diagram into a slide
In your slides.md:
## SLIDE 3 — template_index: 2 (Single Point)
- placeholder: "SINGLE POINT SLIDE" → "System Architecture"
- diagram: "diagrams/architecture.drawio"
Or export a diagram directly:
text2officeprocessor drawio-export diagrams/architecture.drawio --output outputs/architecture.png
text2officeprocessor drawio-export diagrams/multi-page.drawio --all-pages
Watch mode — auto-regenerate on save
text2officeprocessor watch \
--input notes.md \
--output presentation.pptx \
--type pptx
Every time you save notes.md, the output is regenerated automatically. A 1-second debounce prevents multiple rapid saves from triggering redundant builds. Press Ctrl+C to stop.
text2officeprocessor watch \
--input slides.md \
--output presentation.xlsx \
--type xlsx \
--debounce 0.5 \
--no-validate
LLM semantic validation
After rendering, ask an LLM to check the output for truncated text, unreplaced placeholders, content mismatches, and empty sections:
text2officeprocessor convert \
--slides-md slides.md \
--output output.pptx \
--llm ollama --llm-model mistral \
--llm-validate
The check is non-blocking — if the LLM is unavailable the document is still saved.
Web UI
Install the web extras and launch the browser interface:
pip install text2officeprocessor[web]
text2officeprocessor serve
Open http://127.0.0.1:8000 — drag-and-drop a .md, .txt, or .html file, choose an output format, and download the result.
By default, the UI uses Auto (default local) LLM mode, which resolves to the provider in config/llm_config.yaml (default: Ollama). You can switch to Rule-based only to disable LLM.
text2officeprocessor serve --host 0.0.0.0 --port 8080
Custom validation config
Override the built-in placeholder list, artifact tokens, LLM prompt, and more by pointing to your own YAML:
text2officeprocessor convert --input doc.md --output out.xlsx --type xlsx --config my-rules.yaml
See config/default_rules.yaml for all available keys (validation.known_placeholders, llm_validation.prompt, etc.).
Analyze a template before authoring
text2officeprocessor analyze path/to/template.pptx
Slides Markdown Format
The primary PPTX workflow uses a structured markdown format:
## SLIDE 1 — template_index: 0 (Section Header)
- placeholder: "Section Name Here" → "Getting Started"
- placeholder: "SECTION Number" → "SECTION 1"
---
## SLIDE 2 — template_index: 3 (Multi Point)
- placeholder: "Multi Point Slide" → "Key Points"
- bullets:
- "First key point goes here"
- "Second key point goes here"
- "Third key point goes here"
---
## SLIDE 3 — template_index: 7 (Key Highlights — 4 columns)
- placeholder: "Key Highlights" → "What You Will Learn"
- card_1_title: "Topic One"
- card_1_body: "Short description of topic one"
- card_2_title: "Topic Two"
- card_2_body: "Short description of topic two"
See docs/guide.md for the full format specification.
Architecture
Input (.md / .txt / .html)
│
▼
InputPreprocessor → ParsedDocument
│
▼
LLMNormalizer (optional, fallback: rules) → [(SlideIntent, SlideContent)]
│
▼
ContentPlanner → SlidePlan / DocPlan / SpreadsheetPlan
│
├── PPTXEngine (SlidePart clone + inject)
├── DOCXEngine (template body injection)
└── XLSXEngine (openpyxl structured mapping)
│
▼
ProgrammaticValidator
│
▼
Output (.pptx / .docx / .xlsx)
Full architecture diagram: docs/architecture.drawio
LLM Providers
LLM is entirely optional — the tool works fully offline with rule-based normalization.
| Provider | Flag | Required Env Var |
|---|---|---|
| Ollama (local) | --llm ollama |
None |
| vLLM (OpenAI-compatible local/server) | --llm vllm |
Optional VLLM_API_KEY |
| OpenAI | --llm openai |
OPENAI_API_KEY |
| Claude | --llm claude |
ANTHROPIC_API_KEY |
| OpenRouter | --llm openrouter |
OPENROUTER_API_KEY |
| Groq | --llm groq |
GROQ_API_KEY |
text2officeprocessor convert --input content.md --template t.pptx \
--output out.pptx --type pptx --llm ollama --llm-model mistral
Local 7B model setup (recommended default)
Ollama (preferred):
ollama pull mistral:7b-instruct
ollama serve
vLLM alternative:
python -m vllm.entrypoints.openai.api_server \
--model mistralai/Mistral-7B-Instruct-v0.2 \
--port 8000
Docker profile option:
docker compose --profile vllm up -d vllm
One-command backend switch:
scripts/switch-llm-backend.sh ollama
scripts/switch-llm-backend.sh vllm --start
Set provider defaults in config/llm_config.yaml and optional cloud keys in .env (see .env.example).
Tutorial: Converting Your First File
This section walks through the complete workflow for each output format — from zero to a finished document.
Step 1 — Analyse your template
Before writing any content, run analyze on your template to discover the exact placeholder text strings in every shape. These are the strings you will reference in your slides markdown:
text2officeprocessor analyze my-template.pptx
Sample output:
Template: my-template.pptx
Total slides: 13
=== SLIDE 0 (layout: Section Header) ===
Shape 1 "Title" para[0] run[0]: 'Section Name Here'
Shape 2 "Subtitle" para[0] run[0]: 'SECTION Number'
=== SLIDE 3 (layout: Multi Point) ===
Shape 1 "Title" para[0] run[0]: 'Multi Point Slide'
The string in quotes ('Section Name Here', 'Multi Point Slide', etc.) is what you put as the "old" value in a placeholder: line.
Step 2 — Create your slides markdown
Create a file called slides.md. Each slide block starts with a header that names the template slide to clone and what content to inject:
## SLIDE 1 — template_index: 0 (Section Header)
- placeholder: "Section Name Here" → "Project Overview"
- placeholder: "SECTION Number" → "SECTION 1"
---
## SLIDE 2 — template_index: 3 (Multi Point)
- placeholder: "Multi Point Slide" → "Goals for This Quarter"
- bullets:
- "Reduce onboarding time by 30%"
- "Launch the self-service portal"
- "Complete security audit"
---
## SLIDE 3 — template_index: 7 (Key Highlights — 4 columns)
- placeholder: "Key Highlights" → "Four Pillars"
- placeholder: "Enter your subhead line here" → "Our strategic focus areas"
- card_1_title: "Speed"
- card_1_body: "Faster delivery cycles"
- card_2_title: "Quality"
- card_2_body: "Zero-defect release policy"
- card_3_title: "Scale"
- card_3_body: "Infrastructure for 10x growth"
- card_4_title: "People"
- card_4_body: "Invest in the team first"
---
## SLIDE 4 — template_index: 11 (Excellence Grid)
- placeholder: "EXCELLENCE IN THE" → "Why We Win"
- item_01_title: "Customer Focus"
- item_01_body: "Every decision starts with the customer."
- item_02_title: "Execution"
- item_02_body: "We ship and iterate fast."
- item_03_title: "Ownership"
- item_03_body: "Everyone is accountable."
Key rules:
template_indexis the zero-based slide number in your template (fromanalyzeoutput)placeholder: "old" → "new"replaces exact text;oldmust match the template character for character- Separate slides with
--- - See docs/guide.md for all supported slide types and item keys
Step 3 — Generate the PPTX
text2officeprocessor convert \
--slides-md slides.md \
--template my-template.pptx \
--output outputs/presentation.pptx \
--type pptx
The tool prints a summary and runs the validator automatically:
Slides to render: 4
Output written: outputs/presentation.pptx
Validation: PASSED (0 errors, 0 warnings)
Tutorial: Converting Markdown to DOCX
Point to any .md or .txt file and a .docx template. The full pipeline parses the markdown structure and injects it into the template body:
text2officeprocessor convert \
--input report.md \
--template my-template.docx \
--output outputs/report.docx \
--type docx
The DOCX engine preserves all template headers, footers, logos, and styles. Only the body content is replaced.
Tutorial: Exporting Markdown Tables to XLSX
Any markdown file containing tables is automatically mapped to sheets. No template needed:
Input data.md:
# Q1 Sales Report
## Regional Breakdown
| Region | Revenue | Growth |
|--------|---------|--------|
| North | 142,000 | +12% |
| South | 98,500 | +4% |
| East | 203,000 | +21% |
Command:
text2officeprocessor convert \
--input data.md \
--output outputs/q1-report.xlsx \
--type xlsx
Each ## section becomes a sheet. Tables become rows. Bullet lists become single-column rows.
Tutorial: Using a Custom Config for a Different Template
If you have a template with different placeholder text than the defaults, create a my-rules.yaml that overrides just the placeholder_map:
placeholder_map:
section_header:
title: "CLICK TO EDIT TITLE"
number: "00"
bullets:
title: "SLIDE TITLE"
key_highlights:
title: "HIGHLIGHTS"
subtitle: "subtitle text"
Then pass it with --config:
text2officeprocessor convert \
--slides-md slides.md \
--template corporate-template.pptx \
--output output.pptx \
--config my-rules.yaml
The tool reads the overrides at runtime. No Python changes needed.
Tutorial: Running via Docker (no Python required)
Mount your working directory to /data and pass all paths relative to that mount:
docker run --rm \
-v $(pwd):/data \
text2officeprocessor convert \
--slides-md /data/slides.md \
--template /data/my-template.pptx \
--output /data/output.pptx \
--type pptx
For LLM-assisted conversion using a local Ollama model:
docker compose run --rm text2officeprocessor convert \
--input /data/content.md \
--template /data/template.pptx \
--output /data/output.pptx \
--type pptx --llm ollama --llm-model mistral
Running Tests
python -m pytest tests/ -v
Expected: 123 tests pass.
Documentation
| Document | Purpose |
|---|---|
| docs/guide.md | Full usage, architecture, folder structure, extending the tool |
| docs/architecture.drawio | System architecture diagram (open with draw.io) |
| docs/development/changelog.md | All changes with timestamps |
| docs/development/diagnostics.md | Bug fixes and troubleshooting |
Project Structure
text2officeprocessor/
├── src/core/
│ ├── models.py # Domain dataclasses
│ ├── exceptions.py # Custom exceptions
│ ├── parser/preprocessor.py # md/txt/html → ParsedDocument
│ ├── planner/ # ParsedDocument → SlidePlan/DocPlan/SpreadsheetPlan
│ ├── engines/pptx/ # PPTX engine (SlidePart clone)
│ ├── engines/docx/ # DOCX engine (body injection)
│ ├── engines/xlsx/ # XLSX engine (openpyxl)
│ ├── llm/ # LLM abstraction layer
│ └── validation/ # Post-render checks
├── src/cli/main.py # Typer CLI entry point
├── config/ # YAML configuration
├── templates/ # Sample templates
├── tests/ # 38 unit + integration tests
└── docs/ # Architecture diagram + guide
Contributing
Contributions are welcome. Please read CONTRIBUTING.md before submitting a pull request.
Key contribution areas:
- New LLM providers (implement
LLMProviderbase class) - New output formats (new engine + plan type)
- Draw.io → PNG integration for flowchart slides
- Web UI (Phase 2 roadmap item)
- Additional template types and placeholder maps
License
MIT License — see LICENSE.
Author
Muhammad Danyal (Sage) Khan
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 text2officeprocessor-0.3.1.tar.gz.
File metadata
- Download URL: text2officeprocessor-0.3.1.tar.gz
- Upload date:
- Size: 220.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f494de1bcc0916bfb5bb887a851d08ef2cf7b5106461cde67dfa49af4ae111a
|
|
| MD5 |
d0cb7b4dae6d385e780da35bcfcb4a35
|
|
| BLAKE2b-256 |
3d006e52eba87bc9d793624c11cc80fc1bf2cafd203bdf8ccc60ef9a2cf825ee
|
Provenance
The following attestation bundles were made for text2officeprocessor-0.3.1.tar.gz:
Publisher:
python-publish.yml on sage-khan/text2officeprocessor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
text2officeprocessor-0.3.1.tar.gz -
Subject digest:
1f494de1bcc0916bfb5bb887a851d08ef2cf7b5106461cde67dfa49af4ae111a - Sigstore transparency entry: 1271152194
- Sigstore integration time:
-
Permalink:
sage-khan/text2officeprocessor@2471c4b2bf88142b368653716e241d132399242f -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/sage-khan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@2471c4b2bf88142b368653716e241d132399242f -
Trigger Event:
release
-
Statement type:
File details
Details for the file text2officeprocessor-0.3.1-py3-none-any.whl.
File metadata
- Download URL: text2officeprocessor-0.3.1-py3-none-any.whl
- Upload date:
- Size: 135.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63185bc83193a298fb8ecd87dd2e1ff977398f42328544f9197f348c3d1e093e
|
|
| MD5 |
94ae75f1b57b50166b1c2193d77b3ef8
|
|
| BLAKE2b-256 |
b0626112cfba0eb13d449deabb514f3d80c5af075e6fe0e422dd60908cd12c4d
|
Provenance
The following attestation bundles were made for text2officeprocessor-0.3.1-py3-none-any.whl:
Publisher:
python-publish.yml on sage-khan/text2officeprocessor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
text2officeprocessor-0.3.1-py3-none-any.whl -
Subject digest:
63185bc83193a298fb8ecd87dd2e1ff977398f42328544f9197f348c3d1e093e - Sigstore transparency entry: 1271152209
- Sigstore integration time:
-
Permalink:
sage-khan/text2officeprocessor@2471c4b2bf88142b368653716e241d132399242f -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/sage-khan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@2471c4b2bf88142b368653716e241d132399242f -
Trigger Event:
release
-
Statement type: