docx_plus.tables.merge¶
Merging is one of the few table features python-docx implements
properly: _Cell.merge grows a rectangular region and refuses L- and
T-shaped selections. This module does not re-implement it. What it
adds is the other three-quarters of the story.
merge_cells— the same operation behind a typed error, so callers canexcept DocxPlusErroruniformly instead of reaching intodocx.exceptions.unmerge_cell— the inverse, which python-docx has no notion of. Nothing in the package removes aw:gridSpanor aw:vMerge, so a merge is otherwise one-way. Works from any cell in the region, including a vertical continuation.normalize_horizontal_merges— rewrites the other horizontal-merge encoding.
The two horizontal-merge encodings¶
OOXML can express a horizontal merge two ways, and python-docx models only one of them:
| Encoding | python-docx | |
|---|---|---|
w:gridSpan |
One <w:tc> widened over several grid columns |
Understood |
w:hMerge |
One <w:tc> per column, followers marked as continuations |
Ignored |
Word renders both identically — verified against Word 2016, where a
converted file rasterises byte-for-byte the same as its original. But
on an hMerge table Table.cell hands back cells that look separate
and are not, and Row.cells reports a column count Word never draws.
Word's own COM object model has the same blind spot, counting the
underlying <w:tc> elements rather than what it lays out.
normalize_horizontal_merges converts the second form into the first,
in place. It refuses by default to drop text held in a continuation
cell: that text is invisible in Word, so keeping it would make hidden
content appear and discarding it silently would lose data. Pass
discard_content=True to choose.
Architecture walkthrough: Table formatting.
docx_plus.tables.merge ¶
Merging, unmerging, and legacy <w:hMerge> spans.
Merging itself is one of the few table features python-docx implements
properly: _Cell.merge grows a rectangular region, refusing L- and
T-shaped selections. This module does not re-implement it. What it
adds is the other three-quarters of the story:
- :func:
merge_cells— the same operation behind a typed error, so a caller canexcept DocxPlusErroruniformly (SPEC §9.7) instead of reaching intodocx.exceptions. - :func:
unmerge_cell— the inverse, which python-docx has no notion of. Nothing in the package removes aw:gridSpanor aw:vMerge, so a merge is one-way. - :func:
normalize_horizontal_merges— rewrites the other horizontal merge encoding. OOXML has two (17.4.22w:hMergeand 17.4.17w:gridSpan); python-docx models onlygridSpan, so a table written withhMerge— older Word versions and several converters do — reads back as separate cells that Word draws as one.
This module imports only from docx_plus.core (SPEC §9.1).
InvalidMergeError ¶
Bases: DocxPlusError, ValueError
Raised when a merge cannot be performed as requested.
Covers a non-rectangular selection — the case python-docx signals
with InvalidSpanError, which this wraps — and a normalization
that would otherwise discard cell content.
Subclasses ValueError so existing handling still catches it;
also subclasses :class:DocxPlusError per SPEC §9.7.
merge_cells ¶
Merge the rectangular region with start and end as corners.
A thin wrapper over python-docx's _Cell.merge that translates
its InvalidSpanError into a :class:DocxPlusError subclass.
Content from every cell in the region is moved into the top-left
one, which is what Word does.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
_Cell
|
One corner of the region. |
required |
end
|
_Cell
|
The diagonally opposite corner. May be |
required |
Returns:
| Type | Description |
|---|---|
_Cell
|
The merged cell — the top-left of the region, which is not |
_Cell
|
necessarily |
Raises:
| Type | Description |
|---|---|
InvalidMergeError
|
If the two cells do not define a rectangular
region, because one of them is already part of a merge that
makes the selection L- or T-shaped. Subclasses
:class: |
Example
from docx import Document from docx_plus.tables import merge_cells doc = Document() table = doc.add_table(rows=2, cols=3) banner = merge_cells(table.cell(0, 0), table.cell(0, 2)) banner.text = "Quarterly results"
Source code in docx_plus/tables/merge.py
unmerge_cell ¶
Split a merged region back into individual cells.
The inverse of :func:merge_cells, which python-docx does not
provide in any form. Works from any cell in the region — pass a
vertical continuation and the whole span is still resolved and
undone.
Content stays in the original top-left cell; the cells restored around it are empty, matching Word's "Split Cells". A merged cell's width is divided evenly among them, since the individual widths were summed away when the merge happened and cannot be recovered.
Idempotent: a cell that is not merged is left untouched.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cell
|
_Cell
|
Any cell in the merged region. |
required |
Example
from docx import Document from docx_plus.tables import merge_cells, unmerge_cell doc = Document() table = doc.add_table(rows=2, cols=3) merged = merge_cells(table.cell(0, 0), table.cell(1, 1)) unmerge_cell(merged) len(table.rows[0].cells) 3
Source code in docx_plus/tables/merge.py
normalize_horizontal_merges ¶
Rewrite <w:hMerge> spans in table as <w:gridSpan>.
OOXML can express a horizontal merge two ways. w:gridSpan widens
one cell over several grid columns; w:hMerge keeps one
<w:tc> per column and marks the followers as continuations.
Word renders both identically, but python-docx's grid model only
understands the first — so on an hMerge table, Table.cell
hands back cells that look separate and are not, and
Row.cells reports a column count Word never shows.
This converts the second form into the first, in place, leaving the rendered table unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table
|
A python-docx :class: |
required |
discard_content
|
bool
|
Whether to drop text held in a continuation cell. Such text is invisible in Word — the cell it lives in is merged away — so keeping it would make hidden content appear, and dropping it silently would lose data. The default refuses rather than choosing for you. |
False
|
Returns:
| Type | Description |
|---|---|
int
|
The number of merged regions converted. |
int
|
used |
Raises:
| Type | Description |
|---|---|
InvalidMergeError
|
If a continuation cell holds content and
|
Example
from docx import Document from docx_plus.tables import normalize_horizontal_merges doc = Document() normalize_horizontal_merges(doc.add_table(rows=1, cols=2)) 0