lxml - 同时使用默认的类元素查找和TreeBuilder解析器目标

时间:2016-03-27 09:53:47

标签: python-3.x lxml

使用lxml,有没有办法同时使用从ElementDefaultClassLookup类派生的target和解析器TreeBuilder

我试过了:

from lxml import etree

class MyElement(etree.ElementBase):
    pass

class MyComment(etree.CommentBase):
    pass

class MyTreeBuilder(etree.TreeBuilder):
    pass

parser_lookup = etree.ElementDefaultClassLookup(element=MyElement, comment=MyComment)
parser = etree.XMLParser(target=MyTreeBuilder()) # (My)TreeBuilder accepts a `parser` argument keyword (which will then use the class lookup from that parser), but we haven't created the parser yet!
parser.set_element_class_lookup(parser_lookup)

xml_string = '<root xmlns:test="hello"><element test:foobar="1" /><!-- world --></root>'

root = etree.fromstring(xml_string, parser=parser)
print(type(root), type(root.xpath('//comment()[1]')[0]), etree.tostring(root))

导致:

<class 'lxml.etree._Element'> <class 'lxml.etree._Comment'> b'<root><element xmlns:ns0="hello" ns0:foobar="1"/><!-- world --></root>'

我想:

<class 'MyElement'> <class 'MyComment'> b'<root xmlns:test="hello"><element test:foobar="1"/><!-- world --></root>'

请注意XML名称空间差异以及Python类。

我正在使用lxml 3.4.4

我可以使用:

在属性上获取正确的名称空间前缀
parser = etree.XMLParser(target=etree.TreeBuilder())

这对我没有任何意义 - 为什么我的派生类的行为方式不一样? (我意识到省略target参数默认情况下会使用TreeBuilder。)

我可以使用:

获取正确的类和名称空间前缀
parser = etree.XMLParser()

但我特别想用自己的“target”创建一棵树,理想情况下不想重新发明轮子。

我尝试在初始化解析器后设置target,如下所示:

parser.target = MyTreeBuilder(parser=parser)

但这会出错:

  

AttributeError:'lxml.etree._BaseParser'对象的属性'target'不可写

我检查了source code for the TreeBuilder class,然后尝试了:

class MyTreeBuilder(etree.TreeBuilder):
    def set_parser(self, parser):
        super()._parser = parser

parser_lookup = etree.ElementDefaultClassLookup(element=MyElement, comment=MyComment)
tb = MyTreeBuilder()
parser = etree.XMLParser(target=tb)
parser.set_element_class_lookup(parser_lookup)
tb.set_parser(parser)

给出:

  

AttributeError:'super'对象没有属性'_parser'

我试过了:

parser_lookup = etree.ElementDefaultClassLookup(element=MyElement, comment=MyComment)
fake_parser = etree.XMLParser()
fake_parser.set_element_class_lookup(parser_lookup)
tb = MyTreeBuilder(parser=fake_parser)
parser = etree.XMLParser(target=tb)
parser.set_element_class_lookup(parser_lookup)

它提供了正确的类,但仍然没有正确的属性名称空间。因此,我想它需要来自正确解析器的一些信息才能正确构建树。

我尝试设置element_factory关键字参数而不是parser,但是得到了相同的结果:

tb = MyTreeBuilder(element_factory=fake_parser.makeelement)
# and
tb = MyTreeBuilder(element_factory=fake_parser.makeelement, parser=fake_parser)

编辑:查看lxml class lookup code,似乎可以在自定义元素上设置一个名为PARSER的类属性。

我试过了:

MyElement.PARSER = parser

但结果与没有它的情况相同。

但是,解析器的makeelement方法按预期工作:

test = parser.makeelement('root', attrib={ '{hello}foobar': '1' }, nsmap={ 'test': 'hello' })
print(type(test), etree.tostring(test))
它给出了

(显然该属性与解析字符串的节点不同):

<class 'MyElement'> b'<root xmlns:test="hello" test:foobar="1"/>'

使用以下方法组合这两种方法:

def m(tag, attrib=None,nsmap=None, *children):
    return MyElement.PARSER.makeelement(tag, attrib=attrib, nsmap=nsmap, *children)

parser = etree.XMLParser(target=MyTreeBuilder(element_factory=m))

给出正确的类,但仍然是不正确的名称空间。

我想要的是什么?一旦我使用不是纯TreeBuilder的自定义目标,为什么名称空间“出错”?我需要在某处做些什么来手动纠正命名空间行为吗?我是否必须实现自己的树构建器?

0 个答案:

没有答案