def parse_xml_content(root_path,
                      xml_findbuch_in,
                      input_type,
                      input_file,
                      error_status,
                      provider_scripts=None):

    # provider_scripts sollte wie folgt als List übergeben werden, welche wiederum die einzelnen Anpassungen als dict-Objekt (ISIL, Modulname) enthält:
    # [{"ISIL": "DE-2088", "Modulname": "module_name"}, {.., ..}]

    if provider_scripts is None:
        provider_scripts = load_provider_modules()

    for script in provider_scripts:
        isil = script["ISIL"]
        module_name = script["Modulname"]
        module_path = "../../modules/provider_specific/{}/{}".format(
            isil.replace("-", "_"), module_name)
        spec = importlib.util.spec_from_file_location("parse_xml_content",
                                                      module_path)
        provider_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(provider_module)

        try:
            xml_findbuch_in = provider_module.parse_xml_content(
                xml_findbuch_in, input_type, input_file)

        except (IndexError, TypeError, AttributeError, KeyError, SyntaxError,
                OSError, etree.XPathEvalError) as e:
            traceback_string = traceback.format_exc()
            logger.warning(
                "Providerspezifische Anpassung {} konnte für die Datei {} nicht angewandt werden; Fehlermeldung: {}. Vermutlich wurde die Anpassung nicht für das vorliegende Exportformat angepasst.\n {}"
                .format(module_name, 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)
            continue

    return xml_findbuch_in, error_status
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 parse_xml_content(xml_findbuch_in,
                      input_type,
                      input_file,
                      module_config,
                      root_path,
                      namespaces=None):
    """Verarbeitung von Nutzerinteraktionen für Workflow-Module."""
    user_interaction_message = module_config["message"]
    user_interaction_input_file = module_config["input_file"]
    process_current_input_file = False

    if user_interaction_input_file == input_file:
        process_current_input_file = True
    elif user_interaction_input_file.startswith("*") and input_file.endswith(
            user_interaction_input_file[1:]):
        process_current_input_file = True
    elif user_interaction_input_file.endswith("*") and input_file.replace(
            ".xml", "").startswith(user_interaction_input_file[:-1]):
        process_current_input_file = True

    if process_current_input_file:
        # processing_status schreiben (handle_session_data.write_processing_status()), damit GUI-Thread darauf reagieren kann

        # Rausschreiben der aktuellen Datei in temporäres Verzeichnis
        temp_dir = "{}/data_input/.{}".format(root_path, str(uuid4().hex))
        temp_file = "{}/{}".format(temp_dir, input_file)
        if not os.path.isdir(temp_dir):
            os.mkdir(temp_dir)

        with open(temp_file, 'wb') as xml_output:
            xml_findbuch_in.write(xml_output,
                                  encoding='utf-8',
                                  xml_declaration=True)
        logger.debug(
            "Input-Datei für Nutzerinteraktion in temporärem Verzeichnis bereitgestellt: {}"
            .format(temp_file))

        handle_session_data.write_processing_status(
            root_path,
            raise_user_interaction="1",
            user_interaction_message=user_interaction_message,
            user_interaction_input_files=temp_dir)

        # Periodisches Auslesen des Processing-Status, um zu ermitteln, wann die modifizierte Input-Datei eingelesen und mit der weiteren Prozessierung fortgefahren werden soll
        user_interaction_finished = False
        while not user_interaction_finished:
            time.sleep(1)
            processing_status = handle_session_data.get_processing_status(
                root_path)
            if processing_status[
                    "raise_user_interaction"] == "0":  # Wert wird durch GUI-Thread auf "0" zurückgesetzt, sobald die Nutzerinteraktion abgeschlossen ist.
                user_interaction_finished = True

        # Erneutes Einlesen der modifizierten Input-Datei und Rückgabe anstelle von xml_findbuch_in
        xml_findbuch_in = etree.parse(temp_file)

        # Temporären Ordner löschen
        if os.path.isdir(temp_dir):
            shutil.rmtree(temp_dir)

        handle_session_data.write_processing_status(
            root_path,
            raise_user_interaction="0",
            user_interaction_message="",
            user_interaction_input_files="")

        logger.debug(
            "Nutzerinteraktion abgeschlossen, Input-Datei erneut eingelesen und Prozessierung fortgesetzt."
        )

    return xml_findbuch_in
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)
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))