diff --git a/docx/blkcntnr.py b/docx/blkcntnr.py index d57a0cd0f..bd549658f 100644 --- a/docx/blkcntnr.py +++ b/docx/blkcntnr.py @@ -38,14 +38,14 @@ def add_paragraph(self, text='', style=None): paragraph.style = style return paragraph - def add_table(self, rows, cols, width): + def add_table(self, rows, cols, width, firstCol=1, firstRow=1, lastCol=0, lastRow=0, hBand=1, vBand=0): """ Return a table of *width* having *rows* rows and *cols* columns, newly appended to the content in this container. *width* is evenly distributed between the table columns. """ from .table import Table - tbl = CT_Tbl.new_tbl(rows, cols, width) + tbl = CT_Tbl.new_tbl(rows, cols, width, firstCol, firstRow, lastCol, lastRow, hBand, vBand) self._element._insert_tbl(tbl) return Table(tbl, self) diff --git a/docx/document.py b/docx/document.py index ba94a7990..a1098101b 100644 --- a/docx/document.py +++ b/docx/document.py @@ -89,14 +89,14 @@ def add_section(self, start_type=WD_SECTION.NEW_PAGE): new_sectPr.start_type = start_type return Section(new_sectPr) - def add_table(self, rows, cols, style=None): + def add_table(self, rows, cols, style=None, firstCol=1, firstRow=1, lastCol=0, lastRow=0, hBand=1, vBand=0): """ Add a table having row and column counts of *rows* and *cols* respectively and table style of *style*. *style* may be a paragraph style object or a paragraph style name. If *style* is |None|, the table inherits the default table style of the document. """ - table = self._body.add_table(rows, cols, self._block_width) + table = self._body.add_table(rows, cols, self._block_width, firstCol, firstRow, lastCol, lastRow, hBand, vBand) table.style = style return table diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index 2731302e2..8905aea59 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -129,7 +129,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None): from .table import ( CT_Height, CT_Row, CT_Tbl, CT_TblGrid, CT_TblGridCol, CT_TblLayoutType, - CT_TblPr, CT_TblWidth, CT_Tc, CT_TcPr, CT_TrPr, CT_VerticalJc, CT_VMerge + CT_TblPr, CT_TblWidth, CT_TblLook, CT_Tc, CT_TcPr, CT_TrPr, CT_VerticalJc, CT_VMerge ) register_element_cls('w:bidiVisual', CT_OnOff) register_element_cls('w:gridCol', CT_TblGridCol) @@ -139,6 +139,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None): register_element_cls('w:tblLayout', CT_TblLayoutType) register_element_cls('w:tblPr', CT_TblPr) register_element_cls('w:tblStyle', CT_String) +register_element_cls('w:tblLook', CT_TblLook) register_element_cls('w:tc', CT_Tc) register_element_cls('w:tcPr', CT_TcPr) register_element_cls('w:tcW', CT_TblWidth) diff --git a/docx/oxml/table.py b/docx/oxml/table.py index 95f9c6243..b18ed47c1 100644 --- a/docx/oxml/table.py +++ b/docx/oxml/table.py @@ -12,7 +12,7 @@ from .ns import nsdecls, qn from ..shared import Emu, Twips from .simpletypes import ( - ST_Merge, ST_TblLayoutType, ST_TblWidth, ST_TwipsMeasure, XsdInt + ST_Merge, ST_TblLayoutType, ST_TblWidth, ST_TwipsMeasure, XsdInt, XsdBoolean ) from .xmlchemy import ( BaseOxmlElement, OneAndOnlyOne, OneOrMore, OptionalAttribute, @@ -150,12 +150,12 @@ def iter_tcs(self): yield tc @classmethod - def new_tbl(cls, rows, cols, width): + def new_tbl(cls, rows, cols, width, firstCol=1, firstRow=1, lastCol=0, lastRow=0, hBand=1, vBand=0): """ Return a new `w:tbl` element having *rows* rows and *cols* columns with *width* distributed evenly between the columns. """ - return parse_xml(cls._tbl_xml(rows, cols, width)) + return parse_xml(cls._tbl_xml(rows, cols, width, firstCol, firstRow, lastCol, lastRow, hBand, vBand)) @property def tblStyle_val(self): @@ -181,21 +181,27 @@ def tblStyle_val(self, styleId): tblPr._add_tblStyle().val = styleId @classmethod - def _tbl_xml(cls, rows, cols, width): + def _tbl_xml(cls, rows, cols, width, firstCol, firstRow, lastCol, lastRow, hBand, vBand): col_width = Emu(width/cols) if cols > 0 else Emu(0) return ( '\n' ' \n' ' \n' - ' \n' + ' \n' ' \n' '%s' # tblGrid '%s' # trs '\n' ) % ( nsdecls('w'), + firstCol, + firstRow, + lastCol, + lastRow, + 0 if hBand else 1, + 0 if vBand else 1, cls._tblGrid_xml(cols, col_width), cls._trs_xml(rows, cols, col_width) ) @@ -282,6 +288,7 @@ class CT_TblPr(BaseOxmlElement): bidiVisual = ZeroOrOne('w:bidiVisual', successors=_tag_seq[4:]) jc = ZeroOrOne('w:jc', successors=_tag_seq[8:]) tblLayout = ZeroOrOne('w:tblLayout', successors=_tag_seq[13:]) + tblLook = ZeroOrOne('w:tblLook', successors=_tag_seq[15:]) del _tag_seq @property @@ -365,6 +372,33 @@ def width(self, value): self.type = 'dxa' self.w = Emu(value).twips +class CT_TblLook(BaseOxmlElement): + """ + Used for ```` elements and many others, to + specify table-related looks. + """ + # 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 + firstCol = RequiredAttribute('w:firstColumn', XsdBoolean) + firstRow = RequiredAttribute('w:firstRow', XsdBoolean) + lastCol = RequiredAttribute('w:lastColumn', XsdBoolean) + lastRow = RequiredAttribute('w:lastRow', XsdBoolean) + noHBand = RequiredAttribute('w:noHBand', XsdBoolean) + noVBand = RequiredAttribute('w:noVBand', XsdBoolean) + + def hBand(self): + return 0 if self.noHBand else 1 + + def hBand(self, value): + self.noHBand = 0 if value else 1 + + def vBand(self): + return 0 if self.noVBand else 1 + + def vBand(self, value): + self.noVBand = 0 if value else 1 + class CT_Tc(BaseOxmlElement): """ diff --git a/docx/table.py b/docx/table.py index b3bc090fb..687fb1da4 100644 --- a/docx/table.py +++ b/docx/table.py @@ -158,6 +158,54 @@ def table_direction(self): def table_direction(self, value): self._element.bidiVisual_val = value + @property + def first_col(self): + return self._tbl.tblPr.tblLook.firstCol + + @first_col.setter + def first_col(self, value): + self._tbl.tblPr.tblLook.firstCol = value + + @property + def first_row(self): + return self._tbl.tblPr.tblLook.firstRow + + @first_row.setter + def first_row(self, value): + self._tbl.tblPr.tblLook.firstRow = value + + @property + def last_col(self): + return self._tbl.tblPr.tblLook.lastCol + + @last_col.setter + def last_col(self, value): + self._tbl.tblPr.tblLook.lastCol = value + + @property + def last_row(self): + return self._tbl.tblPr.tblLook.lastRow + + @last_row.setter + def last_row(self, value): + self._tbl.tblPr.tblLook.lastRow = value + + @property + def h_band(self): + return self._tbl.tblPr.tblLook.hBand + + @h_band.setter + def h_band(self, value): + self._tbl.tblPr.tblLook.hBand(value) + + @property + def v_band(self): + return self._tbl.tblPr.tblLook.vBand + + @v_band.setter + def v_band(self, value): + self._tbl.tblPr.tblLook.vBand(value) + @property def _cells(self): """ diff --git a/tests/test_document.py b/tests/test_document.py index c1cb060ec..2c7aaeb08 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -77,9 +77,9 @@ def it_can_add_a_section(self, add_section_fixture): assert section is section_ def it_can_add_a_table(self, add_table_fixture): - document, rows, cols, style, width, table_ = add_table_fixture - table = document.add_table(rows, cols, style) - document._body.add_table.assert_called_once_with(rows, cols, width) + document, rows, cols, style, width, firstCol, firstRow, lastCol, lastRow, hBand, vBand, table_ = add_table_fixture + table = document.add_table(rows, cols, style, firstCol, firstRow, lastCol, lastRow, hBand, vBand) + document._body.add_table.assert_called_once_with(rows, cols, width, firstCol, firstRow, lastCol, lastRow, hBand, vBand) assert table == table_ assert table.style == style @@ -201,9 +201,10 @@ def add_section_fixture(self, request, Section_): def add_table_fixture(self, _block_width_prop_, body_prop_, table_): document = Document(None, None) rows, cols, style = 4, 2, 'Light Shading Accent 1' + firstCol, firstRow, lastCol, lastRow, hBand, vBand = 1,0,1,0,1,0 body_prop_.return_value.add_table.return_value = table_ _block_width_prop_.return_value = width = 42 - return document, rows, cols, style, width, table_ + return document, rows, cols, style, width, firstCol, firstRow, lastCol, lastRow, hBand, vBand, table_ @pytest.fixture def block_width_fixture(self, sections_prop_, section_):