def parse_xml_content(preview_testset_ids, root_path):

    # Alle Dateien im Ordner "findbuch" und "tektonik" mit den folgenden Dateiendungen werden für die Vorschau berücksichtigt:
    ext = [".xml", ".XML"]
    if preview_testset_ids is not None:
        preview_testset_ids = preview_testset_ids.split(" | ")
        if preview_testset_ids[0] == "_generate_preview_testset_":
            preview_testset_ids = build_preview_testset.parse_xml_content(
                preview_type="Verzeichnungseinheiten",
                preview_create_count=int(preview_testset_ids[1]))

    def process_findbuch(findbuch_file_in, testset_ids):

        preview_data = {}
        xml_findbuch_in = etree.parse(findbuch_file_in)

        # Ermitteln aller VZE-Datensätze im Findbuch:

        if testset_ids is not None:
            preview_objects = []
            for testset_id in testset_ids:
                xpath_id_selector = "@id='{}'".format(testset_id)
                xpath_string = "//{urn:isbn:1-931666-22-9}c[@level='file'][%s]" % xpath_id_selector
                preview_object = xml_findbuch_in.findall(xpath_string)
                for element in preview_object:
                    preview_objects.append(element)
        else:
            preview_objects = xml_findbuch_in.findall(
                "//{urn:isbn:1-931666-22-9}c[@level='file']"
            )  # TODO: item ergänzen

        collection_element = xml_findbuch_in.findall(
            "//{urn:isbn:1-931666-22-9}c[@level='collection']")
        kontext_tektonik = []
        tektonik_path = "../tektonik/"
        tektonik_files = os.listdir(tektonik_path)
        tektonik_files_xml = []
        for filename in tektonik_files:
            if filename.endswith(tuple(ext)):
                tektonik_files_xml.append(filename)
        if len(tektonik_files_xml
               ) > 0 and "id" in collection_element[0].attrib:
            tektonik_file_in = tektonik_files_xml[
                0]  # Pfad zur Tektonik dynamisch generieren

            try:
                xml_tektonik_in = etree.parse(tektonik_path + tektonik_file_in)
                tektonik_enrich_element = xml_tektonik_in.findall(
                    "//{urn:isbn:1-931666-22-9}c[@id='%s']" %
                    collection_element[0].attrib["id"])
                if len(tektonik_enrich_element) > 0:
                    file_parents_tektonik = tektonik_enrich_element[
                        0].iterancestors(tag="{urn:isbn:1-931666-22-9}c")
                    for file_parent_tektonik in file_parents_tektonik:
                        ead_level = None
                        if "level" in file_parent_tektonik.attrib:
                            ead_level = file_parent_tektonik.attrib["level"]
                        ead_source = file_parent_tektonik.findall(
                            "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle"
                        )
                        if len(ead_source) > 0:
                            single_kontext = [ead_level, ead_source[0].text]
                            kontext_tektonik.append(single_kontext)

                    del (
                        kontext_tektonik[-1]
                    )  # oberstes Element entfernen, damit der Bestandsknoten nicht aus der Tektonik kopiert wird (schon aus Findbuch übernommen)
            except etree.XMLSyntaxError as e:
                logger.warning(
                    "Verarbeitung der XML-Datei übersprungen (Fehler beim Parsen): {}"
                    .format(e))

        for c_element in preview_objects:
            preview_data["Titel"] = ""
            preview_data["Verzeichnungsstufe"] = None
            preview_data["Archivaliensignatur"] = None
            preview_data["Alt-/Vorsignatur"] = None
            preview_data["Laufzeit"] = None
            preview_data["Enthältvermerke"] = None
            preview_data["Provenienz"] = None
            preview_data["Vorprovenienz"] = None
            preview_data["Umfang"] = None
            preview_data["Maße"] = None
            preview_data["Formalbeschreibung"] = None
            preview_data["Material"] = None
            preview_data["Urheber"] = None
            preview_data["Archivalientyp"] = None
            preview_data["Sprache der Unterlagen"] = None
            preview_data["Verwandte Bestände und Literatur"] = None
            preview_data["Sonstige Erschließungsangaben"] = None
            preview_data["Bemerkungen"] = None
            preview_data["Indexbegriffe Person"] = None
            preview_data["Indexbegriffe Ort"] = None
            preview_data["Indexbegriffe Sache"] = None
            preview_data["Digitalisat im Angebot des Archivs"] = None
            preview_data["Bestand"] = None
            preview_data["Online-Findbuch im Angebot des Archivs"] = None
            preview_data["Rechteinformation"] = None
            preview_data["Rechtsstatus"] = None
            preview_data["Datengeber-Rücklink"] = None
            preview_data["Logo"] = None
            preview_data["Institution"] = None

            # Titel:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle"
            )
            for element in findlist:
                preview_data["Titel"] = element.text

            # Verzeichnungsstufe:
            preview_data["Verzeichnungsstufe"] = "Archivale"

            # Bestand:
            archiv = ""
            findlist = xml_findbuch_in.findall(
                "//{urn:isbn:1-931666-22-9}archdesc/{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}repository/{urn:isbn:1-931666-22-9}corpname"
            )
            for element in findlist:
                if element.text is not None:
                    if "role" in element.attrib:
                        if element.attrib["role"] == "Aggregator":
                            continue
                    archiv = element.text
                    preview_data["Institution"] = archiv

            #collection_element = xml_findbuch_in.findall("//{urn:isbn:1-931666-22-9}c[@level='collection']")
            bestandssignatur = ""
            bestandstitel = ""
            if len(collection_element) > 0:
                collection_unitid = collection_element[0].findall(
                    "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unitid"
                )
                collection_unittitle = collection_element[0].findall(
                    "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle"
                )
                for element in collection_unitid:
                    bestandssignatur = element.text
                for element in collection_unittitle:
                    bestandstitel = element.text
            preview_data[
                "Bestand"] = archiv + ", " + bestandssignatur + " " + bestandstitel

            # Archivaliensignatur:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unitid")
            for element in findlist:
                if "type" not in element.attrib:
                    if element.text is not None:
                        archivaliensignatur = element.text
                    else:
                        archivaliensignatur = ""
                    if archiv != "":
                        preview_data[
                            "Archivaliensignatur"] = archiv + ", " + archivaliensignatur
                    else:
                        preview_data["Archivaliensignatur"] = element.text

            # Alt-/Vorsignatur:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unitid[@type]"
            )
            for element in findlist:
                preview_data["Alt-/Vorsignatur"] = element.text

            # Laufzeit:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unitdate")
            for element in findlist:
                preview_data["Laufzeit"] = element.text

            # Enthältvermerke:
            abstract_elements = []
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}abstract")
            for element in findlist:
                # if "type" in element.attrib:
                #     abstract_type = element.attrib["type"]
                # else:
                #     abstract_type = None
                # single_element = {"type": abstract_type, "text": element.text}
                if "type" in element.attrib:
                    if element.attrib["type"].startswith("ddbmapping_"):
                        continue
                abstract_elements.append(element)

            if len(abstract_elements) > 0:
                preview_data["Enthältvermerke"] = abstract_elements

            # Provenienz:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}origination"
            )
            for element in findlist:
                preview_data["Provenienz"] = element.text

            # Vorprovenienz:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}origination[@type='pre']"
            )
            for element in findlist:
                preview_data["Vorprovenienz"] = element.text

            # Umfang:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}physdesc/{urn:isbn:1-931666-22-9}extent"
            )
            for element in findlist:
                preview_data["Umfang"] = element.text

            # Maße:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}physdesc/{urn:isbn:1-931666-22-9}dimensions"
            )
            for element in findlist:
                preview_data["Maße"] = element.text

            # Formalbeschreibung:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}physdesc")
            physdesc_elements = []
            for element in findlist:
                if element.text is not None:  # nur physdesc-Element mit Text-Inhalt berücksichtigen, welches die Formalbeschreibung enthält
                    extent_elements = element.findall(
                        "{urn:isbn:1-931666-22-9}extent")
                    dimensions_elements = element.findall(
                        "{urn:isbn:1-931666-22-9}dimensions")
                    if len(extent_elements) == 0 and len(
                            dimensions_elements
                    ) == 0:  # nur solche physdesc-Elemente berücksichtigen, welche kein dimensions- oder extent-Element besitzen.
                        physdesc_elements.append(element)
            if len(physdesc_elements) > 0:
                preview_data["Formalbeschreibung"] = physdesc_elements

            # Material:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}materialspec"
            )
            for element in findlist:
                preview_data["Material"] = element.text

            # Urheber:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}origination[@type]"
            )
            for element in findlist:
                if element.attrib[
                        "type"] != "pre":  # type vorhanden, aber darf nicht "pre" sein
                    preview_data["Urheber"] = element.text

            # Archivalientyp:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}physdesc/{urn:isbn:1-931666-22-9}genreform"
            )
            for element in findlist:
                if "normal" in element.attrib:  # @normal verwenden, falls vorhanden
                    preview_data["Archivalientyp"] = element.attrib["normal"]
                else:
                    preview_data["Archivalientyp"] = element.text

            # Sprache der Unterlagen:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}langmaterial/{urn:isbn:1-931666-22-9}language"
            )
            for element in findlist:
                preview_data["Sprache der Unterlagen"] = element.text

            # Verwandte Bestände und Literatur:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}relatedmaterial")
            relatedmaterial_elements = []
            if len(findlist) == 1:
                preview_data["Verwandte Bestände und Literatur"] = findlist[
                    0].text
            elif len(findlist) > 0:
                for element in findlist:
                    relatedmaterial_elements.append(element.text)
                preview_data[
                    "Verwandte Bestände und Literatur"] = relatedmaterial_elements

            # Sonstige Erschließungsangaben:
            odd_elements = []
            findlist = c_element.findall("{urn:isbn:1-931666-22-9}odd")
            for element in findlist:
                odd_head_element = element.find("{urn:isbn:1-931666-22-9}head")
                if odd_head_element is not None:
                    if odd_head_element.text.startswith("ddbmapping_"):
                        continue
                odd_elements.append(element)
            if len(odd_elements) > 0:
                preview_data["Sonstige Erschließungsangaben"] = odd_elements

            # Bemerkungen:
            note_elements = []
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}note/{urn:isbn:1-931666-22-9}p"
            )
            for element in findlist:
                note_elements.append(element)
            if len(note_elements) > 0:
                preview_data["Bemerkungen"] = note_elements

            # Indexbegriffe Person:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}index/{urn:isbn:1-931666-22-9}indexentry/{urn:isbn:1-931666-22-9}persname"
            )
            indexentry_person_elements = []
            for element in findlist:
                indexentry_person_elements.append(
                    element
                )  # nicht als String, sondern als etree-Objekt übergeben, damit auf @source und @authfilenumber zugegriffen werden kann
            if len(indexentry_person_elements) > 0:
                preview_data[
                    "Indexbegriffe Person"] = indexentry_person_elements

            # Indexbegriffe Ort:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}index/{urn:isbn:1-931666-22-9}indexentry/{urn:isbn:1-931666-22-9}geogname"
            )
            indexentry_geogname_elements = []
            for element in findlist:
                indexentry_geogname_elements.append(
                    element
                )  # nicht als String, sondern als etree-Objekt übergeben, damit auf @source und @authfilenumber zugegriffen werden kann
            if len(indexentry_geogname_elements) > 0:
                preview_data[
                    "Indexbegriffe Ort"] = indexentry_geogname_elements

            # Indexbegriffe Sache:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}index/{urn:isbn:1-931666-22-9}indexentry/{urn:isbn:1-931666-22-9}subject"
            )
            indexentry_subject_elements = []
            for element in findlist:
                indexentry_subject_elements.append(
                    element
                )  # nicht als String, sondern als etree-Objekt übergeben, damit auf @source und @authfilenumber zugegriffen werden kann
            if len(indexentry_subject_elements) > 0:
                preview_data[
                    "Indexbegriffe Sache"] = indexentry_subject_elements

            # Digitalisat im Angebot des Archivs:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}daogrp/{urn:isbn:1-931666-22-9}daoloc[@{http://www.w3.org/1999/xlink}role='externer_viewer']"
            )
            if len(findlist) > 0:
                preview_data["Digitalisat im Angebot des Archivs"] = findlist[
                    0].attrib["{http://www.w3.org/1999/xlink}href"]

            # Online-Findbuch im Angebot des Archivs:
            findlist = xml_findbuch_in.findall(
                "//{urn:isbn:1-931666-22-9}archdesc[@level='collection']/{urn:isbn:1-931666-22-9}otherfindaid/{urn:isbn:1-931666-22-9}extref[@{http://www.w3.org/1999/xlink}role='url_findbuch']"
            )
            for element in findlist:
                preview_data[
                    "Online-Findbuch im Angebot des Archivs"] = element.attrib[
                        "{http://www.w3.org/1999/xlink}href"]

            # Rechteinformation (aus Konkordanz befüllt):
            preview_data[
                "Rechteinformation"] = "Rechteangaben beim Datengeber zu klären."

            # Rechtsstatus (aus Konkordanz befüllt):
            preview_data[
                "Rechtsstatus"] = "Rechtsstatus beim Datengeber zu klären"

            # Datengeber-Rücklink:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}otherfindaid/{urn:isbn:1-931666-22-9}extref[@{http://www.w3.org/1999/xlink}role='url_archivalunit']"
            )
            for element in findlist:
                if "{http://www.w3.org/1999/xlink}href" in element.attrib:
                    preview_data["Datengeber-Rücklink"] = element.attrib[
                        "{http://www.w3.org/1999/xlink}href"]

            # Kontext:
            kontext = []

            file_parents = c_element.iterancestors(
                tag="{urn:isbn:1-931666-22-9}c")

            for file_parent in file_parents:
                ead_level = None
                if "level" in file_parent.attrib:
                    ead_level = file_parent.attrib["level"]
                ead_source = file_parent.findall(
                    "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle"
                )
                if len(ead_source) > 0:
                    single_kontext = [ead_level, ead_source[0].text]
                    kontext.append(single_kontext)

            # Kontext (Tektonik):
            kontext += kontext_tektonik

            # Bilder:
            bilder = []
            daogrp_elements = c_element.findall(
                "{urn:isbn:1-931666-22-9}daogrp")
            for element in daogrp_elements:
                item_name = None
                image_full = None

                item_name_element = element.findall(
                    "{urn:isbn:1-931666-22-9}daodesc/{urn:isbn:1-931666-22-9}list/{urn:isbn:1-931666-22-9}item/{urn:isbn:1-931666-22-9}name"
                )
                image_full_element = element.findall(
                    "{urn:isbn:1-931666-22-9}daoloc[@{http://www.w3.org/1999/xlink}role='image_full']"
                )

                if len(item_name_element) > 0:
                    item_name = item_name_element[0].text
                if len(image_full_element) > 0:
                    image_full = image_full_element[0].attrib[
                        "{http://www.w3.org/1999/xlink}href"]

                single_element = {
                    "item_name": item_name,
                    "image_full": image_full
                }
                bilder.append(single_element)

            # Ausgabe der HTML-Datei:
            create_html_files.parse_xml_content(
                kontext,
                preview_data,
                findbuch_file_in,
                bilder,
                preview_type="Verzeichnungseinheiten")

    os.chdir("findbuch")

    for input_file in os.listdir("."):
        if handle_thread_actions.load_from_xml("stop_thread",
                                               root_path) is False:
            if input_file.endswith(tuple(ext)):
                process_findbuch(input_file, preview_testset_ids)
        else:
            break

    os.chdir("..")
