首页 > 解决方案 > Python:如何有效地导航 XML 子节点?

问题描述

我正在尝试从 XML 中提取某些数据点并尝试了两个选项...

  1. 使用 ElementTree 处理 XML 格式
  2. 使用 xmltodict 处理字典

这是我到目前为止所得到的,

代码

# Packages
# --------------------------------------
import xml.etree.ElementTree as ET

# XML Data
# --------------------------------------
message_xml = \
'<ClinicalDocument> \
    <code code="34133-9" displayName="Summarization of Episode Note"/> \
    <title>Care Summary</title> \
    <recordTarget> \
        <patientRole> \
            <id assigningAuthorityName="LOCAL" extension="L123456"/> \
            <id assigningAuthorityName="SSN" extension="788889999"/> \
            <id assigningAuthorityName="GLOBAL" extension="G123456"/> \
            <addr use="HP"> \
                <streetAddressLine>1000 N SOME AVENUE</streetAddressLine> \
                <city>BIG CITY</city> \
                <state>NA</state> \
                <postalCode>12345-1010</postalCode> \
                <country>US</country> \
            </addr> \
            <telecom nullFlavor="NI"/> \
            <patient> \
                <name use="L"> \
                    <given>JANE</given> \
                    <given>JOE</given> \
                    <family>DOE</family> \
                </name> \
            </patient> \
        </patientRole> \
    </recordTarget> \
</ClinicalDocument>'

# Get Tree & Root
# --------------------------------------
tree = ET.ElementTree(ET.fromstring(message_xml))
root = tree.getroot()

# Iterate
# --------------------------------------
for node in root:

    tag = node.tag
    attribute = node.attrib

    # Get ClinicalDocument.code values
    if tag == 'code':
        document_code_code = attribute.get('code')
        document_code_name = attribute.get('displayName')

    else:
        pass

    # Get ClinicalDocument.recordTarget values
    if tag == 'recordTarget':

        for child in node.iter():

            # Multiple <id> tags
            record_target_local = ??
            record_target_ssn = ??
            record_target_global = ??

            # Multiple <given> tags
            record_target_name_first = ??
            record_target_name_middle = ??
            record_target_name_last = ??

    else:
        pass

预期产出

document_code,document_name,id_local,id_ssn,id_global,name_first, name_middle,name_last
34133-9,Summarization of Episode Note,L123456,788889999,G123456,JANE,JOE,DOE

可接受的输出

document_code,document_name,id_type,id,name_first,name_middle,name_last
34133-9,Summarization of Episode Note,LOCAL,L123456,JANE,JOE,DOE
34133-9,Summarization of Episode Note,SSN,788889999,JANE,JOE,DOE
34133-9,Summarization of Episode Note,GLOBAL,G123456,JANE,JOE,DOE

问题

  1. 如何有效地导航具有多个子节点的子节点?
  2. 如何处理重复的标签(例如:<id>、、<given>)?

标签: pythonxmllxmlelementtreexmltodict

解决方案


如何有效地导航具有多个子节点的子节点?

导航 XML 的一个好方法是使用XPath。ElementTree 对XPath 的支持有限,但它似乎足以满足您的需要。如果您最终需要使用更复杂的 XPath,我建议在 lxml 中使用 XPath

如何处理重复的标签(例如:<id>、、<given>)?

这取决于您需要对这些元素做什么。例如,如果您希望每个id元素有单独的行,则需要遍历每个元素(findall()在 ElementTree 或xpath()lxml 中)。

如果您只需要一个值(文本或属性值),则需要将其缩小到 XPath 中的单个元素。

例如,属性值等于的id元素将是。assigningAuthorityNameLOCALid[@assigningAuthorityName='LOCAL']

given元素有点棘手;你怎么知道一个是名字,一个是中间名?我能看到的唯一方法是位置;第一个given( given[1]) 是名字,第二个given( given[2]) 是第二个名字。你保证总是有两个given元素吗?如果没有,您可能需要进行一些检查或 try/except 语句来获得所需的输出。

此外,由于您正在创建 csv 输出,我建议使用csv 模块;特别是DictWriter

这将允许您将 XML 中的值存储在 dict 中以写入行。您可以为新行创建 dict 的新副本,同时保持常用值(如document_codedocument_name)。

