pptxfill 0.2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ /console.py
13
+ .ipynb_checkpoints
14
+ /example/output.pptx
15
+
16
+ /docs/_build
@@ -0,0 +1,38 @@
1
+ variables:
2
+ # GitLab CI creates a separate mountpoint for the build directory,
3
+ # so we need to copy instead of using hard links.
4
+ UV_VERSION: "0.9.22"
5
+ PYTHON_VERSION: "3.12"
6
+ BASE_LAYER: bookworm-slim
7
+ UV_LINK_MODE: copy
8
+ UV_CACHE_DIR: .uv-cache
9
+
10
+ publish:
11
+ image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
12
+ rules:
13
+ - if: $CI_COMMIT_TAG =~ /^v/
14
+ cache:
15
+ - key:
16
+ files:
17
+ - uv.lock
18
+ paths:
19
+ - $UV_CACHE_DIR
20
+ script:
21
+ - uv build
22
+ - uv publish --token "$UV_PUBLISH_TOKEN"
23
+ - uv cache prune --ci
24
+
25
+ docs:
26
+ image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
27
+ rules:
28
+ - if: $CI_COMMIT_TAG =~ /^v/
29
+ cache:
30
+ - key:
31
+ files:
32
+ - uv.lock
33
+ paths:
34
+ - $UV_CACHE_DIR
35
+ script:
36
+ - uv run --group dev sphinx-build docs public
37
+ - uv cache prune --ci
38
+ pages: true
@@ -0,0 +1,12 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/uv-pre-commit
3
+ rev: 0.5.28
4
+ hooks:
5
+ - id: uv-lock
6
+ # Apply version tag whenever version changes in pyproject.toml
7
+ - repo: https://gitlab.com/snorkysnark/pyproject-auto-tag
8
+ rev: v0.3.0
9
+ hooks:
10
+ - id: pyproject-auto-tag
11
+
12
+ default_install_hook_types: [pre-commit, post-commit]
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.5
2
+ Name: pptxfill
3
+ Version: 0.2.5
4
+ Summary: Add your description here
5
+ Project-URL: Repository, https://gitlab.com/csml-tools/pptxfill/
6
+ Project-URL: Documentation, https://csml-tools.gitlab.io/pptxfill/
7
+ Requires-Python: >=3.13
8
+ Requires-Dist: pandas>=2.3.3
9
+ Requires-Dist: python-pptx>=1.0.2
10
+ Requires-Dist: types-lxml>=2026.1.1
11
+ Description-Content-Type: text/markdown
12
+
13
+ # pptxfill
14
+
15
+ **pptxfill** is a templating library for PowerPoint presentations.
16
+
17
+ Unlike other similar template engines, this one does not rely on Slide Layouts,
18
+ instead using a more flexible system similar to ones used for Word documents,
19
+ where you can substitute variables in text blocks using string formatting,
20
+ replace arbitrary images and fill tables with data.
21
+
22
+ [Documentation](https://csml-tools.gitlab.io/pptxfill/)
23
+
24
+ ## Installation:
25
+
26
+ ```
27
+ uv add pptxfill
28
+ ```
29
+
30
+ or
31
+
32
+ ```
33
+ pip install pptxfill
34
+ ```
@@ -0,0 +1,22 @@
1
+ # pptxfill
2
+
3
+ **pptxfill** is a templating library for PowerPoint presentations.
4
+
5
+ Unlike other similar template engines, this one does not rely on Slide Layouts,
6
+ instead using a more flexible system similar to ones used for Word documents,
7
+ where you can substitute variables in text blocks using string formatting,
8
+ replace arbitrary images and fill tables with data.
9
+
10
+ [Documentation](https://csml-tools.gitlab.io/pptxfill/)
11
+
12
+ ## Installation:
13
+
14
+ ```
15
+ uv add pptxfill
16
+ ```
17
+
18
+ or
19
+
20
+ ```
21
+ pip install pptxfill
22
+ ```
@@ -0,0 +1,20 @@
1
+ # Minimal makefile for Sphinx documentation
2
+ #
3
+
4
+ # You can set these variables from the command line, and also
5
+ # from the environment for the first two.
6
+ SPHINXOPTS ?=
7
+ SPHINXBUILD ?= sphinx-build
8
+ SOURCEDIR = .
9
+ BUILDDIR = _build
10
+
11
+ # Put it first so that "make" without argument is like "make help".
12
+ help:
13
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14
+
15
+ .PHONY: help Makefile
16
+
17
+ # Catch-all target: route all unknown targets to Sphinx using the new
18
+ # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19
+ %: Makefile
20
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@@ -0,0 +1,44 @@
1
+ # Configuration file for the Sphinx documentation builder.
2
+ #
3
+ # For the full list of built-in configuration values, see the documentation:
4
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html
5
+
6
+ # -- Project information -----------------------------------------------------
7
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8
+
9
+ import sys
10
+
11
+ sys.path.append(".")
12
+
13
+ project = "pptxfill"
14
+ copyright = "2026, snorkysnark"
15
+ author = "snorkysnark"
16
+ release = "0.2.5"
17
+
18
+ # -- General configuration ---------------------------------------------------
19
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
20
+
21
+ extensions = [
22
+ "sphinx.ext.autodoc",
23
+ "sphinx.ext.napoleon",
24
+ "sphinx.ext.intersphinx",
25
+ ]
26
+
27
+ templates_path = ["_templates"]
28
+ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
29
+
30
+
31
+ # -- Options for HTML output -------------------------------------------------
32
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
33
+
34
+ html_theme = "alabaster"
35
+ html_static_path = ["_static"]
36
+
37
+ napoleon_google_docstring = True
38
+
39
+ intersphinx_mapping = {
40
+ "python": ("https://docs.python.org/3", None),
41
+ "pptx": ("https://python-pptx.readthedocs.io/en/latest/", None),
42
+ "pandas": ("https://pandas.pydata.org/docs/", None),
43
+ }
44
+ autodoc_typehints = "both"
@@ -0,0 +1,159 @@
1
+ Examples
2
+ ========
3
+
4
+ .. |br| raw:: html
5
+
6
+ <br />
7
+
8
+ Loading your template
9
+ ---------------------
10
+
11
+ Since this library doesn't use Slide Layouts, your template is just a regular presentation. |br|
12
+ The template used in these examples: `template.pptx <https://gitlab.com/csml-tools/pptxfill/-/blob/main/example/template.pptx?ref_type=heads>`_
13
+
14
+ Ref: :func:`pptxfill.load_template`
15
+
16
+ .. code-block:: python
17
+
18
+ from pptxfill import load_template
19
+
20
+ template = load_template("template.pptx")
21
+
22
+ Text formatting
23
+ ---------------
24
+
25
+ Apply string formatting to all text blocks on Slide 0. |br|
26
+ Python's `string formatting rules <https://www.w3schools.com/python/python_string_formatting.asp>`_ apply, including format specifiers for numbers.
27
+
28
+ Ref: :func:`pptxfill.format_text_on_slide`
29
+
30
+ .. code-block:: python
31
+
32
+ from pptxfill import format_text_on_slide
33
+
34
+ format_text_on_slide(
35
+ template.slides[0],
36
+ name="Example University",
37
+ publications=2932,
38
+ fwci=7.3658,
39
+ )
40
+
41
+ .. image:: img/example_text_1.png
42
+
43
+ .. image:: img/example_text_2.png
44
+
45
+ Images
46
+ ------
47
+
48
+ First, open the `Selection Pane <https://support.microsoft.com/en-us/powerpoint/use-the-selection-pane-to-manage-objects-in-documents>`_
49
+ in PowerPoint (``Alt + F10``) and give your placeholder a unique name.
50
+
51
+ .. image:: img/selection-pane.png
52
+
53
+ Then it can be replaced by the target image.
54
+
55
+ Set ``full_width=True`` or ``full_height=True`` to stretch the image to the placeholder's width and height
56
+
57
+ Ref: :func:`pptxfill.replace_image_by_name`
58
+
59
+ .. code-block:: python
60
+
61
+ from pptxfill import replace_image_by_name
62
+
63
+ replace_image_by_name(template.slides[0], "logo", "logo.png")
64
+
65
+ Tables
66
+ ------
67
+
68
+ Fill a PowerPoint table with the contents of a pandas DataFrame. |br|
69
+ Example data: `data.csv <https://gitlab.com/csml-tools/pptxfill/-/blob/main/example/data.csv?ref_type=heads>`_
70
+
71
+ Ref: :func:`pptxfill.fill_table_by_name`
72
+
73
+ .. code-block:: python
74
+
75
+ import pandas as pd
76
+ from pptxfill import fill_table_by_name
77
+
78
+ data = pd.read_csv("data.csv")
79
+ fill_table_by_name(template.slides[0], "table", data)
80
+
81
+ Since by default ``skip_header=True``, the first row would not be filled.
82
+
83
+ The folliwing row must contain string templates, where data would be inserted
84
+ using :meth:`str.format` with a single argument: ``string.format(value)``)
85
+
86
+ .. image:: img/example_table_1.png
87
+
88
+ .. image:: img/example_table_2.png
89
+
90
+ This function *does not* automatically expand the table,
91
+ so the data will be cut off where the table ends
92
+
93
+ Repeating slides
94
+ ----------------
95
+
96
+ Sometimes you may wish to duplicate the slide of the original presentation.
97
+
98
+ Since this would modify :attr:`template.slides <pptx.presentation.Presentation.slides>`,
99
+ you can make a copy of it to still be able to refere to slides by their original indices:
100
+
101
+ .. code-block:: python
102
+
103
+ template_slides = list(template.slides)
104
+
105
+ Let's make Slide #2 repeat 5 times:
106
+
107
+ Ref: :func:`pptxfill.multiply_slide`
108
+
109
+ .. code-block:: python
110
+
111
+ from pptxfill import multiply_slide
112
+
113
+ repeating_slides = multiply_slide(template.slides, template_slides[1], 5)
114
+ for i, slide in enumerate(repeating_slides):
115
+ format_text_on_slide(slide, slide_num=i + 1)
116
+
117
+ Split table into multiple slides
118
+ --------------------------------
119
+
120
+ If you wish to show the entire DataFrame's content, you can split it into multiple slides:
121
+
122
+ .. code-block:: python
123
+
124
+ from pptxfill import find_shape_by_name
125
+ from pptx.shapes.graphfrm import GraphicFrame
126
+
127
+ table = find_shape_by_name(template_slides[1], "table", GraphicFrame).table
128
+
129
+ # Since by default skip_header=True, the first row is not filled with data
130
+ table_size = len(table.rows) - 1
131
+ table_slides = multiply_slide(
132
+ template.slides,
133
+ template_slides[1],
134
+ math.ceil(len(data) / table_size)
135
+ )
136
+
137
+ for i, slide in enumerate(table_slides):
138
+ format_text_on_slide(slide, slide_num=i + 1)
139
+ fill_table_by_name(
140
+ slide,
141
+ "table",
142
+ data.iloc[i * table_size : (i + 1) * table_size],
143
+ rownum=True,
144
+ )
145
+
146
+ Here, we are using ``rownum=True`` to display the row index in the first column.
147
+
148
+ .. image:: img/example_multi_table_1.png
149
+
150
+ .. image:: img/example_multi_table_2.png
151
+
152
+ .. image:: img/example_multi_table_3.png
153
+
154
+ Exporting
155
+ ---------
156
+
157
+ .. code-block:: python
158
+
159
+ template.save("result.pptx")
@@ -0,0 +1,35 @@
1
+ .. pptxfill documentation master file, created by
2
+ sphinx-quickstart on Mon Aug 24 14:38:25 2026.
3
+ You can adapt this file completely to your liking, but it should at least
4
+ contain the root `toctree` directive.
5
+
6
+ pptxfill documentation
7
+ ========================
8
+
9
+ **pptxfill** is a templating library for PowerPoint presentations.
10
+
11
+ Unlike other similar template engines, this one does not rely on Slide Layouts,
12
+ instead using a more flexible system similar to ones used for Word documents,
13
+ where you can substitute variables in text blocks using string formatting,
14
+ replace arbitrary images and fill tables with data.
15
+
16
+ Installation
17
+ ------------
18
+
19
+ .. code-block::
20
+
21
+ uv add pptxfill --index https://gitlab.com/api/v4/groups/101754947/-/packages/pypi/simple
22
+
23
+ or
24
+
25
+ .. code-block::
26
+
27
+ pip install --index-url https://gitlab.com/api/v4/groups/101754947/-/packages/pypi/simple pptxfill
28
+
29
+
30
+ .. toctree::
31
+ :maxdepth: 2
32
+ :caption: Contents:
33
+
34
+ examples
35
+ reference
@@ -0,0 +1,35 @@
1
+ @ECHO OFF
2
+
3
+ pushd %~dp0
4
+
5
+ REM Command file for Sphinx documentation
6
+
7
+ if "%SPHINXBUILD%" == "" (
8
+ set SPHINXBUILD=sphinx-build
9
+ )
10
+ set SOURCEDIR=.
11
+ set BUILDDIR=_build
12
+
13
+ %SPHINXBUILD% >NUL 2>NUL
14
+ if errorlevel 9009 (
15
+ echo.
16
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17
+ echo.installed, then set the SPHINXBUILD environment variable to point
18
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
19
+ echo.may add the Sphinx directory to PATH.
20
+ echo.
21
+ echo.If you don't have Sphinx installed, grab it from
22
+ echo.https://www.sphinx-doc.org/
23
+ exit /b 1
24
+ )
25
+
26
+ if "%1" == "" goto help
27
+
28
+ %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29
+ goto end
30
+
31
+ :help
32
+ %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33
+
34
+ :end
35
+ popd
@@ -0,0 +1,46 @@
1
+ API Reference
2
+ =============
3
+
4
+ Template
5
+ --------
6
+
7
+ .. autofunction:: pptxfill.load_template
8
+
9
+ Shape Tree
10
+ ----------
11
+
12
+ .. autofunction:: pptxfill.walk_shapes
13
+
14
+ .. autofunction:: pptxfill.find_shape_by_name
15
+
16
+
17
+ Slide Manipulation
18
+ ------------------
19
+
20
+ .. autofunction:: pptxfill.copy_slide_content
21
+
22
+ .. autofunction:: pptxfill.insert_slide
23
+
24
+ .. autofunction:: pptxfill.delete_slide
25
+
26
+ .. autofunction:: pptxfill.multiply_slide
27
+
28
+ Text
29
+ ----
30
+
31
+ .. autofunction:: pptxfill.format_text_on_slide
32
+
33
+ Images
34
+ ------
35
+
36
+ .. autofunction:: pptxfill.replace_image
37
+
38
+ .. autofunction:: pptxfill.replace_image_by_name
39
+
40
+ Tables
41
+ ------
42
+
43
+ .. autofunction:: pptxfill.fill_table
44
+
45
+ .. autofunction:: pptxfill.fill_table_by_name
46
+
@@ -0,0 +1,51 @@
1
+ name,cost,percentage
2
+ logistics,1783,27.418243245046312
3
+ league,3737,3.3290774158836633
4
+ spectrograph,2635,50.07984744768956
5
+ noodle,3245,30.434240332249495
6
+ budget,2841,61.96213623361662
7
+ chili,2201,37.97385252086871
8
+ wonderful,4939,74.8964289076211
9
+ tempo,4828,45.28791637745536
10
+ knowledge,4512,68.6937978433078
11
+ legend,2315,90.10919179049583
12
+ needy,1588,73.43247827182032
13
+ divide,4765,63.63810285920232
14
+ reliability,3871,32.03240986723951
15
+ interlay,3475,33.249377311697096
16
+ bowling,2585,22.03289180796749
17
+ animated,4967,93.45380236016199
18
+ fur,2768,37.45503608654145
19
+ spread,1234,83.06500436711761
20
+ horst,1755,9.120487683357359
21
+ meeting,3808,52.405902077128474
22
+ spree,1309,66.72493850677978
23
+ cloudy,4214,97.41879061022084
24
+ frenzy,4007,63.620655039985586
25
+ fog,4919,21.67586948741935
26
+ macro,3208,23.05521140069565
27
+ hearing,3821,1.306688325996963
28
+ settler,2208,15.73287084523165
29
+ underpants,1044,39.204788529376664
30
+ starboard,2712,84.98825725278499
31
+ former,4858,26.053151850542566
32
+ monopoly,3870,48.57777263757497
33
+ rag,1255,23.219516416486996
34
+ wake,3207,18.642743704938557
35
+ elevation,1990,89.36114451631306
36
+ colonization,1320,84.41680453820814
37
+ nice,4346,10.194224035576793
38
+ king,3682,99.506334861964
39
+ pulley,1309,11.529774855044206
40
+ edge,4319,59.955557737761865
41
+ cinema,3713,88.72685001257503
42
+ motorboat,1525,96.67543307972387
43
+ steward,4690,67.82904861043019
44
+ helmet,3917,54.24036679578976
45
+ gambling,2594,2.016212327948097
46
+ rainstorm,3893,1.1354635256942913
47
+ algorithm,4883,70.74882506429151
48
+ restructuring,4050,60.716841589077
49
+ washtub,3875,2.8019898207405136
50
+ chivalrous,1580,75.18100874664756
51
+ flashy,3755,16.009085621452137
@@ -0,0 +1,46 @@
1
+ import math
2
+
3
+ import pandas as pd
4
+ from pptx.shapes.graphfrm import GraphicFrame
5
+
6
+ from pptxfill import (
7
+ fill_table_by_name,
8
+ find_shape_by_name,
9
+ format_text_on_slide,
10
+ load_template,
11
+ multiply_slide,
12
+ replace_image_by_name,
13
+ )
14
+
15
+ template = load_template("template.pptx")
16
+ template_slides = list(template.slides)
17
+
18
+ format_text_on_slide(
19
+ template_slides[0],
20
+ name="Example University",
21
+ publications=2932,
22
+ fwci=7.3658,
23
+ )
24
+
25
+ replace_image_by_name(template_slides[0], "logo", "logo.png")
26
+
27
+ data = pd.read_csv("data.csv")
28
+ fill_table_by_name(template_slides[0], "table", data)
29
+
30
+ table = find_shape_by_name(template_slides[1], "table", GraphicFrame).table
31
+ # Since by default skip_header=True, the first row is not filled with data
32
+ table_size = len(table.rows) - 1
33
+ table_slides = multiply_slide(
34
+ template.slides, template_slides[1], math.ceil(len(data) / table_size)
35
+ )
36
+
37
+ for i, slide in enumerate(table_slides):
38
+ format_text_on_slide(slide, slide_num=i + 1)
39
+ fill_table_by_name(
40
+ slide,
41
+ "table",
42
+ data.iloc[i * table_size : (i + 1) * table_size],
43
+ rownum=True,
44
+ )
45
+
46
+ template.save("result.pptx")
Binary file
Binary file
Binary file
@@ -0,0 +1,21 @@
1
+ from .loader import load_template
2
+ from .picture import replace_image, replace_image_by_name
3
+ from .slides import copy_slide_content, delete_slide, insert_slide, multiply_slide
4
+ from .table import fill_table, fill_table_by_name
5
+ from .text import format_text_on_slide
6
+ from .walker import find_shape_by_name, walk_shapes
7
+
8
+ __all__ = [
9
+ "load_template",
10
+ "replace_image",
11
+ "replace_image_by_name",
12
+ "copy_slide_content",
13
+ "delete_slide",
14
+ "insert_slide",
15
+ "multiply_slide",
16
+ "fill_table",
17
+ "fill_table_by_name",
18
+ "format_text_on_slide",
19
+ "walk_shapes",
20
+ "find_shape_by_name",
21
+ ]
@@ -0,0 +1,66 @@
1
+ from typing import Callable, Optional, cast
2
+
3
+ from lxml.etree import Element, QName
4
+ from lxml.etree import tostring as lxml_tostring
5
+ from pptx.oxml.action import CT_Hyperlink
6
+ from pptx.oxml.text import CT_TextCharacterProperties, CT_TextFont
7
+
8
+
9
+ def optional_eq[T](v1: Optional[T], v2: Optional[T], eq: Callable[[T, T], bool]):
10
+ if v1 is None or v2 is None:
11
+ return (v1 is None) == (v2 is None)
12
+
13
+ return eq(v1, v2)
14
+
15
+
16
+ def get_fill_properties(rpr: CT_TextCharacterProperties):
17
+ """rPr.eg_fillProperties doesn't cover all fill-related tags, so using a custom method"""
18
+ return next(
19
+ filter(
20
+ lambda el: not isinstance(el.tag, bytearray)
21
+ and QName(el.tag).localname
22
+ in {
23
+ "noFill",
24
+ "solidFill",
25
+ "gradFill",
26
+ "blipFill",
27
+ "pattFill",
28
+ "grpFill",
29
+ "effectLst",
30
+ "effectDag",
31
+ "highlight",
32
+ "uLnTx",
33
+ "uLn",
34
+ "uFillTx",
35
+ "uFill",
36
+ },
37
+ cast(Element, rpr),
38
+ ),
39
+ None,
40
+ )
41
+
42
+
43
+ def elements_eq(el1: Element, el2: Element):
44
+ return lxml_tostring(el1) == lxml_tostring(el2)
45
+
46
+
47
+ def text_font_eq(t1: CT_TextFont, t2: CT_TextFont):
48
+ return t1.typeface == t2.typeface
49
+
50
+
51
+ def hyperlink_eq(h1: CT_Hyperlink, h2: CT_Hyperlink):
52
+ return h1.rId == h2.rId and h1.action == h2.action
53
+
54
+
55
+ def text_character_properties_eq(
56
+ rpr1: CT_TextCharacterProperties, rpr2: CT_TextCharacterProperties
57
+ ):
58
+ return (
59
+ optional_eq(get_fill_properties(rpr1), get_fill_properties(rpr2), elements_eq)
60
+ and optional_eq(rpr1.latin, rpr2.latin, text_font_eq)
61
+ and optional_eq(rpr1.hlinkClick, rpr2.hlinkClick, hyperlink_eq)
62
+ and rpr1.sz == rpr2.sz
63
+ and rpr1.b == rpr2.b
64
+ and rpr1.i == rpr2.i
65
+ and rpr1.u == rpr2.u
66
+ )