X Tutup
Skip to content

Commit e386299

Browse files
author
Steve Canny
committed
txt: add Run.add_break()
Along the way: * refactor existing DescribeRun test methods * refactor CT_Text.new() * remove leftover print statements in test_package.py
1 parent 10055d2 commit e386299

File tree

9 files changed

+194
-43
lines changed

9 files changed

+194
-43
lines changed

docx/enum/text.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Enumerations related to text in WordprocessingML files
5+
"""
6+
7+
from __future__ import absolute_import, print_function, unicode_literals
8+
9+
10+
class WD_BREAK_TYPE(object):
11+
"""
12+
Corresponds to WdBreakType enumeration
13+
http://msdn.microsoft.com/en-us/library/office/ff195905.aspx
14+
"""
15+
COLUMN = 8
16+
LINE = 6
17+
LINE_CLEAR_LEFT = 9
18+
LINE_CLEAR_RIGHT = 10
19+
LINE_CLEAR_ALL = 11 # added for consistency, not in MS version
20+
PAGE = 7
21+
SECTION_CONTINUOUS = 3
22+
SECTION_EVEN_PAGE = 4
23+
SECTION_NEXT_PAGE = 2
24+
SECTION_ODD_PAGE = 5
25+
TEXT_WRAPPING = 11
26+
27+
WD_BREAK = WD_BREAK_TYPE

docx/oxml/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class ValidationError(Exception):
4545
register_custom_element_class('w:tc', CT_Tc)
4646
register_custom_element_class('w:tr', CT_Row)
4747

48-
from docx.oxml.text import CT_P, CT_PPr, CT_R, CT_Text
48+
from docx.oxml.text import CT_Br, CT_P, CT_PPr, CT_R, CT_Text
49+
register_custom_element_class('w:br', CT_Br)
4950
register_custom_element_class('w:p', CT_P)
5051
register_custom_element_class('w:pPr', CT_PPr)
5152
register_custom_element_class('w:pStyle', CT_String)

docx/oxml/text.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,34 @@
1010
)
1111

1212

13+
class CT_Br(OxmlBaseElement):
14+
"""
15+
``<w:br>`` element, indicating a line, page, or column break in a run.
16+
"""
17+
@classmethod
18+
def new(cls):
19+
"""
20+
Return a new ``<w:br>`` element.
21+
"""
22+
return OxmlElement('w:br')
23+
24+
@property
25+
def clear(self):
26+
self.get(qn('w:clear'))
27+
28+
@clear.setter
29+
def clear(self, clear_str):
30+
self.set(qn('w:clear'), clear_str)
31+
32+
@property
33+
def type(self):
34+
self.get(qn('w:type'))
35+
36+
@type.setter
37+
def type(self, type_str):
38+
self.set(qn('w:type'), type_str)
39+
40+
1341
class CT_P(OxmlBaseElement):
1442
"""
1543
``<w:p>`` element, containing the properties and text for a paragraph.
@@ -155,6 +183,14 @@ class CT_R(OxmlBaseElement):
155183
"""
156184
``<w:r>`` element, containing the properties and text for a run.
157185
"""
186+
def add_br(self):
187+
"""
188+
Return a newly appended CT_Br (<w:br>) child element.
189+
"""
190+
br = CT_Br.new()
191+
self.append(br)
192+
return br
193+
158194
def add_drawing(self, inline_or_anchor):
159195
"""
160196
Return a newly appended ``CT_Drawing`` (``<w:drawing>``) child
@@ -192,10 +228,11 @@ class CT_Text(OxmlBaseElement):
192228
"""
193229
``<w:t>`` element, containing a sequence of characters within a run.
194230
"""
195-
@staticmethod
196-
def new(text):
231+
@classmethod
232+
def new(cls, text):
197233
"""
198234
Return a new ``<w:t>`` element.
199235
"""
200-
xml = '<w:t %s>%s</w:t>' % (nsdecls('w'), text)
201-
return oxml_fromstring(xml)
236+
t = OxmlElement('w:t')
237+
t.text = text
238+
return t

docx/text.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
Text-related proxy types for python-docx, such as Paragraph and Run.
55
"""
66

7+
from __future__ import absolute_import, print_function, unicode_literals
8+
9+
from docx.enum.text import WD_BREAK
10+
711

