feat: Phase D.9: Numbering style control (restart, custom lists, nested lists)#74
feat: Phase D.9: Numbering style control (restart, custom lists, nested lists)#74citconv-agents[bot] wants to merge 3 commits intomasterfrom
Conversation
Implement comprehensive numbering/list control addressing upstream issues python-openxml#582, #25, and python-openxml#122. Adds three-layer support following existing codebase patterns: oxml layer: - CT_AbstractNum for abstract numbering definitions with multi-level support - CT_Lvl for level format (numFmt, lvlText, start value) - Extended CT_Numbering with abstractNum management - CT_NumPr with ilvl_val/numId_val property accessors Parts layer: - Extended NumberingPart with proper new() factory and numbering_element Proxy layer: - Numbering: document.numbering with definitions list and add_numbering_definition() for custom multi-level lists - NumberingDefinition: num_id, abstract_num_id, levels, restart() - LevelFormat: level, number_format, text_pattern, start - ListFormat: paragraph.list_format with level, num_id, apply() - Paragraph: list_level, numbering_format, restart_numbering() Closes #22 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Security Agent Report SECURITY_PASS Security Review — PR #74 (Numbering Style Control)Reviewed: 2026-04-05 SummaryNo security issues found. The PR adds numbering/list formatting support (new Files Reviewed
Detailed FindingsInjection Risks — NONEXPath construction ( xpath = './w:lvl[@w:ilvl="%d"]' % ilvl # integer-only format
xpath = './w:abstractNum[@w:abstractNumId="%d"]' % abstractNumId # integer-only formatBoth use XML attribute setting (e.g. lvlText.set(qn("w:val"), value)
numFmt.set(qn("w:val"), value)Uses lxml's Hardcoded XML template ( Dependency Risks — NONENo new third-party packages introduced. All imports are from the existing Data Exposure — NONENo file I/O operations that accept user-controlled paths. No logging of sensitive data. Secrets — NONENo API keys, tokens, passwords, or credentials anywhere in the diff. ConclusionThe PR is safe to merge from a security standpoint. |
|
Review Agent REVIEW_CHANGES_NEEDED PR #74 — Numbering Style Control (restart, custom lists, nested lists)Overall this is well-structured work that follows the codebase's three-layer pattern. Tests for the new oxml and proxy classes are thorough. A couple of correctness issues need fixing before merge. Bug:
|
- NumberingPart.new() now accepts and propagates package arg (fixes save failure) - Remove false get_or_add_lvl annotation on CT_AbstractNum (ZeroOrMore doesn't generate it) - Remove unreachable return in _next_abstractNumId - Remove redundant element aliases in proxy classes, use self._element - Remove unused TYPE_CHECKING import of qn in numbering.py - Add tests for Paragraph.list_format, list_level, numbering_format, restart_numbering() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Security Agent Report SECURITY_PASS Security Review: PR #74 — Numbering Style ControlSummaryReviewed all changed files for injection risks, dependency risks, data exposure, and secrets. No security issues found. Files Reviewed
FindingsXML Injection / XPath InjectionNot a risk. Two XPath expressions use dynamic values:
Both use XML Attribute Value InjectionNot a risk. User-supplied strings ( XXE (XML External Entity)Not a risk. The hardcoded XML template in New DependenciesNone. No new packages added to requirements or setup files. Secrets / CredentialsNone found. No API keys, tokens, passwords, or credentials anywhere in the diff. Path TraversalNot applicable. No file path operations in the changed code. Data ExposureNo issue. The ConclusionAll changes are limited to OOXML numbering definition management within a document processing library. The code follows established safe patterns. No security concerns identified. |
|
Review Agent REVIEW_CHANGES_NEEDED PR #74 — Add numbering style control (restart, custom lists, nested lists)Overall the implementation is solid: the three-layer architecture is respected, proxy objects are clean, and test coverage is good. There are a few bugs that need fixing before merge. Bug 1 (High): Off-by-one in
|
- Fix off-by-one in CT_Lvl successors (lvlJc, pPr, rPr included themselves in their own successors tuples) - Fix restart_numbering() to use paragraph's current level instead of hardcoding ilvl=0 - Add level parameter to NumberingDefinition.restart() with default 0 - Extract inline XML bytes literal to default-numbering.xml template - Add missing PEP 8 blank line before Numbering class Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Security Agent Report SECURITY_PASS Security Review — PR #74 (Numbering Style Control)Reviewed: 2026-04-05 Files Reviewed
FindingsXPath Construction — CLEANLocations:
Both XPath expressions use XML Attribute Values from User Input — CLEANLocations:
Values are set via lxml's File Path Construction — CLEANLocation: path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-numbering.xml")Path is derived entirely from Static XML Template — CLEANLocation: No New Dependencies — NONENo new packages added to Secrets in Code — NONENo API keys, tokens, passwords, or credentials found in any changed file. Data Exposure — NONENo sensitive data is read, logged, or returned in an unsafe manner. All accessed data is OOXML numbering metadata from the document being processed. SummaryNo security issues found. The PR follows safe patterns throughout:
|
|
Review Agent REVIEW_CHANGES_NEEDED PR #74 — Numbering Style Control (restart, custom lists, nested lists)The PR is well-structured and introduces useful functionality with good test coverage. Three Issue 1 — Layering violation: proxy layer accesses private oxml internalsFile: abstract_num_id = numbering._next_abstractNumId
abstract_num = CT_AbstractNum.new(abstract_num_id)
The fix is to add a factory method to # in CT_Numbering
def new_abstractNum(self) -> CT_AbstractNum:
"""Create, insert, and return a new <w:abstractNum> with next available ID."""
abstract_num_id = self._next_abstractNumId
abstract_num = cast(
CT_AbstractNum,
OxmlElement("w:abstractNum", attrs={qn("w:abstractNumId"): str(abstract_num_id)}),
)
return self._insert_abstractNum(abstract_num)Then abstract_num = numbering.new_abstractNum()
abstract_num_id = abstract_num.abstractNumId
# ... add levels ...
num = numbering.add_num(abstract_num_id)
Issue 2 — Code duplication: restart logic repeated in two placesFiles:
def restart_numbering(self) -> None:
list_fmt = self.list_format
num_id = list_fmt.num_id
if num_id is None or num_id == 0:
return
numbering = self.part.numbering
num_def = next((d for d in numbering.definitions if d.num_id == num_id), None)
if num_def is None:
return
current_level = list_fmt.level or 0
new_defn = num_def.restart(level=current_level)
list_fmt.num_id = new_defn.num_idThis keeps the oxml mutation logic in one place and makes Issue 3 —
|
|
Closing to re-run through the fixed pipeline (Product → Developer → Security → Review → Merge). |
|
Product Agent — APPROVED APPROVED The issue is well-defined with a concrete API design, clear XML mapping, and strong upstream demand (multiple high-comment issues). The scope fits squarely within python-docx's purpose of OOXML manipulation. The previous review cycles have already surfaced specific bugs (package=None, off-by-one successors, layering violation) that the developer will need to address, but those are implementation-level concerns, not gaps in the issue specification itself. Handing off to the Developer Agent. |
Summary
Implements #22
This PR was automatically generated by the Developer Agent.
Original Issue
Add comprehensive numbering/list control. Most requested upstream feature (python-openxml#582: 83 comments, #25: 37 comments, python-openxml#122: 27 comments).
API Design
Restart numbering:
Custom list styles:
Nested lists:
Access existing numbering:
XML
Upstream issues: python-openxml#582, #25, python-openxml#122
Generated by Developer Agent using Claude Code