A workbook declares three sheets named 'First', 'Second', and 'Third' in its <sheets> catalogue.
Library verdicts: python-xlsx: — xlsxjs: pass
| Feature id | xlsx/multiple-sheets |
|---|---|
| Format | xlsx |
| Category | workbook-structure |
| Spec | ecma-376-5-part-1 § 18.2.2 sheets |
| ID / part | Predicate | XPath | python-xlsx | xlsxjs |
|---|---|---|---|---|
sheets-count-3xl/workbook.xml | match = ^3(\.0+)?$The workbook must declare exactly 3 sheets in its <sheets> catalogue. (XPath count() returns a float; lxml stringifies as '3.0', so we regex-match.) | count(//x:sheets/x:sheet) | — | — |
sheet-named-firstxl/workbook.xml | existA sheet named 'First' must appear in the workbook catalogue. | //x:sheets/x:sheet[@name='First'] | — | — |
sheet-named-secondxl/workbook.xml | existA sheet named 'Second' must appear in the workbook catalogue. | //x:sheets/x:sheet[@name='Second'] | — | — |
sheet-named-thirdxl/workbook.xml | existA sheet named 'Third' must appear in the workbook catalogue. | //x:sheets/x:sheet[@name='Third'] | — | — |
| ID | Predicate | Selector | python-xlsx | xlsxjs |
|---|---|---|---|---|
multi-sheet-sections | css_selector/ equal-countThe renderer must emit one section.xlsx per sheet (xlsxjs convention) — 3 sheets => 3 sections. | section.xlsx | — | pass |
scripts/gen_multiple_sheets.py
#!/usr/bin/env python3
"""Generate ``fixtures/xlsx/multiple-sheets.xlsx``.
A minimal three-sheet workbook with sheets named ``First``, ``Second``,
and ``Third``, each containing a single cell value. Designed to satisfy
the assertions in ``features/xlsx/multiple-sheets.json``.
"""
from __future__ import annotations
from pathlib import Path
from xlsx import Workbook
import datetime as _dt
_REPRODUCIBLE_DT = _dt.datetime(2000, 1, 1, 0, 0, 0)
_REPO_ROOT = Path(__file__).resolve().parent.parent
_OUT = _REPO_ROOT / "fixtures" / "xlsx" / "multiple-sheets.xlsx"
def main() -> int:
wb = Workbook()
ws1 = wb.active
ws1.title = "First"
ws1["A1"] = "Sheet one content"
ws2 = wb.create_sheet(title="Second")
ws2["A1"] = "Sheet two content"
ws3 = wb.create_sheet(title="Third")
ws3["A1"] = "Sheet three content"
_OUT.parent.mkdir(parents=True, exist_ok=True)
wb.save(_OUT, zip_date_time=_REPRODUCIBLE_DT)
print(_OUT)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "xlsx/multiple-sheets",
"title": "Workbook with multiple sheets",
"format": "xlsx",
"category": "workbook-structure",
"summary": "A workbook declares three sheets named 'First', 'Second', and 'Third' in its <sheets> catalogue.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "18.2.2",
"element": "sheets",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/SpreadsheetML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/sml.xsd",
"notes": "Workbook sheet enumeration in SpreadsheetML lives on <sheets> inside xl/workbook.xml, with one <sheet name='...' sheetId='...' r:id='...'/> per worksheet. The r:id attribute ties the sheet to a worksheet part via xl/_rels/workbook.xml.rels. Each sheet's data lives in its own part (xl/worksheets/sheet1.xml, sheet2.xml, sheet3.xml). SpreadsheetML parts use a default namespace with no prefix; XPath therefore needs an explicit binding (here 'x'). A single XPath assertion can only target one part, so the sheet-name presence check is split into three assertions, one per expected name."
},
"fixtures": {
"machine": "xlsx/multiple-sheets",
"office": "xlsx/multiple-sheets"
},
"generator": {
"python": "scripts/gen_multiple_sheets.py"
},
"assertions": [
{
"id": "sheets-count-3",
"part": "xl/workbook.xml",
"namespaces": {
"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
},
"xpath": "count(//x:sheets/x:sheet)",
"must": "match",
"value": "^3(\\.0+)?$",
"description": "The workbook must declare exactly 3 sheets in its <sheets> catalogue. (XPath count() returns a float; lxml stringifies as '3.0', so we regex-match.)"
},
{
"id": "sheet-named-first",
"part": "xl/workbook.xml",
"namespaces": {
"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
},
"xpath": "//x:sheets/x:sheet[@name='First']",
"must": "exist",
"description": "A sheet named 'First' must appear in the workbook catalogue."
},
{
"id": "sheet-named-second",
"part": "xl/workbook.xml",
"namespaces": {
"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
},
"xpath": "//x:sheets/x:sheet[@name='Second']",
"must": "exist",
"description": "A sheet named 'Second' must appear in the workbook catalogue."
},
{
"id": "sheet-named-third",
"part": "xl/workbook.xml",
"namespaces": {
"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
},
"xpath": "//x:sheets/x:sheet[@name='Third']",
"must": "exist",
"description": "A sheet named 'Third' must appear in the workbook catalogue."
}
],
"render_assertions": [
{
"id": "multi-sheet-sections",
"kind": "css_selector",
"selector": "section.xlsx",
"must": "equal-count",
"count": 3,
"description": "The renderer must emit one section.xlsx per sheet (xlsxjs convention) — 3 sheets => 3 sections."
}
]
}
