본문 바로가기
Programming/python

파이썬 xml.etree.ElementTree로 CDATA 처리

by 조창대 2024. 2. 20.
반응형

xml 파일 형식을 처음 접해본 자의 처절..까진 아니지만 조금 고단했던 개발기록.

 

파이썬 xml.etree.ElementTree 모듈의 기본 설정으론 CDATA 마크업 처리를 하지 못한다.

그래서 CDATA를 마크업으로 처리하도록 ElementTree 파일을 수정하는 방법을 정리할 예정이다.

 

 

XML ?


 

파이썬으로 xml 파일을 쓰려면 우선 xml이 어떤 구조를 지니는지 파악해야 한다!!

gpt에 물어보니까 이렇게 친절하게 알려준다. 코딩하기 좋은 세상이여 ~

 

암튼 xml은 웹 사이트, 데이터 베이스와 같은 컴퓨터 시스템 간의 통신이 용이하도록 사전 정의된 규칙으로, HTML과 비슷하게 태그로 구별이 되어있다. 모든 네트워크에서 xml 파일로 데이터를 손쉽게 전송 가능하다. 

 

xml 구조는 다음과 같다.

<ecommerce>
  <item>
    <id>1234</id>
    <name>미니 건담</name>
    <price>26000</price>
    <sale_price>23500</sale_price>
    <description>- 조립이 필요한 상품입니다.제품 상세 이미지는 참고용으로, 도색 및 색감 연출 등에서 실제 제품과 차이가 있을 수 있습니다.</description>
  </item>
</ecommerce>

 

여기서 <ecommerce>가 최상위 요소이며, <item>은 최상위 요소에 포함된 하위 요소이다. 그리고 id / price / sale_price / description 은 item의 하위 요소이다. 

HTML과 같이 각 태그의 attribute도 설정 가능하다.

각 태그 안에는 태그 값으로 텍스트를 넣을 수 있다. 

CDATA ?


그런데 만약에 xml 문서가 다음과 같이 수정되었다고 하자.

<ecommerce>
  <item>
    <id>1234</id>
    <name>미니 건담</name>
    <price>26000</price>
    <sale_price>23500</sale_price>
    <description>- <strong>조립이 필요한 상품입니다.제품 상세 이미지는 참고용으로, 도색 및 색감 연출 등에서 <b>실제 제품과 차이<b>가 있을 수 있습니다.</description>
  </item>
</ecommerce>

 

<description> 요소의 텍스트에 <strong> 과 <b> 가 태그 형식으로 삽입되었다.

코드블럭으로도 알 수 있듯이, 해당 텍스트들은 xml 구조를 이루는 태그로 쓰지 않지만 태그로 인식되고 있다. 이렇게 태그로 쓰이지 않지만 xml 파서가 태그로 인식하게 되면 잘못된 파싱 결과를 불러올 수 있다.

 

이럴 때 쓰는게 'CDATA' 마크업 언어이다!

xml 요소로 잡히면 곤란한 텍스트를 <![CDATA[ 텍스트 ]]> 로 감싸주면 CDATA 안에 있는 텍스트가 태그 형식이라도 xml 요소로 인식하지 않고 텍스트 그 자체로 인식한다! 이 CDATA를 xml.etree.ElementTree 모듈에서 처리할 수 있도록 설정하는 방법을 알아보쟛

 

 

xml.etree.ElementTree 파일 수정


xml 모듈은 기본적으로 CDATA 를 escape 하도록 되어 있다.

이걸 수정해줘야 하기 때문에 xml.etree.ElementTree 파일을 찾아야 한다.

파이썬 설치할 때 경로를 따로 지정하지 않았다면 보통 이 경로에서 모듈 파일을 찾을 수 있을거다.

C:\Users\사용자이름\AppData\Local\Programs\Python\Python311\Lib\xml\etree

파일 열어서 '_escape_cdata' 메서드 찾아보면 

def _escape_cdata(text):
    # escape character data
    try:
        # it's worth avoiding do-nothing calls for strings that are
        # shorter than 500 characters, or so.  assume that's, by far,
        # the most common case in most applications.
        if "&" in text:
            text = text.replace("&", "&amp;")
        if "<" in text:
            text = text.replace("<", "&lt;")
        if ">" in text:
            text = text.replace(">", "&gt;")
        return text
    except (TypeError, AttributeError):
        _raise_serialization_error(text)

 

