'एलीमेंटट्री' के माध्यम से पाइथन में नेमस्पेस के साथ XML को पार्स करना


163

मेरे पास निम्नलिखित XML है जिसे मैं पायथन के उपयोग से पार्स करना चाहता हूं 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>

मैं सभी owl:Classटैग ढूंढना चाहता हूं और फिर rdfs:labelउनके अंदर के सभी उदाहरणों का मूल्य निकालना चाहता हूं। मैं निम्नलिखित कोड का उपयोग कर रहा हूं:

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

नाम स्थान के कारण, मुझे निम्न त्रुटि मिल रही है।

SyntaxError: prefix 'owl' not found in prefix map

मैंने http://effbot.org/zone/element-namespaces.htm पर दस्तावेज़ पढ़ने की कोशिश की, लेकिन मैं अभी भी यह काम नहीं कर पा रहा हूँ क्योंकि उपरोक्त XML में कई नेस्टेड नेमस्पेस हैं।

कृपया मुझे बताएं कि सभी owl:Classटैग खोजने के लिए कोड को कैसे बदलना है ।

जवाबों:


226

ElementTree नामस्थानों के बारे में बहुत स्मार्ट नहीं है। आपको एक स्पष्ट नाम स्थान शब्दकोश देने की जरूरत है .find(), findall()और iterfind()तरीके। यह बहुत अच्छी तरह से प्रलेखित नहीं है:

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

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

