पायथन एलिमेंटट्री मॉड्यूल: "खोज", "फाइंडॉल" विधि का उपयोग करते समय मिलान तत्व का पता लगाने के लिए XML फ़ाइलों के नाम स्थान की उपेक्षा कैसे करें


136

मैं ElementTree मॉड्यूल में स्रोत xml फ़ाइल के कुछ तत्वों का पता लगाने के लिए "findall" की विधि का उपयोग करना चाहता हूं।

हालाँकि, स्रोत xml फ़ाइल (test.xml) में नेमस्पेस है। मैं नमूने के रूप में xml फ़ाइल का हिस्सा छोटा करता हूं:

<?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>

नमूना पायथन कोड नीचे है:

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>

यद्यपि यह काम कर सकता है, क्योंकि एक नामस्थान "{http://www.test.com}" है, प्रत्येक टैग के सामने नामस्थान जोड़ने के लिए बहुत असुविधाजनक है।

"खोज", "खोज" और इसी तरह की विधि का उपयोग करते समय मैं नाम स्थान की उपेक्षा कैसे कर सकता हूं?


18
है tree.findall("xmlns:DEAL_LEVEL/xmlns:PAID_OFF", namespaces={'xmlns': 'http://www.test.com'})सुविधाजनक पर्याप्त?
iMom0

बहुत बहुत धन्यवाद। मैं आपके तरीके की कोशिश करता हूं और यह काम कर सकता है। यह मेरी तुलना में अधिक सुविधाजनक है लेकिन यह अभी भी थोड़ा अजीब है। क्या आप जानते हैं कि इस समस्या को हल करने के लिए ElementTree मॉड्यूल में कोई अन्य उचित विधि नहीं है या ऐसी कोई विधि नहीं है?
केविनलेंग

या कोशिश करेंtree.findall("{0}DEAL_LEVEL/{0}PAID_OFF".format('{http://www.test.com}'))
वारफ

पायथन 3.8 में, नेमस्पेस के लिए वाइल्डकार्ड का उपयोग किया जा सकता है। stackoverflow.com/a/62117710/407651
mzjn

जवाबों:


62

एक्सएमएल दस्तावेज़ को स्वयं संशोधित करने के बजाय, इसे पार्स करना और फिर परिणाम में टैग को संशोधित करना सबसे अच्छा है। इस तरह आप कई नामस्थान और नामस्थान उपनामों को संभाल सकते हैं:

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

यह यहाँ चर्चा पर आधारित है: http://bugs.python.org/issue18304

अद्यतन: rpartition इसके बजाय partitionयह सुनिश्चित करता है कि आपको postfixकोई नामस्थान न होने के बावजूद टैग नाम मिल जाए। इस प्रकार आप इसे गाढ़ा कर सकते हैं:

for _, el in it:
    _, _, el.tag = el.tag.rpartition('}') # strip ns

2
यह। यह यह। मल्टीपल नेम स्पेस में मेरी मौत होने वाली थी।
जेस

8
ठीक है, यह अच्छा और अधिक उन्नत है, लेकिन फिर भी यह नहीं है et.findall('{*}sometag')। और यह स्वयं भी तत्व पेड़ का प्रबंधन कर रहा है, न केवल "इस बार सिर्फ नाम की अनदेखी करने वाले नामस्थानों को निष्पादित करें, दस्तावेज को फिर से पार्स किए बिना, नामस्थान की जानकारी को बनाए रखते हुए"। ठीक है, उस स्थिति के लिए आपको पेड़ के माध्यम से पुनरावृत्ति करने की आवश्यकता है, और अपने आप को देखें, यदि नोड नाम स्थान को हटाने के बाद आपकी इच्छाओं से मेल खाता है।
टॉमस गैंडर

1
यह स्ट्रिंग को स्ट्रिप करके काम करता है लेकिन जब मैं लिखने का उपयोग करके XML फ़ाइल को सहेजता हूं (तो ...) नेमस्पेस नाम XML xmlns = " bla " डिसएपर्स की भीख से गायब हो जाता है। कृपया सलाह दें
TraceKira

@TomaszGandor: आप नेमस्पेस को एक अलग विशेषता में जोड़ सकते हैं, शायद। सरल टैग नियंत्रण परीक्षणों के लिए ( क्या इस दस्तावेज़ में यह टैग नाम है? ) यह समाधान बहुत अच्छा है और इसे कम परिचालित किया जा सकता है।
मार्टिन पीटर्स