def run_transformation_p2(root_path, session_data=None, is_gui_session=False):

    # Import der Common-Module:
    from modules.common.provider_metadata import handle_provider_metadata  # Modul zum Erstellen und Auslesen der provider.xml-Datei
    from gui_session.handle_session_data import synchronize_with_gui
    from gui_session.handle_session_data import write_processing_status
    from gui_session import handle_thread_actions

    # Einbindung der Analyse-Module:
    from modules.analysis.statistics import metadata_analysis
    from modules.analysis.enrichment import tektonik_enrichment
    from modules.analysis.previews import html_previews
    from modules.analysis.identifiers import obsolete_objects

    # Es wird jeweils auf den data_output/{ISIL} - Ordner des Providers zurückgregriffen.
    # Die Provider-Angaben sollten aus transformation_p1.py in eine XML-Datei ausgegliedert werden, damit beide Prozesse darauf zugreifen können (eigentlich wird nur die ISIL benötigt, da Findbuch und Tektonik schon getrennt in data_output liegen)

    # Parameter definieren:
    provider_isil = "DE-Pfo2"  # ISIL des Providers. Der entsprechende Unterordner in data_output wird für die Analyseprozesse herangezogen.
    transformation_date = None  # Datum der Transformation -- wenn auf "None" gesetzt, wird automatisch der Ordner der neuesten Transformation verwendet.

    preview_testset_ids = []  # Hier können gezielt IDs von Datensätzen angegeben werden, für die eine HTML-Vorschau erstellt werden soll (IDs durch " | " getrennt eingeben)

    # Einstellungen setzen:
    enable_tektonik_enrichment = True  # Soll die Tektonik um Informationen aus den Findbüchern angereichert werden?
    enable_metadata_analysis = True  # Sollen die Metadaten einer statistischen Analyse unterzogen werden?
    enable_metadata_preview = False  # Sollen HTML-Vorschauen einzelner Datensätze erstellt werden?
    handle_obsolete_objects = False  # Sollen obsolete Objekte automatisch aus der Lieferung entfernt werden?

    # Übernahme der Sitzungsdaten, falls es sich um eine GUI-Sitzung handelt:
    if is_gui_session and (session_data is not None):
        provider_isil = session_data["provider"]
        enable_tektonik_enrichment = synchronize_with_gui(session_data["enable_tektonik_enrichment"])
        enable_metadata_analysis = synchronize_with_gui(session_data["enable_metadata_analysis"])
        enable_metadata_preview = synchronize_with_gui(session_data["enable_metadata_preview"])
        preview_testset_ids = session_data["preview_testset_ids"]
        handle_obsolete_objects = synchronize_with_gui(session_data["handle_obsolete_objects"])

    if transformation_date is None:
        transformation_dates = []
        provider_path = "./data_output/{}".format(provider_isil.replace("-", "_"))
        for date in os.listdir(provider_path):
            if date.startswith("2"):  # Workaround, um nur relevante Ordner einzuschließen (schließt insbesondere unsichtbare Ordner auf Unix-Systemen aus, die mit einem "." beginnen)
                transformation_dates.append(date)
        transformation_date = max(transformation_dates, key= lambda d: datetime.datetime.strptime(d, "%Y%m%d"))

    logger.info("Analyse der Transformation des Datums: {}".format(transformation_date))
    root_path = os.path.abspath(".")
    analysis_source_path = "data_output/" + provider_isil.replace("-", "_") + "/" + transformation_date
    analysis_source_path = os.path.abspath(analysis_source_path)  # vollen Pfad verwenden, damit Analyse-Ordner unabhängig vom momentanen CWD erreicht werden kann.

    # Auslesen der provider.xml aus dem Input-Ordner:
    input_folder_name = provider_isil.replace("-", "_")
    providerxml_source_path = "data_input/" + input_folder_name
    os.chdir(providerxml_source_path)

    handle_provider_metadata.create_provider_template(input_folder_name)

    provider_name, provider_website, provider_id, provider_tektonik_url, provider_addressline_strasse, provider_addressline_ort, provider_addressline_mail, provider_state, provider_archivtyp, provider_software = handle_provider_metadata.get_provider_metadata(
        provider_isil)

    # Zurücksetzen des Prozessierungs-Status:
    write_processing_status(root_path=root_path, processing_step=None, status_message=None, error_status=0)
    error_status = 0

    os.chdir("../..")

    # Anforderungen für eine Implementation von Analyse- und Anreicherungsprozessen, vgl. ausgelagerte Module unter modules/analysis/..

    # In den Output-Pfad wechseln:
    logger.debug("Analyse-Pfad: {}".format(analysis_source_path))
    os.chdir(analysis_source_path)

    if enable_tektonik_enrichment and not handle_thread_actions.load_from_xml("stop_thread", root_path):
        write_processing_status(root_path=root_path, processing_step=10, status_message="Tektonik-Anreicherung wird durchgeführt ...", error_status=error_status)
        try:
            tektonik_enrichment.enrich_tektonik(provider_isil, provider_website, provider_name, provider_state, provider_archivtyp, provider_id, provider_addressline_strasse, provider_addressline_ort, provider_addressline_mail, provider_tektonik_url)  # Übergabe der Provider-Werte zur Befüllung der Fake-Tektonik (Repository)
        except (IndexError, TypeError, AttributeError, KeyError, SyntaxError) as e:
            traceback_string = traceback.format_exc()
            logger.warning("Tektonik-Anreicherung fehlgeschlagen; Fehlermeldung: {}.\n {}".format(e, traceback_string))
            error_status = 1
            write_processing_status(root_path=root_path, processing_step=None, status_message=None, error_status=error_status)

        os.chdir(analysis_source_path)  # in den Output-Pfad zurückwechseln.

    if enable_metadata_analysis:
        if handle_thread_actions.load_from_xml("stop_thread", root_path) is False:
            write_processing_status(root_path=root_path, processing_step=40, status_message="Metadaten werden analysiert ...", error_status=error_status)
            try:
                metadata_analysis.analyze_metadata_basic(provider_isil, transformation_date, root_path)
            except (IndexError, TypeError, AttributeError, KeyError, SyntaxError) as e:
                traceback_string = traceback.format_exc()
                logger.warning("Metadaten-Analyse fehlgeschlagen; Fehlermeldung: {}.\n {}".format(e, traceback_string))
                error_status = 1
                write_processing_status(root_path=root_path, processing_step=None, status_message=None, error_status=error_status)

            os.chdir(analysis_source_path)  # in den Output-Pfad zurückwechseln.

    if enable_metadata_preview:
        if handle_thread_actions.load_from_xml("stop_thread", root_path) is False:
            write_processing_status(root_path=root_path, processing_step=75, status_message="Voransichten werden generiert ...", error_status=error_status)
            try:
                html_previews.generate_html_previews(preview_testset_ids, root_path)
            except (IndexError, TypeError, AttributeError, KeyError, SyntaxError) as e:
                traceback_string = traceback.format_exc()
                logger.warning("Voransichten-Generierung fehlgeschlagen; Fehlermeldung: {}.\n {}".format(e, traceback_string))
                error_status = 1
                write_processing_status(root_path=root_path, processing_step=None, status_message=None, error_status=error_status)

    if handle_obsolete_objects:
        if handle_thread_actions.load_from_xml("stop_thread", root_path) is False:
            write_processing_status(root_path=root_path, processing_step=95, status_message="Obsolete Objekte werden bearbeitet ...", error_status=error_status)
            try:
                obsolete_objects.handle_obsolete_objects(provider_id, provider_isil, root_path)
            except (IndexError, TypeError, AttributeError, KeyError, SyntaxError) as e:
                traceback_string = traceback.format_exc()
                logger.warning(
                    "Bearbeitung der obsoleten Objekte fehlgeschlagen; Fehlermeldung: {}.\n {}".format(e, traceback_string))
                error_status = 1
                write_processing_status(root_path=root_path, processing_step=None, status_message=None, error_status=error_status)

    write_processing_status(root_path=root_path, processing_step=100, status_message="Analyse abgeschlossen", error_status=error_status)

    os.chdir("../../..")
