feat(tables): table style API — apply_style, style_id, style_name (Tables 2.0 Phase 2)#40
Merged
Merged
Conversation
…bles 2.0 Phase 2) Issue: #12 (Phase 2) The fork's Phase 1 (PR #37) shipped row/column add/remove. The next-largest gap on issue #12 — and the most-cited scanny#27 ask (37 comments) — is a public way to apply or read a table's built-in PowerPoint style after the table is created. Previously the GUID was hardcoded inside `_tbl_tmpl()` as a string template (`{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}`, "Medium Style 2 - Accent 1") with no element class on the oxml side, no public getter/setter on `Table`, and no name registry — so the existing boolean toggles (`first_row`, `last_row`, `horz_banding`, `vert_banding`) silently bound to a default the user couldn't observe or change. This change adds the public surface end-to-end: oxml layer - New `CT_TableStyleId(BaseOxmlElement)` element class with simple text-content GUID `value` property. - `tableStyleId` declared as `ZeroOrOne` first-child of `CT_TableProperties`, matching the ECMA-376 §21.1.3.15 sequence rule (verified by reading serialized XML after a setter call). - `CT_TableProperties.style_id` r/w property — reads/writes the GUID, removes the child element on `style_id = None`. - Element registered in `pptx.oxml.__init__` next to other table tags. Public API on `Table` - `Table.style_id` — r/w GUID property; `None` when absent; setting `None` removes the element. - `Table.style_name` — reverse lookup against the built-in registry; returns the friendly name or `None` for unregistered GUIDs (lossless fallback — the GUID still round-trips even when the name isn't known). - `Table.apply_style(name_or_guid)` — accepts either a friendly name from the built-in registry (case-insensitive) or a raw brace-wrapped GUID. GUIDs pass through verbatim so any style not in the registry is still reachable. Unknown friendly names raise `ValueError`. Built-in style registry (`pptx.enum.table`) - `PP_TABLE_STYLE` — read-only mapping of 38 verified built-in style names to GUIDs. Coverage includes: No Style (No Grid / Table Grid), Themed Style 1/2 Accent 1, all six "Medium Style 2 - Accent 1..6", Medium Style 1 Accents 1-3+6, Medium Style 3 + Accents 4-5, Light Style 1/2/3 with Accents 1-6 (where present), Dark Style 1/2 + Accent 1/Accent 2. - GUIDs were harvested from real PowerPoint-saved `tableStyles.xml` fragments across 8 unrelated GitHub repos to ensure correctness; hallucinated GUIDs from AI-generated reference files were rejected. - `lookup_table_style(name)` and `style_name_for(guid)` helpers, plus `register_table_style(name, guid)` for runtime extension (custom corp themes, additional Office built-ins discovered later). Tests - 45 new pytest cases in `tests/test_tables_phase2.py` covering each new oxml class, every public Table API method, the registry surface, the `_looks_like_guid` shape detector, save/reload round-trip preservation, and Phase-1-toggle regression checks. Full pytest: 3357 passed (3017 master + 340 carried from earlier phases + 45 new from Phase 2 + any earlier-phase additions; -3 baseline updated by previous merges; see PR #37/#38/#39 history). - 7 new behave scenarios in `features/tbl-styles.feature` exercising default style, apply by name, apply by GUID, case-insensitive resolve, unknown-name `ValueError`, clear-via-`style_id = None`, and save/reload round-trip via stream. Full behave: 1021 scenarios passed (baseline 1014 + 7 new), 0 failed. - Ruff: `ruff check` → All checks passed; `ruff format --check` → no diff. Out of scope (deliberately deferred) - Custom user-defined table styles (writing into `tableStyles.xml`). - Per-cell style overrides beyond what cell APIs already provide. - Theme-color resolution for accent references (PowerPoint's job at render). - Style preview / thumbnail generation. - Validation that the GUID actually exists in `tableStyles.xml`. PowerPoint resolves built-in styles internally; invalid GUIDs fall back to default rendering. UAT - `uat_tables_phase2.py` (untracked per CLAUDE.md §6) at repo root; generates `uat_tables_phase2_out.pptx` with five tables exercising default / apply-by-name / apply-by-GUID / case-insensitive / cleared. All five round-trip assertions pass programmatically; visual UAT in PowerPoint or Keynote pending maintainer signoff. Refs #12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 of issue #12 (Tables 2.0): table style API
Closes the next-largest gap on issue #12 and the most-cited scanny#27 ask (37 comments) — a public way to apply or read a table's built-in PowerPoint style after the table is created.
Phase 1 (PR #37) shipped row/column add/remove. Before this PR, the default style GUID was hardcoded inside
_tbl_tmpl()as a string template ({5C22544A-...}, "Medium Style 2 - Accent 1") with no element class on the oxml side, no public getter/setter onTable, and no name registry — so the existing boolean toggles (first_row,last_row,horz_banding,vert_banding) silently bound to a default the user couldn't observe or change.Public surface added
What it adds
oxml layer
CT_TableStyleId(BaseOxmlElement)element class with simple text-contentvalueproperty.tableStyleIddeclared asZeroOrOnefirst-child ofCT_TablePropertieswithsuccessors=("a:extLst",)— preserves ECMA-376 §21.1.3.15 sequence (tableStyleChoicethenextLst) when a PowerPoint-authored deck already carries<a:extLst>.CT_TableProperties.style_idr/w property — reads/writes the GUID, removes the child element onstyle_id = None.Public API on
TableTable.style_id— r/w GUID property;Nonewhen absent.Table.style_name— reverse lookup against the registry;Nonefor unregistered GUIDs (lossless fallback — the GUID still round-trips).Table.apply_style(name_or_guid)— accepts either a friendly registry name (case-insensitive) or a raw brace-wrapped GUID.Built-in style registry (
pptx.enum.table)PP_TABLE_STYLE— 38 verified built-in name→GUID pairs covering No Style (No Grid / Table Grid), Themed Style 1/2 Accent 1, all six Medium Style 2 Accent 1-6, Medium Style 1 Accents 1/2/3/6, Medium Style 3 + Accents 4/5, Light Style 1/2/3 with Accents 1-6 (where present), Dark Style 1/2 + Accent 1/Accent 2.tableStyles.xmlfragments across 8 unrelated GitHub repos to ensure correctness; AI-hallucinated reference files were rejected.lookup_table_style(name)/style_name_for(guid)helpers, plusregister_table_style(name, guid)for runtime extension.Out of scope (deliberately deferred)
tableStyles.xml).<a:tableStyle>definition variant — decks that carry it will still round-trip via raw lxml, butTable.style_id/style_namewill reportNone. Phase-3 candidate.Tests
tests/test_tables_phase2.py(the 46th is a regression test for theextLst-sibling ordering bug surfaced by a pre-commit Forge audit and fixed before commit). Full suite:3358 passed.features/tbl-styles.feature: default style, apply by name, apply by GUID, case-insensitive resolve, unknown-nameValueError, clear-via-style_id = None, save/reload round-trip via stream. Full behave:1021 scenarios passed, 0 failed.ruff check src tests→All checks passed!;ruff format --check→211 files already formatted.Reporting contract (CLAUDE.md §7)
UAT
uat_tables_phase2.py(untracked per CLAUDE.md §6) at repo root.uat_tables_phase2_out.pptxwith five tables exercising default / apply-by-name / apply-by-GUID / case-insensitive / cleared.Refs #12