812
class Paragraph(object):
913
"""
@@ -49,6 +53,24 @@ def __init__(self, r_elm):
4953
super(Run, self).__init__()
5054
self._r = r_elm
5155

56+
def add_break(self, break_type=WD_BREAK.LINE):
57+
"""
58+
Add a break element of *break_type* to this run.
59+
"""
60+
type_, clear = {
61+
WD_BREAK.LINE: (None, None),
62+
WD_BREAK.PAGE: ('page', None),
63+
WD_BREAK.COLUMN: ('column', None),
64+
WD_BREAK.LINE_CLEAR_LEFT: ('textWrapping', 'left'),
65+
WD_BREAK.LINE_CLEAR_RIGHT: ('textWrapping', 'right'),
66+
WD_BREAK.LINE_CLEAR_ALL: ('textWrapping', 'all'),
67+
}[break_type]
68+
br = self._r.add_br()
69+
if type_ is not None:
70+
br.type = type_
71+
if clear is not None:
72+
br.clear = clear
73+
5274
def add_text(self, text):
5375
"""
5476
Add a text element to this run.

features/steps/text.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from behave import given, then, when
1010

1111
from docx import Document
12+
from docx.enum.text import WD_BREAK
13+
from docx.oxml.shared import qn
1214

1315

1416
# given ===================================================
@@ -21,22 +23,30 @@ def given_a_run(context):
2123

2224
# when ====================================================
2325

26+
@when('I add a column break')
27+
def when_add_column_break(context):
28+
run = context.run
29+
run.add_break(WD_BREAK.COLUMN)
30+
31+
2432
@when('I add a line break')
2533
def when_add_line_break(context):
2634
run = context.run
2735
run.add_break()
2836

2937

38+
@when('I add a page break')
39+
def when_add_page_break(context):
40+
run = context.run
41+
run.add_break(WD_BREAK.PAGE)
42+
43+
3044
# then =====================================================
3145

32-
@then('the last item in the run is a break')
33-
def then_last_item_in_run_is_a_break(context):
34-
run = context.run
35-
context.last_child = run._r[-1]
36-
expected_tag = (
37-
'{http://schemas.openxmlformats.org/wordprocessingml/2006/main}br'
38-
)
39-
assert context.last_child.tag == expected_tag
46+
@then('it is a column break')
47+
def then_type_is_column_break(context):
48+
attrib = context.last_child.attrib
49+
assert attrib == {qn('w:type'): 'column'}
4050

4151

4252
@then('it is a line break')
@@ -48,10 +58,14 @@ def then_type_is_line_break(context):
4858
@then('it is a page break')
4959
def then_type_is_page_break(context):
5060
attrib = context.last_child.attrib
51-
assert attrib == {'type': 'page'}
61+
assert attrib == {qn('w:type'): 'page'}
5262

5363

54-
@then('it is a column break')
55-
def then_type_is_column_break(context):
56-
attrib = context.last_child.attrib
57-
assert attrib == {'type': 'column'}
64+
@then('the last item in the run is a break')
65+
def then_last_item_in_run_is_a_break(context):
66+
run = context.run
67+
context.last_child = run._r[-1]
68+
expected_tag = (
69+
'{http://schemas.openxmlformats.org/wordprocessingml/2006/main}br'
70+
)
71+
assert context.last_child.tag == expected_tag

features/txt-add-break.feature

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,18 @@ Feature: Add a line, page, or column break
33
As an python-docx developer
44
I need the ability to add a line, page, or column break
55

6-
@wip
76
Scenario: Add a line break
87
Given a run
98
When I add a line break
109
Then the last item in the run is a break
1110
And it is a line break
1211

13-
@wip
1412
Scenario: Add a page break
1513
Given a run
1614
When I add a page break
1715
Then the last item in the run is a break
1816
And it is a page break
1917

20-
@wip
2118
Scenario: Add a column break
2219
Given a run
2320
When I add a column break

tests/oxml/unitdata/text.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
from .shared import CT_StringBuilder
99

1010

11+
class CT_BrBuilder(BaseBuilder):
12+
__tag__ = 'w:br'
13+
__nspfxs__ = ('w',)
14+
__attrs__ = ('w:type', 'w:clear')
15+
16+
1117
class CT_PBuilder(BaseBuilder):
1218
__tag__ = 'w:p'
1319
__nspfxs__ = ('w',)
@@ -38,6 +44,10 @@ class CT_TextBuilder(BaseBuilder):
3844
__attrs__ = ()
3945

4046

47+
def a_br():
48+
return CT_BrBuilder()
49+
50+
4151
def a_p():
4252
return CT_PBuilder()
4353

tests/test_package.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ def it_knows_the_next_available_image_partname(
4646
self, next_partname_fixture):
4747
image_parts, ext, expected_partname = next_partname_fixture
4848
assert image_parts._next_image_partname(ext) == expected_partname
49-
print('\n%s' % image_parts._next_image_partname(ext))
50-
print(expected_partname)
5149

