SDO DOCX Operations -- officecli guardrails
Use this skill for reliable, repeatable officecli operations that require XML-level edits on standards documents.
Workflow
- Open the document in resident mode before multi-step edits:
officecli open <doc> - Run one mutation command at a time and check exit codes.
- Use style/ordinal or explicit XPath targeting, not paraId targeting.
- Prefer
officecli batchfor template population, text updates, and paragraph additions. Python zipfile/ElementTree scripts can strip namespace declarations from the root element (see "Python OOXML manipulation — namespace caveat" below). Reserve Python scripts for edits that officecli cannot express. - Validate with
officecli view/get/querybefore and after mutation. - Close the document at the end:
officecli close <doc>
Script entry points
scripts/add_bookmarks.py-- figure/table/reference bookmark insertion and reference run replacement.scripts/fix_heading_tabs.py-- heading number-tab-title run repair.scripts/fix_enum_tabs.py-- enumeration dash-tab-text run repair.scripts/fix_note_tabs.py-- NOTE number-tab-text run repair and sequence validation.scripts/officecli_xml_common.py-- shared helpers (run_officecli, raw_set, validate_doc, load_json_spec).
All scripts declare PEP 723 inline metadata and are run via uv run:
uv run scripts/add_bookmarks.py --doc <doc> --spec assets/specs/add_bookmarks.example.json
uv run scripts/fix_heading_tabs.py --doc <doc> --mapping assets/specs/fix_heading_tabs.example.json
uv run scripts/fix_enum_tabs.py --doc <doc> --items assets/specs/fix_enum_tabs.example.json
uv run scripts/fix_note_tabs.py --doc <doc> --notes assets/specs/fix_note_tabs.example.json
JSON specs
assets/schemas/-- JSON Schema contracts for each script input.assets/specs/-- example specs for reuse across projects.
Idempotent updates — avoid hanging paragraphs
When a script inserts paragraphs (figures, tables) that are later refreshed by re-running the same script, hanging paragraphs accumulate — old inserted elements are left behind and new ones are added on each run.
Rules:
- Before inserting Figure-style image paragraphs: query and remove ALL existing
style=Figureparagraphs (exact match, not contains).- Use
paragraph[style=Figure]— notparagraph[style~=Figure]. - Captions (
style=Figure_No & title/FigureNotitle0) are updated in-place viaset; do NOT remove them — they serve as stable anchor paraIds for the insertion--beforetarget.
- Use
- Before rebuilding a table section: remove all dynamic tables by iterating
remove /body/tbl[N]in awhile True / except RuntimeError: breakloop rather than a fixed count. A fixed count fails on repeated runs once tables have already been removed. - Before removing paragraphs by paraId: wrap each removal in try/except (or check existence first) so the script is safe to run multiple times. ParaIds that were already removed on a previous run must not abort the script.
- Exact vs contains style match matters:
paragraph[style=Figure]— exact match (image containers only)paragraph[style~=Figure_No]— contains match (would also remove captions)
Safety rules
- Never use
raw-set --action replacewith an empty XML payload. - Always keep bookmark names and IDs unique.
- Always preserve non-breaking spaces and explicit tab runs where required.
- Keep scripts platform-independent and executable via
uv run. - Always use
uv run python, never barepython.
Python OOXML manipulation — namespace caveat
When using Python zipfile + ElementTree to manipulate OOXML directly,
ET.register_namespace() must be called for every namespace prefix used
in the document — otherwise ET.tostring() strips undeclared prefixes from
the root element, causing OpenXML schema validation errors and potentially
breaking the document in Word.
Safer alternative: Prefer officecli batch for template population,
text updates, and paragraph additions. officecli batch preserves all
namespace declarations automatically and handles the OOXML schema correctly.
Reserve Python zipfile/ElementTree for cases where officecli cannot express
the required edit (e.g., inserting hyperlink relationships in .rels files).
# Required namespace registrations for even a simple docx:
ET.register_namespace(
"w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
)
ET.register_namespace(
"r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
)
ET.register_namespace("w14", "http://schemas.microsoft.com/office/word/2010/wordml")
ET.register_namespace("w15", "http://schemas.microsoft.com/office/word/2012/wordml")
# ... and potentially 20+ more (wp14, w16, w16se, w16cid, w16sdtdh, etc.)
If even one namespace is missing, the document's root element loses those declarations on serialization and the file fails validation.
- Use
extract_paragraphs()fromreferences/raw-xml-manipulation.md— never the naive<w:p ...>.*?</w:p>regex (breaks on self-closing<w:p/>). - When extracting
<w:rPr>, use balanced<w:rPr>token matching — neverfind('<w:rPr', ...)which also matches<w:rPrChange>. - Validate replacement XML with
validate_para_xml()before batching. - Check for
' REF REF_' in pxmlbefore tag-scanning to avoid double-replacement after a paragraph is already fixed.
References
- references/tool-usage.md
- references/constraints.md
- references/xpath-patterns.md
- references/field-code-patterns.md
- references/tracked-changes.md
- references/raw-xml-manipulation.md
Cross-references
sdo-docx-formatting-- formatting rules that these operations implement3gpp-drafting/etsi-drafting/itut-drafting-- concrete style names required for XPath targeting (e.g.Heading1,B1,NO,TF)docx-svg-- SVG image insertion when officecli is not available