8cb1f9f479
Publish SDK (PyPI) / publish (push) Waiting to run
Publish SDK (npm) / publish (@aionui/officecli-sdk) (push) Waiting to run
Publish SDK (npm) / publish (@officecli/officecli-sdk) (push) Waiting to run
Publish SDK (npm) / publish (@officecli/sdk) (push) Waiting to run
Publish SDK (npm) / publish (officecli-sdk) (push) Waiting to run
SDK smoke / smoke (windows-latest) (push) Waiting to run
SDK smoke / smoke (macos-latest) (push) Waiting to run
SDK smoke / smoke (ubuntu-latest) (push) Waiting to run
Skill parity / diff (push) Waiting to run
312 lines
12 KiB
Python
312 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Numbering & List Showcase — generates numbering.docx exercising every supported
|
|
`num` / `abstractNum` feature:
|
|
• abstractNum top-level props: name, styleLink, numStyleLink, multiLevelType
|
|
• Per-level dotted props on all levels: format, text, start, indent, hanging,
|
|
justification, suff, font, size, color, bold, italic
|
|
• num mode A (auto-create matching abstractNum from format/text/indent)
|
|
• num mode B (reuse existing abstractNum via abstractNumId)
|
|
• num mode C (startOverride — restart numbering for one instance)
|
|
• Two num instances sharing one abstractNum → independent counters
|
|
• continue=true opt-in to Word-style continuation
|
|
• style-borne numPr (paragraph inherits numbering via pStyle)
|
|
• Set on /numbering/abstractNum[@id=N]/level[L] after creation
|
|
|
|
SDK twin of numbering.sh (officecli CLI). Both produce an equivalent
|
|
numbering.docx. This one drives the **officecli Python SDK**
|
|
(`pip install officecli-sdk`): one resident is started, the `add num` calls go
|
|
out via `doc.send(...)` so we can read back each freshly-minted numId, and the
|
|
paragraphs that reference those ids ship in `doc.batch(...)` round-trips. Each
|
|
item is the same `{"command","parent","type","props"}` dict you'd put in an
|
|
`officecli batch` list.
|
|
|
|
Usage:
|
|
pip install officecli-sdk # plus the `officecli` binary on PATH
|
|
python3 numbering.py
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
# --- locate the SDK: prefer an installed `officecli-sdk`, else the in-repo copy
|
|
try:
|
|
import officecli # pip install officecli-sdk
|
|
except ImportError:
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"..", "..", "sdk", "python"))
|
|
import officecli
|
|
|
|
FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "numbering.docx")
|
|
|
|
|
|
def para(text, **props):
|
|
"""One `add paragraph` item in batch-shape."""
|
|
return {"command": "add", "parent": "/body", "type": "paragraph",
|
|
"props": {"text": text, **props}}
|
|
|
|
|
|
def _num_id(resp):
|
|
"""Pull the numId out of an `add num` envelope ("Added num at
|
|
/numbering/num[@id=N]") — the SDK twin of the .sh `sed @id=N` capture."""
|
|
text = resp.get("data") or resp.get("message") or "" if isinstance(resp, dict) else str(resp)
|
|
m = re.search(r"@id=(\d+)\]", text)
|
|
if not m:
|
|
raise RuntimeError(f"could not parse numId from add response: {resp!r}")
|
|
return m.group(1)
|
|
|
|
|
|
print(f"Building {FILE} ...")
|
|
|
|
with officecli.create(FILE, "--force") as doc:
|
|
def add_num(**props):
|
|
"""`add /numbering --type num` via send(), returning the new numId."""
|
|
return _num_id(doc.send({"command": "add", "parent": "/numbering",
|
|
"type": "num", "props": props}))
|
|
|
|
# ============================================================
|
|
# Title
|
|
# ============================================================
|
|
doc.batch([
|
|
para("Numbering & List Showcase",
|
|
align="center", bold="true", size="20"),
|
|
para("Generated by officecli — covers abstractNum, num, and style-borne numPr",
|
|
align="center", italic="true", color="666666"),
|
|
para(""),
|
|
|
|
# ===== Section 1 heading + abstractNum #100 (custom 3-level template) =====
|
|
para("1. Three-level numbered list (custom marker styling)",
|
|
bold="true", size="14", spaceBefore="240", spaceAfter="120"),
|
|
])
|
|
|
|
# abstractNum #100 — fully customized 3-level numbered template
|
|
doc.send({"command": "add", "parent": "/numbering", "type": "abstractNum", "props": {
|
|
"id": "100",
|
|
"name": "ShowcaseMultilevel",
|
|
"type": "hybridMultilevel",
|
|
"level0.format": "decimal",
|
|
"level0.text": "%1.",
|
|
"level0.indent": "720",
|
|
"level0.hanging": "360",
|
|
"level0.justification": "left",
|
|
"level0.suff": "tab",
|
|
"level0.color": "C00000",
|
|
"level0.bold": "true",
|
|
"level0.size": "14",
|
|
"level1.format": "lowerLetter",
|
|
"level1.text": "%2)",
|
|
"level1.indent": "1440",
|
|
"level1.hanging": "360",
|
|
"level1.color": "2E74B5",
|
|
"level1.italic": "true",
|
|
"level2.format": "lowerRoman",
|
|
"level2.text": "%3.",
|
|
"level2.indent": "2160",
|
|
"level2.hanging": "360",
|
|
"level2.color": "666666",
|
|
}})
|
|
|
|
# A num instance pointing at #100
|
|
num_a = add_num(abstractNumId="100")
|
|
print(f" Created num #{num_a} → abstractNum #100")
|
|
|
|
doc.batch([
|
|
para("Project Phoenix kickoff agenda", numId=num_a, ilvl="0"),
|
|
para("Stakeholder alignment", numId=num_a, ilvl="1"),
|
|
para("identify decision makers", numId=num_a, ilvl="2"),
|
|
para("schedule discovery interviews", numId=num_a, ilvl="2"),
|
|
para("Architecture review", numId=num_a, ilvl="1"),
|
|
para("Sprint planning", numId=num_a, ilvl="0"),
|
|
para("Resource allocation", numId=num_a, ilvl="0"),
|
|
|
|
# ===== Section 2 heading =====
|
|
para(""),
|
|
para("2. Independent counters (default) and continue=true opt-in",
|
|
bold="true", size="14", spaceBefore="240", spaceAfter="120"),
|
|
])
|
|
|
|
# Two new nums on the same abstractNum: by default each gets its own
|
|
# auto-injected startOverride.0 → independent counters. The third opts into
|
|
# Word's literal continuation via continue=true.
|
|
num_b = add_num(abstractNumId="100")
|
|
print(f" Created num #{num_b} → independent counter (auto-injected startOverride.0=1)")
|
|
num_cont = add_num(abstractNumId="100", **{"continue": "true"})
|
|
print(f" Created num #{num_cont} → Word-style continuation (continue=true)")
|
|
|
|
doc.batch([
|
|
para("List B starts fresh at 1 (default behavior)", numId=num_b, ilvl="0"),
|
|
para("List B item two (counts 2)", numId=num_b, ilvl="0"),
|
|
para("List C continues from List A's count (continue=true)", numId=num_cont, ilvl="0"),
|
|
para("List C item two", numId=num_cont, ilvl="0"),
|
|
|
|
# ===== Section 3 heading =====
|
|
para(""),
|
|
para("3. Restart numbering with startOverride",
|
|
bold="true", size="14", spaceBefore="240", spaceAfter="120"),
|
|
])
|
|
|
|
# Mode C — num with startOverride (restart at 100)
|
|
num_c = add_num(abstractNumId="100", start="100")
|
|
print(f" Created num #{num_c} → abstractNum #100 with startOverride.0=100")
|
|
|
|
doc.batch([
|
|
para("Numbered starting from 100", numId=num_c, ilvl="0"),
|
|
para("Continues from 101", numId=num_c, ilvl="0"),
|
|
|
|
# ===== Section 4 heading =====
|
|
para(""),
|
|
para("4. Custom-styled bullet list (★ / ▶ / ●)",
|
|
bold="true", size="14", spaceBefore="240", spaceAfter="120"),
|
|
])
|
|
|
|
# abstractNum #200 — bullet list with custom glyphs and font color
|
|
doc.send({"command": "add", "parent": "/numbering", "type": "abstractNum", "props": {
|
|
"id": "200",
|
|
"name": "StarBullet",
|
|
"type": "hybridMultilevel",
|
|
"level0.format": "bullet",
|
|
"level0.text": "★",
|
|
"level0.color": "E8B003",
|
|
"level0.size": "12",
|
|
"level1.format": "bullet",
|
|
"level1.text": "▶",
|
|
"level1.font": "Arial",
|
|
"level1.color": "2E74B5",
|
|
"level1.indent": "1440",
|
|
"level2.format": "bullet",
|
|
"level2.text": "●",
|
|
"level2.color": "70AD47",
|
|
"level2.indent": "2160",
|
|
}})
|
|
|
|
num_bullet = add_num(abstractNumId="200")
|
|
|
|
doc.batch([
|
|
para("Top-level milestone", numId=num_bullet, ilvl="0"),
|
|
para("Sub-milestone with deliverable", numId=num_bullet, ilvl="1"),
|
|
para("Nitty-gritty detail", numId=num_bullet, ilvl="2"),
|
|
para("Another top-level milestone", numId=num_bullet, ilvl="0"),
|
|
|
|
# ===== Section 5 heading =====
|
|
para(""),
|
|
para("5. Mode A — num auto-creates abstractNum",
|
|
bold="true", size="14", spaceBefore="240", spaceAfter="120"),
|
|
])
|
|
|
|
# Mode A — num auto-creates a matching abstractNum on the fly
|
|
num_auto = add_num(**{
|
|
"level0.format": "upperRoman",
|
|
"level0.text": "%1.",
|
|
"level0.indent": "720",
|
|
"level0.size": "12",
|
|
"level0.color": "7030A0",
|
|
"level0.bold": "true",
|
|
})
|
|
print(f" Mode A created num #{num_auto} + matching abstractNum")
|
|
|
|
doc.batch([
|
|
para("The first part of the proposal", numId=num_auto, ilvl="0"),
|
|
para("The second part", numId=num_auto, ilvl="0"),
|
|
para("The third part", numId=num_auto, ilvl="0"),
|
|
|
|
# ===== Section 6 heading =====
|
|
para(""),
|
|
para("6. Style-borne numbering (paragraph inherits via pStyle)",
|
|
bold="true", size="14", spaceBefore="240", spaceAfter="120"),
|
|
])
|
|
|
|
# Build a dedicated abstractNum + num for the style
|
|
doc.send({"command": "add", "parent": "/numbering", "type": "abstractNum", "props": {
|
|
"id": "300",
|
|
"name": "StyleBorne",
|
|
"level0.format": "decimalZero",
|
|
"level0.text": "%1.",
|
|
"level0.indent": "720",
|
|
"level0.color": "C00000",
|
|
}})
|
|
num_style = add_num(abstractNumId="300")
|
|
|
|
# Style holds the numPr; paragraphs reference the style without their own numId
|
|
doc.send({"command": "add", "parent": "/styles", "type": "style", "props": {
|
|
"id": "ShowcaseListItem",
|
|
"name": "Showcase List Item",
|
|
"type": "paragraph",
|
|
"basedOn": "Normal",
|
|
"numId": num_style,
|
|
"ilvl": "0",
|
|
}})
|
|
|
|
doc.batch([
|
|
para("Inherits numbering through style", style="ShowcaseListItem"),
|
|
para("Second item, also via style", style="ShowcaseListItem"),
|
|
para("Third item — note: paragraphs themselves have no numPr", style="ShowcaseListItem"),
|
|
|
|
# ===== Section 7 heading =====
|
|
para(""),
|
|
para("7. Modify abstractNum after creation",
|
|
bold="true", size="14", spaceBefore="240", spaceAfter="120"),
|
|
])
|
|
|
|
# Set after create — override abstractNum #100 level 3 with green + larger size
|
|
doc.send({"command": "set",
|
|
"path": "/numbering/abstractNum[@id=100]/level[3]",
|
|
"props": {"format": "decimal", "text": "Step %4 ⇒",
|
|
"color": "70AD47", "bold": "true", "size": "12"}})
|
|
|
|
num_deep = add_num(abstractNumId="100")
|
|
|
|
doc.batch([
|
|
para("Outer step", numId=num_deep, ilvl="0"),
|
|
para("Mid step", numId=num_deep, ilvl="1"),
|
|
para("Inner step", numId=num_deep, ilvl="2"),
|
|
para("Deepest step (modified after creation)", numId=num_deep, ilvl="3"),
|
|
|
|
# ===== Section 8 heading =====
|
|
para(""),
|
|
para("8. styleLink, numStyleLink, level<N>.start, direction, isLgl, lvlRestart",
|
|
bold="true", size="14", spaceBefore="240", spaceAfter="120"),
|
|
])
|
|
|
|
# abstractNum #400 — styleLink + numStyleLink + per-level start
|
|
doc.send({"command": "add", "parent": "/numbering", "type": "abstractNum", "props": {
|
|
"id": "400",
|
|
"name": "CoverageAbs",
|
|
"type": "multilevel",
|
|
"styleLink": "CoverageStyle",
|
|
"numStyleLink": "OutlineRef",
|
|
"level0.format": "decimal",
|
|
"level0.text": "%1.",
|
|
"level0.start": "1",
|
|
"level1.format": "lowerLetter",
|
|
"level1.text": "%2)",
|
|
"level1.start": "3",
|
|
"level2.format": "lowerRoman",
|
|
"level2.text": "%3.",
|
|
"level2.start": "5",
|
|
}})
|
|
|
|
# Set direction=rtl, isLgl=true, lvlRestart=0 on individual levels via Set
|
|
doc.send({"command": "set",
|
|
"path": "/numbering/abstractNum[@id=400]/level[1]",
|
|
"props": {"direction": "rtl"}})
|
|
doc.send({"command": "set",
|
|
"path": "/numbering/abstractNum[@id=400]/level[2]",
|
|
"props": {"isLgl": "true", "lvlRestart": "0"}})
|
|
|
|
num_cov = add_num(abstractNumId="400")
|
|
print(f" Created num #{num_cov} → abstractNum #400 (coverage)")
|
|
|
|
doc.batch([
|
|
para("Item starting at 1 (level0.start=1)", numId=num_cov, ilvl="0"),
|
|
para("Sub-item starting at c (level1.start=3, direction=rtl)", numId=num_cov, ilvl="1"),
|
|
para("Deep item starting at v (level2.start=5, isLgl, lvlRestart=0)", numId=num_cov, ilvl="2"),
|
|
|
|
# ===== Closer =====
|
|
para(""),
|
|
para("End of showcase. Open in Word/Google Docs to see all numbering rendered.",
|
|
italic="true", color="666666", align="center"),
|
|
])
|
|
|
|
print(f"Generated: {FILE}")
|