XML'yi 'ElementTree' ile Python'da ad alanı ile ayrıştırma


163

Python'ın kullanarak ayrıştırmak istediğiniz aşağıdaki XML var ElementTree:

<rdf:RDF xml:base="http://dbpedia.org/ontology/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:owl="http://www.w3.org/2002/07/owl#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns="http://dbpedia.org/ontology/">

    <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
        <rdfs:label xml:lang="en">basketball league</rdfs:label>
        <rdfs:comment xml:lang="en">
          a group of sports teams that compete against each other
          in Basketball
        </rdfs:comment>
    </owl:Class>

</rdf:RDF>

Tüm owl:Classetiketleri bulmak ve rdfs:labeliçindeki tüm örneklerin değerini ayıklamak istiyorum . Aşağıdaki kodu kullanıyorum:

tree = ET.parse("filename")
root = tree.getroot()
root.findall('owl:Class')

Ad alanı nedeniyle, aşağıdaki hatayı alıyorum.

SyntaxError: prefix 'owl' not found in prefix map

Http://effbot.org/zone/element-namespaces.htm adresindeki dokümanı okumaya çalıştım, ancak yukarıdaki XML'in birden fazla iç içe ad alanı olduğundan bu çalışmayı hala alamıyorum.

Lütfen tüm owl:Classetiketleri bulmak için kodun nasıl değiştirileceğini bana bildirin .

Yanıtlar:


226

ElementTree, ad alanları hakkında çok akıllı değil. Vermeniz gerekiyor .find(),findall() ve iterfind()yöntemleri açık bir ad sözlüğü. Bu çok iyi belgelenmemiştir:

namespaces = {'owl': 'http://www.w3.org/2002/07/owl#'} # add more as needed

root.findall('owl:Class', namespaces)

Önekleri edilir sadece yukarı baktım namespacessen geçmek parametrenin herhangi ad kullanabilirsiniz Bu araçlar sever önek.; API owl:parçayı böler , ilgili ad alanı URL'sini arar.namespaces sözlükteki , ardından aramayı XPath ifadesini arayacak şekilde değiştirir {http://www.w3.org/2002/07/owl}Class. Elbette aynı sözdizimini de kendiniz kullanabilirsiniz:

root.findall('{http://www.w3.org/2002/07/owl#}Class')

lxmlKütüphaneye geçebiliyorsanız işler daha iyidir; bu kitaplık aynı ElementTree API'sini destekler, ancak .nsmapöğeler üzerindeki bir öznitelikte ad alanlarını toplar .


7
Teşekkür ederim. Herhangi bir fikrini sabit kodlamadan doğrudan XML'den nasıl alabilirim? Ya da nasıl görmezden gelebilirim? Findall'ı denedim ('{*} Sınıf') ama benim durumumda çalışmayacak.
Kostanos

7
Ağacı xmlnsöznitelikler için kendiniz taramanız gerekir ; cevapta belirtildiği gibi, lxmlbunu sizin için xml.etree.ElementTreeyapar , modül yapmaz. Ancak belirli (zaten kodlanmış) bir öğeyi eşleştirmeye çalışıyorsanız, belirli bir ad alanındaki belirli bir öğeyi eşleştirmeye çalışıyorsunuz demektir. Bu ad alanı, dokümanlar arasında öğe adından daha fazla değişmeyecek. Bunu öğe adıyla da sabit kodlayabilirsiniz.
Martijn Pieters

14
@Jon: register_namespacesadece serileştirmeyi etkiler, aramayı değil.
Martijn Pieters

5
Yararlı olabilecek küçük bir ekleme: cElementTreeyerine kullanırken ElementTree, findallad alanlarını bir anahtar kelime argümanı olarak değil, basit bir şekilde normal bir argüman olarak alır, yani kullanın ctree.findall('owl:Class', namespaces).
egpbos

