Python: Как эффективно перемещаться по XML-узлам? - PullRequest
1 голос
/ 18 апреля 2019

Я пытаюсь извлечь определенные точки данных из XML и попробовал два варианта ...

  1. Работа с форматом XML с использованием ElementTree
  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>)?

1 Ответ

1 голос
/ 22 апреля 2019

Как эффективно перемещаться по дочерним узлам с несколькими дочерними узлами под ними?

Хороший способ навигации по XML - это XPath .ElementTree имеет ограниченную поддержку XPath , но она выглядит достаточно хорошо для того, что вам нужно.Если вам в конечном итоге потребуется использовать более сложный XPath, я бы предложил использовать XPath в lxml .

Как обрабатывать дублирующиеся теги (например: <id>, <given>)?

Это зависит от того, что вам нужно делать с этими элементами.Например, если вам нужны отдельные строки для каждого элемента id, вам необходимо выполнить итерацию по каждому элементу (с findall() в ElementTree или xpath() в lxml).

Если вы просто хотитезначение (либо текст, либо значение атрибута), вам нужно сузить его до одного элемента в XPath.

Например, элемент id с атрибутом assigningAuthorityName, равным LOCAL, будет id[@assigningAuthorityName='LOCAL'].

Элемент given немного сложнее;как вы можете сказать, что одно имя, а второе отчество?Единственный способ увидеть это позиция;первое given (given[1]) является первым именем, а второе given (given[2]) является вторым именем.Вы гарантированно всегда имеете два given элемента?Если нет, то вам может потребоваться выполнить некоторые проверки или операторы / исключить, чтобы получить нужный вывод.

Кроме того, поскольку вы создаете вывод в формате csv, я бы рекомендовал использовать модуль csv * 1041.*;в частности DictWriter .

Это позволит вам хранить значения из XML в поле для записи строк.Вы можете создавать новые копии dict для новых строк, сохраняя общие значения (например, document_code и document_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

Вот еще один пример, который создаст новую строку для каждой записиTarget / 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
...