pptxizza 0.1.0__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.
Files changed (38) hide show
  1. pptxizza-0.1.0/PKG-INFO +197 -0
  2. pptxizza-0.1.0/README.md +184 -0
  3. pptxizza-0.1.0/pyproject.toml +31 -0
  4. pptxizza-0.1.0/setup.cfg +4 -0
  5. pptxizza-0.1.0/src/pptxizza/__init__.py +12 -0
  6. pptxizza-0.1.0/src/pptxizza/charts.py +95 -0
  7. pptxizza-0.1.0/src/pptxizza/colors.py +55 -0
  8. pptxizza-0.1.0/src/pptxizza/media.py +121 -0
  9. pptxizza-0.1.0/src/pptxizza/opc.py +248 -0
  10. pptxizza-0.1.0/src/pptxizza/presentation.py +202 -0
  11. pptxizza-0.1.0/src/pptxizza/shapes/__init__.py +12 -0
  12. pptxizza-0.1.0/src/pptxizza/shapes/bar_chart.py +7 -0
  13. pptxizza-0.1.0/src/pptxizza/shapes/base_shape.py +208 -0
  14. pptxizza-0.1.0/src/pptxizza/shapes/chart.py +27 -0
  15. pptxizza-0.1.0/src/pptxizza/shapes/circle.py +57 -0
  16. pptxizza-0.1.0/src/pptxizza/shapes/factory.py +84 -0
  17. pptxizza-0.1.0/src/pptxizza/shapes/format.py +168 -0
  18. pptxizza-0.1.0/src/pptxizza/shapes/graphic_frame.py +27 -0
  19. pptxizza-0.1.0/src/pptxizza/shapes/line_chart.py +7 -0
  20. pptxizza-0.1.0/src/pptxizza/shapes/picture.py +52 -0
  21. pptxizza-0.1.0/src/pptxizza/shapes/pie_chart.py +7 -0
  22. pptxizza-0.1.0/src/pptxizza/shapes/rect.py +57 -0
  23. pptxizza-0.1.0/src/pptxizza/shapes/shape.py +99 -0
  24. pptxizza-0.1.0/src/pptxizza/shapes/table.py +256 -0
  25. pptxizza-0.1.0/src/pptxizza/slide.py +304 -0
  26. pptxizza-0.1.0/src/pptxizza/templates/default.pptx +0 -0
  27. pptxizza-0.1.0/src/pptxizza/text.py +108 -0
  28. pptxizza-0.1.0/src/pptxizza/util.py +47 -0
  29. pptxizza-0.1.0/src/pptxizza/xml_utils.py +96 -0
  30. pptxizza-0.1.0/src/pptxizza.egg-info/PKG-INFO +197 -0
  31. pptxizza-0.1.0/src/pptxizza.egg-info/SOURCES.txt +36 -0
  32. pptxizza-0.1.0/src/pptxizza.egg-info/dependency_links.txt +1 -0
  33. pptxizza-0.1.0/src/pptxizza.egg-info/requires.txt +4 -0
  34. pptxizza-0.1.0/src/pptxizza.egg-info/top_level.txt +1 -0
  35. pptxizza-0.1.0/tests/test_image_insertion.py +37 -0
  36. pptxizza-0.1.0/tests/test_shapes.py +48 -0
  37. pptxizza-0.1.0/tests/test_slides.py +39 -0
  38. pptxizza-0.1.0/tests/test_table.py +113 -0
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.4
2
+ Name: pptxizza
3
+ Version: 0.1.0
4
+ Summary: A streamlined, LLM-friendly pip package for creating and editing pptx files from templates.
5
+ Author: Kameron
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Operating System :: OS Independent
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: lxml>=4.9.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
13
+
14
+ # pptxizza
15
+
16
+ A streamlined, LLM-friendly pip package for creating and editing `.pptx` files dynamically. `pptxizza` uses direct XML manipulation under the hood (via `lxml`) to fill text, replace images, and update charts and tables within existing PowerPoint templates quickly and efficiently, featuring a highly-granular Object-Oriented API for direct shape manipulation!
17
+
18
+ ## Features
19
+
20
+ - **Object-Oriented Integrity**: Native interaction with shapes (`Shape`, `Rect`, `Circle`) and charts (`BarChart`, `PieChart`, etc.).
21
+ - **Programmatic Shape Insertion**: Generate and structure exact geometry auto-shapes or vector pictures dynamically from code.
22
+ - **Text Replacement**: Replace `{{mustache}}` template variables anywhere on a slide, or target specific named shapes.
23
+ - **Image Replacement**: Easily swap out placeholder images with `.png`, `.jpg`, or `.svg` files while preserving position and styling.
24
+ - **Chart & Table Data Binding**: Update the underlying data of native PowerPoint charts (`BarChart`, `PieChart`, `LineChart`) directly via their class objects.
25
+
26
+ ## Installation
27
+
28
+ As this package is under development, you can use it locally by ensuring the package is in your `sys.path` or installing it in editable mode:
29
+
30
+ ```bash
31
+ pip install -e .
32
+ ```
33
+
34
+ Dependencies:
35
+ - `lxml`
36
+
37
+ ## Quick Start (Templating)
38
+
39
+ The most robust way to use `pptxizza` is to create a template (`template.pptx`) with some named placeholders or `{{mustache_keys}}`, then parse it:
40
+
41
+ ```python
42
+ from pptxizza import Presentation
43
+
44
+ def main():
45
+ pres = Presentation("template.pptx")
46
+ slide = pres.slides[0]
47
+
48
+ # Fill the slide with dynamic content mapped to shape names
49
+ slide.fill({
50
+ "{{title}}": "Quarterly Business Review", # Global mustache text replacement
51
+ "SubtitleShape": "Q3 2026 Results", # Named shape text replacement
52
+ "LogoPlaceholder": "company_logo.svg" # Named picture shape replacement (injects SVG!)
53
+ })
54
+
55
+ pres.save("output.pptx")
56
+
57
+ if __name__ == "__main__":
58
+ main()
59
+ ```
60
+
61
+ ## Creating a Presentation from Scratch
62
+
63
+ If you just need a blank presentation without an existing template, you can initialize `Presentation` with no arguments.
64
+
65
+ ```python
66
+ from pptxizza import Presentation
67
+
68
+ def main():
69
+ # Calling Presentation() without an argument loads a default blank template
70
+ pres = Presentation()
71
+
72
+ # You can now add slides or insert shapes programmatically
73
+ # ...
74
+
75
+ pres.save("new_presentation.pptx")
76
+
77
+ if __name__ == "__main__":
78
+ main()
79
+ ```
80
+
81
+ ## The Object-Oriented API (`pptxizza.shapes`)
82
+
83
+ `pptxizza` maps OpenXML structures directly into typed Python objects, allowing for programmatic instantiation and highly specific template discovery.
84
+
85
+ ### 1. Generating & Inserting Shapes Programmatically
86
+
87
+ Instead of relying solely on existing templates, you can import granular geometric components from `pptxizza.shapes` to build slides from scratch!
88
+
89
+ ```python
90
+ from pptxizza.shapes import Shape, Rect, Circle, Picture, Table
91
+ from pptxizza import Inches
92
+
93
+ slide = pres.slides[0]
94
+
95
+ # Create a primitive Rect shape with text
96
+ my_rect = Rect(text="Action Item", x=Inches(1), y=Inches(1), cx=Inches(3), cy=Inches(1))
97
+
98
+ # Create an Oval/Circle
99
+ my_circle = Circle(text="1", x=Inches(5), y=Inches(1), cx=Inches(1), cy=Inches(1))
100
+
101
+ # Create a Table (3 rows, 3 columns)
102
+ my_table = Table(rows=3, cols=3, x=Inches(1), y=Inches(3), cx=Inches(6), cy=Inches(1.5))
103
+
104
+ # Create an SVG Vector graphic dynamically
105
+ my_ufo = Picture(image_path="ufo.svg", x=Inches(3), y=Inches(5), cx=Inches(2), cy=Inches(2))
106
+
107
+ # Insert the objects natively. Background relation mapping is handled automatically!
108
+ slide.insert_shape(my_rect)
109
+ slide.insert_shape(my_circle)
110
+ slide.insert_shape(my_table)
111
+ slide.insert_shape(my_ufo)
112
+ ```
113
+
114
+ _Note_: `x`, `y`, `cx`, and `cy` are tracked in English Metric Units (EMUs). All shapes also expose `x_inches`, `y_inches`, `width_inches`, and `height_inches` for easier visual math.
115
+
116
+ ### 2. Inspecting and Manipulating Dynamic Shapes
117
+
118
+ `pptxizza` intelligently probes into the ZIP packages under the hood to detect exactly what's sitting on a template slide. When examining `slide.shapes`, generic graphic frames are dynamically resolved into explicit subclasses like `BarChart`, `PieChart`, or `LineChart`.
119
+
120
+ ```python
121
+ for shape in slide.shapes:
122
+ print(f"Discovered: {type(shape).__name__} named '{shape.shape_name}'")
123
+
124
+ # You can access common underlying properties easily!
125
+ shape.x_inches += 1.5 # shift everything right by 1.5 inches
126
+ ```
127
+
128
+ ### 3. Updating Chart Data via Shape Objects
129
+
130
+ When `slide.shapes` returns a chart class (e.g. `BarChart`, `PieChart`), you can call `.replace_data()` directly onto the object to overwrite its internal points caches.
131
+
132
+ ```python
133
+ from pptxizza.shapes import BarChart
134
+
135
+ for shape in slide.shapes:
136
+ if isinstance(shape, BarChart) and shape.shape_name == "SalesChart":
137
+ categories = ["Jan", "Feb", "Mar"]
138
+ data = {
139
+ 0: [150, 200, 250], # Update series 0 by its index
140
+ "Europe": [120, 180, 210] # Update series by string name matching
141
+ }
142
+
143
+ # Inject seamlessly into the cache!
144
+ shape.replace_data(data, categories)
145
+ ```
146
+
147
+ ### 4. Manipulating Tables
148
+
149
+ `Table` objects allow for granular cell access and styling using presets.
150
+
151
+ ```python
152
+ from pptxizza.shapes import Table
153
+
154
+ for shape in slide.shapes:
155
+ if isinstance(shape, Table):
156
+ # Access cells by (row, col)
157
+ shape.cell(0, 0).text = "Header 1"
158
+
159
+ # Helper to set first row names
160
+ shape.set_column_names(["ID", "Name", "Status"])
161
+
162
+ # Add new rows or columns
163
+ new_row = shape.add_row()
164
+ new_row.cells[0].text = "001"
165
+
166
+ # Apply visual presets
167
+ shape.apply_preset("Medium Style 2 - Accent 1")
168
+ ```
169
+
170
+ ### 5. Styling and Rich Text
171
+
172
+ Most shapes support direct styling of fill and font properties. You can also use the `Text` and `Run` objects for rich text formatting.
173
+
174
+ ```python
175
+ from pptxizza import RGBColor, HexColor, ThemeColor, Pt, Text
176
+
177
+ # Solid Fill
178
+ shape.fill.solid(HexColor("#4287f5"))
179
+
180
+ # Font Styling
181
+ shape.font.size = Pt(24)
182
+ shape.font.bold = True
183
+ shape.font.color = RGBColor(255, 255, 255)
184
+
185
+ # Rich Text with Runs
186
+ t = Text()
187
+ t.add_run("Normal text, ")
188
+ t.add_run("BOLD AND RED", bold=True, color=HexColor("FF0000"))
189
+ t.add_run(" and ", italic=True)
190
+ t.add_run("LARGE BLUE", size=Pt(40), color=ThemeColor("accent1"))
191
+
192
+ shape.text = t
193
+ ```
194
+
195
+ ## Advanced Native Image Support (SVG)
196
+
197
+ Native PowerPoint doesn't typically embed pure SVGs comfortably via older XML specifications. `pptxizza` utilizes modern Microsoft Open XML extension nodes natively mapping `a:svgBlip` relations to automatically parse, embed, and inject SVGs interchangeably with PNGs and JPGs!
@@ -0,0 +1,184 @@
1
+ # pptxizza
2
+
3
+ A streamlined, LLM-friendly pip package for creating and editing `.pptx` files dynamically. `pptxizza` uses direct XML manipulation under the hood (via `lxml`) to fill text, replace images, and update charts and tables within existing PowerPoint templates quickly and efficiently, featuring a highly-granular Object-Oriented API for direct shape manipulation!
4
+
5
+ ## Features
6
+
7
+ - **Object-Oriented Integrity**: Native interaction with shapes (`Shape`, `Rect`, `Circle`) and charts (`BarChart`, `PieChart`, etc.).
8
+ - **Programmatic Shape Insertion**: Generate and structure exact geometry auto-shapes or vector pictures dynamically from code.
9
+ - **Text Replacement**: Replace `{{mustache}}` template variables anywhere on a slide, or target specific named shapes.
10
+ - **Image Replacement**: Easily swap out placeholder images with `.png`, `.jpg`, or `.svg` files while preserving position and styling.
11
+ - **Chart & Table Data Binding**: Update the underlying data of native PowerPoint charts (`BarChart`, `PieChart`, `LineChart`) directly via their class objects.
12
+
13
+ ## Installation
14
+
15
+ As this package is under development, you can use it locally by ensuring the package is in your `sys.path` or installing it in editable mode:
16
+
17
+ ```bash
18
+ pip install -e .
19
+ ```
20
+
21
+ Dependencies:
22
+ - `lxml`
23
+
24
+ ## Quick Start (Templating)
25
+
26
+ The most robust way to use `pptxizza` is to create a template (`template.pptx`) with some named placeholders or `{{mustache_keys}}`, then parse it:
27
+
28
+ ```python
29
+ from pptxizza import Presentation
30
+
31
+ def main():
32
+ pres = Presentation("template.pptx")
33
+ slide = pres.slides[0]
34
+
35
+ # Fill the slide with dynamic content mapped to shape names
36
+ slide.fill({
37
+ "{{title}}": "Quarterly Business Review", # Global mustache text replacement
38
+ "SubtitleShape": "Q3 2026 Results", # Named shape text replacement
39
+ "LogoPlaceholder": "company_logo.svg" # Named picture shape replacement (injects SVG!)
40
+ })
41
+
42
+ pres.save("output.pptx")
43
+
44
+ if __name__ == "__main__":
45
+ main()
46
+ ```
47
+
48
+ ## Creating a Presentation from Scratch
49
+
50
+ If you just need a blank presentation without an existing template, you can initialize `Presentation` with no arguments.
51
+
52
+ ```python
53
+ from pptxizza import Presentation
54
+
55
+ def main():
56
+ # Calling Presentation() without an argument loads a default blank template
57
+ pres = Presentation()
58
+
59
+ # You can now add slides or insert shapes programmatically
60
+ # ...
61
+
62
+ pres.save("new_presentation.pptx")
63
+
64
+ if __name__ == "__main__":
65
+ main()
66
+ ```
67
+
68
+ ## The Object-Oriented API (`pptxizza.shapes`)
69
+
70
+ `pptxizza` maps OpenXML structures directly into typed Python objects, allowing for programmatic instantiation and highly specific template discovery.
71
+
72
+ ### 1. Generating & Inserting Shapes Programmatically
73
+
74
+ Instead of relying solely on existing templates, you can import granular geometric components from `pptxizza.shapes` to build slides from scratch!
75
+
76
+ ```python
77
+ from pptxizza.shapes import Shape, Rect, Circle, Picture, Table
78
+ from pptxizza import Inches
79
+
80
+ slide = pres.slides[0]
81
+
82
+ # Create a primitive Rect shape with text
83
+ my_rect = Rect(text="Action Item", x=Inches(1), y=Inches(1), cx=Inches(3), cy=Inches(1))
84
+
85
+ # Create an Oval/Circle
86
+ my_circle = Circle(text="1", x=Inches(5), y=Inches(1), cx=Inches(1), cy=Inches(1))
87
+
88
+ # Create a Table (3 rows, 3 columns)
89
+ my_table = Table(rows=3, cols=3, x=Inches(1), y=Inches(3), cx=Inches(6), cy=Inches(1.5))
90
+
91
+ # Create an SVG Vector graphic dynamically
92
+ my_ufo = Picture(image_path="ufo.svg", x=Inches(3), y=Inches(5), cx=Inches(2), cy=Inches(2))
93
+
94
+ # Insert the objects natively. Background relation mapping is handled automatically!
95
+ slide.insert_shape(my_rect)
96
+ slide.insert_shape(my_circle)
97
+ slide.insert_shape(my_table)
98
+ slide.insert_shape(my_ufo)
99
+ ```
100
+
101
+ _Note_: `x`, `y`, `cx`, and `cy` are tracked in English Metric Units (EMUs). All shapes also expose `x_inches`, `y_inches`, `width_inches`, and `height_inches` for easier visual math.
102
+
103
+ ### 2. Inspecting and Manipulating Dynamic Shapes
104
+
105
+ `pptxizza` intelligently probes into the ZIP packages under the hood to detect exactly what's sitting on a template slide. When examining `slide.shapes`, generic graphic frames are dynamically resolved into explicit subclasses like `BarChart`, `PieChart`, or `LineChart`.
106
+
107
+ ```python
108
+ for shape in slide.shapes:
109
+ print(f"Discovered: {type(shape).__name__} named '{shape.shape_name}'")
110
+
111
+ # You can access common underlying properties easily!
112
+ shape.x_inches += 1.5 # shift everything right by 1.5 inches
113
+ ```
114
+
115
+ ### 3. Updating Chart Data via Shape Objects
116
+
117
+ When `slide.shapes` returns a chart class (e.g. `BarChart`, `PieChart`), you can call `.replace_data()` directly onto the object to overwrite its internal points caches.
118
+
119
+ ```python
120
+ from pptxizza.shapes import BarChart
121
+
122
+ for shape in slide.shapes:
123
+ if isinstance(shape, BarChart) and shape.shape_name == "SalesChart":
124
+ categories = ["Jan", "Feb", "Mar"]
125
+ data = {
126
+ 0: [150, 200, 250], # Update series 0 by its index
127
+ "Europe": [120, 180, 210] # Update series by string name matching
128
+ }
129
+
130
+ # Inject seamlessly into the cache!
131
+ shape.replace_data(data, categories)
132
+ ```
133
+
134
+ ### 4. Manipulating Tables
135
+
136
+ `Table` objects allow for granular cell access and styling using presets.
137
+
138
+ ```python
139
+ from pptxizza.shapes import Table
140
+
141
+ for shape in slide.shapes:
142
+ if isinstance(shape, Table):
143
+ # Access cells by (row, col)
144
+ shape.cell(0, 0).text = "Header 1"
145
+
146
+ # Helper to set first row names
147
+ shape.set_column_names(["ID", "Name", "Status"])
148
+
149
+ # Add new rows or columns
150
+ new_row = shape.add_row()
151
+ new_row.cells[0].text = "001"
152
+
153
+ # Apply visual presets
154
+ shape.apply_preset("Medium Style 2 - Accent 1")
155
+ ```
156
+
157
+ ### 5. Styling and Rich Text
158
+
159
+ Most shapes support direct styling of fill and font properties. You can also use the `Text` and `Run` objects for rich text formatting.
160
+
161
+ ```python
162
+ from pptxizza import RGBColor, HexColor, ThemeColor, Pt, Text
163
+
164
+ # Solid Fill
165
+ shape.fill.solid(HexColor("#4287f5"))
166
+
167
+ # Font Styling
168
+ shape.font.size = Pt(24)
169
+ shape.font.bold = True
170
+ shape.font.color = RGBColor(255, 255, 255)
171
+
172
+ # Rich Text with Runs
173
+ t = Text()
174
+ t.add_run("Normal text, ")
175
+ t.add_run("BOLD AND RED", bold=True, color=HexColor("FF0000"))
176
+ t.add_run(" and ", italic=True)
177
+ t.add_run("LARGE BLUE", size=Pt(40), color=ThemeColor("accent1"))
178
+
179
+ shape.text = t
180
+ ```
181
+
182
+ ## Advanced Native Image Support (SVG)
183
+
184
+ Native PowerPoint doesn't typically embed pure SVGs comfortably via older XML specifications. `pptxizza` utilizes modern Microsoft Open XML extension nodes natively mapping `a:svgBlip` relations to automatically parse, embed, and inject SVGs interchangeably with PNGs and JPGs!
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pptxizza"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="Kameron" },
10
+ ]
11
+ description = "A streamlined, LLM-friendly pip package for creating and editing pptx files from templates."
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ dependencies = [
19
+ "lxml>=4.9.0",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ dev = [
24
+ "pytest>=7.0.0",
25
+ ]
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
29
+
30
+ [tool.setuptools.package-data]
31
+ pptxizza = ["templates/*.pptx"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ from .presentation import Presentation
2
+ from .util import Length, Inches, Cm, Pt, Emu
3
+ from .colors import Color, RGBColor, HexColor, ThemeColor
4
+ from .text import Text, Run
5
+ from .shapes.table import Table
6
+
7
+ __all__ = [
8
+ "Presentation",
9
+ "Length", "Inches", "Cm", "Pt", "Emu",
10
+ "Color", "RGBColor", "HexColor", "ThemeColor",
11
+ "Text", "Run", "Table"
12
+ ]
@@ -0,0 +1,95 @@
1
+ from typing import Dict, List, Any, Optional, Union
2
+ from .xml_utils import etree, xpath, qname
3
+
4
+ def replace_chart_data(slide: Any, shape_name: str, data: Dict[Union[int, str], List[Any]], categories: List[str]) -> None:
5
+ """
6
+ Replaces the underlying data cache bindings inside an existing chart shape.
7
+
8
+ Args:
9
+ slide (Slide): The origin slide wrapper instance resolving the chart relational context.
10
+ shape_name (str): The logical embedded drawingML name attribute for the chart.
11
+ data (Dict[Union[int, str], List[Any]]): A dictionary mapping Series index integers or string headers to lists of arbitrary values.
12
+ categories (List[str]): Overriding axis category label names (e.g. Month headers / "Q1", "Q2").
13
+
14
+ Raises:
15
+ ValueError: If the shape wasn't found, isn't a chart, or lacks valid XML relationship dependencies.
16
+ """
17
+ cnvprs = xpath(slide._xml, f"//p:cNvPr[@name='{shape_name}']")
18
+ if not cnvprs:
19
+ raise ValueError(f"Chart shape '{shape_name}' not found on slide.")
20
+
21
+ parent = cnvprs[0].getparent()
22
+ if parent is None:
23
+ raise ValueError("Invalid shape hierarchy")
24
+ graphicFrame = parent.getparent()
25
+ if graphicFrame is None:
26
+ raise ValueError("Invalid shape hierarchy")
27
+
28
+ chart_ref = xpath(graphicFrame, ".//c:chart")
29
+ if not chart_ref:
30
+ raise ValueError(f"Shape '{shape_name}' is not a chart.")
31
+
32
+ rId = chart_ref[0].get(qname("r", "id"))
33
+
34
+ slide_rels = slide._package.get_rels(slide.part_name)
35
+ rel = slide_rels.rels.get(rId)
36
+ if not rel:
37
+ raise ValueError(f"Relationship {rId} for chart not found.")
38
+
39
+ target = rel['Target']
40
+ if target.startswith("../"):
41
+ target = target[3:]
42
+ chart_part_name = f"ppt/{target}"
43
+
44
+ chart_xml = slide._package.get_xml_part(chart_part_name)
45
+ if chart_xml is None:
46
+ raise ValueError(f"Chart part {chart_part_name} not found in package.")
47
+
48
+ series_nodes = xpath(chart_xml, "//c:ser")
49
+ for idx, ser in enumerate(series_nodes):
50
+ ser_vals = data.get(idx) or data.get(str(idx))
51
+ if ser_vals is None:
52
+ tx_node = xpath(ser, "c:tx//c:v")
53
+ if tx_node and tx_node[0].text in data:
54
+ ser_vals = data[tx_node[0].text]
55
+
56
+ if ser_vals is None:
57
+ continue
58
+
59
+ cat_caches = xpath(ser, "c:cat//c:strCache")
60
+ if not cat_caches:
61
+ cat_caches = xpath(ser, "c:cat//c:numCache")
62
+
63
+ if cat_caches:
64
+ _update_cache(cat_caches[0], categories)
65
+
66
+ val_caches = xpath(ser, "c:val//c:numCache")
67
+ if val_caches:
68
+ _update_cache(val_caches[0], ser_vals)
69
+
70
+ slide._package.set_xml_part(chart_part_name, chart_xml)
71
+
72
+
73
+ def _update_cache(cache_node: etree._Element, items: List[Any]) -> None:
74
+ """
75
+ Overwrites the `ptCount` size cache and constructs identical `pt` mapping points internally.
76
+
77
+ Args:
78
+ cache_node (etree._Element): The internal c:strCache or c:numCache DOM node target wrapper.
79
+ items (List[Any]): Array of primitive values to serialize into the point cache elements.
80
+ """
81
+ ptCount = xpath(cache_node, "c:ptCount")
82
+ if not ptCount:
83
+ ptCount_elem = etree.SubElement(cache_node, qname("c", "ptCount"))
84
+ else:
85
+ ptCount_elem = ptCount[0]
86
+ ptCount_elem.set("val", str(len(items)))
87
+
88
+ for pt in xpath(cache_node, "c:pt"):
89
+ cache_node.remove(pt)
90
+
91
+ for i, item in enumerate(items):
92
+ pt_elem = etree.SubElement(cache_node, qname("c", "pt"))
93
+ pt_elem.set("idx", str(i))
94
+ v_elem = etree.SubElement(pt_elem, qname("c", "v"))
95
+ v_elem.text = str(item)
@@ -0,0 +1,55 @@
1
+ from lxml import etree
2
+ from .xml_utils import qname
3
+
4
+ class Color:
5
+ """
6
+ Base class for shape and text colors in pptxizza.
7
+ Subclasses must implement _get_color_node.
8
+ """
9
+ def _get_color_node(self) -> etree._Element:
10
+ raise NotImplementedError("Subclasses must implement _get_color_node")
11
+
12
+
13
+ class RGBColor(Color):
14
+ """
15
+ Represents an RGB color constructed from integer red, green, and blue values (0-255).
16
+ """
17
+ def __init__(self, r: int, g: int, b: int) -> None:
18
+ self.r = r
19
+ self.g = g
20
+ self.b = b
21
+
22
+ def _get_color_node(self) -> etree._Element:
23
+ node = etree.Element(qname("a", "srgbClr"))
24
+ # Render hex format without the #
25
+ hex_val = f"{self.r:02X}{self.g:02X}{self.b:02X}"
26
+ node.set("val", hex_val)
27
+ return node
28
+
29
+
30
+ class HexColor(Color):
31
+ """
32
+ Represents a color constructed from a standard hexadecimal string (e.g. '#FF0000' or 'FF0000').
33
+ """
34
+ def __init__(self, hex_str: str) -> None:
35
+ if hex_str.startswith("#"):
36
+ hex_str = hex_str[1:]
37
+ self.hex_str = hex_str.upper()
38
+
39
+ def _get_color_node(self) -> etree._Element:
40
+ node = etree.Element(qname("a", "srgbClr"))
41
+ node.set("val", self.hex_str)
42
+ return node
43
+
44
+
45
+ class ThemeColor(Color):
46
+ """
47
+ Represents a color tied to the presentation's theme scheme (e.g., 'accent1', 'tx1', 'bg1').
48
+ """
49
+ def __init__(self, theme_val: str) -> None:
50
+ self.theme_val = theme_val
51
+
52
+ def _get_color_node(self) -> etree._Element:
53
+ node = etree.Element(qname("a", "schemeClr"))
54
+ node.set("val", self.theme_val)
55
+ return node