python : How to navigate XML sub-nodes efficiently?

标签 python xml lxml elementtree xmltodict

我正在尝试从 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> )?

最佳答案

How to efficiently navigate child-nodes with multiple child-nodes under them?

导航 XML 的一个好方法是使用 XPath 。 ElementTree 有 limited XPath support ,但它看起来足以满足您的需要。如果您最终需要使用更复杂的 XPath,我建议使用 XPath in lxml

How to handle duplicate tags (ex: <id>, <given>)?

这取决于您需要如何处理这些元素。例如,如果您希望每个 id 元素都有单独的行,则需要迭代每个元素(使用 ElementTree 中的 findall() 或 lxml 中的 xpath())。

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

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

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

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

这将允许您将 XML 中的值存储在字典中以写入行。您可以为新行创建字典的新副本,同时维护通用值(例如 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/patentRole/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

关于 python : How to navigate XML sub-nodes efficiently?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55737318/

相关文章:

python - 当我不想让 Luigi 输出文件但显示任务已完成时怎么办?

python:使用多处理时访问变量的问题

c# - XStreamingElement 作为 XDocument 的一部分

html - XML 样式和可读性 API

python - 警报信号未在无限循环中触发

python - 模块 'darknet' 没有属性 'load_network'

android - 使用真实设备时运行布局

python - 使用 Xpath、Python 从网站提取信息

python - 使用 lxml 的 etree 创建文档类型

python - 如何在 python lxml 包中的 XPATH 表达式中设置数组索引