Можно ли сказать ElementTree для сохранения порядка атрибутов? - PullRequest
21 голосов
/ 30 апреля 2010

Я написал довольно простой фильтр на python, используя ElementTree для изменения контекста некоторых XML-файлов. И это работает, более или менее.

Но он переупорядочивает атрибуты различных тегов, и я бы хотел, чтобы это не делалось.

Кто-нибудь знает переключатель, который я могу включить, чтобы он удерживал их в указанном порядке?

Контекст для этого

Я работаю с инструментом физики элементарных частиц, который имеет сложную, но странно ограниченную систему конфигурации, основанную на XML-файлах. Среди множества вещей, настроенных таким образом, - пути к различным файлам статических данных. Эти пути жестко запрограммированы в существующем xml, и нет никаких средств для их установки или изменения в зависимости от переменных среды, и в нашей локальной установке они обязательно находятся в другом месте.

Это не катастрофа, потому что комбинированный инструмент управления исходным кодом и сборкой, который мы используем, позволяет нам скрывать определенные файлы с локальными копиями. Но даже несмотря на то, что поля данных статичны, а xml - нет, поэтому я написал скрипт для исправления путей, но с перестановкой атрибутов различия между локальной и основной версиями труднее читать, чем необходимо.


Это мой первый раз, когда я использую ElementTree для вращения (и только мой пятый или шестой проект Python), поэтому, возможно, я просто делаю это неправильно.

Абстрагированный для простоты код выглядит так:

tree = elementtree.ElementTree.parse(inputfile)
i = tree.getiterator()
for e in i:
    e.text = filter(e.text)
tree.write(outputfile)

Разумно или глупо?


Ссылки по теме:

Ответы [ 9 ]

22 голосов
/ 18 июня 2015

С помощью ответа @ bobince и этих двух ( порядок установки атрибутов , переопределяющие методы модуля )

Мне удалось исправить эту обезьяну, она грязная, и яЯ бы предложил использовать другой модуль, который лучше справляется с этим сценарием, но когда это невозможно:

# =======================================================================
# Monkey patch ElementTree
import xml.etree.ElementTree as ET

def _serialize_xml(write, elem, encoding, qnames, namespaces):
    tag = elem.tag
    text = elem.text
    if tag is ET.Comment:
        write("<!--%s-->" % ET._encode(text, encoding))
    elif tag is ET.ProcessingInstruction:
        write("<?%s?>" % ET._encode(text, encoding))
    else:
        tag = qnames[tag]
        if tag is None:
            if text:
                write(ET._escape_cdata(text, encoding))
            for e in elem:
                _serialize_xml(write, e, encoding, qnames, None)
        else:
            write("<" + tag)
            items = elem.items()
            if items or namespaces:
                if namespaces:
                    for v, k in sorted(namespaces.items(),
                                       key=lambda x: x[1]):  # sort on prefix
                        if k:
                            k = ":" + k
                        write(" xmlns%s=\"%s\"" % (
                            k.encode(encoding),
                            ET._escape_attrib(v, encoding)
                            ))
                #for k, v in sorted(items):  # lexical order
                for k, v in items: # Monkey patch
                    if isinstance(k, ET.QName):
                        k = k.text
                    if isinstance(v, ET.QName):
                        v = qnames[v.text]
                    else:
                        v = ET._escape_attrib(v, encoding)
                    write(" %s=\"%s\"" % (qnames[k], v))
            if text or len(elem):
                write(">")
                if text:
                    write(ET._escape_cdata(text, encoding))
                for e in elem:
                    _serialize_xml(write, e, encoding, qnames, None)
                write("</" + tag + ">")
            else:
                write(" />")
    if elem.tail:
        write(ET._escape_cdata(elem.tail, encoding))

ET._serialize_xml = _serialize_xml

from collections import OrderedDict

class OrderedXMLTreeBuilder(ET.XMLTreeBuilder):
    def _start_list(self, tag, attrib_in):
        fixname = self._fixname
        tag = fixname(tag)
        attrib = OrderedDict()
        if attrib_in:
            for i in range(0, len(attrib_in), 2):
                attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1])
        return self._target.start(tag, attrib)

# =======================================================================

Тогда в вашем коде:

tree = ET.parse(pathToFile, OrderedXMLTreeBuilder())
18 голосов
/ 30 апреля 2010

Неа. ElementTree использует словарь для хранения значений атрибутов, поэтому он по сути неупорядочен.

Даже DOM не гарантирует, что вы упорядочите атрибуты, и DOM раскрывает гораздо больше деталей инфо-набора XML, чем ElementTree. (Есть некоторые DOM, которые предлагают его как функцию, но это не стандарт.)

Можно ли это исправить? Может быть. Вот удар, который заменяет словарь при разборе на упорядоченный (collections.OrderedDict()).

from xml.etree import ElementTree
from collections import OrderedDict
import StringIO

class OrderedXMLTreeBuilder(ElementTree.XMLTreeBuilder):
    def _start_list(self, tag, attrib_in):
        fixname = self._fixname
        tag = fixname(tag)
        attrib = OrderedDict()
        if attrib_in:
            for i in range(0, len(attrib_in), 2):
                attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1])
        return self._target.start(tag, attrib)

>>> xmlf = StringIO.StringIO('<a b="c" d="e" f="g" j="k" h="i"/>')

>>> tree = ElementTree.ElementTree()
>>> root = tree.parse(xmlf, OrderedXMLTreeBuilder())
>>> root.attrib
OrderedDict([('b', 'c'), ('d', 'e'), ('f', 'g'), ('j', 'k'), ('h', 'i')])

