A workbook declares a merged cell range A1:C1 in the worksheet part and carries the merged-region text in the top-left cell.
Library verdicts: python-xlsx: — xlsxjs: pass
| Feature id | xlsx/merged-cell |
|---|---|
| Format | xlsx |
| Category | cell-layout |
| Spec | ecma-376-5-part-1 § 18.3.1.55 mergeCell |
| ID / part | Predicate | XPath | python-xlsx | xlsxjs |
|---|---|---|---|---|
merge-cell-declaredxl/worksheets/sheet1.xml | existThe worksheet must declare a merged cell range A1:C1 via a <mergeCell ref='A1:C1'/> element inside <mergeCells>. | //x:mergeCells/x:mergeCell[@ref='A1:C1'] | — | — |
shared-string-presentxl/sharedStrings.xml | existThe shared-strings table must contain 'Merged header' (the top-left cell's value). | //x:sst/x:si/x:t[normalize-space()='Merged header'] | — | — |
| ID | Predicate | Selector | python-xlsx | xlsxjs |
|---|---|---|---|---|
merged-cell-text-present | css_selector/ match-text = Merged headerThe rendered sheet must contain a merged cell (colspan=3 or marked as merged via class/attribute) showing the text 'Merged header'. | section.xlsx td[colspan='3'], section.xlsx td.merged-col-3, section.xlsx td[data-merged] | — | pass |
merged-cell-colspan-exists | css_selector/ existAt least one rendered <td> must carry a colspan attribute (the merged A1:C1 range). Catches renderers that drop the merge. | section.xlsx td[colspan] | — | — |
sheet-count | css_selector/ equal-countExactly one rendered sheet section. Catches renderers that emit zero sheets (hard failure) or duplicate a sheet. | section.xlsx | — | — |
scripts/gen_merged_cell.py
#!/usr/bin/env python3
"""Generate ``fixtures/xlsx/merged-cell.xlsx``.
A minimal one-sheet workbook with cells A1:C1 merged into a single region
containing ``Merged header``. Designed to satisfy the assertions in
``features/xlsx/merged-cell.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" / "merged-cell.xlsx"
def main() -> int:
wb = Workbook()
ws = wb.active
ws["A1"] = "Merged header"
ws.merge_cells("A1:C1")
_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/merged-cell",
"title": "Merged cell range",
"format": "xlsx",
"category": "cell-layout",
"summary": "A workbook declares a merged cell range A1:C1 in the worksheet part and carries the merged-region text in the top-left cell.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "18.3.1.55",
"element": "mergeCell",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/SpreadsheetML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/sml.xsd",
"notes": "Merged cells in SpreadsheetML live in a <mergeCells> container inside the worksheet part, as <mergeCell ref=\"A1:C1\"/> entries. Only the top-left cell of the range carries a value; the other cells in the range are logically blank but physically present with their own <c> elements. The assertion here targets the <mergeCell> element's ref attribute. SpreadsheetML parts use a default namespace with no prefix; XPath therefore needs an explicit binding (here 'x')."
},
"fixtures": {
"machine": "xlsx/merged-cell",
"office": "xlsx/merged-cell"
},
"generator": {
"python": "scripts/gen_merged_cell.py"
},
"assertions": [
{
"id": "merge-cell-declared",
"part": "xl/worksheets/sheet1.xml",
"namespaces": {
"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
},
"xpath": "//x:mergeCells/x:mergeCell[@ref='A1:C1']",
"must": "exist",
"description": "The worksheet must declare a merged cell range A1:C1 via a <mergeCell ref='A1:C1'/> element inside <mergeCells>."
},
{
"id": "shared-string-present",
"part": "xl/sharedStrings.xml",
"namespaces": {
"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
},
"xpath": "//x:sst/x:si/x:t[normalize-space()='Merged header']",
"must": "exist",
"description": "The shared-strings table must contain 'Merged header' (the top-left cell's value)."
}
],
"render_assertions": [
{
"id": "merged-cell-text-present",
"kind": "css_selector",
"selector": "section.xlsx td[colspan='3'], section.xlsx td.merged-col-3, section.xlsx td[data-merged]",
"must": "match-text",
"value": "Merged header",
"description": "The rendered sheet must contain a merged cell (colspan=3 or marked as merged via class/attribute) showing the text 'Merged header'."
},
{
"id": "merged-cell-colspan-exists",
"kind": "css_selector",
"selector": "section.xlsx td[colspan]",
"must": "exist",
"description": "At least one rendered <td> must carry a colspan attribute (the merged A1:C1 range). Catches renderers that drop the merge."
},
{
"id": "sheet-count",
"kind": "css_selector",
"selector": "section.xlsx",
"must": "equal-count",
"count": 1,
"description": "Exactly one rendered sheet section. Catches renderers that emit zero sheets (hard failure) or duplicate a sheet."
}
]
}
