docx/table-border-style

Each ST_Border line style applied to a single edge of w:tblBorders (top / left / bottom / right). Expands to 20 styles x 4 sides = 80 cases.

Cases (80)

Feature IDAxis bindingspython-docxdocxjs
docx/table-border-style--bottom--dash-dot-strokedside=bottom, style=dash-dot-strokedpass
docx/table-border-style--bottom--dash-small-gapside=bottom, style=dash-small-gappass
docx/table-border-style--bottom--dashedside=bottom, style=dashedpass
docx/table-border-style--bottom--dot-dashside=bottom, style=dot-dashpass
docx/table-border-style--bottom--dot-dot-dashside=bottom, style=dot-dot-dashpass
docx/table-border-style--bottom--dottedside=bottom, style=dottedpass
docx/table-border-style--bottom--doubleside=bottom, style=doublepass
docx/table-border-style--bottom--insetside=bottom, style=insetpass
docx/table-border-style--bottom--nilside=bottom, style=nilpass
docx/table-border-style--bottom--outsetside=bottom, style=outsetpass
docx/table-border-style--bottom--singleside=bottom, style=singlepass
docx/table-border-style--bottom--thickside=bottom, style=thickpass
docx/table-border-style--bottom--thick-thin-small-gapside=bottom, style=thick-thin-small-gappass
docx/table-border-style--bottom--thin-thick-small-gapside=bottom, style=thin-thick-small-gappass
docx/table-border-style--bottom--three-d-embossside=bottom, style=three-d-embosspass
docx/table-border-style--bottom--three-d-engraveside=bottom, style=three-d-engravepass
docx/table-border-style--bottom--tripleside=bottom, style=triplepass
docx/table-border-style--bottom--two-dashesside=bottom, style=two-dashespass
docx/table-border-style--bottom--waveside=bottom, style=wavepass
docx/table-border-style--bottom--wavy-lineside=bottom, style=wavy-linepass
docx/table-border-style--left--dash-dot-strokedside=left, style=dash-dot-strokedpass
docx/table-border-style--left--dash-small-gapside=left, style=dash-small-gappass
docx/table-border-style--left--dashedside=left, style=dashedpass
docx/table-border-style--left--dot-dashside=left, style=dot-dashpass
docx/table-border-style--left--dot-dot-dashside=left, style=dot-dot-dashpass
docx/table-border-style--left--dottedside=left, style=dottedpass
docx/table-border-style--left--doubleside=left, style=doublepass
docx/table-border-style--left--insetside=left, style=insetpass
docx/table-border-style--left--nilside=left, style=nilpass
docx/table-border-style--left--outsetside=left, style=outsetpass
docx/table-border-style--left--singleside=left, style=singlepass
docx/table-border-style--left--thickside=left, style=thickpass
docx/table-border-style--left--thick-thin-small-gapside=left, style=thick-thin-small-gappass
docx/table-border-style--left--thin-thick-small-gapside=left, style=thin-thick-small-gappass
docx/table-border-style--left--three-d-embossside=left, style=three-d-embosspass
docx/table-border-style--left--three-d-engraveside=left, style=three-d-engravepass
docx/table-border-style--left--tripleside=left, style=triplepass
docx/table-border-style--left--two-dashesside=left, style=two-dashespass
docx/table-border-style--left--waveside=left, style=wavepass
docx/table-border-style--left--wavy-lineside=left, style=wavy-linepass
docx/table-border-style--right--dash-dot-strokedside=right, style=dash-dot-strokedpass
docx/table-border-style--right--dash-small-gapside=right, style=dash-small-gappass
docx/table-border-style--right--dashedside=right, style=dashedpass
docx/table-border-style--right--dot-dashside=right, style=dot-dashpass
docx/table-border-style--right--dot-dot-dashside=right, style=dot-dot-dashpass
docx/table-border-style--right--dottedside=right, style=dottedpass
docx/table-border-style--right--doubleside=right, style=doublepass
docx/table-border-style--right--insetside=right, style=insetpass
docx/table-border-style--right--nilside=right, style=nilpass
docx/table-border-style--right--outsetside=right, style=outsetpass
docx/table-border-style--right--singleside=right, style=singlepass
docx/table-border-style--right--thickside=right, style=thickpass
docx/table-border-style--right--thick-thin-small-gapside=right, style=thick-thin-small-gappass
docx/table-border-style--right--thin-thick-small-gapside=right, style=thin-thick-small-gappass
docx/table-border-style--right--three-d-embossside=right, style=three-d-embosspass
docx/table-border-style--right--three-d-engraveside=right, style=three-d-engravepass
docx/table-border-style--right--tripleside=right, style=triplepass
docx/table-border-style--right--two-dashesside=right, style=two-dashespass
docx/table-border-style--right--waveside=right, style=wavepass
docx/table-border-style--right--wavy-lineside=right, style=wavy-linepass
docx/table-border-style--top--dash-dot-strokedside=top, style=dash-dot-strokedpass
docx/table-border-style--top--dash-small-gapside=top, style=dash-small-gappass
docx/table-border-style--top--dashedside=top, style=dashedpass
docx/table-border-style--top--dot-dashside=top, style=dot-dashpass
docx/table-border-style--top--dot-dot-dashside=top, style=dot-dot-dashpass
docx/table-border-style--top--dottedside=top, style=dottedpass
docx/table-border-style--top--doubleside=top, style=doublepass
docx/table-border-style--top--insetside=top, style=insetpass
docx/table-border-style--top--nilside=top, style=nilpass
docx/table-border-style--top--outsetside=top, style=outsetpass
docx/table-border-style--top--singleside=top, style=singlepass
docx/table-border-style--top--thickside=top, style=thickpass
docx/table-border-style--top--thick-thin-small-gapside=top, style=thick-thin-small-gappass
docx/table-border-style--top--thin-thick-small-gapside=top, style=thin-thick-small-gappass
docx/table-border-style--top--three-d-embossside=top, style=three-d-embosspass
docx/table-border-style--top--three-d-engraveside=top, style=three-d-engravepass
docx/table-border-style--top--tripleside=top, style=triplepass
docx/table-border-style--top--two-dashesside=top, style=two-dashespass
docx/table-border-style--top--waveside=top, style=wavepass
docx/table-border-style--top--wavy-lineside=top, style=wavy-linepass