@TraceKira: यह तकनीक पार्स किए गए डॉक्यूमेंट से नेमस्पेस को हटाती है, और आप नेमसपेस के साथ एक नया XML स्ट्रिंग बनाने के लिए इसका उपयोग नहीं कर सकते। या तो एक अतिरिक्त विशेषता में नेमस्पेस मान संग्रहीत करें (और XML ट्री को वापस स्ट्रिंग में बदलने से पहले नेमस्पेस वापस डाल दें) या स्ट्रिप किए गए ट्री के आधार पर परिवर्तन लागू करने के लिए मूल स्रोत से पुनः पार्स करें।
मार्टिन पीटर्स

48

यदि आप xmlns को xml से पार्स करने से पहले उसकी विशेषता को हटा देते हैं तो पेड़ में प्रत्येक टैग के लिए एक नाम स्थान नहीं होगा।

import re

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

5
इसने मेरे लिए कई मामलों में काम किया, लेकिन फिर मैं कई नामस्थान और नामस्थान उपनामों में भाग गया। एक अन्य दृष्टिकोण के लिए मेरा जवाब देखें जो इन मामलों को संभालता है।
गैर-

47
-1 parsing से पहले एक नियमित अभिव्यक्ति के माध्यम से xml में हेरफेर करना सिर्फ गलत है। हालांकि यह कुछ मामलों में काम कर सकता है, यह शीर्ष मतदान का जवाब नहीं होना चाहिए और इसका उपयोग पेशेवर अनुप्रयोग में नहीं किया जाना चाहिए।
माइक

1
इस तथ्य के अलावा कि एक्सएमएल पार्सिंग जॉब के लिए रेगेक्स का उपयोग करना स्वाभाविक है, यह कई एक्सएमएल दस्तावेजों के लिए काम नहीं करने वाला है , क्योंकि यह नेमस्पेस प्रीफिक्स को नजरअंदाज करता है, और एक्सएमएल सिंटैक्स विशेषता नामों से पहले व्हाट्सएप को मनमाने ढंग से भरने की अनुमति देता है (न कि सिर्फ रिक्त स्थान) और उसके चारों ओर =बराबर चिह्न हैं।
मार्टिन पीटर्स

हां, यह त्वरित और गंदा है, लेकिन यह निश्चित रूप से सरल उपयोग के मामलों के लिए सबसे सुरुचिपूर्ण समाधान है, धन्यवाद!
रिमक्शॉक्स

18

अब तक के जवाबों ने पटकथा में नाम स्थान के मान को स्पष्ट रूप से रखा है। अधिक सामान्य समाधान के लिए, मैं xml से नाम स्थान निकालना चाहूंगा:

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

और इसे खोजने की विधि में उपयोग करें:

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

15
यह मानने के लिए कि केवल एक हैnamespace
कश्यप

यह ध्यान नहीं रखता है कि नेस्टेड टैग विभिन्न नामस्थानों का उपयोग कर सकते हैं।
मार्टिन पीटर्स

15

