هل يمكن إخبار Elementtree بالحفاظ على ترتيب السمات؟

StackOverflow https://stackoverflow.com/questions/2741480

  •  02-10-2019
  •  | 
  •  

سؤال

لقد كتبت مرشحًا بسيطًا إلى حد ما في 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)

معقول أم غبي؟


روابط ذات علاقة:

هل كانت مفيدة؟

المحلول

بمساعدة من @Bobince's Answer وهذين (تحديد ترتيب السمة, طرق الوحدة النمطية الغالبة)

تمكنت من الحصول على هذا القرد ، فهو قذر وأقترح استخدام وحدة أخرى تعالج هذا السيناريو بشكل أفضل ولكن عندما لا يكون ذلك احتمالًا:

# =======================================================================
# 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())

نصائح أخرى

لا. يستخدم ElementTree قاموسًا لتخزين قيم السمات ، لذلك يتم ترتيبه بطبيعته.

حتى DOM لا يضمن لك أن تنسب الطلب ، ويكشف DOM الكثير من التفاصيل عن XML Infoset أكثر من ElementTree. (هناك بعض DOMs التي تقدمها كميزة ، لكنها ليست قياسية.)

هل يمكن إصلاحه؟ يمكن. إليك طعنة عليها تحل محل القاموس عند التحليل مع أحدهم (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(). ناه ، ربما يكون هذا أسوأ ويجب أن أذهب إلى الفراش قبل أن أتوصل إلى أي شيء أكثر رعباً من ذلك.

سؤال خاطئ. يجب أن يكون: "أين أجد ملف diff الأداة التي تعمل بشكل معقول مع ملفات XML؟

الإجابة: Google هو صديقك. النتيجة الأولى للبحث على "XML Diff" => هذه. هناك عدد قليل من الإمكانات.

نعم ، مع 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

الخيار الأفضل هو استخدام LXML مكتبة http://lxml.de/تثبيت LXML وفقط تبديل المكتبة فعل السحر لي.

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

من القسم 3.1 من توصية XML:

لاحظ أن ترتيب مواصفات السمة في علامة البدء أو علامة العناصر الفارغة ليست كبيرة.

أي نظام يعتمد على ترتيب السمات في عنصر XML سوف يكسر.

واجهت مشكلتك. أولاً ، بحث عن بعض البرمجيات Python للتخلي عن الكنسي ، لم يجد أحداً. ثم بدأت التفكير في صنع واحدة. أخيراً xmllintتم حلها.

هذا هو الحل الجزئي ، للحالة التي يتم فيها تنبعث XML وترتيب يمكن التنبؤ به. لا يحل تحليل الرحلة ذهابا وإيابا والكتابة. كلا 2.7 و 3.x الاستخدام sorted() لإجبار السمة. لذلك ، فإن هذا الرمز ، بالتزامن مع استخدام orderdictionary للاحتفاظ بالسمات ، سيحافظ على ترتيب إخراج 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 في شجرة عنصر هي أن الكود يخلق داخليًا عاديًا dictS التي يتم تمريرها إلى العنصر () ، وفي هذه النقطة يتم فقد الترتيب. لا يوجد تصحيح بسيط معادل ممكن.

لقد استخدمت الإجابة المقبولة أعلاه ، مع كلا البيانين:

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) سيتم إعادة ترتيب السمات في الذاكرة. قد لا يؤدي ذلك إلى DICT من السمات المصنفة ألفا ، ولكن قد لا يكون للترتيب المتوقع أيضًا.

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

الآن يستمر الطلب.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top