Python ElementTree 모듈 : "find", "findall"메소드를 사용할 때 XML 파일의 네임 스페이스를 무시하여 일치하는 요소를 찾는 방법


136

"findall"메소드를 사용하여 ElementTree 모듈에서 소스 xml 파일의 일부 요소를 찾고 싶습니다.

그러나 소스 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}"이 있기 때문에 작동 할 수 있지만 각 태그 앞에 네임 스페이스를 추가하는 것은 매우 불편합니다.

"find", "findall"등의 메소드를 사용할 때 네임 스페이스를 무시하려면 어떻게해야합니까?


18
tree.findall("xmlns:DEAL_LEVEL/xmlns:PAID_OFF", namespaces={'xmlns': 'http://www.test.com'})편리 충분?
iMom0

매우 감사합니다. 나는 당신의 방법을 시도하고 작동 할 수 있습니다. 그것은 내 것보다 더 편리하지만 여전히 조금 어색합니다. ElementTree 모듈에이 문제를 해결하기위한 다른 적절한 방법이 없는지 또는 그러한 방법이 전혀 없는지 알고 있습니까?
KevinLeng

또는 시도tree.findall("{0}DEAL_LEVEL/{0}PAID_OFF".format('{http://www.test.com}'))
워프

Python 3.8에서는 네임 스페이스에 와일드 카드를 사용할 수 있습니다. stackoverflow.com/a/62117710/407651
mzjn

답변:


62

XML 문서 자체를 수정하는 대신 구문 분석 한 다음 결과에서 태그를 수정하는 것이 가장 좋습니다. 이 방법으로 여러 네임 스페이스 및 네임 스페이스 별칭을 처리 할 수 ​​있습니다.

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
이. 이것은 이것입니다. 여러 네임 스페이스가 저의 죽음이 될 것입니다.
Jess

8
좋아, 이것은 훌륭하고 고급이지만 여전히 그렇지 않습니다 et.findall('{*}sometag'). 또한 "문서 이름 등을 구문 분석하지 않고 네임 스페이스 정보를 유지하지 않고 이번에는 네임 스페이스를 무시하고 검색을 수행하는 것"뿐만 아니라 요소 트리 자체도 조작하고 있습니다. 글쎄,이 경우 네임 스페이스를 제거한 후 노드가 원하는대로 일치하면 트리를 반복하고 직접 참조해야합니다.
Tomasz Gandor

1
이것은 문자열을 제거하여 작동하지만 write (...)를 사용하여 XML 파일을 저장하면 XML xmlns = " bla "dissapears 의 구걸에서 네임 스페이스가 사라집니다 . 조언을 부탁드립니다
TraceKira

@TomaszGandor : 네임 스페이스를 별도의 속성에 추가 할 수 있습니다. 간단한 태그 억제 테스트 ( 이 문서에이 태그 이름이 포함되어 있습니까? )의 경우이 솔루션은 훌륭하며 단락 될 수 있습니다.
Martijn Pieters

@TraceKira :이 기술은 구문 분석 된 문서에서 네임 스페이스를 제거하므로 네임 스페이스를 사용하여 새 XML 문자열을 만드는 데 사용할 수 없습니다. 네임 스페이스 값을 추가 속성에 저장하고 XML 트리를 다시 문자열로 변환하기 전에 네임 스페이스를 다시 넣거나 원본 소스에서 다시 구문 분석하여 제거 된 트리를 기반으로 변경 사항을 적용하십시오.
Martijn Pieters

48

구문 분석하기 전에 xmlns에서 xmlns 특성을 제거하면 트리의 각 태그 앞에 네임 스페이스가 없습니다.

import re

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

5
이것은 많은 경우에 효과가 있었지만 여러 네임 스페이스와 네임 스페이스 별칭이 발생했습니다. 이러한 경우를 처리하는 다른 접근법에 대한 내 답변을 참조하십시오.
nonagon

47
구문 분석하기 전에 정규 표현식을 통해 XML을 조작하는 것은 잘못되었습니다. 경우에 따라 작동 할 수도 있지만,이 답변은 가장 많이 투표 된 답변이 아니어야하며 전문 응용 프로그램에 사용해서는 안됩니다.
Mike

1
XML 파싱 작업에 정규 표현식을 사용하는 것이 본질적으로 좋지 않다는 사실 외에도 네임 스페이스 접두사를 무시하고 XML 구문이 속성 이름 앞에 임의의 공백을 허용한다는 사실 때문에 많은 XML 문서 에서 작동 하지 않습니다. 공백) 및 =등호 주변 .
Martijn Pieters

예, 빠르고 더럽지 만 간단한 사용 사례를위한 가장 우아한 솔루션입니다. 감사합니다!
rimkashox

