Skip to content

Commit b08f691

Browse files
author
Steve Canny
committed
text: add Run.bold getter
Along the way: * extract oxml exceptions to docx.oxml.exceptions to avoid import loop
1 parent 8f6ba19 commit b08f691

File tree

10 files changed

+111
-14
lines changed

10 files changed

+111
-14
lines changed

docs/dev/analysis/features/run-properties.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ below when it writes the file.::
8787
<xsd:complexType name="CT_R"> <!-- denormalized -->
8888
<xsd:sequence>
8989
<xsd:element name="rPr" type="CT_RPr" minOccurs="0"/>
90-
<xsd:group ref="EG_RunInnerContent" minOccurs="0" maxOccurs="unbounded"/>
90+
<xsd:group ref="EG_RunInnerContent" minOccurs="0" maxOccurs="unbounded"/>
9191
</xsd:sequence>
9292
<xsd:attribute name="rsidRPr" type="ST_LongHexNumber"/>
9393
<xsd:attribute name="rsidDel" type="ST_LongHexNumber"/>

docx/oxml/__init__.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,11 @@
88
from docx.oxml.shared import register_custom_element_class
99

1010

11-
class ValidationError(Exception):
12-
"""
13-
Raised when invalid XML is encountered, such as on attempt to access a
14-
missing required child element
15-
"""
16-
17-
1811
# ===========================================================================
1912
# custom element class mappings
2013
# ===========================================================================
2114

22-
from docx.oxml.shared import CT_String
15+
from docx.oxml.shared import CT_OnOff, CT_String
2316

