Подтвердить что ты не робот

Можно ли указать ElementTree для сохранения порядка атрибутов?

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

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

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

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

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

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


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

Сказанное для простоты код выглядит следующим образом:

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

Разумный или немой?


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

4b9b3361

Ответ 1

С помощью ответа @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())

Ответ 2

Неа. 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" />'

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

Это выглядит как строка, которая виновата в ElementTree._write:

            items.sort() # lexical order

Подклассификация или исправление обезьян, которая будет раздражать, поскольку она находится прямо в середине большого метода.

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

Ответ 3

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

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

Ответ 4

Да, с 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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

Это частичное решение для случая, когда 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, который передается элементу Element(), после чего порядок теряется. Нет эквивалентного простого патча.

Ответ 8

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

#import xml.etree.ElementTree as ET
from lxml import etree as ET