Skip to content

docx_plus.tables.read

Reads back the borders, shading, and merge state actually written on a table, its rows, and its cells.

Direct formatting only

This reports what is present on the <w:tblPr> and <w:tcPr> elements themselves. It does not resolve the cell-formatting cascade (table style → <w:tblStylePr> conditional branch → direct properties), so a table whose ruling comes entirely from a style such as Table Grid reads back with no borders at all — the truth about its XML, not about its appearance.

That resolver is a considerably larger piece of work than every writer in this package put together. docx_plus.styles.inspect resolves the paragraph and run cascade but scopes this one out in the same terms.

CellFormatting.column is a grid offset, not an index into Row.cells: cells to the right of a merged one are offset by the span. One entry is produced per <w:tc> element, so a merged cell appears once rather than once per grid column it covers.

Note that space on a returned Border is always 0 — see tables.borders for why the writers pin it.

Architecture walkthrough: Table formatting.

docx_plus.tables.read

Reading table and cell formatting back out.

The inverse of :mod:docx_plus.tables.borders, :mod:docx_plus.tables.shading, and :mod:docx_plus.tables.merge: report the borders, shading, and merge state actually written on a table, its rows, and its cells.

.. warning:: This reports direct formatting only — what is present on the <w:tblPr> and <w:tcPr> elements themselves. It does not resolve the cell-formatting cascade (table style → <w:tblStylePr> conditional branch → direct properties), so a table whose ruling comes entirely from a style such as Table Grid reads back with no borders at all — which is the truth about its XML, not about its appearance.

That resolver is a considerably larger piece of work than every writer in this package put together, and is scoped out in the same terms by :mod:docx_plus.styles.inspect, which resolves the paragraph and run cascade but explicitly not this one.

This module imports only from docx_plus.core and its siblings in docx_plus.tables (SPEC §9.1).

TableFormatting dataclass

TableFormatting(
    style: str | None = None,
    borders: dict[str, Border] = dict(),
    shading: Shading | None = None,
    cells: tuple[CellFormatting, ...] = (),
)

Direct formatting of a table and each of its cells.

Attributes:

Name Type Description
style str | None

The w:tblStyle id, or None if the table names no style. Note that a style is exactly what borders and shading below do not account for.

borders dict[str, Border]

Edge name to :class:~docx_plus.core.borders.Border from the table's <w:tblBorders>. Empty when absent.

shading Shading | None

The table's :class:~docx_plus.tables.Shading, or None.

cells tuple[CellFormatting, ...]

Every cell in the table, in row-major document order. One entry per <w:tc> element, so a merged cell appears once rather than once per grid column it covers.

CellFormatting dataclass

CellFormatting(
    row: int,
    column: int,
    grid_span: int = 1,
    vertical_merge: str | None = None,
    horizontal_merge: str | None = None,
    borders: dict[str, Border] = dict(),
    shading: Shading | None = None,
)

Direct formatting and merge state of one <w:tc>.

Attributes:

Name Type Description
row int

Zero-based index of the row the cell appears in. For a vertical continuation this is the continuation's own row, not the row the span started in.

column int

Zero-based grid offset of the cell's left edge. Cells to the right of a merged one are offset by the span, so this is not the cell's position in Row.cells.

grid_span int

Number of grid columns the cell covers. 1 for an unmerged cell.

vertical_merge str | None

"restart" on the top cell of a vertical span, "continue" on the cells beneath it, None when the cell is not vertically merged.

horizontal_merge str | None

The same for the legacy <w:hMerge> encoding, which is None on anything Word wrote recently. See :func:~docx_plus.tables.normalize_horizontal_merges.

borders dict[str, Border]

Edge name ("top", "insideV", "tl2br", …) to :class:~docx_plus.core.borders.Border. Empty when the cell carries no <w:tcBorders>.

shading Shading | None

The cell's :class:~docx_plus.tables.Shading, or None when it carries no <w:shd>.

read_table_formatting

read_table_formatting(table: Table) -> TableFormatting

Read the direct border, shading, and merge state of table.

Parameters:

Name Type Description Default
table Table

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

required

Returns:

Name Type Description
A TableFormatting

class:TableFormatting describing the table and every cell

TableFormatting

in it. Formatting inherited from a table style is not

TableFormatting

resolved — see the module warning.

Example

from docx import Document from docx_plus.core import Border from docx_plus.tables import read_table_formatting, set_table_borders doc = Document() table = doc.add_table(rows=1, cols=2) set_table_borders(table, all_edges=Border(style="single")) read_table_formatting(table).borders["insideV"].style 'single'

Source code in docx_plus/tables/read.py
def read_table_formatting(table: Table) -> TableFormatting:
    """Read the direct border, shading, and merge state of ``table``.

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

    Returns:
        A :class:`TableFormatting` describing the table and every cell
        in it. Formatting inherited from a table style is **not**
        resolved — see the module warning.

    Example:
        >>> from docx import Document
        >>> from docx_plus.core import Border
        >>> from docx_plus.tables import read_table_formatting, set_table_borders
        >>> doc = Document()
        >>> table = doc.add_table(rows=1, cols=2)
        >>> set_table_borders(table, all_edges=Border(style="single"))
        >>> read_table_formatting(table).borders["insideV"].style
        'single'
    """
    tbl_pr = table._tbl.tblPr
    cells: list[CellFormatting] = []
    for row_index, row in enumerate(table._tbl.tr_lst):
        for tc in row.tc_lst:
            tc_pr = tc.tcPr
            cells.append(
                CellFormatting(
                    row=row_index,
                    column=tc.grid_offset,
                    grid_span=tc.grid_span,
                    vertical_merge=tc.vMerge,
                    horizontal_merge=_merge_val(tc_pr, "w:hMerge"),
                    borders=_read_borders(tc_pr, "w:tcBorders", _TC_BORDER_EDGES),
                    shading=_read_shading(tc_pr),
                )
            )

    return TableFormatting(
        style=table._tbl.tblStyle_val,
        borders=_read_borders(tbl_pr, "w:tblBorders", _TBL_BORDER_EDGES),
        shading=_read_shading(tbl_pr),
        cells=tuple(cells),
    )