Skip to content

docx_plus.tables.borders

Table and cell borders. python-docx has no element class for any of this — no CT_Border, no CT_TblBorders, no CT_TcBorders, and none of those tags is registered — so ruling a table has meant writing OOXML by hand.

Both writers are a full replacement, not a merge: any existing container is discarded first, so an edge you do not name ends up absent. Naming no edges at all removes the element rather than leaving an empty container behind.

Border.space is ignored here

Both writers emit w:space="0", which is what Word does and the only value its UI can produce for a table. The Border dataclass defaults space to 24 — a page-border value — which would otherwise put a third of an inch between every table edge and its text.

Architecture walkthrough: Table formatting.

docx_plus.tables.borders

Table and cell borders — <w:tblBorders> and <w:tcBorders>.

python-docx has no element class for any of this: there is no CT_Border, no CT_TblBorders, and no CT_TcBorders anywhere in the package, and none of those tags is registered, so a border set by hand round-trips as an anonymous lxml element. Drawing a ruled table has meant writing the XML yourself.

ECMA-376 uses the same CT_Border shape here as it does for page borders, so both go through :class:~docx_plus.core.borders.Border and :func:~docx_plus.core.borders.border_attrs. Tables add the two inside edges (17.4.39) and cells add the two diagonals (17.4.67).

.. note:: Border.space is ignored for tables and cells — both writers emit w:space="0", which is what Word does and the only value its UI can produce. The dataclass default of 24 is a page-border default and would otherwise leak a third of an inch onto every table edge.

This module imports only from docx_plus.core (SPEC §9.1).

set_table_borders

set_table_borders(
    table: Table,
    *,
    all_edges: Border | None = None,
    top: Border | None = None,
    bottom: Border | None = None,
    left: Border | None = None,
    right: Border | None = None,
    inside_h: Border | None = None,
    inside_v: Border | None = None,
) -> None

Set the table-level borders on table.

A full replacement, not a merge: any existing <w:tblBorders> is discarded first, so an edge left unset ends up absent. Calling with every edge None removes the element rather than writing an empty container.

These are the table's borders. A cell's own <w:tcBorders> overrides them for that cell, and a table style supplies them when neither is present — see :func:set_cell_borders.

Parameters:

Name Type Description Default
table Table

A python-docx :class:~docx.table.Table.

required
all_edges Border | None

Applied to the four outer edges and both inside edges, as a shorthand for the common "rule everything the same way" case. Any explicit edge below overrides it.

None
top Border | None

The table's top edge.

None
bottom Border | None

The table's bottom edge.

None
left Border | None

The table's left edge.

None
right Border | None

The table's right edge.

None
inside_h Border | None

The horizontal rules between rows.

None
inside_v Border | None

The vertical rules between columns.

None
Example

from docx import Document from docx_plus.core import Border from docx_plus.tables import set_table_borders doc = Document() table = doc.add_table(rows=2, cols=2) hairline = Border(style="single", size=4, color="808080") set_table_borders(table, all_edges=hairline, ... top=Border(style="single", size=12))

Source code in docx_plus/tables/borders.py
def set_table_borders(
    table: Table,
    *,
    all_edges: Border | None = None,
    top: Border | None = None,
    bottom: Border | None = None,
    left: Border | None = None,
    right: Border | None = None,
    inside_h: Border | None = None,
    inside_v: Border | None = None,
) -> None:
    """Set the table-level borders on ``table``.

    A full replacement, not a merge: any existing ``<w:tblBorders>`` is
    discarded first, so an edge left unset ends up absent. Calling with
    every edge ``None`` removes the element rather than writing an empty
    container.

    These are the *table's* borders. A cell's own ``<w:tcBorders>``
    overrides them for that cell, and a table *style* supplies them when
    neither is present — see :func:`set_cell_borders`.

    Args:
        table: A python-docx :class:`~docx.table.Table`.
        all_edges: Applied to the four outer edges and both inside
            edges, as a shorthand for the common "rule everything the
            same way" case. Any explicit edge below overrides it.
        top: The table's top edge.
        bottom: The table's bottom edge.
        left: The table's left edge.
        right: The table's right edge.
        inside_h: The horizontal rules *between* rows.
        inside_v: The vertical rules *between* columns.

    Example:
        >>> from docx import Document
        >>> from docx_plus.core import Border
        >>> from docx_plus.tables import set_table_borders
        >>> doc = Document()
        >>> table = doc.add_table(rows=2, cols=2)
        >>> hairline = Border(style="single", size=4, color="808080")
        >>> set_table_borders(table, all_edges=hairline,
        ...                   top=Border(style="single", size=12))
    """
    tbl_pr = table._tbl.tblPr
    edges = _resolve_edges(
        all_edges,
        {
            "w:top": top,
            "w:left": left,
            "w:bottom": bottom,
            "w:right": right,
            "w:insideH": inside_h,
            "w:insideV": inside_v,
        },
    )
    _write_borders(tbl_pr, "w:tblBorders", edges, _TBL_BORDER_EDGES, _TBL_PR_AFTER_BORDERS)

