| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149 |
- # cython: binding=True
- # cython: auto_pickle=False
- # cython: language_level=3
- """
- The ``lxml.objectify`` module implements a Python object API for XML.
- It is based on `lxml.etree`.
- """
- cimport cython
- from lxml.includes.etreepublic cimport _Document, _Element, ElementBase, ElementClassLookup
- from lxml.includes.etreepublic cimport elementFactory, import_lxml__etree, textOf, pyunicode
- from lxml.includes.tree cimport const_xmlChar, _xcstr
- from lxml cimport python
- from lxml.includes cimport tree
- cimport lxml.includes.etreepublic as cetree
- cimport libc.string as cstring_h # not to be confused with stdlib 'string'
- from libc.string cimport const_char
- from libc cimport limits
- __all__ = ['BoolElement', 'DataElement', 'E', 'Element', 'ElementMaker',
- 'FloatElement', 'IntElement', 'NoneElement',
- 'NumberElement', 'ObjectPath', 'ObjectifiedDataElement',
- 'ObjectifiedElement', 'ObjectifyElementClassLookup',
- 'PYTYPE_ATTRIBUTE', 'PyType', 'StringElement', 'SubElement',
- 'XML', 'annotate', 'deannotate', 'dump', 'enable_recursive_str',
- 'fromstring', 'getRegisteredTypes', 'makeparser', 'parse',
- 'pyannotate', 'pytypename', 'set_default_parser',
- 'set_pytype_attribute_tag', 'xsiannotate']
- cdef object etree
- from lxml import etree
- # initialize C-API of lxml.etree
- import_lxml__etree()
- __version__ = etree.__version__
- cdef object _float_is_inf, _float_is_nan
- from math import isinf as _float_is_inf, isnan as _float_is_nan
- cdef object re
- import re
- cdef tuple IGNORABLE_ERRORS = (ValueError, TypeError)
- cdef object is_special_method = re.compile('__.*__$').match
- cdef object _typename(object t):
- cdef const_char* c_name
- c_name = python._fqtypename(t)
- s = cstring_h.strrchr(c_name, c'.')
- if s is not NULL:
- c_name = s + 1
- return pyunicode(<const_xmlChar*>c_name)
- # namespace/name for "pytype" hint attribute
- cdef object PYTYPE_NAMESPACE
- cdef bytes PYTYPE_NAMESPACE_UTF8
- cdef const_xmlChar* _PYTYPE_NAMESPACE
- cdef object PYTYPE_ATTRIBUTE_NAME
- cdef bytes PYTYPE_ATTRIBUTE_NAME_UTF8
- cdef const_xmlChar* _PYTYPE_ATTRIBUTE_NAME
- PYTYPE_ATTRIBUTE = None
- cdef unicode TREE_PYTYPE_NAME = "TREE"
- cdef tuple _unicodeAndUtf8(s):
- return s, python.PyUnicode_AsUTF8String(s)
- def set_pytype_attribute_tag(attribute_tag=None):
- """set_pytype_attribute_tag(attribute_tag=None)
- Change name and namespace of the XML attribute that holds Python type
- information.
- Do not use this unless you know what you are doing.
- Reset by calling without argument.
- Default: "{http://codespeak.net/lxml/objectify/pytype}pytype"
- """
- global PYTYPE_ATTRIBUTE, _PYTYPE_NAMESPACE, _PYTYPE_ATTRIBUTE_NAME
- global PYTYPE_NAMESPACE, PYTYPE_NAMESPACE_UTF8
- global PYTYPE_ATTRIBUTE_NAME, PYTYPE_ATTRIBUTE_NAME_UTF8
- if attribute_tag is None:
- PYTYPE_NAMESPACE, PYTYPE_NAMESPACE_UTF8 = \
- _unicodeAndUtf8("http://codespeak.net/lxml/objectify/pytype")
- PYTYPE_ATTRIBUTE_NAME, PYTYPE_ATTRIBUTE_NAME_UTF8 = \
- _unicodeAndUtf8("pytype")
- else:
- PYTYPE_NAMESPACE_UTF8, PYTYPE_ATTRIBUTE_NAME_UTF8 = \
- cetree.getNsTag(attribute_tag)
- PYTYPE_NAMESPACE = PYTYPE_NAMESPACE_UTF8.decode('utf8')
- PYTYPE_ATTRIBUTE_NAME = PYTYPE_ATTRIBUTE_NAME_UTF8.decode('utf8')
- _PYTYPE_NAMESPACE = PYTYPE_NAMESPACE_UTF8
- _PYTYPE_ATTRIBUTE_NAME = PYTYPE_ATTRIBUTE_NAME_UTF8
- PYTYPE_ATTRIBUTE = cetree.namespacedNameFromNsName(
- _PYTYPE_NAMESPACE, _PYTYPE_ATTRIBUTE_NAME)
- set_pytype_attribute_tag()
- # namespaces for XML Schema
- cdef object XML_SCHEMA_NS, XML_SCHEMA_NS_UTF8
- XML_SCHEMA_NS, XML_SCHEMA_NS_UTF8 = \
- _unicodeAndUtf8("http://www.w3.org/2001/XMLSchema")
- cdef const_xmlChar* _XML_SCHEMA_NS = _xcstr(XML_SCHEMA_NS_UTF8)
- cdef object XML_SCHEMA_INSTANCE_NS, XML_SCHEMA_INSTANCE_NS_UTF8
- XML_SCHEMA_INSTANCE_NS, XML_SCHEMA_INSTANCE_NS_UTF8 = \
- _unicodeAndUtf8("http://www.w3.org/2001/XMLSchema-instance")
- cdef const_xmlChar* _XML_SCHEMA_INSTANCE_NS = _xcstr(XML_SCHEMA_INSTANCE_NS_UTF8)
- cdef object XML_SCHEMA_INSTANCE_NIL_ATTR = "{%s}nil" % XML_SCHEMA_INSTANCE_NS
- cdef object XML_SCHEMA_INSTANCE_TYPE_ATTR = "{%s}type" % XML_SCHEMA_INSTANCE_NS
- ################################################################################
- # Element class for the main API
- cdef class ObjectifiedElement(ElementBase):
- """Main XML Element class.
- Element children are accessed as object attributes. Multiple children
- with the same name are available through a list index. Example::
- >>> root = XML("<root><c1><c2>0</c2><c2>1</c2></c1></root>")
- >>> second_c2 = root.c1.c2[1]
- >>> print(second_c2.text)
- 1
- Note that you cannot (and must not) instantiate this class or its
- subclasses.
- """
- def __iter__(self):
- """Iterate over self and all siblings with the same tag.
- """
- parent = self.getparent()
- if parent is None:
- return iter([self])
- return etree.ElementChildIterator(parent, tag=self.tag)
- def __str__(self):
- if __RECURSIVE_STR:
- return _dump(self, 0)
- else:
- return textOf(self._c_node) or ''
- # pickle support for objectified Element
- def __reduce__(self):
- return fromstring, (etree.tostring(self),)
- @property
- def text(self):
- return textOf(self._c_node)
- @property
- def __dict__(self):
- """A fake implementation for __dict__ to support dir() etc.
- Note that this only considers the first child with a given name.
- """
- cdef _Element child
- cdef dict children
- c_ns = tree._getNs(self._c_node)
- tag = "{%s}*" % pyunicode(c_ns) if c_ns is not NULL else None
- children = {}
- for child in etree.ElementChildIterator(self, tag=tag):
- if c_ns is NULL and tree._getNs(child._c_node) is not NULL:
- continue
- name = pyunicode(child._c_node.name)
- if name not in children:
- children[name] = child
- return children
- def __len__(self):
- """Count self and siblings with the same tag.
- """
- return _countSiblings(self._c_node)
- def countchildren(self):
- """countchildren(self)
- Return the number of children of this element, regardless of their
- name.
- """
- # copied from etree
- cdef Py_ssize_t c
- cdef tree.xmlNode* c_node
- c = 0
- c_node = self._c_node.children
- while c_node is not NULL:
- if tree._isElement(c_node):
- c += 1
- c_node = c_node.next
- return c
- def getchildren(self):
- """getchildren(self)
- Returns a sequence of all direct children. The elements are
- returned in document order.
- """
- cdef tree.xmlNode* c_node
- result = []
- c_node = self._c_node.children
- while c_node is not NULL:
- if tree._isElement(c_node):
- result.append(cetree.elementFactory(self._doc, c_node))
- c_node = c_node.next
- return result
- def __getattr__(self, tag):
- """Return the (first) child with the given tag name. If no namespace
- is provided, the child will be looked up in the same one as self.
- """
- return _lookupChildOrRaise(self, tag)
- def __setattr__(self, tag, value):
- """Set the value of the (first) child with the given tag name. If no
- namespace is provided, the child will be looked up in the same one as
- self.
- """
- cdef _Element element
- # properties are looked up /after/ __setattr__, so we must emulate them
- if tag == 'text' or tag == 'pyval':
- # read-only !
- raise TypeError, f"attribute '{tag}' of '{_typename(self)}' objects is not writable"
- elif tag == 'tail':
- cetree.setTailText(self._c_node, value)
- return
- elif tag == 'tag':
- ElementBase.tag.__set__(self, value)
- return
- elif tag == 'base':
- ElementBase.base.__set__(self, value)
- return
- tag = _buildChildTag(self, tag)
- element = _lookupChild(self, tag)
- if element is None:
- _appendValue(self, tag, value)
- else:
- _replaceElement(element, value)
- def __delattr__(self, tag):
- child = _lookupChildOrRaise(self, tag)
- self.remove(child)
- def addattr(self, tag, value):
- """addattr(self, tag, value)
- Add a child value to the element.
- As opposed to append(), it sets a data value, not an element.
- """
- _appendValue(self, _buildChildTag(self, tag), value)
- def __getitem__(self, key):
- """Return a sibling, counting from the first child of the parent. The
- method behaves like both a dict and a sequence.
- * If argument is an integer, returns the sibling at that position.
- * If argument is a string, does the same as getattr(). This can be
- used to provide namespaces for element lookup, or to look up
- children with special names (``text`` etc.).
- * If argument is a slice object, returns the matching slice.
- """
- cdef tree.xmlNode* c_self_node
- cdef tree.xmlNode* c_parent
- cdef tree.xmlNode* c_node
- cdef Py_ssize_t c_index
- if python._isString(key):
- return _lookupChildOrRaise(self, key)
- elif isinstance(key, slice):
- return list(self)[key]
- # normal item access
- c_index = key # raises TypeError if necessary
- c_self_node = self._c_node
- c_parent = c_self_node.parent
- if c_parent is NULL:
- if c_index == 0 or c_index == -1:
- return self
- raise IndexError, unicode(key)
- if c_index < 0:
- c_node = c_parent.last
- else:
- c_node = c_parent.children
- c_node = _findFollowingSibling(
- c_node, tree._getNs(c_self_node), c_self_node.name, c_index)
- if c_node is NULL:
- raise IndexError, unicode(key)
- return elementFactory(self._doc, c_node)
- def __setitem__(self, key, value):
- """Set the value of a sibling, counting from the first child of the
- parent. Implements key assignment, item assignment and slice
- assignment.
- * If argument is an integer, sets the sibling at that position.
- * If argument is a string, does the same as setattr(). This is used
- to provide namespaces for element lookup.
- * If argument is a sequence (list, tuple, etc.), assign the contained
- items to the siblings.
- """
- cdef _Element element
- cdef tree.xmlNode* c_node
- if python._isString(key):
- key = _buildChildTag(self, key)
- element = _lookupChild(self, key)
- if element is None:
- _appendValue(self, key, value)
- else:
- _replaceElement(element, value)
- return
- if self._c_node.parent is NULL:
- # the 'root[i] = ...' case
- raise TypeError, "assignment to root element is invalid"
- if isinstance(key, slice):
- # slice assignment
- _setSlice(key, self, value)
- else:
- # normal index assignment
- if key < 0:
- c_node = self._c_node.parent.last
- else:
- c_node = self._c_node.parent.children
- c_node = _findFollowingSibling(
- c_node, tree._getNs(self._c_node), self._c_node.name, key)
- if c_node is NULL:
- raise IndexError, unicode(key)
- element = elementFactory(self._doc, c_node)
- _replaceElement(element, value)
- def __delitem__(self, key):
- parent = self.getparent()
- if parent is None:
- raise TypeError, "deleting items not supported by root element"
- if isinstance(key, slice):
- # slice deletion
- del_items = list(self)[key]
- remove = parent.remove
- for el in del_items:
- remove(el)
- else:
- # normal index deletion
- sibling = self.__getitem__(key)
- parent.remove(sibling)
- def descendantpaths(self, prefix=None):
- """descendantpaths(self, prefix=None)
- Returns a list of object path expressions for all descendants.
- """
- if prefix is not None and not python._isString(prefix):
- prefix = '.'.join(prefix)
- return _build_descendant_paths(self._c_node, prefix)
- cdef inline bint _tagMatches(tree.xmlNode* c_node, const_xmlChar* c_href, const_xmlChar* c_name):
- if c_node.name != c_name:
- return 0
- if c_href == NULL:
- return 1
- c_node_href = tree._getNs(c_node)
- if c_node_href == NULL:
- return c_href[0] == c'\0'
- return tree.xmlStrcmp(c_node_href, c_href) == 0
- cdef Py_ssize_t _countSiblings(tree.xmlNode* c_start_node):
- cdef tree.xmlNode* c_node
- cdef Py_ssize_t count
- c_tag = c_start_node.name
- c_href = tree._getNs(c_start_node)
- count = 1
- c_node = c_start_node.next
- while c_node is not NULL:
- if c_node.type == tree.XML_ELEMENT_NODE and \
- _tagMatches(c_node, c_href, c_tag):
- count += 1
- c_node = c_node.next
- c_node = c_start_node.prev
- while c_node is not NULL:
- if c_node.type == tree.XML_ELEMENT_NODE and \
- _tagMatches(c_node, c_href, c_tag):
- count += 1
- c_node = c_node.prev
- return count
- cdef tree.xmlNode* _findFollowingSibling(tree.xmlNode* c_node,
- const_xmlChar* href, const_xmlChar* name,
- Py_ssize_t index):
- cdef tree.xmlNode* (*next)(tree.xmlNode*)
- if index >= 0:
- next = cetree.nextElement
- else:
- index = -1 - index
- next = cetree.previousElement
- while c_node is not NULL:
- if c_node.type == tree.XML_ELEMENT_NODE and \
- _tagMatches(c_node, href, name):
- index = index - 1
- if index < 0:
- return c_node
- c_node = next(c_node)
- return NULL
- cdef object _lookupChild(_Element parent, tag):
- cdef tree.xmlNode* c_result
- cdef tree.xmlNode* c_node
- c_node = parent._c_node
- ns, tag = cetree.getNsTagWithEmptyNs(tag)
- c_tag_len = len(<bytes> tag)
- if c_tag_len > limits.INT_MAX:
- return None
- c_tag = tree.xmlDictExists(
- c_node.doc.dict, _xcstr(tag), <int> c_tag_len)
- if c_tag is NULL:
- return None # not in the hash map => not in the tree
- if ns is None:
- # either inherit ns from parent or use empty (i.e. no) namespace
- c_href = tree._getNs(c_node) or <const_xmlChar*>''
- else:
- c_href = _xcstr(ns)
- c_result = _findFollowingSibling(c_node.children, c_href, c_tag, 0)
- if c_result is NULL:
- return None
- return elementFactory(parent._doc, c_result)
- cdef object _lookupChildOrRaise(_Element parent, tag):
- element = _lookupChild(parent, tag)
- if element is None:
- raise AttributeError, "no such child: " + _buildChildTag(parent, tag)
- return element
- cdef object _buildChildTag(_Element parent, tag):
- ns, tag = cetree.getNsTag(tag)
- c_tag = _xcstr(tag)
- c_href = tree._getNs(parent._c_node) if ns is None else _xcstr(ns)
- return cetree.namespacedNameFromNsName(c_href, c_tag)
- cdef _replaceElement(_Element element, value):
- cdef _Element new_element
- if isinstance(value, _Element):
- # deep copy the new element
- new_element = cetree.deepcopyNodeToDocument(
- element._doc, (<_Element>value)._c_node)
- new_element.tag = element.tag
- elif isinstance(value, (list, tuple)):
- element[:] = value
- return
- else:
- new_element = element.makeelement(element.tag)
- _setElementValue(new_element, value)
- element.getparent().replace(element, new_element)
- cdef _appendValue(_Element parent, tag, value):
- cdef _Element new_element
- if isinstance(value, _Element):
- # deep copy the new element
- new_element = cetree.deepcopyNodeToDocument(
- parent._doc, (<_Element>value)._c_node)
- new_element.tag = tag
- cetree.appendChildToElement(parent, new_element)
- elif isinstance(value, (list, tuple)):
- for item in value:
- _appendValue(parent, tag, item)
- else:
- new_element = cetree.makeElement(
- tag, parent._doc, None, None, None, None, None)
- _setElementValue(new_element, value)
- cetree.appendChildToElement(parent, new_element)
- cdef _setElementValue(_Element element, value):
- if value is None:
- cetree.setAttributeValue(
- element, XML_SCHEMA_INSTANCE_NIL_ATTR, "true")
- elif isinstance(value, _Element):
- _replaceElement(element, value)
- return
- else:
- cetree.delAttributeFromNsName(
- element._c_node, _XML_SCHEMA_INSTANCE_NS, <unsigned char*>"nil")
- if python._isString(value):
- pytype_name = "str"
- py_type = <PyType>_PYTYPE_DICT.get(pytype_name)
- else:
- pytype_name = _typename(value)
- py_type = <PyType>_PYTYPE_DICT.get(pytype_name)
- if py_type is not None:
- value = py_type.stringify(value)
- else:
- value = unicode(value)
- if py_type is not None:
- cetree.setAttributeValue(element, PYTYPE_ATTRIBUTE, pytype_name)
- else:
- cetree.delAttributeFromNsName(
- element._c_node, _PYTYPE_NAMESPACE, _PYTYPE_ATTRIBUTE_NAME)
- cetree.setNodeText(element._c_node, value)
- cdef _setSlice(sliceobject, _Element target, items):
- cdef _Element parent
- cdef tree.xmlNode* c_node
- cdef Py_ssize_t c_step, c_start, pos
- # collect existing slice
- if (<slice>sliceobject).step is None:
- c_step = 1
- else:
- c_step = (<slice>sliceobject).step
- if c_step == 0:
- raise ValueError, "Invalid slice"
- cdef list del_items = target[sliceobject]
- # collect new values
- new_items = []
- tag = target.tag
- for item in items:
- if isinstance(item, _Element):
- # deep copy the new element
- new_element = cetree.deepcopyNodeToDocument(
- target._doc, (<_Element>item)._c_node)
- new_element.tag = tag
- else:
- new_element = cetree.makeElement(
- tag, target._doc, None, None, None, None, None)
- _setElementValue(new_element, item)
- new_items.append(new_element)
- # sanity check - raise what a list would raise
- if c_step != 1 and len(del_items) != len(new_items):
- raise ValueError, \
- f"attempt to assign sequence of size {len(new_items)} to extended slice of size {len(del_items)}"
- # replace existing items
- pos = 0
- parent = target.getparent()
- replace = parent.replace
- while pos < len(new_items) and pos < len(del_items):
- replace(del_items[pos], new_items[pos])
- pos += 1
- # remove leftover items
- if pos < len(del_items):
- remove = parent.remove
- while pos < len(del_items):
- remove(del_items[pos])
- pos += 1
- # append remaining new items
- if pos < len(new_items):
- # the sanity check above guarantees (step == 1)
- if pos > 0:
- item = new_items[pos-1]
- else:
- if (<slice>sliceobject).start > 0:
- c_node = parent._c_node.children
- else:
- c_node = parent._c_node.last
- c_node = _findFollowingSibling(
- c_node, tree._getNs(target._c_node), target._c_node.name,
- (<slice>sliceobject).start - 1)
- if c_node is NULL:
- while pos < len(new_items):
- cetree.appendChildToElement(parent, new_items[pos])
- pos += 1
- return
- item = cetree.elementFactory(parent._doc, c_node)
- while pos < len(new_items):
- add = item.addnext
- item = new_items[pos]
- add(item)
- pos += 1
- ################################################################################
- # Data type support in subclasses
- cdef class ObjectifiedDataElement(ObjectifiedElement):
- """This is the base class for all data type Elements. Subclasses should
- override the 'pyval' property and possibly the __str__ method.
- """
- @property
- def pyval(self):
- return textOf(self._c_node)
- def __str__(self):
- return textOf(self._c_node) or ''
- def __repr__(self):
- return textOf(self._c_node) or ''
- def _setText(self, s):
- """For use in subclasses only. Don't use unless you know what you are
- doing.
- """
- cetree.setNodeText(self._c_node, s)
- cdef class NumberElement(ObjectifiedDataElement):
- cdef object _parse_value
- def _setValueParser(self, function):
- """Set the function that parses the Python value from a string.
- Do not use this unless you know what you are doing.
- """
- self._parse_value = function
- @property
- def pyval(self):
- return _parseNumber(self)
- def __int__(self):
- return int(_parseNumber(self))
- def __float__(self):
- return float(_parseNumber(self))
- def __complex__(self):
- return complex(_parseNumber(self))
- def __str__(self):
- return unicode(_parseNumber(self))
- def __repr__(self):
- return repr(_parseNumber(self))
- def __oct__(self):
- return oct(_parseNumber(self))
- def __hex__(self):
- return hex(_parseNumber(self))
- def __richcmp__(self, other, int op):
- return _richcmpPyvals(self, other, op)
- def __hash__(self):
- return hash(_parseNumber(self))
- def __add__(self, other):
- return _numericValueOf(self) + _numericValueOf(other)
- def __radd__(self, other):
- return _numericValueOf(other) + _numericValueOf(self)
- def __sub__(self, other):
- return _numericValueOf(self) - _numericValueOf(other)
- def __rsub__(self, other):
- return _numericValueOf(other) - _numericValueOf(self)
- def __mul__(self, other):
- return _numericValueOf(self) * _numericValueOf(other)
- def __rmul__(self, other):
- return _numericValueOf(other) * _numericValueOf(self)
- def __div__(self, other):
- return _numericValueOf(self) / _numericValueOf(other)
- def __rdiv__(self, other):
- return _numericValueOf(other) / _numericValueOf(self)
- def __truediv__(self, other):
- return _numericValueOf(self) / _numericValueOf(other)
- def __rtruediv__(self, other):
- return _numericValueOf(other) / _numericValueOf(self)
- def __floordiv__(self, other):
- return _numericValueOf(self) // _numericValueOf(other)
- def __rfloordiv__(self, other):
- return _numericValueOf(other) // _numericValueOf(self)
- def __mod__(self, other):
- return _numericValueOf(self) % _numericValueOf(other)
- def __rmod__(self, other):
- return _numericValueOf(other) % _numericValueOf(self)
- def __divmod__(self, other):
- return divmod(_numericValueOf(self), _numericValueOf(other))
- def __rdivmod__(self, other):
- return divmod(_numericValueOf(other), _numericValueOf(self))
- def __pow__(self, other, modulo):
- if modulo is None:
- return _numericValueOf(self) ** _numericValueOf(other)
- else:
- return pow(_numericValueOf(self), _numericValueOf(other), modulo)
- def __rpow__(self, other, modulo):
- if modulo is None:
- return _numericValueOf(other) ** _numericValueOf(self)
- else:
- return pow(_numericValueOf(other), _numericValueOf(self), modulo)
- def __neg__(self):
- return - _numericValueOf(self)
- def __pos__(self):
- return + _numericValueOf(self)
- def __abs__(self):
- return abs( _numericValueOf(self) )
- def __bool__(self):
- return bool(_numericValueOf(self))
- def __invert__(self):
- return ~ _numericValueOf(self)
- def __lshift__(self, other):
- return _numericValueOf(self) << _numericValueOf(other)
- def __rlshift__(self, other):
- return _numericValueOf(other) << _numericValueOf(self)
- def __rshift__(self, other):
- return _numericValueOf(self) >> _numericValueOf(other)
- def __rrshift__(self, other):
- return _numericValueOf(other) >> _numericValueOf(self)
- def __and__(self, other):
- return _numericValueOf(self) & _numericValueOf(other)
- def __rand__(self, other):
- return _numericValueOf(other) & _numericValueOf(self)
- def __or__(self, other):
- return _numericValueOf(self) | _numericValueOf(other)
- def __ror__(self, other):
- return _numericValueOf(other) | _numericValueOf(self)
- def __xor__(self, other):
- return _numericValueOf(self) ^ _numericValueOf(other)
- def __rxor__(self, other):
- return _numericValueOf(other) ^ _numericValueOf(self)
- cdef class IntElement(NumberElement):
- def _init(self):
- self._parse_value = int
- def __index__(self):
- return int(_parseNumber(self))
- cdef class FloatElement(NumberElement):
- def _init(self):
- self._parse_value = float
- cdef class StringElement(ObjectifiedDataElement):
- """String data class.
- Note that this class does *not* support the sequence protocol of strings:
- len(), iter(), str_attr[0], str_attr[0:1], etc. are *not* supported.
- Instead, use the .text attribute to get a 'real' string.
- """
- @property
- def pyval(self):
- return textOf(self._c_node) or ''
- def __repr__(self):
- return repr(textOf(self._c_node) or '')
- def strlen(self):
- text = textOf(self._c_node)
- if text is None:
- return 0
- else:
- return len(text)
- def __bool__(self):
- return bool(textOf(self._c_node))
- def __richcmp__(self, other, int op):
- return _richcmpPyvals(self, other, op)
- def __hash__(self):
- return hash(textOf(self._c_node) or '')
- def __add__(self, other):
- text = _strValueOf(self)
- other = _strValueOf(other)
- return text + other
- def __radd__(self, other):
- text = _strValueOf(self)
- other = _strValueOf(other)
- return other + text
- def __mul__(self, other):
- if isinstance(self, StringElement):
- return (textOf((<StringElement>self)._c_node) or '') * _numericValueOf(other)
- elif isinstance(other, StringElement):
- return _numericValueOf(self) * (textOf((<StringElement>other)._c_node) or '')
- else:
- return NotImplemented
- def __rmul__(self, other):
- return _numericValueOf(other) * (textOf((<StringElement>self)._c_node) or '')
- def __mod__(self, other):
- return (_strValueOf(self) or '') % other
- def __int__(self):
- return int(textOf(self._c_node))
- def __float__(self):
- return float(textOf(self._c_node))
- def __complex__(self):
- return complex(textOf(self._c_node))
- cdef class NoneElement(ObjectifiedDataElement):
- def __str__(self):
- return "None"
- def __repr__(self):
- return "None"
- def __bool__(self):
- return False
- def __richcmp__(self, other, int op):
- if other is None or self is None:
- return python.PyObject_RichCompare(None, None, op)
- if isinstance(self, NoneElement):
- return python.PyObject_RichCompare(None, other, op)
- else:
- return python.PyObject_RichCompare(self, None, op)
- def __hash__(self):
- return hash(None)
- @property
- def pyval(self):
- return None
- cdef class BoolElement(IntElement):
- """Boolean type base on string values: 'true' or 'false'.
- Note that this inherits from IntElement to mimic the behaviour of
- Python's bool type.
- """
- def _init(self):
- self._parse_value = _parseBool # wraps as Python callable
- def __bool__(self):
- return _parseBool(textOf(self._c_node))
- def __int__(self):
- return 0 + _parseBool(textOf(self._c_node))
- def __float__(self):
- return 0.0 + _parseBool(textOf(self._c_node))
- def __richcmp__(self, other, int op):
- return _richcmpPyvals(self, other, op)
- def __hash__(self):
- return hash(_parseBool(textOf(self._c_node)))
- def __str__(self):
- return unicode(_parseBool(textOf(self._c_node)))
- def __repr__(self):
- return repr(_parseBool(textOf(self._c_node)))
- @property
- def pyval(self):
- return _parseBool(textOf(self._c_node))
- cdef _checkBool(s):
- cdef int value = -1
- if s is not None:
- value = __parseBoolAsInt(s)
- if value == -1:
- raise ValueError
- cdef bint _parseBool(s) except -1:
- cdef int value
- if s is None:
- return False
- value = __parseBoolAsInt(s)
- if value == -1:
- raise ValueError, f"Invalid boolean value: '{s}'"
- return value
- cdef inline int __parseBoolAsInt(text) except -2:
- if text == 'false':
- return 0
- elif text == 'true':
- return 1
- elif text == '0':
- return 0
- elif text == '1':
- return 1
- return -1
- cdef object _parseNumber(NumberElement element):
- return element._parse_value(textOf(element._c_node))
- cdef enum NumberParserState:
- NPS_SPACE_PRE = 0
- NPS_SIGN = 1
- NPS_DIGITS = 2
- NPS_POINT_LEAD = 3
- NPS_POINT = 4
- NPS_FRACTION = 5
- NPS_EXP = 6
- NPS_EXP_SIGN = 7
- NPS_DIGITS_EXP = 8
- NPS_SPACE_TAIL = 9
- NPS_INF1 = 20
- NPS_INF2 = 21
- NPS_INF3 = 22
- NPS_NAN1 = 23
- NPS_NAN2 = 24
- NPS_NAN3 = 25
- NPS_ERROR = 99
- ctypedef fused bytes_unicode:
- bytes
- unicode
- cdef _checkNumber(bytes_unicode s, bint allow_float):
- cdef Py_UCS4 c
- cdef NumberParserState state = NPS_SPACE_PRE
- for c in s:
- if c in '0123456789':
- if state in (NPS_DIGITS, NPS_FRACTION, NPS_DIGITS_EXP):
- pass
- elif state in (NPS_SPACE_PRE, NPS_SIGN):
- state = NPS_DIGITS
- elif state in (NPS_POINT_LEAD, NPS_POINT):
- state = NPS_FRACTION
- elif state in (NPS_EXP, NPS_EXP_SIGN):
- state = NPS_DIGITS_EXP
- else:
- state = NPS_ERROR
- else:
- if c == '.':
- if state in (NPS_SPACE_PRE, NPS_SIGN):
- state = NPS_POINT_LEAD
- elif state == NPS_DIGITS:
- state = NPS_POINT
- else:
- state = NPS_ERROR
- if not allow_float:
- state = NPS_ERROR
- elif c in '-+':
- if state == NPS_SPACE_PRE:
- state = NPS_SIGN
- elif state == NPS_EXP:
- state = NPS_EXP_SIGN
- else:
- state = NPS_ERROR
- elif c == 'E':
- if state in (NPS_DIGITS, NPS_POINT, NPS_FRACTION):
- state = NPS_EXP
- else:
- state = NPS_ERROR
- if not allow_float:
- state = NPS_ERROR
- # Allow INF and NaN. XMLSchema requires case, we don't, like Python.
- elif c in 'iI':
- state = NPS_INF1 if allow_float and state in (NPS_SPACE_PRE, NPS_SIGN) else NPS_ERROR
- elif c in 'fF':
- state = NPS_INF3 if state == NPS_INF2 else NPS_ERROR
- elif c in 'aA':
- state = NPS_NAN2 if state == NPS_NAN1 else NPS_ERROR
- elif c in 'nN':
- # Python also allows [+-]NaN, so let's accept that.
- if state in (NPS_SPACE_PRE, NPS_SIGN):
- state = NPS_NAN1 if allow_float else NPS_ERROR
- elif state == NPS_NAN2:
- state = NPS_NAN3
- elif state == NPS_INF1:
- state = NPS_INF2
- else:
- state = NPS_ERROR
- # Allow spaces around text values.
- else:
- if c.isspace() if (bytes_unicode is unicode) else c in b'\x09\x0a\x0b\x0c\x0d\x20':
- if state in (NPS_SPACE_PRE, NPS_SPACE_TAIL):
- pass
- elif state in (NPS_DIGITS, NPS_POINT, NPS_FRACTION, NPS_DIGITS_EXP, NPS_INF3, NPS_NAN3):
- state = NPS_SPACE_TAIL
- else:
- state = NPS_ERROR
- else:
- state = NPS_ERROR
- if state == NPS_ERROR:
- break
- if state not in (NPS_DIGITS, NPS_FRACTION, NPS_POINT, NPS_DIGITS_EXP, NPS_INF3, NPS_NAN3, NPS_SPACE_TAIL):
- raise ValueError
- cdef _checkInt(s):
- return _checkNumber(<unicode>s, allow_float=False)
- cdef _checkFloat(s):
- return _checkNumber(<unicode>s, allow_float=True)
- cdef object _strValueOf(obj):
- if python._isString(obj):
- return obj
- if isinstance(obj, _Element):
- return textOf((<_Element>obj)._c_node) or ''
- if obj is None:
- return ''
- return unicode(obj)
- cdef object _numericValueOf(obj):
- if isinstance(obj, NumberElement):
- return _parseNumber(<NumberElement>obj)
- try:
- # not always numeric, but Python will raise the right exception
- return obj.pyval
- except AttributeError:
- pass
- return obj
- cdef _richcmpPyvals(left, right, int op):
- left = getattr(left, 'pyval', left)
- right = getattr(right, 'pyval', right)
- return python.PyObject_RichCompare(left, right, op)
- ################################################################################
- # Python type registry
- cdef class PyType:
- """PyType(self, name, type_check, type_class, stringify=None)
- User defined type.
- Named type that contains a type check function, a type class that
- inherits from ObjectifiedDataElement and an optional "stringification"
- function. The type check must take a string as argument and raise
- ValueError or TypeError if it cannot handle the string value. It may be
- None in which case it is not considered for type guessing. For registered
- named types, the 'stringify' function (or unicode() if None) is used to
- convert a Python object with type name 'name' to the string representation
- stored in the XML tree.
- Example::
- PyType('int', int, MyIntClass).register()
- Note that the order in which types are registered matters. The first
- matching type will be used.
- """
- cdef readonly object name
- cdef readonly object type_check
- cdef readonly object stringify
- cdef object _type
- cdef list _schema_types
- def __init__(self, name, type_check, type_class, stringify=None):
- if isinstance(name, bytes):
- name = (<bytes>name).decode('ascii')
- elif not isinstance(name, unicode):
- raise TypeError, "Type name must be a string"
- if type_check is not None and not callable(type_check):
- raise TypeError, "Type check function must be callable (or None)"
- if name != TREE_PYTYPE_NAME and \
- not issubclass(type_class, ObjectifiedDataElement):
- raise TypeError, \
- "Data classes must inherit from ObjectifiedDataElement"
- self.name = name
- self._type = type_class
- self.type_check = type_check
- if stringify is None:
- stringify = unicode
- self.stringify = stringify
- self._schema_types = []
- def __repr__(self):
- return "PyType(%s, %s)" % (self.name, self._type.__name__)
- def register(self, before=None, after=None):
- """register(self, before=None, after=None)
- Register the type.
- The additional keyword arguments 'before' and 'after' accept a
- sequence of type names that must appear before/after the new type in
- the type list. If any of them is not currently known, it is simply
- ignored. Raises ValueError if the dependencies cannot be fulfilled.
- """
- if self.name == TREE_PYTYPE_NAME:
- raise ValueError, "Cannot register tree type"
- if self.type_check is not None:
- for item in _TYPE_CHECKS:
- if item[0] is self.type_check:
- _TYPE_CHECKS.remove(item)
- break
- entry = (self.type_check, self)
- first_pos = 0
- last_pos = -1
- if before or after:
- if before is None:
- before = ()
- elif after is None:
- after = ()
- for i, (check, pytype) in enumerate(_TYPE_CHECKS):
- if last_pos == -1 and pytype.name in before:
- last_pos = i
- if pytype.name in after:
- first_pos = i+1
- if last_pos == -1:
- _TYPE_CHECKS.append(entry)
- elif first_pos > last_pos:
- raise ValueError, "inconsistent before/after dependencies"
- else:
- _TYPE_CHECKS.insert(last_pos, entry)
- _PYTYPE_DICT[self.name] = self
- for xs_type in self._schema_types:
- _SCHEMA_TYPE_DICT[xs_type] = self
- def unregister(self):
- "unregister(self)"
- if _PYTYPE_DICT.get(self.name) is self:
- del _PYTYPE_DICT[self.name]
- for xs_type, pytype in list(_SCHEMA_TYPE_DICT.items()):
- if pytype is self:
- del _SCHEMA_TYPE_DICT[xs_type]
- if self.type_check is None:
- return
- try:
- _TYPE_CHECKS.remove( (self.type_check, self) )
- except ValueError:
- pass
- property xmlSchemaTypes:
- """The list of XML Schema datatypes this Python type maps to.
- Note that this must be set before registering the type!
- """
- def __get__(self):
- return self._schema_types
- def __set__(self, types):
- self._schema_types = list(map(unicode, types))
- cdef dict _PYTYPE_DICT = {}
- cdef dict _SCHEMA_TYPE_DICT = {}
- cdef list _TYPE_CHECKS = []
- cdef unicode _xml_bool(value):
- return "true" if value else "false"
- cdef unicode _xml_float(value):
- if _float_is_inf(value):
- if value > 0:
- return "INF"
- return "-INF"
- if _float_is_nan(value):
- return "NaN"
- return unicode(repr(value))
- cdef _pytypename(obj):
- return "str" if python._isString(obj) else _typename(obj)
- def pytypename(obj):
- """pytypename(obj)
- Find the name of the corresponding PyType for a Python object.
- """
- return _pytypename(obj)
- cdef _registerPyTypes():
- pytype = PyType('int', _checkInt, IntElement) # wraps functions for Python
- pytype.xmlSchemaTypes = ("integer", "int", "short", "byte", "unsignedShort",
- "unsignedByte", "nonPositiveInteger",
- "negativeInteger", "long", "nonNegativeInteger",
- "unsignedLong", "unsignedInt", "positiveInteger",)
- pytype.register()
- # 'long' type just for backwards compatibility
- pytype = PyType('long', None, IntElement)
- pytype.register()
- pytype = PyType('float', _checkFloat, FloatElement, _xml_float) # wraps functions for Python
- pytype.xmlSchemaTypes = ("double", "float")
- pytype.register()
- pytype = PyType('bool', _checkBool, BoolElement, _xml_bool) # wraps functions for Python
- pytype.xmlSchemaTypes = ("boolean",)
- pytype.register()
- pytype = PyType('str', None, StringElement)
- pytype.xmlSchemaTypes = ("string", "normalizedString", "token", "language",
- "Name", "NCName", "ID", "IDREF", "ENTITY",
- "NMTOKEN", )
- pytype.register()
- # since lxml 2.0
- pytype = PyType('NoneType', None, NoneElement)
- pytype.register()
- # backwards compatibility
- pytype = PyType('none', None, NoneElement)
- pytype.register()
- # non-registered PyType for inner tree elements
- cdef PyType TREE_PYTYPE = PyType(TREE_PYTYPE_NAME, None, ObjectifiedElement)
- _registerPyTypes()
- def getRegisteredTypes():
- """getRegisteredTypes()
- Returns a list of the currently registered PyType objects.
- To add a new type, retrieve this list and call unregister() for all
- entries. Then add the new type at a suitable position (possibly replacing
- an existing one) and call register() for all entries.
- This is necessary if the new type interferes with the type check functions
- of existing ones (normally only int/float/bool) and must the tried before
- other types. To add a type that is not yet parsable by the current type
- check functions, you can simply register() it, which will append it to the
- end of the type list.
- """
- cdef list types = []
- cdef set known = set()
- for check, pytype in _TYPE_CHECKS:
- name = pytype.name
- if name not in known:
- known.add(name)
- types.append(pytype)
- for pytype in _PYTYPE_DICT.values():
- name = pytype.name
- if name not in known:
- known.add(name)
- types.append(pytype)
- return types
- cdef PyType _guessPyType(value, PyType defaulttype):
- if value is None:
- return None
- for type_check, tested_pytype in _TYPE_CHECKS:
- try:
- type_check(value)
- return <PyType>tested_pytype
- except IGNORABLE_ERRORS:
- # could not be parsed as the specified type => ignore
- pass
- return defaulttype
- cdef object _guessElementClass(tree.xmlNode* c_node):
- value = textOf(c_node)
- if value is None:
- return None
- if value == '':
- return StringElement
- for type_check, pytype in _TYPE_CHECKS:
- try:
- type_check(value)
- return (<PyType>pytype)._type
- except IGNORABLE_ERRORS:
- pass
- return None
- ################################################################################
- # adapted ElementMaker supports registered PyTypes
- @cython.final
- @cython.internal
- cdef class _ObjectifyElementMakerCaller:
- cdef object _tag
- cdef object _nsmap
- cdef object _element_factory
- cdef bint _annotate
- def __call__(self, *children, **attrib):
- "__call__(self, *children, **attrib)"
- cdef _ObjectifyElementMakerCaller elementMaker
- cdef _Element element
- cdef _Element childElement
- cdef bint has_children
- cdef bint has_string_value
- if self._element_factory is None:
- element = _makeElement(self._tag, None, attrib, self._nsmap)
- else:
- element = self._element_factory(self._tag, attrib, self._nsmap)
- pytype_name = None
- has_children = False
- has_string_value = False
- for child in children:
- if child is None:
- if len(children) == 1:
- cetree.setAttributeValue(
- element, XML_SCHEMA_INSTANCE_NIL_ATTR, "true")
- elif python._isString(child):
- _add_text(element, child)
- has_string_value = True
- elif isinstance(child, _Element):
- cetree.appendChildToElement(element, <_Element>child)
- has_children = True
- elif isinstance(child, _ObjectifyElementMakerCaller):
- elementMaker = <_ObjectifyElementMakerCaller>child
- if elementMaker._element_factory is None:
- cetree.makeSubElement(element, elementMaker._tag,
- None, None, None, None)
- else:
- childElement = elementMaker._element_factory(
- elementMaker._tag)
- cetree.appendChildToElement(element, childElement)
- has_children = True
- elif isinstance(child, dict):
- for name, value in child.items():
- # keyword arguments in attrib take precedence
- if name in attrib:
- continue
- pytype = _PYTYPE_DICT.get(_typename(value))
- if pytype is not None:
- value = (<PyType>pytype).stringify(value)
- elif not python._isString(value):
- value = unicode(value)
- cetree.setAttributeValue(element, name, value)
- else:
- if pytype_name is not None:
- # concatenation always makes the result a string
- has_string_value = True
- pytype_name = _typename(child)
- pytype = _PYTYPE_DICT.get(_typename(child))
- if pytype is not None:
- _add_text(element, (<PyType>pytype).stringify(child))
- else:
- has_string_value = True
- child = unicode(child)
- _add_text(element, child)
- if self._annotate and not has_children:
- if has_string_value:
- cetree.setAttributeValue(element, PYTYPE_ATTRIBUTE, "str")
- elif pytype_name is not None:
- cetree.setAttributeValue(element, PYTYPE_ATTRIBUTE, pytype_name)
- return element
- cdef _add_text(_Element elem, text):
- # add text to the tree in construction, either as element text or
- # tail text, depending on the current tree state
- cdef tree.xmlNode* c_child
- c_child = cetree.findChildBackwards(elem._c_node, 0)
- if c_child is not NULL:
- old = cetree.tailOf(c_child)
- if old is not None:
- text = old + text
- cetree.setTailText(c_child, text)
- else:
- old = cetree.textOf(elem._c_node)
- if old is not None:
- text = old + text
- cetree.setNodeText(elem._c_node, text)
- cdef class ElementMaker:
- """ElementMaker(self, namespace=None, nsmap=None, annotate=True, makeelement=None)
- An ElementMaker that can be used for constructing trees.
- Example::
- >>> M = ElementMaker(annotate=False)
- >>> attributes = {'class': 'par'}
- >>> html = M.html( M.body( M.p('hello', attributes, M.br, 'objectify', style="font-weight: bold") ) )
- >>> from lxml.etree import tostring
- >>> print(tostring(html, method='html').decode('ascii'))
- <html><body><p style="font-weight: bold" class="par">hello<br>objectify</p></body></html>
- To create tags that are not valid Python identifiers, call the factory
- directly and pass the tag name as first argument::
- >>> root = M('tricky-tag', 'some text')
- >>> print(root.tag)
- tricky-tag
- >>> print(root.text)
- some text
- Note that this module has a predefined ElementMaker instance called ``E``.
- """
- cdef object _makeelement
- cdef object _namespace
- cdef object _nsmap
- cdef bint _annotate
- cdef dict _cache
- def __init__(self, *, namespace=None, nsmap=None, annotate=True,
- makeelement=None):
- if nsmap is None:
- nsmap = _DEFAULT_NSMAP if annotate else {}
- self._nsmap = nsmap
- self._namespace = None if namespace is None else "{%s}" % namespace
- self._annotate = annotate
- if makeelement is not None:
- if not callable(makeelement):
- raise TypeError(
- f"argument of 'makeelement' parameter must be callable, got {type(makeelement)}")
- self._makeelement = makeelement
- else:
- self._makeelement = None
- self._cache = {}
- @cython.final
- cdef _build_element_maker(self, tag, bint caching):
- cdef _ObjectifyElementMakerCaller element_maker
- element_maker = _ObjectifyElementMakerCaller.__new__(_ObjectifyElementMakerCaller)
- if self._namespace is not None and tag[0] != "{":
- element_maker._tag = self._namespace + tag
- else:
- element_maker._tag = tag
- element_maker._nsmap = self._nsmap
- element_maker._annotate = self._annotate
- element_maker._element_factory = self._makeelement
- if caching:
- if len(self._cache) > 200:
- self._cache.clear()
- self._cache[tag] = element_maker
- return element_maker
- def __getattr__(self, tag):
- element_maker = self._cache.get(tag)
- if element_maker is None:
- return self._build_element_maker(tag, caching=True)
- return element_maker
- def __call__(self, tag, *args, **kwargs):
- element_maker = self._cache.get(tag)
- if element_maker is None:
- element_maker = self._build_element_maker(
- tag, caching=not is_special_method(tag))
- return element_maker(*args, **kwargs)
- ################################################################################
- # Recursive element dumping
- cdef bint __RECURSIVE_STR = 0 # default: off
- def enable_recursive_str(on=True):
- """enable_recursive_str(on=True)
- Enable a recursively generated tree representation for str(element),
- based on objectify.dump(element).
- """
- global __RECURSIVE_STR
- __RECURSIVE_STR = on
- def dump(_Element element not None):
- """dump(_Element element not None)
- Return a recursively generated string representation of an element.
- """
- return _dump(element, 0)
- cdef object _dump(_Element element, int indent):
- indentstr = " " * indent
- if isinstance(element, ObjectifiedDataElement):
- value = repr(element)
- else:
- value = textOf(element._c_node)
- if value is not None:
- if not value.strip():
- value = None
- else:
- value = repr(value)
- result = f"{indentstr}{element.tag} = {value} [{_typename(element)}]\n"
- xsi_ns = "{%s}" % XML_SCHEMA_INSTANCE_NS
- pytype_ns = "{%s}" % PYTYPE_NAMESPACE
- for name, value in sorted(cetree.iterattributes(element, 3)):
- if '{' in name:
- if name == PYTYPE_ATTRIBUTE:
- if value == TREE_PYTYPE_NAME:
- continue
- else:
- name = name.replace(pytype_ns, 'py:')
- name = name.replace(xsi_ns, 'xsi:')
- result += f"{indentstr} * {name} = {value!r}\n"
- indent += 1
- for child in element.iterchildren():
- result += _dump(child, indent)
- if indent == 1:
- return result[:-1] # strip last '\n'
- else:
- return result
- ################################################################################
- # Pickle support for objectified ElementTree
- def __unpickleElementTree(data):
- return etree.ElementTree(fromstring(data))
- cdef _setupPickle(elementTreeReduceFunction):
- import copyreg
- copyreg.pickle(etree._ElementTree,
- elementTreeReduceFunction, __unpickleElementTree)
- def pickleReduceElementTree(obj):
- return __unpickleElementTree, (etree.tostring(obj),)
- _setupPickle(pickleReduceElementTree)
- del pickleReduceElementTree
- ################################################################################
- # Element class lookup
- cdef class ObjectifyElementClassLookup(ElementClassLookup):
- """ObjectifyElementClassLookup(self, tree_class=None, empty_data_class=None)
- Element class lookup method that uses the objectify classes.
- """
- cdef object empty_data_class
- cdef object tree_class
- def __init__(self, tree_class=None, empty_data_class=None):
- """Lookup mechanism for objectify.
- The default Element classes can be replaced by passing subclasses of
- ObjectifiedElement and ObjectifiedDataElement as keyword arguments.
- 'tree_class' defines inner tree classes (defaults to
- ObjectifiedElement), 'empty_data_class' defines the default class for
- empty data elements (defaults to StringElement).
- """
- self._lookup_function = _lookupElementClass
- if tree_class is None:
- tree_class = ObjectifiedElement
- self.tree_class = tree_class
- if empty_data_class is None:
- empty_data_class = StringElement
- self.empty_data_class = empty_data_class
- cdef object _lookupElementClass(state, _Document doc, tree.xmlNode* c_node):
- cdef ObjectifyElementClassLookup lookup
- lookup = <ObjectifyElementClassLookup>state
- # if element has children => no data class
- if cetree.hasChild(c_node):
- return lookup.tree_class
- # if element is defined as xsi:nil, return NoneElement class
- if "true" == cetree.attributeValueFromNsName(
- c_node, _XML_SCHEMA_INSTANCE_NS, <unsigned char*>"nil"):
- return NoneElement
- # check for Python type hint
- value = cetree.attributeValueFromNsName(
- c_node, _PYTYPE_NAMESPACE, _PYTYPE_ATTRIBUTE_NAME)
- if value is not None:
- if value == TREE_PYTYPE_NAME:
- return lookup.tree_class
- py_type = <PyType>_PYTYPE_DICT.get(value)
- if py_type is not None:
- return py_type._type
- # unknown 'pyval' => try to figure it out ourself, just go on
- # check for XML Schema type hint
- value = cetree.attributeValueFromNsName(
- c_node, _XML_SCHEMA_INSTANCE_NS, <unsigned char*>"type")
- if value is not None:
- schema_type = <PyType>_SCHEMA_TYPE_DICT.get(value)
- if schema_type is None and ':' in value:
- prefix, value = value.split(':', 1)
- schema_type = <PyType>_SCHEMA_TYPE_DICT.get(value)
- if schema_type is not None:
- return schema_type._type
- # otherwise determine class based on text content type
- el_class = _guessElementClass(c_node)
- if el_class is not None:
- return el_class
- # if element is a root node => default to tree node
- if c_node.parent is NULL or not tree._isElement(c_node.parent):
- return lookup.tree_class
- return lookup.empty_data_class
- ################################################################################
- # Type annotations
- cdef PyType _check_type(tree.xmlNode* c_node, PyType pytype):
- if pytype is None:
- return None
- value = textOf(c_node)
- try:
- pytype.type_check(value)
- return pytype
- except IGNORABLE_ERRORS:
- # could not be parsed as the specified type => ignore
- pass
- return None
- def pyannotate(element_or_tree, *, ignore_old=False, ignore_xsi=False,
- empty_pytype=None):
- """pyannotate(element_or_tree, ignore_old=False, ignore_xsi=False, empty_pytype=None)
- Recursively annotates the elements of an XML tree with 'pytype'
- attributes.
- If the 'ignore_old' keyword argument is True (the default), current 'pytype'
- attributes will be ignored and replaced. Otherwise, they will be checked
- and only replaced if they no longer fit the current text value.
- Setting the keyword argument ``ignore_xsi`` to True makes the function
- additionally ignore existing ``xsi:type`` annotations. The default is to
- use them as a type hint.
- The default annotation of empty elements can be set with the
- ``empty_pytype`` keyword argument. The default is not to annotate empty
- elements. Pass 'str', for example, to make string values the default.
- """
- cdef _Element element
- element = cetree.rootNodeOrRaise(element_or_tree)
- _annotate(element, 0, 1, ignore_xsi, ignore_old, None, empty_pytype)
- def xsiannotate(element_or_tree, *, ignore_old=False, ignore_pytype=False,
- empty_type=None):
- """xsiannotate(element_or_tree, ignore_old=False, ignore_pytype=False, empty_type=None)
- Recursively annotates the elements of an XML tree with 'xsi:type'
- attributes.
- If the 'ignore_old' keyword argument is True (the default), current
- 'xsi:type' attributes will be ignored and replaced. Otherwise, they will be
- checked and only replaced if they no longer fit the current text value.
- Note that the mapping from Python types to XSI types is usually ambiguous.
- Currently, only the first XSI type name in the corresponding PyType
- definition will be used for annotation. Thus, you should consider naming
- the widest type first if you define additional types.
- Setting the keyword argument ``ignore_pytype`` to True makes the function
- additionally ignore existing ``pytype`` annotations. The default is to
- use them as a type hint.
- The default annotation of empty elements can be set with the
- ``empty_type`` keyword argument. The default is not to annotate empty
- elements. Pass 'string', for example, to make string values the default.
- """
- cdef _Element element
- element = cetree.rootNodeOrRaise(element_or_tree)
- _annotate(element, 1, 0, ignore_old, ignore_pytype, empty_type, None)
- def annotate(element_or_tree, *, ignore_old=True, ignore_xsi=False,
- empty_pytype=None, empty_type=None, annotate_xsi=0,
- annotate_pytype=1):
- """annotate(element_or_tree, ignore_old=True, ignore_xsi=False, empty_pytype=None, empty_type=None, annotate_xsi=0, annotate_pytype=1)
- Recursively annotates the elements of an XML tree with 'xsi:type'
- and/or 'py:pytype' attributes.
- If the 'ignore_old' keyword argument is True (the default), current
- 'py:pytype' attributes will be ignored for the type annotation. Set to False
- if you want reuse existing 'py:pytype' information (iff appropriate for the
- element text value).
- If the 'ignore_xsi' keyword argument is False (the default), existing
- 'xsi:type' attributes will be used for the type annotation, if they fit the
- element text values.
- Note that the mapping from Python types to XSI types is usually ambiguous.
- Currently, only the first XSI type name in the corresponding PyType
- definition will be used for annotation. Thus, you should consider naming
- the widest type first if you define additional types.
- The default 'py:pytype' annotation of empty elements can be set with the
- ``empty_pytype`` keyword argument. Pass 'str', for example, to make
- string values the default.
- The default 'xsi:type' annotation of empty elements can be set with the
- ``empty_type`` keyword argument. The default is not to annotate empty
- elements. Pass 'string', for example, to make string values the default.
- The keyword arguments 'annotate_xsi' (default: 0) and 'annotate_pytype'
- (default: 1) control which kind(s) of annotation to use.
- """
- cdef _Element element
- element = cetree.rootNodeOrRaise(element_or_tree)
- _annotate(element, annotate_xsi, annotate_pytype, ignore_xsi,
- ignore_old, empty_type, empty_pytype)
- cdef _annotate(_Element element, bint annotate_xsi, bint annotate_pytype,
- bint ignore_xsi, bint ignore_pytype,
- empty_type_name, empty_pytype_name):
- cdef _Document doc
- cdef tree.xmlNode* c_node
- cdef PyType empty_pytype, StrType, NoneType
- if not annotate_xsi and not annotate_pytype:
- return
- if empty_type_name is not None:
- if isinstance(empty_type_name, bytes):
- empty_type_name = (<bytes>empty_type_name).decode("ascii")
- empty_pytype = <PyType>_SCHEMA_TYPE_DICT.get(empty_type_name)
- elif empty_pytype_name is not None:
- if isinstance(empty_pytype_name, bytes):
- empty_pytype_name = (<bytes>empty_pytype_name).decode("ascii")
- empty_pytype = <PyType>_PYTYPE_DICT.get(empty_pytype_name)
- else:
- empty_pytype = None
- StrType = <PyType>_PYTYPE_DICT.get('str')
- NoneType = <PyType>_PYTYPE_DICT.get('NoneType')
- doc = element._doc
- c_node = element._c_node
- tree.BEGIN_FOR_EACH_ELEMENT_FROM(c_node, c_node, 1)
- if c_node.type == tree.XML_ELEMENT_NODE:
- _annotate_element(c_node, doc, annotate_xsi, annotate_pytype,
- ignore_xsi, ignore_pytype,
- empty_type_name, empty_pytype, StrType, NoneType)
- tree.END_FOR_EACH_ELEMENT_FROM(c_node)
- cdef int _annotate_element(tree.xmlNode* c_node, _Document doc,
- bint annotate_xsi, bint annotate_pytype,
- bint ignore_xsi, bint ignore_pytype,
- empty_type_name, PyType empty_pytype,
- PyType StrType, PyType NoneType) except -1:
- cdef tree.xmlNs* c_ns
- cdef PyType pytype = None
- typename = None
- istree = 0
- # if element is defined as xsi:nil, represent it as None
- if cetree.attributeValueFromNsName(
- c_node, _XML_SCHEMA_INSTANCE_NS, <unsigned char*>"nil") == "true":
- pytype = NoneType
- if pytype is None and not ignore_xsi:
- # check that old xsi type value is valid
- typename = cetree.attributeValueFromNsName(
- c_node, _XML_SCHEMA_INSTANCE_NS, <unsigned char*>"type")
- if typename is not None:
- pytype = <PyType>_SCHEMA_TYPE_DICT.get(typename)
- if pytype is None and ':' in typename:
- prefix, typename = typename.split(':', 1)
- pytype = <PyType>_SCHEMA_TYPE_DICT.get(typename)
- if pytype is not None and pytype is not StrType:
- # StrType does not have a typecheck but is the default
- # anyway, so just accept it if given as type
- # information
- pytype = _check_type(c_node, pytype)
- if pytype is None:
- typename = None
- if pytype is None and not ignore_pytype:
- # check that old pytype value is valid
- old_pytypename = cetree.attributeValueFromNsName(
- c_node, _PYTYPE_NAMESPACE, _PYTYPE_ATTRIBUTE_NAME)
- if old_pytypename is not None:
- if old_pytypename == TREE_PYTYPE_NAME:
- if not cetree.hasChild(c_node):
- # only case where we should keep it,
- # everything else is clear enough
- pytype = TREE_PYTYPE
- else:
- if old_pytypename == 'none':
- # transition from lxml 1.x
- old_pytypename = "NoneType"
- pytype = <PyType>_PYTYPE_DICT.get(old_pytypename)
- if pytype is not None and pytype is not StrType:
- # StrType does not have a typecheck but is the
- # default anyway, so just accept it if given as
- # type information
- pytype = _check_type(c_node, pytype)
- if pytype is None:
- # try to guess type
- if not cetree.hasChild(c_node):
- # element has no children => data class
- pytype = _guessPyType(textOf(c_node), StrType)
- else:
- istree = 1
- if pytype is None:
- # use default type for empty elements
- if cetree.hasText(c_node):
- pytype = StrType
- else:
- pytype = empty_pytype
- if typename is None:
- typename = empty_type_name
- if pytype is not None:
- if typename is None:
- if not istree:
- if pytype._schema_types:
- # pytype->xsi:type is a 1:n mapping
- # simply take the first
- typename = pytype._schema_types[0]
- elif typename not in pytype._schema_types:
- typename = pytype._schema_types[0]
- if annotate_xsi:
- if typename is None or istree:
- cetree.delAttributeFromNsName(
- c_node, _XML_SCHEMA_INSTANCE_NS, <unsigned char*>"type")
- else:
- # update or create attribute
- typename_utf8 = cetree.utf8(typename)
- c_ns = cetree.findOrBuildNodeNsPrefix(
- doc, c_node, _XML_SCHEMA_NS, <unsigned char*>'xsd')
- if c_ns is not NULL:
- if b':' in typename_utf8:
- prefix, name = typename_utf8.split(b':', 1)
- if c_ns.prefix is NULL or c_ns.prefix[0] == c'\0':
- typename_utf8 = name
- elif tree.xmlStrcmp(_xcstr(prefix), c_ns.prefix) != 0:
- typename_utf8 = (<unsigned char*>c_ns.prefix) + b':' + name
- elif c_ns.prefix is not NULL and c_ns.prefix[0] != c'\0':
- typename_utf8 = (<unsigned char*>c_ns.prefix) + b':' + typename_utf8
- c_ns = cetree.findOrBuildNodeNsPrefix(
- doc, c_node, _XML_SCHEMA_INSTANCE_NS, <unsigned char*>'xsi')
- tree.xmlSetNsProp(c_node, c_ns, <unsigned char*>"type", _xcstr(typename_utf8))
- if annotate_pytype:
- if pytype is None:
- # delete attribute if it exists
- cetree.delAttributeFromNsName(
- c_node, _PYTYPE_NAMESPACE, _PYTYPE_ATTRIBUTE_NAME)
- else:
- # update or create attribute
- c_ns = cetree.findOrBuildNodeNsPrefix(
- doc, c_node, _PYTYPE_NAMESPACE, <unsigned char*>'py')
- pytype_name = cetree.utf8(pytype.name)
- tree.xmlSetNsProp(c_node, c_ns, _PYTYPE_ATTRIBUTE_NAME,
- _xcstr(pytype_name))
- if pytype is NoneType:
- c_ns = cetree.findOrBuildNodeNsPrefix(
- doc, c_node, _XML_SCHEMA_INSTANCE_NS, <unsigned char*>'xsi')
- tree.xmlSetNsProp(c_node, c_ns, <unsigned char*>"nil", <unsigned char*>"true")
- return 0
- cdef object _strip_attributes = etree.strip_attributes
- cdef object _cleanup_namespaces = etree.cleanup_namespaces
- def deannotate(element_or_tree, *, bint pytype=True, bint xsi=True,
- bint xsi_nil=False, bint cleanup_namespaces=False):
- """deannotate(element_or_tree, pytype=True, xsi=True, xsi_nil=False, cleanup_namespaces=False)
- Recursively de-annotate the elements of an XML tree by removing 'py:pytype'
- and/or 'xsi:type' attributes and/or 'xsi:nil' attributes.
- If the 'pytype' keyword argument is True (the default), 'py:pytype'
- attributes will be removed. If the 'xsi' keyword argument is True (the
- default), 'xsi:type' attributes will be removed.
- If the 'xsi_nil' keyword argument is True (default: False), 'xsi:nil'
- attributes will be removed.
- Note that this does not touch the namespace declarations by
- default. If you want to remove unused namespace declarations from
- the tree, pass the option ``cleanup_namespaces=True``.
- """
- cdef list attribute_names = []
- if pytype:
- attribute_names.append(PYTYPE_ATTRIBUTE)
- if xsi:
- attribute_names.append(XML_SCHEMA_INSTANCE_TYPE_ATTR)
- if xsi_nil:
- attribute_names.append(XML_SCHEMA_INSTANCE_NIL_ATTR)
- _strip_attributes(element_or_tree, *attribute_names)
- if cleanup_namespaces:
- _cleanup_namespaces(element_or_tree)
- ################################################################################
- # Module level parser setup
- cdef object __DEFAULT_PARSER
- __DEFAULT_PARSER = etree.XMLParser(remove_blank_text=True)
- __DEFAULT_PARSER.set_element_class_lookup( ObjectifyElementClassLookup() )
- cdef object objectify_parser
- objectify_parser = __DEFAULT_PARSER
- def set_default_parser(new_parser = None):
- """set_default_parser(new_parser = None)
- Replace the default parser used by objectify's Element() and
- fromstring() functions.
- The new parser must be an etree.XMLParser.
- Call without arguments to reset to the original parser.
- """
- global objectify_parser
- if new_parser is None:
- objectify_parser = __DEFAULT_PARSER
- elif isinstance(new_parser, etree.XMLParser):
- objectify_parser = new_parser
- else:
- raise TypeError, "parser must inherit from lxml.etree.XMLParser"
- def makeparser(**kw):
- """makeparser(remove_blank_text=True, **kw)
- Create a new XML parser for objectify trees.
- You can pass all keyword arguments that are supported by
- ``etree.XMLParser()``. Note that this parser defaults to removing
- blank text. You can disable this by passing the
- ``remove_blank_text`` boolean keyword option yourself.
- """
- if 'remove_blank_text' not in kw:
- kw['remove_blank_text'] = True
- parser = etree.XMLParser(**kw)
- parser.set_element_class_lookup( ObjectifyElementClassLookup() )
- return parser
- cdef _Element _makeElement(tag, text, attrib, nsmap):
- return cetree.makeElement(tag, None, objectify_parser, text, None, attrib, nsmap)
- ################################################################################
- # Module level factory functions
- cdef object _fromstring
- _fromstring = etree.fromstring
- SubElement = etree.SubElement
- def fromstring(xml, parser=None, *, base_url=None):
- """fromstring(xml, parser=None, base_url=None)
- Objectify specific version of the lxml.etree fromstring() function
- that uses the objectify parser.
- You can pass a different parser as second argument.
- The ``base_url`` keyword argument allows to set the original base URL of
- the document to support relative Paths when looking up external entities
- (DTD, XInclude, ...).
- """
- if parser is None:
- parser = objectify_parser
- return _fromstring(xml, parser, base_url=base_url)
- def XML(xml, parser=None, *, base_url=None):
- """XML(xml, parser=None, base_url=None)
- Objectify specific version of the lxml.etree XML() literal factory
- that uses the objectify parser.
- You can pass a different parser as second argument.
- The ``base_url`` keyword argument allows to set the original base URL of
- the document to support relative Paths when looking up external entities
- (DTD, XInclude, ...).
- """
- if parser is None:
- parser = objectify_parser
- return _fromstring(xml, parser, base_url=base_url)
- cdef object _parse
- _parse = etree.parse
- def parse(f, parser=None, *, base_url=None):
- """parse(f, parser=None, base_url=None)
- Parse a file or file-like object with the objectify parser.
- You can pass a different parser as second argument.
- The ``base_url`` keyword allows setting a URL for the document
- when parsing from a file-like object. This is needed when looking
- up external entities (DTD, XInclude, ...) with relative paths.
- """
- if parser is None:
- parser = objectify_parser
- return _parse(f, parser, base_url=base_url)
- cdef dict _DEFAULT_NSMAP = {
- "py" : PYTYPE_NAMESPACE,
- "xsi" : XML_SCHEMA_INSTANCE_NS,
- "xsd" : XML_SCHEMA_NS
- }
- E = ElementMaker()
- def Element(_tag, attrib=None, nsmap=None, *, _pytype=None, **_attributes):
- """Element(_tag, attrib=None, nsmap=None, _pytype=None, **_attributes)
- Objectify specific version of the lxml.etree Element() factory that
- always creates a structural (tree) element.
- NOTE: requires parser based element class lookup activated in lxml.etree!
- """
- if attrib is not None:
- if _attributes:
- attrib = dict(attrib)
- attrib.update(_attributes)
- _attributes = attrib
- if _pytype is None:
- _pytype = TREE_PYTYPE_NAME
- if nsmap is None:
- nsmap = _DEFAULT_NSMAP
- _attributes[PYTYPE_ATTRIBUTE] = _pytype
- return _makeElement(_tag, None, _attributes, nsmap)
- def DataElement(_value, attrib=None, nsmap=None, *, _pytype=None, _xsi=None,
- **_attributes):
- """DataElement(_value, attrib=None, nsmap=None, _pytype=None, _xsi=None, **_attributes)
- Create a new element from a Python value and XML attributes taken from
- keyword arguments or a dictionary passed as second argument.
- Automatically adds a 'pytype' attribute for the Python type of the value,
- if the type can be identified. If '_pytype' or '_xsi' are among the
- keyword arguments, they will be used instead.
- If the _value argument is an ObjectifiedDataElement instance, its py:pytype,
- xsi:type and other attributes and nsmap are reused unless they are redefined
- in attrib and/or keyword arguments.
- """
- if nsmap is None:
- nsmap = _DEFAULT_NSMAP
- if attrib is not None and attrib:
- if _attributes:
- attrib = dict(attrib)
- attrib.update(_attributes)
- _attributes = attrib
- if isinstance(_value, ObjectifiedElement):
- if _pytype is None:
- if _xsi is None and not _attributes and nsmap is _DEFAULT_NSMAP:
- # special case: no change!
- return _value.__copy__()
- if isinstance(_value, ObjectifiedDataElement):
- # reuse existing nsmap unless redefined in nsmap parameter
- temp = _value.nsmap
- if temp is not None and temp:
- temp = dict(temp)
- temp.update(nsmap)
- nsmap = temp
- # reuse existing attributes unless redefined in attrib/_attributes
- temp = _value.attrib
- if temp is not None and temp:
- temp = dict(temp)
- temp.update(_attributes)
- _attributes = temp
- # reuse existing xsi:type or py:pytype attributes, unless provided as
- # arguments
- if _xsi is None and _pytype is None:
- _xsi = _attributes.get(XML_SCHEMA_INSTANCE_TYPE_ATTR)
- _pytype = _attributes.get(PYTYPE_ATTRIBUTE)
- if _xsi is not None:
- if ':' in _xsi:
- prefix, name = _xsi.split(':', 1)
- ns = nsmap.get(prefix)
- if ns != XML_SCHEMA_NS:
- raise ValueError, "XSD types require the XSD namespace"
- elif nsmap is _DEFAULT_NSMAP:
- name = _xsi
- _xsi = 'xsd:' + _xsi
- else:
- name = _xsi
- for prefix, ns in nsmap.items():
- if ns == XML_SCHEMA_NS:
- if prefix is not None and prefix:
- _xsi = prefix + ':' + _xsi
- break
- else:
- raise ValueError, "XSD types require the XSD namespace"
- _attributes[XML_SCHEMA_INSTANCE_TYPE_ATTR] = _xsi
- if _pytype is None:
- # allow using unregistered or even wrong xsi:type names
- py_type = <PyType>_SCHEMA_TYPE_DICT.get(_xsi)
- if py_type is None:
- py_type = <PyType>_SCHEMA_TYPE_DICT.get(name)
- if py_type is not None:
- _pytype = py_type.name
- if _pytype is None:
- _pytype = _pytypename(_value)
- if _value is None and _pytype != "str":
- _pytype = _pytype or "NoneType"
- strval = None
- elif python._isString(_value):
- strval = _value
- elif isinstance(_value, bool):
- if _value:
- strval = "true"
- else:
- strval = "false"
- else:
- py_type = <PyType>_PYTYPE_DICT.get(_pytype)
- stringify = unicode if py_type is None else py_type.stringify
- strval = stringify(_value)
- if _pytype is not None:
- if _pytype == "NoneType" or _pytype == "none":
- strval = None
- _attributes[XML_SCHEMA_INSTANCE_NIL_ATTR] = "true"
- else:
- # check if type information from arguments is valid
- py_type = <PyType>_PYTYPE_DICT.get(_pytype)
- if py_type is not None:
- if py_type.type_check is not None:
- py_type.type_check(strval)
- _attributes[PYTYPE_ATTRIBUTE] = _pytype
- return _makeElement("value", strval, _attributes, nsmap)
- ################################################################################
- # ObjectPath
- include "objectpath.pxi"
|