Skip to main content

A Windows-only xltpl fork that preserves VBA, images, and complex Excel formatting using COM

Project description

mog-xltpl

A Windows-only CLI tool and Python module to generate .xlsx/.xlsm files from templates while preserving VBA, images, and complex Excel formatting using COM. 中文 | 日本語

Primary Use: Command-line tool for templated Excel document generation via Taskfile integration.

Note: This tool is designed to work with Taskfile. It uses YAML vars: sections for variable definitions, allowing you to specify templates and output files from Taskfile. Taskfile-style {{ .VAR }} is not a full Go template implementation; we only normalize it to {{ VAR }} before rendering.

How it works

When xltpl reads a .xlsx/.xlsm file, it creates a tree for each worksheet.
Each tree is translated to a Jinja2 template with custom tags.
When the template is rendered, Jinja2 extensions of custom tags call corresponding tree nodes to write the output file.

Key Feature: Uses Excel COM API (via pywin32) to ensure complete preservation of images, drawings, and Excel-specific formatting that other tools cannot maintain.

How to install

pip install mog-xltpl

or with uv tool (recommended):

uv tool install mog-xltpl

Requirements:

  • Windows OS
  • Python 3.8+
  • Microsoft Excel (COM API access required)
  • pywin32 (automatically installed, required for preserving images and formatting)

Note: This tool requires Windows and Microsoft Excel because it uses Excel COM API to ensure complete preservation of images, drawings, and Excel-specific formatting. Without Excel or on non-Windows systems, the tool will exit with an error message.

Develop & test with uv

uv venv
uv pip install -e .[test]
uv run pytest

Quick Start: CLI Usage (Recommended)

Simple Usage

Specify template file, output file, and variables file:

mog-xltpl template.xlsx output.xlsx vars.yaml

# To emit an additional highlighted copy (auto-named as output_highlight.xlsx)
mog-xltpl template.xlsx output.xlsx vars.yaml --highlight-output

# If you want to set color explicitly
mog-xltpl template.xlsx output.xlsx vars.yaml \
  --highlight-output \
  --highlight-color FFFF9999

Integration with Taskfile (Recommended Workflow)

Taskfile.yml:

version: '3'

vars:
  DOC_TYPE: invoice
  DATE: "2025-12-30"
  NAME: "John Doe"

tasks:
  render:
    cmds:
      - mog-xltpl templates/{{.DOC_TYPE}}.xlsx output/result.xlsx vars.yaml

vars.yaml:

vars:
  doc_type: "invoice"
  date: "2025-12-30"
  name: "John Doe"
  items:
    - name: "Product A"
      price: 1000
    - name: "Product B"
      price: 2000

Run:

task render

Path Expansion Rules

  • Template and output files are specified from the command line.
  • YAML file contains only the vars section.
  • Relative paths are resolved from the execution directory.
  • Use --highlight-output to auto-emit a highlighted copy named <output>_highlight (color via --highlight-color, e.g., FFFF9999).

Vars Resolution

  • vars accepts either a mapping or a list of single-key mappings (Taskfile style: - KEY: value).
  • Values are rendered against the same vars map for a few passes, so self-references like FOOBAR: "foo_{{ VAR }}" are expanded before the workbook is rendered.

Python API (Advanced Usage)

  • To use xltpl, you need to be familiar with the syntax of jinja2 template.
  • Get a pre-written xls/x file as the template.
  • Insert variables in the cells, such as :
{{name}}
  • Insert control statements in the notes(comments) of cells, use beforerow, beforecell or aftercell to seperate them :
beforerow{% for item in items %}
beforerow{% endfor %}
  • Insert control statements in the cells (v0.9) :
{%- for row in rows %}
{% set outer_loop = loop %}{% for row in rows %}
Cell
{{outer_loop.index}}{{loop.index}}
{%+ endfor%}{%+ endfor%}

Image insertion

To insert images, use the img filter:

{{ image_path | img(120, 140) }}
  • First argument: Path to image file
  • Second argument (optional): Width in pixels
  • Third argument (optional): Height in pixels

You can also use keyword arguments:

{{ image_path | img(width=120, height=140) }}

Other handy filters

  • sha256: {{ file | sha256 }}
  • mtime: {{ file | mtime('%Y-%m-%d') }}
  • to_fullwidth: convert half-width digits and - to full-width for Excel-friendly formatting
  • Run the code
from xltpl.writerx import BookWriter
writer = BookWriter('tpl.xlsx')
person_info = {'name': u'Hello Wizard'}
items = ['1', '1', '1', '1', '1', '1', '1', '1', ]
person_info['items'] = items
payloads = [person_info]
writer.render_book(payloads)
writer.save('result.xlsx')

