Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:09:29 +08:00

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}")