# encoding: utf-8
"""
Test suite for docx.oxml.xmlchemy
"""
from __future__ import absolute_import, print_function, unicode_literals
import pytest
from docx.compat import Unicode
from docx.oxml import parse_xml, register_element_cls
from docx.oxml.exceptions import InvalidXmlError
from docx.oxml.ns import qn
from docx.oxml.simpletypes import BaseIntType
from docx.oxml.xmlchemy import (
BaseOxmlElement, Choice, serialize_for_reading, OneOrMore, OneAndOnlyOne,
OptionalAttribute, RequiredAttribute, ZeroOrMore, ZeroOrOne,
ZeroOrOneChoice, XmlString
)
from ..unitdata import BaseBuilder
from .unitdata.text import a_b, a_u, an_i, an_rPr
class DescribeBaseOxmlElement(object):
def it_can_find_the_first_of_its_children_named_in_a_sequence(
self, first_fixture):
element, tagnames, matching_child = first_fixture
assert element.first_child_found_in(*tagnames) is matching_child
def it_can_insert_an_element_before_named_successors(
self, insert_fixture):
element, child, tagnames, expected_xml = insert_fixture
element.insert_element_before(child, *tagnames)
assert element.xml == expected_xml
def it_can_remove_all_children_with_name_in_sequence(
self, remove_fixture):
element, tagnames, expected_xml = remove_fixture
element.remove_all(*tagnames)
assert element.xml == expected_xml
# fixtures ---------------------------------------------
@pytest.fixture(params=[
('biu', 'iu', 'i'),
('bu', 'iu', 'u'),
('bi', 'u', None),
('b', 'iu', None),
('iu', 'biu', 'i'),
('', 'biu', None),
])
def first_fixture(self, request):
present, matching, match = request.param
element = self.rPr_bldr(present).element
tagnames = self.nsptags(matching)
matching_child = element.find(qn('w:%s' % match)) if match else None
return element, tagnames, matching_child
@pytest.fixture(params=[
('iu', 'b', 'iu', 'biu'),
('u', 'b', 'iu', 'bu'),
('', 'b', 'iu', 'b'),
('bu', 'i', 'u', 'biu'),
('bi', 'u', '', 'biu'),
])
def insert_fixture(self, request):
present, new, successors, after = request.param
element = self.rPr_bldr(present).element
child = {
'b': a_b(), 'i': an_i(), 'u': a_u()
}[new].with_nsdecls().element
tagnames = [('w:%s' % char) for char in successors]
expected_xml = self.rPr_bldr(after).xml()
return element, child, tagnames, expected_xml
@pytest.fixture(params=[
('biu', 'b', 'iu'), ('biu', 'bi', 'u'), ('bbiiuu', 'i', 'bbuu'),
('biu', 'i', 'bu'), ('biu', 'bu', 'i'), ('bbiiuu', '', 'bbiiuu'),
('biu', 'u', 'bi'), ('biu', 'ui', 'b'), ('bbiiuu', 'bi', 'uu'),
('bu', 'i', 'bu'), ('', 'ui', ''),
])
def remove_fixture(self, request):
present, remove, after = request.param
element = self.rPr_bldr(present).element
tagnames = self.nsptags(remove)
expected_xml = self.rPr_bldr(after).xml()
return element, tagnames, expected_xml
# fixture components ---------------------------------------------
def nsptags(self, letters):
return [('w:%s' % letter) for letter in letters]
def rPr_bldr(self, children):
rPr_bldr = an_rPr().with_nsdecls()
for char in children:
if char == 'b':
rPr_bldr.with_child(a_b())
elif char == 'i':
rPr_bldr.with_child(an_i())
elif char == 'u':
rPr_bldr.with_child(a_u())
else:
raise NotImplementedError("got '%s'" % char)
return rPr_bldr
class DescribeSerializeForReading(object):
def it_pretty_prints_an_lxml_element(self, pretty_fixture):
element, expected_xml_text = pretty_fixture
xml_text = serialize_for_reading(element)
assert xml_text == expected_xml_text
def it_returns_unicode_text(self, type_fixture):
element = type_fixture
xml_text = serialize_for_reading(element)
assert isinstance(xml_text, Unicode)
# fixtures ---------------------------------------------
@pytest.fixture
def pretty_fixture(self, element):
expected_xml_text = (
'\n'
' text\n'
'\n'
)
return element, expected_xml_text
@pytest.fixture
def type_fixture(self, element):
return element
# fixture components -----------------------------------
@pytest.fixture
def element(self):
return parse_xml('text')
class DescribeXmlString(object):
def it_parses_a_line_to_help_compare(self, parse_fixture):
"""
This internal function is important to test separately because if it
doesn't parse a line properly, false equality can result.
"""
line, expected_front, expected_attrs = parse_fixture[:3]
expected_close, expected_text = parse_fixture[3:]
front, attrs, close, text = XmlString._parse_line(line)
# print("'%s' '%s' '%s' %s" % (
# front, attrs, close, ('%s' % text) if text else text))
assert front == expected_front
assert attrs == expected_attrs
assert close == expected_close
assert text == expected_text
def it_knows_if_two_xml_lines_are_equivalent(self, xml_line_case):
line, other, differs = xml_line_case
xml = XmlString(line)
assert xml == other
assert xml != differs
# fixtures ---------------------------------------------
@pytest.fixture(params=[
('text', '', 'text'),
('', '', None),
('', '', None),
('t', '', 't'),
('2013-12-23T23:15:00Z', '', '2013-12-23T23:15:00Z'),
])
def parse_fixture(self, request):
line, front, attrs, close, text = request.param
return line, front, attrs, close, text
@pytest.fixture(params=[
'simple_elm', 'nsp_tagname', 'indent', 'attrs', 'nsdecl_order',
'closing_elm',
])
def xml_line_case(self, request):
cases = {
'simple_elm': (
'',
'',
'',
),
'nsp_tagname': (
'',
'',
'',
),
'indent': (
' ',
' ',
'',
),
'attrs': (
' ',
' ',
' ',
),
'nsdecl_order': (
' ',
' ',
' ',
),
'closing_elm': (
'',
'',
'',
),
}
line, other, differs = cases[request.param]
return line, other, differs
class DescribeChoice(object):
def it_adds_a_getter_property_for_the_choice_element(
self, getter_fixture):
parent, expected_choice = getter_fixture
assert parent.choice is expected_choice
def it_adds_a_creator_method_for_the_child_element(self, new_fixture):
parent, expected_xml = new_fixture
choice = parent._new_choice()
assert choice.xml == expected_xml
def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
parent, choice, expected_xml = insert_fixture
parent._insert_choice(choice)
assert parent.xml == expected_xml
assert parent._insert_choice.__doc__.startswith(
'Return the passed ```` '
)
def it_adds_an_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
choice = parent._add_choice()
assert parent.xml == expected_xml
assert isinstance(choice, CT_Choice)
assert parent._add_choice.__doc__.startswith(
'Add a new ```` child element '
)
def it_adds_a_get_or_change_to_method_for_the_child_element(
self, get_or_change_to_fixture):
parent, expected_xml = get_or_change_to_fixture
choice = parent.get_or_change_to_choice()
assert isinstance(choice, CT_Choice)
assert parent.xml == expected_xml
# fixtures -------------------------------------------------------
@pytest.fixture
def add_fixture(self):
parent = self.parent_bldr().element
expected_xml = self.parent_bldr('choice').xml()
return parent, expected_xml
@pytest.fixture(params=[
('choice2', 'choice'),
(None, 'choice'),
('choice', 'choice'),
])
def get_or_change_to_fixture(self, request):
before_member_tag, after_member_tag = request.param
parent = self.parent_bldr(before_member_tag).element
expected_xml = self.parent_bldr(after_member_tag).xml()
return parent, expected_xml
@pytest.fixture(params=['choice', None])
def getter_fixture(self, request):
choice_tag = request.param
parent = self.parent_bldr(choice_tag).element
expected_choice = parent.find(qn('w:choice')) # None if not found
return parent, expected_choice
@pytest.fixture
def insert_fixture(self):
parent = (
a_parent().with_nsdecls().with_child(
an_oomChild()).with_child(
an_oooChild())
).element
choice = a_choice().with_nsdecls().element
expected_xml = (
a_parent().with_nsdecls().with_child(
a_choice()).with_child(
an_oomChild()).with_child(
an_oooChild())
).xml()
return parent, choice, expected_xml
@pytest.fixture
def new_fixture(self):
parent = self.parent_bldr().element
expected_xml = a_choice().with_nsdecls().xml()
return parent, expected_xml
# fixture components ---------------------------------------------
def parent_bldr(self, choice_tag=None):
parent_bldr = a_parent().with_nsdecls()
if choice_tag == 'choice':
parent_bldr.with_child(a_choice())
if choice_tag == 'choice2':
parent_bldr.with_child(a_choice2())
return parent_bldr
class DescribeOneAndOnlyOne(object):
def it_adds_a_getter_property_for_the_child_element(self, getter_fixture):
parent, oooChild = getter_fixture
assert parent.oooChild is oooChild
# fixtures -------------------------------------------------------
@pytest.fixture
def getter_fixture(self):
parent = a_parent().with_nsdecls().with_child(an_oooChild()).element
oooChild = parent.find(qn('w:oooChild'))
return parent, oooChild
class DescribeOneOrMore(object):
def it_adds_a_getter_property_for_the_child_element_list(
self, getter_fixture):
parent, oomChild = getter_fixture
assert parent.oomChild_lst[0] is oomChild
def it_adds_a_creator_method_for_the_child_element(self, new_fixture):
parent, expected_xml = new_fixture
oomChild = parent._new_oomChild()
assert oomChild.xml == expected_xml
def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
parent, oomChild, expected_xml = insert_fixture
parent._insert_oomChild(oomChild)
assert parent.xml == expected_xml
assert parent._insert_oomChild.__doc__.startswith(
'Return the passed ```` '
)
def it_adds_a_private_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
oomChild = parent._add_oomChild()
assert parent.xml == expected_xml
assert isinstance(oomChild, CT_OomChild)
assert parent._add_oomChild.__doc__.startswith(
'Add a new ```` child element '
)
def it_adds_a_public_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
oomChild = parent.add_oomChild()
assert parent.xml == expected_xml
assert isinstance(oomChild, CT_OomChild)
assert parent._add_oomChild.__doc__.startswith(
'Add a new ```` child element '
)
# fixtures -------------------------------------------------------
@pytest.fixture
def add_fixture(self):
parent = self.parent_bldr(False).element
expected_xml = self.parent_bldr(True).xml()
return parent, expected_xml
@pytest.fixture
def getter_fixture(self):
parent = self.parent_bldr(True).element
oomChild = parent.find(qn('w:oomChild'))
return parent, oomChild
@pytest.fixture
def insert_fixture(self):
parent = (
a_parent().with_nsdecls().with_child(
an_oooChild()).with_child(
a_zomChild()).with_child(
a_zooChild())
).element
oomChild = an_oomChild().with_nsdecls().element
expected_xml = (
a_parent().with_nsdecls().with_child(
an_oomChild()).with_child(
an_oooChild()).with_child(
a_zomChild()).with_child(
a_zooChild())
).xml()
return parent, oomChild, expected_xml
@pytest.fixture
def new_fixture(self):
parent = self.parent_bldr(False).element
expected_xml = an_oomChild().with_nsdecls().xml()
return parent, expected_xml
# fixture components ---------------------------------------------
def parent_bldr(self, oomChild_is_present):
parent_bldr = a_parent().with_nsdecls()
if oomChild_is_present:
parent_bldr.with_child(an_oomChild())
return parent_bldr
class DescribeOptionalAttribute(object):
def it_adds_a_getter_property_for_the_attr_value(self, getter_fixture):
parent, optAttr_python_value = getter_fixture
assert parent.optAttr == optAttr_python_value
def it_adds_a_setter_property_for_the_attr(self, setter_fixture):
parent, value, expected_xml = setter_fixture
parent.optAttr = value
assert parent.xml == expected_xml
def it_adds_a_docstring_for_the_property(self):
assert CT_Parent.optAttr.__doc__.startswith(
"ST_IntegerType type-converted value of "
)
# fixtures -------------------------------------------------------
@pytest.fixture
def getter_fixture(self):
parent = a_parent().with_nsdecls().with_optAttr('24').element
return parent, 24
@pytest.fixture(params=[36, None])
def setter_fixture(self, request):
value = request.param
parent = a_parent().with_nsdecls().with_optAttr('42').element
if value is None:
expected_xml = a_parent().with_nsdecls().xml()
else:
expected_xml = a_parent().with_nsdecls().with_optAttr(value).xml()
return parent, value, expected_xml
class DescribeRequiredAttribute(object):
def it_adds_a_getter_property_for_the_attr_value(self, getter_fixture):
parent, reqAttr_python_value = getter_fixture
assert parent.reqAttr == reqAttr_python_value
def it_adds_a_setter_property_for_the_attr(self, setter_fixture):
parent, value, expected_xml = setter_fixture
parent.reqAttr = value
assert parent.xml == expected_xml
def it_adds_a_docstring_for_the_property(self):
assert CT_Parent.reqAttr.__doc__.startswith(
"ST_IntegerType type-converted value of "
)
def it_raises_on_get_when_attribute_not_present(self):
parent = a_parent().with_nsdecls().element
with pytest.raises(InvalidXmlError):
parent.reqAttr
def it_raises_on_assign_invalid_value(self, invalid_assign_fixture):
parent, value, expected_exception = invalid_assign_fixture
with pytest.raises(expected_exception):
parent.reqAttr = value
# fixtures -------------------------------------------------------
@pytest.fixture
def getter_fixture(self):
parent = a_parent().with_nsdecls().with_reqAttr('42').element
return parent, 42
@pytest.fixture(params=[
(None, TypeError),
(-4, ValueError),
('2', TypeError),
])
def invalid_assign_fixture(self, request):
invalid_value, expected_exception = request.param
parent = a_parent().with_nsdecls().with_reqAttr(1).element
return parent, invalid_value, expected_exception
@pytest.fixture
def setter_fixture(self):
parent = a_parent().with_nsdecls().with_reqAttr('42').element
value = 24
expected_xml = a_parent().with_nsdecls().with_reqAttr(value).xml()
return parent, value, expected_xml
class DescribeZeroOrMore(object):
def it_adds_a_getter_property_for_the_child_element_list(
self, getter_fixture):
parent, zomChild = getter_fixture
assert parent.zomChild_lst[0] is zomChild
def it_adds_a_creator_method_for_the_child_element(self, new_fixture):
parent, expected_xml = new_fixture
zomChild = parent._new_zomChild()
assert zomChild.xml == expected_xml
def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
parent, zomChild, expected_xml = insert_fixture
parent._insert_zomChild(zomChild)
assert parent.xml == expected_xml
assert parent._insert_zomChild.__doc__.startswith(
'Return the passed ```` '
)
def it_adds_an_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
zomChild = parent._add_zomChild()
assert parent.xml == expected_xml
assert isinstance(zomChild, CT_ZomChild)
assert parent._add_zomChild.__doc__.startswith(
'Add a new ```` child element '
)
def it_adds_a_public_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
zomChild = parent.add_zomChild()
assert parent.xml == expected_xml
assert isinstance(zomChild, CT_ZomChild)
assert parent._add_zomChild.__doc__.startswith(
'Add a new ```` child element '
)
def it_removes_the_property_root_name_used_for_declaration(self):
assert not hasattr(CT_Parent, 'zomChild')
# fixtures -------------------------------------------------------
@pytest.fixture
def add_fixture(self):
parent = self.parent_bldr(False).element
expected_xml = self.parent_bldr(True).xml()
return parent, expected_xml
@pytest.fixture
def getter_fixture(self):
parent = self.parent_bldr(True).element
zomChild = parent.find(qn('w:zomChild'))
return parent, zomChild
@pytest.fixture
def insert_fixture(self):
parent = (
a_parent().with_nsdecls().with_child(
an_oomChild()).with_child(
an_oooChild()).with_child(
a_zooChild())
).element
zomChild = a_zomChild().with_nsdecls().element
expected_xml = (
a_parent().with_nsdecls().with_child(
an_oomChild()).with_child(
an_oooChild()).with_child(
a_zomChild()).with_child(
a_zooChild())
).xml()
return parent, zomChild, expected_xml
@pytest.fixture
def new_fixture(self):
parent = self.parent_bldr(False).element
expected_xml = a_zomChild().with_nsdecls().xml()
return parent, expected_xml
def parent_bldr(self, zomChild_is_present):
parent_bldr = a_parent().with_nsdecls()
if zomChild_is_present:
parent_bldr.with_child(a_zomChild())
return parent_bldr
class DescribeZeroOrOne(object):
def it_adds_a_getter_property_for_the_child_element(self, getter_fixture):
parent, zooChild = getter_fixture
assert parent.zooChild is zooChild
def it_adds_an_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
zooChild = parent._add_zooChild()
assert parent.xml == expected_xml
assert isinstance(zooChild, CT_ZooChild)
assert parent._add_zooChild.__doc__.startswith(
'Add a new ```` child element '
)
def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
parent, zooChild, expected_xml = insert_fixture
parent._insert_zooChild(zooChild)
assert parent.xml == expected_xml
assert parent._insert_zooChild.__doc__.startswith(
'Return the passed ```` '
)
def it_adds_a_get_or_add_method_for_the_child_element(
self, get_or_add_fixture):
parent, expected_xml = get_or_add_fixture
zooChild = parent.get_or_add_zooChild()
assert isinstance(zooChild, CT_ZooChild)
assert parent.xml == expected_xml
def it_adds_a_remover_method_for_the_child_element(self, remove_fixture):
parent, expected_xml = remove_fixture
parent._remove_zooChild()
assert parent.xml == expected_xml
# fixtures -------------------------------------------------------
@pytest.fixture
def add_fixture(self):
parent = self.parent_bldr(False).element
expected_xml = self.parent_bldr(True).xml()
return parent, expected_xml
@pytest.fixture(params=[True, False])
def getter_fixture(self, request):
zooChild_is_present = request.param
parent = self.parent_bldr(zooChild_is_present).element
zooChild = parent.find(qn('w:zooChild')) # None if not found
return parent, zooChild
@pytest.fixture(params=[True, False])
def get_or_add_fixture(self, request):
zooChild_is_present = request.param
parent = self.parent_bldr(zooChild_is_present).element
expected_xml = self.parent_bldr(True).xml()
return parent, expected_xml
@pytest.fixture
def insert_fixture(self):
parent = (
a_parent().with_nsdecls().with_child(
an_oomChild()).with_child(
an_oooChild()).with_child(
a_zomChild())
).element
zooChild = a_zooChild().with_nsdecls().element
expected_xml = (
a_parent().with_nsdecls().with_child(
an_oomChild()).with_child(
an_oooChild()).with_child(
a_zomChild()).with_child(
a_zooChild())
).xml()
return parent, zooChild, expected_xml
@pytest.fixture(params=[True, False])
def remove_fixture(self, request):
zooChild_is_present = request.param
parent = self.parent_bldr(zooChild_is_present).element
expected_xml = self.parent_bldr(False).xml()
return parent, expected_xml
# fixture components ---------------------------------------------
def parent_bldr(self, zooChild_is_present):
parent_bldr = a_parent().with_nsdecls()
if zooChild_is_present:
parent_bldr.with_child(a_zooChild())
return parent_bldr
class DescribeZeroOrOneChoice(object):
def it_adds_a_getter_for_the_current_choice(self, getter_fixture):
parent, expected_choice = getter_fixture
assert parent.eg_zooChoice is expected_choice
# fixtures -------------------------------------------------------
@pytest.fixture(params=[None, 'choice', 'choice2'])
def getter_fixture(self, request):
choice_tag = request.param
parent = self.parent_bldr(choice_tag).element
tagname = 'w:%s' % choice_tag
expected_choice = parent.find(qn(tagname)) # None if not found
return parent, expected_choice
# fixture components ---------------------------------------------
def parent_bldr(self, choice_tag=None):
parent_bldr = a_parent().with_nsdecls()
if choice_tag == 'choice':
parent_bldr.with_child(a_choice())
if choice_tag == 'choice2':
parent_bldr.with_child(a_choice2())
return parent_bldr
# --------------------------------------------------------------------
# static shared fixture
# --------------------------------------------------------------------
class ST_IntegerType(BaseIntType):
@classmethod
def validate(cls, value):
cls.validate_int(value)
if value < 1 or value > 42:
raise ValueError(
"value must be in range 1 to 42 inclusive"
)
class CT_Parent(BaseOxmlElement):
"""
```` element, an invented element for use in testing.
"""
eg_zooChoice = ZeroOrOneChoice(
(Choice('w:choice'), Choice('w:choice2')),
successors=('w:oomChild', 'w:oooChild')
)
oomChild = OneOrMore('w:oomChild', successors=(
'w:oooChild', 'w:zomChild', 'w:zooChild'
))
oooChild = OneAndOnlyOne('w:oooChild')
zomChild = ZeroOrMore('w:zomChild', successors=('w:zooChild',))
zooChild = ZeroOrOne('w:zooChild', successors=())
optAttr = OptionalAttribute('w:optAttr', ST_IntegerType)
reqAttr = RequiredAttribute('reqAttr', ST_IntegerType)
class CT_Choice(BaseOxmlElement):
"""
```` element
"""
class CT_OomChild(BaseOxmlElement):
"""
Oom standing for 'OneOrMore', ```` element, representing a
child element that can appear multiple times in sequence, but must appear
at least once.
"""
class CT_ZomChild(BaseOxmlElement):
"""
Zom standing for 'ZeroOrMore', ```` element, representing an
optional child element that can appear multiple times in sequence.
"""
class CT_ZooChild(BaseOxmlElement):
"""
Zoo standing for 'ZeroOrOne', ```` element, an invented
element for use in testing.
"""
register_element_cls('w:parent', CT_Parent)
register_element_cls('w:choice', CT_Choice)
register_element_cls('w:oomChild', CT_OomChild)
register_element_cls('w:zomChild', CT_ZomChild)
register_element_cls('w:zooChild', CT_ZooChild)
class CT_ChoiceBuilder(BaseBuilder):
__tag__ = 'w:choice'
__nspfxs__ = ('w',)
__attrs__ = ()
class CT_Choice2Builder(BaseBuilder):
__tag__ = 'w:choice2'
__nspfxs__ = ('w',)
__attrs__ = ()
class CT_ParentBuilder(BaseBuilder):
__tag__ = 'w:parent'
__nspfxs__ = ('w',)
__attrs__ = ('w:optAttr', 'reqAttr')
class CT_OomChildBuilder(BaseBuilder):
__tag__ = 'w:oomChild'
__nspfxs__ = ('w',)
__attrs__ = ()
class CT_OooChildBuilder(BaseBuilder):
__tag__ = 'w:oooChild'
__nspfxs__ = ('w',)
__attrs__ = ()
class CT_ZomChildBuilder(BaseBuilder):
__tag__ = 'w:zomChild'
__nspfxs__ = ('w',)
__attrs__ = ()
class CT_ZooChildBuilder(BaseBuilder):
__tag__ = 'w:zooChild'
__nspfxs__ = ('w',)
__attrs__ = ()
def a_choice():
return CT_ChoiceBuilder()
def a_choice2():
return CT_Choice2Builder()
def a_parent():
return CT_ParentBuilder()
def a_zomChild():
return CT_ZomChildBuilder()
def a_zooChild():
return CT_ZooChildBuilder()
def an_oomChild():
return CT_OomChildBuilder()
def an_oooChild():
return CT_OooChildBuilder()