aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/docx/oxml/table.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/docx/oxml/table.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/docx/oxml/table.py')
-rw-r--r--.venv/lib/python3.12/site-packages/docx/oxml/table.py977
1 files changed, 977 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/docx/oxml/table.py b/.venv/lib/python3.12/site-packages/docx/oxml/table.py
new file mode 100644
index 00000000..e38d5856
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docx/oxml/table.py
@@ -0,0 +1,977 @@
+"""Custom element classes for tables."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable, cast
+
+from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_ROW_HEIGHT_RULE, WD_TABLE_DIRECTION
+from docx.exceptions import InvalidSpanError
+from docx.oxml.ns import nsdecls, qn
+from docx.oxml.parser import parse_xml
+from docx.oxml.shared import CT_DecimalNumber
+from docx.oxml.simpletypes import (
+ ST_Merge,
+ ST_TblLayoutType,
+ ST_TblWidth,
+ ST_TwipsMeasure,
+ XsdInt,
+)
+from docx.oxml.text.paragraph import CT_P
+from docx.oxml.xmlchemy import (
+ BaseOxmlElement,
+ OneAndOnlyOne,
+ OneOrMore,
+ OptionalAttribute,
+ RequiredAttribute,
+ ZeroOrMore,
+ ZeroOrOne,
+)
+from docx.shared import Emu, Length, Twips
+
+if TYPE_CHECKING:
+ from docx.enum.table import WD_TABLE_ALIGNMENT
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
+ from docx.oxml.shared import CT_OnOff, CT_String
+ from docx.oxml.text.parfmt import CT_Jc
+
+
+class CT_Height(BaseOxmlElement):
+ """Used for `w:trHeight` to specify a row height and row height rule."""
+
+ val: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "w:val", ST_TwipsMeasure
+ )
+ hRule: WD_ROW_HEIGHT_RULE | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "w:hRule", WD_ROW_HEIGHT_RULE
+ )
+
+
+class CT_Row(BaseOxmlElement):
+ """``<w:tr>`` element."""
+
+ add_tc: Callable[[], CT_Tc]
+ get_or_add_trPr: Callable[[], CT_TrPr]
+ _add_trPr: Callable[[], CT_TrPr]
+
+ tc_lst: list[CT_Tc]
+ # -- custom inserter below --
+ tblPrEx: CT_TblPrEx | None = ZeroOrOne("w:tblPrEx") # pyright: ignore[reportAssignmentType]
+ # -- custom inserter below --
+ trPr: CT_TrPr | None = ZeroOrOne("w:trPr") # pyright: ignore[reportAssignmentType]
+ tc = ZeroOrMore("w:tc")
+
+ @property
+ def grid_after(self) -> int:
+ """The number of unpopulated layout-grid cells at the end of this row."""
+ trPr = self.trPr
+ if trPr is None:
+ return 0
+ return trPr.grid_after
+
+ @property
+ def grid_before(self) -> int:
+ """The number of unpopulated layout-grid cells at the start of this row."""
+ trPr = self.trPr
+ if trPr is None:
+ return 0
+ return trPr.grid_before
+
+ def tc_at_grid_offset(self, grid_offset: int) -> CT_Tc:
+ """The `tc` element in this tr at exact `grid offset`.
+
+ Raises ValueError when this `w:tr` contains no `w:tc` with exact starting `grid_offset`.
+ """
+ # -- account for omitted cells at the start of the row --
+ remaining_offset = grid_offset - self.grid_before
+
+ for tc in self.tc_lst:
+ # -- We've gone past grid_offset without finding a tc, no sense searching further. --
+ if remaining_offset < 0:
+ break
+ # -- We've arrived at grid_offset, this is the `w:tc` we're looking for. --
+ if remaining_offset == 0:
+ return tc
+ # -- We're not there yet, skip forward the number of layout-grid cells this cell
+ # -- occupies.
+ remaining_offset -= tc.grid_span
+
+ raise ValueError(f"no `tc` element at grid_offset={grid_offset}")
+
+ @property
+ def tr_idx(self) -> int:
+ """Index of this `w:tr` element within its parent `w:tbl` element."""
+ tbl = cast(CT_Tbl, self.getparent())
+ return tbl.tr_lst.index(self)
+
+ @property
+ def trHeight_hRule(self) -> WD_ROW_HEIGHT_RULE | None:
+ """The value of `./w:trPr/w:trHeight/@w:hRule`, or |None| if not present."""
+ trPr = self.trPr
+ if trPr is None:
+ return None
+ return trPr.trHeight_hRule
+
+ @trHeight_hRule.setter
+ def trHeight_hRule(self, value: WD_ROW_HEIGHT_RULE | None):
+ trPr = self.get_or_add_trPr()
+ trPr.trHeight_hRule = value
+
+ @property
+ def trHeight_val(self):
+ """Return the value of `w:trPr/w:trHeight@w:val`, or |None| if not present."""
+ trPr = self.trPr
+ if trPr is None:
+ return None
+ return trPr.trHeight_val
+
+ @trHeight_val.setter
+ def trHeight_val(self, value: Length | None):
+ trPr = self.get_or_add_trPr()
+ trPr.trHeight_val = value
+
+ def _insert_tblPrEx(self, tblPrEx: CT_TblPrEx):
+ self.insert(0, tblPrEx)
+
+ def _insert_trPr(self, trPr: CT_TrPr):
+ tblPrEx = self.tblPrEx
+ if tblPrEx is not None:
+ tblPrEx.addnext(trPr)
+ else:
+ self.insert(0, trPr)
+
+ def _new_tc(self):
+ return CT_Tc.new()
+
+
+class CT_Tbl(BaseOxmlElement):
+ """``<w:tbl>`` element."""
+
+ add_tr: Callable[[], CT_Row]
+ tr_lst: list[CT_Row]
+
+ tblPr: CT_TblPr = OneAndOnlyOne("w:tblPr") # pyright: ignore[reportAssignmentType]
+ tblGrid: CT_TblGrid = OneAndOnlyOne("w:tblGrid") # pyright: ignore[reportAssignmentType]
+ tr = ZeroOrMore("w:tr")
+
+ @property
+ def bidiVisual_val(self) -> bool | None:
+ """Value of `./w:tblPr/w:bidiVisual/@w:val` or |None| if not present.
+
+ Controls whether table cells are displayed right-to-left or left-to-right.
+ """
+ bidiVisual = self.tblPr.bidiVisual
+ if bidiVisual is None:
+ return None
+ return bidiVisual.val
+
+ @bidiVisual_val.setter
+ def bidiVisual_val(self, value: WD_TABLE_DIRECTION | None):
+ tblPr = self.tblPr
+ if value is None:
+ tblPr._remove_bidiVisual() # pyright: ignore[reportPrivateUsage]
+ else:
+ tblPr.get_or_add_bidiVisual().val = bool(value)
+
+ @property
+ def col_count(self):
+ """The number of grid columns in this table."""
+ return len(self.tblGrid.gridCol_lst)
+
+ def iter_tcs(self):
+ """Generate each of the `w:tc` elements in this table, left to right and top to
+ bottom.
+
+ Each cell in the first row is generated, followed by each cell in the second
+ row, etc.
+ """
+ for tr in self.tr_lst:
+ for tc in tr.tc_lst:
+ yield tc
+
+ @classmethod
+ def new_tbl(cls, rows: int, cols: int, width: Length) -> CT_Tbl:
+ """Return a new `w:tbl` element having `rows` rows and `cols` columns.
+
+ `width` is distributed evenly between the columns.
+ """
+ return cast(CT_Tbl, parse_xml(cls._tbl_xml(rows, cols, width)))
+
+ @property
+ def tblStyle_val(self) -> str | None:
+ """`w:tblPr/w:tblStyle/@w:val` (a table style id) or |None| if not present."""
+ tblStyle = self.tblPr.tblStyle
+ if tblStyle is None:
+ return None
+ return tblStyle.val
+
+ @tblStyle_val.setter
+ def tblStyle_val(self, styleId: str | None) -> None:
+ """Set the value of `w:tblPr/w:tblStyle/@w:val` (a table style id) to `styleId`.
+
+ If `styleId` is None, remove the `w:tblStyle` element.
+ """
+ tblPr = self.tblPr
+ tblPr._remove_tblStyle() # pyright: ignore[reportPrivateUsage]
+ if styleId is None:
+ return
+ tblPr._add_tblStyle().val = styleId # pyright: ignore[reportPrivateUsage]
+
+ @classmethod
+ def _tbl_xml(cls, rows: int, cols: int, width: Length) -> str:
+ col_width = Emu(width // cols) if cols > 0 else Emu(0)
+ return (
+ f"<w:tbl {nsdecls('w')}>\n"
+ f" <w:tblPr>\n"
+ f' <w:tblW w:type="auto" w:w="0"/>\n'
+ f' <w:tblLook w:firstColumn="1" w:firstRow="1"\n'
+ f' w:lastColumn="0" w:lastRow="0" w:noHBand="0"\n'
+ f' w:noVBand="1" w:val="04A0"/>\n'
+ f" </w:tblPr>\n"
+ f"{cls._tblGrid_xml(cols, col_width)}"
+ f"{cls._trs_xml(rows, cols, col_width)}"
+ f"</w:tbl>\n"
+ )
+
+ @classmethod
+ def _tblGrid_xml(cls, col_count: int, col_width: Length) -> str:
+ xml = " <w:tblGrid>\n"
+ for _ in range(col_count):
+ xml += ' <w:gridCol w:w="%d"/>\n' % col_width.twips
+ xml += " </w:tblGrid>\n"
+ return xml
+
+ @classmethod
+ def _trs_xml(cls, row_count: int, col_count: int, col_width: Length) -> str:
+ return f" <w:tr>\n{cls._tcs_xml(col_count, col_width)} </w:tr>\n" * row_count
+
+ @classmethod
+ def _tcs_xml(cls, col_count: int, col_width: Length) -> str:
+ return (
+ f" <w:tc>\n"
+ f" <w:tcPr>\n"
+ f' <w:tcW w:type="dxa" w:w="{col_width.twips}"/>\n'
+ f" </w:tcPr>\n"
+ f" <w:p/>\n"
+ f" </w:tc>\n"
+ ) * col_count
+
+
+class CT_TblGrid(BaseOxmlElement):
+ """`w:tblGrid` element.
+
+ Child of `w:tbl`, holds `w:gridCol> elements that define column count, width, etc.
+ """
+
+ add_gridCol: Callable[[], CT_TblGridCol]
+ gridCol_lst: list[CT_TblGridCol]
+
+ gridCol = ZeroOrMore("w:gridCol", successors=("w:tblGridChange",))
+
+
+class CT_TblGridCol(BaseOxmlElement):
+ """`w:gridCol` element, child of `w:tblGrid`, defines a table column."""
+
+ w: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "w:w", ST_TwipsMeasure
+ )
+
+ @property
+ def gridCol_idx(self) -> int:
+ """Index of this `w:gridCol` element within its parent `w:tblGrid` element."""
+ tblGrid = cast(CT_TblGrid, self.getparent())
+ return tblGrid.gridCol_lst.index(self)
+
+
+class CT_TblLayoutType(BaseOxmlElement):
+ """`w:tblLayout` element.
+
+ Specifies whether column widths are fixed or can be automatically adjusted based on
+ content.
+ """
+
+ type: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "w:type", ST_TblLayoutType
+ )
+
+
+class CT_TblPr(BaseOxmlElement):
+ """``<w:tblPr>`` element, child of ``<w:tbl>``, holds child elements that define
+ table properties such as style and borders."""
+
+ get_or_add_bidiVisual: Callable[[], CT_OnOff]
+ get_or_add_jc: Callable[[], CT_Jc]
+ get_or_add_tblLayout: Callable[[], CT_TblLayoutType]
+ _add_tblStyle: Callable[[], CT_String]
+ _remove_bidiVisual: Callable[[], None]
+ _remove_jc: Callable[[], None]
+ _remove_tblStyle: Callable[[], None]
+
+ _tag_seq = (
+ "w:tblStyle",
+ "w:tblpPr",
+ "w:tblOverlap",
+ "w:bidiVisual",
+ "w:tblStyleRowBandSize",
+ "w:tblStyleColBandSize",
+ "w:tblW",
+ "w:jc",
+ "w:tblCellSpacing",
+ "w:tblInd",
+ "w:tblBorders",
+ "w:shd",
+ "w:tblLayout",
+ "w:tblCellMar",
+ "w:tblLook",
+ "w:tblCaption",
+ "w:tblDescription",
+ "w:tblPrChange",
+ )
+ tblStyle: CT_String | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:tblStyle", successors=_tag_seq[1:]
+ )
+ bidiVisual: CT_OnOff | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:bidiVisual", successors=_tag_seq[4:]
+ )
+ jc: CT_Jc | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:jc", successors=_tag_seq[8:]
+ )
+ tblLayout: CT_TblLayoutType | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:tblLayout", successors=_tag_seq[13:]
+ )
+ del _tag_seq
+
+ @property
+ def alignment(self) -> WD_TABLE_ALIGNMENT | None:
+ """Horizontal alignment of table, |None| if `./w:jc` is not present."""
+ jc = self.jc
+ if jc is None:
+ return None
+ return cast("WD_TABLE_ALIGNMENT | None", jc.val)
+
+ @alignment.setter
+ def alignment(self, value: WD_TABLE_ALIGNMENT | None):
+ self._remove_jc()
+ if value is None:
+ return
+ jc = self.get_or_add_jc()
+ jc.val = cast("WD_ALIGN_PARAGRAPH", value)
+
+ @property
+ def autofit(self) -> bool:
+ """|False| when there is a `w:tblLayout` child with `@w:type="fixed"`.
+
+ Otherwise |True|.
+ """
+ tblLayout = self.tblLayout
+ return True if tblLayout is None else tblLayout.type != "fixed"
+
+ @autofit.setter
+ def autofit(self, value: bool):
+ tblLayout = self.get_or_add_tblLayout()
+ tblLayout.type = "autofit" if value else "fixed"
+
+ @property
+ def style(self):
+ """Return the value of the ``val`` attribute of the ``<w:tblStyle>`` child or
+ |None| if not present."""
+ tblStyle = self.tblStyle
+ if tblStyle is None:
+ return None
+ return tblStyle.val
+
+ @style.setter
+ def style(self, value: str | None):
+ self._remove_tblStyle()
+ if value is None:
+ return
+ self._add_tblStyle().val = value
+
+
+class CT_TblPrEx(BaseOxmlElement):
+ """`w:tblPrEx` element, exceptions to table-properties.
+
+ Applied at a lower level, like a `w:tr` to modify the appearance. Possibly used when
+ two tables are merged. For more see:
+ http://officeopenxml.com/WPtablePropertyExceptions.php
+ """
+
+
+class CT_TblWidth(BaseOxmlElement):
+ """Used for `w:tblW` and `w:tcW` and others, specifies a table-related width."""
+
+ # the type for `w` attr is actually ST_MeasurementOrPercent, but using
+ # XsdInt for now because only dxa (twips) values are being used. It's not
+ # entirely clear what the semantics are for other values like -01.4mm
+ w: int = RequiredAttribute("w:w", XsdInt) # pyright: ignore[reportAssignmentType]
+ type = RequiredAttribute("w:type", ST_TblWidth)
+
+ @property
+ def width(self) -> Length | None:
+ """EMU length indicated by the combined `w:w` and `w:type` attrs."""
+ if self.type != "dxa":
+ return None
+ return Twips(self.w)
+
+ @width.setter
+ def width(self, value: Length):
+ self.type = "dxa"
+ self.w = Emu(value).twips
+
+
+class CT_Tc(BaseOxmlElement):
+ """`w:tc` table cell element."""
+
+ add_p: Callable[[], CT_P]
+ get_or_add_tcPr: Callable[[], CT_TcPr]
+ p_lst: list[CT_P]
+ tbl_lst: list[CT_Tbl]
+ _insert_tbl: Callable[[CT_Tbl], CT_Tbl]
+ _new_p: Callable[[], CT_P]
+
+ # -- tcPr has many successors, `._insert_tcPr()` is overridden below --
+ tcPr: CT_TcPr | None = ZeroOrOne("w:tcPr") # pyright: ignore[reportAssignmentType]
+ p = OneOrMore("w:p")
+ tbl = OneOrMore("w:tbl")
+
+ @property
+ def bottom(self) -> int:
+ """The row index that marks the bottom extent of the vertical span of this cell.
+
+ This is one greater than the index of the bottom-most row of the span, similar
+ to how a slice of the cell's rows would be specified.
+ """
+ if self.vMerge is not None:
+ tc_below = self._tc_below
+ if tc_below is not None and tc_below.vMerge == ST_Merge.CONTINUE:
+ return tc_below.bottom
+ return self._tr_idx + 1
+
+ def clear_content(self):
+ """Remove all content elements, preserving `w:tcPr` element if present.
+
+ Note that this leaves the `w:tc` element in an invalid state because it doesn't
+ contain at least one block-level element. It's up to the caller to add a
+ `w:p`child element as the last content element.
+ """
+ # -- remove all cell inner-content except a `w:tcPr` when present. --
+ for e in self.xpath("./*[not(self::w:tcPr)]"):
+ self.remove(e)
+
+ @property
+ def grid_offset(self) -> int:
+ """Starting offset of `tc` in the layout-grid columns of its table.
+
+ A cell in the leftmost grid-column has offset 0.
+ """
+ grid_before = self._tr.grid_before
+ preceding_tc_grid_spans = sum(
+ tc.grid_span for tc in self.xpath("./preceding-sibling::w:tc")
+ )
+ return grid_before + preceding_tc_grid_spans
+
+ @property
+ def grid_span(self) -> int:
+ """The integer number of columns this cell spans.
+
+ Determined by ./w:tcPr/w:gridSpan/@val, it defaults to 1.
+ """
+ tcPr = self.tcPr
+ return 1 if tcPr is None else tcPr.grid_span
+
+ @grid_span.setter
+ def grid_span(self, value: int):
+ tcPr = self.get_or_add_tcPr()
+ tcPr.grid_span = value
+
+ @property
+ def inner_content_elements(self) -> list[CT_P | CT_Tbl]:
+ """Generate all `w:p` and `w:tbl` elements in this document-body.
+
+ Elements appear in document order. Elements shaded by nesting in a `w:ins` or
+ other "wrapper" element will not be included.
+ """
+ return self.xpath("./w:p | ./w:tbl")
+
+ def iter_block_items(self):
+ """Generate a reference to each of the block-level content elements in this
+ cell, in the order they appear."""
+ block_item_tags = (qn("w:p"), qn("w:tbl"), qn("w:sdt"))
+ for child in self:
+ if child.tag in block_item_tags:
+ yield child
+
+ @property
+ def left(self) -> int:
+ """The grid column index at which this ``<w:tc>`` element appears."""
+ return self.grid_offset
+
+ def merge(self, other_tc: CT_Tc) -> CT_Tc:
+ """Return top-left `w:tc` element of a new span.
+
+ Span is formed by merging the rectangular region defined by using this tc
+ element and `other_tc` as diagonal corners.
+ """
+ top, left, height, width = self._span_dimensions(other_tc)
+ top_tc = self._tbl.tr_lst[top].tc_at_grid_offset(left)
+ top_tc._grow_to(width, height)
+ return top_tc
+
+ @classmethod
+ def new(cls) -> CT_Tc:
+ """A new `w:tc` element, containing an empty paragraph as the required EG_BlockLevelElt."""
+ return cast(CT_Tc, parse_xml("<w:tc %s>\n" " <w:p/>\n" "</w:tc>" % nsdecls("w")))
+
+ @property
+ def right(self) -> int:
+ """The grid column index that marks the right-side extent of the horizontal span
+ of this cell.
+
+ This is one greater than the index of the right-most column of the span, similar
+ to how a slice of the cell's columns would be specified.
+ """
+ return self.grid_offset + self.grid_span
+
+ @property
+ def top(self) -> int:
+ """The top-most row index in the vertical span of this cell."""
+ if self.vMerge is None or self.vMerge == ST_Merge.RESTART:
+ return self._tr_idx
+ return self._tc_above.top
+
+ @property
+ def vMerge(self) -> str | None:
+ """Value of ./w:tcPr/w:vMerge/@val, |None| if w:vMerge is not present."""
+ tcPr = self.tcPr
+ if tcPr is None:
+ return None
+ return tcPr.vMerge_val
+
+ @vMerge.setter
+ def vMerge(self, value: str | None):
+ tcPr = self.get_or_add_tcPr()
+ tcPr.vMerge_val = value
+
+ @property
+ def width(self) -> Length | None:
+ """EMU length represented in `./w:tcPr/w:tcW` or |None| if not present."""
+ tcPr = self.tcPr
+ if tcPr is None:
+ return None
+ return tcPr.width
+
+ @width.setter
+ def width(self, value: Length):
+ tcPr = self.get_or_add_tcPr()
+ tcPr.width = value
+
+ def _add_width_of(self, other_tc: CT_Tc):
+ """Add the width of `other_tc` to this cell.
+
+ Does nothing if either this tc or `other_tc` does not have a specified width.
+ """
+ if self.width and other_tc.width:
+ self.width = Length(self.width + other_tc.width)
+
+ def _grow_to(self, width: int, height: int, top_tc: CT_Tc | None = None):
+ """Grow this cell to `width` grid columns and `height` rows.
+
+ This is accomplished by expanding horizontal spans and creating continuation
+ cells to form vertical spans.
+ """
+
+ def vMerge_val(top_tc: CT_Tc):
+ return (
+ ST_Merge.CONTINUE
+ if top_tc is not self
+ else None if height == 1 else ST_Merge.RESTART
+ )
+
+ top_tc = self if top_tc is None else top_tc
+ self._span_to_width(width, top_tc, vMerge_val(top_tc))
+ if height > 1:
+ tc_below = self._tc_below
+ assert tc_below is not None
+ tc_below._grow_to(width, height - 1, top_tc)
+
+ def _insert_tcPr(self, tcPr: CT_TcPr) -> CT_TcPr:
+ """Override default `._insert_tcPr()`."""
+ # -- `tcPr`` has a large number of successors, but always comes first if it appears,
+ # -- so just using insert(0, ...) rather than spelling out successors.
+ self.insert(0, tcPr)
+ return tcPr
+
+ @property
+ def _is_empty(self) -> bool:
+ """True if this cell contains only a single empty `w:p` element."""
+ block_items = list(self.iter_block_items())
+ if len(block_items) > 1:
+ return False
+ # -- cell must include at least one block item but can be a `w:tbl`, `w:sdt`,
+ # -- `w:customXml` or a `w:p`
+ only_item = block_items[0]
+ if isinstance(only_item, CT_P) and len(only_item.r_lst) == 0:
+ return True
+ return False
+
+ def _move_content_to(self, other_tc: CT_Tc):
+ """Append the content of this cell to `other_tc`.
+
+ Leaves this cell with a single empty ``<w:p>`` element.
+ """
+ if other_tc is self:
+ return
+ if self._is_empty:
+ return
+ other_tc._remove_trailing_empty_p()
+ # -- appending moves each element from self to other_tc --
+ for block_element in self.iter_block_items():
+ other_tc.append(block_element)
+ # -- add back the required minimum single empty <w:p> element --
+ self.append(self._new_p())
+
+ def _new_tbl(self) -> None:
+ raise NotImplementedError(
+ "use CT_Tbl.new_tbl() to add a new table, specifying rows and columns"
+ )
+
+ @property
+ def _next_tc(self) -> CT_Tc | None:
+ """The `w:tc` element immediately following this one in this row, or |None| if
+ this is the last `w:tc` element in the row."""
+ following_tcs = self.xpath("./following-sibling::w:tc")
+ return following_tcs[0] if following_tcs else None
+
+ def _remove(self):
+ """Remove this `w:tc` element from the XML tree."""
+ parent_element = self.getparent()
+ assert parent_element is not None
+ parent_element.remove(self)
+
+ def _remove_trailing_empty_p(self):
+ """Remove last content element from this cell if it's an empty `w:p` element."""
+ block_items = list(self.iter_block_items())
+ last_content_elm = block_items[-1]
+ if not isinstance(last_content_elm, CT_P):
+ return
+ p = last_content_elm
+ if len(p.r_lst) > 0:
+ return
+ self.remove(p)
+
+ def _span_dimensions(self, other_tc: CT_Tc) -> tuple[int, int, int, int]:
+ """Return a (top, left, height, width) 4-tuple specifying the extents of the
+ merged cell formed by using this tc and `other_tc` as opposite corner
+ extents."""
+
+ def raise_on_inverted_L(a: CT_Tc, b: CT_Tc):
+ if a.top == b.top and a.bottom != b.bottom:
+ raise InvalidSpanError("requested span not rectangular")
+ if a.left == b.left and a.right != b.right:
+ raise InvalidSpanError("requested span not rectangular")
+
+ def raise_on_tee_shaped(a: CT_Tc, b: CT_Tc):
+ top_most, other = (a, b) if a.top < b.top else (b, a)
+ if top_most.top < other.top and top_most.bottom > other.bottom:
+ raise InvalidSpanError("requested span not rectangular")
+
+ left_most, other = (a, b) if a.left < b.left else (b, a)
+ if left_most.left < other.left and left_most.right > other.right:
+ raise InvalidSpanError("requested span not rectangular")
+
+ raise_on_inverted_L(self, other_tc)
+ raise_on_tee_shaped(self, other_tc)
+
+ top = min(self.top, other_tc.top)
+ left = min(self.left, other_tc.left)
+ bottom = max(self.bottom, other_tc.bottom)
+ right = max(self.right, other_tc.right)
+
+ return top, left, bottom - top, right - left
+
+ def _span_to_width(self, grid_width: int, top_tc: CT_Tc, vMerge: str | None):
+ """Incorporate `w:tc` elements to the right until this cell spans `grid_width`.
+
+ Incorporated `w:tc` elements are removed (replaced by gridSpan value).
+
+ Raises |ValueError| if `grid_width` cannot be exactly achieved, such as when a
+ merged cell would drive the span width greater than `grid_width` or if not
+ enough grid columns are available to make this cell that wide. All content from
+ incorporated cells is appended to `top_tc`. The val attribute of the vMerge
+ element on the single remaining cell is set to `vMerge`. If `vMerge` is |None|,
+ the vMerge element is removed if present.
+ """
+ self._move_content_to(top_tc)
+ while self.grid_span < grid_width:
+ self._swallow_next_tc(grid_width, top_tc)
+ self.vMerge = vMerge
+
+ def _swallow_next_tc(self, grid_width: int, top_tc: CT_Tc):
+ """Extend the horizontal span of this `w:tc` element to incorporate the
+ following `w:tc` element in the row and then delete that following `w:tc`
+ element.
+
+ Any content in the following `w:tc` element is appended to the content of
+ `top_tc`. The width of the following `w:tc` element is added to this one, if
+ present. Raises |InvalidSpanError| if the width of the resulting cell is greater
+ than `grid_width` or if there is no next `<w:tc>` element in the row.
+ """
+
+ def raise_on_invalid_swallow(next_tc: CT_Tc | None):
+ if next_tc is None:
+ raise InvalidSpanError("not enough grid columns")
+ if self.grid_span + next_tc.grid_span > grid_width:
+ raise InvalidSpanError("span is not rectangular")
+
+ next_tc = self._next_tc
+ raise_on_invalid_swallow(next_tc)
+ assert next_tc is not None
+ next_tc._move_content_to(top_tc)
+ self._add_width_of(next_tc)
+ self.grid_span += next_tc.grid_span
+ next_tc._remove()
+
+ @property
+ def _tbl(self) -> CT_Tbl:
+ """The tbl element this tc element appears in."""
+ return cast(CT_Tbl, self.xpath("./ancestor::w:tbl[position()=1]")[0])
+
+ @property
+ def _tc_above(self) -> CT_Tc:
+ """The `w:tc` element immediately above this one in its grid column."""
+ return self._tr_above.tc_at_grid_offset(self.grid_offset)
+
+ @property
+ def _tc_below(self) -> CT_Tc | None:
+ """The tc element immediately below this one in its grid column."""
+ tr_below = self._tr_below
+ if tr_below is None:
+ return None
+ return tr_below.tc_at_grid_offset(self.grid_offset)
+
+ @property
+ def _tr(self) -> CT_Row:
+ """The tr element this tc element appears in."""
+ return cast(CT_Row, self.xpath("./ancestor::w:tr[position()=1]")[0])
+
+ @property
+ def _tr_above(self) -> CT_Row:
+ """The tr element prior in sequence to the tr this cell appears in.
+
+ Raises |ValueError| if called on a cell in the top-most row.
+ """
+ tr_aboves = self.xpath("./ancestor::w:tr[position()=1]/preceding-sibling::w:tr[1]")
+ if not tr_aboves:
+ raise ValueError("no tr above topmost tr in w:tbl")
+ return tr_aboves[0]
+
+ @property
+ def _tr_below(self) -> CT_Row | None:
+ """The tr element next in sequence after the tr this cell appears in, or |None|
+ if this cell appears in the last row."""
+ tr_lst = self._tbl.tr_lst
+ tr_idx = tr_lst.index(self._tr)
+ try:
+ return tr_lst[tr_idx + 1]
+ except IndexError:
+ return None
+
+ @property
+ def _tr_idx(self) -> int:
+ """The row index of the tr element this tc element appears in."""
+ return self._tbl.tr_lst.index(self._tr)
+
+
+class CT_TcPr(BaseOxmlElement):
+ """``<w:tcPr>`` element, defining table cell properties."""
+
+ get_or_add_gridSpan: Callable[[], CT_DecimalNumber]
+ get_or_add_tcW: Callable[[], CT_TblWidth]
+ get_or_add_vAlign: Callable[[], CT_VerticalJc]
+ _add_vMerge: Callable[[], CT_VMerge]
+ _remove_gridSpan: Callable[[], None]
+ _remove_vAlign: Callable[[], None]
+ _remove_vMerge: Callable[[], None]
+
+ _tag_seq = (
+ "w:cnfStyle",
+ "w:tcW",
+ "w:gridSpan",
+ "w:hMerge",
+ "w:vMerge",
+ "w:tcBorders",
+ "w:shd",
+ "w:noWrap",
+ "w:tcMar",
+ "w:textDirection",
+ "w:tcFitText",
+ "w:vAlign",
+ "w:hideMark",
+ "w:headers",
+ "w:cellIns",
+ "w:cellDel",
+ "w:cellMerge",
+ "w:tcPrChange",
+ )
+ tcW: CT_TblWidth | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:tcW", successors=_tag_seq[2:]
+ )
+ gridSpan: CT_DecimalNumber | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:gridSpan", successors=_tag_seq[3:]
+ )
+ vMerge: CT_VMerge | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:vMerge", successors=_tag_seq[5:]
+ )
+ vAlign: CT_VerticalJc | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:vAlign", successors=_tag_seq[12:]
+ )
+ del _tag_seq
+
+ @property
+ def grid_span(self) -> int:
+ """The integer number of columns this cell spans.
+
+ Determined by ./w:gridSpan/@val, it defaults to 1.
+ """
+ gridSpan = self.gridSpan
+ return 1 if gridSpan is None else gridSpan.val
+
+ @grid_span.setter
+ def grid_span(self, value: int):
+ self._remove_gridSpan()
+ if value > 1:
+ self.get_or_add_gridSpan().val = value
+
+ @property
+ def vAlign_val(self):
+ """Value of `w:val` attribute on `w:vAlign` child.
+
+ Value is |None| if `w:vAlign` child is not present. The `w:val` attribute on
+ `w:vAlign` is required.
+ """
+ vAlign = self.vAlign
+ if vAlign is None:
+ return None
+ return vAlign.val
+
+ @vAlign_val.setter
+ def vAlign_val(self, value: WD_CELL_VERTICAL_ALIGNMENT | None):
+ if value is None:
+ self._remove_vAlign()
+ return
+ self.get_or_add_vAlign().val = value
+
+ @property
+ def vMerge_val(self):
+ """The value of the ./w:vMerge/@val attribute, or |None| if the w:vMerge element
+ is not present."""
+ vMerge = self.vMerge
+ if vMerge is None:
+ return None
+ return vMerge.val
+
+ @vMerge_val.setter
+ def vMerge_val(self, value: str | None):
+ self._remove_vMerge()
+ if value is not None:
+ self._add_vMerge().val = value
+
+ @property
+ def width(self) -> Length | None:
+ """EMU length in `./w:tcW` or |None| if not present or its type is not 'dxa'."""
+ tcW = self.tcW
+ if tcW is None:
+ return None
+ return tcW.width
+
+ @width.setter
+ def width(self, value: Length):
+ tcW = self.get_or_add_tcW()
+ tcW.width = value
+
+
+class CT_TrPr(BaseOxmlElement):
+ """``<w:trPr>`` element, defining table row properties."""
+
+ get_or_add_trHeight: Callable[[], CT_Height]
+
+ _tag_seq = (
+ "w:cnfStyle",
+ "w:divId",
+ "w:gridBefore",
+ "w:gridAfter",
+ "w:wBefore",
+ "w:wAfter",
+ "w:cantSplit",
+ "w:trHeight",
+ "w:tblHeader",
+ "w:tblCellSpacing",
+ "w:jc",
+ "w:hidden",
+ "w:ins",
+ "w:del",
+ "w:trPrChange",
+ )
+ gridAfter: CT_DecimalNumber | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:gridAfter", successors=_tag_seq[4:]
+ )
+ gridBefore: CT_DecimalNumber | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:gridBefore", successors=_tag_seq[3:]
+ )
+ trHeight: CT_Height | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "w:trHeight", successors=_tag_seq[8:]
+ )
+ del _tag_seq
+
+ @property
+ def grid_after(self) -> int:
+ """The number of unpopulated layout-grid cells at the end of this row."""
+ gridAfter = self.gridAfter
+ return 0 if gridAfter is None else gridAfter.val
+
+ @property
+ def grid_before(self) -> int:
+ """The number of unpopulated layout-grid cells at the start of this row."""
+ gridBefore = self.gridBefore
+ return 0 if gridBefore is None else gridBefore.val
+
+ @property
+ def trHeight_hRule(self) -> WD_ROW_HEIGHT_RULE | None:
+ """Return the value of `w:trHeight@w:hRule`, or |None| if not present."""
+ trHeight = self.trHeight
+ return None if trHeight is None else trHeight.hRule
+
+ @trHeight_hRule.setter
+ def trHeight_hRule(self, value: WD_ROW_HEIGHT_RULE | None):
+ if value is None and self.trHeight is None:
+ return
+ trHeight = self.get_or_add_trHeight()
+ trHeight.hRule = value
+
+ @property
+ def trHeight_val(self):
+ """Return the value of `w:trHeight@w:val`, or |None| if not present."""
+ trHeight = self.trHeight
+ return None if trHeight is None else trHeight.val
+
+ @trHeight_val.setter
+ def trHeight_val(self, value: Length | None):
+ if value is None and self.trHeight is None:
+ return
+ trHeight = self.get_or_add_trHeight()
+ trHeight.val = value
+
+
+class CT_VerticalJc(BaseOxmlElement):
+ """`w:vAlign` element, specifying vertical alignment of cell."""
+
+ val: WD_CELL_VERTICAL_ALIGNMENT = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "w:val", WD_CELL_VERTICAL_ALIGNMENT
+ )
+
+
+class CT_VMerge(BaseOxmlElement):
+ """``<w:vMerge>`` element, specifying vertical merging behavior of a cell."""
+
+ val: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "w:val", ST_Merge, default=ST_Merge.CONTINUE
+ )