Programming/python

[파이썬] Python에서 XML 파일 다루는법 - XPath에 대해서 알아보자.

방황하는 데이터불도저 2024. 5. 16. 20:28

XML 파일을 그냥 읽으면 parsing되지않고, 모두 이어진 텍스트로 읽혀 가독성이 매우 좋지않다.

 

이를 가독성 좋게 읽기 위해서는 우선 python에서 지원하는 xml parsing 도구를 사용할 수 있다.

보편적으로 아래와 같은 코드로 xml 파일을 읽어들이게 된다.

import xml.etree.ElementTree as ET

tree = ET.parse(xml_path)
root = tree.getroot()

 

아래의 코드로 root에 저장된 내용을 각 계층에 맞게 출력해주면 가독성 좋은 텍스트로 출력할 수 있다.

def print_xml_structure(elem, level=0):

    indent = '\t' * level
    print(f"{indent}<{elem.tag}", end='')
    
    if elem.attrib:
        print(" " + " ".join([f'{k}="{v}"' for k, v in elem.attrib.items()]), end="")
    
    # Print closing bracket
    print(">")
    
    if elem.text:
        print(f"{indent}  {elem.text.strip()}")
    
    # 재귀적으로 호출하여 모든 계층의 자식노드까지 출력해준다.
    for child in elem:
        print_xml_structure(child, level+1)
    
    print(f"{indent}</{elem.tag}>")

print_xml_structure(root)

 

가독성 좋은 xml

더보기
<annotation>
	<folder>
	  OXIIIT
	</folder>
	<filename>
	  Abyssinian_1.jpg
	</filename>
	<source>
		<database>
		  OXFORD-IIIT Pet Dataset
		</database>
		<annotation>
		  OXIIIT
		</annotation>
		<image>
		  flickr
		</image>
	</source>
	<size>
		<width>
		  600
		</width>
		<height>
		  400
		</height>
		<depth>
		  3
		</depth>
	</size>
	<segmented>
	  0
	</segmented>
	<object>
		<name>
		  cat
		</name>
		<pose>
		  Frontal
		</pose>
		<truncated>
		  0
		</truncated>
		<occluded>
		  0
		</occluded>
		<bndbox>
			<xmin>
			  333
			</xmin>
			<ymin>
			  72
			</ymin>
			<xmax>
			  425
			</xmax>
			<ymax>
			  158
			</ymax>
		</bndbox>
		<difficult>
		  0
		</difficult>
	</object>
</annotation>

 

여기에서 만약에 바로 xmin, ymin 과 같은 자식노드의 텍스트를 바로 접근하여 추출하고 싶다면 어떻게 할 수 있을까?

그럴 때는 아래와 같이 tag의 경로를 통해 바로 접근할 수 있다.

def find_text_by_tag(root, tag_name):
    # Find all elements with the specified tag
    elements = root.findall(f".//{tag_name}")
    # Extract and return the text of each element
    return [elem.text for elem in elements if elem is not None]
    
tag_names = ['xmin', 'ymin', 'xmax', 'ymax']

bbox = [int(find_text_by_tag(root, tag_name)[0]) for tag_name in tag_names]
bbox
# [333, 72, 425, 158]

 

그렇다면 findall에 들어가는 경로 문자열은 어떤 규칙을 입력할 수 있을까?

 

이 때 XPath가 사용된다. XML 문서 내의 특정 부분을 선택하거나 검색할 때 사용되는 언어이다. 경로 표현식(path expression)을 사용해서 XML 문서의 노드(node)를 선택하게 된다. 

 

XPath 기본 문법과 형식

기본 구문

  • /: 루트 노드를 선택합니다.
  • //: 현재 노드의 자손 노드 중 모든 노드를 선택합니다.
  • .: 현재 노드를 선택합니다.
  • ..: 현재 노드의 부모 노드를 선택합니다.
  • @: 속성을 선택합니다.

예제 XML

더보기
<library>
    <section>
        <shelf>
            <book>
                <title>Python Programming</title>
                <author>John Doe</author>
                <year>2020</year>
                <info>
                    <publisher>XYZ Press</publisher>
                    <isbn>1234567890</isbn>
                </info>
            </book>
        </shelf>
    </section>
    <section>
        <shelf>
            <book>
                <title>Data Science Handbook</title>
                <author>Jane Smith</author>
                <year>2018</year>
                <info>
                    <publisher>ABC Press</publisher>
                    <isbn>0987654321</isbn>
                </info>
            </book>
        </shelf>
    </section>
</library>

예제 XPath 표현식

더보기
  1. 루트 노드 선택:
    • /library: 루트 노드인 library를 선택합니다.
  2. 모든 section 노드 선택:
    • //section: 문서 내의 모든 section 노드를 선택합니다.
  3. 첫 번째 book 노드의 title 선택:
    • /library/section/shelf/book/title: 첫 번째 book 노드의 title 요소를 선택합니다.
  4. 모든 title 요소 선택:
    • //title: 문서 내의 모든 title 요소를 선택합니다.
  5. 특정 속성 선택:
    • //book[@year="2020"]: year 속성이 2020인 모든 book 요소를 선택합니다.
  6. 부모 노드 선택:
    • //title/..: 모든 title 요소의 부모 노드를 선택합니다.
  7. 속성 값 선택:
    • //book/title/@lang: 모든 book 요소 내 title 요소의 lang 속성 값을 선택합니다 (속성 예제 필요).