उपसर्ग केवल उस namespacesपैरामीटर में देखे जाते हैं जिसे आप पास करते हैं। इसका मतलब है कि आप अपनी पसंद के किसी भी नामस्थान उपसर्ग का उपयोग कर सकते हैं; एपीआई owl:भाग से अलग हो जाता है , namespacesशब्दकोश में संबंधित नाम स्थान URL को देखता है, फिर {http://www.w3.org/2002/07/owl}Classबदले में XPath अभिव्यक्ति की तलाश करने के लिए खोज को बदल देता है । आप खुद भी उसी वाक्य रचना का उपयोग कर सकते हैं:

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

यदि आप lxmlलाइब्रेरी में जा सकते हैं तो चीजें बेहतर होंगी; वह लाइब्रेरी समान ElementTree API का समर्थन करता है, लेकिन .nsmapतत्वों पर एक विशेषता में आपके लिए नामस्थान एकत्र करता है ।


7
धन्यवाद। किसी भी विचार कैसे मैं XML से सीधे नाम स्थान प्राप्त कर सकते हैं, बिना हार्ड-कोडिंग के? या मैं इसे कैसे अनदेखा कर सकता हूं? मैंने खोज ('{*} वर्ग') की कोशिश की है, लेकिन यह मेरे मामले में काम नहीं करेगा।
कोस्टानोस

7
आपको xmlnsविशेषताओं के लिए पेड़ को स्कैन करना होगा ; जैसा कि उत्तर में कहा गया है, lxmlयह आपके लिए है, xml.etree.ElementTreeमॉड्यूल नहीं करता है। लेकिन यदि आप एक विशिष्ट (पहले से ही हार्डकोड) तत्व से मेल खाने की कोशिश कर रहे हैं, तो आप किसी विशिष्ट नाम स्थान में एक विशिष्ट तत्व से मेल खाने की कोशिश कर रहे हैं। तत्व नाम से किसी भी अधिक दस्तावेजों के बीच नाम स्थान बदलने वाला नहीं है। आप तत्व नाम के साथ हार्डकोड भी कर सकते हैं।
मार्टिन पीटर्स

14
@ जॉन: register_namespaceकेवल क्रमबद्धता को प्रभावित करता है, खोज को नहीं।
मार्टिन पीटर्स

5
छोटा जोड़ जो उपयोगी हो सकता है: का उपयोग cElementTreeकरते समय ElementTree, findallकीवर्ड तर्क के रूप में नामस्थान नहीं लेगा, बल्कि सामान्य तर्क के रूप में उपयोग करेगा ctree.findall('owl:Class', namespaces)
egpbos

2
@ बल्डवर्फ़: डॉक्स इसका उल्लेख करते हैं (अब, अगर आपने ऐसा नहीं लिखा है), लेकिन आपको उन्हें ध्यान से पढ़ना होगा। Namespaces सेक्शन के साथ पार्सिंग XML देखें : findallबिना namespaceतर्क के उपयोग के विपरीत और फिर तर्क के साथ एक उदाहरण है, लेकिन तत्व ऑब्जेक्ट सेक्शन में विधि पद्धति के तर्कों में से एक के रूप में तर्क का उल्लेख नहीं किया गया है ।
विल्सन एफ

57

यहाँ बिना नाम-पत्र के हार्ड-कोड किए या उनके लिए पाठ स्कैन किए बिना lxml के साथ ऐसा करने का तरीका बताया गया है (जैसा कि मार्टिज़न पीटर उल्लेख करते हैं):

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

अद्यतन :

5 साल बाद भी मैं इस मुद्दे पर बदलाव कर रहा हूं। जैसा कि मैंने ऊपर दिखाया है, लेकिन हर मामले में lxml मदद करता है। इस तकनीक के बारे में टिप्पणीकारों के पास एक मान्य बिंदु हो सकता है जब यह दस्तावेजों के विलय की बात आती है, लेकिन मुझे लगता है कि ज्यादातर लोगों को बस दस्तावेजों को खोजने में कठिनाई हो रही है।

यहाँ एक और मामला है और मैंने इसे कैसे संभाला:

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

बिना उपसर्ग के xmlns का अर्थ है कि उपसर्गित टैग्स को यह डिफ़ॉल्ट नाम स्थान मिलता है। इसका मतलब यह है कि जब आप टैग 2 की खोज करते हैं, तो आपको इसे खोजने के लिए नामस्थान को शामिल करना होगा। हालांकि, lxml कुंजी के रूप में कोई नहीं के साथ एक nsmap प्रविष्टि बनाता है, और मैं इसके लिए खोज करने का कोई तरीका नहीं ढूंढ सका। इसलिए, मैंने एक नया नामस्थान शब्दकोश बनाया

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
पूर्ण नाम स्थान URL एक ऐसा नाम पहचानकर्ता है जिसे आप हार्ड-कोड के लिए चाहते हैं। स्थानीय उपसर्ग ( owl) फ़ाइल से फ़ाइल में बदल सकता है। इसलिए यह उत्तर क्या करता है यह वास्तव में एक बुरा विचार है।
मत्ती वीरकुंकेन

1
@MattiVirkkunen अगर उल्लू की परिभाषा फ़ाइल से फ़ाइल में बदल सकती है, तो क्या हमें हार्डकोडिंग के बजाय प्रत्येक फ़ाइलों में परिभाषित परिभाषा का उपयोग नहीं करना चाहिए?
लूस्क फॉरेस-लैक्रोसिक्स

@ Lo @cFaure-Lacroix: आमतौर पर XML लाइब्रेरीज़ आपको उस हिस्से को अमूर्त करने देती हैं। आपको फ़ाइल में उपयोग किए गए उपसर्ग के बारे में भी जानने या देखभाल करने की आवश्यकता नहीं है, आप केवल पार्सिंग के उद्देश्य के लिए अपने स्वयं के उपसर्ग को परिभाषित करते हैं या केवल पूर्ण नामस्थान नाम का उपयोग करते हैं।
मैटी विर्ककुनेन

इस उत्तर ने मुझे खोज फ़ंक्शन का उपयोग करने में कम से कम सक्षम होने में मदद की। अपने स्वयं के उपसर्ग बनाने की आवश्यकता नहीं है। मैंने सिर्फ key = list (root.nsmap.keys ()) [0] किया और फिर उपसर्ग के रूप में कुंजी जोड़ी: root.find (f '{key}: Tag2', root.nsmap)
Eelco van Vliet

30

नोट : यह हार्डकोडेड नेमस्पेस का उपयोग किए बिना पायथन के एलीमेंटट्री मानक पुस्तकालय के लिए एक उत्तर उपयोगी है।

XML डेटा से नेमस्पेस के उपसर्ग और यूआरआई निकालने के लिए ElementTree.iterparse, आप फ़ंक्शन का उपयोग कर सकते हैं , केवल नेमस्पेस स्टार्ट इवेंट ( स्टार्ट-एनएस ) पार्सिंग :

>>> 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#'}

तब शब्दकोश को खोज कार्यों के तर्क के रूप में पारित किया जा सकता है:

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

1
यह हम में से उन लोगों के लिए उपयोगी है जो बिना lxml के उपयोग के हैं और हार्डकोड नेमस्पेस के बिना चाहते हैं।
डेलरोको

1
मुझे त्रुटि मिली: ValueError: write to closedइस पंक्ति के लिए filemy_namespaces = dict([node for _, node in ET.iterparse(StringIO(my_schema), events=['start-ns'])])। कोई भी विचार गलत चाहता है?
युली

संभवतः त्रुटि वर्ग io.StringIO से संबंधित है, जो ASCII तार को मना कर देता है। मैंने पायथन 3 के साथ अपने नुस्खा का परीक्षण किया था। यूनिकोड स्ट्रिंग प्रीफिक्स 'यू' को सैंपल स्ट्रिंग में जोड़ना यह पायथन 2 (2.7) के साथ भी काम करता है।
डेविड ब्रुनाटो

इसकी जगह dict([...])आप ताना-बाना का भी इस्तेमाल कर सकते हैं।
आर्मिनियस

इसके बजाय StringIO(my_schema)आप XML फ़ाइल का फ़ाइल नाम भी डाल सकते हैं।
JustAC0der

6

मैं इस तरह के कोड का उपयोग कर रहा हूं और पाया है कि यह हमेशा दस्तावेज को पढ़ने के लायक है ... हमेशा की तरह!

findall () में केवल वे तत्व मिलेंगे जो वर्तमान टैग के प्रत्यक्ष बच्चे हैं । तो, वास्तव में सभी नहीं।

आपके कोड को निम्नलिखित के साथ काम करने की कोशिश करते समय यह आपके लायक हो सकता है, खासकर यदि आप बड़ी और जटिल xml फ़ाइलों के साथ काम कर रहे हैं ताकि उप-उप-तत्व (आदि) भी शामिल हों। यदि आप स्वयं जानते हैं कि तत्व आपके xml में कहाँ हैं, तो मुझे लगता है कि यह ठीक हो जाएगा! बस यह याद रखने लायक था।

root.iter()

रेफरी: https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall () केवल उन तत्वों को टैग के साथ पाता है जो वर्तमान तत्व के प्रत्यक्ष बच्चे हैं। Element.find () एक विशेष टैग के साथ पहले बच्चे को पाता है, और Element.text तत्व की पाठ सामग्री तक पहुँच प्राप्त करता है। तत्व (तत्व) तत्व के गुणों को एक्सेस करता है: "


6

नामस्थान को उसके नाम स्वरूप में प्राप्त करने के लिए, उदाहरण के लिए {myNameSpace}, आप निम्न कार्य कर सकते हैं:

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

इस तरह, आप नोड कोड खोजने के लिए अपने कोड पर बाद में इसका उपयोग कर सकते हैं, उदाहरण के लिए स्ट्रिंग प्रक्षेप (पायथन 3) का उपयोग कर।

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

0

मेरा समाधान @Martijn पीटर की टिप्पणी पर आधारित है:

register_namespace केवल क्रमबद्धता को प्रभावित करता है, खोज को नहीं।

तो यहाँ चाल को क्रमांकन के लिए और खोज के लिए विभिन्न शब्दकोशों का उपयोग करना है।

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

अब, पार्सिंग और लेखन के लिए सभी नामस्थानों को पंजीकृत करें:

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

खोज के लिए ( find(), findall(), iterfind()) हम एक गैर खाली उपसर्ग की जरूरत है। इन फ़ंक्शंस को संशोधित शब्दकोश में पास करें (यहाँ मैं मूल शब्दकोश को संशोधित करता हूँ, लेकिन नामस्थान पंजीकृत होने के बाद ही इसे बनाया जाना चाहिए)।

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

अब, उपसर्ग के find()साथ परिवार के कार्यों का उपयोग किया जा सकता है default:

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

परंतु

tree.write(destination)

डिफ़ॉल्ट नाम स्थान में तत्वों के लिए किसी भी उपसर्ग का उपयोग नहीं करता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.