feat(text): Field authoring — _Paragraph.add_field, _Field class, CT_TextField.text setter (Phase 3)#50
Merged
Conversation
…TextField.text setter (Phase 3) Public Python API for the headers/footers/slide-numbers/dates epic (#20). Phase 3 adds the field-authoring surface that lets users create auto-updating slide numbers, dates, and other PowerPoint-resolved fields inside any paragraph. Builds on Phase 1 (PR #48) OOXML primitives and Phase 2 (PR #49) slide/master public API. Design source: scanny#797 ("Added a:fld type to paragraphs for page numbers and datetimes"). Manually ported — per CLAUDE.md §2, this fork's master had a repo-wide ruff format pass (PR #10) while upstream did not, so cherry-pick conflicts on whitespace across every touched file. Semantic diff re-derived against the current ruff- formatted, post-Phase-1 source. Changes: - pptx.oxml.simpletypes.ST_FieldType (NEW) — XsdString subclass for the `a:fld@type` attribute value, replacing the plain XsdString declaration Phase 1 used as a placeholder. - pptx.oxml.text.CT_TextField.text — read-only property from Phase 1 now has a setter. Writes through get_or_add_t() and routes the value through CT_TextField._escape_ctrl_chars (NEW static method) which replaces chars in `[\x00-\x08\x0B-\x1F]` with `_xNNNN_` uppercase-hex form per OOXML §22.9.2.19, leaving `\t` (0x09) and `\n` (0x0A) alone. - pptx.oxml.text.CT_TextParagraph.fld — ZeroOrMore("a:fld", successors= ("a:endParaRPr",)) accessor; the `a:pPr` successor tuple already named `a:fld` per Phase 1 (forward declaration). xmlchemy auto-generates `_add_fld()` from the ZeroOrMore. - pptx.text.text._Field (NEW) — public-via-add_field-return-value class wrapping `<a:fld>`. Leading-underscore private name matches `_Run` and `_Paragraph`. Properties: `font` (Font wrapping rPr), `text` (read/ write, routes through the escaping setter), `type` (read/write, str | None). - pptx.text.text._Paragraph.add_field() (NEW) — appends a fresh `<a:fld>` with a uuid4 GUID id wrapped in braces, uppercase hex — matches what PowerPoint's "Insert → Slide Number" writes. Returns a `_Field`; caller sets `type` and optionally `text`. The Run-style symmetry is deliberate: users who know `add_run()` should not have to learn a new pattern. Out of scope for Phase 3 (deliberate): - Field discovery during paragraph iteration — `p.runs` continues to yield only `_Run` objects. Phase 4 will surface `_Field` instances alongside, with a stable ordering rule. - HandoutMaster Python class and watermark helper — Phase 5. - MSO_FIELD_TYPE enum — `type` stays plain `str` for now to mirror scanny#797. An enum can land in a later cleanup once the canonical field-type list is settled. Verification (local, CPython 3.14.4): - python3 -m pytest tests/ -q → 3626 passed in 5.32s (+28 vs Phase 2 baseline) - 14 new tests in tests/oxml/test_text.py (CT_TextField setter + _escape_ctrl_chars + CT_TextParagraph.add_fld) - 14 new tests in tests/text/test_text.py (Describe_Field ×10 + Describe_Paragraph_add_field ×4) - python3 -m ruff check src tests → All checks passed! - python3 -m ruff format --check src tests → 216 files already formatted - python3 -m behave features/ --no-color → 1048 scenarios, 0 failed - python3 uat/uat_headers_footers_phase3.py → PASS (full <a:fld> with id, type, and text round-tripped through save+reopen; GUID preserved byte-for-byte at {2ED44585-07B5-4BC8-93B2-49122D50BCC2}) Refs #20.
This was referenced May 13, 2026
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 3 of #20 — Field authoring API
Ports
scanny/python-pptx#797("Added a:fld type to paragraphs for page numbers and datetimes") onto Phase 1's (PR #48) OOXML primitives and Phase 2's (PR #49) public slide/master surface. Manually re-derived against this fork's ruff-formatted, post-Phase-1 source perCLAUDE.md§2 —git cherry-pickwould conflict on every touched file because upstream predates the repo-wide ruff format pass (PR #10).What this PR lets users do
Open the resulting
.pptxin PowerPoint or Keynote and the slide number renders automatically. TheidGUID is generated transparently in the format PowerPoint emits when the user runs Insert → Slide Number.Changes
pptx.oxml.simpletypes.ST_FieldType(NEW) —XsdStringsubclass for thea:fld@typeattribute value. Phase 1 used a plainXsdStringplaceholder; this is the named subtype.pptx.oxml.text.CT_TextField.text— read-only property from Phase 1 now has a setter. Writes throughget_or_add_t()and routes the value throughCT_TextField._escape_ctrl_chars(NEW static method) which replaces chars in[\x00-\x08\x0B-\x1F]with_xNNNN_uppercase-hex form per OOXML §22.9.2.19, leaving\t(0x09) and\n(0x0A) alone.pptx.oxml.text.CT_TextParagraph.fld—ZeroOrMore("a:fld", successors=("a:endParaRPr",))accessor. Thea:pPrsuccessor tuple already nameda:fldper Phase 1 (forward declaration). xmlchemy auto-generates_add_fld()from the ZeroOrMore.pptx.text.text._Field(NEW) — public-via-add_field-return-value class wrapping<a:fld>. Leading-underscore private name matches_Runand_Paragraph. Properties:font(Font wrapping rPr),text(read/write, routes through the escaping setter),type(read/write,str | None).pptx.text.text._Paragraph.add_field()(NEW) — appends a fresh<a:fld>with auuid4GUID id wrapped in braces, uppercase hex. Returns a_Field; caller setstypeand optionallytext. Run-style symmetry is deliberate — users who knowadd_run()don't have to learn a new pattern.Out of scope (deliberate)
p.runscontinues to yield only_Runobjects. Phase 4 will surface_Fieldinstances via a separate accessor.HandoutMasterPython class and watermark helper — Phase 5.MSO_FIELD_TYPEenum —typestays plainstrfor now to mirror Added a:fld type to paragraphs for page numbers and datetimes scanny/python-pptx#797. An enum can land in a later cleanup once the canonical field-type list is settled.Verification (local, CPython 3.14.4)
28 new tests:
tests/oxml/test_text.py—CT_TextFieldsetter,_escape_ctrl_chars(BEL/tab/newline edges),CT_TextParagraph.fld/_add_fld.tests/text/test_text.py—Describe_Field(10 tests: font, text get/set with escape, type get/set/clear) +Describe_Paragraph_add_field(4 tests: GUID regex match, distinct ids on consecutive calls, fld-after-runs ordering, parent chain).Refs #20.