요런 메서드가 나온다.

"&" / "<" / ">" 특수 문장 부호를 태그로 인식하지 않고 텍스트로서 xml 파서가 인식하게끔

텍스트에 특수 문장 부호가 있으면 코드화해주는 메서드로 보인다.

이것 때문에 xml 요소의 텍스트로 <![CDATA[ 랑 ]]> 를 넣으면 자동으로 &lt;![CDATA[ ]]&gt; 로 변환되기 때문에 이게 마크업 언어라고 xml 파서가 인식하지 못한다.

CDATA가 필요한 경우엔 이 메서드를 찾아서 '<' 와 '>'에 대한 조건문을 주석처리해주면 된다.

def _escape_cdata(text):
    # escape character data
    try:
        # it's worth avoiding do-nothing calls for strings that are
        # shorter than 500 characters, or so.  assume that's, by far,
        # the most common case in most applications.
        if "&" in text:
            text = text.replace("&", "&amp;")
        # CDATA를 위한 주석 처리
        #if "<" in text:
            #text = text.replace("<", "&lt;")
        #if ">" in text:
            #text = text.replace(">", "&gt;")
        return text
    except (TypeError, AttributeError):
        _raise_serialization_error(text)

 

 

주석처리하고 저장하면.. 끝이다

xml 파일 작성 코드에서 요소의 텍스트 값을 cdata로 감싸서 텍스트 지정해주면 텍스트가 태그 형식으로 쓰여 있어도 태그로 인식되지 않게 작성이 된다.

아래는 xml 작성 코드이다.

import xml.etree.ElementTree as ET

# xml 예쁘게 추출
def _pretty_print(current, parent=None, index=-1, depth=0):
    for i, node in enumerate(current):
        _pretty_print(node, current, i, depth + 1)
    if parent is not None:
        if index == 0:
            parent.text = '\n' + ('\t' * depth)
        else:
            parent[index - 1].tail = '\n' + ('\t' * depth)
        if index == len(parent) - 1:
            current.tail = '\n' + ('\t' * (depth - 1))

# ----------------------------------
tag_name_list = ['id', 'name', 'price', 'sale_price', 'description']
data_list = ['1234', '미니건담', '26000', '23500', '- <strong>조립이 필요한 상품입니다.제품 상세 이미지는 참고용으로, 도색 및 색감 연출 등에서 <b>실제 제품과 차이<b>가 있을 수 있습니다.']


root = ET.Element('ecommerce')
element1 = ET.Element('item')
root.append(element1)


for i in range(len(tag_name_list)):
    ET.SubElement(element1, tag_name_list[i]).text = '<![CDATA[' + data_list[i] + ']]>' 
    

_pretty_print(root)
tree = ET.ElementTree(root)


file_name = "Example_cdata.xml"
with open(file_name, 'wb') as file:
    tree.write(file, encoding='utf-8', xml_declaration=True, short_empty_elements=False) # short ~ : text 공백인 태그 여는 태그/닫는 태그 생성 옵션
file.close()

 

tag_name_list와 data_list를 각각 만들어서 for문을 돌리며 요소에 맞는 텍스트 데이터가 매치되도록 코드를 짰다.

ET.SubElement(element1, tag_name_list[i]).text = '<![CDATA[' + data_list[i] + ']]>' 이 부분이 cdata를 감싸는 코드이다.

 

_pretty_print는 xml이 트리 구조로 쓰이도록 사용하는 메서드이다.

이렇게 xml 작성하면 아래와 같은 결과가 나온다.

<ecommerce>
  <item>
    <id><![CDATA[ 1234 ]]></id>
    <name><![CDATA[ 미니건담 ]]></name>
    <price><![CDATA[ 26000 ]]></price>
    <sale_price><![CDATA[ 23500 ]]></sale_price>
    <description><![CDATA[ - <strong>조립이 필요한 상품입니다.제품 상세 이미지는 참고용으로, 도색 및 색감 연출 등에서 <b>실제 제품과 차이<b>가 있을 수 있습니다. ]]></description>
  </item>
</ecommerce>

결과를 보면 description 요소의 텍스트에 <strong>과 <b> 가 포함되어 있어도

태그로 인식하지 않고 문자열로 인식하고 있다. 데이터 양이 많아서 데이터 전송할 때 모두 CDATA로 묶어야 될 경우에 유용한 방법이다! 

반응형