这是一个将为每个recordTarget.

XML 输入(input.xml)

<ClinicalDocument> 
    <code code="34133-9" displayName="Summarization of Episode Note"/> 
    <title>Care Summary</title> 
    <recordTarget> 
        <patientRole> 
            <id assigningAuthorityName="LOCAL" extension="L123456"/> 
            <id assigningAuthorityName="SSN" extension="788889999"/> 
            <id assigningAuthorityName="GLOBAL" extension="G123456"/> 
            <addr use="HP"> 
                <streetAddressLine>1000 N SOME AVENUE</streetAddressLine> 
                <city>BIG CITY</city> 
                <state>NA</state> 
                <postalCode>12345-1010</postalCode> 
                <country>US</country> 
            </addr> 
            <telecom nullFlavor="NI"/> 
            <patient> 
                <name use="L"> 
                    <given>JANE</given> 
                    <given>JOE</given> 
                    <family>DOE</family> 
                </name> 
            </patient> 
        </patientRole> 
    </recordTarget>
</ClinicalDocument>

Python

import csv
import xml.etree.ElementTree as ET
from copy import deepcopy

values_template = {"document_code": "", "document_name": "", "id_local": "", "id_ssn": "",
                   "id_global": "", "name_first": "", "name_middle": "", "name_last": ""}

with open("output.csv", "w", newline="") as csvfile:
    csvwriter = csv.DictWriter(csvfile, delimiter=",", quoting=csv.QUOTE_MINIMAL,
                               fieldnames=[name for name in values_template])
    csvwriter.writeheader()

    tree = ET.parse('input.xml')

    values_template["document_code"] = tree.find("code").get("code")
    values_template["document_name"] = tree.find("code").get("displayName")

    for target in tree.findall("recordTarget"):

        values = deepcopy(values_template)

        values["id_local"] = target.find("patientRole/id[@assigningAuthorityName='LOCAL']").get("extension")
        values["id_ssn"] = target.find("patientRole/id[@assigningAuthorityName='SSN']").get("extension")
        values["id_global"] = target.find("patientRole/id[@assigningAuthorityName='GLOBAL']").get("extension")
        values["name_first"] = target.find("patientRole/patient/name/given[1]").text
        values["name_middle"] = target.find("patientRole/patient/name/given[2]").text
        values["name_last"] = target.find("patientRole/patient/name/family").text

        csvwriter.writerow(values)

CSV 输出(output.csv)

document_code,document_name,id_local,id_ssn,id_global,name_first,name_middle,name_last
34133-9,Summarization of Episode Note,L123456,788889999,G123456,JANE,JOE,DOE

这是另一个示例,它将为每个 recordTarget/patientRole/id 创建一个新行...

Python

import csv
import xml.etree.ElementTree as ET
from copy import deepcopy

values_template = {"document_code": "", "document_name": "", "id": "",
                   "name_first": "", "name_middle": "", "name_last": ""}

with open("output.csv", "w", newline="") as csvfile:
    csvwriter = csv.DictWriter(csvfile, delimiter=",", quoting=csv.QUOTE_MINIMAL,
                               fieldnames=[name for name in values_template])
    csvwriter.writeheader()

    tree = ET.parse('input.xml')

    values_template["document_code"] = tree.find("code").get("code")
    values_template["document_name"] = tree.find("code").get("displayName")

    for target in tree.findall("recordTarget"):

        values = deepcopy(values_template)

        values["name_first"] = target.find("patientRole/patient/name/given[1]").text
        values["name_middle"] = target.find("patientRole/patient/name/given[2]").text
        values["name_last"] = target.find("patientRole/patient/name/family").text

        for role_id in target.findall("patientRole/id"):
            values["id"] = role_id.get("extension")
            csvwriter.writerow(values)

CSV 输出(output.csv)

document_code,document_name,id,name_first,name_middle,name_last
34133-9,Summarization of Episode Note,L123456,JANE,JOE,DOE
34133-9,Summarization of Episode Note,788889999,JANE,JOE,DOE
34133-9,Summarization of Episode Note,G123456,JANE,JOE,DOE

推荐阅读