lxml.objectify

转译自:https://lxml.de/objectify.html
lxml.objectify主要用于处理以数据为中心的文档,可以根据叶子节点所含的内容自动推断数据类型。该模块依然使用lxml.etree的ElementTree,但是节点元素分成两类:结构节点元素(Tree Element)和数据节点元素(Data Element)。

基本


该模块继承了一些etree的API,如:

>>> from lxml import objectify, etree
>>> from io import BytesIO

>>> root = objectify.Element("root")
>>> a = objectify.SubElement(root, "a")

>>> root = objectify.fromstring("test11")

>>> doc = objectify.parse(BytesIO(b"test11"))
>>> root = doc.getroot()

注意,该模块的parse函数依然生成lxml.etree模块的ElementTree,但是节点元素分成两类:结构节点元素,默认类型为objectify.ObjectifiedElement;以及数据节点元素,类型为objectify.IntElement、objectify.StringElement等。

>>> from lxml import objectify
>>> from io import BytesIO
>>> doc = objectify.parse(BytesIO(b"test5"))
>>> type(doc)

>>> root = doc.getroot()
>>> type(root)

>>> type(root.a), type(root.b)
(, )

子元素可以直接通过'.'语法访问。

>>> root = objectify.Element("root")
>>> b1 = objectify.SubElement(root, "b")
>>> print(root.b.tag)
b

注意,有多个相同tag名的子元素时,'.'语法的返回值也可以看成是具有该tag名的子元素的序列。因为对objectify的元素使用下标[n]运算,会寻找它的兄弟元素,且迭代操作也是对同样tag名的兄弟元素进行迭代:

>>> b2 = objectify.SubElement(root, "b")
>>> root.b[0] is b1, root.b[1] is b2
(True, True)
>>> for b in root.b: print(b.tag)
b
b

特别注意下列用法:

# 迭代自己
>>> for b in b1: print(b.tag)
b
b

可以参考源代码实现(Cython):

cdef class ObjectifiedElement(ElementBase):
    def __iter__(self):
        u"""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 __len__(self):
        u"""Count self and siblings with the same tag.
        """
        return _countSiblings(self._c_node)

    def __getattr__(self, tag):
        u"""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.
        """
        if is_special_method(tag):
            return object.__getattr__(self, tag)
        return _lookupChildOrRaise(self, tag)

    def __getitem__(self, key):
        u"""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:
                return self
            else:
                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)

迭代root.X得到的是root的tag名为X的子元素的序列,要访问root的所有子元素可以通过iterchildren()或getchildren()方法。

>>> root = objectify.fromstring("1011test12")
>>> [el.tag for el in root.b]
['b', 'b', 'b']
>>> [el.tag for el in root.iterchildren()]
['b', 'b', 'a', 'b']
>>> root.index(root.b[0]), root.index(root.b[1]), root.index(root.b[2])
(0, 1, 3)

类似地,len(elt)返回elt的兄弟个数(包括自身),而elt.countchildren()返回elt的子元素个数。

零散


元素的属性依然是通过get和set方法来操作。

>>> root.set('myattr', 'someval')
>>> root.get('myattr')
'someval'

直接用'.'语法赋值也可以添加子元素,此时子元素(子树)会被自动deep copied且子树的根的tag会被改成指定的名字:

>>> el = objectify.Element('other')
>>> root.c = el
>>> root.c.tag
'c'
>>> el.tag
'other'

也可以用列表赋值:

>>> root.c = [ objectify.Element("c"), objectify.Element("c") ]
>>> [el.tag for el in root.c]
['c', 'c']

注意,如果用数字,字符串等赋值,会生成Data Element并添加到树上:

>>> root.d = 1
>>> root.d
1
>>> type(root.d)

对于Data Element,访问它的.pyval属性可以得到对应的数据值。另外,.text属性依然是字符串。

>>> root.d.pyval
1
>>> root.d.text
'1'

objectify提供DataElement() 工厂函数来生成数据节点,生成的元素的tag默认是'value'。

>>> el = objectify.DataElement(5, _pytype="int")
>>> el.pyval
5
>>> el.tag
'value'
>>> root.e = objectify.DataElement(5, _pytype="int")

类型标记


某些方法(如Element()工厂函数)生成元素的时候会自动加上namespace和type标记:

>>> a = objectify.Element('a')
>>> etree.tostring(a)
b''

某些方法(如fromstring)则不会自动加上这些标记:

>>> a = objectify.fromstring("test")
>>> etree.tostring(a)
b'test'

但是源数据带type标记时,fromstring函数会利用这些标记来解析数据:

>>> a = objectify.fromstring('5')
>>> a
'5'
>>> etree.tostring(a)
b'5'

annotate和deannotate方法可以用于添加和移除这些标记:

>>> objectify.deannotate(a, cleanup_namespaces=True)
>>> etree.tostring(a)
b'5'
>>> type(a)

>>> a.attrib
{}
>>> objectify.annotate(a)
>>> etree.tostring(a)
b'5'
>>> type(a)         # a仍然是StringElement类

>>> a.pyval
'5'
>>> a.attrib        # 但是a的attrib字典变了
{'{http://codespeak.net/lxml/objectify/pytype}pytype': 'int'}

注意deannotate默认并不去除命名空间,因此需要设cleanup_namespaces为True。另外注意这里可能出现了一个bug:annotate添加标记时并不管原来的元素所属的class,因此出现了标记跟元素本身的class不符合的现象。

你可能感兴趣的:(lxml.objectify)