Skip to content
This repository was archived by the owner on Jan 7, 2024. It is now read-only.

Commit 22752a9

Browse files
author
Steve Canny
committed
tbl: add Table._cells
1 parent 316b003 commit 22752a9

File tree

7 files changed

+316
-30
lines changed

7 files changed

+316
-30
lines changed

docx/oxml/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,10 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
115115

116116
from docx.oxml.table import (
117117
CT_Row, CT_Tbl, CT_TblGrid, CT_TblGridCol, CT_TblLayoutType, CT_TblPr,
118-
CT_TblWidth, CT_Tc, CT_TcPr
118+
CT_TblWidth, CT_Tc, CT_TcPr, CT_VMerge
119119
)
120120
register_element_cls('w:gridCol', CT_TblGridCol)
121+
register_element_cls('w:gridSpan', CT_DecimalNumber)
121122
register_element_cls('w:tbl', CT_Tbl)
122123
register_element_cls('w:tblGrid', CT_TblGrid)
123124
register_element_cls('w:tblLayout', CT_TblLayoutType)
@@ -127,6 +128,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
127128
register_element_cls('w:tcPr', CT_TcPr)
128129
register_element_cls('w:tcW', CT_TblWidth)
129130
register_element_cls('w:tr', CT_Row)
131+
register_element_cls('w:vMerge', CT_VMerge)
130132

131133
from docx.oxml.text import (
132134
CT_Br, CT_Jc, CT_P, CT_PPr, CT_R, CT_RPr, CT_Text, CT_Underline

docx/oxml/simpletypes.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,34 +54,45 @@ def validate_string(cls, value):
5454
)
5555

5656

57-
class BaseStringType(BaseSimpleType):
57+
class BaseIntType(BaseSimpleType):
5858

5959
@classmethod
6060
def convert_from_xml(cls, str_value):
61-
return str_value
61+
return int(str_value)
6262

6363
@classmethod
6464
def convert_to_xml(cls, value):
65-
return value
65+
return str(value)
6666

6767
@classmethod
6868
def validate(cls, value):
69-
cls.validate_string(value)
69+
cls.validate_int(value)
7070

7171

72-
class BaseIntType(BaseSimpleType):
72+
class BaseStringType(BaseSimpleType):
7373

7474
@classmethod
7575
def convert_from_xml(cls, str_value):
76-
return int(str_value)
76+
return str_value
7777

7878
@classmethod
7979
def convert_to_xml(cls, value):
80-
return str(value)
80+
return value
8181

8282
@classmethod
8383
def validate(cls, value):
84-
cls.validate_int(value)
84+
cls.validate_string(value)
85+
86+
87+
class BaseStringEnumerationType(BaseStringType):
88+
89+
@classmethod
90+
def validate(cls, value):
91+
cls.validate_string(value)
92+
if value not in cls._members:
93+
raise ValueError(
94+
"must be one of %s, got '%s'" % (cls._members, value)
95+
)
8596

8697

8798
class XsdAnyUri(BaseStringType):
@@ -144,6 +155,12 @@ class XsdString(BaseStringType):
144155
pass
145156

146157

158+
class XsdStringEnumeration(BaseStringEnumerationType):
159+
"""
160+
Set of enumerated xsd:string values.
161+
"""
162+
163+
147164
class XsdToken(BaseStringType):
148165
"""
149166
xsd:string with whitespace collapsing, e.g. multiple spaces reduced to
@@ -218,6 +235,16 @@ class ST_DrawingElementId(XsdUnsignedInt):
218235
pass
219236

220237

238+
class ST_Merge(XsdStringEnumeration):
239+
"""
240+
Valid values for <w:xMerge val=""> attribute
241+
"""
242+
CONTINUE = 'continue'
243+
RESTART = 'restart'
244+
245+
_members = (CONTINUE, RESTART)
246+
247+
221248
class ST_OnOff(XsdBoolean):
222249

223250
@classmethod

docx/oxml/table.py

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from .ns import nsdecls
1111
from ..shared import Emu, Twips
1212
from .simpletypes import (
13-
ST_TblLayoutType, ST_TblWidth, ST_TwipsMeasure, XsdInt
13+
ST_Merge, ST_TblLayoutType, ST_TblWidth, ST_TwipsMeasure, XsdInt
1414
)
1515
from .xmlchemy import (
1616
BaseOxmlElement, OneAndOnlyOne, OneOrMore, OptionalAttribute,
@@ -44,6 +44,16 @@ class CT_Tbl(BaseOxmlElement):
4444
tblGrid = OneAndOnlyOne('w:tblGrid')
4545
tr = ZeroOrMore('w:tr')
4646

47+
def iter_tcs(self):
48+
"""
49+
Generate each of the `w:tc` elements in this table, left to right and
50+
top to bottom. Each cell in the first row is generated, followed by
51+
each cell in the second row, etc.
52+
"""
53+
for tr in self.tr_lst:
54+
for tc in tr.tc_lst:
55+
yield tc
56+
4757
@classmethod
4858
def new(cls):
4959
"""
@@ -182,18 +192,6 @@ class CT_Tc(BaseOxmlElement):
182192
p = OneOrMore('w:p')
183193
tbl = OneOrMore('w:tbl')
184194

