athena-python-pptx 0.16.1__tar.gz → 0.17.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.
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/AGENTS.md +29 -2
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/API_PARITY_REPORT.md +1 -1
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/CHANGELOG.md +132 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/PKG-INFO +2 -1
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/docs/API_PARITY_EXCEPTIONS.md +355 -8
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/__init__.py +2 -2
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/_unsupported.py +85 -43
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/batching.py +179 -4
- athena_python_pptx-0.17.0/pptx/oxml/__init__.py +114 -0
- athena_python_pptx-0.17.0/pptx/oxml/_mirror.py +471 -0
- athena_python_pptx-0.17.0/pptx/oxml/_tbl.py +1152 -0
- athena_python_pptx-0.17.0/pptx/oxml/_txbody.py +1782 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/oxml/ns.py +8 -0
- athena_python_pptx-0.17.0/pptx/oxml/presentation.py +378 -0
- athena_python_pptx-0.17.0/pptx/oxml/shapes/__init__.py +68 -0
- athena_python_pptx-0.17.0/pptx/oxml/shapes/autoshape.py +32 -0
- athena_python_pptx-0.17.0/pptx/oxml/shapes/connector.py +31 -0
- athena_python_pptx-0.17.0/pptx/oxml/shapes/graphfrm.py +32 -0
- athena_python_pptx-0.17.0/pptx/oxml/shapes/groupshape.py +402 -0
- athena_python_pptx-0.17.0/pptx/oxml/shapes/picture.py +31 -0
- athena_python_pptx-0.17.0/pptx/oxml/shapes/shared.py +871 -0
- athena_python_pptx-0.17.0/pptx/oxml/simpletypes.py +727 -0
- athena_python_pptx-0.17.0/pptx/oxml/table.py +583 -0
- athena_python_pptx-0.17.0/pptx/oxml/text.py +652 -0
- athena_python_pptx-0.17.0/pptx/oxml/xmlchemy.py +716 -0
- athena_python_pptx-0.17.0/pptx/parts/presentation.py +54 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/presentation.py +24 -2
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/__init__.py +837 -84
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/slides.py +69 -4
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/text/__init__.py +169 -2
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/units.py +6 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pyproject.toml +5 -1
- athena_python_pptx-0.16.1/pptx/oxml/__init__.py +0 -43
- athena_python_pptx-0.16.1/pptx/parts/presentation.py +0 -15
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/.gitignore +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/CLAUDE.md +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/README.md +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/_citations.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/_references.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/action.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/client.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/commands.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/errors.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/exc.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/generated/__init__.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/generated/autoshape_types.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/kit.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/media.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/package.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/shared.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/slide.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/spec.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/table.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/types.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/typing.py +0 -0
- {athena_python_pptx-0.16.1 → athena_python_pptx-0.17.0}/pptx/util.py +0 -0
|
@@ -69,7 +69,8 @@ A small number of REST-SDK-specific departures are documented in
|
|
|
69
69
|
|
|
70
70
|
These standard python-pptx members are omitted because a REST SDK has no
|
|
71
71
|
local XML / OPC parts:
|
|
72
|
-
- `Shape.
|
|
72
|
+
- `Shape.part` — package part access. (`Shape.element` is implemented: the
|
|
73
|
+
shape's element, for python-pptx's shape-tree recipes; see Architecture.)
|
|
73
74
|
- `TextFrame.part`, `Paragraph.part`, `Run.part` — package part access.
|
|
74
75
|
- `FillFormat.from_fill_parent()`, `ColorFormat.from_colorchoice_parent()` — internal XML constructors.
|
|
75
76
|
- `Font.fill` — Font fill format (uncommon; use `font.color` for text color).
|
|
@@ -119,7 +120,33 @@ install fails the parity tests rather than skipping them.
|
|
|
119
120
|
|
|
120
121
|
This is a **REST API client** that mimics the python-pptx API. Instead of manipulating XML directly, method calls are translated to commands sent to the PPTX Studio API server. Key differences from python-pptx internals:
|
|
121
122
|
|
|
122
|
-
- No
|
|
123
|
+
- No local OOXML package. python-pptx's private XML attributes (`_tbl`,
|
|
124
|
+
`_tc`, `series._element`, ...) raise `UnsupportedAttributeError` naming the
|
|
125
|
+
supported API (`pptx/_unsupported.py`), except where common recipes map onto
|
|
126
|
+
commands. Those edits are held until complete
|
|
127
|
+
(`CommandBuffer.hold_pending_edit`):
|
|
128
|
+
- `prs.slides._sldIdLst` and `prs.part.drop_rel()` for slide delete and move
|
|
129
|
+
(`pptx/oxml/presentation.py`, tests in `tests/test_sldidlst_compat.py`);
|
|
130
|
+
- `shape._element` / `shape.element` (with `_sp`, `_pic`, `_graphicFrame`,
|
|
131
|
+
`_grpSp`) and `slide.shapes._spTree` for shape delete, z-order and
|
|
132
|
+
duplicate, sent as `DeleteShape`, `SetShapeZOrder` and `CloneShape`
|
|
133
|
+
(`pptx/oxml/shapes/`, tests in `tests/test_shape_element_compat.py`);
|
|
134
|
+
- a shape's text: `text_frame._txBody` / `_bodyPr`, `paragraph._p`,
|
|
135
|
+
`run._r` and `font._rPr` are real lxml elements of python-pptx's classes
|
|
136
|
+
(`pptx/oxml/text.py` on the ported `xmlchemy` / `simpletypes`), a mirror of
|
|
137
|
+
the deck that is diffed into the public API's commands when the change
|
|
138
|
+
resolves. `pptx/oxml/_mirror.py` holds the reusable lifecycle (hold,
|
|
139
|
+
resolve, in-place re-sync, late edits, the access hooks that resolve before
|
|
140
|
+
a public call), `pptx/oxml/_txbody.py` the text translation; tests in
|
|
141
|
+
`tests/test_text_xml_compat.py`. A change no command carries raises
|
|
142
|
+
naming the XML path; never drop one silently.
|
|
143
|
+
- a table: `table._tbl`, `cell._tc`, `row._tr` and `column._gridCol`
|
|
144
|
+
(`pptx/oxml/table.py`), the same kind of mirror (`pptx/oxml/_tbl.py`):
|
|
145
|
+
cell borders, margins, anchor and fill, row heights, column widths, the
|
|
146
|
+
look flags, and row / column removal. It captures rows by their
|
|
147
|
+
serialization and re-syncs only the cells an API write touched, so a
|
|
148
|
+
recipe that walks a large table cell by cell stays linear; tests in
|
|
149
|
+
`tests/test_table_xml_compat.py`.
|
|
123
150
|
- Operations are batched via `with prs.batch():` context manager
|
|
124
151
|
- State is synchronized via the PPTX Studio collaboration layer (Yjs)
|
|
125
152
|
- **Programmatic Tool Calling (PTC):** every `CommandBuffer.add` and
|
|
@@ -91,7 +91,7 @@ via Tranches R–W:
|
|
|
91
91
|
| SlideLayout | PASS (+`index`) |
|
|
92
92
|
| SlideMaster | PASS |
|
|
93
93
|
| SlideShapes | PASS (+SDK REST helpers; `add_connector` accepts optional `begin_shape`/`end_shape`) |
|
|
94
|
-
| Table | PASS (+SDK REST helpers + inherited `Shape.element`
|
|
94
|
+
| Table | PASS (+SDK REST helpers + inherited `Shape.element`, its frame's `p:graphicFrame` element) |
|
|
95
95
|
| `_Cell` | PASS (+SDK REST helpers) |
|
|
96
96
|
| PlaceholderFormat | PASS (+convenience type-check booleans + custom-prompt/transform fields) |
|
|
97
97
|
| Chart | PASS (`category_axis` / `value_axis` / `legend` / `font` / `chart_style` added in v0.1.68; replaces `UnsupportedFeatureError` stubs) |
|
|
@@ -2,6 +2,138 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.17.0
|
|
6
|
+
|
|
7
|
+
- python-pptx's slide delete and move recipes work. They edit
|
|
8
|
+
`prs.slides._sldIdLst`, which raised `UnsupportedAttributeError`; it is now
|
|
9
|
+
python-pptx's `p:sldIdLst` (`pptx.oxml.presentation.CT_SlideIdList`):
|
|
10
|
+
- `xml_slides.remove(entry)` (or `del xml_slides[i]`) deletes the slide;
|
|
11
|
+
- re-inserting it (`insert`, `append`, `addnext`, `addprevious`) moves it
|
|
12
|
+
instead, and inserting a slide that is still in the list moves it, as
|
|
13
|
+
lxml does;
|
|
14
|
+
- `prs.part.drop_rel(entry.rId)` then `del` deletes the slide once;
|
|
15
|
+
- an entry's `id` is its slide's `slide_id`, as in python-pptx, so the
|
|
16
|
+
recipe that finds a slide's entry by comparing the two works.
|
|
17
|
+
|
|
18
|
+
Edits are held until the change is complete, then sent as deletes and one
|
|
19
|
+
reorder, so a move never deletes its slide first and "remove every slide,
|
|
20
|
+
then append them in a new order" is a single reorder. Anything that reads
|
|
21
|
+
slide positions sends an open change first (the next command, `prs.slides`,
|
|
22
|
+
a slide's index, a flush, a refresh, a `batch()` or `with prs:` block), so
|
|
23
|
+
later commands address slides by their new positions; a block that raises
|
|
24
|
+
drops the edits made inside it. The bulk-delete guard applies. Other lxml
|
|
25
|
+
members, and other OPC members of `prs.part`, raise naming the supported
|
|
26
|
+
API.
|
|
27
|
+
- A slide proxy held from before its slide was deleted (`prs.slides.delete`
|
|
28
|
+
or the recipes above) raises `ConflictError` on an edit. It wrote to the
|
|
29
|
+
slide that had taken its position.
|
|
30
|
+
- python-pptx's shape delete, z-order and duplicate recipes work.
|
|
31
|
+
`shape._element` and python-pptx's public `shape.element` (both raised or
|
|
32
|
+
answered with a delete-only proxy) are the shape's element, with python-pptx's
|
|
33
|
+
`_sp`, `_pic`, `_graphicFrame` and `_grpSp` aliases, and
|
|
34
|
+
`slide.shapes._spTree` is the slide's shape tree (`pptx.oxml.shapes`:
|
|
35
|
+
`CT_Shape`, `CT_Picture`, `CT_GraphicalObjectFrame`, `CT_Connector`,
|
|
36
|
+
`CT_GroupShape`). As in python-pptx, `group.shapes._spTree` is the group's
|
|
37
|
+
element and the tree holds `p:nvGrpSpPr` and `p:grpSpPr` first.
|
|
38
|
+
- `sp.getparent().remove(sp)` (or `spTree.remove(el)`, `del spTree[i]`)
|
|
39
|
+
deletes the shape or placeholder;
|
|
40
|
+
- putting it back (`insert`, `append`, `addnext`, `addprevious`), or
|
|
41
|
+
inserting a shape that is still in the tree, moves it, as lxml does, sent
|
|
42
|
+
as the fewest `SetShapeZOrder` steps that give exactly the new order:
|
|
43
|
+
`spTree.insert(2, el)` sends it to the back;
|
|
44
|
+
- inserting `copy.deepcopy(shape._element)` into any slide's tree clones the
|
|
45
|
+
shape there in place (`CloneShape` with no offset), and a copy never
|
|
46
|
+
inserted sends nothing.
|
|
47
|
+
|
|
48
|
+
Edits are held until the change is complete, as for `_sldIdLst`, so a move
|
|
49
|
+
never deletes its shape; the next command, a flush or refresh, and reading
|
|
50
|
+
`slide.shapes` send them (clones, then deletes, then z-order). An element's
|
|
51
|
+
tag, parent and siblings, and its `p:cNvPr` id and name, read as in
|
|
52
|
+
python-pptx. Other lxml members, property-level XML (`spPr`, `txBody`),
|
|
53
|
+
moving a shape to another slide or group, and copies changed after
|
|
54
|
+
`deepcopy()` raise naming the supported API.
|
|
55
|
+
- A held XML-recipe change that completes sends its commands without forcing
|
|
56
|
+
the other held change, so a shape move made during a slide move no longer
|
|
57
|
+
turns the slide's removal into a delete, and a clone lands on the slide it
|
|
58
|
+
named.
|
|
59
|
+
- A group member's `bring_to_front()` / `send_to_back()` / `bring_forward()` /
|
|
60
|
+
`send_backward()` and `delete()` update `group.shapes`, which kept the old
|
|
61
|
+
order and the deleted member until a refresh.
|
|
62
|
+
- python-pptx's text XML recipes work: bullets on and off, numbered lists,
|
|
63
|
+
hanging indents, superscript, letter spacing, line spacing and autofit
|
|
64
|
+
through XML. `text_frame._txBody` / `_bodyPr`, `paragraph._p` / `_pPr` /
|
|
65
|
+
`_defRPr`, `run._r` / `_rPr` and `font._rPr` raised
|
|
66
|
+
`UnsupportedAttributeError`; they are now real lxml elements of
|
|
67
|
+
python-pptx's classes (`pptx.oxml.text`), one tree per text frame built from
|
|
68
|
+
the deck, with python-pptx's helpers (`get_or_add_pPr()`,
|
|
69
|
+
`insert_element_before()`, `pPr.lvl`, ...). What a recipe changes is sent
|
|
70
|
+
through the public API's setters:
|
|
71
|
+
- `a:pPr` as `SetParagraphStyle` (indents, level, alignment, spacing, bullet
|
|
72
|
+
color and the `•` / `◦` / `▪`, `a:buNone` and `arabicPeriod` bullets),
|
|
73
|
+
its `a:defRPr` as `paragraph.font`;
|
|
74
|
+
- `a:rPr` as `SetRunStyle` (bold, italic, underline, size, strike,
|
|
75
|
+
baseline, caps, spacing, language, color, highlight, typeface);
|
|
76
|
+
- `a:bodyPr` as `SetTextFrameProperties` (wrap, anchor, insets, autofit);
|
|
77
|
+
- run text, appended runs, breaks and paragraphs, and trailing removals
|
|
78
|
+
through the existing text commands.
|
|
79
|
+
|
|
80
|
+
The change is sent before the next command, explicit flush, refresh or API
|
|
81
|
+
call on that text (never by the idle timer), so a later API write wins and
|
|
82
|
+
reads see the edit; like `_sldIdLst`'s, a `prs.batch()` or `with prs:` block
|
|
83
|
+
that raises drops the changes made inside it. Requested again, the tree shows
|
|
84
|
+
the deck's state in place. Anything no command carries raises
|
|
85
|
+
`UnsupportedAttributeError` naming the XML path and the supported API, and
|
|
86
|
+
is dropped. `pptx.oxml.parse_xml` and `pptx.oxml.xmlchemy.OxmlElement` build
|
|
87
|
+
real elements instead of raising. `lxml` is now a dependency.
|
|
88
|
+
- `str()` of a length (`Emu`, `Pt`, ...) and f-strings give the EMU integer, as
|
|
89
|
+
in python-pptx, so `pPr.set("marL", str(Emu(Pt(18))))` writes `"228600"`
|
|
90
|
+
(it wrote `"Emu(228600)"`); `repr()` is unchanged.
|
|
91
|
+
- A run's color writes merged into one command drop an earlier brightness the
|
|
92
|
+
later write does not set, as the studio does when it applies them one by
|
|
93
|
+
one: `font.color.brightness = 0.4` then `font.color.brightness = 0` (or a
|
|
94
|
+
new `theme_color`) sent the 0.4.
|
|
95
|
+
- python-pptx's table XML recipes work: cell borders, row heights, column
|
|
96
|
+
widths, the look flags, a cell's margins, anchor and fill through XML, and
|
|
97
|
+
the row and column delete recipes. `table._tbl`, `cell._tc`, `row._tr` and
|
|
98
|
+
`column._gridCol` raised `UnsupportedAttributeError`; they are now real lxml
|
|
99
|
+
elements of python-pptx's classes (`pptx.oxml.table`), one tree per table
|
|
100
|
+
built from the deck. What a recipe changes is sent through the public API's
|
|
101
|
+
setters:
|
|
102
|
+
- a cell's `a:lnL` / `a:lnR` / `a:lnT` / `a:lnB` as one `SetCellBorders`
|
|
103
|
+
(`w`, an RGB or no line, the dash), which `cell.border_*` then reads (a
|
|
104
|
+
later `width` or `dash_style` write keeps the no line or theme color);
|
|
105
|
+
- `marL` / `marR` / `marT` / `marB` as `SetCellMargins`, `anchor` as
|
|
106
|
+
`SetCellAnchor`, an RGB `a:solidFill` or `a:noFill` as `SetTableCell`;
|
|
107
|
+
- `a:tr/@h` as `SetRowHeight`, `a:gridCol/@w` as `SetColWidth`, the
|
|
108
|
+
`a:tblPr` flags as one `SetTableLook`;
|
|
109
|
+
- a removed `a:tr` as `DeleteTableRow`, a removed column (its `a:gridCol`
|
|
110
|
+
and its `a:tc` in every row) as `DeleteTableColumn`.
|
|
111
|
+
|
|
112
|
+
The studio cannot change an existing table's style, so an edit to
|
|
113
|
+
`a:tableStyleId` raises `UnsupportedAttributeError` naming what can change;
|
|
114
|
+
so do merges, a cell's text, rows or columns added through XML and the
|
|
115
|
+
other properties no command carries. A border's `cap` is ignored with a
|
|
116
|
+
warning. Walking the table (`table.cell(r, c)`, `row.cells`) does not send a
|
|
117
|
+
pending change, so a recipe that edits every cell sends its edits together.
|
|
118
|
+
- `table.delete_row(i)` / `table.delete_column(i)` drop the row's height (the
|
|
119
|
+
column's width) locally, as the server does: the rows below kept reading
|
|
120
|
+
the height of the row after them.
|
|
121
|
+
- A row or column proxy (`table.rows[i]`, `table.columns[i]`) kept across
|
|
122
|
+
rows or columns inserted or removed before it keeps meaning its own row or
|
|
123
|
+
column, as python-pptx's does: it read and wrote the one that took its
|
|
124
|
+
index. One whose row or column was removed raises `IndexError`.
|
|
125
|
+
- A chart data label's `text_frame` has python-pptx's `paragraphs`: one
|
|
126
|
+
paragraph over the label's text whose runs and font are the label's, so
|
|
127
|
+
`point.data_label.text_frame.paragraphs[0].runs[0].font.bold = True` and
|
|
128
|
+
`paragraphs[0].text = ...` work. `paragraphs` raised `AttributeError`.
|
|
129
|
+
- Table cell text frames have python-pptx's `vertical_anchor` and margins
|
|
130
|
+
(they write the cell's own), and `word_wrap` / `auto_size`; cell paragraphs
|
|
131
|
+
have `line_spacing`, `space_before`, `space_after` and `level`. A
|
|
132
|
+
one-paragraph cell cannot apply those last members, so a write warns once
|
|
133
|
+
per presentation and keeps the value for read-after-write, instead of
|
|
134
|
+
raising `AttributeError`, which production scripts hit. An uploaded cell's
|
|
135
|
+
paragraph reads its own level and spacing (`a:pPr`), as python-pptx does.
|
|
136
|
+
|
|
5
137
|
## 0.16.1 — `Presentation.upload()` and `create(pptx=...)` report the deck they create
|
|
6
138
|
|
|
7
139
|
- `Presentation.upload()` emits the `CreatePresentation` tool-call event with
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration
|
|
5
5
|
Project-URL: Homepage, https://github.com/pptx-studio/python-sdk
|
|
6
6
|
Project-URL: Documentation, https://docs.pptx-studio.com/sdk/python
|
|
@@ -22,6 +22,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: athena-references>=0.1.0
|
|
25
|
+
Requires-Dist: lxml>=4.9.0
|
|
25
26
|
Requires-Dist: requests>=2.28.0
|
|
26
27
|
Provides-Extra: dev
|
|
27
28
|
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
@@ -45,10 +45,8 @@ parity test's known-missing list by `tests/test_no_silent_noops.py`).
|
|
|
45
45
|
| `pptx.shapes.DateAxis` | `element` |
|
|
46
46
|
| `pptx.shapes.DoughnutPlot` | `element` |
|
|
47
47
|
| `pptx.shapes.FillFormat` | `from_fill_parent` |
|
|
48
|
-
| `pptx.shapes.GroupShapes` | `element` |
|
|
49
48
|
| `pptx.shapes.LayoutPlaceholder` | `get_or_add_ln`, `ln` |
|
|
50
49
|
| `pptx.shapes.LayoutPlaceholders` | `element` |
|
|
51
|
-
| `pptx.shapes.LayoutShapes` | `element` |
|
|
52
50
|
| `pptx.shapes.Legend` | `element` |
|
|
53
51
|
| `pptx.shapes.LinePlot` | `element` |
|
|
54
52
|
| `pptx.shapes.LineSeries` | `element` |
|
|
@@ -56,12 +54,10 @@ parity test's known-missing list by `tests/test_no_silent_noops.py`).
|
|
|
56
54
|
| `pptx.shapes.Marker` | `element` |
|
|
57
55
|
| `pptx.shapes.MasterPlaceholder` | `get_or_add_ln`, `ln` |
|
|
58
56
|
| `pptx.shapes.MasterPlaceholders` | `element` |
|
|
59
|
-
| `pptx.shapes.MasterShapes` | `element` |
|
|
60
57
|
| `pptx.shapes.Movie` | `get_or_add_ln`, `ln` |
|
|
61
58
|
| `pptx.shapes.NotesMaster` | `element` |
|
|
62
59
|
| `pptx.shapes.NotesSlidePlaceholder` | `get_or_add_ln`, `ln` |
|
|
63
60
|
| `pptx.shapes.NotesSlidePlaceholders` | `element` |
|
|
64
|
-
| `pptx.shapes.NotesSlideShapes` | `element` |
|
|
65
61
|
| `pptx.shapes.Picture` | `get_or_add_ln`, `ln` |
|
|
66
62
|
| `pptx.shapes.PicturePlaceholder` | `get_or_add_ln`, `ln` |
|
|
67
63
|
| `pptx.shapes.PiePlot` | `element` |
|
|
@@ -72,7 +68,6 @@ parity test's known-missing list by `tests/test_no_silent_noops.py`).
|
|
|
72
68
|
| `pptx.shapes.RadarPlot` | `element` |
|
|
73
69
|
| `pptx.shapes.RadarSeries` | `element` |
|
|
74
70
|
| `pptx.shapes.Shape` | `get_or_add_ln`, `ln` |
|
|
75
|
-
| `pptx.shapes.Shapes` | `element` |
|
|
76
71
|
| `pptx.shapes.SlidePlaceholder` | `get_or_add_ln`, `ln` |
|
|
77
72
|
| `pptx.shapes.SlidePlaceholders` | `element` |
|
|
78
73
|
| `pptx.shapes.TableCell` | `element` |
|
|
@@ -111,14 +106,13 @@ parity test's known-missing list by `tests/test_no_silent_noops.py`).
|
|
|
111
106
|
| `pptx.parts.embeddedpackage.EmbeddedXlsxPart` | `factory`, `load`, `load_rels_from_xml`, `new`, `package`, `part_related_by`, `partname_template`, `relate_to`, `related_part`, `rels`, `target_ref` |
|
|
112
107
|
| `pptx.parts.image.ImagePart` | `desc`, `ext`, `image`, `load`, `load_rels_from_xml`, `new`, `package`, `part_related_by`, `relate_to`, `related_part`, `rels`, `scale`, `sha1`, `target_ref` |
|
|
113
108
|
| `pptx.parts.media.MediaPart` | `load`, `load_rels_from_xml`, `new`, `package`, `part_related_by`, `relate_to`, `related_part`, `rels`, `sha1`, `target_ref` |
|
|
114
|
-
| `pptx.parts.presentation.PresentationPart` | `add_slide`, `core_properties`, `
|
|
109
|
+
| `pptx.parts.presentation.PresentationPart` | `add_slide`, `core_properties`, `get_slide`, `load`, `load_rels_from_xml`, `notes_master`, `notes_master_part`, `package`, `part`, `part_related_by`, `presentation`, `relate_to`, `related_part`, `related_slide`, `related_slide_master`, `rels`, `rename_slide_parts`, `save`, `slide_id`, `target_ref` |
|
|
115
110
|
| `pptx.parts.slide.BaseSlidePart` | `drop_rel`, `get_image`, `get_or_add_image_part`, `load`, `load_rels_from_xml`, `name`, `package`, `part`, `part_related_by`, `relate_to`, `related_part`, `rels`, `target_ref` |
|
|
116
111
|
| `pptx.parts.slide.NotesMasterPart` | `create_default`, `drop_rel`, `get_image`, `get_or_add_image_part`, `load`, `load_rels_from_xml`, `name`, `notes_master`, `package`, `part`, `part_related_by`, `relate_to`, `related_part`, `rels`, `target_ref` |
|
|
117
112
|
| `pptx.parts.slide.NotesSlidePart` | `drop_rel`, `get_image`, `get_or_add_image_part`, `load`, `load_rels_from_xml`, `name`, `new`, `notes_master`, `notes_slide`, `package`, `part`, `part_related_by`, `relate_to`, `related_part`, `rels`, `target_ref` |
|
|
118
113
|
| `pptx.parts.slide.SlideLayoutPart` | `drop_rel`, `get_image`, `get_or_add_image_part`, `load`, `load_rels_from_xml`, `name`, `package`, `part`, `part_related_by`, `relate_to`, `related_part`, `rels`, `slide_layout`, `slide_master`, `target_ref` |
|
|
119
114
|
| `pptx.parts.slide.SlideMasterPart` | `drop_rel`, `get_image`, `get_or_add_image_part`, `load`, `load_rels_from_xml`, `name`, `package`, `part`, `part_related_by`, `relate_to`, `related_part`, `related_slide_layout`, `rels`, `slide_master`, `target_ref` |
|
|
120
115
|
| `pptx.parts.slide.SlidePart` | `add_chart_part`, `add_embedded_ole_object_part`, `drop_rel`, `get_image`, `get_or_add_image_part`, `get_or_add_video_media_part`, `has_notes_slide`, `load`, `load_rels_from_xml`, `name`, `new`, `notes_slide`, `package`, `part`, `part_related_by`, `relate_to`, `related_part`, `rels`, `slide`, `slide_id`, `slide_layout`, `target_ref` |
|
|
121
|
-
| `pptx.presentation.Presentation` | `part` |
|
|
122
116
|
| `pptx.shapes.Area3DPlot` | `part` |
|
|
123
117
|
| `pptx.shapes.AreaPlot` | `part` |
|
|
124
118
|
| `pptx.shapes.AreaSeries` | `part` |
|
|
@@ -271,6 +265,9 @@ After changing any of these behaviours run
|
|
|
271
265
|
<!-- BEGIN GENERATED: unsupported-writes (scripts/generate_parity_tables.py; do not edit by hand) -->
|
|
272
266
|
| Call | SDK member | Behaviour | Why / what to do instead |
|
|
273
267
|
|---|---|---|---|
|
|
268
|
+
| `CT_GroupShape item assignment` | `CT_GroupShape.__setitem__` | raises `UnsupportedAttributeError` | replacing a child by assignment is not mirrored: remove() the old element and insert() the new one (spTree.insert(i, el) puts el before child i). |
|
|
269
|
+
| `CT_NonVisualDrawingProps.id` | `CT_NonVisualDrawingProps.id` | raises `UnsupportedAttributeError` | the studio gives every shape its own id, a copy inserted with copy.deepcopy() included, so a recipe's id renumbering is not needed: drop the line. |
|
|
270
|
+
| `CT_NonVisualDrawingProps.name` | `CT_NonVisualDrawingProps.name` | raises `UnsupportedAttributeError` | set the shape's name with shape.name = '...' (for a copy, on the shape it becomes once inserted: slide.shapes[-1].name after spTree.append(copy)). |
|
|
274
271
|
| `Connector.text` | `Connector.text` | raises `UnsupportedAttributeError` | a Connector has no text frame (has_text_frame is False). Put the text in a text box or an autoshape instead. |
|
|
275
272
|
| `Font.fill.gradient` | `_FontFillFormat.gradient` | raises `UnsupportedFeatureError` | Gradient text fills aren't yet wired through to the studio exporter. Use Font.color for a solid colour, or render the gradient text to an image and add_picture(...) instead. |
|
|
276
273
|
| `Font.fill.patterned` | `_FontFillFormat.patterned` | raises `UnsupportedFeatureError` | Pattern text fills aren't yet wired through to the studio exporter. |
|
|
@@ -327,7 +324,13 @@ After changing any of these behaviours run
|
|
|
327
324
|
| `slide_layout.background.fill.fore_color.rgb` | `_LayoutBackgroundColor.rgb` | raises `UnsupportedFeatureError` | Layout and master backgrounds are read-only in athena-python-pptx (no command writes them). Set slide.background.fill on each slide instead. |
|
|
328
325
|
| `slide_layout.background.fill.solid` | `_LayoutBackgroundFill.solid` | raises `UnsupportedFeatureError` | Layout and master backgrounds are read-only in athena-python-pptx (no command writes them). Set slide.background.fill on each slide instead. |
|
|
329
326
|
| `slide_master.background.fill.fore_color.rgb` | `_LayoutBackgroundColor.rgb` | raises `UnsupportedFeatureError` | Layout and master backgrounds are read-only in athena-python-pptx (no command writes them). Set slide.background.fill on each slide instead. |
|
|
327
|
+
| `table cell paragraph.level` | `_TableCellParagraph.level` | warns once; value kept locally | a table cell has one top-level paragraph |
|
|
328
|
+
| `table cell paragraph.line_spacing` | `_TableCellParagraph.line_spacing` | warns once; value kept locally | a table cell is one paragraph that follows the table style's spacing; set the row height (table.rows[i].height) or the cell margins instead |
|
|
329
|
+
| `table cell paragraph.space_after` | `_TableCellParagraph.space_after` | warns once; value kept locally | a table cell is one paragraph that follows the table style's spacing; set the row height (table.rows[i].height) or the cell margins instead |
|
|
330
|
+
| `table cell paragraph.space_before` | `_TableCellParagraph.space_before` | warns once; value kept locally | a table cell is one paragraph that follows the table style's spacing; set the row height (table.rows[i].height) or the cell margins instead |
|
|
330
331
|
| `table cell run.hyperlink.address` | `_TableCellHyperlink.address` | raises `UnsupportedFeatureError` | Setting a hyperlink on table-cell text is not supported yet. Put the link on a text box run (run.hyperlink.address) or a shape (shape.click_action.hyperlink.address) instead. |
|
|
332
|
+
| `table cell text_frame.auto_size` | `_TableCellTextFrame.auto_size` | warns once; value kept locally | a table row grows to fit its text |
|
|
333
|
+
| `table cell text_frame.word_wrap` | `_TableCellTextFrame.word_wrap` | warns once; value kept locally | table cell text wraps at the cell's width |
|
|
331
334
|
| `tick_labels.offset` | `TickLabels.offset` | warns once; value kept locally | no command carries it |
|
|
332
335
|
<!-- END GENERATED: unsupported-writes -->
|
|
333
336
|
---
|
|
@@ -337,6 +340,340 @@ After changing any of these behaviours run
|
|
|
337
340
|
These are documented intentional differences in behavior where the
|
|
338
341
|
SDK doesn't yet match python-pptx's API one-for-one.
|
|
339
342
|
|
|
343
|
+
### `prs.slides._sldIdLst` and `prs.part.drop_rel()` — the slide delete and move recipes (v0.17.0)
|
|
344
|
+
|
|
345
|
+
python-pptx has no public slide delete or move, so the recipes everyone copies
|
|
346
|
+
edit the slide-list element. They work here, line for line:
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
xml_slides = prs.slides._sldIdLst
|
|
350
|
+
slides = list(xml_slides)
|
|
351
|
+
xml_slides.remove(slides[i]) # delete slide i
|
|
352
|
+
xml_slides.insert(j, slides[i]) # ...or, re-inserted, move it to j
|
|
353
|
+
|
|
354
|
+
rId = prs.slides._sldIdLst[i].rId # delete, the other common spelling
|
|
355
|
+
prs.part.drop_rel(rId)
|
|
356
|
+
del prs.slides._sldIdLst[i]
|
|
357
|
+
|
|
358
|
+
id_dict = {sldId.id: [i, sldId.rId] for i, sldId in enumerate(prs.slides._sldIdLst)}
|
|
359
|
+
prs.part.drop_rel(id_dict[slide.slide_id][1]) # ...and keyed by slide id
|
|
360
|
+
del prs.slides._sldIdLst[id_dict[slide.slide_id][0]]
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
`prs.slides._sldIdLst` is python-pptx's `p:sldIdLst`
|
|
364
|
+
(`pptx.oxml.presentation.CT_SlideIdList`). Each entry is a `CT_SlideId`
|
|
365
|
+
with an `id` and an `rId`.
|
|
366
|
+
|
|
367
|
+
- **`id`** is the slide's `slide_id`, as in python-pptx, so recipes that
|
|
368
|
+
match the two find the slide. python-pptx's is an int (`p:sldId/@id`, from
|
|
369
|
+
256); this SDK's slide ids are the studio's strings, so `id` is that string.
|
|
370
|
+
- **`rId`** is never reused within the presentation, even after a delete or
|
|
371
|
+
a refresh, so an `rId` read earlier still names the same slide.
|
|
372
|
+
- **List edits order slides as lxml does:** `insert(i, entry)` puts the entry
|
|
373
|
+
before the slide now at `i` (counting the entry itself when it is still in
|
|
374
|
+
the list), or last when `i` is past the end, so on A B C D `insert(3, A)`
|
|
375
|
+
gives B C A D.
|
|
376
|
+
- **What differs from lxml:** the edits are not applied one line at a time.
|
|
377
|
+
- They collect in one pending change, and nothing reaches the studio until
|
|
378
|
+
the change is complete.
|
|
379
|
+
- It commits when no slide is detached, or before anything else reads slide
|
|
380
|
+
positions: the next command, a read of `prs.slides` or of a slide's index,
|
|
381
|
+
an explicit flush, a refresh, or the start of a `batch()` or `with prs:`
|
|
382
|
+
block. It sends the deletes first, then one `ReorderSlides`, so a command
|
|
383
|
+
built after the edit addresses slides by their new positions.
|
|
384
|
+
- The idle auto-flush timer never commits a change, because it could fire
|
|
385
|
+
between a `remove()` and its `insert()`.
|
|
386
|
+
- Because reading `prs.slides` commits it, inserting a removed entry after
|
|
387
|
+
other deck access raises: that slide is already deleted.
|
|
388
|
+
- A `batch()` or `with prs:` block that raises drops the edits made inside
|
|
389
|
+
it, and keeps those made before it.
|
|
390
|
+
- **Deletes** go through `Slides.delete`, so the bulk-delete guard applies. A
|
|
391
|
+
proxy of a deleted slide (`slide = prs.slides[i]` held from before) refuses
|
|
392
|
+
further edits with `ConflictError` instead of writing to the slide that took
|
|
393
|
+
its position.
|
|
394
|
+
- **Supported operations:** `len`, iteration, indexing, `index`, `remove`,
|
|
395
|
+
`del`, `insert`, `append`, `extend`, `getparent`, plus the entries' `id`,
|
|
396
|
+
`rId`, `get`, `addnext` and `addprevious`.
|
|
397
|
+
- **Refused operations:** anything else lxml offers (`xpath`, `set`, a new or
|
|
398
|
+
copied `p:sldId`) raises `UnsupportedAttributeError`. To add a slide use
|
|
399
|
+
`prs.slides.add_slide(layout)`; to copy one, `slide.clone(target_index)`.
|
|
400
|
+
- **`prs.part`:** its only working member is `drop_rel(rId)`. Other OPC members
|
|
401
|
+
raise.
|
|
402
|
+
|
|
403
|
+
### `shape._element` and `slide.shapes._spTree` — the shape delete, z-order and duplicate recipes (v0.17.0)
|
|
404
|
+
|
|
405
|
+
python-pptx has no public shape delete, z-order or shape copy, so the recipes
|
|
406
|
+
everyone copies edit the shape tree. They work here, line for line:
|
|
407
|
+
|
|
408
|
+
```python
|
|
409
|
+
sp = shape._element # delete a shape or a placeholder
|
|
410
|
+
sp.getparent().remove(sp)
|
|
411
|
+
shape.element.getparent().remove(shape.element)
|
|
412
|
+
|
|
413
|
+
spTree = slide.shapes._spTree
|
|
414
|
+
spTree.remove(el); spTree.insert(2, el) # send it to the back
|
|
415
|
+
spTree.append(el) # bring it to the front
|
|
416
|
+
slide.shapes[0]._element.addprevious(el) # put it below another shape
|
|
417
|
+
|
|
418
|
+
new_el = copy.deepcopy(shape._element) # duplicate it, on any slide
|
|
419
|
+
other_slide.shapes._spTree.insert_element_before(new_el, "p:extLst")
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
- **The elements** come from `pptx.oxml.shapes`, as in python-pptx:
|
|
423
|
+
- `shape._element` and `shape.element` (python-pptx's `BaseShape.element`)
|
|
424
|
+
are a `CT_Shape` (`p:sp`: autoshape, text box, freeform, placeholder; also
|
|
425
|
+
`shape._sp`), a `CT_Picture` (`_pic`), a `CT_GraphicalObjectFrame` (table,
|
|
426
|
+
chart, OLE object; `_graphicFrame`), a `CT_Connector` or a `CT_GroupShape`
|
|
427
|
+
(`_grpSp`). A connector has no `_cxnSp` and a picture no `_sp`, as in
|
|
428
|
+
python-pptx.
|
|
429
|
+
- `slide.shapes._spTree` (also `_element`, `_grpSp`, `element`) is the
|
|
430
|
+
slide's `p:spTree`: `p:nvGrpSpPr`, `p:grpSpPr`, then one element per shape,
|
|
431
|
+
back to front, so index 2 is the back. A group's tree is its own element:
|
|
432
|
+
`group.shapes._spTree is group._element`.
|
|
433
|
+
- **What it sends** (the public API's commands):
|
|
434
|
+
- removing an element deletes its shape (`Shape.delete`, `DeleteShape`)
|
|
435
|
+
unless it is put back first, which moves it;
|
|
436
|
+
- moves are `SetShapeZOrder` steps (`to_back`, `to_front`, `forward`,
|
|
437
|
+
`backward`) composed so the order is exactly the one the XML says,
|
|
438
|
+
including `spTree.insert(i, el)` of a shape still in the tree, which lxml
|
|
439
|
+
treats as "move it before the child at `i`";
|
|
440
|
+
- inserting a `copy.deepcopy()` (or `copy.copy()`) of an element clones its
|
|
441
|
+
shape (`Shape.clone`, `CloneShape` with no offset) where python-pptx's copy
|
|
442
|
+
lands: in place, or for a group member at its group coordinates (one
|
|
443
|
+
`SetTransform`), then z-order steps if it is not on top. A copy that is
|
|
444
|
+
never inserted sends nothing.
|
|
445
|
+
- **What differs from lxml:** as for `_sldIdLst`, the edits collect in one
|
|
446
|
+
pending change per presentation.
|
|
447
|
+
- It commits when no shape is left detached, or before the next command,
|
|
448
|
+
an explicit flush, `prs.refresh()`, the start of an outermost
|
|
449
|
+
`prs.batch()` or `with prs:` block, or a read of `slide.shapes` or
|
|
450
|
+
`slide.placeholders`. Reading `slide.shapes._spTree` does not commit, so
|
|
451
|
+
`slide.shapes._spTree.remove(el); slide.shapes._spTree.insert(2, el)` is
|
|
452
|
+
one move.
|
|
453
|
+
- It sends the clones first, then the deletes, then the z-order steps, so a
|
|
454
|
+
copy of a shape the same change deletes is cloned before the delete. A
|
|
455
|
+
held `_sldIdLst` change stays held while it sends, and edits on a slide
|
|
456
|
+
such a change deleted first go with the slide.
|
|
457
|
+
- The idle auto-flush timer never commits a change, and a batch or executor
|
|
458
|
+
block whose body raises drops the edits it held.
|
|
459
|
+
- Because reads commit, inserting a removed element after reading the
|
|
460
|
+
shapes raises: that shape is already deleted.
|
|
461
|
+
- A copy is cloned from its shape as the studio has it when inserted, so a
|
|
462
|
+
copy whose original changed after `deepcopy()` raises instead of
|
|
463
|
+
differing from python-pptx's copy.
|
|
464
|
+
- **Supported operations:** an element's `tag`, `getparent`, `getnext`,
|
|
465
|
+
`getprevious`, `addnext`, `addprevious`, `copy.deepcopy`, `shape_id`,
|
|
466
|
+
`shape_name`, `has_ph_elm`, and its read-only `p:cNvPr` (`sp.nvSpPr.cNvPr`,
|
|
467
|
+
any shape's `sp._nvXxPr.cNvPr`): `get`, `attrib`, `keys`, `values`, `items`,
|
|
468
|
+
`id`, `name`, for the id, name and hidden flag. A tree's `len`, iteration,
|
|
469
|
+
indexing, `index`, `remove`, `del`, `insert`, `append`, `extend`,
|
|
470
|
+
`insert_element_before`, `first_child_found_in`, `iterchildren`,
|
|
471
|
+
`getchildren`, `iter_shape_elms`, `iter_ph_elms`, `nvGrpSpPr` and
|
|
472
|
+
`grpSpPr`.
|
|
473
|
+
- **Ids:** `p:cNvPr/@id` is the studio's string shape id (`shape.shape_id`),
|
|
474
|
+
not python-pptx's integer. A copy gets a new id, so a recipe's id
|
|
475
|
+
renumbering (`cNvPr.id = ...`) is not needed and raises.
|
|
476
|
+
- **Refused operations** raise `UnsupportedAttributeError` naming the
|
|
477
|
+
supported API: property-level XML (`spPr`, `txBody`, `xfrm`), XML built
|
|
478
|
+
elsewhere (`parse_xml`, a new element), writes to `p:cNvPr`, `descr` and
|
|
479
|
+
`title` reads (the studio does not report them), moving a shape to another
|
|
480
|
+
slide or into another group, a copy into a group, and a notes slide's,
|
|
481
|
+
layout's or master's tree. Use `shape.text_frame`, `shape.fill` /
|
|
482
|
+
`shape.line`, `shape.delete()`, `shape.bring_to_front()` /
|
|
483
|
+
`send_to_back()` / `bring_forward()` / `send_backward()`,
|
|
484
|
+
`shape.clone(target_slide=...)` and `slide.shapes.add_group_shape([...])`.
|
|
485
|
+
|
|
486
|
+
### `paragraph._p`, `run._r`, `text_frame._bodyPr` — python-pptx's text XML recipes (v0.17.0)
|
|
487
|
+
|
|
488
|
+
python-pptx has no public API for bullets, hanging indents, superscript, letter
|
|
489
|
+
spacing or autofit details, so the recipes everyone copies edit the text's lxml
|
|
490
|
+
tree. They work here, line for line:
|
|
491
|
+
|
|
492
|
+
```python
|
|
493
|
+
from lxml import etree
|
|
494
|
+
from pptx.oxml.ns import qn
|
|
495
|
+
from pptx.oxml.xmlchemy import OxmlElement
|
|
496
|
+
|
|
497
|
+
pPr = paragraph._p.get_or_add_pPr()
|
|
498
|
+
pPr.set("marL", str(Emu(Pt(18)))) # hanging indent
|
|
499
|
+
pPr.set("indent", str(-Emu(Pt(18))))
|
|
500
|
+
pPr.insert(0, pPr.makeelement(qn("a:buChar"), {"char": "•"})) # bullet on
|
|
501
|
+
etree.SubElement(other_paragraph._p.get_or_add_pPr(), qn("a:buNone")) # off
|
|
502
|
+
buAutoNum = OxmlElement("a:buAutoNum") # numbered
|
|
503
|
+
buAutoNum.set("type", "arabicPeriod")
|
|
504
|
+
run.font._rPr.set("baseline", "30000") # superscript
|
|
505
|
+
run._r.get_or_add_rPr().set("spc", "300") # letter spacing (3pt)
|
|
506
|
+
etree.SubElement(text_frame._bodyPr, qn("a:normAutofit"))
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
`text_frame._txBody` (also `_element`), `text_frame._bodyPr`, `paragraph._p`
|
|
510
|
+
(`_element`, `_pPr`, `_defRPr`), `run._r` and `font._rPr` (`_element`) are real
|
|
511
|
+
lxml elements of python-pptx's classes (`pptx.oxml.text.CT_TextBody`,
|
|
512
|
+
`CT_TextParagraph`, ...), one tree per text frame, built from the deck, with
|
|
513
|
+
python-pptx's helpers (`get_or_add_pPr()`, `get_or_add_rPr()`,
|
|
514
|
+
`insert_element_before()`, `pPr.lvl`, `rPr.sz`, `bodyPr.autofit`, ...).
|
|
515
|
+
`run._rPr` is the run's `a:rPr` too (python-pptx spells it
|
|
516
|
+
`run._r.get_or_add_rPr()`). `pptx.oxml.parse_xml` and
|
|
517
|
+
`pptx.oxml.xmlchemy.OxmlElement` build loose elements of the same classes;
|
|
518
|
+
they reach the deck once attached to such a tree.
|
|
519
|
+
|
|
520
|
+
- **What differs from lxml:** an edit is not applied when its line runs.
|
|
521
|
+
- The edits collect, and the change is sent before the next command,
|
|
522
|
+
before an explicit flush (`flush()`, `refresh()`, a batch's end) and
|
|
523
|
+
before any public call on that text frame's paragraphs, runs and fonts.
|
|
524
|
+
The idle auto-flush never sends it. So a later API write wins
|
|
525
|
+
(`pPr.set("lvl", "2")` then `paragraph.level = 1` leaves level 1), and
|
|
526
|
+
`paragraph.alignment` reads an alignment the XML just set.
|
|
527
|
+
- A `prs.batch()` body (or an executor `with prs:` block) that raises
|
|
528
|
+
drops the XML edits made inside it, as it drops its commands; edits made
|
|
529
|
+
before it are sent when it starts.
|
|
530
|
+
- Requested again after that, the tree shows the deck's state (an API write
|
|
531
|
+
in between included), updated in place: elements a recipe kept stay
|
|
532
|
+
valid. An edit to a kept element after its change was sent is sent too,
|
|
533
|
+
before the next write on that text, the next request or the next
|
|
534
|
+
explicit flush.
|
|
535
|
+
- A change no command carries raises `UnsupportedAttributeError` when it
|
|
536
|
+
resolves (the line that resolved it, or the flush), naming the XML path
|
|
537
|
+
and the supported API. The change is dropped: the tree goes back to the
|
|
538
|
+
deck's state and nothing of it is sent.
|
|
539
|
+
- **What is sent** (through the setters the public API uses, so validation
|
|
540
|
+
and coalescing are the same):
|
|
541
|
+
- `a:pPr` as one `SetParagraphStyle`: `marL`, `indent`, `lvl`, `algn`
|
|
542
|
+
(`l` / `ctr` / `r` / `just`), `a:lnSpc` (`a:spcPct` or `a:spcPts`),
|
|
543
|
+
`a:spcBef` / `a:spcAft` (`a:spcPts`), `a:buClr` (`a:srgbClr`), and the
|
|
544
|
+
bullet: `a:buNone`, `a:buChar` with `•` (U+2022), `◦` (U+25E6) or `▪`
|
|
545
|
+
(U+25AA), `a:buAutoNum type="arabicPeriod"`. A bullet element added next
|
|
546
|
+
to an old one replaces it. Its `a:defRPr` is `paragraph.font`.
|
|
547
|
+
- `a:rPr` as `SetRunStyle`: `b`, `i`, `u`, `sz`, `strike` (`sngStrike` /
|
|
548
|
+
`noStrike`), `baseline`, `cap`, `spc`, `lang` (removing it clears the
|
|
549
|
+
language), `a:solidFill` (`a:srgbClr`, or `a:schemeClr` with python-pptx's
|
|
550
|
+
`a:lumMod` / `a:lumOff` brightness), `a:highlight`, `a:latin`.
|
|
551
|
+
`a:hlinkClick` carries a link of this text frame: a copied run keeps it.
|
|
552
|
+
- `a:bodyPr` as one `SetTextFrameProperties`: `wrap`, `anchor` (`t` /
|
|
553
|
+
`ctr` / `b`), `lIns` / `tIns` / `rIns` / `bIns` (removing one sets
|
|
554
|
+
python-pptx's default) and `a:noAutofit` / `a:normAutofit` /
|
|
555
|
+
`a:spAutoFit`.
|
|
556
|
+
- The text of an `a:r` (`SetRunText`); an `a:r` or `a:br` appended to a
|
|
557
|
+
paragraph and an `a:p` appended to the frame (`add_run()`,
|
|
558
|
+
`add_line_break()`, `add_paragraph()`, with their properties), and
|
|
559
|
+
trailing `a:r` / `a:p` removed (one frame rewrite, as `paragraph.clear()`).
|
|
560
|
+
- **Warns once:** an `a:buFont` with a text font (the studio draws its
|
|
561
|
+
bullets in the text's font), and a `baseline` other than python-pptx's
|
|
562
|
+
30000 / -25000 / 0 (the studio stores superscript / subscript at the
|
|
563
|
+
default offset).
|
|
564
|
+
- **Refused operations** (the studio's commands cannot carry them):
|
|
565
|
+
- any other bullet character, numbering scheme or `startAt`; bullet size
|
|
566
|
+
(`a:buSzPct` / `a:buSzPts`), `a:buSzTx`, `a:buClrTx`, `a:buFontTx`, a
|
|
567
|
+
symbol-font `a:buFont` and picture bullets; changing a paragraph's
|
|
568
|
+
imported bullet character or numbering;
|
|
569
|
+
- removing an attribute or element whose value the studio cannot clear
|
|
570
|
+
(`algn`, `marL`, `indent`, `b`, `sz`, a color, `a:latin`, the bullet, the
|
|
571
|
+
autofit, ...): set a value instead;
|
|
572
|
+
- spacing in percent for `a:spcBef` / `a:spcAft`, other `algn` values, tab
|
|
573
|
+
stops, an `a:normAutofit` `fontScale` / `lnSpcReduction`, other `a:bodyPr`
|
|
574
|
+
attributes and children (`vert`, `rot`, `numCol`, 3-D, ...), `a:ea` /
|
|
575
|
+
`a:cs` / `a:sym`, text outlines and effects, `a:endParaRPr`, `a:lstStyle`;
|
|
576
|
+
- inserting, moving or removing `a:p` / `a:r` / `a:br` other than at the end,
|
|
577
|
+
editing `a:fld` text fields, and editing an element of a paragraph or run
|
|
578
|
+
that `text_frame.text` / `paragraph.text` replaced since it was read.
|
|
579
|
+
- **Not mirrored:** the text of table cells (read only in `cell._tc`, see
|
|
580
|
+
below), charts and notes.
|
|
581
|
+
- **Lengths in attributes:** as in python-pptx, `str(Emu(...))` and
|
|
582
|
+
f-strings give the EMU integer the recipes write (`repr()` stays `Emu(...)`).
|
|
583
|
+
|
|
584
|
+
### `cell._tc`, `row._tr`, `table._tbl` — python-pptx's table XML recipes (v0.17.0)
|
|
585
|
+
|
|
586
|
+
python-pptx has no public API for cell borders or for deleting a row or a
|
|
587
|
+
column, so the recipes everyone copies edit the table's lxml tree. They work
|
|
588
|
+
here, line for line:
|
|
589
|
+
|
|
590
|
+
```python
|
|
591
|
+
from pptx.oxml.xmlchemy import OxmlElement
|
|
592
|
+
|
|
593
|
+
def SubElement(parent, tagname, **kwargs):
|
|
594
|
+
element = OxmlElement(tagname)
|
|
595
|
+
element.attrib.update(kwargs)
|
|
596
|
+
parent.append(element)
|
|
597
|
+
return element
|
|
598
|
+
|
|
599
|
+
tcPr = cell._tc.get_or_add_tcPr() # cell borders
|
|
600
|
+
for edge in ("a:lnL", "a:lnR", "a:lnT", "a:lnB"):
|
|
601
|
+
ln = SubElement(tcPr, edge, w=str(Pt(1)), cmpd="sng", algn="ctr")
|
|
602
|
+
fill = SubElement(ln, "a:solidFill")
|
|
603
|
+
SubElement(fill, "a:srgbClr", val="1F497D")
|
|
604
|
+
SubElement(ln, "a:prstDash", val="solid")
|
|
605
|
+
table.rows[1]._tr.h = Inches(0.8) # row height
|
|
606
|
+
table.columns[0]._gridCol.w = Inches(2.5) # column width
|
|
607
|
+
tbl = table._tbl
|
|
608
|
+
tbl.firstRow = False # look flags
|
|
609
|
+
tbl.remove(table.rows[2]._tr) # delete a row
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
`table._tbl` (also `table.rows._tbl` / `table.columns._tbl`), `cell._tc`,
|
|
613
|
+
`row._tr` and `column._gridCol` are real lxml elements of python-pptx's
|
|
614
|
+
classes (`pptx.oxml.table.CT_Table`, `CT_TableCell`, `CT_TableRow`,
|
|
615
|
+
`CT_TableCol`, ...), one tree per table, built from the deck, with python-pptx's
|
|
616
|
+
helpers (`get_or_add_tcPr()`, `tc.marL`, `tc.anchor`, `tbl.firstRow`,
|
|
617
|
+
`tcPr.get_or_change_to_solidFill()`, `tbl.tc(row, col)`, `tbl.iter_tcs()`, ...).
|
|
618
|
+
|
|
619
|
+
- **When it is sent:** as for the text XML above. The edits collect and are
|
|
620
|
+
sent before the next command, an explicit flush and any public call that
|
|
621
|
+
reads or writes what the XML can change (a cell's margins, anchor, fill and
|
|
622
|
+
borders, the row heights and column widths, the look flags), never by the
|
|
623
|
+
idle auto-flush; a failed `prs.batch()` or `with prs:` block drops the edits
|
|
624
|
+
made inside it. Walking the table (`table.cell(r, c)`, `table.rows`,
|
|
625
|
+
`row.cells`, a cell's text) does not send them unless rows or columns were
|
|
626
|
+
removed, so a recipe that edits every cell sends its edits together.
|
|
627
|
+
Requested again, the tree shows what the API changed since, updated in
|
|
628
|
+
place; a cell the recipe edited stays as it wrote it (a border's `cap`
|
|
629
|
+
included) until an API write on it, so the elements a recipe holds stay
|
|
630
|
+
valid. Rows the API added or removed leave the other rows' elements valid,
|
|
631
|
+
and an edit to a kept element lands on its cell even after rows were
|
|
632
|
+
inserted above it. A kept `table.rows[i]` / `table.columns[i]` follows its
|
|
633
|
+
row or column the same way (`row._tr`, `row.height`), and raises
|
|
634
|
+
`IndexError` once its row or column was removed.
|
|
635
|
+
- **What is sent** (through the setters the public API uses):
|
|
636
|
+
- a cell's `a:lnL` / `a:lnR` / `a:lnT` / `a:lnB` (`w`, `a:solidFill` with
|
|
637
|
+
an `a:srgbClr`, `a:noFill`, `a:prstDash`) as one `SetCellBorders` per
|
|
638
|
+
cell; removing an edge clears it (`cell.border_left.no_border()`), and
|
|
639
|
+
`cell.border_*` reads what the XML set (a later `width` or `dash_style`
|
|
640
|
+
write keeps an edge's `a:noFill` or theme color). On an uploaded table a
|
|
641
|
+
border's color may also be an `a:schemeClr`.
|
|
642
|
+
- `marL` / `marR` / `marT` / `marB` as one `SetCellMargins` per cell
|
|
643
|
+
(removing one resets it), `anchor` (`t` / `ctr` / `b`) as
|
|
644
|
+
`SetCellAnchor`, and the cell's fill as `SetTableCell`: an `a:solidFill`
|
|
645
|
+
holding an `a:srgbClr` (`cell.fill.fore_color.rgb`) or `a:noFill`
|
|
646
|
+
(`cell.fill.background()`);
|
|
647
|
+
- `a:tr/@h` as `SetRowHeight` and `a:gridCol/@w` as `SetColWidth`;
|
|
648
|
+
- the `a:tblPr` flags `firstRow` / `firstCol` / `lastRow` / `lastCol` /
|
|
649
|
+
`bandRow` / `bandCol` as one `SetTableLook`;
|
|
650
|
+
- an `a:tr` removed as `DeleteTableRow`, and a column removed (its
|
|
651
|
+
`a:gridCol` and its `a:tc` in every row) as `DeleteTableColumn`.
|
|
652
|
+
- **Accepted as the line the studio draws:** `cmpd="sng"`, `algn="ctr"`, the
|
|
653
|
+
line joins (`a:round`, `a:bevel`, `a:miter`) and arrow ends of type `none`.
|
|
654
|
+
A border's `cap` is not stored: it warns once.
|
|
655
|
+
- **Refused operations** (the studio's commands cannot carry them):
|
|
656
|
+
- the table style: an edit to `a:tblPr/a:tableStyleId`. The studio sets a
|
|
657
|
+
table's style when the table is added (`AddTable` / `AddLinkedTable`) and
|
|
658
|
+
has no command to change it; the error names what can change instead (the
|
|
659
|
+
look flags and each cell's fill and borders);
|
|
660
|
+
- other border properties (compound or inset lines, gradient lines, custom
|
|
661
|
+
dashes, arrowheads, a color with modifiers, a theme color on a table the
|
|
662
|
+
SDK added), diagonal borders, `vert`, `anchorCtr` and the other `a:tcPr`
|
|
663
|
+
attributes and children, gradient, picture, pattern and theme-color cell
|
|
664
|
+
fills, and removing a cell's fill (handing it back to the table style);
|
|
665
|
+
- merges (`gridSpan` / `rowSpan` / `hMerge` / `vMerge`: use `cell.merge()` /
|
|
666
|
+
`cell.split()`), a cell's text (use `cell.text` / `cell.text_frame`), rows
|
|
667
|
+
or columns added or moved through XML (use `table.rows.add()` /
|
|
668
|
+
`insert()`, `table.columns.add()`), a column removed from some rows only,
|
|
669
|
+
and removing every row or column;
|
|
670
|
+
- an edit to an element of a row, column or cell the API removed since it
|
|
671
|
+
was read.
|
|
672
|
+
- **Read only:** an uploaded table's tree also shows what its snapshot
|
|
673
|
+
records that no command sets (diagonal borders, `vert`, `anchorCtr`,
|
|
674
|
+
gradient fills); a cell's text is plain `a:p` / `a:r` / `a:t`, without run
|
|
675
|
+
properties.
|
|
676
|
+
|
|
340
677
|
### `NotesSlide.shapes` / `.placeholders` / `.notes_placeholder` / `.background` / `.name` — not yet exposed
|
|
341
678
|
|
|
342
679
|
Upstream's `NotesSlide` exposes a full shape tree (`notes_slide.shapes`,
|
|
@@ -541,6 +878,17 @@ paragraph.
|
|
|
541
878
|
- Assigning `None` to `font.name` / `font.italic` cannot clear a value this
|
|
542
879
|
session wrote (the wire format has no inherit marker): the cell keeps it and
|
|
543
880
|
the SDK emits a `UserWarning`. Assign `False` to turn italic off.
|
|
881
|
+
- The text frame's `vertical_anchor` and `margin_left` / `margin_right` /
|
|
882
|
+
`margin_top` / `margin_bottom` are the cell's own (`cell.vertical_anchor`,
|
|
883
|
+
`cell.margin_left`, ...) and send the same command (v0.17.0).
|
|
884
|
+
- `text_frame.word_wrap` / `auto_size` and the paragraph's `line_spacing` /
|
|
885
|
+
`space_before` / `space_after` / `level` exist as in python-pptx, but the
|
|
886
|
+
studio cannot apply them to a one-paragraph cell. A write warns once per
|
|
887
|
+
presentation (table above) instead of raising `AttributeError`, and the value
|
|
888
|
+
is kept for read-after-write. Otherwise an uploaded cell's paragraph reads
|
|
889
|
+
its own `a:pPr` as python-pptx does (`lvl`; `a:lnSpc` as a multiple or a
|
|
890
|
+
pitch; `a:spcBef` / `a:spcAft` in points, a percent reading `None`) until its
|
|
891
|
+
text is replaced, and other reads return `None` (`level` 0) (v0.17.0).
|
|
544
892
|
|
|
545
893
|
```python
|
|
546
894
|
cell.text = "Revenue"
|
|
@@ -2646,7 +2994,6 @@ python-pptx. Generated from `pptx._athena_extension.iter_athena_extensions()`.
|
|
|
2646
2994
|
| `pptx.kit.Kit` | class | 0.13.0 | Athena | Kit — slide composition helpers with overflow-safe, server-measured text. |
|
|
2647
2995
|
| `pptx.kit.Palette` | class | 0.13.0 | Athena | Palette — named hex colours the kit paints with (Athena defaults). |
|
|
2648
2996
|
| `pptx.kit.Region` | class | 0.13.0 | Athena | Region — EMU rectangle with inset / split helpers for slide composition. |
|
|
2649
|
-
| `pptx.oxml` | module | 0.9.2 | | pptx.oxml import-compatibility shim (REST-SDK only — no local XML tree). |
|
|
2650
2997
|
| `pptx.presentation.Presentation.asset_id` | property | 0.1.0 | | Presentation.asset_id — Athena asset id alias for deck_id. |
|
|
2651
2998
|
| `pptx.presentation.Presentation.batch` | method | 0.1.0 | | Presentation.batch — context manager for batched commands. |
|
|
2652
2999
|
| `pptx.presentation.Presentation.close` | method | 0.1.0 | | Presentation.close — flush pending commands and release resources. |
|