1# encoding: utf-8
2
3"""
4Step implementations for text-related features
5"""
6
7from __future__ import absolute_import, print_function, unicode_literals
8
9import hashlib
10
11from behave import given, then, when
12
13from docx import Document
14from docx.enum.text import WD_BREAK, WD_UNDERLINE
15from docx.oxml import parse_xml
16from docx.oxml.ns import nsdecls, qn
17from docx.text.font import Font
18from docx.text.run import Run
19
20from helpers import test_docx, test_file, test_text
21
22
23# given ===================================================
24
25@given('a run')
26def given_a_run(context):
27    document = Document()
28    run = document.add_paragraph().add_run()
29    context.document = document
30    context.run = run
31
32
33@given('a run having {bool_prop_name} set on')
34def given_a_run_having_bool_prop_set_on(context, bool_prop_name):
35    run = Document().add_paragraph().add_run()
36    setattr(run, bool_prop_name, True)
37    context.run = run
38
39
40@given('a run having known text and formatting')
41def given_a_run_having_known_text_and_formatting(context):
42    run = Document().add_paragraph().add_run('foobar')
43    run.bold = True
44    run.italic = True
45    context.run = run
46
47
48@given('a run having mixed text content')
49def given_a_run_having_mixed_text_content(context):
50    """
51    Mixed here meaning it contains ``<w:tab/>``, ``<w:cr/>``, etc. elements.
52    """
53    r_xml = """\
54        <w:r %s>
55          <w:t>abc</w:t>
56          <w:tab/>
57          <w:t>def</w:t>
58          <w:cr/>
59          <w:t>ghi</w:t>
60          <w:drawing/>
61          <w:br/>
62          <w:t>jkl</w:t>
63        </w:r>""" % nsdecls('w')
64    r = parse_xml(r_xml)
65    context.run = Run(r, None)
66
67
68@given('a run having {underline_type} underline')
69def given_a_run_having_underline_type(context, underline_type):
70    run_idx = {
71        'inherited': 0, 'no': 1, 'single': 2, 'double': 3
72    }[underline_type]
73    document = Document(test_docx('run-enumerated-props'))
74    context.run = document.paragraphs[0].runs[run_idx]
75
76
77@given('a run having {style} style')
78def given_a_run_having_style(context, style):
79    run_idx = {
80        'no explicit': 0,
81        'Emphasis':    1,
82        'Strong':      2,
83    }[style]
84    context.document = document = Document(test_docx('run-char-style'))
85    context.run = document.paragraphs[0].runs[run_idx]
86
87
88@given('a run inside a table cell retrieved from {cell_source}')
89def given_a_run_inside_a_table_cell_from_source(context, cell_source):
90    document = Document()
91    table = document.add_table(rows=2, cols=2)
92    if cell_source == 'Table.cell':
93        cell = table.cell(0, 0)
94    elif cell_source == 'Table.row.cells':
95        cell = table.rows[0].cells[1]
96    elif cell_source == 'Table.column.cells':
97        cell = table.columns[1].cells[0]
98    run = cell.paragraphs[0].add_run()
99    context.document = document
100    context.run = run
101
102
103# when ====================================================
104
105@when('I add a column break')
106def when_add_column_break(context):
107    run = context.run
108    run.add_break(WD_BREAK.COLUMN)
109
110
111@when('I add a line break')
112def when_add_line_break(context):
113    run = context.run
114    run.add_break()
115
116
117@when('I add a page break')
118def when_add_page_break(context):
119    run = context.run
120    run.add_break(WD_BREAK.PAGE)
121
122
123@when('I add a picture to the run')
124def when_I_add_a_picture_to_the_run(context):
125    run = context.run
126    run.add_picture(test_file('monty-truth.png'))
127
128
129@when('I add a run specifying its text')
130def when_I_add_a_run_specifying_its_text(context):
131    context.run = context.paragraph.add_run(test_text)
132
133
134@when('I add a run specifying the character style Emphasis')
135def when_I_add_a_run_specifying_the_character_style_Emphasis(context):
136    context.run = context.paragraph.add_run(test_text, 'Emphasis')
137
138
139@when('I add a tab')
140def when_I_add_a_tab(context):
141    context.run.add_tab()
142
143
144@when('I add text to the run')
145def when_I_add_text_to_the_run(context):
146    context.run.add_text(test_text)
147
148
149@when('I assign mixed text to the text property')
150def when_I_assign_mixed_text_to_the_text_property(context):
151    context.run.text = 'abc\tdef\nghi\rjkl'
152
153
154@when('I assign {value_str} to its {bool_prop_name} property')
155def when_assign_true_to_bool_run_prop(context, value_str, bool_prop_name):
156    value = {'True': True, 'False': False, 'None': None}[value_str]
157    run = context.run
158    setattr(run, bool_prop_name, value)
159
160
161@when('I assign {value} to run.style')
162def when_I_assign_value_to_run_style(context, value):
163    if value == 'None':
164        new_value = None
165    elif value.startswith('styles['):
166        new_value = context.document.styles[value.split('\'')[1]]
167    else:
168        new_value = context.document.styles[value]
169
170    context.run.style = new_value
171
172
173@when('I clear the run')
174def when_I_clear_the_run(context):
175    context.run.clear()
176
177
178@when('I set the run underline to {underline_value}')
179def when_I_set_the_run_underline_to_value(context, underline_value):
180    new_value = {
181        'True': True, 'False': False, 'None': None,
182        'WD_UNDERLINE.SINGLE': WD_UNDERLINE.SINGLE,
183        'WD_UNDERLINE.DOUBLE': WD_UNDERLINE.DOUBLE,
184    }[underline_value]
185    context.run.underline = new_value
186
187
188# then =====================================================
189
190@then('it is a column break')
191def then_type_is_column_break(context):
192    attrib = context.last_child.attrib
193    assert attrib == {qn('w:type'): 'column'}
194
195
196@then('it is a line break')
197def then_type_is_line_break(context):
198    attrib = context.last_child.attrib
199    assert attrib == {}
200
201
202@then('it is a page break')
203def then_type_is_page_break(context):
204    attrib = context.last_child.attrib
205    assert attrib == {qn('w:type'): 'page'}
206
207
208@then('run.font is the Font object for the run')
209def then_run_font_is_the_Font_object_for_the_run(context):
210    run, font = context.run, context.run.font
211    assert isinstance(font, Font)
212    assert font.element is run.element
213
214
215@then('run.style is styles[\'{style_name}\']')
216def then_run_style_is_style(context, style_name):
217    expected_value = context.document.styles[style_name]
218    run = context.run
219    assert run.style == expected_value, 'got %s' % run.style
220
221
222@then('the last item in the run is a break')
223def then_last_item_in_run_is_a_break(context):
224    run = context.run
225    context.last_child = run._r[-1]
226    expected_tag = (
227        '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}br'
228    )
229    assert context.last_child.tag == expected_tag
230
231
232@then('the picture appears at the end of the run')
233def then_the_picture_appears_at_the_end_of_the_run(context):
234    run = context.run
235    r = run._r
236    blip_rId = r.xpath(
237        './w:drawing/wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/'
238        'a:blip/@r:embed'
239    )[0]
240    image_part = run.part.related_parts[blip_rId]
241    image_sha1 = hashlib.sha1(image_part.blob).hexdigest()
242    expected_sha1 = '79769f1e202add2e963158b532e36c2c0f76a70c'
243    assert image_sha1 == expected_sha1, (
244        "image SHA1 doesn't match, expected %s, got %s" %
245        (expected_sha1, image_sha1)
246    )
247
248
249@then('the run appears in {boolean_prop_name} unconditionally')
250def then_run_appears_in_boolean_prop_name(context, boolean_prop_name):
251    run = context.run
252    assert getattr(run, boolean_prop_name) is True
253
254
255@then('the run appears with its inherited {boolean_prop_name} setting')
256def then_run_inherits_bool_prop_value(context, boolean_prop_name):
257    run = context.run
258    assert getattr(run, boolean_prop_name) is None
259
260
261@then('the run appears without {boolean_prop_name} unconditionally')
262def then_run_appears_without_bool_prop(context, boolean_prop_name):
263    run = context.run
264    assert getattr(run, boolean_prop_name) is False
265
266
267@then('the run contains no text')
268def then_the_run_contains_no_text(context):
269    assert context.run.text == ''
270
271
272@then('the run contains the text I specified')
273def then_the_run_contains_the_text_I_specified(context):
274    assert context.run.text == test_text
275
276
277@then('the run formatting is preserved')
278def then_the_run_formatting_is_preserved(context):
279    assert context.run.bold is True
280    assert context.run.italic is True
281
282
283@then('the run underline property value is {underline_value}')
284def then_the_run_underline_property_value_is(context, underline_value):
285    expected_value = {
286        'None': None, 'False': False, 'True': True,
287        'WD_UNDERLINE.DOUBLE': WD_UNDERLINE.DOUBLE
288    }[underline_value]
289    assert context.run.underline == expected_value
290
291
292@then('the tab appears at the end of the run')
293def then_the_tab_appears_at_the_end_of_the_run(context):
294    r = context.run._r
295    tab = r.find(qn('w:tab'))
296    assert tab is not None
297
298
299@then('the text of the run represents the textual run content')
300def then_the_text_of_the_run_represents_the_textual_run_content(context):
301    assert context.run.text == 'abc\tdef\nghi\njkl', (
302        'got \'%s\'' % context.run.text
303    )
304