Skip to content

Commit d32fc11

Browse files
author
Steve Canny
committed
tbl: add Table.add_column()
1 parent 38eaca6 commit d32fc11

File tree

7 files changed

+252
-21
lines changed

7 files changed

+252
-21
lines changed

docs/dev/analysis/features/table.rst

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -199,19 +199,10 @@ Schema Definitions
199199
<xsd:attribute name="val" type="ST_ShortHexNumber"/>
200200
</xsd:complexType>
201201

202-
<xsd:complexType name="CT_TblGrid">
203-
<xsd:complexContent>
204-
<xsd:extension base="CT_TblGridBase">
205-
<xsd:sequence>
206-
<xsd:element name="tblGridChange" type="CT_TblGridChange" minOccurs="0"/>
207-
</xsd:sequence>
208-
</xsd:extension>
209-
</xsd:complexContent>
210-
</xsd:complexType>
211-
212-
<xsd:complexType name="CT_TblGridBase">
202+
<xsd:complexType name="CT_TblGrid"> <!-- denormalized -->
213203
<xsd:sequence>
214-
<xsd:element name="gridCol" type="CT_TblGridCol" minOccurs="0" maxOccurs="unbounded"/>
204+
<xsd:element name="gridCol" type="CT_TblGridCol" minOccurs="0" maxOccurs="unbounded"/>
205+
<xsd:element name="tblGridChange" type="CT_TblGridChange" minOccurs="0"/>
215206
</xsd:sequence>
216207
</xsd:complexType>
217208

docx/oxml/__init__.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,29 @@
88
from docx.oxml.shared import register_custom_element_class
99

1010

11+
class ValidationError(Exception):
12+
"""
13+
Raised when invalid XML is encountered, such as on attempt to access a
14+
missing required child element
15+
"""
16+
17+
1118
# ===========================================================================
1219
# custom element class mappings
1320
# ===========================================================================
1421

1522
from docx.oxml.parts import CT_Body, CT_Document
16-
register_custom_element_class('w:body', CT_Body)
23+
register_custom_element_class('w:body', CT_Body)
1724
register_custom_element_class('w:document', CT_Document)
1825

26+
from docx.oxml.table import CT_Row, CT_Tbl, CT_TblGrid
27+
register_custom_element_class('w:tbl', CT_Tbl)
28+
register_custom_element_class('w:tblGrid', CT_TblGrid)
29+
register_custom_element_class('w:tr', CT_Row)
30+
1931
from docx.oxml.text import CT_P, CT_PPr, CT_R, CT_String, CT_Text
20-
register_custom_element_class('w:p', CT_P)
21-
register_custom_element_class('w:pPr', CT_PPr)
32+
register_custom_element_class('w:p', CT_P)
33+
register_custom_element_class('w:pPr', CT_PPr)
2234
register_custom_element_class('w:pStyle', CT_String)
23-
register_custom_element_class('w:r', CT_R)
24-
register_custom_element_class('w:t', CT_Text)
35+
register_custom_element_class('w:r', CT_R)
36+
register_custom_element_class('w:t', CT_Text)

