athena-python-pptx 0.17.0__tar.gz → 0.17.1__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.17.0 → athena_python_pptx-0.17.1}/CHANGELOG.md +29 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/PKG-INFO +1 -1
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/docs/API_PARITY_EXCEPTIONS.md +25 -11
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/__init__.py +1 -1
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/_unsupported.py +15 -6
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/commands.py +3 -2
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/_tbl.py +22 -14
- athena_python_pptx-0.17.1/pptx/oxml/shapes/autoshape.py +75 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/shapes/shared.py +23 -6
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/__init__.py +42 -30
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pyproject.toml +1 -1
- athena_python_pptx-0.17.0/pptx/oxml/shapes/autoshape.py +0 -32
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/.gitignore +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/AGENTS.md +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/API_PARITY_REPORT.md +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/CLAUDE.md +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/README.md +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/_citations.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/_references.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/action.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/batching.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/client.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/errors.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/exc.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/generated/__init__.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/generated/autoshape_types.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/kit.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/media.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/__init__.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/_mirror.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/_txbody.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/ns.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/presentation.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/shapes/__init__.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/shapes/connector.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/shapes/groupshape.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/shapes/picture.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/simpletypes.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/table.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/text.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/oxml/xmlchemy.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/package.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/presentation.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/shared.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/slide.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/slides.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/spec.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/table.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/text/__init__.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/types.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/typing.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/units.py +0 -0
- {athena_python_pptx-0.17.0 → athena_python_pptx-0.17.1}/pptx/util.py +0 -0
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.17.1
|
|
6
|
+
|
|
7
|
+
- `Table.table_style` returns the table's style GUID, the text of
|
|
8
|
+
`table._tbl`'s `a:tableStyleId`, which python-pptx code reads: an uploaded
|
|
9
|
+
table's from the deck, or the one `add_table()` / `add_linked_table()` gave a
|
|
10
|
+
table the SDK added; None for a table without one. It returned None for every
|
|
11
|
+
table (it read a snapshot field the studio never sends). It stays read-only:
|
|
12
|
+
the studio has no command that changes an existing table's style.
|
|
13
|
+
- `shape._element.txBody` (and `shape._sp.txBody`) of an autoshape, text box
|
|
14
|
+
or placeholder is its text frame's `_txBody`, the same lxml element
|
|
15
|
+
(`sp.txBody is shape.text_frame._txBody`, as in python-pptx), and
|
|
16
|
+
`sp.get_or_add_txBody()` returns it; it raised `UnsupportedAttributeError`.
|
|
17
|
+
Edits through it are held and sent as the text's commands, as edits through
|
|
18
|
+
`text_frame._txBody` are, so python-pptx's "for every shape, rewrite each
|
|
19
|
+
`a:t` of `shape._element.txBody`" recipe works. As in python-pptx it is None
|
|
20
|
+
for a picture's, graphic frame's, connector's or group's element, and for a
|
|
21
|
+
shape without a text frame.
|
|
22
|
+
- A table cell's cleared edge (`cell.border_left.no_border()`, or
|
|
23
|
+
`SetCellBorders` with `None`) reads in `cell._tc` as an `a:lnX` holding
|
|
24
|
+
`a:noFill` on every table, as the studio now stores and exports it (an
|
|
25
|
+
uploaded table's read as no `a:lnX`, which in PowerPoint is the table
|
|
26
|
+
style's line). A width or dash written after `no_border()` keeps the edge
|
|
27
|
+
without a line, as it does an `a:noFill` an XML recipe wrote, and an RGB
|
|
28
|
+
color draws it again (it sent a width-only edge). Removing a cell's `a:lnX`
|
|
29
|
+
through `cell._tc` raises
|
|
30
|
+
`UnsupportedAttributeError`: in python-pptx that hands the edge back to the
|
|
31
|
+
table style, which no studio command does (it cleared the edge before). For
|
|
32
|
+
no line, keep the `a:lnX` and give it an `a:noFill`, or call `no_border()`.
|
|
33
|
+
|
|
5
34
|
## 0.17.0
|
|
6
35
|
|
|
7
36
|
- python-pptx's slide delete and move recipes work. They edit
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.1
|
|
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
|
|
@@ -463,7 +463,10 @@ other_slide.shapes._spTree.insert_element_before(new_el, "p:extLst")
|
|
|
463
463
|
differing from python-pptx's copy.
|
|
464
464
|
- **Supported operations:** an element's `tag`, `getparent`, `getnext`,
|
|
465
465
|
`getprevious`, `addnext`, `addprevious`, `copy.deepcopy`, `shape_id`,
|
|
466
|
-
`shape_name`, `has_ph_elm`,
|
|
466
|
+
`shape_name`, `has_ph_elm`, `txBody` (a `p:sp`'s is its text frame's
|
|
467
|
+
`_txBody`, the same element, as in python-pptx, with `get_or_add_txBody()`;
|
|
468
|
+
the other elements' is None, as python-pptx answers; see the text XML
|
|
469
|
+
section below), and its read-only `p:cNvPr` (`sp.nvSpPr.cNvPr`,
|
|
467
470
|
any shape's `sp._nvXxPr.cNvPr`): `get`, `attrib`, `keys`, `values`, `items`,
|
|
468
471
|
`id`, `name`, for the id, name and hidden flag. A tree's `len`, iteration,
|
|
469
472
|
indexing, `index`, `remove`, `del`, `insert`, `append`, `extend`,
|
|
@@ -474,7 +477,7 @@ other_slide.shapes._spTree.insert_element_before(new_el, "p:extLst")
|
|
|
474
477
|
not python-pptx's integer. A copy gets a new id, so a recipe's id
|
|
475
478
|
renumbering (`cNvPr.id = ...`) is not needed and raises.
|
|
476
479
|
- **Refused operations** raise `UnsupportedAttributeError` naming the
|
|
477
|
-
supported API: property-level XML (`spPr`, `
|
|
480
|
+
supported API: property-level XML (`spPr`, `xfrm`), XML built
|
|
478
481
|
elsewhere (`parse_xml`, a new element), writes to `p:cNvPr`, `descr` and
|
|
479
482
|
`title` reads (the studio does not report them), moving a shape to another
|
|
480
483
|
slide or into another group, a copy into a group, and a notes slide's,
|
|
@@ -506,7 +509,8 @@ run._r.get_or_add_rPr().set("spc", "300") # letter spacing (3pt)
|
|
|
506
509
|
etree.SubElement(text_frame._bodyPr, qn("a:normAutofit"))
|
|
507
510
|
```
|
|
508
511
|
|
|
509
|
-
`text_frame._txBody` (also `_element
|
|
512
|
+
`text_frame._txBody` (also `_element`, and a `p:sp`'s `shape._element.txBody`),
|
|
513
|
+
`text_frame._bodyPr`, `paragraph._p`
|
|
510
514
|
(`_element`, `_pPr`, `_defRPr`), `run._r` and `font._rPr` (`_element`) are real
|
|
511
515
|
lxml elements of python-pptx's classes (`pptx.oxml.text.CT_TextBody`,
|
|
512
516
|
`CT_TextParagraph`, ...), one tree per text frame, built from the deck, with
|
|
@@ -635,7 +639,8 @@ helpers (`get_or_add_tcPr()`, `tc.marL`, `tc.anchor`, `tbl.firstRow`,
|
|
|
635
639
|
- **What is sent** (through the setters the public API uses):
|
|
636
640
|
- a cell's `a:lnL` / `a:lnR` / `a:lnT` / `a:lnB` (`w`, `a:solidFill` with
|
|
637
641
|
an `a:srgbClr`, `a:noFill`, `a:prstDash`) as one `SetCellBorders` per
|
|
638
|
-
cell;
|
|
642
|
+
cell; an edge with no line is an `a:lnX` holding `a:noFill`
|
|
643
|
+
(`cell.border_left.no_border()`), and
|
|
639
644
|
`cell.border_*` reads what the XML set (a later `width` or `dash_style`
|
|
640
645
|
write keeps an edge's `a:noFill` or theme color). On an uploaded table a
|
|
641
646
|
border's color may also be an `a:schemeClr`.
|
|
@@ -661,7 +666,10 @@ helpers (`get_or_add_tcPr()`, `tc.marL`, `tc.anchor`, `tbl.firstRow`,
|
|
|
661
666
|
dashes, arrowheads, a color with modifiers, a theme color on a table the
|
|
662
667
|
SDK added), diagonal borders, `vert`, `anchorCtr` and the other `a:tcPr`
|
|
663
668
|
attributes and children, gradient, picture, pattern and theme-color cell
|
|
664
|
-
fills,
|
|
669
|
+
fills, removing a cell's fill (handing it back to the table style), and
|
|
670
|
+
removing a cell's `a:lnL` / `a:lnR` / `a:lnT` / `a:lnB` (which hands that
|
|
671
|
+
edge back to the table style: the studio stores an edge as a line or as no
|
|
672
|
+
line, so for no line keep the `a:lnX` and give it an `a:noFill`);
|
|
665
673
|
- merges (`gridSpan` / `rowSpan` / `hMerge` / `vMerge`: use `cell.merge()` /
|
|
666
674
|
`cell.split()`), a cell's text (use `cell.text` / `cell.text_frame`), rows
|
|
667
675
|
or columns added or moved through XML (use `table.rows.add()` /
|
|
@@ -2008,7 +2016,7 @@ cell.border_top.color.rgb = RGBColor(0x1F, 0x49, 0x7D)
|
|
|
2008
2016
|
cell.border_top.width = Pt(1.5)
|
|
2009
2017
|
cell.border_top.dash_style = "solid"
|
|
2010
2018
|
|
|
2011
|
-
cell.border_bottom.no_border() #
|
|
2019
|
+
cell.border_bottom.no_border() # no line on the bottom edge
|
|
2012
2020
|
```
|
|
2013
2021
|
|
|
2014
2022
|
Each edge stores `colorHex`, `widthEmu`, `dashStyle` and the
|
|
@@ -2024,11 +2032,17 @@ indirectly.
|
|
|
2024
2032
|
|
|
2025
2033
|
## `Table.table_style` is read-only
|
|
2026
2034
|
|
|
2027
|
-
python-pptx
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2035
|
+
python-pptx has no `Table.table_style`: python-pptx code reads a table's
|
|
2036
|
+
style GUID from its XML, the text of `table._tbl.tblPr`'s
|
|
2037
|
+
`<a:tableStyleId>`. The SDK's read-only `Table.table_style` returns that
|
|
2038
|
+
same GUID: an uploaded table's style from the deck, or the one the studio
|
|
2039
|
+
gave a table the SDK added (`add_table()` uses python-pptx's default,
|
|
2040
|
+
`{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}`; `add_linked_table()` takes
|
|
2041
|
+
`table_style_id`). It is None for a table without a style, and
|
|
2042
|
+
`table._tbl` shows the same value. The studio has no command that changes
|
|
2043
|
+
an existing table's style, so assigning the property raises
|
|
2044
|
+
`AttributeError`, and an edit to `a:tableStyleId` through `table._tbl`
|
|
2045
|
+
raises `UnsupportedAttributeError`.
|
|
2032
2046
|
|
|
2033
2047
|
---
|
|
2034
2048
|
|
|
@@ -423,18 +423,27 @@ def keep_getter_error(obj: Any, name: str, error: UnsupportedAttributeError) ->
|
|
|
423
423
|
pass
|
|
424
424
|
|
|
425
425
|
|
|
426
|
+
def take_getter_error(obj: Any, name: str) -> UnsupportedAttributeError | None:
|
|
427
|
+
"""The error the getter of property ``name`` left on ``obj``
|
|
428
|
+
(:func:`keep_getter_error`), taken off it, for a guard's ``__getattr__`` to
|
|
429
|
+
raise; None when it left none. A class whose own ``__getattr__`` does this
|
|
430
|
+
flags it ``__athena_member_guard__``."""
|
|
431
|
+
try:
|
|
432
|
+
stashed = vars(obj).pop(_GETTER_ERROR, None)
|
|
433
|
+
except TypeError:
|
|
434
|
+
return None
|
|
435
|
+
return stashed[1] if stashed is not None and stashed[0] == name else None
|
|
436
|
+
|
|
437
|
+
|
|
426
438
|
def _make_guard(
|
|
427
439
|
cls: type, members: dict[str, str], guessed: dict[str, str]
|
|
428
440
|
) -> Callable[[Any, str], Any]:
|
|
429
441
|
own = cls.__dict__.get("__getattr__")
|
|
430
442
|
|
|
431
443
|
def __getattr__(self: Any, name: str) -> Any:
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
stashed = None
|
|
436
|
-
if stashed is not None and stashed[0] == name:
|
|
437
|
-
raise stashed[1]
|
|
444
|
+
stashed = take_getter_error(self, name)
|
|
445
|
+
if stashed is not None:
|
|
446
|
+
raise stashed
|
|
438
447
|
reason = members.get(name)
|
|
439
448
|
if reason is not None:
|
|
440
449
|
raise UnsupportedAttributeError(f"{type(self).__name__}.{name}", REASONS[reason])
|
|
@@ -1542,13 +1542,14 @@ class SetCellBorders(Command):
|
|
|
1542
1542
|
``"dashDot"`` / ``"lgDash"`` / ``"lgDashDot"`` / ``"lgDashDotDot"`` /
|
|
1543
1543
|
``"sysDash"`` / ``"sysDot"`` / ``"sysDashDot"`` / ``"sysDashDotDot"``)
|
|
1544
1544
|
|
|
1545
|
-
Passing ``None`` for an edge
|
|
1545
|
+
Passing ``None`` for an edge gives that edge no line (the studio stores
|
|
1546
|
+
and exports it as ``<a:lnX><a:noFill/></a:lnX>``).
|
|
1546
1547
|
|
|
1547
1548
|
Args:
|
|
1548
1549
|
shape_id: ID of the table shape
|
|
1549
1550
|
row: Row index (0-based)
|
|
1550
1551
|
col: Column index (0-based)
|
|
1551
|
-
border_left: Left-edge border spec, or ``None``
|
|
1552
|
+
border_left: Left-edge border spec, or ``None`` for no line
|
|
1552
1553
|
border_right: Right-edge border spec
|
|
1553
1554
|
border_top: Top-edge border spec
|
|
1554
1555
|
border_bottom: Bottom-edge border spec
|
|
@@ -8,7 +8,7 @@ commands the public API sends (:mod:`pptx.oxml._mirror` runs the lifecycle):
|
|
|
8
8
|
|
|
9
9
|
* ``a:tcPr`` borders ``a:lnL`` / ``a:lnR`` / ``a:lnT`` / ``a:lnB`` (``w``,
|
|
10
10
|
``a:solidFill``, ``a:noFill``, ``a:prstDash``) as one ``SetCellBorders`` per
|
|
11
|
-
cell;
|
|
11
|
+
cell; an edge with no line is an ``a:lnX`` holding ``a:noFill``;
|
|
12
12
|
* ``a:tcPr`` ``marL`` / ``marR`` / ``marT`` / ``marB`` as ``SetCellMargins``,
|
|
13
13
|
``anchor`` as ``SetCellAnchor``, and the cell's fill (an RGB ``a:solidFill``
|
|
14
14
|
or ``a:noFill``) as ``SetTableCell``;
|
|
@@ -18,8 +18,9 @@ commands the public API sends (:mod:`pptx.oxml._mirror` runs the lifecycle):
|
|
|
18
18
|
``a:gridCol`` and its ``a:tc`` in every row) as ``DeleteTableColumn``.
|
|
19
19
|
|
|
20
20
|
The studio has no command that changes an existing table's style, so an edit
|
|
21
|
-
to ``a:tblPr/a:tableStyleId`` raises, as does
|
|
22
|
-
(
|
|
21
|
+
to ``a:tblPr/a:tableStyleId`` raises, as does removing an edge's ``a:lnX``
|
|
22
|
+
(which hands the edge back to the table style) and anything else no command
|
|
23
|
+
carries (merges, rows or columns added through XML, the cells' text).
|
|
23
24
|
"""
|
|
24
25
|
|
|
25
26
|
from __future__ import annotations
|
|
@@ -165,6 +166,11 @@ _ADD = (
|
|
|
165
166
|
"column's formatting), and table.delete_row(i) / table.delete_column(i)."
|
|
166
167
|
)
|
|
167
168
|
_MERGE = "Merge and split cells with cell.merge(other_cell) / cell.split()."
|
|
169
|
+
_EDGE_REMOVED = (
|
|
170
|
+
"Removing a cell's a:lnX hands the edge back to the table style, and the studio has no "
|
|
171
|
+
"command for that: it stores an edge as a line or as no line. For no line, keep the "
|
|
172
|
+
"a:lnX and give it an a:noFill (cell.border_left.no_border())."
|
|
173
|
+
)
|
|
168
174
|
_TEXT = (
|
|
169
175
|
"A cell's text is not edited through XML here: use cell.text, or "
|
|
170
176
|
"cell.text_frame.paragraphs[0].runs[0].font for its formatting."
|
|
@@ -207,11 +213,12 @@ def _fill_type(cell: TableCell) -> str | None:
|
|
|
207
213
|
|
|
208
214
|
def _border_spec(cell: TableCell, edge: str, tag: str) -> dict[str, Any] | None:
|
|
209
215
|
"""An edge's border as the studio stores it (``widthEmu``, ``colorHex`` /
|
|
210
|
-
``schemeColor`` / ``noFill``, ``dashStyle``): what this session wrote
|
|
211
|
-
else the snapshot's; None when the cell has
|
|
216
|
+
``schemeColor`` / ``noFill``, ``dashStyle``): what this session wrote (an
|
|
217
|
+
edge it cleared has no line), else the snapshot's; None when the cell has
|
|
218
|
+
none of its own."""
|
|
212
219
|
written = cell._border_specs
|
|
213
220
|
if written is not None and edge in written:
|
|
214
|
-
return written[edge]
|
|
221
|
+
return written[edge] or {"noFill": True}
|
|
215
222
|
spec = (cell._snapshot_borders or {}).get(tag)
|
|
216
223
|
return spec if isinstance(spec, dict) and spec else None
|
|
217
224
|
|
|
@@ -295,8 +302,9 @@ def _render_tblPr(table: Table) -> Any:
|
|
|
295
302
|
for flag, name, _ in _LOOK:
|
|
296
303
|
if getattr(table, name):
|
|
297
304
|
tblPr.set(flag, "1")
|
|
298
|
-
|
|
299
|
-
|
|
305
|
+
style_id = table._style_id()
|
|
306
|
+
if style_id:
|
|
307
|
+
_sub(tblPr, "a:tableStyleId").text = style_id
|
|
300
308
|
return tblPr
|
|
301
309
|
|
|
302
310
|
|
|
@@ -922,6 +930,8 @@ class TableMirror(XmlMirror):
|
|
|
922
930
|
for edge, tag in _EDGES:
|
|
923
931
|
if base.edges.get(tag) == cur.edges.get(tag):
|
|
924
932
|
continue
|
|
933
|
+
if not cur.edges.get(tag):
|
|
934
|
+
raise unsupported(f"{tcpr_path}/a:{tag}", _EDGE_REMOVED)
|
|
925
935
|
spec, cap = self._edge_spec(cur.tcpr, tag, f"{tcpr_path}/a:{tag}")
|
|
926
936
|
edges[edge] = spec
|
|
927
937
|
warn_cap = warn_cap or cap
|
|
@@ -942,14 +952,12 @@ class TableMirror(XmlMirror):
|
|
|
942
952
|
steps.append(self._plan_fill(cell, cur.tcpr, tcpr_path))
|
|
943
953
|
return steps
|
|
944
954
|
|
|
945
|
-
def _edge_spec(self, tcPr: Any, tag: str, path: str) -> tuple[dict[str, Any]
|
|
946
|
-
"""An edge's ``a:lnX`` as the border spec
|
|
947
|
-
|
|
955
|
+
def _edge_spec(self, tcPr: Any, tag: str, path: str) -> tuple[dict[str, Any], bool]:
|
|
956
|
+
"""An edge's ``a:lnX`` (the cell has one) as the border spec
|
|
957
|
+
``SetCellBorders`` sends, and whether it set a line cap."""
|
|
948
958
|
lines = tcPr.findall(qn(f"a:{tag}")) if tcPr is not None else []
|
|
949
959
|
if len(lines) > 1:
|
|
950
960
|
raise unsupported(path, f"Keep one a:{tag} in an a:tcPr.")
|
|
951
|
-
if not lines:
|
|
952
|
-
return None, False
|
|
953
961
|
return _line_spec(lines[0], path, scheme_colors=self._table._source in _uploaded_sources())
|
|
954
962
|
|
|
955
963
|
def _plan_fill(self, cell: TableCell, tcPr: Any, path: str) -> Callable[[], None]:
|
|
@@ -1078,7 +1086,7 @@ def _line_spec(line: Any, path: str, *, scheme_colors: bool) -> tuple[dict[str,
|
|
|
1078
1086
|
if not spec:
|
|
1079
1087
|
raise unsupported(
|
|
1080
1088
|
path,
|
|
1081
|
-
"A border sets a width (w), a fill or a:prstDash;
|
|
1089
|
+
"A border sets a width (w), a fill or a:prstDash; for no line put an a:noFill in it "
|
|
1082
1090
|
"(cell.border_left.no_border()).",
|
|
1083
1091
|
)
|
|
1084
1092
|
return spec, cap
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""``p:sp``, python-pptx's ``pptx.oxml.shapes.autoshape``.
|
|
2
|
+
|
|
3
|
+
The element of an autoshape, a text box, a freeform or a placeholder:
|
|
4
|
+
``shape._element`` / ``shape._sp``. See :mod:`pptx.oxml.shapes.shared` for what
|
|
5
|
+
it mirrors. Its ``p:txBody`` is the shape's text frame's: ``sp.txBody`` is
|
|
6
|
+
``shape.text_frame._txBody`` (:mod:`pptx.oxml._txbody`), so what a recipe changes
|
|
7
|
+
in it is held and sent as that text's commands.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ..._unsupported import keep_getter_errors
|
|
15
|
+
from ...errors import UnsupportedAttributeError
|
|
16
|
+
from ..ns import qn
|
|
17
|
+
from .shared import BaseShapeElement, _NonVisualProps
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CT_ShapeNonVisual(_NonVisualProps):
|
|
21
|
+
"""``p:nvSpPr``: the shape's non-visual properties (its ``cNvPr``)."""
|
|
22
|
+
|
|
23
|
+
_tag = qn("p:nvSpPr")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CT_Shape(BaseShapeElement):
|
|
27
|
+
"""``p:sp``: an autoshape, text box, freeform or placeholder. Its
|
|
28
|
+
``p:nvSpPr`` (the id and name) and its ``p:txBody`` (the text frame's) are
|
|
29
|
+
mirrored; its ``p:spPr`` and ``p:style`` are not."""
|
|
30
|
+
|
|
31
|
+
_tag = qn("p:sp")
|
|
32
|
+
_nv_class = CT_ShapeNonVisual
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def nvSpPr(self) -> CT_ShapeNonVisual:
|
|
36
|
+
"""The ``p:nvSpPr`` child."""
|
|
37
|
+
return self._nvXxPr # type: ignore[return-value]
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
@keep_getter_errors
|
|
41
|
+
def txBody(self) -> Any:
|
|
42
|
+
"""The ``p:txBody`` child: the shape's text frame's ``_txBody``, the same
|
|
43
|
+
element (``sp.txBody is shape.text_frame._txBody``, as in python-pptx).
|
|
44
|
+
What a recipe changes in it is held and sent before the next command, an
|
|
45
|
+
explicit flush or an API call on that text, and it shows the text's
|
|
46
|
+
current state when requested again. None for a shape without a text frame,
|
|
47
|
+
as python-pptx answers for a ``p:sp`` without a ``p:txBody``."""
|
|
48
|
+
shape = self._shape
|
|
49
|
+
if shape is None:
|
|
50
|
+
raise UnsupportedAttributeError(
|
|
51
|
+
"CT_Shape.txBody",
|
|
52
|
+
"this is a copy (copy.deepcopy()) that is not in a shape tree yet, and the "
|
|
53
|
+
"studio clones a shape with the text its original has when the copy is "
|
|
54
|
+
"inserted: insert the copy, then edit the text of the shape it becomes "
|
|
55
|
+
"(slide.shapes[-1].text_frame).",
|
|
56
|
+
)
|
|
57
|
+
if not shape.has_text_frame:
|
|
58
|
+
return None
|
|
59
|
+
return shape.text_frame._txBody
|
|
60
|
+
|
|
61
|
+
def get_or_add_txBody(self) -> Any:
|
|
62
|
+
"""The ``p:txBody`` (:attr:`txBody`). python-pptx adds one to a ``p:sp``
|
|
63
|
+
without one; here every shape that can hold text has its text frame's,
|
|
64
|
+
and no command gives one to a shape that cannot."""
|
|
65
|
+
txBody = self.txBody
|
|
66
|
+
if txBody is None:
|
|
67
|
+
raise UnsupportedAttributeError(
|
|
68
|
+
"CT_Shape.get_or_add_txBody",
|
|
69
|
+
"this shape has no text frame (shape.has_text_frame is False), and no studio "
|
|
70
|
+
"command gives it one: put the text in a text box or an autoshape.",
|
|
71
|
+
)
|
|
72
|
+
return txBody
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
__all__ = ["CT_Shape", "CT_ShapeNonVisual"]
|
|
@@ -6,7 +6,8 @@ what python-pptx's delete, z-order and duplicate recipes do with one: read its
|
|
|
6
6
|
``tag``, find it in its tree (``getparent()``, ``getnext()``, ``getprevious()``),
|
|
7
7
|
place another shape beside it (``addnext()``, ``addprevious()``) and copy it
|
|
8
8
|
(``copy.deepcopy()``). Its non-visual properties (``sp.nvSpPr.cNvPr``) give the
|
|
9
|
-
shape's id and name, read-only.
|
|
9
|
+
shape's id and name, read-only. A ``p:sp``'s ``p:txBody`` is its text frame's
|
|
10
|
+
(:class:`~pptx.oxml.shapes.autoshape.CT_Shape`).
|
|
10
11
|
|
|
11
12
|
Edits go through :class:`_ShapeTreeEdit`, one per presentation, which turns
|
|
12
13
|
them into studio commands once they are complete (see
|
|
@@ -20,6 +21,7 @@ import contextlib
|
|
|
20
21
|
from collections.abc import Iterator, Mapping
|
|
21
22
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
22
23
|
|
|
24
|
+
from ..._unsupported import take_getter_error
|
|
23
25
|
from ...errors import UnsupportedAttributeError
|
|
24
26
|
from ..ns import qn
|
|
25
27
|
|
|
@@ -32,8 +34,9 @@ _MIRRORED: str = (
|
|
|
32
34
|
"athena-python-pptx mirrors the shape-tree XML python-pptx's delete, z-order and "
|
|
33
35
|
"duplicate recipes use: an element's tag, getparent(), getnext() / getprevious(), "
|
|
34
36
|
"addnext() / addprevious() and copy.deepcopy(); the read-only p:cNvPr id and name "
|
|
35
|
-
"(sp.nvSpPr.cNvPr.get('name'));
|
|
36
|
-
"
|
|
37
|
+
"(sp.nvSpPr.cNvPr.get('name')); a p:sp's p:txBody (sp.txBody, its text frame's "
|
|
38
|
+
"_txBody); and the shape tree's len, iteration, indexing, index, remove, del, "
|
|
39
|
+
"insert, append, extend and insert_element_before."
|
|
37
40
|
)
|
|
38
41
|
_SHAPE_API: str = (
|
|
39
42
|
"For the rest use the shape API: shape.text_frame for text, shape.fill / shape.line "
|
|
@@ -55,9 +58,9 @@ _PROPERTY_HINTS: dict[str, str] = {
|
|
|
55
58
|
"Its a:xfrm is not mirrored: use shape.left / top / width / height, rotation, "
|
|
56
59
|
"flip_h and flip_v.",
|
|
57
60
|
),
|
|
58
|
-
|
|
59
|
-
(
|
|
60
|
-
"
|
|
61
|
+
"get_or_add_txBody": (
|
|
62
|
+
"Only a p:sp (an autoshape, text box or placeholder) holds a p:txBody; there "
|
|
63
|
+
"sp.txBody is its text frame's (shape.text_frame._txBody)."
|
|
61
64
|
),
|
|
62
65
|
**dict.fromkeys(
|
|
63
66
|
("blipFill", "blip_rId", "srcRect_l", "srcRect_t", "srcRect_r", "srcRect_b"),
|
|
@@ -369,6 +372,13 @@ class BaseShapeElement:
|
|
|
369
372
|
"""True for a placeholder (a ``p:sp`` / ``p:pic`` / ``p:graphicFrame`` with ``p:ph``)."""
|
|
370
373
|
return bool(self._source.is_placeholder)
|
|
371
374
|
|
|
375
|
+
@property
|
|
376
|
+
def txBody(self) -> Any:
|
|
377
|
+
"""The ``p:txBody`` child: None, as python-pptx answers for every shape
|
|
378
|
+
element but a ``p:sp``, the only one that holds one (there it is the
|
|
379
|
+
text frame's: :class:`~pptx.oxml.shapes.autoshape.CT_Shape`)."""
|
|
380
|
+
return None
|
|
381
|
+
|
|
372
382
|
# -- its place in a tree -------------------------------------------------
|
|
373
383
|
|
|
374
384
|
def _edit(self) -> _ShapeTreeEdit:
|
|
@@ -514,8 +524,15 @@ class BaseShapeElement:
|
|
|
514
524
|
def __getattr__(self, name: str) -> Any:
|
|
515
525
|
if name.startswith("__"):
|
|
516
526
|
raise AttributeError(name)
|
|
527
|
+
# The explanation a property getter raised (keep_getter_errors), which
|
|
528
|
+
# Python would otherwise replace with this lookup's.
|
|
529
|
+
kept = take_getter_error(self, name)
|
|
530
|
+
if kept is not None:
|
|
531
|
+
raise kept
|
|
517
532
|
raise unsupported(f"{type(self).__name__}.{name}", name)
|
|
518
533
|
|
|
534
|
+
__getattr__.__athena_member_guard__ = True # type: ignore[attr-defined]
|
|
535
|
+
|
|
519
536
|
def __repr__(self) -> str:
|
|
520
537
|
return f"<{type(self).__name__} '<{nsptag(self.tag)}>' at 0x{id(self):x}>"
|
|
521
538
|
|
|
@@ -13703,19 +13703,17 @@ _CELL_EDGE_FIELDS: tuple[tuple[str, str], ...] = (
|
|
|
13703
13703
|
)
|
|
13704
13704
|
|
|
13705
13705
|
|
|
13706
|
-
def _snapshot_cell_borders(
|
|
13707
|
-
data: dict[str, Any], tcpr: dict[str, Any], *, uploaded: bool
|
|
13708
|
-
) -> Optional[dict[str, Any]]:
|
|
13706
|
+
def _snapshot_cell_borders(data: dict[str, Any], tcpr: dict[str, Any]) -> Optional[dict[str, Any]]:
|
|
13709
13707
|
"""The cell's own borders as the snapshot records them, keyed by the
|
|
13710
13708
|
``a:tcPr`` child each is (``lnL`` ... ``lnB``, the diagonals), for
|
|
13711
13709
|
``cell._tc``.
|
|
13712
13710
|
|
|
13713
13711
|
The edges are the cell's ``border*`` fields: ingest sets them from the
|
|
13714
|
-
cell's own ``a:lnX`` and a SetCellBorders write sets its edge
|
|
13715
|
-
one to ``noFill``, which
|
|
13716
|
-
|
|
13717
|
-
|
|
13718
|
-
|
|
13712
|
+
cell's own ``a:lnX`` and a SetCellBorders write sets its edge, a cleared
|
|
13713
|
+
one to ``noFill``, which the export writes as ``<a:lnX><a:noFill/></a:lnX>``
|
|
13714
|
+
(``tcPr.borders`` holds it as ``noFill`` too, or as null in a deck edited
|
|
13715
|
+
before the studio stored it so). The diagonals ride on ``tcPr.borders``
|
|
13716
|
+
only.
|
|
13719
13717
|
"""
|
|
13720
13718
|
borders: dict[str, Any] = {
|
|
13721
13719
|
tag: data[field]
|
|
@@ -13727,8 +13725,6 @@ def _snapshot_cell_borders(
|
|
|
13727
13725
|
for tag, spec in written.items():
|
|
13728
13726
|
if tag in ("lnTlToBr", "lnBlToTr") and isinstance(spec, dict) and spec:
|
|
13729
13727
|
borders[tag] = spec
|
|
13730
|
-
elif uploaded and spec is None and tag in borders:
|
|
13731
|
-
del borders[tag]
|
|
13732
13728
|
return borders or None
|
|
13733
13729
|
|
|
13734
13730
|
|
|
@@ -13809,7 +13805,7 @@ def _hydrate_table_cell(cell: "TableCell", data: dict[str, Any], *, uploaded: bo
|
|
|
13809
13805
|
elif isinstance(fill, dict) and fill.get("type") == "complexFill":
|
|
13810
13806
|
cell._snapshot_fill_type = _CELL_COMPLEX_FILL_KINDS.get(fill.get("kind"), "gradient")
|
|
13811
13807
|
# What cell._tc shows of the rest of the cell's own a:tcPr.
|
|
13812
|
-
cell._snapshot_borders = _snapshot_cell_borders(data, tcpr
|
|
13808
|
+
cell._snapshot_borders = _snapshot_cell_borders(data, tcpr)
|
|
13813
13809
|
cell._snapshot_tcpr_attrs = _snapshot_tcpr_attrs(tcpr)
|
|
13814
13810
|
# A link written with SetTableCell.hyperlinkUrl is kept on the cell (the
|
|
13815
13811
|
# export puts it on the runs); an uploaded one is on its run.
|
|
@@ -14496,7 +14492,7 @@ class _CellBorder:
|
|
|
14496
14492
|
server applier stores in ``cell.tcPr.borders`` and the export-worker
|
|
14497
14493
|
writes to ``<a:lnL>`` / ``<a:lnR>`` / ``<a:lnT>`` / ``<a:lnB>``).
|
|
14498
14494
|
|
|
14499
|
-
Calling :meth:`no_border`
|
|
14495
|
+
Calling :meth:`no_border` gives the edge no line.
|
|
14500
14496
|
"""
|
|
14501
14497
|
|
|
14502
14498
|
_DASH_STYLES = {
|
|
@@ -14549,12 +14545,15 @@ class _CellBorder:
|
|
|
14549
14545
|
self._emit()
|
|
14550
14546
|
|
|
14551
14547
|
def no_border(self) -> None:
|
|
14552
|
-
"""
|
|
14548
|
+
"""Give this edge no line: the studio draws none and exports the edge as
|
|
14549
|
+
``<a:lnX><a:noFill/></a:lnX>``, so the table style's line does not show
|
|
14550
|
+
there either. A later width or dash write keeps it without a line, as
|
|
14551
|
+
it does an ``a:noFill`` an XML recipe wrote; an RGB color draws it."""
|
|
14553
14552
|
self._color_hex = None
|
|
14554
14553
|
self._width_emu = None
|
|
14555
14554
|
self._dash_style = None
|
|
14556
14555
|
self._scheme_color = None
|
|
14557
|
-
self._no_fill =
|
|
14556
|
+
self._no_fill = True
|
|
14558
14557
|
self._cell._emit_border_change(self._edge, None)
|
|
14559
14558
|
|
|
14560
14559
|
def _emit(self) -> None:
|
|
@@ -15159,10 +15158,11 @@ class TableCell:
|
|
|
15159
15158
|
if existing is not None:
|
|
15160
15159
|
return existing
|
|
15161
15160
|
adapter = _CellBorder(cell=self, edge=edge)
|
|
15162
|
-
# A border this session sent through cell._tc
|
|
15163
|
-
|
|
15164
|
-
|
|
15165
|
-
|
|
15161
|
+
# A border this session sent (through cell._tc or a cleared edge's
|
|
15162
|
+
# no line) reads back here.
|
|
15163
|
+
specs = self._border_specs or {}
|
|
15164
|
+
if edge in specs:
|
|
15165
|
+
_adopt_border_spec(adapter, specs[edge] or {"noFill": True})
|
|
15166
15166
|
setattr(self, cache_attr, adapter)
|
|
15167
15167
|
return adapter
|
|
15168
15168
|
|
|
@@ -15191,14 +15191,15 @@ class TableCell:
|
|
|
15191
15191
|
``"top"`` / ``"bottom"`` -> border spec; None clears the edge).
|
|
15192
15192
|
|
|
15193
15193
|
Each edge's spec is kept for ``cell._tc`` and for its ``border_*``
|
|
15194
|
-
adapter, so a border sent through either reads back through both.
|
|
15194
|
+
adapter, so a border sent through either reads back through both. A
|
|
15195
|
+
cleared edge is one with no line (the studio stores it so).
|
|
15195
15196
|
"""
|
|
15196
15197
|
specs = dict(self._border_specs or {})
|
|
15197
15198
|
for edge, spec in edges.items():
|
|
15198
15199
|
specs[edge] = dict(spec) if spec else None
|
|
15199
15200
|
adapter = getattr(self, f"_border_{edge}_cached", None)
|
|
15200
15201
|
if adapter is not None:
|
|
15201
|
-
_adopt_border_spec(adapter, spec or {})
|
|
15202
|
+
_adopt_border_spec(adapter, spec or {"noFill": True})
|
|
15202
15203
|
self._border_specs = specs
|
|
15203
15204
|
if self._table._buffer is None:
|
|
15204
15205
|
return
|
|
@@ -17439,16 +17440,27 @@ class Table(Shape):
|
|
|
17439
17440
|
|
|
17440
17441
|
@property
|
|
17441
17442
|
def table_style(self) -> Optional[str]:
|
|
17442
|
-
"""
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
``
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17443
|
+
"""The table style's GUID, the text of the table's ``a:tableStyleId``;
|
|
17444
|
+
None for a table without one.
|
|
17445
|
+
|
|
17446
|
+
python-pptx has no such property: its code reads the GUID from the
|
|
17447
|
+
table's XML (``table._tbl``'s ``a:tblPr``), which shows the same value.
|
|
17448
|
+
It is the style the deck's table has (an uploaded table's, from the
|
|
17449
|
+
deck), or the one ``add_table()`` / ``add_linked_table()`` gave a new
|
|
17450
|
+
table. Read-only: the studio sets a table's style when the table is
|
|
17451
|
+
added and has no command that changes it.
|
|
17452
|
+
"""
|
|
17453
|
+
return self._style_id()
|
|
17454
|
+
|
|
17455
|
+
def _style_id(self) -> Optional[str]:
|
|
17456
|
+
"""The ``a:tableStyleId`` GUID, or None: the one the deck's table data or
|
|
17457
|
+
the add command gave this table ("" is none), else, for a table whose
|
|
17458
|
+
data names none, the snapshot properties' ``tableStyleId``."""
|
|
17459
|
+
style_id = self._table_style_id
|
|
17460
|
+
if style_id is None:
|
|
17461
|
+
stored = self._properties.get("tableStyleId")
|
|
17462
|
+
style_id = stored if isinstance(stored, str) else None
|
|
17463
|
+
return style_id or None
|
|
17452
17464
|
|
|
17453
17465
|
def _emit_look_change(self) -> None:
|
|
17454
17466
|
"""Emit a SetTableLook command with the current look flag values."""
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-pptx"
|
|
7
|
-
version = "0.17.
|
|
7
|
+
version = "0.17.1"
|
|
8
8
|
description = "Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"""``p:sp``, python-pptx's ``pptx.oxml.shapes.autoshape``.
|
|
2
|
-
|
|
3
|
-
The element of an autoshape, a text box, a freeform or a placeholder:
|
|
4
|
-
``shape._element`` / ``shape._sp``. See :mod:`pptx.oxml.shapes.shared` for what
|
|
5
|
-
it mirrors.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
from ..ns import qn
|
|
11
|
-
from .shared import BaseShapeElement, _NonVisualProps
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class CT_ShapeNonVisual(_NonVisualProps):
|
|
15
|
-
"""``p:nvSpPr``: the shape's non-visual properties (its ``cNvPr``)."""
|
|
16
|
-
|
|
17
|
-
_tag = qn("p:nvSpPr")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class CT_Shape(BaseShapeElement):
|
|
21
|
-
"""``p:sp``: an autoshape, text box, freeform or placeholder."""
|
|
22
|
-
|
|
23
|
-
_tag = qn("p:sp")
|
|
24
|
-
_nv_class = CT_ShapeNonVisual
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def nvSpPr(self) -> CT_ShapeNonVisual:
|
|
28
|
-
"""The ``p:nvSpPr`` child."""
|
|
29
|
-
return self._nvXxPr # type: ignore[return-value]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
__all__ = ["CT_Shape", "CT_ShapeNonVisual"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|