Инкрементная XML-сериализация lmxl повторяет пространства имен - PullRequest
0 голосов
/ 31 октября 2018

В настоящее время я сериализую некоторые большие XML-файлы в Python с помощью lxml Я хочу использовать инкрементный писатель для этого. Мой формат 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 голосов
/ 10 ноября 2018

Возможно произвести что-то близкое к тому, что вы ищете:

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, которые затем сериализуются.

0 голосов
/ 31 октября 2018

Вам необходимо создать подэлемент:

_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>
...