def run_transformation_p1(root_path,
                          session_data=None,
                          is_gui_session=False,
                          propagate_logging=False,
                          is_unattended_session=False):
    """Aufruf der Transformationsmodule.

    Wird propagate_logging=True übergeben, so werden die durch Loguru erfassten Logmeldungen auch an stdout übergeben sowie in eine Log-Datei im Output-Verzeichnis geschrieben.
    """

    # Prozessierungs-Metadaten angeben:
    provider_isil = "DE-Pfo2"  # ISIL des Archivs, z.B. "DE-2410"

    # Weitere Prozessierungsangaben werden aus der Datei data_input/{ISIL}/provider.xml bezogen.

    # Optionen zum Anziehen der Binaries:

    process_binaries = False  # Anziehen von Binaries: Wenn "True", werden die Binaries heruntergeladen (in data_output/{provider_isil}/{Datum}/findbuch/binaries). DIe Links im XML werden durch relative Pfadangaben ersetzt ("findbuch/bild1.jpg")

    # Optionen zur Generierung von METS-Dateien:

    enable_mets_generation = False  # Falls "True", wird - bei Vorhandensein von Digitalisaten - pro Verzeichnungseinheit eine METS-Datei generiert, die zur Übergabe an den DFG-Viewer geeignet ist.

    # Optionen zur Anreicherung der Rechteinfomation:
    enrich_rights_info = True  # Falls "True", wird die Rechteinformation angereichert.

    # Optionen zur Vorprozessierung für den DDB2017-Ingest:
    enable_ddb2017_preprocessing = False

    # Optionen zur Anreicherung der Aggregatoren-Zuordnung:
    enrich_aggregator_info = True  # Falls "True", wird die Aggregatorinformation angereichert.

    # Optionen zum Anwenden der GUI-Mapping-Definition:
    apply_mapping_definition = True

    mdb_override = 0

    # Übernahme der Sitzungsdaten, falls es sich um eine GUI-Sitzung handelt:
    if is_gui_session and (session_data is not None):
        provider_isil = session_data["provider"]
        process_binaries = synchronize_with_gui(
            session_data["process_binaries"])
        enable_mets_generation = synchronize_with_gui(
            session_data["enable_mets_generation"])
        enrich_rights_info = synchronize_with_gui(
            session_data["enrich_rights_info"])
        enable_ddb2017_preprocessing = synchronize_with_gui(
            session_data["enable_ddb2017_preprocessing"])
        enrich_aggregator_info = synchronize_with_gui(
            session_data["enrich_aggregator_info"])
        apply_mapping_definition = synchronize_with_gui(
            session_data["apply_mapping_definition"])

    # Festlegen des Input-Paths: (data_input/ISIL/findbuch|tektonik; Erstellen fehlender Unterordner
    input_folder_name = provider_isil.replace("-", "_")
    current_date = datetime.datetime.now().strftime("%Y%m%d")
    input_path = "{}/data_input/{}/".format(root_path, input_folder_name)
    output_path = "{}/data_output/{}/{}/".format(root_path, input_folder_name,
                                                 current_date)
    output_path_findbuch = "{}/findbuch".format(output_path)
    output_path_tektonik = "{}/tektonik".format(output_path)

    if not os.path.isdir(input_path):
        os.makedirs(input_path)
    if not os.path.isdir(output_path_findbuch):
        os.makedirs(output_path_findbuch)
    if not os.path.isdir(output_path_tektonik):
        os.makedirs(output_path_tektonik)

    os.chdir(input_path)

    # Erstellen einer provider.xml-Datei, falls noch nicht vorhanden:
    handle_provider_metadata.create_provider_template(input_folder_name)

    # Auslesen der provider.xml-Datei; Zuweisung der belegten Feldinhalte:
    provider_name, provider_website, provider_id, provider_tektonik_url, provider_addressline_strasse, provider_addressline_ort, provider_addressline_mail, provider_state, provider_archivtyp, provider_software = handle_provider_metadata.get_provider_metadata(
        provider_isil)
    administrative_data = {
        "provider_isil": provider_isil,
        "provider_id": provider_id,
        "provider_name": provider_name,
        "provider_archivtyp": provider_archivtyp,
        "provider_state": provider_state,
        "provider_addressline_strasse": provider_addressline_strasse,
        "provider_addressline_ort": provider_addressline_ort,
        "provider_addressline_mail": provider_addressline_mail,
        "provider_website": provider_website,
        "provider_tektonik_url": provider_tektonik_url
    }

    if propagate_logging:
        logger.add(sys.stdout)
        logfile_path = "{}transformation.log".format(output_path)
        logger.add(logfile_path, rotation="10 MB")

    # Zurücksetzen des Prozessierungs-Status:
    write_processing_status(root_path=root_path,
                            processing_step=None,
                            status_message=None,
                            error_status=0)
    error_status = 0

    # Einlesen der Input-Dateien
    ext = [".xml", ".XML"]
    input_files = []
    for input_file_candidate in os.listdir("."):
        if input_file_candidate.endswith(
                tuple(ext)) and input_file_candidate != "provider.xml":
            input_files.append(input_file_candidate)
    input_files_count = len(input_files)

    for input_file_i, input_file in enumerate(input_files):
        if handle_thread_actions.load_from_xml("stop_thread",
                                               root_path) is True:
            break

        transformation_progress = int((input_file_i / input_files_count) * 100)

        try:
            xml_findbuch_in = etree.parse(input_file)
        except etree.XMLSyntaxError as e:
            logger.warning(
                "Verarbeitung der XML-Datei übersprungen (Fehler beim Parsen): {}"
                .format(e))
            error_status = 1
            write_processing_status(root_path=root_path,
                                    processing_step=None,
                                    status_message=None,
                                    error_status=error_status)
            continue

        # Bestimmen von input_type (Findbuch oder Tektonik). Kann kein Wert ermittelt werden, so erfolgt ein Fallback auf den Standardwert "findbuch"
        input_type = "findbuch"  # Setzen eines Standard-Werts für "input_type". Beim Aufruf des allg. Softwareskripts wird versucht, den Typ aus der XML-Datei auszulesen.
        archdesc_type = xml_findbuch_in.findall(
            '//{urn:isbn:1-931666-22-9}archdesc[@level="collection"]')
        if len(archdesc_type) == 1:
            if "type" in archdesc_type[0].attrib:
                input_type = archdesc_type[0].attrib["type"].lower()

        logger.info("Beginne Prozessierung für {}: {} (Datei {}/{})".format(
            input_type, input_file, input_file_i + 1, input_files_count))

        # Wenn die folgenden Maintenance-Funktionen bereits im Workflow vorkommen, sollen diese bei der globalen Prozessierung übersprungen werden.
        mapping_definition_in_workflow_modules = False
        ddb2017_preprocessing_in_workflow_modules = False
        rights_enrichment_in_workflow_modules = False
        aggregator_enrichment_in_workflow_modules = False

        result_format = "xml"  # Wert wird angepasst, sobald durch Anwenden der Mappingdefinition aus der XML-Ursprungsdatei ein anderes Format entsteht, etwa JSON oder eine Liste mehrerer Dictionaries. Ist is_xml_result ungleich "xml", so werden nachfolgende Prozessierungen (Providerskripte, Binaries, METS, Rechte/Aggregatoren-Anreicherung und Vorprozessierung) übersprungen.

        # Ermitteln und ausführen der Workflow-Module
        write_processing_status(
            root_path=root_path,
            processing_step=transformation_progress,
            status_message="Verarbeite Workflow-Module für {}: {} (Datei {}/{})"
            .format(input_type, input_file, input_file_i + 1,
                    input_files_count),
            error_status=error_status)
        workflow_modules = load_provider_modules()
        for workflow_module in workflow_modules:
            if handle_thread_actions.load_from_xml("stop_thread",
                                                   root_path) is True:
                break
            if workflow_module["ISIL"] == "common":
                if workflow_module["Modulname"] == "maintenance_function.py":
                    workflow_module_type = "Anwenden des Workflow-Moduls"
                    if workflow_module["Konfiguration"] is not None:
                        if workflow_module["Konfiguration"][
                                "maintenance_type"] == "ddb2017_preprocessing":
                            workflow_module_type = "DDB2017-Vorprozessierung"
                            ddb2017_preprocessing_in_workflow_modules = True
                        elif workflow_module["Konfiguration"][
                                "maintenance_type"] == "rights_info_enrichment":
                            workflow_module_type = "Anreichern der Rechteinformation"
                            rights_enrichment_in_workflow_modules = True
                        elif workflow_module["Konfiguration"][
                                "maintenance_type"] == "aggregator_info_enrichment":
                            workflow_module_type = "Anreichern der Aggregatorzuordnung"
                            aggregator_enrichment_in_workflow_modules = True
                        elif workflow_module["Konfiguration"][
                                "maintenance_type"] == "mapping_definition":
                            workflow_module_type = "Anwenden der Mapping-Definition"
                            mapping_definition_in_workflow_modules = True

                    if result_format == "xml":
                        write_processing_status(
                            root_path=root_path,
                            processing_step=transformation_progress,
                            status_message=
                            "Verarbeite Workflow-Modul '{}' für {}: {} (Datei {}/{})"
                            .format(workflow_module_type, input_type,
                                    input_file, input_file_i + 1,
                                    input_files_count),
                            error_status=error_status,
                            log_status_message=True)
                        try:
                            xml_findbuch_in, result_format = maintenance_function.parse_xml_content(
                                xml_findbuch_in,
                                input_type,
                                input_file,
                                provider_id,
                                session_data,
                                administrative_data,
                                error_status,
                                propagate_logging,
                                module_config=workflow_module["Konfiguration"])
                        except (IndexError, TypeError, AttributeError,
                                KeyError, SyntaxError) as e:
                            traceback_string = traceback.format_exc()
                            logger.warning(
                                "{} für {} {} fehlgeschlagen; Fehlermeldung: {}.\n {}"
                                .format(workflow_module_type, input_type,
                                        input_file, e, traceback_string))
                            error_status = 1
                            write_processing_status(root_path=root_path,
                                                    processing_step=None,
                                                    status_message=None,
                                                    error_status=error_status)
                elif workflow_module["Modulname"] == "user_interaction.py":
                    if is_unattended_session:
                        # bei unbeaufsichtigter Ausführung (etwa in Prefect) Nutzerinteraktions-Module überspringen
                        logger.info(
                            "Unbeaufsichtigte Ausführung: Nutzerinteraktions-Modul wird übersprungen."
                        )
                    else:
                        write_processing_status(
                            root_path=root_path,
                            processing_step=transformation_progress,
                            status_message=
                            "Verarbeite Workflow-Modul (Nutzerinteraktion) für {}: {} (Datei {}/{})"
                            .format(input_type, input_file, input_file_i + 1,
                                    input_files_count),
                            error_status=error_status,
                            log_status_message=True)

                        xml_findbuch_in = user_interaction.parse_xml_content(
                            xml_findbuch_in,
                            input_type,
                            input_file,
                            module_config=workflow_module["Konfiguration"],
                            root_path=root_path)
                elif workflow_module["Modulname"] == "filesystem_operation.py":
                    if result_format == "xml":
                        write_processing_status(
                            root_path=root_path,
                            processing_step=transformation_progress,
                            status_message=
                            "Verarbeite Workflow-Modul (Dateisystem-Operation) für {}: {} (Datei {}/{})"
                            .format(input_type, input_file, input_file_i + 1,
                                    input_files_count),
                            error_status=error_status,
                            log_status_message=True)
                        xml_findbuch_in = filesystem_operation.parse_xml_content(
                            xml_findbuch_in,
                            input_type,
                            input_file,
                            module_config=workflow_module["Konfiguration"])
            else:
                # für normale Providerskripte handle_provider_scripts aufrufen
                if result_format == "xml":
                    write_processing_status(
                        root_path=root_path,
                        processing_step=transformation_progress,
                        status_message=
                        "Verarbeite Workflow-Modul (providerspezifische Anpassung '{}' des Providers {}) für {}: {} (Datei {}/{})"
                        .format(workflow_module["Modulname"],
                                workflow_module["ISIL"], input_type,
                                input_file, input_file_i + 1,
                                input_files_count),
                        error_status=error_status,
                        log_status_message=True)

                    provider_module_args = [
                        root_path, xml_findbuch_in, input_type, input_file,
                        error_status
                    ]  # Parameter zur Übergabe an die providerspezifischen Anpassungen
                    xml_findbuch_in, error_status = handle_provider_scripts.parse_xml_content(
                        *provider_module_args,
                        provider_scripts=[workflow_module])

        # Anwenden der Mapping-Definiton:
        if not mapping_definition_in_workflow_modules:
            mapping_definition_args = [
                xml_findbuch_in, input_type, input_file, error_status,
                propagate_logging
            ]  # Parameter zur Übergabe an die Mapping-Definition
            if apply_mapping_definition:
                write_processing_status(
                    root_path=root_path,
                    processing_step=transformation_progress,
                    status_message=
                    "Anwenden der Mapping-Definition für {}: {} (Datei {}/{})".
                    format(input_type, input_file, input_file_i + 1,
                           input_files_count),
                    error_status=error_status)
                try:
                    xml_findbuch_in, result_format = mapping_definition.apply_mapping(
                        session_data, administrative_data,
                        *mapping_definition_args)
                except (IndexError, TypeError, AttributeError, KeyError,
                        SyntaxError) as e:
                    traceback_string = traceback.format_exc()
                    logger.warning(
                        "Anwenden der Mapping-Definition für {} {} fehlgeschlagen; Fehlermeldung: {}.\n {}"
                        .format(input_type, input_file, e, traceback_string))
                    error_status = 1
                    write_processing_status(root_path=root_path,
                                            processing_step=None,
                                            status_message=None,
                                            error_status=error_status)

        # Anziehen der Binaries (falls "fetch_and_link_binaries = True" in transformation_p1)
        if process_binaries and result_format == "xml":
            write_processing_status(
                root_path=root_path,
                processing_step=transformation_progress,
                status_message="Lade Binaries für {}: {} (Datei {}/{})".format(
                    input_type, input_file, input_file_i + 1,
                    input_files_count),
                error_status=error_status)
            xml_findbuch_in = fetch_and_link_binaries.parse_xml_content(
                xml_findbuch_in, input_file, output_path, input_type,
                input_path)

        # Generierung von METS-Dateien (falls "enable_mets_generation = True" in transformation_p1)
        if enable_mets_generation and result_format == "xml":
            write_processing_status(
                root_path=root_path,
                processing_step=transformation_progress,
                status_message="Generiere METS-Dateien für {}: {} (Datei {}/{})"
                .format(input_type, input_file, input_file_i + 1,
                        input_files_count),
                error_status=error_status)
            xml_findbuch_in = create_mets_files.parse_xml_content(
                xml_findbuch_in, input_file, output_path, input_type,
                input_path, session_data)

        # Anreicherung der Rechte- und Lizenzinformation
        if not rights_enrichment_in_workflow_modules:
            if enrich_rights_info and result_format == "xml":
                write_processing_status(
                    root_path=root_path,
                    processing_step=transformation_progress,
                    status_message=
                    "Anreichern der Rechteinformation für {}: {} (Datei {}/{})"
                    .format(input_type, input_file, input_file_i + 1,
                            input_files_count),
                    error_status=error_status)
                try:
                    xml_findbuch_in = handle_provider_rights.parse_xml_content(
                        xml_findbuch_in, input_file, input_type)
                except (IndexError, TypeError, AttributeError, KeyError,
                        SyntaxError) as e:
                    traceback_string = traceback.format_exc()
                    logger.warning(
                        "Anreichern der Rechteinformation für {} {} fehlgeschlagen; Fehlermeldung: {}.\n {}"
                        .format(input_type, input_file, e, traceback_string))
                    error_status = 1
                    write_processing_status(root_path=root_path,
                                            processing_step=None,
                                            status_message=None,
                                            error_status=error_status)

        # Anreicherung der Aggregator-Zuordnung
        if not aggregator_enrichment_in_workflow_modules:
            if enrich_aggregator_info and result_format == "xml":
                write_processing_status(
                    root_path=root_path,
                    processing_step=transformation_progress,
                    status_message=
                    "Anreichern der Aggregatorinformation für {}: {} (Datei {}/{})"
                    .format(input_type, input_file, input_file_i + 1,
                            input_files_count),
                    error_status=error_status)
                try:
                    xml_findbuch_in = handle_provider_aggregator_mapping.parse_xml_content(
                        xml_findbuch_in, input_file, input_type)
                except (IndexError, TypeError, AttributeError, KeyError,
                        SyntaxError) as e:
                    traceback_string = traceback.format_exc()
                    logger.warning(
                        "Anreichern der Aggregator-Zuordnung für {} {} fehlgeschlagen; Fehlermeldung: {}.\n {}"
                        .format(input_type, input_file, e, traceback_string))
                    error_status = 1
                    write_processing_status(root_path=root_path,
                                            processing_step=None,
                                            status_message=None,
                                            error_status=error_status)

        # Vorprozessierung für die DDB2017-Transformation
        if not ddb2017_preprocessing_in_workflow_modules:
            if enable_ddb2017_preprocessing and result_format == "xml":
                write_processing_status(
                    root_path=root_path,
                    processing_step=transformation_progress,
                    status_message=
                    "DDB2017-Vorprozessierung für {}: {} (Datei {}/{})".format(
                        input_type, input_file, input_file_i + 1,
                        input_files_count),
                    error_status=error_status)
                try:
                    xml_findbuch_in = ddb2017_preprocessing.parse_xml_content(
                        xml_findbuch_in, input_file, input_type, provider_id)
                except (IndexError, TypeError, AttributeError, KeyError,
                        SyntaxError) as e:
                    traceback_string = traceback.format_exc()
                    logger.warning(
                        "DDB2017-Vorprozessierung für {} {} fehlgeschlagen; Fehlermeldung: {}.\n {}"
                        .format(input_type, input_file, e, traceback_string))
                    error_status = 1
                    write_processing_status(root_path=root_path,
                                            processing_step=None,
                                            status_message=None,
                                            error_status=error_status)

        if result_format == "xml":
            serialize_xml_result(xml_findbuch_in, input_file, output_path,
                                 input_type, mdb_override)
        elif result_format == "json_multiple":
            serialize_json_result(xml_findbuch_in, input_file, output_path,
                                  input_type)

        input_file_i += 1
        os.chdir(
            'data_input/' + input_folder_name
        )  # Zurücksetzen des CWD (current working directory) für das Einlesen der nächsten Datei

    write_processing_status(root_path=root_path,
                            processing_step=100,
                            status_message="Transformation abgeschlossen.",
                            error_status=error_status)
    os.chdir(root_path)