set_cell_borders

set_cell_borders(
    cell: _Cell,
    *,
    all_edges: Border | None = None,
    top: Border | None = None,
    bottom: Border | None = None,
    left: Border | None = None,
    right: Border | None = None,
    tl2br: Border | None = None,
    tr2bl: Border | None = None,
) -> None

Set the borders on a single cell.

A full replacement, exactly as :func:set_table_borders is. Cell borders take precedence over the table's, which is how a single emphasized row or a boxed total is expressed.

Parameters:

Name Type Description Default
cell _Cell

A python-docx :class:~docx.table._Cell.

required
all_edges Border | None

Applied to the four sides. The diagonals are deliberately not included — they are a decorative "crossed-out cell" mark, never something a caller means by "all borders".

None
top Border | None

The cell's top edge.

None
bottom Border | None

The cell's bottom edge.

None
left Border | None

The cell's left edge.

None
right Border | None

The cell's right edge.

None
tl2br Border | None

Diagonal from the top-left to the bottom-right corner.

None
tr2bl Border | None

Diagonal from the top-right to the bottom-left corner.

None
Example

from docx import Document from docx_plus.core import Border from docx_plus.tables import set_cell_borders doc = Document() table = doc.add_table(rows=2, cols=2) set_cell_borders(table.cell(1, 1), ... top=Border(style="double", size=6))

Source code in docx_plus/tables/borders.py
def set_cell_borders(
    cell: _Cell,
    *,
    all_edges: Border | None = None,
    top: Border | None = None,
    bottom: Border | None = None,
    left: Border | None = None,
    right: Border | None = None,
    tl2br: Border | None = None,
    tr2bl: Border | None = None,
) -> None:
    """Set the borders on a single ``cell``.

    A full replacement, exactly as :func:`set_table_borders` is. Cell
    borders take precedence over the table's, which is how a single
    emphasized row or a boxed total is expressed.

    Args:
        cell: A python-docx :class:`~docx.table._Cell`.
        all_edges: Applied to the four sides. The diagonals are
            deliberately *not* included — they are a decorative
            "crossed-out cell" mark, never something a caller means by
            "all borders".
        top: The cell's top edge.
        bottom: The cell's bottom edge.
        left: The cell's left edge.
        right: The cell's right edge.
        tl2br: Diagonal from the top-left to the bottom-right corner.
        tr2bl: Diagonal from the top-right to the bottom-left corner.

    Example:
        >>> from docx import Document
        >>> from docx_plus.core import Border
        >>> from docx_plus.tables import set_cell_borders
        >>> doc = Document()
        >>> table = doc.add_table(rows=2, cols=2)
        >>> set_cell_borders(table.cell(1, 1),
        ...                  top=Border(style="double", size=6))
    """
    tc_pr = cell._tc.get_or_add_tcPr()
    edges = _resolve_edges(
        all_edges,
        {
            "w:top": top,
            "w:left": left,
            "w:bottom": bottom,
            "w:right": right,
        },
    )
    edges.update({tag: b for tag, b in (("w:tl2br", tl2br), ("w:tr2bl", tr2bl)) if b is not None})
    _write_borders(tc_pr, "w:tcBorders", edges, _TC_BORDER_EDGES, _TC_PR_AFTER_BORDERS)