Skip to content

Commit 6a85cab

Browse files
author
Steve Canny
committed
run: add Run.text setter
1 parent 7beb124 commit 6a85cab

File tree

5 files changed

+85
-2
lines changed

5 files changed

+85
-2
lines changed

docx/oxml/text.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@ def text(self):
196196
text += '\n'
197197
return text
198198

199+
@text.setter
200+
def text(self, text):
201+
self.clear_content()
202+
_RunContentAppender.append_to_run_from_text(self, text)
203+
199204
@property
200205
def underline(self):
201206
"""
@@ -319,3 +324,59 @@ def val(self, value):
319324

320325
val = WD_UNDERLINE.to_xml(value)
321326
self.set(qn('w:val'), val)
327+
328+
329+
class _RunContentAppender(object):
330+
"""
331+
Service object that knows how to translate a Python string into run
332+
content elements appended to a specified ``<w:r>`` element. Contiguous
333+
sequences of regular characters are appended in a single ``<w:t>``
334+
element. Each tab character ('\t') causes a ``<w:tab/>`` element to be
335+
appended. Likewise a newline or carriage return character ('\n', '\r')
336+
causes a ``<w:cr>`` element to be appended.
337+
"""
338+
def __init__(self, r):
339+
self._r = r
340+
self._bfr = []
341+
342+
@classmethod
343+
def append_to_run_from_text(cls, r, text):
344+
"""
345+
Create a "one-shot" ``_RunContentAppender`` instance and use it to
346+
append the run content elements corresponding to *text* to the
347+
``<w:r>`` element *r*.
348+
"""
349+
appender = cls(r)
350+
appender.add_text(text)
351+
352+
def add_text(self, text):
353+
"""
354+
Append the run content elements corresponding to *text* to the
355+
``<w:r>`` element of this instance.
356+
"""
357+
for char in text:
358+
self.add_char(char)
359+
self.flush()
360+
361+
def add_char(self, char):
362+
"""
363+
Process the next character of input through the translation finite
364+
state maching (FSM). There are two possible states, buffer pending
365+
and not pending, but those are hidden behind the ``.flush()`` method
366+
which must be called at the end of text to ensure any pending
367+
``<w:t>`` element is written.
368+
"""
369+
if char == '\t':
370+
self.flush()
371+
self._r.add_tab()
372+
elif char in '\r\n':
373+
self.flush()
374+
self._r.add_cr()
375+
else:
376+
self._bfr.append(char)
377+
378+
def flush(self):
379+
text = ''.join(self._bfr)
380+
if text:
381+
self._r.add_t(text)
382+
del self._bfr[:]

docx/text.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ def text(self):
371371
"""
372372
return self._r.text
373373

374+
@text.setter
375+
def text(self, text):
376+
self._r.text = text
377+
374378
@property
375379
def underline(self):
376380
"""

features/run-access-content.feature

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ Feature: Access run content
44
I need ways to access the run content
55

66

7-
@wip
87
Scenario: Get run content as Python text
98
Given a run having mixed text content
109
Then the text of the run represents the textual run content

features/run-add-content.feature

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ Feature: Add content to a run
1010
Then the tab appears at the end of the run
1111

1212

13-
@wip
1413
Scenario: Assign mixed text to text property
1514
Given a run
1615
When I assign mixed text to the text property

tests/test_text.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ def it_knows_the_text_it_contains(self, text_get_fixture):
223223
run, expected_text = text_get_fixture
224224
assert run.text == expected_text
225225

226+
def it_can_replace_the_text_it_contains(self, text_set_fixture):
227+
run, text, expected_xml = text_set_fixture
228+
run.text = text
229+
assert run._r.xml == expected_xml
230+
226231
# fixtures -------------------------------------------------------
227232

228233
@pytest.fixture(params=[
@@ -433,6 +438,21 @@ def text_get_fixture(self, request):
433438
run = Run(r_bldr.element)
434439
return run, expected_text
435440

441+
@pytest.fixture(params=[
442+
('abc', ('xabc',)),
443+
('abc\tdef', ('xabc', 't', 'xdef')),
444+
('abc\rdef', ('xabc', 'n', 'xdef')),
445+
])
446+
def text_set_fixture(self, request):
447+
text, expected_elm_codes = request.param
448+
# starting run contains text, so can tell if it doesn't get replaced
449+
r_bldr = self.r_content_bldr(('xfoobar'))
450+
run = Run(r_bldr.element)
451+
# expected_xml -----------------
452+
r_bldr = self.r_content_bldr(expected_elm_codes)
453+
expected_xml = r_bldr.xml()
454+
return run, text, expected_xml
455+
436456
@pytest.fixture(params=[
437457
(None, None),
438458
('single', True),

0 commit comments

Comments
 (0)