Пример #4
0
def parse_xml_content(preview_testset_ids, root_path):

    # Alle Dateien im Ordner "findbuch" und "tektonik" mit den folgenden Dateiendungen werden für die Vorschau berücksichtigt:
    ext = [".xml", ".XML"]
    if preview_testset_ids is not None:
        preview_testset_ids = preview_testset_ids.split(" | ")
        if preview_testset_ids[0] == "_generate_preview_testset_":
            preview_testset_ids = build_preview_testset.parse_xml_content(
                preview_type="Bestaende",
                preview_create_count=int(preview_testset_ids[1]))

    def process_tektonik(tektonik_file_in, testset_ids):

        preview_data = {}
        xml_tektonik_in = etree.parse(tektonik_file_in)

        # Ermitteln aller Bestandsdatensätze in der Tektonik:

        if testset_ids is not None:
            preview_objects = []
            for testset_id in testset_ids:
                xpath_id_selector = "@id='{}'".format(testset_id)
                xpath_string = "//{urn:isbn:1-931666-22-9}c[@level='file'][%s]" % xpath_id_selector
                preview_object = xml_tektonik_in.findall(xpath_string)
                for element in preview_object:
                    preview_objects.append(element)
        else:
            preview_objects = xml_tektonik_in.findall(
                "//{urn:isbn:1-931666-22-9}c[@level='file']")

        for c_element in preview_objects:
            preview_data["Titel"] = ""
            preview_data["Verzeichnungsstufe"] = None
            preview_data["Bestandssignatur"] = None
            preview_data["Bestandslaufzeit"] = None
            preview_data["Bestandsbeschreibung"] = None
            preview_data["Provenienz"] = None
            preview_data["Vorprovenienz"] = None
            preview_data["Umfang"] = None
            preview_data["Urheber"] = None
            preview_data["Archivalientyp"] = None
            preview_data["Sprache der Unterlagen"] = None
            preview_data["Verwandte Bestände und Literatur"] = None
            preview_data["Indexbegriffe Person"] = None
            preview_data["Indexbegriffe Ort"] = None
            preview_data["Indexbegriffe Sache"] = None
            preview_data["Online-Beständeübersicht"] = None
            preview_data["Rechteinformation"] = None
            preview_data["Datengeber-Rücklink"] = None
            preview_data["Institution"] = None

            # Titel:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle"
            )
            for element in findlist:
                preview_data["Titel"] = element.text

            # Verzeichnungsstufe:
            preview_data["Verzeichnungsstufe"] = "Bestand"

            # Bestandssignatur:
            #   Ermitteln des Archivnamens, um die Bestandssignatur zusammenzusetzen:
            archiv = None
            findlist = xml_tektonik_in.findall(
                "//{urn:isbn:1-931666-22-9}c[@level='collection']/{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}repository/{urn:isbn:1-931666-22-9}corpname"
            )
            for element in findlist:
                if element.text is not None:
                    if "role" in element.attrib:
                        if element.attrib["role"] == "Aggregator":
                            continue
                    archiv = element.text
                    preview_data["Institution"] = archiv

            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unitid")
            for element in findlist:
                # if "type" not in element.attrib:
                if archiv is not None and element.text is not None:
                    preview_data[
                        "Bestandssignatur"] = archiv + " " + element.text
                else:
                    # preview_data["Bestandssignatur"] = element.text
                    preview_data["Bestandssignatur"] = None

            # Bestandslaufzeit:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unitdate")
            for element in findlist:
                preview_data["Bestandslaufzeit"] = element.text

            # Kontext:
            kontext = []

            file_parents = c_element.iterancestors(
                tag="{urn:isbn:1-931666-22-9}c")

            for file_parent in file_parents:
                ead_level = None
                if "level" in file_parent.attrib:
                    ead_level = file_parent.attrib["level"]
                ead_source = file_parent.findall(
                    "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle"
                )
                if len(ead_source) > 0:
                    single_kontext = [ead_level, ead_source[0].text]
                    kontext.append(single_kontext)
            del (
                kontext[-1]
            )  # # oberstes Element entfernen, damit der Bestandsknoten nicht doppelt angezeigt wird

            # Bestandsbeschreibung:
            abstract_elements = []
            scopecontent_elements = []

            findlist_abstract = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}abstract")
            findlist_scopecontent = c_element.findall(
                "{urn:isbn:1-931666-22-9}scopecontent")

            if len(findlist_abstract) > 0:
                for element in findlist_abstract:
                    if "type" in element.attrib:
                        if element.attrib["type"].startswith("ddbmapping_"):
                            continue
                    abstract_elements.append(element)

            if len(findlist_scopecontent) > 0:
                for element in findlist_scopecontent:
                    scopecontent_elements.append(element)

            preview_data[
                "Bestandsbeschreibung"] = abstract_elements + scopecontent_elements

            # Provenienz:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}origination"
            )
            for element in findlist:
                preview_data["Provenienz"] = element.text

            # Vorprovenienz:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}origination[@type='pre']"
            )
            for element in findlist:
                preview_data["Vorprovenienz"] = element.text

            # Umfang:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}physdesc/{urn:isbn:1-931666-22-9}extent"
            )
            for element in findlist:
                preview_data["Umfang"] = element.text

            # Urheber:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}origination[@type]"
            )
            for element in findlist:
                if element.attrib[
                        "type"] != "pre":  # type vorhanden, aber darf nicht "pre" sein
                    preview_data["Urheber"] = element.text

            # Archivalientyp:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}physdesc/{urn:isbn:1-931666-22-9}genreform"
            )
            for element in findlist:
                if "normal" in element.attrib:  # @normal verwenden, falls vorhanden
                    preview_data["Archivalientyp"] = element.attrib["normal"]
                else:
                    preview_data["Archivalientyp"] = element.text

            # Sprache der Unterlagen:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}langmaterial/{urn:isbn:1-931666-22-9}language"
            )
            for element in findlist:
                preview_data["Sprache der Unterlagen"] = element.text

            # Verwandte Bestände und Literatur:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}relatedmaterial")
            if len(findlist) > 0:
                relatedmaterial_elements = []
                for element in findlist:
                    relatedmaterial_elements.append(element)
                preview_data[
                    "Verwandte Bestände und Literatur"] = relatedmaterial_elements

            # Indexbegriffe Person:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}index/{urn:isbn:1-931666-22-9}indexentry/{urn:isbn:1-931666-22-9}persname"
            )
            indexentry_person_elements = []
            for element in findlist:
                indexentry_person_elements.append(
                    element
                )  # nicht als String, sondern als etree-Objekt übergeben, damit auf @source und @authfilenumber zugegriffen werden kann
            if len(indexentry_person_elements) > 0:
                preview_data[
                    "Indexbegriffe Person"] = indexentry_person_elements

            # Indexbegriffe Ort:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}index/{urn:isbn:1-931666-22-9}indexentry/{urn:isbn:1-931666-22-9}geogname"
            )
            indexentry_geogname_elements = []
            for element in findlist:
                indexentry_geogname_elements.append(
                    element
                )  # nicht als String, sondern als etree-Objekt übergeben, damit auf @source und @authfilenumber zugegriffen werden kann
            if len(indexentry_geogname_elements) > 0:
                preview_data[
                    "Indexbegriffe Ort"] = indexentry_geogname_elements

            # Indexbegriffe Sache:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}index/{urn:isbn:1-931666-22-9}indexentry/{urn:isbn:1-931666-22-9}subject"
            )
            indexentry_subject_elements = []
            for element in findlist:
                indexentry_subject_elements.append(
                    element
                )  # nicht als String, sondern als etree-Objekt übergeben, damit auf @source und @authfilenumber zugegriffen werden kann
            if len(indexentry_subject_elements) > 0:
                preview_data[
                    "Indexbegriffe Sache"] = indexentry_subject_elements

            # Online-Beständeübersicht im Angebot des Archivs
            findlist = xml_tektonik_in.findall(
                "//{urn:isbn:1-931666-22-9}c[@level='collection']/{urn:isbn:1-931666-22-9}otherfindaid/{urn:isbn:1-931666-22-9}extref[@{http://www.w3.org/1999/xlink}role='url_tektonik']"
            )
            for element in findlist:
                preview_data["Online-Beständeübersicht"] = element.attrib[
                    "{http://www.w3.org/1999/xlink}href"]

            # Rechteinformation (aus Konkordanz befüllt)
            preview_data[
                "Rechteinformation"] = "Rechteangaben beim Datengeber zu klären."

            # Datengeber-Rücklink:
            findlist = c_element.findall(
                "{urn:isbn:1-931666-22-9}otherfindaid/{urn:isbn:1-931666-22-9}extref[@{http://www.w3.org/1999/xlink}role='url_bestand']"
            )
            for element in findlist:
                preview_data["Datengeber-Rücklink"] = element.attrib[
                    "{http://www.w3.org/1999/xlink}href"]

            # Ausgabe der HTML-Datei:
            create_html_files.parse_xml_content(kontext,
                                                preview_data,
                                                tektonik_file_in,
                                                preview_type="Bestaende")

    os.chdir("tektonik")

    for input_file in os.listdir("."):
        if handle_thread_actions.load_from_xml("stop_thread",
                                               root_path) is False:
            if input_file.endswith(tuple(ext)):
                process_tektonik(input_file, preview_testset_ids)
        else:
            break

    os.chdir("..")