185-
def _insert_tcPr(self, tcPr):
186-
"""
187-
``tcPr`` has a bunch of successors, but it comes first if it appears,
188-
so just overriding and using insert(0, ...) rather than spelling out
189-
successors.
190-
"""
191-
self.insert(0, tcPr)
192-
return tcPr
193-
194-
def _new_tbl(self):
195-
return CT_Tbl.new()
196-
197195
def clear_content(self):
198196
"""
199197
Remove all content child elements, preserving the ``<w:tcPr>``
@@ -208,6 +206,17 @@ def clear_content(self):
208206
new_children.append(tcPr)
209207
self[:] = new_children
210208

209+
@property
210+
def grid_span(self):
211+
"""
212+
The integer number of columns this cell spans. Determined by
213+
./w:tcPr/w:gridSpan/@val, it defaults to 1.
214+
"""
215+
tcPr = self.tcPr
216+
if tcPr is None:
217+
return 1
218+
return tcPr.grid_span
219+
211220
@classmethod
212221
def new(cls):
213222
"""
@@ -220,6 +229,17 @@ def new(cls):
220229
'</w:tc>' % nsdecls('w')
221230
)
222231

232+
@property
233+
def vMerge(self):
234+
"""
235+
The value of the ./w:tcPr/w:vMerge/@val attribute, or |None| if the
236+
w:vMerge element is not present.
237+
"""
238+
tcPr = self.tcPr
239+
if tcPr is None:
240+
return None
241+
return tcPr.vMerge_val
242+
223243
@property
224244
def width(self):
225245
"""
@@ -236,17 +256,55 @@ def width(self, value):
236256
tcPr = self.get_or_add_tcPr()
237257
tcPr.width = value
238258

259+
def _insert_tcPr(self, tcPr):
260+
"""
261+
``tcPr`` has a bunch of successors, but it comes first if it appears,
262+
so just overriding and using insert(0, ...) rather than spelling out
263+
successors.
264+
"""
265+
self.insert(0, tcPr)
266+
return tcPr
267+
268+
def _new_tbl(self):
269+
return CT_Tbl.new()
270+
239271

240272
class CT_TcPr(BaseOxmlElement):
241273
"""
242274
``<w:tcPr>`` element, defining table cell properties
243275
"""
244-
tcW = ZeroOrOne('w:tcW', successors=(
245-
'w:gridSpan', 'w:hMerge', 'w:vMerge', 'w:tcBorders', 'w:shd',
246-
'w:noWrap', 'w:tcMar', 'w:textDirection', 'w:tcFitText', 'w:vAlign',
247-
'w:hideMark', 'w:headers', 'w:cellIns', 'w:cellDel', 'w:cellMerge',
248-
'w:tcPrChange'
249-
))
276+
_tag_seq = (
277+
'w:cnfStyle', 'w:tcW', 'w:gridSpan', 'w:hMerge', 'w:vMerge',
278+
'w:tcBorders', 'w:shd', 'w:noWrap', 'w:tcMar', 'w:textDirection',
279+
'w:tcFitText', 'w:vAlign', 'w:hideMark', 'w:headers', 'w:cellIns',
280+
'w:cellDel', 'w:cellMerge', 'w:tcPrChange'
281+
)
282+
tcW = ZeroOrOne('w:tcW', successors=_tag_seq[2:])
283+
gridSpan = ZeroOrOne('w:gridSpan', successors=_tag_seq[3:])
284+
vMerge = ZeroOrOne('w:vMerge', successors=_tag_seq[5:])
285+
del _tag_seq
286+
287+
@property
288+
def grid_span(self):
289+
"""
290+
The integer number of columns this cell spans. Determined by
291+
./w:gridSpan/@val, it defaults to 1.
292+
"""
293+
gridSpan = self.gridSpan
294+
if gridSpan is None:
295+
return 1
296+
return gridSpan.val
297+
298+
@property
299+
def vMerge_val(self):
300+
"""
301+
The value of the ./w:vMerge/@val attribute, or |None| if the
302+
w:vMerge element is not present.
303+
"""
304+
vMerge = self.vMerge
305+
if vMerge is None:
306+
return None
307+
return vMerge.val
250308

251309
@property
252310
def width(self):
@@ -263,3 +321,10 @@ def width(self):
263321
def width(self, value):
264322
tcW = self.get_or_add_tcW()
265323
tcW.width = value
324+
325+
326+
class CT_VMerge(BaseOxmlElement):
327+
"""
328+
``<w:vMerge>`` element, specifying vertical merging behavior of a cell.
329+
"""
330+
val = OptionalAttribute('w:val', ST_Merge, default=ST_Merge.CONTINUE)

docx/table.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from __future__ import absolute_import, print_function, unicode_literals
88

99
from .blkcntnr import BlockItemContainer
10+
from .oxml.simpletypes import ST_Merge
1011
from .shared import lazyproperty, Parented
1112

1213

@@ -113,7 +114,17 @@ def _cells(self):
113114
If the table contains a span, one or more |_Cell| object references
114115
are repeated.
115116
"""
116-
raise NotImplementedError
117+
col_count = self._column_count
118+
cells = []
119+
for tc in self._tbl.iter_tcs():
120+
for grid_span_idx in range(tc.grid_span):
121+
if tc.vMerge == ST_Merge.CONTINUE:
122+
cells.append(cells[-col_count])
123+
elif grid_span_idx > 0:
124+
cells.append(cells[-1])
125+
else:
126+
cells.append(_Cell(tc, self))
127+
return cells
117128

118129
@property
119130
def _column_count(self):

features/tbl-cell-access.feature

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ Feature: Access table cells
33
As a developer using python-docx
44
I need a way to access a cell from a table, row, or column
55

6-
@wip
76
Scenario Outline: Access cell sequence of a row
87
Given a 3x3 table having <span-state>
98
Then the row cells text is <expected-text>

0 commit comments

Comments
 (0)