Supported

  • xls (xlrd/xlwt) / xlsx and xlsm (openpyxl)
  • MergedCell
  • Non-string value for a cell (use {% xv variable %} to specify a variable)
  • For xlsx family
    Image (use {% img variable %})
    DataValidation
    AutoFilter

Architecture and Image Preservation

Why pywin32/COM API?

This tool uses Excel COM API (via pywin32) for saving files to ensure complete preservation of:

  • Images and drawings embedded in templates
  • All Excel namespaces and XML attributes
  • Complex formatting and workbook properties
  • Macro-enabled files (.xlsm)

Previous approach using openpyxl had limitations:

  • openpyxl removes images and drawings when saving
  • openpyxl strips Excel-specific XML namespaces
  • Result files often couldn't be opened by Excel

Current approach (pywin32 + COM API):

  1. Load template using openpyxl (read-only, for Jinja2 rendering)
  2. Open template copy using Excel COM API
  3. Update only cell values from rendered data
  4. Save via COM API → All images, drawings, and formatting preserved

Testing Reproducibility

To verify that images and formatting are preserved:

# Create a test template with images
# Use static_image.xlsm as example

# Run rendering
xltpl static_image.xlsm static_image_out.xlsm static_image.yaml

# Verify file integrity
python -c "
import zipfile
z = zipfile.ZipFile('static_image_out.xlsm')
images = [n for n in z.namelist() if 'image' in n.lower() or 'drawing' in n.lower()]
print(f'Images/drawings preserved: {len(images)}')
for img in images:
    print(f'  {img}')
"

# Compare with template
python -c "
import zipfile
t = zipfile.ZipFile('static_image.xlsm')
o = zipfile.ZipFile('static_image_out.xlsm')
t_imgs = set([n for n in t.namelist() if 'drawing' in n or 'media' in n])
o_imgs = set([n for n in o.namelist() if 'drawing' in n or 'media' in n])
print(f'Template: {len(t_imgs)} image-related files')
print(f'Output: {len(o_imgs)} image-related files')
print(f'Match: {t_imgs == o_imgs}')
"

# Verify namespaces preserved
python -c "
import zipfile
o = zipfile.ZipFile('static_image_out.xlsm')
sheet_xml = o.read('xl/worksheets/sheet1.xml').decode('utf-8')
preserved = all([
    'xmlns:mc' in sheet_xml,
    'mc:Ignorable' in sheet_xml,
    'xr:uid' in sheet_xml
])
print(f'Excel namespaces preserved: {preserved}')
"

Parallel Execution Safety

The tool uses a threading lock to prevent concurrent Excel COM operations:

  • Multiple xltpl processes can run in parallel (e.g., via Taskfile)
  • Each process acquires a lock before using Excel COM
  • Prevents COM conflicts and ensures stability

Example with Taskfile parallel execution:

tasks:
  process-all:
    deps:
      - task: process-file-1  # Runs in parallel
      - task: process-file-2  # Runs in parallel
    cmds:
      - echo "All files processed"

Related

Notes

xlrd

xlrd does not extract print settings.
This repo does.

xlwt

xlwt always sets the default font to 'Arial'.
Excel measures column width units based on the default font.
This repo does not.

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

mog_xltpl-1.0.0.tar.gz (784.0 kB view details)

Uploaded Source

Built Distribution

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

mog_xltpl-1.0.0-py3-none-any.whl (44.5 kB view details)

Uploaded Python 3

File details

Details for the file mog_xltpl-1.0.0.tar.gz.

File metadata

  • Download URL: mog_xltpl-1.0.0.tar.gz
  • Upload date:
  • Size: 784.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for mog_xltpl-1.0.0.tar.gz
Algorithm Hash digest
SHA256 4ed6eb1778dc8699deb829f6ee58e148e4b9231faf0cc0870e5f89fc3ba4f798
MD5 31158e78e985c2b158c816e22802021c
BLAKE2b-256 7b2a26323a56e72b2904bec744110224e492c3e349cac50d575ad49cb9fcca8f

See more details on using hashes here.

File details

Details for the file mog_xltpl-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: mog_xltpl-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 44.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for mog_xltpl-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c33576fa4f673690198ed15b95f0f4047e3ea6d7a38a0497dfd0f2361177f35a
MD5 d6f1ffa4c52576e06a956b18b23781f5
BLAKE2b-256 4f88259392bcc1f458b43b57965ca7feeeaf46183f64b990bd6ac8afe89fa06e

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