def handle_validation(root_path: str, input_files: list, rule_definition: str):
    """Übergebene Liste von Input-Dateien an validify übergeben, Validierungsergebnisse pro Datei als List sammeln und in Gesamt-Liste zusammennführen.

    Überführen in ein Pandas DataFrame und Aggregrieren der Validierungsergebnisse (mehrere Vorkommen zusammenfassen).
    Übergabe der Validierungsergebnisse an Template-Erzeugung.
    """
    timer_start = datetime.datetime.now()

    # Zurücksetzen des Prozessierungs-Status:
    write_processing_status(root_path=root_path,
                            processing_step=None,
                            status_message=None,
                            error_status=0)
    error_status = 0

    validation_rules_findbuch = eadddb_findbuch.compile_validation_rules()
    validation_rules_tektonik = eadddb_tektonik.compile_validation_rules()

    ext = [".xml", ".XML"]
    validation_results = []
    input_files_count = len(input_files)

    for input_file_i, input_file in enumerate(input_files):
        if handle_thread_actions.load_from_xml("stop_thread",
                                               root_path) is True:
            break
        validation_progress = int((input_file_i / input_files_count) * 100)
        write_processing_status(
            root_path=root_path,
            processing_step=validation_progress,
            status_message="Validierung läuft für Datei {} ({}/{})".format(
                input_file, input_file_i + 1, input_files_count),
            error_status=error_status)
        if input_file.endswith(tuple(ext)):
            validation_results_single = []
            if rule_definition == "eadddb_findbuch":
                validation_results_single = validify.validate(
                    input_file,
                    validation_rules=validation_rules_findbuch,
                    log_to_console=False)
            elif rule_definition == "eadddb_tektonik":
                validation_results_single = validify.validate(
                    input_file,
                    validation_rules=validation_rules_tektonik,
                    log_to_console=False)

            for item in validation_results_single:
                item.update({"input_file": input_file})
            validation_results.extend(validation_results_single)

    timer_end = datetime.datetime.now()
    processing_duration = timer_end - timer_start
    logger.debug("Prozessierungsdauer (Validierung - validify): {}".format(
        processing_duration))

    # Aggregrieren der Validierungsergebnisse (mehrere Vorkommen zusammenfassen)
    timer_start = datetime.datetime.now()
    aggregated_validation_results = []

    if len(validation_results) > 0:
        validation_results_dataframe = pd.DataFrame(validation_results)
        validation_results_dataframe[
            'aggregated_details'] = validation_results_dataframe[
                "element_local_name"].astype(
                    str
                ) + ";" + validation_results_dataframe["input_file"].astype(
                    str) + ";" + validation_results_dataframe[
                        "element_sourceline"].astype(
                            str) + ";" + validation_results_dataframe[
                                "element_path"].astype(str)

        aggregated_validation_results = (validation_results_dataframe.groupby(
            ['message_id',
             'element_name']).agg(set).reset_index().to_dict('r'))

    timer_end = datetime.datetime.now()
    processing_duration = timer_end - timer_start
    logger.debug("Prozessierungsdauer (Validierung - pandas): {}".format(
        processing_duration))

    # Serialisierung der Validierungsergebnisse
    timer_start = datetime.datetime.now()

    serialize_validation_results.export_to_html(aggregated_validation_results)

    timer_end = datetime.datetime.now()
    write_processing_status(root_path=root_path,
                            processing_step=100,
                            status_message="Validierung abgeschlossen.",
                            error_status=error_status)
    processing_duration = timer_end - timer_start
    logger.debug(
        "Prozessierungsdauer (Validierung - HTML-Serialisierung): {}".format(
            processing_duration))