2
@Bludwarf: Dokümanlar bundan bahsediyor (şimdi, yazdığınızda değilse), ancak bunları dikkatli bir şekilde okumalısınız. Bkz Ad ile ayrıştırma XML bölümünün: orada kullanımı zıt bir örnek findallolmayan ve daha sonra namespacebağımsız değişken, fakat bağımsız değişken yöntem olup yöntemin bağımsız değişken olarak söz konusu değildir element obje bölümü.
Wilson F

57

Bunu, ad alanlarını kodlamak veya metni onlar için taramak zorunda kalmadan lxml ile nasıl yapacağınız aşağıda açıklanmıştır (Martijn Pieters'ın belirttiği gibi):

from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)

GÜNCELLEME :

5 yıl sonra hala bu sorunun varyasyonları ile karşılaşıyorum. lxml, yukarıda gösterdiğim gibi yardımcı olur, ancak her durumda değil. Yorumcular, dokümanları birleştirme söz konusu olduğunda bu teknikle ilgili geçerli bir noktaya sahip olabilirler, ancak çoğu insanın dokümanları aramakta zorlandığını düşünüyorum.

İşte başka bir dava ve nasıl ele aldığımı:

<?xml version="1.0" ?><Tag1 xmlns="http://www.mynamespace.com/prefix">
<Tag2>content</Tag2></Tag1>

Önek içermeyen xmlns, ön düzeltilmemiş etiketlerin bu varsayılan ad alanını aldığı anlamına gelir. Bu, Tag2'yi aradığınızda bulmak için ad alanını eklemeniz gerektiği anlamına gelir. Ancak, lxml anahtar olarak Hiçbiri ile bir nsmap girişi oluşturur ve onu aramak için bir yol bulamadım. Böylece, böyle bir ad alanı sözlüğü oluşturdum

namespaces = {}
# response uses a default namespace, and tags don't mention it
# create a new ns map using an identifier of our choice
for k,v in root.nsmap.iteritems():
    if not k:
        namespaces['myprefix'] = v
e = root.find('myprefix:Tag2', namespaces)

3
Tam ad alanı URL'si , sabit kodlamanız gereken ad alanı tanımlayıcısıdır. Yerel önek ( owl) dosyadan dosyaya değişebilir. Bu nedenle bu cevabın önerisini yapmak gerçekten kötü bir fikirdir.
Matti Virkkunen

1
@MattiVirkkunen tam olarak baykuş tanımı dosyadan dosyaya değişebiliyorsa, sabit kodlama yerine her dosyada tanımlanan tanımı kullanmamalı mıyız?
Loïc Faure-Lacroix

@ LoïcFaure-Lacroix: Genellikle XML kütüphaneleri bu bölümü soyutlamanıza izin verir. Dosyanın kendisinde kullanılan öneki bilmeniz veya önemsemenize gerek yoktur, sadece ayrıştırmak için kendi önekinizi tanımlar veya tam ad alanı adını kullanırsınız.
Matti Virkkunen

bu cevap en azından find fonksiyonunu kullanabilmemde yardımcı oldu. Kendi önekinizi oluşturmanıza gerek yok. Ben sadece key = list (root.nsmap.keys ()) [0] yaptım ve sonra anahtarı önek olarak ekledim: root.find (f '{key}: Tag2', root.nsmap)
Eelco van Vliet

30

Not : Bu, sabit kodlanmış ad alanları kullanmadan Python'un ElementTree standart kütüphanesi için yararlı bir cevaptır.

XML verilerinden ad alanının öneklerini ve URI'sini ayıklamak için ElementTree.iterparse, yalnızca ad alanı başlangıç ​​olaylarını ( başlangıç ) ayrıştırarak işlevi kullanabilirsiniz :

>>> from io import StringIO
>>> from xml.etree import ElementTree
>>> my_schema = u'''<rdf:RDF xml:base="http://dbpedia.org/ontology/"
...     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
...     xmlns:owl="http://www.w3.org/2002/07/owl#"
...     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
...     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
...     xmlns="http://dbpedia.org/ontology/">
... 
...     <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
...         <rdfs:label xml:lang="en">basketball league</rdfs:label>
...         <rdfs:comment xml:lang="en">
...           a group of sports teams that compete against each other
...           in Basketball
...         </rdfs:comment>
...     </owl:Class>
... 
... </rdf:RDF>'''
>>> my_namespaces = dict([
...     node for _, node in ElementTree.iterparse(
...         StringIO(my_schema), events=['start-ns']
...     )
... ])
>>> from pprint import pprint
>>> pprint(my_namespaces)
{'': 'http://dbpedia.org/ontology/',
 'owl': 'http://www.w3.org/2002/07/owl#',
 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 'xsd': 'http://www.w3.org/2001/XMLSchema#'}

