Skip to content

Commit 29a2454

Browse files
onlyjusSteve Canny
authored andcommitted
run: add Run.underline getter
1 parent 7b40ab3 commit 29a2454

6 files changed

Lines changed: 120 additions & 4 deletions

File tree

docx/oxml/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
register_custom_element_class('w:tc', CT_Tc)
5454
register_custom_element_class('w:tr', CT_Row)
5555

56-
from docx.oxml.text import CT_Br, CT_P, CT_PPr, CT_R, CT_RPr, CT_Text
56+
from docx.oxml.text import (
57+
CT_Br, CT_P, CT_PPr, CT_R, CT_RPr, CT_Text, CT_Underline
58+
)
5759
register_custom_element_class('w:b', CT_OnOff)
5860
register_custom_element_class('w:bCs', CT_OnOff)
5961
register_custom_element_class('w:br', CT_Br)
@@ -80,5 +82,6 @@
8082
register_custom_element_class('w:specVanish', CT_OnOff)
8183
register_custom_element_class('w:strike', CT_OnOff)
8284
register_custom_element_class('w:t', CT_Text)
85+
register_custom_element_class('w:u', CT_Underline)
8386
register_custom_element_class('w:vanish', CT_OnOff)
8487
register_custom_element_class('w:webHidden', CT_OnOff)

docx/oxml/text.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
(CT_R).
66
"""
77

8+
from docx.enum.text import WD_UNDERLINE
89
from docx.oxml.parts.numbering import CT_NumPr
910
from docx.oxml.shared import (
1011
CT_String, nsdecls, OxmlBaseElement, OxmlElement, oxml_fromstring, qn
@@ -294,6 +295,17 @@ def t_lst(self):
294295
"""
295296
return self.findall(qn('w:t'))
296297

298+
@property
299+
def underline(self):
300+
"""
301+
String contained in w:val attribute of <w:u> grandchild, or |None| if
302+
that element is not present.
303+
"""
304+
rPr = self.rPr
305+
if rPr is None:
306+
return None
307+
return rPr.underline
308+
297309
def _add_rPr(self):
298310
"""
299311
Return a newly added rPr child element. Assumes one is not present.
@@ -737,6 +749,24 @@ def style(self, style):
737749
else:
738750
self.rStyle.val = style
739751

752+
@property
753+
def u(self):
754+
"""
755+
First ``<w:u>`` child element or |None| if none are present.
756+
"""
757+
return self.find(qn('w:u'))
758+
759+
@property
760+
def underline(self):
761+
"""
762+
Underline type specified in <w:u> child, or None if that element is
763+
not present.
764+
"""
765+
u = self.u
766+
if u is None:
767+
return None
768+
return u.val
769+
740770
@property
741771
def vanish(self):
742772
"""
@@ -769,3 +799,37 @@ def new(cls, text):
769799
t = OxmlElement('w:t')
770800
t.text = text
771801
return t
802+
803+
804+
class CT_Underline(OxmlBaseElement):
805+
"""
806+
``<w:u>`` element, specifying the underlining style for a run.
807+
"""
808+
@property
809+
def val(self):
810+
"""
811+
The underline type corresponding to the ``w:val`` attribute value.
812+
"""
813+
underline_type_map = {
814+
None: None,
815+
'none': False,
816+
'single': True,
817+
'words': WD_UNDERLINE.WORDS,
818+
'double': WD_UNDERLINE.DOUBLE,
819+
'dotted': WD_UNDERLINE.DOTTED,
820+
'thick': WD_UNDERLINE.THICK,
821+
'dash': WD_UNDERLINE.DASH,
822+
'dotDash': WD_UNDERLINE.DOT_DASH,
823+
'dotDotDash': WD_UNDERLINE.DOT_DOT_DASH,
824+
'wave': WD_UNDERLINE.WAVY,
825+
'dottedHeavy': WD_UNDERLINE.DOTTED_HEAVY,
826+
'dashedHeavy': WD_UNDERLINE.DASH_HEAVY,
827+
'dashDotHeavy': WD_UNDERLINE.DOT_DASH_HEAVY,
828+
'dashDotDotHeavy': WD_UNDERLINE.DOT_DOT_DASH_HEAVY,
829+
'wavyHeavy': WD_UNDERLINE.WAVY_HEAVY,
830+
'dashLong': WD_UNDERLINE.DASH_LONG,
831+
'wavyDouble': WD_UNDERLINE.WAVY_DOUBLE,
832+
'dashLongHeavy': WD_UNDERLINE.DASH_LONG_HEAVY,
833+
}
834+
val = self.get(qn('w:val'))
835+
return underline_type_map[val]