docx/oxml/table.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Custom element classes for tables
5+
"""
6+
7+
from __future__ import absolute_import, print_function, unicode_literals
8+
9+
from docx.oxml.shared import OxmlBaseElement, OxmlElement, qn
10+
11+
from . import ValidationError
12+
from .text import CT_P
13+
14+
15+
class CT_Row(OxmlBaseElement):
16+
"""
17+
``<w:tr>`` element
18+
"""
19+
def add_tc(self):
20+
"""
21+
Return a new <w:tc> element that has been added at the end of any
22+
existing tc elements.
23+
"""
24+
tc = CT_Tc.new()
25+
return self._append_tc(tc)
26+
27+
def _append_tc(self, tc):
28+
"""
29+
Return *tc* after appending it to end of tc sequence.
30+
"""
31+
self.append(tc)
32+
return tc
33+
34+
35+
class CT_Tbl(OxmlBaseElement):
36+
"""
37+
``<w:tbl>`` element
38+
"""
39+
@property
40+
def tblGrid(self):
41+
tblGrid = self.find(qn('w:tblGrid'))
42+
if tblGrid is None:
43+
raise ValidationError('required w:tblGrid child not found')
44+
return tblGrid
45+
46+
@property
47+
def tr_lst(self):
48+
"""
49+
Sequence containing the ``<w:tr>`` child elements in this
50+
``<w:tbl>``.
51+
"""
52+
return self.findall(qn('w:tr'))
53+
54+
55+
class CT_TblGrid(OxmlBaseElement):
56+
"""
57+
``<w:tblGrid>`` element, child of ``<w:tbl>``, holds ``<w:gridCol>``
58+
elements that define column count, width, etc.
59+
"""
60+
def add_gridCol(self):
61+
"""
62+
Return a new <w:gridCol> element that has been added at the end of
63+
any existing gridCol elements.
64+
"""
65+
gridCol = CT_TblGridCol.new()
66+
return self._append_gridCol(gridCol)
67+
68+
def _append_gridCol(self, gridCol):
69+
"""
70+
Return *gridCol* after appending it to end of gridCol sequence.
71+
"""
72+
successor = self.first_child_found_in('w:tblGridChange')
73+
if successor is not None:
74+
successor.addprevious(gridCol)
75+
else:
76+
self.append(gridCol)
77+
return gridCol
78+
79+
def first_child_found_in(self, *tagnames):
80+
"""
81+
Return the first child found with tag in *tagnames*, or None if
82+
not found.
83+
"""
84+
for tagname in tagnames:
85+
child = self.find(qn(tagname))
86+
if child is not None:
87+
return child
88+
return None
89+
90+
91+
class CT_TblGridCol(OxmlBaseElement):
92+
"""
93+
``<w:gridCol>`` element, child of ``<w:tblGrid>``, defines a table
94+
column.
95+
"""
96+
@classmethod
97+
def new(cls):
98+
"""
99+
Return a new ``<w:gridCol>`` element.
100+
"""
101+
return OxmlElement('w:gridCol')
102+
103+
104+
class CT_Tc(OxmlBaseElement):
105+
"""
106+
``<w:tc>`` table cell element
107+
"""
108+
@classmethod
109+
def new(cls):
110+
"""
111+
Return a new ``<w:tc>`` element, containing an empty paragraph as the
112+
required EG_BlockLevelElt.
113+
"""
114+
tc = OxmlElement('w:tc')
115+
p = CT_P.new()
116+
tc.append(p)
117+
return tc

docx/table.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,25 @@ class Table(object):
99
"""
1010
Proxy class for a WordprocessingML ``<w:tbl>`` element.
1111
"""
12-
def __init__(self, tbl_elm):
12+
def __init__(self, tbl):
1313
super(Table, self).__init__()
14-
self._tbl = tbl_elm
14+
self._tbl = tbl
15+
16+
def add_column(self):
17+
"""
18+
Return a |_Column| instance, newly added rightmost to the table.
19+
"""
20+
tblGrid = self._tbl.tblGrid
21+
gridCol = tblGrid.add_gridCol()
22+
for tr in self._tbl.tr_lst:
23+
tr.add_tc()
24+
return _Column(gridCol)
25+
26+
27+
class _Column(object):
28+
"""
29+
Table column
30+
"""
31+
def __init__(self, gridCol):
32+
super(_Column, self).__init__()
33+
self._gridCol = gridCol

tests/oxml/unitdata/table.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,51 @@
77
from ...unitdata import BaseBuilder
88

99

10+
class CT_RowBuilder(BaseBuilder):
11+
__tag__ = 'w:tr'
12+
__nspfxs__ = ('w',)
13+
__attrs__ = ('w:w',)
14+
15+
1016
class CT_TblBuilder(BaseBuilder):
1117
__tag__ = 'w:tbl'
1218
__nspfxs__ = ('w',)
1319
__attrs__ = ()
1420

1521

22+
class CT_TblGridBuilder(BaseBuilder):
23+
__tag__ = 'w:tblGrid'
24+
__nspfxs__ = ('w',)
25+
__attrs__ = ('w:w',)
26+
27+
28+
class CT_TblGridColBuilder(BaseBuilder):
29+
__tag__ = 'w:gridCol'
30+
__nspfxs__ = ('w',)
31+
__attrs__ = ('w:w',)
32+
33+
34+
class CT_TcBuilder(BaseBuilder):
35+
__tag__ = 'w:tc'
36+
__nspfxs__ = ('w',)
37+
__attrs__ = ('w:id',)
38+
39+
40+
def a_gridCol():
41+
return CT_TblGridColBuilder()
42+
43+
1644
def a_tbl():
1745
return CT_TblBuilder()
46+
47+
48+
def a_tblGrid():
49+
return CT_TblGridBuilder()
50+
51+
52+
def a_tc():
53+
return CT_TcBuilder()
54+
55+
56+
def a_tr():
57+
return CT_RowBuilder()

tests/test_parts.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66

77
from __future__ import absolute_import, print_function, unicode_literals
88

9-
from docx.parts import _Body, _Document
10-
119
import pytest
1210

1311
from mock import Mock
1412

13+
from docx.parts import _Body, _Document
1514
from docx.table import Table
1615
from docx.text import Paragraph
1716

tests/test_table.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Test suite for the docx.table module
5+
"""
6+
7+
from __future__ import absolute_import, print_function, unicode_literals
8+
9+
import pytest
10+
11+
from docx.table import _Column, Table
12+
13+
from .oxml.unitdata.table import a_gridCol, a_tbl, a_tblGrid, a_tc, a_tr
14+
from .oxml.unitdata.text import a_p
15+
16+
17+
class DescribeTable(object):
18+
19+
def it_can_add_a_column(self, add_column_fixture):
20+
table, expected_xml = add_column_fixture
21+
col = table.add_column()
22+
print('\n%s' % table._tbl.xml)
23+
assert table._tbl.xml == expected_xml
24+
assert isinstance(col, _Column)
25+
26+
# fixtures -------------------------------------------------------
27+
28+
@pytest.fixture
29+
def add_column_fixture(self):
30+
tbl = self._tbl_bldr(2, 1).element
31+
table = Table(tbl)
32+
expected_xml = self._tbl_bldr(2, 2).xml()
33+
return table, expected_xml
34+
35+
def _tbl_bldr(self, rows, cols):
36+
tblGrid_bldr = a_tblGrid()
37+
for i in range(cols):
38+
tblGrid_bldr.with_child(a_gridCol())
39+
tbl_bldr = a_tbl().with_nsdecls().with_child(tblGrid_bldr)
40+
for i in range(rows):
41+
tr_bldr = self._tr_bldr(cols)
42+
tbl_bldr.with_child(tr_bldr)
43+
return tbl_bldr
44+
45+
def _tc_bldr(self):
46+
return a_tc().with_child(a_p())
47+
48+
def _tr_bldr(self, cols):
49+
tr_bldr = a_tr()
50+
for i in range(cols):
51+
tc_bldr = self._tc_bldr()
52+
tr_bldr.with_child(tc_bldr)
53+
return tr_bldr

0 commit comments

Comments
 (0)