Skip to content

Commit 7beb124

Browse files
author
Steve Canny
committed
run: add Run.text getter
Upgraded to handle more of the run content elements.
1 parent e3c2e2b commit 7beb124

File tree

4 files changed

+81
-28
lines changed

4 files changed

+81
-28
lines changed

docx/oxml/text.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ class CT_R(BaseOxmlElement):
125125
rPr = ZeroOrOne('w:rPr')
126126
t = ZeroOrMore('w:t')
127127
br = ZeroOrMore('w:br')
128+
cr = ZeroOrMore('w:cr')
128129
tab = ZeroOrMore('w:tab')
129130
drawing = ZeroOrMore('w:drawing')
130131

@@ -178,6 +179,23 @@ def style(self, style):
178179
rPr = self.get_or_add_rPr()
179180
rPr.style = style
180181

182+
@property
183+
def text(self):
184+
"""
185+
A string representing the textual content of this run, with content
186+
child elements like ``<w:tab/>`` translated to their Python
187+
equivalent.
188+
"""
189+
text = ''
190+
for child in self:
191+
if child.tag == qn('w:t'):
192+
text += child.text
193+
elif child.tag == qn('w:tab'):
194+
text += '\t'
195+
elif child.tag in (qn('w:br'), qn('w:cr')):
196+
text += '\n'
197+
return text
198+
181199
@property
182200
def underline(self):
183201
"""

docx/text.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,10 @@ def add_tab(self):
160160

161161
def add_text(self, text):
162162
"""
163-
Add a text element to this run.
163+
Returns a newly appended |Text| object (corresponding to a new
164+
``<w:t>`` child element) to the run, containing *text*. Compare with
165+
the possibly more friendly approach of assigning text to the
166+
:attr:`Run.text` property.
164167
"""
165168
t = self._r.add_t(text)
166169
return Text(t)
@@ -351,13 +354,22 @@ def style(self, char_style):
351354
@property
352355
def text(self):
353356
"""
354-
A string formed by concatenating all the <w:t> elements present in
355-
this run.
357+
String formed by concatenating the text equivalent of each run
358+
content child element into a Python string. Each ``<w:t>`` element
359+
adds the text characters it contains. A ``<w:tab/>`` element adds
360+
a ``\\t`` character. A ``<w:cr/>`` or ``<w:br>`` element each add
361+
a ``\\n`` character. Note that a ``<w:br>`` element can indicate
362+
a page break or column break as well as a line break. All ``<w:br>``
363+
elements translate to a single ``\\n`` character regardless of their
364+
type. All other content child elements, such as ``<w:drawing>``, are
365+
ignored.
366+
367+
Assigning text to this property has the reverse effect, translating
368+
each ``\\t`` character to a ``<w:tab/>`` element and each ``\\n`` or
369+
``\\r`` character to a ``<w:cr/>`` element. Any existing run content
370+
is replaced. Run formatting is preserved.
356371
"""
357-
text = ''
358-
for t in self._r.t_lst:
359-
text += t.text
360-
return text
372+
return self._r.text
361373

362374
@property
363375
def underline(self):
@@ -369,8 +381,8 @@ def underline(self):
369381
property removes any directly-applied underline value. A value of
370382
|False| indicates a directly-applied setting of no underline,
371383
overriding any inherited value. A value of |True| indicates single
372-
underline. The values from ``WD_UNDERLINE`` are used to specify other
373-
outline styles such as double, wavy, and dotted.
384+
underline. The values from :ref:`WdUnderline` are used to specify
385+
other outline styles such as double, wavy, and dotted.
374386
"""
375387
return self._r.underline
376388

tests/oxml/unitdata/text.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ def a_caps():
8787
return CT_OnOffBuilder('w:caps')
8888

8989

90+
def a_cr():
91+
return CT_EmptyBuilder('w:cr')
92+
93+
9094
def a_cs():
9195
return CT_OnOffBuilder('w:cs')
9296

