Metadata-Version: 2.4
Name: docxmdtpl
Version: 0.1.0
Summary: Convert Markdown to .docx using a .docx template with docxtpl
Author: caiocfcs
License-Expression: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: docxtpl[subdoc]>=0.20.2
Requires-Dist: markdown-it-py>=4.0.0
Requires-Dist: python-docx>=1.2.0
Requires-Dist: python-frontmatter>=1.1.0
Dynamic: license-file

# docxmdtpl

Convert Markdown to `.docx` using a `.docx` template with `docxtpl`.

Requires Python 3.11+.

## Quick start

Install the dependencies:

```bash
uv sync
```

`main.py` is a minimal quickstart. Point `TEMPLATE_PATH`, `MARKDOWN_PATH`, `OUTPUT_PATH` and `IMAGES_PATH` at real files, then run:

```bash
uv run python main.py
```

If `TEMPLATE_PATH` does not exist, the quickstart creates a base template with `BuildTemplate`. If `MARKDOWN_PATH` does not exist, the run fails with `FileNotFoundError`.

## Using it as a library

Recommended layout:

```text
workspace/
├── meu_projeto/
│   ├── .git/
│   └── pyproject.toml
└── docxmdtpl/
    ├── .git/
    ├── pyproject.toml
    └── src/docxmdtpl/
```

From the consumer project, install `docxmdtpl` in editable mode pointing at the local folder:

```bash
cd workspace/meu_projeto
uv pip install -e ../docxmdtpl
```

`-e` keeps the install editable: changes made in `../docxmdtpl` take effect in `meu_projeto` without reinstalling the package.

If the consumer project is managed with `uv sync`, prefer a path dependency in its `pyproject.toml` so `uv sync` does not drop the install:

```toml
[project]
dependencies = [
    "docxmdtpl",
]

[tool.uv.sources]
docxmdtpl = { path = "../docxmdtpl", editable = true }
```

Verify the install:

```bash
uv run python -c "from docxmdtpl import MarkdownToDocxTpl, BuildTemplate; print('ok')"
```

Minimal usage in `meu_projeto`:

```python
from docxmdtpl import BuildTemplate, MarkdownToDocxTpl

template_path = BuildTemplate("template.docx").build()

MarkdownToDocxTpl(
    template_path=str(template_path),
    markdown_content="# Title\n\nSample text.",
    frontmatter=False,
).render("output.docx")
```

## API

Render Markdown to `.docx`:

```python
from docxmdtpl import MarkdownToDocxTpl

MarkdownToDocxTpl(
    template_path="template.docx",
    markdown_content="content.md",
    frontmatter=True,
    path_imagens="images",
).render("output/document.docx")
```

Parameters:

- `template_path`: path to the `.docx` template.
- `markdown_content`: path to a `.md` file **or** inline Markdown text. If the path exists as a file, the file is read; otherwise the string is used as Markdown. Note: a non-existent path is not an error and will be rendered as text.
- `frontmatter`: when `True` (default), the YAML frontmatter is extracted and exposed as `metadata` in the template. When `False`, the frontmatter is not processed and, if present in the file, is rendered as regular text.
- `path_imagens`: base directory used to locate images referenced in the Markdown.
- `source_dir`: alternative base directory for images. When omitted, the Markdown file's directory is used (when `markdown_content` is a file path).
- `render(output_path)`: writes the final `.docx`, creating the output directory if needed.

### Frontmatter and metadata

With `frontmatter=True`, the YAML block at the top of the file becomes the `metadata` context in the template:

```markdown
---
title: Report
author: Name
---
# Section
...
```

```text
{{ metadata.title }}
{{ metadata.author }}
```

The template generated by `BuildTemplate` already uses `{{ metadata.title | default('') }}` in the title paragraph.

## BuildTemplate

If you do not have a template yet, `BuildTemplate` generates a base `.docx` with the placeholders the library expects:

```python
from docxmdtpl import BuildTemplate

template_path = BuildTemplate("template.docx").build()
```

`BuildTemplate(output_path, max_depth=5)` — `max_depth` controls how many nested section levels the template supports (1 to 5).

The generated template:

- applies `Title` to `{{ metadata.title | default('') }}`
- applies `Heading N` to `{{p section_N.heading_subdoc }}`
- applies `Normal` to `{{p section_N.content_subdoc }}`
- includes a `Caption` prototype `Figura <SEQ> - {{ figure_caption }}` for figure captions
- includes a 1x1 table with `{{ quote_block }}` as the quote prototype
- includes a 1x1 table with `{{ code_block }}` as the code block prototype
- includes a 3x2 table with `{{ table_header_* }}`, `{{ table_odd_* }}` and `{{ table_even_* }}` as the Markdown table prototype
- enables field updates in Word, so `SEQ` fields are recalculated when the file is opened

