1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
|
# encoding: utf-8
"""
Shared code for unit test data builders
"""
from __future__ import absolute_import, print_function, unicode_literals
from docx.oxml import parse_xml
from docx.oxml.ns import nsdecls
class BaseBuilder(object):
"""
Provides common behavior for all data builders.
"""
def __init__(self):
self._empty = False
self._nsdecls = ''
self._text = ''
self._xmlattrs = []
self._xmlattr_method_map = {}
for attr_name in self.__attrs__:
base_name = (
attr_name.split(':')[1] if ':' in attr_name else attr_name
)
method_name = 'with_%s' % base_name
self._xmlattr_method_map[method_name] = attr_name
self._child_bldrs = []
def __getattr__(self, name):
"""
Intercept attribute access to generalize "with_{xmlattr_name}()"
methods.
"""
if name in self._xmlattr_method_map:
def with_xmlattr(value):
xmlattr_name = self._xmlattr_method_map[name]
self._set_xmlattr(xmlattr_name, value)
return self
return with_xmlattr
else:
tmpl = "'%s' object has no attribute '%s'"
raise AttributeError(tmpl % (self.__class__.__name__, name))
def clear(self):
"""
Reset this builder back to initial state so it can be reused within
a single test.
"""
BaseBuilder.__init__(self)
return self
@property
def element(self):
"""
Element parsed from XML generated by builder in current state
"""
elm = parse_xml(self.xml())
return elm
def with_child(self, child_bldr):
"""
Cause new child element specified by *child_bldr* to be appended to
the children of this element.
"""
self._child_bldrs.append(child_bldr)
return self
def with_text(self, text):
"""
Cause *text* to be placed between the start and end tags of this
element. Not robust enough for mixed elements, intended only for
elements having no child elements.
"""
self._text = text
return self
def with_nsdecls(self, *nspfxs):
"""
Cause the element to contain namespace declarations. By default, the
namespace prefixes defined in the Builder class are used. These can
be overridden by providing exlicit prefixes, e.g.
``with_nsdecls('a', 'r')``.
"""
if not nspfxs:
nspfxs = self.__nspfxs__
self._nsdecls = ' %s' % nsdecls(*nspfxs)
return self
def xml(self, indent=0):
"""
Return element XML based on attribute settings
"""
indent_str = ' ' * indent
if self._is_empty:
xml = '%s%s\n' % (indent_str, self._empty_element_tag)
else:
xml = '%s\n' % self._non_empty_element_xml(indent)
return xml
def xml_bytes(self, indent=0):
return self.xml(indent=indent).encode('utf-8')
@property
def _empty_element_tag(self):
return '<%s%s%s/>' % (self.__tag__, self._nsdecls, self._xmlattrs_str)
@property
def _end_tag(self):
return '</%s>' % self.__tag__
@property
def _is_empty(self):
return len(self._child_bldrs) == 0 and len(self._text) == 0
def _non_empty_element_xml(self, indent):
indent_str = ' ' * indent
if self._text:
xml = ('%s%s%s%s' %
(indent_str, self._start_tag, self._text, self._end_tag))
else:
xml = '%s%s\n' % (indent_str, self._start_tag)
for child_bldr in self._child_bldrs:
xml += child_bldr.xml(indent+2)
xml += '%s%s' % (indent_str, self._end_tag)
return xml
def _set_xmlattr(self, xmlattr_name, value):
xmlattr_str = ' %s="%s"' % (xmlattr_name, str(value))
self._xmlattrs.append(xmlattr_str)
@property
def _start_tag(self):
return '<%s%s%s>' % (self.__tag__, self._nsdecls, self._xmlattrs_str)
@property
def _xmlattrs_str(self):
"""
Return all element attributes as a string, like ' foo="bar" x="1"'.
"""
return ''.join(self._xmlattrs)
|