tests/test_text.py

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from .oxml.parts.unitdata.document import a_body
2020
from .oxml.unitdata.text import (
21-
a_b, a_bCs, a_br, a_caps, a_cs, a_dstrike, a_p, a_pPr, a_pStyle,
21+
a_b, a_bCs, a_br, a_caps, a_cr, a_cs, a_dstrike, a_p, a_pPr, a_pStyle,
2222
a_shadow, a_smallCaps, a_snapToGrid, a_specVanish, a_strike, a_t, a_tab,
2323
a_u, a_vanish, a_webHidden, an_emboss, an_i, an_iCs, an_imprint,
2424
an_oMath, a_noProof, an_outline, an_r, an_rPr, an_rStyle, an_rtl
@@ -212,17 +212,17 @@ def it_can_add_a_tab(self, add_tab_fixture):
212212
run.add_tab()
213213
assert run._r.xml == expected_xml
214214

215-
def it_knows_the_text_it_contains(self, text_prop_fixture):
216-
run, expected_text = text_prop_fixture
217-
assert run.text == expected_text
218-
219215
def it_can_remove_its_content_while_preserving_formatting(
220216
self, clear_fixture):
221217
run, expected_xml = clear_fixture
222218
_run = run.clear()
223219
assert run._r.xml == expected_xml
224220
assert _run is run
225221

222+
def it_knows_the_text_it_contains(self, text_get_fixture):
223+
run, expected_text = text_get_fixture
224+
assert run.text == expected_text
225+
226226
# fixtures -------------------------------------------------------
227227

228228
@pytest.fixture(params=[
@@ -421,15 +421,17 @@ def style_set_fixture(self, request):
421421
expected_xml = self.r_bldr_with_style(after_style).xml()
422422
return run, after_style, expected_xml
423423

424-
@pytest.fixture
425-
def text_prop_fixture(self, Text_):
426-
r = (
427-
an_r().with_nsdecls().with_child(
428-
a_t().with_text('foo')).with_child(
429-
a_t().with_text('bar'))
430-
).element
431-
run = Run(r)
432-
return run, 'foobar'
424+
@pytest.fixture(params=[
425+
(('',), ''),
426+
(('xfoobar',), 'foobar'),
427+
(('bpage', 'xabc', 'xdef', 't'), '\nabcdef\t'),
428+
(('xabc', 't', 'xdef', 'n'), 'abc\tdef\n'),
429+
])
430+
def text_get_fixture(self, request):
431+
content_children, expected_text = request.param
432+
r_bldr = self.r_content_bldr(content_children)
433+
run = Run(r_bldr.element)
434+
return run, expected_text
433435

434436
@pytest.fixture(params=[
435437
(None, None),
@@ -471,11 +473,6 @@ def underline_set_fixture(self, request):
471473

472474
# fixture components ---------------------------------------------
473475

474-
@pytest.fixture
475-
def run(self):
476-
r = an_r().with_nsdecls().element
477-
return Run(r)
478-
479476
def r_bldr_with_style(self, style):
480477
rPr_bldr = an_rPr()
481478
if style is not None:
@@ -490,6 +487,28 @@ def r_bldr_with_underline(self, underline_type):
490487
r_bldr = an_r().with_nsdecls().with_child(rPr_bldr)
491488
return r_bldr
492489

490+
def r_content_bldr(self, elm_codes):
491+
"""
492+
Return a ``<w:r>`` builder having child elements indicated by
493+
*elm_codes*.
494+
"""
495+
r_bldr = an_r().with_nsdecls()
496+
for e in elm_codes:
497+
if e.startswith('x'):
498+
r_bldr.with_child(a_t().with_text(e[1:]))
499+
elif e == 't':
500+
r_bldr.with_child(a_tab())
501+
elif e.startswith('b'):
502+
r_bldr.with_child(a_br().with_type(e[1:]))
503+
elif e == 'n':
504+
r_bldr.with_child(a_cr())
505+
return r_bldr
506+
507+
@pytest.fixture
508+
def run(self):
509+
r = an_r().with_nsdecls().element
510+
return Run(r)
511+
493512
@pytest.fixture
494513
def Text_(self, request):
495514
return class_mock(request, 'docx.text.Text')

0 commit comments

Comments
 (0)