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
222 lines
10 KiB
Python
Executable File
222 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Field & Table-of-Contents Showcase — generates fields.docx exercising the docx
|
|
`field` and `toc` element surface (schemas/help/docx/field.json + toc.json).
|
|
|
|
A Word FIELD is a complex fldChar run (begin / instrText / separate / result /
|
|
end), addressed as a whole at /field[N]. This builds a small report:
|
|
|
|
Title + TOC — a table of contents over heading levels 1-3
|
|
Sections — Heading1/Heading2 paragraphs the TOC references
|
|
DATE / TIME — fields with picture (\@) switches
|
|
REF — a cross-reference to a bookmark, \h = clickable
|
|
IF — a conditional field (expression + true/false text)
|
|
HYPERLINK — via a raw instruction (no typed URL shortcut)
|
|
TITLE — a document-property field
|
|
Locked PAGE — fldLock=true, Word won't recalc on F9
|
|
Footer — composite "Page X of Y" (PAGE + literal + NUMPAGES)
|
|
|
|
IMPORTANT — fields carry only their CACHED result until Word updates them.
|
|
officecli writes the field CODES correctly; Word computes the
|
|
live values on open, or when you press F9 / Update Field.
|
|
|
|
Like examples/word/document-formatting.py, this drives the officecli Python SDK
|
|
(`pip install officecli-sdk`): one resident, writes shipped over the pipe.
|
|
|
|
Usage:
|
|
python3 fields.py
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
|
|
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__)), "fields.docx")
|
|
|
|
|
|
def para(text, **props):
|
|
return {"command": "add", "parent": "/body", "type": "paragraph",
|
|
"props": {"text": text, **props}}
|
|
|
|
|
|
def field(parent="/body", **props):
|
|
return {"command": "add", "parent": parent, "type": "field", "props": props}
|
|
|
|
|
|
def toc(**props):
|
|
return {"command": "add", "parent": "/body", "type": "toc", "props": props}
|
|
|
|
|
|
print("\n==========================================")
|
|
print(f"Generating field & TOC showcase: {FILE}")
|
|
print("==========================================")
|
|
|
|
with officecli.create(FILE, "--force") as doc:
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Heading styles + updateFields — REQUIRED for a populated TOC.
|
|
# A TOC field gathers paragraphs by OUTLINE LEVEL (from the paragraph
|
|
# style), so the built-in heading styles must exist with an explicit
|
|
# outlineLvl (0 = Heading 1). updateFields=true then makes Word recompute
|
|
# every field on open, so the TOC fills in with real page numbers.
|
|
# ----------------------------------------------------------------------
|
|
print("\n--- Heading styles + updateFields ---")
|
|
doc.batch([
|
|
{"command": "add", "parent": "/styles", "type": "style",
|
|
"props": {"id": "Heading1", "name": "heading 1", "type": "paragraph",
|
|
"outlineLvl": "0", "bold": "true", "size": "16",
|
|
"color": "1F3864"}},
|
|
{"command": "add", "parent": "/styles", "type": "style",
|
|
"props": {"id": "Heading2", "name": "heading 2", "type": "paragraph",
|
|
"outlineLvl": "1", "bold": "true", "size": "13",
|
|
"color": "2E5496"}},
|
|
])
|
|
doc.send({"command": "set", "path": "/", "props": {"updateFields": "true"}})
|
|
|
|
# Title + table of contents (references the headings added below)
|
|
# ----------------------------------------------------------------------
|
|
print("\n--- Title + TOC ---")
|
|
doc.batch([
|
|
para("Field & Table-of-Contents Showcase", style="Title"),
|
|
para("Every field below shows its cached result until Word updates it "
|
|
"(F9); this doc sets updateFields so Word fills the TOC on open.",
|
|
italic="true", color="666666"),
|
|
# TOC field over heading levels 1-3, clickable, with page numbers.
|
|
toc(title="Contents", levels="1-3", hyperlinks="true",
|
|
pageNumbers="true"),
|
|
])
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Section 1 — Introduction (bookmarked for the REF cross-reference)
|
|
# ----------------------------------------------------------------------
|
|
print("--- Section 1: Introduction (+ bookmark) ---")
|
|
doc.batch([
|
|
para("1. Introduction", style="Heading1"),
|
|
{"command": "add", "parent": "/body", "type": "bookmark",
|
|
"props": {"name": "IntroSection", "text": "Introduction"}},
|
|
para("This report is generated by officecli. It demonstrates the full "
|
|
"docx field surface: page numbering, dates, cross-references, "
|
|
"conditional (IF) fields, hyperlinks, and an automatic table of "
|
|
"contents."),
|
|
para("1.1 Scope", style="Heading2"),
|
|
para("Fields are computed values Word maintains for you. officecli "
|
|
"writes the field code; the value you see is a cached result "
|
|
"until the next update."),
|
|
])
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Section 2 — DATE & TIME fields (picture switches)
|
|
# ----------------------------------------------------------------------
|
|
print("--- Section 2: DATE & TIME ---")
|
|
doc.batch([
|
|
para("2. Date & Time Fields", style="Heading1"),
|
|
para("Report date (DATE, formatted yyyy-MM-dd):"),
|
|
# `format` is a bare picture string; handler wraps it into \@ "...".
|
|
field(fieldType="date", format="yyyy-MM-dd"),
|
|
para("Generated at (TIME, formatted HH:mm):"),
|
|
field(fieldType="time", format="HH:mm"),
|
|
])
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Section 3 — REF cross-reference to the IntroSection bookmark
|
|
# ----------------------------------------------------------------------
|
|
print("--- Section 3: REF cross-reference ---")
|
|
doc.batch([
|
|
para("3. Cross-References", style="Heading1"),
|
|
para("See the section titled:"),
|
|
# \h switch makes the reference a clickable hyperlink to the target.
|
|
field(fieldType="ref", bookmarkName="IntroSection", hyperlink="true"),
|
|
])
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Section 4 — IF conditional field
|
|
# ----------------------------------------------------------------------
|
|
print("--- Section 4: IF conditional ---")
|
|
doc.batch([
|
|
para("4. Conditional Fields", style="Heading1"),
|
|
para("An IF field picks one of two texts from a logical expression:"),
|
|
# expression + trueText/falseText fold into the instruction.
|
|
field(fieldType="if", expression="1 = 1",
|
|
trueText="Condition is TRUE", falseText="Condition is FALSE"),
|
|
])
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Section 5 — HYPERLINK (raw instruction) + TITLE (doc property)
|
|
# ----------------------------------------------------------------------
|
|
print("--- Section 5: HYPERLINK & TITLE ---")
|
|
doc.batch([
|
|
para("5. Hyperlink & Property Fields", style="Heading1"),
|
|
para("A HYPERLINK field (raw instruction — no typed shortcut for the "
|
|
"URL form):"),
|
|
# `instruction` bypasses the typed helpers for arbitrary field codes.
|
|
field(instruction=' HYPERLINK "https://example.com" \\o "Visit example.com" '),
|
|
para("The document title, pulled from file metadata (TITLE field):"),
|
|
field(fieldType="title"),
|
|
])
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Section 6 — a locked PAGE field (Word won't recalc it on F9)
|
|
# ----------------------------------------------------------------------
|
|
print("--- Section 6: locked PAGE field ---")
|
|
doc.batch([
|
|
para("6. Locked Fields", style="Heading1"),
|
|
para("A locked PAGE field keeps its cached result even on Update "
|
|
"Field:"),
|
|
# fldLock=true persists in OOXML and is surfaced on get (fldLock=true,
|
|
# only when the field is locked).
|
|
field(fieldType="page", fldLock="true"),
|
|
])
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Footer — composite "Page X of Y" built in steps on /footer[1]/p[1]
|
|
# ----------------------------------------------------------------------
|
|
print("--- Footer: 'Page X of Y' ---")
|
|
doc.batch([
|
|
{"command": "add", "parent": "/", "type": "footer",
|
|
"props": {"text": "Page ", "align": "center"}},
|
|
field(parent="/footer[1]/p[1]", fieldType="page"),
|
|
{"command": "add", "parent": "/footer[1]/p[1]", "type": "run",
|
|
"props": {"text": " of "}},
|
|
field(parent="/footer[1]/p[1]", fieldType="numpages"),
|
|
])
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Set after create — retarget the DATE field's picture switch.
|
|
# TOC is /field[1], so the DATE field is /field[2].
|
|
# ----------------------------------------------------------------------
|
|
print("--- Set: retarget DATE format ---")
|
|
doc.send({"command": "set", "path": "/field[2]",
|
|
"props": {"format": "dddd, MMMM d, yyyy"}})
|
|
|
|
doc.send({"command": "save"})
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Get round-trip: confirm field codes and TOC props read back
|
|
# ----------------------------------------------------------------------
|
|
print("\n--- Round-trip readback ---")
|
|
for path in ["/field[2]", "/field[4]", "/field[5]", "/toc[1]"]:
|
|
node = doc.send({"command": "get", "path": path})
|
|
res = node.get("data", {}).get("results", [{}])[0]
|
|
fmt = res.get("format", {})
|
|
instr = fmt.get("instruction", "")
|
|
extra = ""
|
|
if res.get("type") == "toc":
|
|
extra = (f" levels={fmt.get('levels')} "
|
|
f"hyperlinks={fmt.get('hyperlinks')} "
|
|
f"pageNumbers={fmt.get('pageNumbers')}")
|
|
print(f" {path}: {fmt.get('fieldType', res.get('type'))} "
|
|
f"instruction={instr!r}{extra}")
|
|
|
|
print("\n--- Validate (fresh process, from disk) ---")
|
|
r = subprocess.run(["officecli", "validate", FILE], capture_output=True, text=True)
|
|
print(" ", (r.stdout or r.stderr).strip().split("\n")[0])
|
|
|
|
print(f"\nCreated: {FILE}")
|