XML und pyMARC: Extraktion des Record Identifiers

Hi zusammen,
bin gerade am Transformieren von XML zu JSON mit pyMARC und dessen XML-Handler.
Meine XML-Quelldatei ist ungefähr so strukturiert:

<?xml version="1.0" encoding="ISO-8859-1"?>
<collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="abc.xsd">
<record identifier="xyz" status="e" level="p">
[...]
</record>
<record identifier="hij" status="e" level="p">
[...]
</record>
<record identifier="klm" status="e" level="p">
[...]
</record>
</collection>

Ich benutze parse_xml_to_array um mit den Datensätzen zu arbeiten, damit ich diese letztendlich zu JSON transformieren kann.

Nun frage ich mich, wie ich mir die Property „record identifier“ bei jedem Datensatz ausgeben kann (also im Beispiel: „xyz“, „hij“, „klm“), habe dazu aber keine Möglichkeit gefunden. In meinem Fall ist in der Value des Record Identifiers immer die Datensatznummer hinterlegt, die ich gerne ausgeben würde, wenn bestimmte Transformationsprozesse fehlschlagen, um die betroffenen Datensätze klar identifizieren zu können. Die Datensatznummer ist nicht notwendigerweise auch in einem der MARC-Felder des jeweiligen Datensatzes hinterlegt, daher muss ich zwangsläufig auf die Value des Keys „identifier“ in dem <record>-XML-Tag zugreifen.

Wisst ihr da einen Weg, über pyMARC an den Record Identifier in <record> zu kommen? Möglichst ohne das ganze Python-Paket modifzieren zu müssen? Oder habe ich einfach etwas in der Doku übersehen?

Danke im Voraus.

1 „Gefällt mir“

Das sind keine standardisierten/validen MARCXML Daten, kann das sein?

Könnte ja gut sein, dass PyMarc hier nur mit den im XML Schema spezifizierten Elementen und Attributen arbeiten kann.

1 „Gefällt mir“

Ja, das stimmt leider. Habe mal meine XML-Quelldatei mit ein MARCXML Beispielen von LoC und DNB verglichen. Es gibt kein <leader> und auch keine <controlfield> Tags, sondern es sind nur die Datensätze mit <datafield> und untergeordneten <subfield>s enthalten. Deswegen hatte ich bis zu meinem Identifier-Problem auch keine Schwierigkeiten, weil sich mit den MARC-Feldern wie gewohnt arbeiten lässt.

Also muss ich wohl wirklich die pyMARC-Library modifizieren oder mit einer anderen XML-Library in Python arbeiten, um den Record Identifier ausgeben zu können. Ansonsten könnte ich alternativ andere identifizierende Merkmale wie Autor*in, Titel, Erscheinungsjahr, Verlag etc. ausgeben, aber nichts ist verlässlicher und bequemer wie eine Datensatznummer :wink: Fällt dir vielleicht noch ein anderer Ansatz ein?

Wie wir ja schon festgestellt haben, ist meine XML-Quelldatei nicht gänzlich MARCXML-konform, sodass ich mit pymarc nicht an „identifier“ in <record> herankomme.

Habe mir jetzt mit folgendem Workaround beholfen: Im Pre-Processing (vor dem Splitten in kleinere XML-Teile) lasse ich den Value von „identifier“ aus <record> in das Control Field <controlfield tag="001"> eines jeden Datensatzes schreiben, in dem gewöhnlich Datensatznummern untergebracht sind.

Mit xml.etree funktioniert das folgendermaßen:

import xml.etree.ElementTree as ET

# Parse the input XML file
tree = ET.parse('input.xml')
root = tree.getroot()

# Iterate through the record elements
for record in root.findall('.//record'):
    # Get the identifier attribute
    identifier = record.get('identifier')
    
    # Create a new controlfield element and set its text to the identifier
    controlfield = ET.SubElement(record, 'controlfield', tag='001')
    controlfield.text = identifier
    
    # Remove the identifier attribute from the record element (if necessary)
    # del record.attrib['identifier']

# Write the modified XML to a new file
tree.write('output.xml', encoding='UTF-8', xml_declaration=True)

(Prompted mit llama-3-70b-instruct, angepasst von mir)

Das Resultat am obigen Beispiel angewendet wäre dann:

<?xml version="1.0" encoding="ISO-8859-1"?>
<collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="abc.xsd">
<record identifier="xyz" status="e" level="p">
[...]
<controlfield tag="001">xyz</controlfield>
</record>
<record identifier="hij" status="e" level="p">
[...]
<controlfield tag="001">hij</controlfield>
</record>
<record identifier="klm" status="e" level="p">
[...]
<controlfield tag="001">klm</controlfield>
</record>
</collection>

Den Ansatz mit SAX und den Performancevergleich reiche ich noch nach.

Hier ist noch der light-weight Ansatz mit SAX, der sich im Fall meines riesigen Datenbankabzuges als einzig anwendbar herausgestellt hat, weil etree zu RAM- und übrigens auch CPU-lastig ist (siehe Thread zum Splitten von großen XML-Quelldateien):

import xml.sax

class MARC21Handler(xml.sax.ContentHandler):
    def __init__(self):
        self.in_record = False
        self.identifier = None
        self.outfile = open('output.xml', 'w')

    def startElement(self, name, attrs):
        if name == 'record':
            self.in_record = True
            self.identifier = attrs.get('identifier')
            self.outfile.write('<record identifier="%s" status="%s" level="%s">' % (attrs.get('identifier'), attrs.get('status'), attrs.get('level')))

    def characters(self, content):
        self.outfile.write(content)

    def endElement(self, name):
        if name == 'record':
            self.outfile.write('<controlfield tag="001">%s</controlfield>\n</record>' % self.identifier)
            self.in_record = False
        elif name == 'datafield':
            self.outfile.write('</datafield>')
        elif name == 'subfield':
            self.outfile.write('</subfield>')

    def startDocument(self):
        self.outfile.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        self.outfile.write('<collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="abc.xsd">')

    def endDocument(self):
        self.outfile.write('</collection>\n')
        self.outfile.close()

if __name__ == '__main__':
    parser = xml.sax.make_parser()
    parser.setContentHandler(MARC21Handler())
    parser.parse('input.xml')

(Prompted mit llama-3-70b-instruct, angepasst von mir)

Ist diesmal in OOP Python gehalten, weil LLaMA scheinbar hier ein Fable dafür hatte :sweat_smile: Resultat ist das selbe wie oben. Bei den Feldinhalten am besten noch darauf achten, ob nach dem Prozess Sonderzeichen korrekt umgesetzt wurden. Der Durchlauf mit meinem sehr großen Datenbankabzug hat mit SAX nur 3min gedauert :+1: