Skip to content

Table: feature : allow table looking (header row/col, footer row/col, bands) modification #579

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docx/blkcntnr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions docx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
48 changes: 41 additions & 7 deletions docx/oxml/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand All @@ -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 (
'<w:tbl %s>\n'
' <w:tblPr>\n'
' <w:tblW w:type="auto" w:w="0"/>\n'
' <w:tblLook w:firstColumn="1" w:firstRow="1"\n'
' w:lastColumn="0" w:lastRow="0" w:noHBand="0"\n'
' w:noVBand="1" w:val="04A0"/>\n'
' <w:tblLook w:firstColumn="%d" w:firstRow="%d"\n'
' w:lastColumn="%d" w:lastRow="%d" w:noHBand="%d"\n'
' w:noVBand="%d" w:val="04A0"/>\n'
' </w:tblPr>\n'
'%s' # tblGrid
'%s' # trs
'</w:tbl>\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)
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -365,6 +372,33 @@ def width(self, value):
self.type = 'dxa'
self.w = Emu(value).twips

class CT_TblLook(BaseOxmlElement):
"""
Used for ``<w:tblLook>`` 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):
"""
Expand Down
48 changes: 48 additions & 0 deletions docx/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
9 changes: 5 additions & 4 deletions tests/test_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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_):
Expand Down