Seite 1 von 1

xml Parsing mit Element Tree: Attribut-Werte gemäss Excel-Tabelle ersetzten

Verfasst: Mittwoch 30. März 2022, 16:48
von agl
Ich möchte eine .xml Datei bearbeiten. Die Objekte im xml besitzen den Tag "Code", welcher anhand einer Excel-Liste ersetzt werden musste. Das klappte soweit:

Code: Alles auswählen

import xml.etree.ElementTree as ET
import pandas as pd
import uuid

Data = ET.parse('xml_file.xml')
root = Data.getroot()

## Read excel list
df = pd.read_excel('ReplaceList_UUID.xlsx')
print(df)

## Preparation of searchList
searchList = pd.concat([df['Code_alphanumeric']], axis=0, ignore_index=False)
searchList = searchList.reset_index(drop=True)            # reset index from 0 .. i
searchList = searchList.astype(str)
print(searchList)

## Preparation of output expressions
resultList = pd.concat([df['Code_numeric']], axis=0, ignore_index=False)
resultList = resultList.reset_index(drop=True)
resultList = resultList.astype(str)
print(resultList)

## Replacement function Codes
############################################
for i in range(len(searchList)):
    for Code in root.iter('Code'):                              # iterates over the child named 'Code' and builds callable object
        if Code.text == searchList[i]:                    # condition for replacement
           Code.text = str(resultList[i])                  # what to replace with
           ET.tostring(root)                                              # described input string should replace child value
        else:
            pass

Data.write(str('Code'))                                      # overwrites the defined child Code's
# Check if replacements worked
# for Code in root.iter('Code'):
#     print(Code.text)

## Write output xml file with Codes
#############################################
Data.write('xml_file.xml', encoding='utf-8')

Nun muss ich aber auch die ID's unter dem Attribut "TID" im xml verändern. Ich werde sich mit UUID ersetzen. Die UUIDs konnte ich erstellen. Allerdings schaff ich es nicht, die UUIDs gemäss einer ähnlichen Suchen-Ersetzen Funktion wie oben einzufügen. Ich kann nur einzelne Werte aufrufen und verändern:

Code: Alles auswählen

from xml.dom import minidom
xmldoc = minidom.parse("GeologyModelLookUp_V3.0_00.xml")
cl = xmldoc.getElementsByTagName('GeologyModelLookUp_V3_0.Geology_Catalogues.Unconsolidated_Deposits_PT_Kind')
for i in range(3930):                                                # beste Lösung bisher
    if cl_object.attributes['TID'].value == searchList[i]:
        cl_object.attributes['TID'].value = uuidList[i]
    else:
        pass
print(cl_object.attributes['TID'].value)
Mein Problem: Ich hab sowohl verschiedene Klassen, welche ich aufrufen muss, sowohl auch verschiedene Werte, welche ich gemäss Excel-Liste austauschen muss.
Kann mir jemand dabei helfen?

Mein xml:

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<TRANSFER> 
  <DATASECTION>
    <Model.Catalogues BID="b001">
      <Model.Catalog.Class1 TID="Runc101001">
        <Code>Runc101001</Code>
        <Kind>
            <Text>erratischer Block</Text>
        </Kind>
      </Model.Catalog.Class1>
      <Model.Catalog.Class1 TID="Runc101002">
        <Code>Runc101002</Code>
        <Kind>
            <Text>Schwarm erratischer Blöcke</Text>
        </Kind>
      </Model.Catalog.Class1>
      <Model.Catalog.Class2 TID="Runc104001">
        <Code>Runc104001</Code>
        <Rock_Spe>
          <Text>Konglomerat</Text>
        </Rock_Spe>
      </Model.Catalog.Class2>
      <Model.Catalog.Class2 TID="Runc104002">
        <Code>Runc104002</Code>
        <Rock_Spe>
            <Text>Gabbro</Text>
        </Rock_Spe>
      </Model.Catalog.Class2>
	</Model.Catalogues>
  </DATASECTION>
</TRANSFER>

Und einen Auszug aus der Excel-Liste:

Nr Code_alphanumeric Code_numeric UUID

452 Runc101001 14401001 UUID6
453 Runc101002 14401002 UUID7
454 Runc101003 14401003 UUID8
455 Runc101004 14401004 UUID9
456 Runc101005 14401005 UUID10
457 Runc101006 14401006 UUID11
458 Runc101007 14401007 UUID12
459 Runc101008 14401008 UUID13
460 Runc102001 14402001 UUID14
461 Runc102002 14402002 UUID15
462 Runc103001 14403001 UUID16
463 Runc103002 14403002 UUID17

Re: xml Parsing mit Element Tree: Attribut-Werte gemäss Excel-Tabelle ersetzten

Verfasst: Mittwoch 30. März 2022, 18:44
von Sirius3
@agl: Dataframes sind so praktisch, weil sie zusammengehörende Daten gemeinsam verarbeiten kann. Da ist es schlecht, diese Daten erst auseinander zu reißen, um später wieder zu versuchen, sie zusammenzupappen.
In Python iteriert man nicht über einen Index.
Variablennamen schreibt man komplett klein, die `else: pass` sind überflüssig und können weg.
Der tostring-Aufruf hat keine Wirkung, kann also auch weg.
Alles was Du per str in einen String umwandelst, ist bereits ein String, vor allem der Literal 'Code'. Da fehlt die Endung 'Code.xml'.

