lmxl增量XML序列化重复名称空间

时间:2018-10-31 12:53:40

标签: python xml xml-serialization lxml xml-namespaces

我目前正在使用lxml在Python中序列化一些较大的XML文件。我想为此使用增量编写器。我的XML格式严重依赖于名称空间和属性。当我运行以下代码

from io import BytesIO

from lxml import etree

sink = BytesIO()

nsmap = {
    'test': 'http://test.org',
    'foo': 'http://foo.org',
    'bar': 'http://bar.org',
}

with etree.xmlfile(sink) as xf:
    with xf.element("test:testElement", nsmap=nsmap):
        name = etree.QName(nsmap["foo"], "fooElement")
        elem = etree.Element(name)

        xf.write(elem)

print(sink.getvalue().decode('utf-8'))

然后我得到以下输出:

<test:testElement xmlns:bar="http://bar.org" 
 xmlns:foo="http://foo.org" 
 xmlns:test="http://test.org">
    <ns0:fooElement xmlns:ns0="http://foo.org"/>
</test:testElement>

如您所见,foo的名称空间是重复的,而不是我的前缀:

<ns0:fooElement xmlns:ns0="http://foo.org"/>

如何使lxml仅在根目录中添加名称空间,而子级从那里使用正确的前缀?我认为我需要使用etree.Element,因为我需要向节点添加一些属性。

什么不起作用:

1)使用register_namespace

for prefix, uri in nsmap.items():
    etree.register_namespace(prefix, uri)

这仍然重复,但是使前缀正确。我不太喜欢它,因为它在全球范围内都会改变。

2)在元素中指定nsmap

elem = etree.Element(name, nsmap=nsmap)

收益

<foo:fooElement xmlns:bar="http://bar.org" 
 xmlns:foo="http://foo.org" 
 xmlns:test="http://test.org"/>

fooElement

我也查看了lxml的文档和源代码,但是Cython真的很难阅读和搜索。 xf.element的上下文管理器不返回该元素。例如

with xf.element('foo:fooElement') as e:
    print(e)

打印None

2 个答案:

答案 0 :(得分:1)

有可能产生与您想要的东西接近的东西:

from io import BytesIO

from lxml import etree

sink = BytesIO()

nsmap = {
    'test': 'http://test.org',
    'foo': 'http://foo.org',
    'bar': 'http://bar.org',
}

with etree.xmlfile(sink) as xf:
    with xf.element("test:testElement", nsmap=nsmap):
        with xf.element("foo:fooElement"):
            pass

print(sink.getvalue().decode('utf-8'))

这将产生XML:

<test:testElement xmlns:bar="http://bar.org" xmlns:foo="http://foo.org" xmlns:test="http://test.org"><foo:fooElement></foo:fooElement></test:testElement>

多余的名称空间声明不见了,但是您获得了foo:fooElement的一对开始和结束标签,而不是立即关闭的元素。

我查看了lxml.etree.xmlfile的源代码,但没有看到那里的代码保持状态,然后它会检查知道已经声明了哪些名称空间,并避免不必要地再次声明它们。我可能只是错过了一些东西,但我真的不认为自己做了。增量XML序列化器的要点是无需使用内存块即可进行操作。当内存不成问题时,您只需创建代表XML文档的对象树并将其序列化即可。您需要支付大量的内存成本,因为整个树必须在内存中可用,直到序列化该树为止。通过使用增量串行器,可以避免出现内存问题。为了最大程度地节省内存,串行器必须最小化其维护的状态量。如果当它在序列化中生成一个元素时,要考虑到该元素的父元素,那么它就必须“记住”父元素是什么并保持状态。在最坏的情况下,它将保持如此多的状态,以至于只创建一棵XML对象树然后进行序列化将没有任何好处。

答案 1 :(得分:0)

您需要创建一个子元素:

_nsmap={
    'test': 'http://test.org',
    'foo': 'http://foo.org',
    'bar': 'http://bar.org',
}

root = etree.Element(
    "{http://bar.org}test",
    creator='SO',
    nsmap=_nsmap
)

doc = etree.ElementTree(root)
name = etree.QName(_nsmap["foo"], "fooElement")
elem = etree.SubElement(root, name)

doc.write('/tmp/foo.xml', xml_declaration=True, encoding='utf-8', pretty_print=True)
print (open('/tmp/foo.xml').read())

返回:

<?xml version='1.0' encoding='UTF-8'?>
<bar:test xmlns:bar="http://bar.org" xmlns:foo="http://foo.org" xmlns:test="http://test.org" creator="SO">
  <foo:fooElement/>
</bar:test>