A worksheet carries a <table> (ListObject) over A1:C5 with a header row of column names and four data rows. Four parameter cases toggle headerRowCount, totalsRowCount, tableStyleInfo/@showColumnStripes.
| Library | Pass | Fail | Pending |
|---|---|---|---|
python-xlsx | 0 | 0 | 4 |
xlsxjs | 0 | 0 | 4 |
case (4 values): plain, with-totals-row, with-header-row, with-filterscripts/gen_table_listobject.py — runs with --arg_template --case {case.id} --out fixtures/xlsx/table-listobject--{case.id}.xlsx
#!/usr/bin/env python3
"""Generate ``fixtures/xlsx/table-listobject--<case>.xlsx`` fixtures.
Parameterised generator for the ``xlsx/table-listobject`` manifest.
Four cases drive ``Worksheet.add_table()`` with different toggles:
- ``plain`` — header row only.
- ``with-totals-row`` — ``totals_row={'Price': 'sum'}``.
- ``with-header-row`` — explicit ``header_row=True`` (default; pinned so
the manifest's equality assertion remains honest).
- ``with-filter`` — ``tableStyleInfo/@showColumnStripes=1`` via
``show_col_stripes=True`` and a built-in style.
python-xlsx emits the ListObject at ``xl/tables/table1.xml``.
"""
from __future__ import annotations
import argparse
import datetime as _dt
from pathlib import Path
from xlsx import Workbook
_REPRODUCIBLE_DT = _dt.datetime(2000, 1, 1, 0, 0, 0)
_REPO_ROOT = Path(__file__).resolve().parent.parent
_HEADER = ("Name", "Qty", "Price")
_DATA = (
("Apple", 10, 1.5),
("Pear", 20, 2.5),
("Plum", 30, 3.5),
("Kiwi", 40, 4.5),
)
def _seed(ws) -> None:
"""Write the 5-row data block into A1:C5."""
for j, v in enumerate(_HEADER, 1):
ws.cell(row=1, column=j, value=v)
for i, row in enumerate(_DATA, 2):
for j, v in enumerate(row, 1):
ws.cell(row=i, column=j, value=v)
def _seed_with_totals(ws) -> None:
"""Seed A1:C6 — header + 4 data rows + totals row placeholder."""
_seed(ws)
ws.cell(row=6, column=1, value="Total")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--case", required=True,
choices=("plain", "with-totals-row",
"with-header-row", "with-filter"))
parser.add_argument("--out", required=True)
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = _REPO_ROOT / out_path
wb = Workbook()
ws = wb.active
ws.title = "Sheet1"
if args.case == "plain":
_seed(ws)
ws.add_table("A1:C5", name="TestTable", display_name="TestTable")
elif args.case == "with-totals-row":
_seed_with_totals(ws)
ws.add_table("A1:C6", name="TestTable", display_name="TestTable",
totals_row={"Price": "sum"})
elif args.case == "with-header-row":
_seed(ws)
ws.add_table("A1:C5", name="TestTable", display_name="TestTable",
header_row=True)
elif args.case == "with-filter":
_seed(ws)
ws.add_table("A1:C5", name="TestTable", display_name="TestTable",
style="TableStyleMedium9", show_col_stripes=True)
out_path.parent.mkdir(parents=True, exist_ok=True)
wb.save(out_path, zip_date_time=_REPRODUCIBLE_DT)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "xlsx/table-listobject",
"kind": "parameterised",
"title": "ListObject (structured table) — 4 toggle variants",
"format": "xlsx",
"category": "workbook-structure",
"summary": "A worksheet carries a <table> (ListObject) over A1:C5 with a header row of column names and four data rows. Four parameter cases toggle headerRowCount, totalsRowCount, tableStyleInfo/@showColumnStripes.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "18.5",
"element": "table",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/SpreadsheetML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/sml.xsd",
"notes": "A <table> (ListObject) part lives at xl/tables/tableN.xml, referenced from a worksheet via a table-relationship. @displayName identifies the table in the UI; @ref is the cell-range the table covers. Four variants exercise the common toggles: 'plain' is a vanilla header-only table; 'with-totals-row' adds @totalsRowCount=1 and a TableColumn/@totalsRowFunction aggregate; 'with-header-row' pins the default @headerRowCount=1 explicitly so the assertion is an equality rather than presence check; 'with-filter' adds a <tableStyleInfo> with @showColumnStripes=1. python-xlsx's Worksheet.add_table() drives all four shapes via the same call signature with different kwargs; the emitted part uses the default SpreadsheetML namespace (XPath binds 'x')."
},
"fixtures": {
"machine": "xlsx/table-listobject"
},
"generator": {
"python": "scripts/gen_table_listobject.py",
"arg_template": "--case {case.id} --out fixtures/xlsx/table-listobject--{case.id}.xlsx"
},
"parameters": {
"case": [
{
"id": "plain",
"xpath": "//x:table[@displayName='TestTable']/@ref",
"must": "equal",
"value": "A1:C5"
},
{
"id": "with-totals-row",
"xpath": "//x:table[@displayName='TestTable']/x:tableColumns/x:tableColumn[@name='Price']/@totalsRowFunction",
"must": "equal",
"value": "sum"
},
{
"id": "with-header-row",
"xpath": "//x:table[@displayName='TestTable']/@headerRowCount",
"must": "equal",
"value": "1"
},
{
"id": "with-filter",
"xpath": "//x:table[@displayName='TestTable']/x:tableStyleInfo/@showColumnStripes",
"must": "equal",
"value": "1"
}
]
},
"assertions_template": [
{
"id": "table-present-{case.id}",
"part": "xl/tables/table1.xml",
"namespaces": {
"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
},
"xpath": "//x:table[@displayName='TestTable']",
"must": "exist",
"description": "xl/tables/table1.xml must carry a <table> whose @displayName equals 'TestTable'."
},
{
"id": "table-variant-{case.id}",
"part": "xl/tables/table1.xml",
"namespaces": {
"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
},
"xpath": "{case.xpath}",
"must": "{case.must}",
"value": "{case.value}",
"description": "The case-specific ListObject attribute must match the expected value."
}
]
}