Aggregate

LibraryPassFailPending
python-docx0080
docxjs8000

Parameter axes

Spec notes

<w:tblBorders> inside <w:tblPr> carries per-edge border child elements (w:top, w:left, w:bottom, w:right, w:insideH, w:insideV). Each child's w:val attribute is an ST_Border line style. ECMA-376 Part 1 clause 17.18.2 enumerates ST_Border; this family covers 20 common values on the four outer edges. Rendering of specific border styles varies wildly across consumers, so only a smoke render check is asserted.

Generator source

scripts/gen_table_border_style.py — runs with --arg_template --style {style.val} --side {side.id} --out fixtures/docx/table-border-style--{side.id}--{style.id}.docx

#!/usr/bin/env python3
"""Generate ``fixtures/docx/table-border-style--<style>--<side>.docx``.

Parameterised generator for the ``docx/table-border-style`` family: writes a
single-cell table whose ``w:tblBorders`` declares a single edge with the
requested ``ST_Border`` line style. Expanded at runtime by the manifest's
``generator.arg_template`` into 20 styles x 4 sides = 80 cases.

Uses raw ``OxmlElement`` rather than the fork's high-level
``Table.set_borders`` API because ``set_borders`` only accepts the
``WD_BORDER_STYLE`` enum, which does not cover every ``ST_Border`` value
enumerated in ECMA-376 Part 1 clause 17.18.2.
"""

from __future__ import annotations

import argparse
from pathlib import Path

from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qn

_REPO_ROOT = Path(__file__).resolve().parent.parent

_VALID_SIDES = {"top", "left", "bottom", "right", "insideH", "insideV"}


