Lmxl инкрементная сериализация xml повторяет пространства имен

В настоящее время я сериализую некоторые большие 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.

4
0
251
2

Ответы 2

Вам нужно создать SubElement:

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

Как я могу использовать SubElement с etree.xmlfile? У меня нет рута, а использование xf дает TypeError: Argument '_parent' has incorrect type (expected lxml.etree._Element, got lxml.etree._IncrementalFileWriter)

jcklie 31.10.2018 14:29

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

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

За этот вопрос было назначено вознаграждение, но, похоже, ОП забыл об этом. Возможно, этот ответ не решает проблему, но он заслуживает одобрения.

mzjn 12.11.2018 18:20

Другие вопросы по теме