18

지금까지 답변은 네임 스페이스 값을 스크립트에 명시 적으로 넣었습니다. 보다 일반적인 솔루션을 위해 XML에서 네임 스페이스를 추출하려고합니다.

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

그리고 find 메소드에서 사용하십시오 :

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

15
하나만 있다고 가정하기에는 너무 많이namespace
Kashyap

중첩 태그가 다른 네임 스페이스를 사용할 수 있다는 점을 고려하지 않습니다.
Martijn Pieters

15

다음은 nonagon의 답변에 대한 확장이며, 네임 스페이스를 속성에서 제거합니다.

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()반복자가 작동하도록 추가되었습니다 (Python 3 필요)


14

ericspod의 답변 개선 :

구문 분석 모드를 전체적으로 변경하는 대신 with 구문을 지원하는 객체로 래핑 할 수 있습니다.

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")

이 방법의 장점은 with 블록 외부의 관련없는 코드에 대한 동작을 변경하지 않는다는 것입니다. 나는 expat를 사용하는 ericspod의 버전을 사용한 후 관련없는 라이브러리에서 오류를 얻은 후에 이것을 생성했습니다.


이것은 달콤하고 건강합니다! 내 하루를 구했다! +1
AndreasT

Python 3.8 (다른 버전으로 테스트하지 않은)에서는 이것이 작동하지 않는 것 같습니다. 이 소스를 보면 해야 작동하지만 소스 코드를 보인다 xml.etree.ElementTree.XMLParser어떻게 든 최적화 원숭이 패치되어 expat전혀 효과가 없습니다.
Reinderien

아 그래
@barny

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하고 있고 사용 하지 않는 경우 cElementTreeExpat이 다음을 대체하여 네임 스페이스 처리를 무시하도록 할 수 있습니다 ParserCreate().

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

ElementTreeExpat을 호출하여 사용하려고 ParserCreate()하지만 네임 스페이스 구분 기호 문자열을 제공하지 않는 옵션을 제공하지 않으면 위의 코드는이를 무시하지만 다른 문제가 발생할 수 있음을 경고합니다.


이것은 문자열 처리에 의존하지 않기 때문에 다른 현재 답변보다 더 나은 방법입니다.
lijat

3
python 3.7.2 (및 이전 버전) AFAICT에서는 더 이상 cElementTree 사용을 피할 수
없으므로이

1
cElemTree는 더 이상 사용되지 않지만 C 액셀러레이터로 수행되는 유형의 그림자가 있습니다 . C 코드는 expat을 호출하지 않으므로이 솔루션이 손상되었습니다.
ericspod

@ barny 여전히 가능 ElementTree.fromstring(s, parser=None)합니다. 파서를 전달하려고합니다.
est

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)

Python 3.6에서 테스트되었습니다.

try try문은 코드의 어딘가에 모듈을 다시로드하거나 가져 오는 경우에 유용합니다.

  • 최대 재귀 깊이 초과
  • AttributeError : XMLParser

btw 망할 etree 소스 코드는 정말 지저분 해 보인다.


1

nonagon의 답변관련 질문에 대한 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. 생성 된 이터레이터를 반복하여 나중에 각각 find()또는 iMom0에 의해 제안 된대로findall() 호출 하거나 전달할 수있는 네임 스페이스 dict을 얻습니다 .

  3. 파싱 ​​된 트리의 루트 요소 객체와 네임 스페이스를 반환합니다.

소스 XML이나 결과로 구문 분석 된 결과가 없기 때문에 이것이 최선의 방법이라고 생각합니다 xml.etree.ElementTree.

또한 이 퍼즐의 필수 부분 (반복자에서 파싱 된 루트를 얻을 수 있음)를 제공함으로써 barny의 답변 에 기여하고 싶습니다 . 그때까지 실제로 응용 프로그램에서 XML 트리를 두 번 통과했습니다 (한 번 네임 스페이스를 얻으려면 루트로 두 번).


난 여전히 출력에 네임 스페이스를 참조 그것을 사용하는,하지만 나를 위해 작동하지 않는 방법을 발견

1
OP의 질문에 대한 iMom0의 의견을 보십시오 . 이 기능을 사용하면 당신은 구문 분석 된 객체와 함께 조회 할 수있는 수단을 모두 얻을 수 find()findall(). 네임 스페이스의 dict을 parse_xml()사용 하여 해당 메소드에 피드를 제공 하고 쿼리에서 네임 스페이스의 접두사 를 사용하십시오. 예 :et_element.findall(".//some_ns_prefix:some_xml_tag", namespaces=xml_namespaces)
z33k
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.