5250
def it_can_really_add_a_new_image_part(
5351
self, really_add_image_part_fixture):

tests/test_text.py

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
Test suite for the docx.text module
55
"""
66

7+
from __future__ import absolute_import, print_function, unicode_literals
8+
9+
from docx.enum.text import WD_BREAK
710
from docx.oxml.text import CT_P
811
from docx.text import Paragraph, Run
912

1013
import pytest
1114

1215
from mock import call, create_autospec, Mock
1316

17+
from .oxml.unitdata.text import a_br, a_t, an_r
1418
from .unitutil import class_mock
1519

1620

@@ -68,27 +72,68 @@ def it_can_set_its_paragraph_style(self):
6872

6973
class DescribeRun(object):
7074

75+
def it_can_add_text(self, add_text_fixture):
76+
run, text_str, expected_xml, Text_ = add_text_fixture
77+
_text = run.add_text(text_str)
78+
assert run._r.xml == expected_xml
79+
assert _text is Text_.return_value
80+
81+
def it_can_add_a_break(self, add_break_fixture):
82+
run, break_type, expected_xml = add_break_fixture
83+
run.add_break(break_type)
84+
assert run._r.xml == expected_xml
85+
86+
def it_knows_the_text_it_contains(self, text_prop_fixture):
87+
run, expected_text = text_prop_fixture
88+
assert run.text == expected_text
89+
90+
# fixtures -------------------------------------------------------
91+
92+
@pytest.fixture(params=[
93+
'line', 'page', 'column', 'clr_lt', 'clr_rt', 'clr_all'
94+
])
95+
def add_break_fixture(self, request, run):
96+
type_, clear, break_type = {
97+
'line': (None, None, WD_BREAK.LINE),
98+
'page': ('page', None, WD_BREAK.PAGE),
99+
'column': ('column', None, WD_BREAK.COLUMN),
100+
'clr_lt': ('textWrapping', 'left', WD_BREAK.LINE_CLEAR_LEFT),
101+
'clr_rt': ('textWrapping', 'right', WD_BREAK.LINE_CLEAR_RIGHT),
102+
'clr_all': ('textWrapping', 'all', WD_BREAK.LINE_CLEAR_ALL),
103+
}[request.param]
104+
# expected_xml -----------------
105+
br_bldr = a_br()
106+
if type_ is not None:
107+
br_bldr.with_type(type_)
108+
if clear is not None:
109+
br_bldr.with_clear(clear)
110+
expected_xml = an_r().with_nsdecls().with_child(br_bldr).xml()
111+
return run, break_type, expected_xml
112+
113+
@pytest.fixture
114+
def add_text_fixture(self, run, Text_):
115+
text_str = 'foobar'
116+
expected_xml = (
117+
an_r().with_nsdecls().with_child(
118+
a_t().with_text(text_str))
119+
).xml()
120+
return run, text_str, expected_xml, Text_
121+
122+
@pytest.fixture
123+
def run(self):
124+
r = an_r().with_nsdecls().element
125+
return Run(r)
126+
71127
@pytest.fixture
72128
def Text_(self, request):
73129
return class_mock(request, 'docx.text.Text')
74130

75-
def it_can_add_text_to_itself(self, Text_):
76-
# mockery ----------------------
77-
r_elm = Mock(name='r_elm')
78-
r_elm.add_t.return_value = t_elm = Mock(name='t_elm')
79-
text = Mock(name='text')
80-
r = Run(r_elm)
81-
# exercise ---------------------
82-
t = r.add_text(text)
83-
# verify -----------------------
84-
r_elm.add_t.assert_called_once_with(text)
85-
Text_.assert_called_once_with(t_elm)
86-
assert t is Text_.return_value
87-
88-
def it_has_a_composite_of_the_text_it_contains(self):
89-
# mockery ----------------------
90-
t1, t2 = (Mock(name='t1', text='foo'), Mock(name='t2', text='bar'))
91-
r_elm = Mock(name='r_elm', t_lst=[t1, t2])
92-
r = Run(r_elm)
93-
# verify -----------------------
94-
assert r.text == 'foobar'
131+
@pytest.fixture
132+
def text_prop_fixture(self, Text_):
133+
r = (
134+
an_r().with_nsdecls().with_child(
135+
a_t().with_text('foo')).with_child(
136+
a_t().with_text('bar'))
137+
).element
138+
run = Run(r)
139+
return run, 'foobar'

0 commit comments

Comments
 (0)
X Tutup