Python ElementTree modul: Bagaimana untuk mengabaikan namespace XML file untuk menemukan pencocokan elemen ketika menggunakan metode "cari", "findall"

Saya ingin menggunakan metode "findall" untuk menemukan beberapa elemen dari sumber file xml di ElementTree modul.

Namun, sumber file xml (test.xml) memiliki namespace. Aku memotong bagian dari file xml sebagai contoh:

<?xml version="1.0" encoding="iso-8859-1"?>
<XML_HEADER xmlns="http://www.test.com">
    <TYPE>Updates</TYPE>
    <DATE>9/26/2012 10:30:34 AM</DATE>
    <COPYRIGHT_NOTICE>All Rights Reserved.</COPYRIGHT_NOTICE>
    <LICENSE>newlicense.htm</LICENSE>
    <DEAL_LEVEL>
        <PAID_OFF>N</PAID_OFF>
        </DEAL_LEVEL>
</XML_HEADER>

Sampel kode python di bawah ini:

from xml.etree import ElementTree as ET
tree = ET.parse(r"test.xml")
el1 = tree.findall("DEAL_LEVEL/PAID_OFF") # Return None
el2 = tree.findall("{http://www.test.com}DEAL_LEVEL/{http://www.test.com}PAID_OFF") # Return <Element '{http://www.test.com}DEAL_LEVEL/PAID_OFF' at 0xb78b90>

Meskipun hal ini dapat bekerja, karena ada namespace "{http://www.test.com", it's sangat nyaman untuk menambahkan namespace di depan masing-masing tag.

Bagaimana bisa aku mengabaikan namespace ketika menggunakan metode "cari", "findall" dan sebagainya?

Mengomentari pertanyaan (2)

Bukan memodifikasi dokumen XML itu sendiri,'s terbaik untuk mengurai dan kemudian memodifikasi kategori dalam hasil. Dengan cara ini anda dapat menangani beberapa namespaces dan nama alias:

from io import StringIO  # for Python 2 import from StringIO instead
import xml.etree.ElementTree as ET

# instead of ET.fromstring(xml)
it = ET.iterparse(StringIO(xml))
for _, el in it:
    prefix, has_namespace, postfix = el.tag.partition('}')
    if has_namespace:
        el.tag = postfix  # strip all namespaces
root = it.root

Hal ini didasarkan pada pembahasan berikut ini: http://bugs.python.org/issue18304

Komentar (5)

Jika anda menghapus atribut xmlns dari xml sebelum parsing itu maka tidak ada't menjadi namespace ditambahkan untuk setiap tag di pohon.

import re

xmlstring = re.sub(' xmlns="[^"]+"', '', xmlstring, count=1)
Komentar (3)

Jawaban yang sejauh ini secara eksplisit menempatkan namespace nilai dalam script. Untuk lebih generik solusi, saya lebih suka ekstrak namespace dari xml:

import re
def get_namespace(element):
  m = re.match('\{.*\}', element.tag)
  return m.group(0) if m else ''

Dan menggunakannya dalam menemukan metode:

namespace = get_namespace(tree.getroot())
print tree.find('./{0}parent/{0}version'.format(namespace)).text
Komentar (2)

Berikut ini's ekstensi untuk nonagon's jawaban, yang juga strip namespaces off atribut:

from StringIO import StringIO
import xml.etree.ElementTree as ET

# instead of ET.fromstring(xml)
it = ET.iterparse(StringIO(xml))
for _, el in it:
    if '}' in el.tag:
        el.tag = el.tag.split('}', 1)[1]  # strip all namespaces
    for at in el.attrib.keys(): # strip namespaces of attributes too
        if '}' in at:
            newat = at.split('}', 1)[1]
            el.attrib[newat] = el.attrib[at]
            del el.attrib[at]
root = it.root
Komentar (0)

Peningkatan pada jawaban oleh ericspod:

Bukan mengubah mengurai mode secara global bisa kita bungkus ini di sebuah objek yang mendukung dengan membangun.

from xml.parsers import expat

class DisableXmlNamespaces:
    def __enter__(self):
            self.oldcreate = expat.ParserCreate
            expat.ParserCreate = lambda encoding, sep: self.oldcreate(encoding, None)
    def __exit__(self, type, value, traceback):
            expat.ParserCreate = self.oldcreate