Выглядит потенциально многообещающе.

>>> s = StringIO.StringIO()
>>> tree.write(s)
>>> s.getvalue()
'<a b="c" d="e" f="g" h="i" j="k" />'

Бах, сериализатор выводит их в каноническом порядке.

Это похоже на линию обвинения, в ElementTree._write:

            items.sort() # lexical order

Создание подклассов или исправлений обезьян, которые будут раздражать, так как это прямо в середине большого метода.

Если вы не сделали что-то противное, например, подкласс OrderedDict и взломали items, чтобы вернуть специальный подкласс list, который игнорирует вызовы sort(). Нет, наверное, это еще хуже, и я должен идти спать, прежде чем придумать что-нибудь более ужасное, чем это.

4 голосов
/ 02 января 2016

Да, с lxml

>>> from lxml import etree
>>> root = etree.Element("root", interesting="totally")
>>> etree.tostring(root)
b'<root interesting="totally"/>'
>>> print(root.get("hello"))
None
>>> root.set("hello", "Huhu")
>>> print(root.get("hello"))
Huhu
>>> etree.tostring(root)
b'<root interesting="totally" hello="Huhu"/>'

Вот прямая ссылка на документацию, из которой приведенный выше пример немного адаптирован.

Также обратите внимание, что lxml по своему дизайну имеет хорошую API-совместимость со стандартом xml.etree.ElementTree

4 голосов
/ 30 апреля 2010

Неправильный вопрос. Должно быть: «Где найти diff гаджет, который разумно работает с файлами XML?

Ответ: Google - ваш друг. Первый результат поиска по "xml diff" => this . Есть еще несколько возможных вариантов.

3 голосов
/ 23 января 2018

Лучший вариант - использовать библиотеку lxml http://lxml.de/ Установка lxml и просто переключение библиотеки сделали волшебство для меня.

#import xml.etree.ElementTree as ET
from lxml import etree as ET
3 голосов
/ 01 мая 2010

Из раздела 3.1 рекомендация XML :

Обратите внимание, что порядок спецификаций атрибутов в начальном теге или теге пустого элемента не имеет значения.

Любая система, которая полагается на порядок атрибутов в элементе XML, сломается.

2 голосов
/ 22 ноября 2017

Это частичное решение для случая, когда испускается xml и требуется предсказуемый порядок. Это не решает разбор туда и обратно. И 2.7, и 3.x используют sorted() для принудительного упорядочения атрибутов. Таким образом, этот код в сочетании с использованием OrderedDictionary для хранения атрибутов сохранит порядок вывода xml, соответствующий порядку, используемому для создания элементов.

from collections import OrderedDict
from xml.etree import ElementTree as ET

# Make sorted() a no-op for the ElementTree module
ET.sorted = lambda x: x

try:
    # python3 use a cPython implementation by default, prevent that
    ET.Element = ET._Element_Py
    # similarly, override SubElement method if desired
    def SubElement(parent, tag, attrib=OrderedDict(), **extra):
        attrib = attrib.copy()
        attrib.update(extra)
        element = parent.makeelement(tag, attrib)
        parent.append(element)
        return element
    ET.SubElement = SubElement
except AttributeError:
    pass  # nothing else for python2, ElementTree is pure python

# Make an element with a particular "meaningful" ordering
t = ET.ElementTree(ET.Element('component',
                       OrderedDict([('grp','foo'),('name','bar'),
                                    ('class','exec'),('arch','x86')])))
# Add a child element
ET.SubElement(t.getroot(),'depend',
              OrderedDict([('grp','foo'),('name','util1'),('class','lib')]))  
x = ET.tostring(n)
print (x)
# Order maintained...
# <component grp="foo" name="bar" class="exec" arch="x86"><depend grp="foo" name="util1" class="lib" /></component>

# Parse again, won't be ordered because Elements are created
#   without ordered dict
print ET.tostring(ET.fromstring(x))
# <component arch="x86" name="bar" grp="foo" class="exec"><depend name="util1" grp="foo" class="lib" /></component>

Проблема с анализом XML в дереве элементов состоит в том, что код внутренне создает простые dict s, которые передаются в Element (), и в этот момент порядок теряется. Эквивалентное простое исправление невозможно.

2 голосов
/ 18 июня 2013

Были ваши проблемы. Сначала искал какой-нибудь сценарий Python для канонизации, никого не нашел. Затем начал думать о создании одного. Наконец xmllint решено.

0 голосов
/ 30 июля 2018

Я использовал принятый ответ выше, с обоими утверждениями:

ET._serialize_xml = _serialize_xml
ET._serialize['xml'] = _serialize_xml

Хотя это исправило порядок в каждом узле, упорядочение атрибутов на новых узлах, вставленных из копий существующих узлов, не удалось сохранить без глубокой копии. Остерегайтесь повторного использования узлов для создания других ... В моем случае у меня был элемент с несколькими атрибутами, поэтому я хотел использовать их повторно:

to_add = ET.fromstring(ET.tostring(contract))
to_add.attrib['symbol'] = add
to_add.attrib['uniqueId'] = add
contracts.insert(j + 1, to_add)

fromstring(tostring) будет переупорядочивать атрибуты в памяти. Это может не привести к альфа-сортировке атрибутов, но также может не иметь ожидаемого порядка.

to_add = copy.deepcopy(contract)
to_add.attrib['symbol'] = add
to_add.attrib['uniqueId'] = add
contracts.insert(j + 1, to_add)

Теперь порядок сохраняется.

...