1# encoding: utf-8
2
3"""
4Test suite for docx.oxml.xmlchemy
5"""
6
7from __future__ import absolute_import, print_function, unicode_literals
8
9import pytest
10
11from docx.compat import Unicode
12from docx.oxml import parse_xml, register_element_cls
13from docx.oxml.exceptions import InvalidXmlError
14from docx.oxml.ns import qn
15from docx.oxml.simpletypes import BaseIntType
16from docx.oxml.xmlchemy import (
17    BaseOxmlElement, Choice, serialize_for_reading, OneOrMore, OneAndOnlyOne,
18    OptionalAttribute, RequiredAttribute, ZeroOrMore, ZeroOrOne,
19    ZeroOrOneChoice, XmlString
20)
21
22from ..unitdata import BaseBuilder
23from .unitdata.text import a_b, a_u, an_i, an_rPr
24
25
26class DescribeBaseOxmlElement(object):
27
28    def it_can_find_the_first_of_its_children_named_in_a_sequence(
29            self, first_fixture):
30        element, tagnames, matching_child = first_fixture
31        assert element.first_child_found_in(*tagnames) is matching_child
32
33    def it_can_insert_an_element_before_named_successors(
34            self, insert_fixture):
35        element, child, tagnames, expected_xml = insert_fixture
36        element.insert_element_before(child, *tagnames)
37        assert element.xml == expected_xml
38
39    def it_can_remove_all_children_with_name_in_sequence(
40            self, remove_fixture):
41        element, tagnames, expected_xml = remove_fixture
42        element.remove_all(*tagnames)
43        assert element.xml == expected_xml
44
45    # fixtures ---------------------------------------------
46
47    @pytest.fixture(params=[
48        ('biu', 'iu',  'i'),
49        ('bu',  'iu',  'u'),
50        ('bi',  'u',   None),
51        ('b',   'iu',  None),
52        ('iu',  'biu', 'i'),
53        ('',    'biu', None),
54    ])
55    def first_fixture(self, request):
56        present, matching, match = request.param
57        element = self.rPr_bldr(present).element
58        tagnames = self.nsptags(matching)
59        matching_child = element.find(qn('w:%s' % match)) if match else None
60        return element, tagnames, matching_child
61
62    @pytest.fixture(params=[
63        ('iu', 'b', 'iu', 'biu'),
64        ('u',  'b', 'iu', 'bu'),
65        ('',   'b', 'iu', 'b'),
66        ('bu', 'i', 'u',  'biu'),
67        ('bi', 'u', '',   'biu'),
68    ])
69    def insert_fixture(self, request):
70        present, new, successors, after = request.param
71        element = self.rPr_bldr(present).element
72        child = {
73            'b': a_b(), 'i': an_i(), 'u': a_u()
74        }[new].with_nsdecls().element
75        tagnames = [('w:%s' % char) for char in successors]
76        expected_xml = self.rPr_bldr(after).xml()
77        return element, child, tagnames, expected_xml
78
79    @pytest.fixture(params=[
80        ('biu', 'b', 'iu'), ('biu', 'bi', 'u'), ('bbiiuu',  'i',   'bbuu'),
81        ('biu', 'i', 'bu'), ('biu', 'bu', 'i'), ('bbiiuu',   '', 'bbiiuu'),
82        ('biu', 'u', 'bi'), ('biu', 'ui', 'b'), ('bbiiuu', 'bi',     'uu'),
83        ('bu',  'i', 'bu'), ('',    'ui',  ''),
84    ])
85    def remove_fixture(self, request):
86        present, remove, after = request.param
87        element = self.rPr_bldr(present).element
88        tagnames = self.nsptags(remove)
89        expected_xml = self.rPr_bldr(after).xml()
90        return element, tagnames, expected_xml
91
92    # fixture components ---------------------------------------------
93
94    def nsptags(self, letters):
95        return [('w:%s' % letter) for letter in letters]
96
97    def rPr_bldr(self, children):
98        rPr_bldr = an_rPr().with_nsdecls()
99        for char in children:
100            if char == 'b':
101                rPr_bldr.with_child(a_b())
102            elif char == 'i':
103                rPr_bldr.with_child(an_i())
104            elif char == 'u':
105                rPr_bldr.with_child(a_u())
106            else:
107                raise NotImplementedError("got '%s'" % char)
108        return rPr_bldr
109
110
111class DescribeSerializeForReading(object):
112
113    def it_pretty_prints_an_lxml_element(self, pretty_fixture):
114        element, expected_xml_text = pretty_fixture
115        xml_text = serialize_for_reading(element)
116        assert xml_text == expected_xml_text
117
118    def it_returns_unicode_text(self, type_fixture):
119        element = type_fixture
120        xml_text = serialize_for_reading(element)
121        assert isinstance(xml_text, Unicode)
122
123    # fixtures ---------------------------------------------
124
125    @pytest.fixture
126    def pretty_fixture(self, element):
127        expected_xml_text = (
128            '<foø>\n'
129            '  <bår>text</bår>\n'
130            '</foø>\n'
131        )
132        return element, expected_xml_text
133
134    @pytest.fixture
135    def type_fixture(self, element):
136        return element
137
138    # fixture components -----------------------------------
139
140    @pytest.fixture
141    def element(self):
142        return parse_xml('<foø><bår>text</bår></foø>')
143
144
145class DescribeXmlString(object):
146
147    def it_parses_a_line_to_help_compare(self, parse_fixture):
148        """
149        This internal function is important to test separately because if it
150        doesn't parse a line properly, false equality can result.
151        """
152        line, expected_front, expected_attrs = parse_fixture[:3]
153        expected_close, expected_text = parse_fixture[3:]
154        front, attrs, close, text = XmlString._parse_line(line)
155        # print("'%s' '%s' '%s' %s" % (
156        #     front, attrs, close, ('%s' % text) if text else text))
157        assert front == expected_front
158        assert attrs == expected_attrs
159        assert close == expected_close
160        assert text == expected_text
161
162    def it_knows_if_two_xml_lines_are_equivalent(self, xml_line_case):
163        line, other, differs = xml_line_case
164        xml = XmlString(line)
165        assert xml == other
166        assert xml != differs
167
168    # fixtures ---------------------------------------------
169
170    @pytest.fixture(params=[
171        ('<a>text</a>',  '<a',   '',       '>',  'text</a>'),
172        ('<a:f/>',       '<a:f', '',       '/>', None),
173        ('<a:f b="c"/>', '<a:f', ' b="c"', '/>', None),
174        ('<a:f>t</a:f>', '<a:f', '',       '>',  't</a:f>'),
175        ('<dcterms:created xsi:type="dcterms:W3CDTF">2013-12-23T23:15:00Z</d'
176         'cterms:created>', '<dcterms:created', ' xsi:type="dcterms:W3CDTF"',
177         '>', '2013-12-23T23:15:00Z</dcterms:created>'),
178    ])
179    def parse_fixture(self, request):
180        line, front, attrs, close, text = request.param
181        return line, front, attrs, close, text
182
183    @pytest.fixture(params=[
184        'simple_elm', 'nsp_tagname', 'indent', 'attrs', 'nsdecl_order',
185        'closing_elm',
186    ])
187    def xml_line_case(self, request):
188        cases = {
189            'simple_elm': (
190                '<name/>',
191                '<name/>',
192                '<name>',
193            ),
194            'nsp_tagname': (
195                '<xyz:name/>',
196                '<xyz:name/>',
197                '<abc:name/>',
198            ),
199            'indent': (
200                '  <xyz:name/>',
201                '  <xyz:name/>',
202                '<xyz:name/>',
203            ),
204            'attrs': (
205                '  <abc:Name foo="bar" bar="foo">',
206                '  <abc:Name bar="foo" foo="bar">',
207                '  <abc:Name far="boo" foo="bar">',
208            ),
209            'nsdecl_order': (
210                '    <name xmlns:a="http://ns/1" xmlns:b="http://ns/2"/>',
211                '    <name xmlns:b="http://ns/2" xmlns:a="http://ns/1"/>',
212                '    <name xmlns:b="http://ns/2" xmlns:a="http://ns/1">',
213            ),
214            'closing_elm': (
215                '</xyz:name>',
216                '</xyz:name>',
217                '<xyz:name>',
218            ),
219        }
220        line, other, differs = cases[request.param]
221        return line, other, differs
222
223
224class DescribeChoice(object):
225
226    def it_adds_a_getter_property_for_the_choice_element(
227            self, getter_fixture):
228        parent, expected_choice = getter_fixture
229        assert parent.choice is expected_choice
230
231    def it_adds_a_creator_method_for_the_child_element(self, new_fixture):
232        parent, expected_xml = new_fixture
233        choice = parent._new_choice()
234        assert choice.xml == expected_xml
235
236    def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
237        parent, choice, expected_xml = insert_fixture
238        parent._insert_choice(choice)
239        assert parent.xml == expected_xml
240        assert parent._insert_choice.__doc__.startswith(
241            'Return the passed ``<w:choice>`` '
242        )
243
244    def it_adds_an_add_method_for_the_child_element(self, add_fixture):
245        parent, expected_xml = add_fixture
246        choice = parent._add_choice()
247        assert parent.xml == expected_xml
248        assert isinstance(choice, CT_Choice)
249        assert parent._add_choice.__doc__.startswith(
250            'Add a new ``<w:choice>`` child element '
251        )
252
253    def it_adds_a_get_or_change_to_method_for_the_child_element(
254            self, get_or_change_to_fixture):
255        parent, expected_xml = get_or_change_to_fixture
256        choice = parent.get_or_change_to_choice()
257        assert isinstance(choice, CT_Choice)
258        assert parent.xml == expected_xml
259
260    # fixtures -------------------------------------------------------
261
262    @pytest.fixture
263    def add_fixture(self):
264        parent = self.parent_bldr().element
265        expected_xml = self.parent_bldr('choice').xml()
266        return parent, expected_xml
267
268    @pytest.fixture(params=[
269        ('choice2', 'choice'),
270        (None,      'choice'),
271        ('choice',  'choice'),
272    ])
273    def get_or_change_to_fixture(self, request):
274        before_member_tag, after_member_tag = request.param
275        parent = self.parent_bldr(before_member_tag).element
276        expected_xml = self.parent_bldr(after_member_tag).xml()
277        return parent, expected_xml
278
279    @pytest.fixture(params=['choice', None])
280    def getter_fixture(self, request):
281        choice_tag = request.param
282        parent = self.parent_bldr(choice_tag).element
283        expected_choice = parent.find(qn('w:choice'))  # None if not found
284        return parent, expected_choice
285
286    @pytest.fixture
287    def insert_fixture(self):
288        parent = (
289            a_parent().with_nsdecls().with_child(
290                an_oomChild()).with_child(
291                an_oooChild())
292        ).element
293        choice = a_choice().with_nsdecls().element
294        expected_xml = (
295            a_parent().with_nsdecls().with_child(
296                a_choice()).with_child(
297                an_oomChild()).with_child(
298                an_oooChild())
299        ).xml()
300        return parent, choice, expected_xml
301
302    @pytest.fixture
303    def new_fixture(self):
304        parent = self.parent_bldr().element
305        expected_xml = a_choice().with_nsdecls().xml()
306        return parent, expected_xml
307
308    # fixture components ---------------------------------------------
309
310    def parent_bldr(self, choice_tag=None):
311        parent_bldr = a_parent().with_nsdecls()
312        if choice_tag == 'choice':
313            parent_bldr.with_child(a_choice())
314        if choice_tag == 'choice2':
315            parent_bldr.with_child(a_choice2())
316        return parent_bldr
317
318
319class DescribeOneAndOnlyOne(object):
320
321    def it_adds_a_getter_property_for_the_child_element(self, getter_fixture):
322        parent, oooChild = getter_fixture
323        assert parent.oooChild is oooChild
324
325    # fixtures -------------------------------------------------------
326
327    @pytest.fixture
328    def getter_fixture(self):
329        parent = a_parent().with_nsdecls().with_child(an_oooChild()).element
330        oooChild = parent.find(qn('w:oooChild'))
331        return parent, oooChild
332
333
334class DescribeOneOrMore(object):
335
336    def it_adds_a_getter_property_for_the_child_element_list(
337            self, getter_fixture):
338        parent, oomChild = getter_fixture
339        assert parent.oomChild_lst[0] is oomChild
340
341    def it_adds_a_creator_method_for_the_child_element(self, new_fixture):
342        parent, expected_xml = new_fixture
343        oomChild = parent._new_oomChild()
344        assert oomChild.xml == expected_xml
345
346    def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
347        parent, oomChild, expected_xml = insert_fixture
348        parent._insert_oomChild(oomChild)
349        assert parent.xml == expected_xml
350        assert parent._insert_oomChild.__doc__.startswith(
351            'Return the passed ``<w:oomChild>`` '
352        )
353
354    def it_adds_a_private_add_method_for_the_child_element(self, add_fixture):
355        parent, expected_xml = add_fixture
356        oomChild = parent._add_oomChild()
357        assert parent.xml == expected_xml
358        assert isinstance(oomChild, CT_OomChild)
359        assert parent._add_oomChild.__doc__.startswith(
360            'Add a new ``<w:oomChild>`` child element '
361        )
362
363    def it_adds_a_public_add_method_for_the_child_element(self, add_fixture):
364        parent, expected_xml = add_fixture
365        oomChild = parent.add_oomChild()
366        assert parent.xml == expected_xml
367        assert isinstance(oomChild, CT_OomChild)
368        assert parent._add_oomChild.__doc__.startswith(
369            'Add a new ``<w:oomChild>`` child element '
370        )
371
372    # fixtures -------------------------------------------------------
373
374    @pytest.fixture
375    def add_fixture(self):
376        parent = self.parent_bldr(False).element
377        expected_xml = self.parent_bldr(True).xml()
378        return parent, expected_xml
379
380    @pytest.fixture
381    def getter_fixture(self):
382        parent = self.parent_bldr(True).element
383        oomChild = parent.find(qn('w:oomChild'))
384        return parent, oomChild
385
386    @pytest.fixture
387    def insert_fixture(self):
388        parent = (
389            a_parent().with_nsdecls().with_child(
390                an_oooChild()).with_child(
391                a_zomChild()).with_child(
392                a_zooChild())
393        ).element
394        oomChild = an_oomChild().with_nsdecls().element
395        expected_xml = (
396            a_parent().with_nsdecls().with_child(
397                an_oomChild()).with_child(
398                an_oooChild()).with_child(
399                a_zomChild()).with_child(
400                a_zooChild())
401        ).xml()
402        return parent, oomChild, expected_xml
403
404    @pytest.fixture
405    def new_fixture(self):
406        parent = self.parent_bldr(False).element
407        expected_xml = an_oomChild().with_nsdecls().xml()
408        return parent, expected_xml
409
410    # fixture components ---------------------------------------------
411
412    def parent_bldr(self, oomChild_is_present):
413        parent_bldr = a_parent().with_nsdecls()
414        if oomChild_is_present:
415            parent_bldr.with_child(an_oomChild())
416        return parent_bldr
417
418
419class DescribeOptionalAttribute(object):
420
421    def it_adds_a_getter_property_for_the_attr_value(self, getter_fixture):
422        parent, optAttr_python_value = getter_fixture
423        assert parent.optAttr == optAttr_python_value
424
425    def it_adds_a_setter_property_for_the_attr(self, setter_fixture):
426        parent, value, expected_xml = setter_fixture
427        parent.optAttr = value
428        assert parent.xml == expected_xml
429
430    def it_adds_a_docstring_for_the_property(self):
431        assert CT_Parent.optAttr.__doc__.startswith(
432            "ST_IntegerType type-converted value of "
433        )
434
435    # fixtures -------------------------------------------------------
436
437    @pytest.fixture
438    def getter_fixture(self):
439        parent = a_parent().with_nsdecls().with_optAttr('24').element
440        return parent, 24
441
442    @pytest.fixture(params=[36, None])
443    def setter_fixture(self, request):
444        value = request.param
445        parent = a_parent().with_nsdecls().with_optAttr('42').element
446        if value is None:
447            expected_xml = a_parent().with_nsdecls().xml()
448        else:
449            expected_xml = a_parent().with_nsdecls().with_optAttr(value).xml()
450        return parent, value, expected_xml
451
452
453class DescribeRequiredAttribute(object):
454
455    def it_adds_a_getter_property_for_the_attr_value(self, getter_fixture):
456        parent, reqAttr_python_value = getter_fixture
457        assert parent.reqAttr == reqAttr_python_value
458
459    def it_adds_a_setter_property_for_the_attr(self, setter_fixture):
460        parent, value, expected_xml = setter_fixture
461        parent.reqAttr = value
462        assert parent.xml == expected_xml
463
464    def it_adds_a_docstring_for_the_property(self):
465        assert CT_Parent.reqAttr.__doc__.startswith(
466            "ST_IntegerType type-converted value of "
467        )
468
469    def it_raises_on_get_when_attribute_not_present(self):
470        parent = a_parent().with_nsdecls().element
471        with pytest.raises(InvalidXmlError):
472            parent.reqAttr
473
474    def it_raises_on_assign_invalid_value(self, invalid_assign_fixture):
475        parent, value, expected_exception = invalid_assign_fixture
476        with pytest.raises(expected_exception):
477            parent.reqAttr = value
478
479    # fixtures -------------------------------------------------------
480
481    @pytest.fixture
482    def getter_fixture(self):
483        parent = a_parent().with_nsdecls().with_reqAttr('42').element
484        return parent, 42
485
486    @pytest.fixture(params=[
487        (None, TypeError),
488        (-4,   ValueError),
489        ('2',  TypeError),
490    ])
491    def invalid_assign_fixture(self, request):
492        invalid_value, expected_exception = request.param
493        parent = a_parent().with_nsdecls().with_reqAttr(1).element
494        return parent, invalid_value, expected_exception
495
496    @pytest.fixture
497    def setter_fixture(self):
498        parent = a_parent().with_nsdecls().with_reqAttr('42').element
499        value = 24
500        expected_xml = a_parent().with_nsdecls().with_reqAttr(value).xml()
501        return parent, value, expected_xml
502
503
504class DescribeZeroOrMore(object):
505
506    def it_adds_a_getter_property_for_the_child_element_list(
507            self, getter_fixture):
508        parent, zomChild = getter_fixture
509        assert parent.zomChild_lst[0] is zomChild
510
511    def it_adds_a_creator_method_for_the_child_element(self, new_fixture):
512        parent, expected_xml = new_fixture
513        zomChild = parent._new_zomChild()
514        assert zomChild.xml == expected_xml
515
516    def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
517        parent, zomChild, expected_xml = insert_fixture
518        parent._insert_zomChild(zomChild)
519        assert parent.xml == expected_xml
520        assert parent._insert_zomChild.__doc__.startswith(
521            'Return the passed ``<w:zomChild>`` '
522        )
523
524    def it_adds_an_add_method_for_the_child_element(self, add_fixture):
525        parent, expected_xml = add_fixture
526        zomChild = parent._add_zomChild()
527        assert parent.xml == expected_xml
528        assert isinstance(zomChild, CT_ZomChild)
529        assert parent._add_zomChild.__doc__.startswith(
530            'Add a new ``<w:zomChild>`` child element '
531        )
532
533    def it_adds_a_public_add_method_for_the_child_element(self, add_fixture):
534        parent, expected_xml = add_fixture
535        zomChild = parent.add_zomChild()
536        assert parent.xml == expected_xml
537        assert isinstance(zomChild, CT_ZomChild)
538        assert parent._add_zomChild.__doc__.startswith(
539            'Add a new ``<w:zomChild>`` child element '
540        )
541
542    def it_removes_the_property_root_name_used_for_declaration(self):
543        assert not hasattr(CT_Parent, 'zomChild')
544
545    # fixtures -------------------------------------------------------
546
547    @pytest.fixture
548    def add_fixture(self):
549        parent = self.parent_bldr(False).element
550        expected_xml = self.parent_bldr(True).xml()
551        return parent, expected_xml
552
553    @pytest.fixture
554    def getter_fixture(self):
555        parent = self.parent_bldr(True).element
556        zomChild = parent.find(qn('w:zomChild'))
557        return parent, zomChild
558
559    @pytest.fixture
560    def insert_fixture(self):
561        parent = (
562            a_parent().with_nsdecls().with_child(
563                an_oomChild()).with_child(
564                an_oooChild()).with_child(
565                a_zooChild())
566        ).element
567        zomChild = a_zomChild().with_nsdecls().element
568        expected_xml = (
569            a_parent().with_nsdecls().with_child(
570                an_oomChild()).with_child(
571                an_oooChild()).with_child(
572                a_zomChild()).with_child(
573                a_zooChild())
574        ).xml()
575        return parent, zomChild, expected_xml
576
577    @pytest.fixture
578    def new_fixture(self):
579        parent = self.parent_bldr(False).element
580        expected_xml = a_zomChild().with_nsdecls().xml()
581        return parent, expected_xml
582
583    def parent_bldr(self, zomChild_is_present):
584        parent_bldr = a_parent().with_nsdecls()
585        if zomChild_is_present:
586            parent_bldr.with_child(a_zomChild())
587        return parent_bldr
588
589
590class DescribeZeroOrOne(object):
591
592    def it_adds_a_getter_property_for_the_child_element(self, getter_fixture):
593        parent, zooChild = getter_fixture
594        assert parent.zooChild is zooChild
595
596    def it_adds_an_add_method_for_the_child_element(self, add_fixture):
597        parent, expected_xml = add_fixture
598        zooChild = parent._add_zooChild()
599        assert parent.xml == expected_xml
600        assert isinstance(zooChild, CT_ZooChild)
601        assert parent._add_zooChild.__doc__.startswith(
602            'Add a new ``<w:zooChild>`` child element '
603        )
604
605    def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
606        parent, zooChild, expected_xml = insert_fixture
607        parent._insert_zooChild(zooChild)
608        assert parent.xml == expected_xml
609        assert parent._insert_zooChild.__doc__.startswith(
610            'Return the passed ``<w:zooChild>`` '
611        )
612
613    def it_adds_a_get_or_add_method_for_the_child_element(
614            self, get_or_add_fixture):
615        parent, expected_xml = get_or_add_fixture
616        zooChild = parent.get_or_add_zooChild()
617        assert isinstance(zooChild, CT_ZooChild)
618        assert parent.xml == expected_xml
619
620    def it_adds_a_remover_method_for_the_child_element(self, remove_fixture):
621        parent, expected_xml = remove_fixture
622        parent._remove_zooChild()
623        assert parent.xml == expected_xml
624
625    # fixtures -------------------------------------------------------
626
627    @pytest.fixture
628    def add_fixture(self):
629        parent = self.parent_bldr(False).element
630        expected_xml = self.parent_bldr(True).xml()
631        return parent, expected_xml
632
633    @pytest.fixture(params=[True, False])
634    def getter_fixture(self, request):
635        zooChild_is_present = request.param
636        parent = self.parent_bldr(zooChild_is_present).element
637        zooChild = parent.find(qn('w:zooChild'))  # None if not found
638        return parent, zooChild
639
640    @pytest.fixture(params=[True, False])
641    def get_or_add_fixture(self, request):
642        zooChild_is_present = request.param
643        parent = self.parent_bldr(zooChild_is_present).element
644        expected_xml = self.parent_bldr(True).xml()
645        return parent, expected_xml
646
647    @pytest.fixture
648    def insert_fixture(self):
649        parent = (
650            a_parent().with_nsdecls().with_child(
651                an_oomChild()).with_child(
652                an_oooChild()).with_child(
653                a_zomChild())
654        ).element
655        zooChild = a_zooChild().with_nsdecls().element
656        expected_xml = (
657            a_parent().with_nsdecls().with_child(
658                an_oomChild()).with_child(
659                an_oooChild()).with_child(
660                a_zomChild()).with_child(
661                a_zooChild())
662        ).xml()
663        return parent, zooChild, expected_xml
664
665    @pytest.fixture(params=[True, False])
666    def remove_fixture(self, request):
667        zooChild_is_present = request.param
668        parent = self.parent_bldr(zooChild_is_present).element
669        expected_xml = self.parent_bldr(False).xml()
670        return parent, expected_xml
671
672    # fixture components ---------------------------------------------
673
674    def parent_bldr(self, zooChild_is_present):
675        parent_bldr = a_parent().with_nsdecls()
676        if zooChild_is_present:
677            parent_bldr.with_child(a_zooChild())
678        return parent_bldr
679
680
681class DescribeZeroOrOneChoice(object):
682
683    def it_adds_a_getter_for_the_current_choice(self, getter_fixture):
684        parent, expected_choice = getter_fixture
685        assert parent.eg_zooChoice is expected_choice
686
687    # fixtures -------------------------------------------------------
688
689    @pytest.fixture(params=[None, 'choice', 'choice2'])
690    def getter_fixture(self, request):
691        choice_tag = request.param
692        parent = self.parent_bldr(choice_tag).element
693        tagname = 'w:%s' % choice_tag
694        expected_choice = parent.find(qn(tagname))  # None if not found
695        return parent, expected_choice
696
697    # fixture components ---------------------------------------------
698
699    def parent_bldr(self, choice_tag=None):
700        parent_bldr = a_parent().with_nsdecls()
701        if choice_tag == 'choice':
702            parent_bldr.with_child(a_choice())
703        if choice_tag == 'choice2':
704            parent_bldr.with_child(a_choice2())
705        return parent_bldr
706
707
708# --------------------------------------------------------------------
709# static shared fixture
710# --------------------------------------------------------------------
711
712class ST_IntegerType(BaseIntType):
713
714    @classmethod
715    def validate(cls, value):
716        cls.validate_int(value)
717        if value < 1 or value > 42:
718            raise ValueError(
719                "value must be in range 1 to 42 inclusive"
720            )
721
722
723class CT_Parent(BaseOxmlElement):
724    """
725    ``<w:parent>`` element, an invented element for use in testing.
726    """
727    eg_zooChoice = ZeroOrOneChoice(
728        (Choice('w:choice'), Choice('w:choice2')),
729        successors=('w:oomChild', 'w:oooChild')
730    )
731    oomChild = OneOrMore('w:oomChild', successors=(
732        'w:oooChild', 'w:zomChild', 'w:zooChild'
733    ))
734    oooChild = OneAndOnlyOne('w:oooChild')
735    zomChild = ZeroOrMore('w:zomChild', successors=('w:zooChild',))
736    zooChild = ZeroOrOne('w:zooChild', successors=())
737    optAttr = OptionalAttribute('w:optAttr', ST_IntegerType)
738    reqAttr = RequiredAttribute('reqAttr', ST_IntegerType)
739
740
741class CT_Choice(BaseOxmlElement):
742    """
743    ``<w:choice>`` element
744    """
745
746
747class CT_OomChild(BaseOxmlElement):
748    """
749    Oom standing for 'OneOrMore', ``<w:oomChild>`` element, representing a
750    child element that can appear multiple times in sequence, but must appear
751    at least once.
752    """
753
754
755class CT_ZomChild(BaseOxmlElement):
756    """
757    Zom standing for 'ZeroOrMore', ``<w:zomChild>`` element, representing an
758    optional child element that can appear multiple times in sequence.
759    """
760
761
762class CT_ZooChild(BaseOxmlElement):
763    """
764    Zoo standing for 'ZeroOrOne', ``<w:zooChild>`` element, an invented
765    element for use in testing.
766    """
767
768
769register_element_cls('w:parent',   CT_Parent)
770register_element_cls('w:choice',   CT_Choice)
771register_element_cls('w:oomChild', CT_OomChild)
772register_element_cls('w:zomChild', CT_ZomChild)
773register_element_cls('w:zooChild', CT_ZooChild)
774
775
776class CT_ChoiceBuilder(BaseBuilder):
777    __tag__ = 'w:choice'
778    __nspfxs__ = ('w',)
779    __attrs__ = ()
780
781
782class CT_Choice2Builder(BaseBuilder):
783    __tag__ = 'w:choice2'
784    __nspfxs__ = ('w',)
785    __attrs__ = ()
786
787
788class CT_ParentBuilder(BaseBuilder):
789    __tag__ = 'w:parent'
790    __nspfxs__ = ('w',)
791    __attrs__ = ('w:optAttr', 'reqAttr')
792
793
794class CT_OomChildBuilder(BaseBuilder):
795    __tag__ = 'w:oomChild'
796    __nspfxs__ = ('w',)
797    __attrs__ = ()
798
799
800class CT_OooChildBuilder(BaseBuilder):
801    __tag__ = 'w:oooChild'
802    __nspfxs__ = ('w',)
803    __attrs__ = ()
804
805
806class CT_ZomChildBuilder(BaseBuilder):
807    __tag__ = 'w:zomChild'
808    __nspfxs__ = ('w',)
809    __attrs__ = ()
810
811
812class CT_ZooChildBuilder(BaseBuilder):
813    __tag__ = 'w:zooChild'
814    __nspfxs__ = ('w',)
815    __attrs__ = ()
816
817
818def a_choice():
819    return CT_ChoiceBuilder()
820
821
822def a_choice2():
823    return CT_Choice2Builder()
824
825
826def a_parent():
827    return CT_ParentBuilder()
828
829
830def a_zomChild():
831    return CT_ZomChildBuilder()
832
833
834def a_zooChild():
835    return CT_ZooChildBuilder()
836
837
838def an_oomChild():
839    return CT_OomChildBuilder()
840
841
842def an_oooChild():
843    return CT_OooChildBuilder()
844