Ini kemudian dapat digunakan sebagai berikut

import xml.etree.ElementTree as ET
with DisableXmlNamespaces():
     tree = ET.parse("test.xml")

Keindahan dari cara ini adalah bahwa hal itu tidak mengubah perilaku yang tidak terkait kode luar dengan blok. Akhirnya saya membuat ini setelah mendapatkan kesalahan dalam berhubungan perpustakaan setelah menggunakan versi ericspod yang juga terjadi untuk menggunakan expat.

Komentar (1)

Anda dapat menggunakan elegan string pemformatan membangun serta:

ns='http://www.test.com'
el2 = tree.findall("{%s}DEAL_LEVEL/{%s}PAID_OFF" %(ns,ns))

atau, jika anda're yakin bahwa PAID_OFF hanya muncul di satu tingkat di pohon:

el2 = tree.findall(".//{%s}PAID_OFF" % ns)
Komentar (0)

Jika anda're menggunakan ElementTree dan tidak cElementTree anda dapat memaksa Expat untuk mengabaikan namespace pengolahan dengan mengganti ParserCreate():

from xml.parsers import expat
oldcreate = expat.ParserCreate
expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None)

ElementTree mencoba untuk menggunakan Expat dengan memanggil ParserCreate() tetapi tidak memberikan pilihan untuk tidak memberikan namespace pemisah string, kode di atas akan menyebabkan itu untuk mengabaikan tapi berhati-hatilah, ini bisa memecahkan hal-hal lain.

Komentar (4)

Let's menggabungkan nonagon's jawaban dengan mzjn's jawaban untuk pertanyaan terkait:

def parse_xml(xml_path: Path) -> Tuple[ET.Element, Dict[str, str]]:
    xml_iter = ET.iterparse(xml_path, events=["start-ns"])
    xml_namespaces = dict(prefix_namespace_pair for _, prefix_namespace_pair in xml_iter)
    return xml_iter.root, xml_namespaces

Dengan menggunakan fungsi ini kita:

  1. Membuat sebuah iterator untuk mendapatkan kedua namespaces dan parsing tree objek.

  2. Iterate atas dibuat iterator untuk mendapatkan namespaces dict yang kita dapat kemudian lulus dalam masing-masing find() atau findall() call sebagai sugested oleh iMom0.

  3. Kembali diurai pohon's elemen root objek dan namespaces.

Saya pikir ini adalah pendekatan yang terbaik di sekitar karena ada's tidak ada manipulasi baik dari sumber XML atau dihasilkan diurai xml.etree.ElementTree output apapun yang terlibat.

I'd seperti juga untuk kredit barny's jawaban dengan menyediakan bagian penting dari teka-teki ini (bahwa anda bisa mendapatkan diurai akar dari iterator). Sampai aku benar-benar dilalui pohon XML dua kali di aplikasi saya (sekali untuk mendapatkan namespaces, kedua untuk root).

Komentar (0)

Saya mungkin terlambat untuk ini, tapi saya tidak berpikir kembali.sub adalah solusi yang baik.

Namun menulis ulang xml.parsers.expat tidak bekerja untuk Python 3.x versi,

Penyebab utama adalah xml/etree/ElementTree.py lihat bagian bawah dari kode sumber

# Import the C accelerators
try:
    # Element is going to be shadowed by the C implementation. We need to keep
    # the Python version of it accessible for some "creative" by external code
    # (see tests)
    _Element_Py = Element

    # Element, SubElement, ParseError, TreeBuilder, XMLParser
    from _elementtree import *
except ImportError:
    pass

Yang agak sedih.

Solusinya adalah dengan menyingkirkan terlebih dahulu.

import _elementtree
try:
    del _elementtree.XMLParser
except AttributeError:
    # in case deleted twice
    pass
else:
    from xml.parsers import expat  # NOQA: F811
    oldcreate = expat.ParserCreate
    expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None)

Diuji pada Python 3.6.

Coba coba-coba pernyataan ini berguna dalam kasus di suatu tempat dalam kode anda anda reload atau mengimpor modul dua kali anda mendapatkan beberapa kesalahan aneh seperti

  • maksimum rekursi terlampaui
  • AttributeError: XMLParser

btw sialan dpohon source code terlihat benar-benar berantakan.

Komentar (0)