def parse_xml_content(preview_testset_ids, root_path):

    # Alle Dateien im Ordner "findbuch" und "tektonik" mit den folgenden Dateiendungen werden für die Vorschau berücksichtigt:
    ext = [".xml", ".XML"]
    if preview_testset_ids is not None:
        preview_testset_ids = preview_testset_ids.split(" | ")
        if preview_testset_ids[0] == "_generate_preview_testset_":
            preview_testset_ids = build_preview_testset.parse_xml_content(
                preview_type="Gliederungsgruppen",
                preview_create_count=int(preview_testset_ids[1]))

    def process_findbuch_tektonik(findbuch_file_in, testset_ids):

        preview_data = {}
        xml_findbuch_in = etree.parse(findbuch_file_in)

        # Ermitteln aller Elemente mit abstract-Element:

        if testset_ids is not None:
            preview_objects = []
            for testset_id in testset_ids:
                xpath_id_selector = "@id='{}'".format(testset_id)
                xpath_string = "//{urn:isbn:1-931666-22-9}c[@level='class'][%s]" % xpath_id_selector
                preview_object = xml_findbuch_in.findall(xpath_string)
                for element in preview_object:
                    preview_objects.append(element)
        else:
            preview_objects = xml_findbuch_in.findall(
                "//{urn:isbn:1-931666-22-9}c[@level='class']"
            )  # TODO: für level="series" ergänzen

        for c_element in preview_objects:
            preview_data["Titel"] = ""
            preview_data["Verzeichnungsstufe"] = None
            preview_data["Beschreibung"] = None
            preview_data["Online-Beständeübersicht"] = None
            preview_data["Rechteinformation"] = None
            preview_data["Institution"] = None

            # Ermitteln, ob ein Abstract-Element vorhanden ist und somit ein DDB-Objekt generiert wird
            abstract_exists = c_element.findall(
                "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}abstract")
            if len(abstract_exists) > 0:

                # Ermitteln des Archivnamens (zur Befüllung der Hierarchie benötigt)
                findlist = xml_findbuch_in.findall(
                    "//{urn:isbn:1-931666-22-9}archdesc/{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}repository/{urn:isbn:1-931666-22-9}corpname"
                )
                if len(findlist) == 0:
                    findlist = xml_findbuch_in.findall(
                        "//{urn:isbn:1-931666-22-9}c[@level='collection']/{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}repository/{urn:isbn:1-931666-22-9}corpname"
                    )
                for element in findlist:
                    if element.text is not None:
                        if "role" in element.attrib:
                            if element.attrib["role"] == "Aggregator":
                                continue
                        preview_data["Institution"] = element.text

                # Ermitteln des Kontexts
                kontext = []

                file_parents = c_element.iterancestors(
                    tag="{urn:isbn:1-931666-22-9}c")

                for file_parent in file_parents:
                    ead_level = None
                    if "level" in file_parent.attrib:
                        ead_level = file_parent.attrib["level"]
                    ead_source = file_parent.findall(
                        "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle"
                    )
                    if len(ead_source) > 0:
                        single_kontext = [ead_level, ead_source[0].text]
                        kontext.append(single_kontext)
                # del kontext[file_parent_i_str]  # den obersten Parent (Bestandsdatensatz) löschen, damit dieser nicht doppelt ausgegeben wird  # TODO: Prüfen ob benötigt

                # Ermitteln des Titels
                findlist = c_element.findall(
                    "{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle"
                )
                for element in findlist:
                    preview_data["Titel"] = element.text

                # Verzeichnungsstufe:
                preview_data["Verzeichnungsstufe"] = "Gliederung"

                # Ermitteln der Beschreibung
                abstract_elements = []
                for element in abstract_exists:
                    if "type" in element.attrib:
                        if element.attrib["type"].startswith("ddbmapping_"):
                            continue
                    abstract_elements.append(element)
                preview_data["Beschreibung"] = abstract_elements

                # Online-Beständeübersicht im Angebot des Archivs
                findlist = xml_findbuch_in.findall(
                    "//{urn:isbn:1-931666-22-9}c[@level='collection']/{urn:isbn:1-931666-22-9}otherfindaid/{urn:isbn:1-931666-22-9}extref[@{http://www.w3.org/1999/xlink}role='url_tektonik']"
                )
                if len(findlist) == 0:
                    findlist = xml_findbuch_in.findall(
                        "//{urn:isbn:1-931666-22-9}archdesc[@level='collection']/{urn:isbn:1-931666-22-9}otherfindaid/{urn:isbn:1-931666-22-9}extref[@{http://www.w3.org/1999/xlink}role='url_findbuch']"
                    )
                for element in findlist:
                    preview_data["Online-Beständeübersicht"] = element.attrib[
                        "{http://www.w3.org/1999/xlink}href"]

                # Rechteinformation (aus Konkordanz befüllt)
                preview_data[
                    "Rechteinformation"] = "Rechteangaben beim Datengeber zu klären."

                # Ausgabe der HTML-Datei:
                create_html_files.parse_xml_content(
                    kontext,
                    preview_data,
                    findbuch_file_in,
                    preview_type="Gliederungsgruppen")

    os.chdir("findbuch")

    for input_file in os.listdir("."):
        if handle_thread_actions.load_from_xml("stop_thread",
                                               root_path) is False:
            if input_file.endswith(tuple(ext)):
                process_findbuch_tektonik(input_file, preview_testset_ids)
        else:
            break

    os.chdir("../tektonik")
    [
        process_findbuch_tektonik(input_file, preview_testset_ids)
        for input_file in os.listdir(".") if input_file.endswith(tuple(ext))
    ]

    for input_file in os.listdir("."):
        if handle_thread_actions.load_from_xml("stop_thread",
                                               root_path) is False:
            if input_file.endswith(tuple(ext)):
                process_findbuch_tektonik(input_file, preview_testset_ids)
        else:
            break

    os.chdir("..")