docx/text.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,21 @@ def text(self):
329329
text += t.text
330330
return text
331331

332+
@property
333+
def underline(self):
334+
"""
335+
The underline style for this |Run|, one of |None|, |True|, |False|,
336+
or a value from ``pptx.enum.text.WD_UNDERLINE``. A value of |None|
337+
indicates the run has no directly-applied underline value and so will
338+
inherit the underline value of its containing paragraph. Assigning
339+
|None| to this property removes any directly-applied underline value.
340+
A value of |False| indicates a directly-applied setting of no
341+
underline, overriding any inherited value. A value of |True|
342+
indicates single underline. The values from ``WD_UNDERLINE`` are used
343+
to specify other outline styles such as double, wavy, and dotted.
344+
"""
345+
return self._r.underline
346+
332347
@boolproperty
333348
def web_hidden(self):
334349
"""

features/run-enum-props.feature

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ Feature: Query or apply enumerated run property
44
I need a way to query and set the enumerated properties on a run
55

66

7-
@wip
87
Scenario Outline: Get underline value of a run
98
Given a run having <underline-type> underline
109
Then the run underline property value is <underline-value>

tests/oxml/unitdata/text.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ def with_space(self, value):
5454
return self
5555

5656

57+
class CT_Underline(BaseBuilder):
58+
__tag__ = 'w:u'
59+
__nspfxs__ = ('w',)
60+
__attrs__ = (
61+
'w:val', 'w:color', 'w:themeColor', 'w:themeTint', 'w:themeShade'
62+
)
63+
64+
5765
def a_b():
5866
return CT_OnOffBuilder('w:b')
5967

@@ -130,6 +138,10 @@ def a_t():
130138
return CT_TextBuilder()
131139

132140

141+
def a_u():
142+
return CT_Underline()
143+
144+
133145
def an_emboss():
134146
return CT_OnOffBuilder('w:emboss')
135147

tests/test_text.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
absolute_import, division, print_function, unicode_literals
99
)
1010

11-
from docx.enum.text import WD_BREAK
11+
from docx.enum.text import WD_BREAK, WD_UNDERLINE
1212
from docx.oxml.text import CT_P, CT_R
1313
from docx.text import Paragraph, Run
1414

@@ -18,7 +18,7 @@
1818

1919
from .oxml.unitdata.text import (
2020
a_b, a_bCs, a_br, a_caps, a_cs, a_dstrike, a_p, a_shadow, a_smallCaps,
21-
a_snapToGrid, a_specVanish, a_strike, a_t, a_vanish, a_webHidden,
21+
a_snapToGrid, a_specVanish, a_strike, a_t, a_u, a_vanish, a_webHidden,
2222
an_emboss, an_i, an_iCs, an_imprint, an_oMath, a_noProof, an_outline,
2323
an_r, an_rPr, an_rStyle, an_rtl
2424
)
@@ -152,6 +152,10 @@ def it_can_change_its_character_style(self, style_set_fixture):
152152
run.style = style
153153
assert run._r.xml == expected_xml
154154

155+
def it_knows_its_underline_type(self, underline_get_fixture):
156+
run, expected_value = underline_get_fixture
157+
assert run.underline == expected_value
158+
155159
def it_can_add_text(self, add_text_fixture):
156160
run, text_str, expected_xml, Text_ = add_text_fixture
157161
_text = run.add_text(text_str)
@@ -354,6 +358,18 @@ def text_prop_fixture(self, Text_):
354358
run = Run(r)
355359
return run, 'foobar'
356360

361+
@pytest.fixture(params=[
362+
(None, None),
363+
('single', True),
364+
('none', False),
365+
('double', WD_UNDERLINE.DOUBLE),
366+
])
367+
def underline_get_fixture(self, request):
368+
underline_type, expected_prop_value = request.param
369+
r = self.r_bldr_with_underline(underline_type).element
370+
run = Run(r)
371+
return run, expected_prop_value
372+
357373
# fixture components ---------------------------------------------
358374

359375
@pytest.fixture
@@ -368,6 +384,13 @@ def r_bldr_with_style(self, style):
368384
r_bldr = an_r().with_nsdecls().with_child(rPr_bldr)
369385
return r_bldr
370386

387+
def r_bldr_with_underline(self, underline_type):
388+
rPr_bldr = an_rPr()
389+
if underline_type is not None:
390+
rPr_bldr.with_child(a_u().with_val(underline_type))
391+
r_bldr = an_r().with_nsdecls().with_child(rPr_bldr)
392+
return r_bldr
393+
371394
@pytest.fixture
372395
def Text_(self, request):
373396
return class_mock(request, 'docx.text.Text')

0 commit comments

Comments
 (0)