Code: Alles auswählen

import xml.etree.ElementTree as et
import pandas as pd

data = et.parse('xml_file.xml')
root = data.getroot()

df = pd.read_excel('ReplaceList_UUID.xlsx')
df = df.set_index("Code_alphanumeric")
for code in root.findall('.//Code'):
    try:
        code.text = str(df.loc[code.text].Code_numeric)
    except KeyError:
        pass
data.write('code.xml')
In Deinem zweiten Code benutzt Du minidom. Macht man aber nicht, weil die API schecklich ist.
Über einen Index iteriert man nicht, wenn dann auch noch die Anzahl fix im Code steht, ist das doppelt schlecht.
An sonsten funktioniert das identisch zum ersten Code:

Code: Alles auswählen

data = et.parse("GeologyModelLookUp_V3.0_00.xml")
root = data.getroot()
for element in root.findall('.//GeologyModelLookUp_V3_0.Geology_Catalogues.Unconsolidated_Deposits_PT_Kind'):
    try:
        element.attrib['TID'] = df.loc[element.attrib['TID']].UUID
    except KeyError:
        pass

Re: xml Parsing mit Element Tree: Attribut-Werte gemäss Excel-Tabelle ersetzten

Verfasst: Freitag 1. April 2022, 08:50
von agl
@Sirius3

Vielen Dank für die wertvollen Tipps! Hat mir bereits sehr geholfen, auch zur Struktur und allgemeinen Vorangehensweise.

Nun hab ich aber noch das Problem, dass die Objektklasse, in welcher ich das Attribut "TID" verändern möchte, ebenfalls viele verschiedenen Namen einnehmen kann.
Ich hab eigentlich diese Gliederung (versuchte es etwas einfacher darzustellen im xml-Auszug oben)
Model.Catalog.Class

Mit dem bisherigen Code konnte ich eine spezifische Klasse auswählen und deren TIDs bearbeiten.
Also

Code: Alles auswählen

data = et.parse("Model.xml")
root = data.getroot()
for element in root.findall('.//Model.Catalog1.Class1'):
    try:
        element.attrib['TID'] = df.loc[element.attrib['TID']].UUID
    except KeyError:
        pass
Wie kann ich anhand meines xml-Datenfiles immer neue root.findall - Ausdrücke automatisch produzieren? Also alle Varianten von Model.Catalog1.Class1 bis Model.Catalog2.Class19 ?
Ich hab nur zwei Kataloge, also hätte ich falls nötig die Funktion auf die Klassen fixiert einfach je einmal ausformuliert, was aber auch nicht sonderlich elegant wirkt.
Ich versuchte bisher die Suche der Klasse als Child zu formulieren:

Code: Alles auswählen

data = et.parse("Model.xml")
root = data.getroot()
for element in root.findall('.//Model.Catalog1'):
    try:
    	cla = element.getchildren()
        cla.attrib['TID'] = df.loc[cla.attrib['TID']].UUID
    except KeyError:
        pass
Damit krieg ich allerdings die Fehlermeldung, dass mein definiertes Child "cla" kein Attribute namens "attrib" vorweist. Wie kann ich das lösen?

Re: xml Parsing mit Element Tree: Attribut-Werte gemäss Excel-Tabelle ersetzten

Verfasst: Freitag 1. April 2022, 09:35
von __blackjack__
@agl: Dein `cla` ist hier ja auch kein einzelnes Element mehr. Das Ergebnis ist eine Sequenz von Elementen. Falls Du da mit jedem Element etwas machen möchtest, dann brauchst Du eine Schleife. Hier kann man dann mal den schlechten Namen `cla` anmerken. Ich *rate* mal das sollte `classes` heissen. Da hätte man dann an der Benennung in Mehrzahl schon gesehen das `attrib` schon rein logisch ”interessant” wäre, denn was wäre das denn? Die Schnittmenge aller Attribute der Klassenelemente? Die Vereinigung der Attribute der Klassenelemente?

Falls das Element mit dem TID-Attribut immer das Eltern-Element von dem <Code>-Element ist, würde ich da gar nicht extra nach suchen, sondern `lxml.etree` verwenden. Das hat nämlich eine `getparent()`-Methode mit der sich das leicht in einem Durchlauf lösen lässt. Man kann dann auch sehr simpel gleich noch eine Konsistenzprüfung einbauen, ob TID-Attribut und <Code>-Inhalt den gleichen Wert haben.

Ich würde aus dem DataFrame auch vorher ein Wörterbuch machen. Das ist irgendwie ziemlich unelegant einen DataFrame nur zum Nachschlagen von einer simplen Abbildung zu verwenden. Und eventuell auch nicht so performant. Am Ende wird da im schlechtesten Fall immer linear nach dem Wert gesucht.