Then use the generated file with `MarkdownToDocxTpl`.

## Template-first formatting

The `.docx` template is the source of truth for formatting.

- the formatting of the `{{p ...heading_subdoc }}` paragraph defines the visual base of headings rendered at that loop level
- the formatting of the `{{p ...content_subdoc }}` paragraph defines the visual base of content rendered at that loop level
- nested loops can have differently formatted placeholders; the library replicates the placeholder matching the section depth
- a placeholder must occupy its whole paragraph; extra text in the same paragraph prevents recognition
- prototypes (caption, quote, code, table) can be defined at different loop levels; each level uses its own prototype, falling back to the nearest shallower one

In practice, to change font, size, alignment or spacing, format the placeholder itself in Word.

## Figure captions

To control figure captions separately from paragraphs, define a prototype paragraph containing `{{ figure_caption }}` in the template.

- the paragraph can include fixed text, a `SEQ` field inserted manually in Word, and any font, color, alignment or style formatting
- the library extracts the paragraph before `render()`, removes it from the final template body and clones it for each rendered image
- the final caption format follows the prototype; the position of `{{ figure_caption }}` in the paragraph defines where the caption text is inserted
- without a `{{ figure_caption }}` prototype, the library falls back to an internal `Figura n - <caption>` paragraph with the `Caption` style

## Quotes and code blocks

To control `>` quotes and code blocks with borders, background, padding and width, define 1x1 prototype tables in the template with a single marker in the cell:

- `{{ quote_block }}`
- `{{ code_block }}`

Position in the template does not matter; the loop level the table is in defines its scope.

In practice:

- format the whole table in Word as you like
- format the paragraph inside the cell to set the visual base of the inner text
- the library extracts and removes these tables before `render()`
- each rendered quote or code block clones the matching prototype and injects the real content into the cell
- without prototypes, quotes use the `Quote` style and code blocks use a `Consolas` paragraph
- code blocks inside quotes do not use the code prototype

Example of the template tail area:

```text
Figura SEQ - {{ figure_caption }}

[1x1 table]
{{ quote_block }}

[1x1 table]
{{ code_block }}
```

## Markdown tables

To control the header and the visual alternation of body rows, define a 3x2 prototype table in the template with this layout:

```text
| {{ table_header_1 }} | {{ table_header_2 }} |
| {{ table_odd_1 }}    | {{ table_odd_2 }}    |
| {{ table_even_1 }}   | {{ table_even_2 }}   |
```

Rules:

- the first row is the header; its formatting is reapplied to every rendered header
- the second row is the odd body rows
- the third row is the even body rows
- the library extracts and removes this table before `render()`
- if the Markdown table has more than two columns, the last prototype cell's formatting is reused for the extra columns
- without a prototype, tables use the `Table Grid` style

In practice, this lets you configure in Word:

- table borders and overall style
- cell text alignment
- bold, shaded, centered headers
- odd/even row shading in the body

## Images

Accepted syntax:

- Obsidian: `![[figure.png|Front view]]`
- Standard Markdown: `![Front view](figure.png)`

Image path resolution, in this order:

1. `path_imagens` + reference
2. `source_dir` + reference (or the Markdown file's directory when `markdown_content` is a file path)
3. reference relative to the current directory

Absolute paths are used as-is.

Behavior:

- images are centered and resized automatically to fit the usable page area
- an image with a caption gets a caption paragraph after it; the format follows the `{{ figure_caption }}` prototype (fallback: `Figura n - <caption>`, with `n` sequential per document)
- an image without a caption gets no caption paragraph
- images also work inside Markdown table cells

## Supported syntax

Supported:

- headings level 1 to 5
- paragraphs with bold, italic, bold+italic and inline code
- external links (rendered as hyperlinks)
- ordered and unordered lists, nested
- `>` quotes
- fenced code blocks
- Markdown tables
- horizontal rules `---`
- standalone images, with or without caption

Not supported:

- content before the first heading (the document must start with a heading)
- level 6 headings
- images mixed with text in the same paragraph
- images as standalone list items
- headings inside quotes or lists
- nested links

## Errors

- `MarkdownSourceError`: invalid or unreadable Markdown source
- `FrontmatterError`: invalid YAML frontmatter
- `DocumentStructureError`: unexpected document structure (e.g. content before the first heading)
- `UnsupportedMarkdownError`: Markdown syntax outside the supported scope
- `ImageResolutionError`: referenced image not found
- `TemplateBindingError`: invalid template or render failure
- `OutputPersistenceError`: failure saving the final `.docx`