Ardından sözlük, arama işlevlerine argüman olarak iletilebilir:

root.findall('owl:Class', my_namespaces)

1
Bu, lxml'e erişimi olmayan ve sabit kod ad alanına ihtiyaç duymayanlarımız için yararlıdır.
delrocco

1
Hatayı aldım: ValueError: write to closedbu satır için filemy_namespaces = dict([node for _, node in ET.iterparse(StringIO(my_schema), events=['start-ns'])]). Yanlış bir fikrin var mı?
Yuli

Muhtemelen hata ASCII dizelerini reddeden io.StringIO sınıfıyla ilgilidir. Tarifimi Python3 ile test etmiştim. Örnek dizeye unicode dize öneki 'u' eklendiğinde Python 2 (2.7) ile de çalışır.
Davide Brunato

Bunun yerine dict([...])diksiyon anlama özelliğini de kullanabilirsiniz.
Arminius

Bunun yerine StringIO(my_schema)XML dosyasının dosya adını da koyabilirsiniz.
JustAC0der

6

Ben buna benzer kod kullanıyorum ve her zaman her zamanki gibi belgeleri okumaya değer bulduk!

findall () yalnızca geçerli etiketin doğrudan alt öğeleri olan öğeleri bulur . Yani, gerçekten TÜM değil.

Özellikle büyük ve karmaşık xml dosyaları ile uğraşıyorsanız, özellikle de alt alt öğelerin (vb.) Dahil edilmesi için kodunuzun aşağıdakilerle çalışmasını sağlamaya değer. Xml'nizdeki öğelerin nerede olduğunu kendiniz biliyorsanız, o zaman iyi olacağını varsayalım! Sadece bunun hatırlanmaya değer olduğunu düşündüm.

root.iter()

ref: https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall (), yalnızca geçerli öğenin doğrudan alt öğesi olan bir etikete sahip öğeleri bulur. Element.find () belirli bir etikete sahip ilk alt öğeyi bulur ve Element.text öğenin metin içeriğine erişir. Element.get () öğenin niteliklerine erişir: "


6

Ad alanını kendi ad alanı biçiminde almak için, örneğin {myNameSpace}aşağıdakileri yapabilirsiniz:

root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)

Bu şekilde, daha sonra kodunuzda, örneğin dize enterpolasyonu (Python 3) kullanarak düğümleri bulmak için kullanabilirsiniz.

link = root.find(f"{ns}link")

0

Benim çözümüm @Martijn Pieters'ın yorumuna dayanıyor:

register_namespace yalnızca serileştirmeyi etkiler, aramayı değil.

Buradaki hile, serileştirme ve arama için farklı sözlükler kullanmaktır.

namespaces = {
    '': 'http://www.example.com/default-schema',
    'spec': 'http://www.example.com/specialized-schema',
}

Şimdi, ayrıştırmak ve yazmak için tüm ad alanlarını kaydedin:

for name, value in namespaces.iteritems():
    ET.register_namespace(name, value)

Arama için ( find(), findall(), iterfind()) biz boş olmayan bir önek gerekir. Bu işlevleri değiştirilmiş bir sözlük geçirin (burada orijinal sözlüğü değiştiriyorum, ancak bu yalnızca ad alanları kaydedildikten sonra yapılmalıdır).

self.namespaces['default'] = self.namespaces['']

Şimdi, find()ailenin işlevleri defaultönek ile kullanılabilir :

print root.find('default:myelem', namespaces)

fakat

tree.write(destination)

varsayılan ad alanındaki öğeler için herhangi bir önek kullanmaz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.