@@ -0,0 +1,119 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from docx.oxml import CT_Tbl
|
||||
from docx.oxml.ns import qn
|
||||
from docx.shared import Length, Parented
|
||||
from docx.table import Table, _Row, _Cell
|
||||
|
||||
from md2gost.util import create_element
|
||||
|
||||
|
||||
def _twips(value) -> int:
|
||||
"""Length or EMU-int → twips.
|
||||
|
||||
python-docx ``Length + Length`` returns a bare ``int`` in EMUs, not twips.
|
||||
"""
|
||||
if hasattr(value, "twips"):
|
||||
return int(value.twips)
|
||||
# bare int from Length arithmetic is EMU (1 twip = 635 EMU)
|
||||
return int(value) // 635
|
||||
|
||||
|
||||
__all__ = [
|
||||
"create_table",
|
||||
"create_table_row",
|
||||
"create_table_cell",
|
||||
"apply_cell_merge",
|
||||
]
|
||||
|
||||
|
||||
def _set_table_width(table: Table, width: Length) -> None:
|
||||
"""Force fixed table width (CT_Tbl.new_tbl leaves tblW auto/0 → Word shrinks table)."""
|
||||
tbl = table._tbl
|
||||
tblPr = tbl.tblPr
|
||||
if tblPr is None:
|
||||
tblPr = create_element("w:tblPr")
|
||||
tbl.insert(0, tblPr)
|
||||
for el in tblPr.findall(qn("w:tblW")):
|
||||
tblPr.remove(el)
|
||||
tblPr.append(create_element("w:tblW", {
|
||||
"w:type": "dxa",
|
||||
"w:w": str(_twips(width)),
|
||||
}))
|
||||
for el in tblPr.findall(qn("w:tblLayout")):
|
||||
tblPr.remove(el)
|
||||
tblPr.append(create_element("w:tblLayout", {"w:type": "fixed"}))
|
||||
for el in tblPr.findall(qn("w:tblInd")):
|
||||
tblPr.remove(el)
|
||||
tblPr.append(create_element("w:tblInd", {
|
||||
"w:type": "dxa",
|
||||
"w:w": "0",
|
||||
}))
|
||||
|
||||
|
||||
def _sync_grid_cols(table: Table, cols: int, width: Length) -> None:
|
||||
tbl = table._tbl
|
||||
grid = tbl.tblGrid
|
||||
if grid is None:
|
||||
grid = create_element("w:tblGrid")
|
||||
tbl.insert(list(tbl).index(tbl.tblPr) + 1 if tbl.tblPr is not None else 0, grid)
|
||||
for el in list(grid.findall(qn("w:gridCol"))):
|
||||
grid.remove(el)
|
||||
col_w = max(_twips(width) // max(cols, 1), 1)
|
||||
for _ in range(cols):
|
||||
grid.append(create_element("w:gridCol", {"w:w": str(col_w)}))
|
||||
|
||||
|
||||
def create_table(parent: Parented, rows: int, cols, width: Length, style="Table Grid"):
|
||||
table = Table(CT_Tbl.new_tbl(rows, cols, width), parent)
|
||||
table.style = style
|
||||
_set_table_width(table, width)
|
||||
_sync_grid_cols(table, cols, width)
|
||||
|
||||
# google docs fix
|
||||
borders = parent.part.styles[style]._element.xpath("w:tblPr/w:tblBorders")
|
||||
if borders:
|
||||
tblPr = table._tbl.tblPr
|
||||
for el in tblPr.findall(qn("w:tblBorders")):
|
||||
tblPr.remove(el)
|
||||
tblPr.append(deepcopy(borders[0]))
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
cell = table.cell(i, j)
|
||||
cell._element.remove(cell.paragraphs[0]._element)
|
||||
table.cell(i, j)._element.tcPr.append(create_element("w:shd", {
|
||||
"w:fill": "auto", "w:val": "clear"
|
||||
}))
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def create_table_row(parent: Table, *, header: bool = False):
|
||||
children = [create_element("w:cantSplit")]
|
||||
if header:
|
||||
children.append(create_element("w:tblHeader"))
|
||||
row = _Row(create_element("w:tr"), parent)
|
||||
row._tr.insert(0, create_element("w:trPr", children))
|
||||
return row
|
||||
|
||||
|
||||
def create_table_cell(parent: _Row, width: Length):
|
||||
cell = _Cell(create_element("w:tc"), parent)
|
||||
cell.width = width
|
||||
return cell
|
||||
|
||||
|
||||
def apply_cell_merge(cell: _Cell, v_merge: str | None = None, grid_span: int | None = None) -> None:
|
||||
"""Apply Word OXML vertical merge and/or horizontal gridSpan on a cell.
|
||||
|
||||
v_merge: ``restart`` | ``continue`` | None
|
||||
grid_span: column span (>= 2) or None
|
||||
"""
|
||||
tc = cell._tc
|
||||
tcPr = tc.get_or_add_tcPr()
|
||||
if v_merge == "restart":
|
||||
tcPr.append(create_element("w:vMerge", {"w:val": "restart"}))
|
||||
elif v_merge == "continue":
|
||||
tcPr.append(create_element("w:vMerge"))
|
||||
if grid_span and grid_span > 1:
|
||||
tcPr.append(create_element("w:gridSpan", {"w:val": str(grid_span)}))
|
||||
Reference in New Issue
Block a user