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
326 lines
17 KiB
Python
326 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Complex Tables Showcase — generates tables.docx exercising the docx `table`
|
|
element: merged cells (vmerge / gridspan / hmerge), multi-level headers, a color
|
|
heatmap, full table/row/cell property coverage (borders, layout, direction,
|
|
padding, cell run-formatting), an RTL table, and an inline `data=` shorthand
|
|
grid.
|
|
|
|
SDK twin of tables.sh (officecli CLI). The shell script generates three files
|
|
(tables.docx + tables.xlsx + tables.pptx); this twin produces the **Word**
|
|
document, tables.docx, command-for-command equivalent to the `$DOCX` section of
|
|
tables.sh.
|
|
|
|
It drives the **officecli Python SDK** (`pip install officecli-sdk`): one
|
|
resident is started and every paragraph/table/cell command is shipped over the
|
|
named pipe in a single `doc.batch(...)` round-trip. Each item is the same
|
|
`{"command","parent"/"path","type","props"}` dict you'd put in an
|
|
`officecli batch` list.
|
|
|
|
Usage:
|
|
pip install officecli-sdk # plus the `officecli` binary on PATH
|
|
python3 tables.py
|
|
"""
|
|
|
|
import os
|
|
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__)), "tables.docx")
|
|
|
|
|
|
def para(text, **props):
|
|
"""One `add paragraph` item in batch-shape."""
|
|
return {"command": "add", "parent": "/body", "type": "paragraph",
|
|
"props": {"text": text, **props}}
|
|
|
|
|
|
def table(**props):
|
|
"""One `add table` item in batch-shape."""
|
|
return {"command": "add", "parent": "/body", "type": "table", "props": props}
|
|
|
|
|
|
def cell(path, **props):
|
|
"""One `set` item targeting a tc/tr/tbl path in batch-shape."""
|
|
return {"command": "set", "path": path, "props": props}
|
|
|
|
|
|
print(f"Building {FILE} ...")
|
|
|
|
with officecli.create(FILE, "--force") as doc:
|
|
items = [
|
|
para("Complex Table Examples", style="Heading1", align="center"),
|
|
para(""),
|
|
|
|
# ==================== Table 1: Project Progress Tracker ====================
|
|
# vertical merge (vmerge=restart / continue) spanning 3 rows per project
|
|
para("1. Project Progress Tracker", style="Heading2"),
|
|
table(rows="7", cols="6"),
|
|
|
|
# Header
|
|
cell("/body/tbl[1]/tr[1]/tc[1]", text="Project Name", bold="true",
|
|
shd="4472C4", color="FFFFFF", valign="center"),
|
|
cell("/body/tbl[1]/tr[1]/tc[2]", text="Phase", bold="true",
|
|
shd="4472C4", color="FFFFFF"),
|
|
cell("/body/tbl[1]/tr[1]/tc[3]", text="Owner", bold="true",
|
|
shd="4472C4", color="FFFFFF"),
|
|
cell("/body/tbl[1]/tr[1]/tc[4]", text="Start Date", bold="true",
|
|
shd="4472C4", color="FFFFFF"),
|
|
cell("/body/tbl[1]/tr[1]/tc[5]", text="End Date", bold="true",
|
|
shd="4472C4", color="FFFFFF"),
|
|
cell("/body/tbl[1]/tr[1]/tc[6]", text="Progress", bold="true",
|
|
shd="4472C4", color="FFFFFF"),
|
|
|
|
# Project A - Smart Office System (merge 3 rows)
|
|
cell("/body/tbl[1]/tr[2]/tc[1]", text="Smart Office System",
|
|
vmerge="restart", valign="center", shd="D9E2F3"),
|
|
cell("/body/tbl[1]/tr[2]/tc[2]", text="Requirements"),
|
|
cell("/body/tbl[1]/tr[2]/tc[3]", text="John"),
|
|
cell("/body/tbl[1]/tr[2]/tc[4]", text="2025-01-05"),
|
|
cell("/body/tbl[1]/tr[2]/tc[5]", text="2025-02-15"),
|
|
cell("/body/tbl[1]/tr[2]/tc[6]", text="100%", color="00B050"),
|
|
|
|
cell("/body/tbl[1]/tr[3]/tc[1]", text="", vmerge="continue", shd="D9E2F3"),
|
|
cell("/body/tbl[1]/tr[3]/tc[2]", text="Development"),
|
|
cell("/body/tbl[1]/tr[3]/tc[3]", text="Sarah"),
|
|
cell("/body/tbl[1]/tr[3]/tc[4]", text="2025-02-16"),
|
|
cell("/body/tbl[1]/tr[3]/tc[5]", text="2025-06-30"),
|
|
cell("/body/tbl[1]/tr[3]/tc[6]", text="75%", color="FFC000"),
|
|
|
|
cell("/body/tbl[1]/tr[4]/tc[1]", text="", vmerge="continue", shd="D9E2F3"),
|
|
cell("/body/tbl[1]/tr[4]/tc[2]", text="Testing"),
|
|
cell("/body/tbl[1]/tr[4]/tc[3]", text="Mike"),
|
|
cell("/body/tbl[1]/tr[4]/tc[4]", text="2025-07-01"),
|
|
cell("/body/tbl[1]/tr[4]/tc[5]", text="2025-08-31"),
|
|
cell("/body/tbl[1]/tr[4]/tc[6]", text="0%", color="FF0000"),
|
|
|
|
# Project B - Data Platform Upgrade (merge 3 rows)
|
|
cell("/body/tbl[1]/tr[5]/tc[1]", text="Data Platform Upgrade",
|
|
vmerge="restart", valign="center", shd="E2EFDA"),
|
|
cell("/body/tbl[1]/tr[5]/tc[2]", text="Architecture"),
|
|
cell("/body/tbl[1]/tr[5]/tc[3]", text="Emily"),
|
|
cell("/body/tbl[1]/tr[5]/tc[4]", text="2025-03-01"),
|
|
cell("/body/tbl[1]/tr[5]/tc[5]", text="2025-04-15"),
|
|
cell("/body/tbl[1]/tr[5]/tc[6]", text="100%", color="00B050"),
|
|
|
|
cell("/body/tbl[1]/tr[6]/tc[1]", text="", vmerge="continue", shd="E2EFDA"),
|
|
cell("/body/tbl[1]/tr[6]/tc[2]", text="Migration"),
|
|
cell("/body/tbl[1]/tr[6]/tc[3]", text="David"),
|
|
cell("/body/tbl[1]/tr[6]/tc[4]", text="2025-04-16"),
|
|
cell("/body/tbl[1]/tr[6]/tc[5]", text="2025-07-31"),
|
|
cell("/body/tbl[1]/tr[6]/tc[6]", text="40%", color="FFC000"),
|
|
|
|
cell("/body/tbl[1]/tr[7]/tc[1]", text="", vmerge="continue", shd="E2EFDA"),
|
|
cell("/body/tbl[1]/tr[7]/tc[2]", text="Acceptance"),
|
|
cell("/body/tbl[1]/tr[7]/tc[3]", text="Lisa"),
|
|
cell("/body/tbl[1]/tr[7]/tc[4]", text="2025-08-01"),
|
|
cell("/body/tbl[1]/tr[7]/tc[5]", text="2025-09-30"),
|
|
cell("/body/tbl[1]/tr[7]/tc[6]", text="0%", color="FF0000"),
|
|
|
|
# ==================== Table 2: Financial Statement ====================
|
|
# gridspan horizontal merge + vmerge vertical merge in a two-row header
|
|
para(""),
|
|
para("2. Financial Statement", style="Heading2"),
|
|
table(rows="8", cols="5"),
|
|
|
|
# Header row 1 - gridspan=2 automatically removes the merged tc
|
|
cell("/body/tbl[2]/tr[1]/tc[1]", text="Category", bold="true",
|
|
shd="2E75B6", color="FFFFFF", vmerge="restart", valign="center"),
|
|
cell("/body/tbl[2]/tr[1]/tc[2]", text="Line Item", bold="true",
|
|
shd="2E75B6", color="FFFFFF", vmerge="restart", valign="center"),
|
|
cell("/body/tbl[2]/tr[1]/tc[3]", text="Amount (10K USD)", bold="true",
|
|
shd="2E75B6", color="FFFFFF", gridspan="2", align="center"),
|
|
# gridspan=2 removed original tc[4], original tc[5] becomes tc[4]
|
|
cell("/body/tbl[2]/tr[1]/tc[4]", text="Notes", bold="true",
|
|
shd="2E75B6", color="FFFFFF", vmerge="restart", valign="center"),
|
|
|
|
# Header row 2
|
|
cell("/body/tbl[2]/tr[2]/tc[1]", text="", vmerge="continue", shd="2E75B6"),
|
|
cell("/body/tbl[2]/tr[2]/tc[2]", text="", vmerge="continue", shd="2E75B6"),
|
|
cell("/body/tbl[2]/tr[2]/tc[3]", text="Budget", bold="true",
|
|
shd="5B9BD5", color="FFFFFF", align="center"),
|
|
cell("/body/tbl[2]/tr[2]/tc[4]", text="Actual", bold="true",
|
|
shd="5B9BD5", color="FFFFFF", align="center"),
|
|
cell("/body/tbl[2]/tr[2]/tc[5]", text="", vmerge="continue", shd="2E75B6"),
|
|
|
|
# Revenue (merge 3 rows)
|
|
cell("/body/tbl[2]/tr[3]/tc[1]", text="Revenue", vmerge="restart",
|
|
valign="center", shd="DEEAF6", bold="true"),
|
|
cell("/body/tbl[2]/tr[3]/tc[2]", text="Product Sales"),
|
|
cell("/body/tbl[2]/tr[3]/tc[3]", text="500.00", align="right"),
|
|
cell("/body/tbl[2]/tr[3]/tc[4]", text="523.50", align="right", color="00B050"),
|
|
cell("/body/tbl[2]/tr[3]/tc[5]", text="Exceeded"),
|
|
|
|
cell("/body/tbl[2]/tr[4]/tc[1]", text="", vmerge="continue", shd="DEEAF6"),
|
|
cell("/body/tbl[2]/tr[4]/tc[2]", text="Consulting Services"),
|
|
cell("/body/tbl[2]/tr[4]/tc[3]", text="200.00", align="right"),
|
|
cell("/body/tbl[2]/tr[4]/tc[4]", text="185.30", align="right", color="FF0000"),
|
|
cell("/body/tbl[2]/tr[4]/tc[5]", text="Below target"),
|
|
|
|
cell("/body/tbl[2]/tr[5]/tc[1]", text="", vmerge="continue", shd="DEEAF6"),
|
|
cell("/body/tbl[2]/tr[5]/tc[2]", text="Tech Licensing"),
|
|
cell("/body/tbl[2]/tr[5]/tc[3]", text="80.00", align="right"),
|
|
cell("/body/tbl[2]/tr[5]/tc[4]", text="92.00", align="right", color="00B050"),
|
|
cell("/body/tbl[2]/tr[5]/tc[5]", text="New partners"),
|
|
|
|
# Expenses (merge 3 rows)
|
|
cell("/body/tbl[2]/tr[6]/tc[1]", text="Expenses", vmerge="restart",
|
|
valign="center", shd="FFF2CC", bold="true"),
|
|
cell("/body/tbl[2]/tr[6]/tc[2]", text="Labor Cost"),
|
|
cell("/body/tbl[2]/tr[6]/tc[3]", text="320.00", align="right"),
|
|
cell("/body/tbl[2]/tr[6]/tc[4]", text="335.00", align="right", color="FF0000"),
|
|
cell("/body/tbl[2]/tr[6]/tc[5]", text="New hires"),
|
|
|
|
cell("/body/tbl[2]/tr[7]/tc[1]", text="", vmerge="continue", shd="FFF2CC"),
|
|
cell("/body/tbl[2]/tr[7]/tc[2]", text="Operating Expenses"),
|
|
cell("/body/tbl[2]/tr[7]/tc[3]", text="150.00", align="right"),
|
|
cell("/body/tbl[2]/tr[7]/tc[4]", text="142.80", align="right", color="00B050"),
|
|
cell("/body/tbl[2]/tr[7]/tc[5]", text="Cost savings"),
|
|
|
|
cell("/body/tbl[2]/tr[8]/tc[1]", text="", vmerge="continue", shd="FFF2CC"),
|
|
cell("/body/tbl[2]/tr[8]/tc[2]", text="R&D Investment"),
|
|
cell("/body/tbl[2]/tr[8]/tc[3]", text="180.00", align="right"),
|
|
cell("/body/tbl[2]/tr[8]/tc[4]", text="195.50", align="right"),
|
|
cell("/body/tbl[2]/tr[8]/tc[5]", text="Strategic investment"),
|
|
|
|
# ==================== Table 3: Skill Assessment Matrix ====================
|
|
# color heatmap: Expert=00B050 Proficient=92D050 Familiar=FFC000 Beginner=FF0000
|
|
para(""),
|
|
para("3. Skill Assessment Matrix", style="Heading2"),
|
|
table(rows="6", cols="7"),
|
|
|
|
# Header
|
|
cell("/body/tbl[3]/tr[1]/tc[1]", text="Name/Skill", bold="true",
|
|
shd="002060", color="FFFFFF", align="center"),
|
|
cell("/body/tbl[3]/tr[1]/tc[2]", text="Python", bold="true",
|
|
shd="002060", color="FFFFFF", align="center"),
|
|
cell("/body/tbl[3]/tr[1]/tc[3]", text="Java", bold="true",
|
|
shd="002060", color="FFFFFF", align="center"),
|
|
cell("/body/tbl[3]/tr[1]/tc[4]", text="Frontend", bold="true",
|
|
shd="002060", color="FFFFFF", align="center"),
|
|
cell("/body/tbl[3]/tr[1]/tc[5]", text="Database", bold="true",
|
|
shd="002060", color="FFFFFF", align="center"),
|
|
cell("/body/tbl[3]/tr[1]/tc[6]", text="DevOps", bold="true",
|
|
shd="002060", color="FFFFFF", align="center"),
|
|
cell("/body/tbl[3]/tr[1]/tc[7]", text="AI/ML", bold="true",
|
|
shd="002060", color="FFFFFF", align="center"),
|
|
]
|
|
|
|
# Skill rows: person name in tc[1], then 6 graded skill cells (text:color).
|
|
skill_rows = [
|
|
(2, "John", ["Expert:00B050", "Proficient:92D050", "Familiar:FFC000",
|
|
"Expert:00B050", "Familiar:FFC000", "Expert:00B050"]),
|
|
(3, "Sarah", ["Proficient:92D050", "Expert:00B050", "Expert:00B050",
|
|
"Proficient:92D050", "Familiar:FFC000", "Beginner:FF0000"]),
|
|
(4, "Mike", ["Familiar:FFC000", "Familiar:FFC000", "Expert:00B050",
|
|
"Familiar:FFC000", "Expert:00B050", "Proficient:92D050"]),
|
|
(5, "Emily", ["Expert:00B050", "Beginner:FF0000", "Familiar:FFC000",
|
|
"Expert:00B050", "Proficient:92D050", "Familiar:FFC000"]),
|
|
(6, "David", ["Proficient:92D050", "Proficient:92D050", "Proficient:92D050",
|
|
"Expert:00B050", "Expert:00B050", "Expert:00B050"]),
|
|
]
|
|
for row, person, cells in skill_rows:
|
|
items.append(cell(f"/body/tbl[3]/tr[{row}]/tc[1]", text=person,
|
|
bold="true", shd="D6DCE4", align="center"))
|
|
for i, spec in enumerate(cells):
|
|
text, clr = spec.split(":", 1)
|
|
items.append(cell(f"/body/tbl[3]/tr[{row}]/tc[{i + 2}]", text=text,
|
|
shd=clr, color="FFFFFF", align="center", bold="true"))
|
|
|
|
items += [
|
|
# ==================== Table 4: Property Coverage Table ====================
|
|
# border.all + cellSpacing + colWidths + direction + indent + layout + padding
|
|
para(""),
|
|
para("4. Property Coverage (border / layout / direction / cell formatting)",
|
|
style="Heading2"),
|
|
table(rows="3", cols="4",
|
|
**{"border.all": "single;8;2E74B5",
|
|
"colWidths": "2500,2500,2500,2500",
|
|
"cellSpacing": "20",
|
|
"indent": "200",
|
|
"layout": "fixed",
|
|
"padding": "80"}),
|
|
|
|
# Override outer-edge borders after creation
|
|
cell("/body/tbl[4]", **{"border.top": "double;8;1F3864"}),
|
|
cell("/body/tbl[4]", **{"border.bottom": "double;8;1F3864"}),
|
|
cell("/body/tbl[4]", **{"border.left": "double;8;1F3864"}),
|
|
cell("/body/tbl[4]", **{"border.right": "double;8;1F3864"}),
|
|
cell("/body/tbl[4]", **{"border.horizontal": "single;4;9DC3E6"}),
|
|
cell("/body/tbl[4]", **{"border.vertical": "single;4;9DC3E6"}),
|
|
|
|
# Header row: header=true + height.exact
|
|
cell("/body/tbl[4]/tr[1]", header="true", **{"height.exact": "400"}),
|
|
cell("/body/tbl[4]/tr[1]/tc[1]", text="Cell Borders", bold="true",
|
|
fill="2E74B5", color="FFFFFF"),
|
|
cell("/body/tbl[4]/tr[1]/tc[2]", text="Run Formatting", bold="true",
|
|
fill="2E74B5", color="FFFFFF"),
|
|
cell("/body/tbl[4]/tr[1]/tc[3]", text="Merge / Flow", bold="true",
|
|
fill="2E74B5", color="FFFFFF"),
|
|
cell("/body/tbl[4]/tr[1]/tc[4]", text="Padding / Grid", bold="true",
|
|
fill="2E74B5", color="FFFFFF"),
|
|
|
|
# Data row 2: cell borders (border.all, tl2br, tr2bl), direction, nowrap
|
|
cell("/body/tbl[4]/tr[2]/tc[1]", text="border.all + tl2br + tr2bl",
|
|
**{"border.all": "single;8;FF0000",
|
|
"border.tl2br": "single;4;0000FF",
|
|
"border.tr2bl": "single;4;0000FF"}),
|
|
cell("/body/tbl[4]/tr[2]/tc[2]",
|
|
text="font + italic + strike + underline + highlight",
|
|
font="Times New Roman", italic="true", strike="true",
|
|
underline="single", highlight="yellow"),
|
|
cell("/body/tbl[4]/tr[2]/tc[3]",
|
|
text="direction=rtl + nowrap + textDirection=btlr",
|
|
direction="rtl", nowrap="true", textDirection="btlr"),
|
|
cell("/body/tbl[4]/tr[2]/tc[4]", text="padding per side + skipGridSync",
|
|
**{"padding.top": "50", "padding.bottom": "150",
|
|
"padding.left": "80", "padding.right": "80"}),
|
|
|
|
# Data row 3: border.top/bottom/left/right per cell, fitText, skipGridSync
|
|
cell("/body/tbl[4]/tr[3]/tc[1]", text="border.top + border.bottom",
|
|
**{"border.top": "single;8;FF0000", "border.bottom": "single;8;0000FF"}),
|
|
cell("/body/tbl[4]/tr[3]/tc[2]", text="border.left + border.right",
|
|
**{"border.left": "single;8;00FF00", "border.right": "single;8;FF00FF"}),
|
|
cell("/body/tbl[4]/tr[3]/tc[3]", text="fitText squeezes text to cell width",
|
|
fitText="true"),
|
|
cell("/body/tbl[4]/tr[3]/tc[4]", text="width + skipGridSync",
|
|
width="2500", skipGridSync="true"),
|
|
|
|
# ==================== Table 5: hmerge (horizontal merge) ====================
|
|
# hmerge=restart on tc[1] spans 2 cols and absorbs tc[2]; tc[3]->tc[2] after
|
|
table(rows="2", cols="3", **{"border.all": "single;4;808080"}),
|
|
# Set the non-merged cell before applying hmerge=restart (which removes tc[2])
|
|
cell("/body/tbl[5]/tr[1]/tc[3]", text="normal tc"),
|
|
cell("/body/tbl[5]/tr[1]/tc[1]", text="hmerge restart (spans 2 cols)",
|
|
hmerge="restart"),
|
|
# After hmerge=restart, original tc[3] is now tc[2]
|
|
cell("/body/tbl[5]/tr[2]/tc[1]", text="row 2 col 1"),
|
|
cell("/body/tbl[5]/tr[2]/tc[2]", text="row 2 col 2"),
|
|
cell("/body/tbl[5]/tr[2]/tc[3]", text="row 2 col 3"),
|
|
|
|
# ==================== Table 6: RTL table ====================
|
|
table(rows="2", cols="2",
|
|
**{"direction": "rtl", "border.all": "single;8;C00000"}),
|
|
cell("/body/tbl[6]/tr[1]/tc[1]", text="RTL table", bold="true"),
|
|
cell("/body/tbl[6]/tr[1]/tc[2]", text="column order mirrored"),
|
|
cell("/body/tbl[6]/tr[2]/tc[1]", text="row 2 col 1"),
|
|
cell("/body/tbl[6]/tr[2]/tc[2]", text="row 2 col 2"),
|
|
|
|
# ==================== Table 7: inline data= shorthand ====================
|
|
# rows separated by ';', cells by ',' — builds the whole grid in one prop
|
|
table(**{"data": "Region,Q1,Q2;North,120,150;South,90,110",
|
|
"border.all": "single;4;808080"}),
|
|
]
|
|
|
|
doc.batch(items)
|
|
print(f" added {len(items)} paragraphs/tables/cell-sets")
|
|
|
|
print(f"Generated: {FILE}")
|