Files
wehub-resource-sync 8cb1f9f479
Publish SDK (PyPI) / publish (push) Has been cancelled
Publish SDK (npm) / publish (@aionui/officecli-sdk) (push) Has been cancelled
SDK smoke / smoke (windows-latest) (push) Has been cancelled
Publish SDK (npm) / publish (@officecli/officecli-sdk) (push) Has been cancelled
Publish SDK (npm) / publish (@officecli/sdk) (push) Has been cancelled
Publish SDK (npm) / publish (officecli-sdk) (push) Has been cancelled
SDK smoke / smoke (macos-latest) (push) Has been cancelled
SDK smoke / smoke (ubuntu-latest) (push) Has been cancelled
Skill parity / diff (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:09:29 +08:00

158 lines
9.0 KiB
Python

#!/usr/bin/env python3
"""
tables-nested.py — BUILD, NAVIGATE, and FULLY EXERCISE a nested pptx element:
the table tree (slide → table → tr → tc). 4 slides, so the full property surface
of each level fits without cramming one table:
Slide 1 Structure & ownership — levels, path tokens, property ownership,
colspan, navigation/readback.
Slide 2 Table-level surface — every `table` property (banding, fills,
per-side borders, sizing, name/zorder, data).
Slide 3 Cell box surface — every `tc` box property (borders incl.
diagonals, padding, valign, wrap, textdir,
direction, bevel, opacity, image fill, merge).
Slide 4 Cell text surface — every `tc` text property (font, size, weight,
underline/strike, color, align, line/para spacing).
Coverage: 100% of the settable props on pptx table / table-row / table-cell
(`help pptx <element> --json`), EXCEPT `id` — table ids are auto-assigned and
must stay unique, so it's settable for round-trip fidelity but never set by hand.
SDK twin of tables-nested.sh, mapped one-for-one (no batch):
officecli.create(...) ≈ create + open ; doc.send({...}) ≈ one set/add ; doc.close() ≈ close
Usage:
pip install officecli-sdk
python3 tables-nested.py
"""
import os
import base64
import officecli # pip install officecli-sdk
FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tables-nested.pptx")
# 1x1 PNG for the cell image-fill demo (slide 3); image= needs a real file path.
IMG = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".cell-dot.png")
with open(IMG, "wb") as fh:
fh.write(base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="))
print(f"Building {FILE} ...")
doc = officecli.create(FILE, "--force") # create the .pptx + start its resident
def add(parent, type_, **props):
doc.send({"command": "add", "parent": parent, "type": type_, "props": props})
def setp(path, **props): # one `officecli set`
doc.send({"command": "set", "path": path, "props": props})
def cell(sl, p, **props): # set on /slide[sl]/table[1]/<p>
setp(f"/slide[{sl}]/table[1]/{p}", **props)
def title(sl, text):
add(f"/slide[{sl}]", "shape", geometry="rect", x="1.2cm", y="0.6cm",
width="30cm", height="1.3cm", fill="none", line="none",
text=text, size="20", bold="true", color="1F4E79")
# ═══════════════ SLIDE 1 — Structure & ownership ═══════════════
add("/", "slide")
title(1, "1 · Structure & ownership (slide → table → tr → tc)")
# rows/cols/colWidths are add-time structure; style/banding are settable later.
add("/slide[1]", "table", rows="5", cols="3", x="2.5cm", y="2.4cm",
width="28cm", height="9cm", colWidths="12cm,8cm,8cm") # → /slide[1]/table[1]
setp("/slide[1]/table[1]", style="medium2-accent1", firstRow="true", bandedRows="true") # table owns style + banding
setp("/slide[1]/table[1]/tr[1]", height="2cm") # a row owns only its height
for c, label in enumerate(["Region", "Units", "Revenue"], start=1):
cell(1, f"tr[1]/tc[{c}]", text=label, bold="true", color="FFFFFF",
align="center", valign="middle", fill="1F6FEB") # cell owns box + text
for r, (region, units, rev) in enumerate(
[("North", "1,240", "$11,780"), ("South", "980", "$9,310"),
("East", "1,520", "$14,440")], start=2):
cell(1, f"tr[{r}]/tc[1]", text=region, align="left", valign="middle")
cell(1, f"tr[{r}]/tc[2]", text=units, align="right", valign="middle")
cell(1, f"tr[{r}]/tc[3]", text=rev, align="right", valign="middle")
# Nesting-only op: colspan (alias gridspan). Total row spans all 3 columns.
cell(1, "tr[5]/tc[1]", colspan="3", valign="middle", bold="true", align="center",
text="TOTAL 3,740 units $35,530", fill="DDEBF7")
# Navigate: address a deep node AFTER building — same path that built it reaches it.
node = doc.send({"command": "get", "path": "/slide[1]/table[1]/tr[4]/tc[3]"})
print(" deep readback:", node.get("data", {}).get("results", [{}])[0].get("text"))
cell(1, "tr[4]/tc[3]", fill="FFF2CC", bold="true")
# ═══════════════ SLIDE 2 — Table-level full surface ═══════════════
add("/", "slide")
title(2, "2 · Table level — banding · fills · per-side borders · sizing")
# data= bulk-fills + defines the grid (rows ';', cols ','); zorder/rowHeight/
# colWidths/header+body fills are add-only. `id` is intentionally omitted (auto).
add("/slide[2]", "table", x="2cm", y="2.4cm", width="29cm", height="9cm",
data="Q,FY24,FY25,Growth;Q1,120,138,+15%;Q2,95,121,+27%;Q3,140,162,+16%",
zorder="2", rowHeight="1.8cm", colWidths="8cm,7cm,7cm,7cm",
headerFill="1F6FEB", bodyFill="EEF3FB")
setp("/slide[2]/table[1]", name="QuarterlySales",
firstRow="true", lastRow="true", firstCol="true", lastCol="false",
bandedRows="true", bandedCols="false")
setp("/slide[2]/table[1]", **{
"border.all": "1pt solid B7C7E0",
"border.top": "3pt solid 1F4E79", "border.bottom": "3pt solid 1F4E79",
"border.left": "1.5pt solid 1F6FEB", "border.right": "1.5pt solid 1F6FEB",
"border.horizontal": "1pt solid CCD8EC", "border.vertical": "1pt solid CCD8EC"})
# ═══════════════ SLIDE 3 — Cell box full surface ═══════════════
add("/", "slide")
title(3, "3 · Cell box — borders · padding · valign · direction · bevel · opacity · image · merge")
add("/slide[3]", "table", rows="5", cols="4", x="2cm", y="2.4cm",
width="29cm", height="12cm", style="none")
# Row 1 — per-side, full, and diagonal borders (one kind per cell)
cell(3, "tr[1]/tc[1]", text="border.all", **{"border.all": "1.5pt solid 1F6FEB"})
cell(3, "tr[1]/tc[2]", text="top+bottom", **{"border.top": "3pt solid C00000", "border.bottom": "3pt solid C00000"})
cell(3, "tr[1]/tc[3]", text="left+right", **{"border.left": "3pt solid 2DA44E", "border.right": "3pt solid 2DA44E"})
cell(3, "tr[1]/tc[4]", text="diagonals", **{"border.tl2br": "1.5pt solid BF8700", "border.tr2bl": "1.5pt solid BF8700"})
# Row 2 — fill, opacity, bevel, image fill
cell(3, "tr[2]/tc[1]", text="fill", fill="FFE699")
cell(3, "tr[2]/tc[2]", text="opacity=0.5", fill="1F6FEB", opacity="0.5")
cell(3, "tr[2]/tc[3]", text="bevel=circle", fill="DDEBF7", bevel="circle")
cell(3, "tr[2]/tc[4]", text="image fill", image=IMG)
# Row 3 — padding, padding.bottom, valign (top + bottom)
cell(3, "tr[3]/tc[1]", text="padding=0.4cm", padding="0.4cm", fill="F2F2F2")
cell(3, "tr[3]/tc[2]", text="padding.bottom=0.5cm", **{"padding.bottom": "0.5cm", "fill": "F2F2F2"})
cell(3, "tr[3]/tc[3]", text="valign=top", valign="top", fill="F2F2F2")
cell(3, "tr[3]/tc[4]", text="valign=bottom", valign="bottom", fill="F2F2F2")
# Row 4 — wrap, vertical text, RTL, and merge.down (eats the cell below it)
cell(3, "tr[4]/tc[1]", text="wrap=false: this long line will not wrap inside the cell", wrap="false", fill="E2EFDA")
cell(3, "tr[4]/tc[2]", text="textdir=vertical270", textdirection="vertical270", fill="E2EFDA")
cell(3, "tr[4]/tc[3]", text="direction=rtl العربية", direction="rtl", fill="E2EFDA")
cell(3, "tr[4]/tc[4]", text="merge.down=1 ↓", align="center", fill="FCE4D6", **{"merge.down": "1"})
# Row 5 — merge.right (eats the cell to its right); tc[4] swallowed by merge.down above.
cell(3, "tr[5]/tc[1]", text="merge.right=2 →", align="center", fill="FCE4D6", **{"merge.right": "2"})
# ═══════════════ SLIDE 4 — Cell text full surface ═══════════════
add("/", "slide")
title(4, "4 · Cell text — font · size · weight · underline/strike · color · align · spacing")
add("/slide[4]", "table", rows="4", cols="3", x="2cm", y="2.4cm",
width="29cm", height="10cm", style="light1")
cell(4, "tr[1]/tc[1]", text="font=Georgia", font="Georgia")
cell(4, "tr[1]/tc[2]", text="size=20pt", size="20pt")
cell(4, "tr[1]/tc[3]", text="color", color="C00000")
cell(4, "tr[2]/tc[1]", text="bold", bold="true")
cell(4, "tr[2]/tc[2]", text="italic", italic="true")
cell(4, "tr[2]/tc[3]", text="underline", underline="double")
cell(4, "tr[3]/tc[1]", text="strike", strike="single")
cell(4, "tr[3]/tc[2]", text="align=center", align="center")
cell(4, "tr[3]/tc[3]", text="align=right", align="right")
cell(4, "tr[4]/tc[1]", text="linespacing=1.5x — line one is followed by line two in this cell", linespacing="1.5x")
cell(4, "tr[4]/tc[2]", text="spacebefore=10pt", spacebefore="10pt")
cell(4, "tr[4]/tc[3]", text="spaceafter=10pt", spaceafter="10pt")
setp("/slide[4]/table[1]/tr[4]", height="2.4cm") # table-row also owns height
# Validate over the pipe (in-session), then close.
v = doc.send({"command": "validate"})
print(" Validation passed: no errors found." if v.get("success") else f" {v.get('warnings')}")
doc.close()
os.remove(IMG)
print(f"Created: {FILE}")