#!/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}")