2417
from docx.oxml.shape import (
2518
CT_Blip, CT_BlipFillProperties, CT_GraphicalObject,
@@ -45,10 +38,12 @@ class ValidationError(Exception):
4538
register_custom_element_class('w:tc', CT_Tc)
4639
register_custom_element_class('w:tr', CT_Row)
4740

48-
from docx.oxml.text import CT_Br, CT_P, CT_PPr, CT_R, CT_Text
41+
from docx.oxml.text import CT_Br, CT_P, CT_PPr, CT_R, CT_RPr, CT_Text
42+
register_custom_element_class('w:b', CT_OnOff)
4943
register_custom_element_class('w:br', CT_Br)
5044
register_custom_element_class('w:p', CT_P)
5145
register_custom_element_class('w:pPr', CT_PPr)
5246
register_custom_element_class('w:pStyle', CT_String)
5347
register_custom_element_class('w:r', CT_R)
48+
register_custom_element_class('w:rPr', CT_RPr)
5449
register_custom_element_class('w:t', CT_Text)

docx/oxml/exceptions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Exceptions for oxml sub-package
5+
"""
6+
7+
8+
class ValidationError(Exception):
9+
"""
10+
Raised when invalid XML is encountered, such as on attempt to access a
11+
missing required child element
12+
"""

docx/oxml/shared.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from lxml import etree
88

9+
from .exceptions import ValidationError
10+
911

1012
nsmap = {
1113
'a': ('http://schemas.openxmlformats.org/drawingml/2006/main'),
@@ -168,6 +170,23 @@ def xml(self):
168170
return serialize_for_reading(self)
169171

170172

173+
class CT_OnOff(OxmlBaseElement):
174+
"""
175+
Used for ``<w:b>``, ``<w:i>`` elements and others, containing a bool-ish
176+
string in its ``val`` attribute, xsd:boolean plus 'on' and 'off'.
177+
"""
178+
@property
179+
def val(self):
180+
val = self.get(qn('w:val'))
181+
if val is None:
182+
return True
183+
elif val in ('0', 'false', 'off'):
184+
return False
185+
elif val in ('1', 'true', 'on'):
186+
return True
187+
raise ValidationError("expected xsd:boolean, got '%s'" % val)
188+
189+
171190
class CT_String(OxmlBaseElement):
172191
"""
173192
Used for ``<w:pStyle>`` and ``<w:tblStyle>`` elements and others,

docx/oxml/table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from docx.oxml.shared import OxmlBaseElement, OxmlElement, qn
1010

11-
from . import ValidationError
11+
from .exceptions import ValidationError
1212
from .shared import CT_String
1313
from .text import CT_P
1414

docx/oxml/text.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,13 @@ def new(cls):
216216
"""
217217
return OxmlElement('w:r')
218218

219+
@property
220+
def rPr(self):
221+
"""
222+
``<w:rPr>`` child element or None if not present.
223+
"""
224+
return self.find(qn('w:rPr'))
225+
219226
@property
220227
def t_lst(self):
221228
"""
@@ -224,6 +231,18 @@ def t_lst(self):
224231
return self.findall(qn('w:t'))
225232

226233

234+
class CT_RPr(OxmlBaseElement):
235+
"""
236+
``<w:rPr>`` element, containing the properties for a run.
237+
"""
238+
@property
239+
def b(self):
240+
"""
241+
First ``<w:b>`` child element or None if none are present.
242+
"""
243+
return self.find(qn('w:b'))
244+
245+
227246
class CT_Text(OxmlBaseElement):
228247
"""
229248
``<w:t>`` element, containing a sequence of characters within a run.

docx/text.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,13 @@ def bold(self):
9898
|None|, the run will inherit its bold setting from its style
9999
hierarchy.
100100
"""
101-
raise NotImplementedError
101+
rPr = self._r.rPr
102+
if rPr is None:
103+
return None
104+
b = rPr.b
105+
if b is None:
106+
return None
107+
return b.val
102108

103109
@property
104110
def text(self):

tests/oxml/unitdata/shared.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77
from ...unitdata import BaseBuilder
88

99

10+
class CT_OnOffBuilder(BaseBuilder):
11+
__nspfxs__ = ('w',)
12+
__attrs__ = ('w:val')
13+
14+
def __init__(self, tag):
15+
self.__tag__ = tag
16+
super(CT_OnOffBuilder, self).__init__()
17+
18+
def with_val(self, value):
19+
self._set_xmlattr('w:val', str(value))
20+
return self
21+
22+
1023
class CT_StringBuilder(BaseBuilder):
1124
__nspfxs__ = ('w',)
1225
__attrs__ = ()

tests/oxml/unitdata/text.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
from ...unitdata import BaseBuilder
8-
from .shared import CT_StringBuilder
8+
from .shared import CT_OnOffBuilder, CT_StringBuilder
99

1010

1111
class CT_BrBuilder(BaseBuilder):
@@ -32,6 +32,12 @@ class CT_RBuilder(BaseBuilder):
3232
__attrs__ = ()
3333

3434

35+
class CT_RPrBuilder(BaseBuilder):
36+
__tag__ = 'w:rPr'
37+
__nspfxs__ = ('w',)
38+
__attrs__ = ()
39+
40+
3541
class CT_SectPrBuilder(BaseBuilder):
3642
__tag__ = 'w:sectPr'
3743
__nspfxs__ = ('w',)
@@ -44,6 +50,10 @@ class CT_TextBuilder(BaseBuilder):
4450
__attrs__ = ()
4551

4652

53+
def a_b():
54+
return CT_OnOffBuilder('w:b')
55+
56+
4757
def a_br():
4858
return CT_BrBuilder()
4959

@@ -70,3 +80,7 @@ def a_t():
7080

7181
def an_r():
7282
return CT_RBuilder()
83+
84+
85+
def an_rPr():
86+
return CT_RPrBuilder()

tests/test_text.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from mock import call, create_autospec, Mock
1616

17-
from .oxml.unitdata.text import a_br, a_t, a_p, an_r
17+
from .oxml.unitdata.text import a_b, a_br, a_t, a_p, an_r, an_rPr
1818
from .unitutil import class_mock
1919

2020

@@ -90,6 +90,11 @@ def text_prop_fixture(self):
9090

9191
class DescribeRun(object):
9292

93+
def it_knows_if_its_bold(self, bold_fixture):
94+
run, is_bold = bold_fixture
95+
print('\n%s' % run._r.xml)
96+
assert run.bold == is_bold
97+
9398
def it_can_add_text(self, add_text_fixture):
9499
run, text_str, expected_xml, Text_ = add_text_fixture
95100
_text = run.add_text(text_str)
@@ -137,6 +142,20 @@ def add_text_fixture(self, run, Text_):
137142
).xml()
138143
return run, text_str, expected_xml, Text_
139144

145+
@pytest.fixture(params=[True, False, None])
146+
def bold_fixture(self, request):
147+
is_bold = request.param
148+
r_bldr = an_r().with_nsdecls()
149+
if is_bold is not None:
150+
b_bldr = a_b()
151+
if is_bold is False:
152+
b_bldr.with_val('off')
153+
rPr_bldr = an_rPr().with_child(b_bldr)
154+
r_bldr.with_child(rPr_bldr)
155+
r = r_bldr.element
156+
run = Run(r)
157+
return run, is_bold
158+
140159
@pytest.fixture
141160
def run(self):
142161
r = an_r().with_nsdecls().element

0 commit comments

Comments
 (0)