def _write(style_val: str, side: str, out: Path) -> None:
    if side not in _VALID_SIDES:
        raise ValueError(
            f"Unknown side {side!r}; expected one of {sorted(_VALID_SIDES)}"
        )

    doc = Document()
    table = doc.add_table(rows=1, cols=1)
    table.rows[0].cells[0].text = "Border test cell"

    tblPr = table._tbl.tblPr
    tblBorders = OxmlElement("w:tblBorders")
    edge = OxmlElement(f"w:{side}")
    edge.set(qn("w:val"), style_val)
    edge.set(qn("w:sz"), "8")
    edge.set(qn("w:space"), "0")
    edge.set(qn("w:color"), "000000")
    tblBorders.append(edge)
    tblPr.append(tblBorders)

    out.parent.mkdir(parents=True, exist_ok=True)
    doc.save(out, reproducible=True)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--style", required=True, help="ST_Border val, e.g. 'single'")
    parser.add_argument(
        "--side",
        required=True,
        help="Edge element local name: top | left | bottom | right (also insideH/insideV)",
    )
    parser.add_argument("--out", required=True, help="Output .docx path")
    args = parser.parse_args()

    out_path = Path(args.out)
    if not out_path.is_absolute():
        out_path = _REPO_ROOT / out_path
    _write(args.style, args.side, out_path)
    print(out_path)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Manifest (parent, unexpanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/table-border-style",
  "kind": "parameterised",
  "title": "Table border style",
  "format": "docx",
  "category": "tables",
  "summary": "Each ST_Border line style applied to a single edge of w:tblBorders (top / left / bottom / right). Expands to 20 styles x 4 sides = 80 cases.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "17.4.38",
    "element": "w:tblBorders",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
    "notes": "<w:tblBorders> inside <w:tblPr> carries per-edge border child elements (w:top, w:left, w:bottom, w:right, w:insideH, w:insideV). Each child's w:val attribute is an ST_Border line style. ECMA-376 Part 1 clause 17.18.2 enumerates ST_Border; this family covers 20 common values on the four outer edges. Rendering of specific border styles varies wildly across consumers, so only a smoke render check is asserted."
  },
  "fixtures": {
    "machine": "docx/table-border-style"
  },
  "generator": {
    "python": "scripts/gen_table_border_style.py",
    "arg_template": "--style {style.val} --side {side.id} --out fixtures/docx/table-border-style--{side.id}--{style.id}.docx"
  },
  "parameters": {
    "style": [
      {
        "id": "single",
        "val": "single"
      },
      {
        "id": "double",
        "val": "double"
      },
      {
        "id": "thick",
        "val": "thick"
      },
      {
        "id": "thin-thick-small-gap",
        "val": "thinThickSmallGap"
      },
      {
        "id": "thick-thin-small-gap",
        "val": "thickThinSmallGap"
      },
      {
        "id": "dashed",
        "val": "dashed"
      },
      {
        "id": "dash-small-gap",
        "val": "dashSmallGap"
      },
      {
        "id": "dotted",
        "val": "dotted"
      },
      {
        "id": "dot-dash",
        "val": "dotDash"
      },
      {
        "id": "dot-dot-dash",
        "val": "dotDotDash"
      },
      {
        "id": "wave",
        "val": "wave"
      },
      {
        "id": "wavy-line",
        "val": "wavyLine"
      },
      {
        "id": "three-d-emboss",
        "val": "threeDEmboss"
      },
      {
        "id": "three-d-engrave",
        "val": "threeDEngrave"
      },
      {
        "id": "inset",
        "val": "inset"
      },
      {
        "id": "outset",
        "val": "outset"
      },
      {
        "id": "triple",
        "val": "triple"
      },
      {
        "id": "dash-dot-stroked",
        "val": "dashDotStroked"
      },
      {
        "id": "two-dashes",
        "val": "twoDashes"
      },
      {
        "id": "nil",
        "val": "nil"
      }
    ],
    "side": [
      {
        "id": "top",
        "elem": "top"
      },
      {
        "id": "left",
        "elem": "left"
      },
      {
        "id": "bottom",
        "elem": "bottom"
      },
      {
        "id": "right",
        "elem": "right"
      }
    ]
  },
  "assertions_template": [
    {
      "id": "border-{side.id}-is-{style.id}",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:tblPr/w:tblBorders/w:{side.elem}/@w:val",
      "must": "equal",
      "value": "{style.val}",
      "description": "The table's w:tblBorders/{side.elem} edge must declare line style '{style.val}'."
    }
  ],
  "render_assertions_template": [
    {
      "id": "table-border-style-{style.id}-{side.id}-table-present",
      "kind": "css_selector",
      "selector": ".docx-wrapper table",
      "must": "exist",
      "description": "A rendered <table> element must be present under .docx-wrapper."
    },
    {
      "id": "table-border-style-{style.id}-{side.id}-cell-text-smoke",
      "kind": "css_selector",
      "selector": ".docx-wrapper table td, .docx-wrapper table th",
      "must": "match-text",
      "value": "Border test cell",
      "description": "Smoke check: the rendered table cell text must include 'Border test cell'. Exact border-style rendering is renderer-specific and intentionally not asserted."
    }
  ]
}