使用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的自定义目标,为什么名称空间“出错”?我需要在某处做些什么来手动纠正命名空间行为吗?我是否必须实现自己的树构建器?