यहाँ गैर-उत्तर के विस्तार के बारे में बताया गया है, जो विशेषताओं के नामस्थानों को भी अलग करता है:

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 list(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

अद्यतन: जोड़ी list()गई इट्रेटर कार्य (पायथन 3 के लिए आवश्यक)


14

Ericspod द्वारा उत्तर पर सुधार:

विश्व स्तर पर पार्स मोड को बदलने के बजाय हम निर्माण के साथ समर्थन करने वाले ऑब्जेक्ट में इसे लपेट सकते हैं।

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

यह तो निम्नानुसार इस्तेमाल किया जा सकता है

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

इस तरह की सुंदरता यह है कि यह ब्लॉक के साथ असंबंधित कोड के लिए किसी भी व्यवहार को नहीं बदलता है। मैंने एरिक्सपोड द्वारा संस्करण का उपयोग करने के बाद असंबंधित पुस्तकालयों में त्रुटियां प्राप्त करने के बाद इसे बनाना समाप्त कर दिया जो कि एक्सपैट का उपयोग करने के लिए भी हुआ।


यह मीठा और स्वस्थ है! मेरा दिन बचाया! +1
एंड्रियासटी

पायथन 3.8 में (अन्य संस्करणों के साथ परीक्षण नहीं किया गया है) यह मेरे लिए काम नहीं करता है। स्रोत को देखते हुए यह काम करना चाहिए , लेकिन ऐसा लगता है कि स्रोत कोड xml.etree.ElementTree.XMLParserकिसी तरह से अनुकूलित है और बंदर-पैचिंग expatका कोई प्रभाव नहीं है।
Reinderien

आह बिल्कुल। : @ Barny की टिप्पणी देखें stackoverflow.com/questions/13412496/...
Reinderien

5

आप सुरुचिपूर्ण स्ट्रिंग स्वरूपण निर्माण का उपयोग कर सकते हैं:

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

या, यदि आप सुनिश्चित हैं कि PAID_OFF केवल पेड़ में एक स्तर पर दिखाई देता है:

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

2

यदि आप उपयोग कर रहे हैं ElementTreeऔर नहीं cElementTreeतो आप एक्सपैट को नाम स्थान प्रसंस्करण को अनदेखा करने के लिए बाध्य कर सकते हैं ParserCreate():

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

ElementTreeएक्सपैट को कॉल करके उपयोग करने की कोशिश करता है, ParserCreate()लेकिन नेमस्पेस सेपरेटर स्ट्रिंग प्रदान नहीं करने का कोई विकल्प प्रदान नहीं करता है, उपरोक्त कोड इसे अनदेखा करने का कारण होगा लेकिन चेतावनी दी जाए कि यह अन्य चीजों को तोड़ सकता है।


यह अन्य वर्तमान उत्तरों की तुलना में एक बेहतर तरीका है क्योंकि यह स्ट्रिंग प्रसंस्करण पर निर्भर नहीं करता है
लिजाट

3
अजगर 3.7.2 (और संभवतया ईयरलर) AFAICT में cElementTree का उपयोग करने से बचना संभव नहीं है, इसलिए यह समाधान संभव नहीं हो सकता है :-(
barny

1
cElemTree को हटा दिया गया है लेकिन C त्वरक के साथ किए जा रहे प्रकारों की छायांकन है । C कोड एक्सपैट में कॉल नहीं कर रहा है इसलिए हां यह समाधान टूट गया है।
ericspod

@ बर्नी यह अभी भी संभव है, ElementTree.fromstring(s, parser=None)मैं इसे पार्सर पास करने की कोशिश कर रहा हूं।
एस्ट

2

मुझे इसके लिए देर हो सकती है लेकिन मुझे नहीं लगता कि re.subयह एक अच्छा समाधान है।

हालांकि फिर से लिखना xml.parsers.expatपायथन 3.x संस्करणों के लिए काम नहीं करता है,

मुख्य अपराधी xml/etree/ElementTree.pyस्रोत कोड का निचला हिस्सा है

# 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

जो थोड़े दुखी है।

इसका हल पहले निकालना है।

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)

पायथन 3.6 पर परीक्षण किया गया।

प्रयास करें tryबयान आप फिर से लोड या एक मॉड्यूल आयात दो बार आप की तरह कुछ अजीब त्रुटियों मिल अपने कोड में मामला कहीं में उपयोगी है

  • अधिकतम पुनरावृत्ति गहराई पार हो गई
  • विशेषता: XMLParser

btw लानत है etree स्रोत कोड वास्तव में गन्दा लगता है।


1

आइए गैर-जवाब को mzjn के संबंधित प्रश्न के उत्तर के साथ संयोजित करें :

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

इस फ़ंक्शन का उपयोग करते हुए हम:

  1. नाम स्थान और पार्स ट्री ट्री ऑब्जेक्ट दोनों को प्राप्त करने के लिए एक इटरेटर बनाएँ ।

  2. नाम स्थान को तानाशाही से मुक्त करने के लिए बनाए गए पुनरावृत्ति पर Iterate करें जिसे हम बाद में प्रत्येक में पास कर सकते हैं find()या iMom0 द्वारा sugested कह सकते हैंfindall()

  3. पार्स किए गए पेड़ की जड़ तत्व वस्तु और नामस्थान लौटाएं।

मुझे लगता है कि यह चारों ओर से सबसे अच्छा तरीका है क्योंकि किसी भी स्रोत एक्सएमएल का कोई हेरफेर नहीं है या जिसके परिणामस्वरूप xml.etree.ElementTreeआउटपुट पार्स हुआ है ।

मैं इस पहेली का एक आवश्यक टुकड़ा प्रदान करने के साथ बार्नी के जवाब को भी श्रेय देना चाहता हूं (कि आप इसे पुनरावृत्त रूट प्राप्त कर सकते हैं)। इससे पहले कि मैं वास्तव में अपने आवेदन में दो बार एक्सएमएल पेड़ का पता लगाया था (एक बार नाम स्थान पाने के लिए, एक रूट के लिए दूसरा)।


पता चला कि इसका उपयोग कैसे करना है, लेकिन यह मेरे लिए काम नहीं करता है, मैं अभी भी आउटपुट में नाम स्थान देखता हूं
taiko

1
ओप्पो के सवाल पर iMom0 की टिप्पणी को देखें । इस फ़ंक्शन का उपयोग करके आपको पार्स की गई ऑब्जेक्ट और इसे क्वेरी करने के साधन दोनों मिलते हैं find()और findall()। आप बस उन विधियों को नामस्थानों के अधिदेश से फ़ीड करते हैं parse_xml()और अपने प्रश्नों में नामस्थान के उपसर्ग का उपयोग करते हैं। जैसे:et_element.findall(".//some_ns_prefix:some_xml_tag", namespaces=xml_namespaces)
z33k
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.