Пример #7
0
def parse_xml_content(missing_links_list, root_path):

    id_list = []
    id_list_tektonik = []

    def find_all_ids(findbuch_file_in, input_type_):
        # Schritt 1: sammeln aller IDs
        # if not findbuch_file_in.startswith("enriched_"):  # Die angereicherte Tektonik soll nicht verarbeitet werden, da dies zur Erkennung von Dubletten führt, wenn sich die ursprüngliche Tektonik noch im Ordner befindet.
        xml_findbuch_in = etree.parse(findbuch_file_in)

        findlist = xml_findbuch_in.findall("//{urn:isbn:1-931666-22-9}c")
        for element in findlist:
            if "id" in element.attrib:
                add_id = element.attrib["id"]
                if input_type_ == "findbuch":
                    id_list.append(add_id)
                if input_type_ == "tektonik":
                    id_list_tektonik.append(add_id)
            else:
                logger.warning("Fehlende ID für Objekt in Datei " + findbuch_file_in + " auf Ebene " + element.attrib["level"])

    ext = [".xml", ".XML"]

    os.chdir("findbuch")
    input_type = "findbuch"
    [find_all_ids(input_file, input_type) for input_file in os.listdir('.') if input_file.endswith(tuple(ext))]

    os.chdir("../tektonik")
    input_type = "tektonik"
    [find_all_ids(input_file, input_type) for input_file in os.listdir('.') if input_file.endswith(tuple(ext))]

    os.chdir("..")

    logger.info("[Erfolgreich] Schritt 1: Sammeln aller IDs")
    logger.info("Anzahl aller IDs (Findbuch): {}".format(len(id_list)))
    logger.info("Anzahl aller IDs (Tektonik): {}".format(len(id_list_tektonik)))


    def list_duplicates(seq):
        # Schritt 2: Duplikate finden und auflisten

        seen = set()
        seen_add = seen.add
        # adds all elements it doesn't know yet to seen and all other to seen_twice
        seen_twice = set(x for x in seq if x in seen or seen_add(x))
        # turn the set into a list
        return list(seen_twice)

    duplicates_list = list_duplicates(id_list)
    duplicates_list_tektonik = list_duplicates(id_list_tektonik)

    logger.info("[Erfolgreich] Schritt 2: Identifizieren doppelter IDs")
    logger.info("Anzahl doppelter IDs (Findbuch): {}".format(len(duplicates_list)))

    if len(duplicates_list) >= 1:
        logger.info("Doppelte IDs (Findbuch): {}".format(duplicates_list))

    logger.info("Anzahl doppelter IDs (Tektonik): {}".format(len(duplicates_list_tektonik)))

    if len(duplicates_list_tektonik) >= 1:
        logger.info("Doppelte IDs (Tektonik): {}".format(duplicates_list_tektonik))

    # Übergabe der doppelten IDs und fehlenden Findbuch-Tektonik-Links an die HTML-Generierung:
    export_statistics.export_to_html_technichal(duplicates_list, duplicates_list_tektonik, missing_links_list)

    # Ausgabe der doppelten IDs als Plaintext:
    if len(duplicates_list) >= 1:
        txt_output_file = 'duplicates_findbuch.txt'
        txt_output = open(txt_output_file, 'w')
        for entry in duplicates_list:
            txt_output.write(entry + '\n')
        txt_output.close()

    if len(duplicates_list_tektonik) >= 1:
        txt_output_file = 'duplicates_tektonik.txt'
        txt_output = open(txt_output_file, 'w')
        for entry in duplicates_list_tektonik:
            txt_output.write(entry + '\n')
        txt_output.close()

    # Schritt 3: Löschen der Duplikate
    os.chdir("findbuch")
    if not os.path.isdir('./deduplicated_xml') and len(duplicates_list) >= 1:
        os.mkdir('deduplicated_xml')
    os.chdir("../tektonik")
    if not os.path.isdir('./deduplicated_xml') and len(duplicates_list_tektonik) >= 1:
        os.mkdir('deduplicated_xml')
    os.chdir("..")

    def delete_duplicate_items(findbuch_file_in, input_type_):
        xml_findbuch_in = etree.parse(findbuch_file_in)

        duplicate_list_to_process = []
        if input_type_ == "findbuch":
            duplicate_list_to_process = duplicates_list

        if input_type_ == "tektonik":
            duplicate_list_to_process = duplicates_list_tektonik

        for duplicate_id in duplicate_list_to_process:
            duplicate_id_xpath = "//{urn:isbn:1-931666-22-9}c[@id='%s']" % duplicate_id
            findlist = xml_findbuch_in.findall(duplicate_id_xpath)
            anzahl_elemente = len(findlist)
            if anzahl_elemente > 0:
                unitid_orig = findlist[0].findall("{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unitid")
                try:
                    unitid_orig = unitid_orig[0].text
                except IndexError:
                    unitid_orig = None
                    continue
                unittitle_orig = findlist[0].findall("{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle")
                try:
                    unittitle_orig = unittitle_orig[0].text
                except IndexError:
                    unittitle_orig = None
                    continue
            element_i = 0

            while element_i <= anzahl_elemente-1 and anzahl_elemente > 0:
                if element_i > 0:
                    duplicate_object = findlist[element_i]
                    unitid_dup = duplicate_object.findall("{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unitid")
                    if len(unitid_dup) > 0:
                        unitid_dup = unitid_dup[0].text
                    else:
                        unitid_dup = None
                    unittitle_dup = duplicate_object.findall("{urn:isbn:1-931666-22-9}did/{urn:isbn:1-931666-22-9}unittitle")
                    if len(unittitle_dup) > 0:
                        unittitle_dup = unittitle_dup[0].text
                    else:
                        unittitle_dup = None

                    if (unittitle_orig and unittitle_dup and unitid_orig and unitid_dup) is not None:
                    #if unittitle_orig is not None and unittitle_dup is not None and unitid_orig is not None and unitid_dup is not None:
                        if unittitle_dup == str(unittitle_orig) and unitid_dup == str(unitid_orig):
                            logger.info("[Wird gelöscht] Objekt mit XML-ID: " + duplicate_id + "; " + findbuch_file_in + "; Vorkommen Nr.: " + str(element_i+1) + "; Level: " + duplicate_object.attrib["level"])
                            duplicate_object.clear()
                            try:
                                duplicate_object.getparent().remove(duplicate_object)
                            except AttributeError:
                                logger.warning("[Fehler] Fehlender Parent? Verwaisten Knoten prüfen" + duplicate_id + "; " + findbuch_file_in + "; " + "Vorkommen-Nr.: " + str(element_i+1))
                        else:
                            logger.info("[Nicht gelöscht, da nicht identisch]" + duplicate_id + "; " + findbuch_file_in + "; Vorkommen Nr.: " + str(element_i+1) + "; Level: " + duplicate_object.attrib["level"])

                    else:
                        logger.warning("[Kein Vergleich möglich, da unittitle und/oder unitid nicht vorhanden] " + duplicate_id + "; " + findbuch_file_in + "; Vorkommen Nr.: " + str(element_i+1) + "; Level: " + duplicate_object.attrib["level"])
                element_i += 1

        xml_output_file = 'deduplicated_xml/' + findbuch_file_in
        xml_findbuch_out = open(xml_output_file, 'wb')
        xml_findbuch_in.write(xml_findbuch_out, encoding='utf-8', xml_declaration=True)
        xml_findbuch_out.close()


    ext = [".xml", ".XML"]

    os.chdir("findbuch")
    input_type = "findbuch"
    if len(duplicates_list) >= 1:
        # [delete_duplicate_items(input_file, input_type) for input_file in os.listdir('.') if input_file.endswith(tuple(ext))]
        for input_file in os.listdir('.'):
            if handle_thread_actions.load_from_xml("stop_thread", root_path) is False:
                if input_file.endswith(tuple(ext)):
                    delete_duplicate_items(input_file, input_type)
            else:
                break

    os.chdir("../tektonik")
    input_type = "tektonik"
    if len(duplicates_list_tektonik) >= 1:
        if handle_thread_actions.load_from_xml("stop_thread", root_path) is False:
            [delete_duplicate_items(input_file, input_type) for input_file in os.listdir('.') if input_file.endswith(tuple(ext))]

    os.chdir("..")

    logger.info("[Erfolgreich] Schritt 3: Löschen ermittelter Dubletten")