Kalliope-Verbund - Daten 3, Liste der Archivbestände und Nachlässe
Einführung
Das R Script erstellt eine Übersicht von Bestandsbeschreibungen am Beispiel der Staatsbibliothek zu Berlin - Preußischer Kulturbesitz mit mehreren Abteilungen: Handschriften- und Musikabteilung, bpk-Fototarchiv, Orientabteilung. Die Abfrage ist adaptierbar für verschiedene Einrichtungen, deren Archivbestände wie Nachlässe etc. im Kalliope-Katalog nachgewiesen werden.
R Code
Für das folgende Beispiel werden die R-Pakete httr, xml2, stringr und dplyr mit der R-Funktion library() geladen. Durch den Parameter {r message=FALSE} werden Antworten des Systems unterdrückt.
Das Beispiel enthält: - Daten abfrage (SRU-Schnittstelle) - Daten laden - Daten selektieren (XPath) - Daten transformieren - Daten speichern CSV und HTML Die CSV-Daten können bspw. in Excel geladen und analysiert werden.
library(httr)library(xml2)library(stringr)library(dplyr)# Definition der Webadresse der SRU-Schnittstellebase_url <-"https://kalliope-verbund.info/sru"# Definition der Parameter der SRU-Schnittstelleparams <-list(version ="1.2",operation ="searchRetrieve",query ="parent_s:\"\"&& (ead.repository.isil:\"DE-1a\" || ead.repository.isil:\"DE-1\" || ead.repository.isil:\"DE-1b\")",recordSchema ="mods37",startRecord =1, #maximumRecords =5500)# Abfrage ausführensru.result.set <-GET(base_url, query = params)# Test der Schnittstelleif (status_code(sru.result.set) ==200) { data.set <-read_xml(content(sru.result.set, "text"))# Namensraum definieren (MODS) ns <-xml_ns(data.set) mods_ns <- ns[grep("mods", names(ns))]# Vorbereitung von Listen für diverse Ausgaben result_list <-list() # HTML result_list_plain <-list() # CSV# Datensätze durchlaufen lassen records <-xml_find_all(data.set, "//mods:mods", mods_ns) num_records <-length(records)cat("Gefundene Datensätze:", num_records, "\n")# Daten selektierenfor (record in records) {# Function to extract text and replace NA or empty values with "-" extract_text <-function(xpath, record, ns) { value <-xml_find_first(record, xpath, ns) |>xml_text()ifelse(is.na(value) || value =="", "-", value) }# Element //nameParts mit URI und abhänigg von der Funktion extrahieren name_parts <- record |>xml_find_all("mods:name[mods:role/mods:roleTerm[text()='Bestandsbildner']]/mods:namePart", mods_ns) |>xml_text() name_uris <- record |>xml_find_all("mods:name[mods:role/mods:roleTerm[text()='Bestandsbildner']]/@valueURI", mods_ns) |>xml_text() name_parts_links <-if (length(name_parts) >0) {sapply(seq_along(name_parts), function(i) {paste0("<a href='", name_uris[i], "'>", name_parts[i], "</a>") }) |>paste(collapse ="; ") } else {"-" }# Element //title extrahieren title <-extract_text("mods:titleInfo/mods:title", record, mods_ns)# Element //shelfLocator extrahieren shelf_location <-extract_text("mods:location/mods:shelfLocator", record, mods_ns)# Element //abstract extrahieren abstract <-extract_text("mods:abstract", record, mods_ns)# Element //extent extrahieren physical_description <-extract_text("mods:physicalDescription/mods:extent", record, mods_ns)# Element //genre extrahieren genres <- record |>xml_find_all("mods:genre", mods_ns) |>xml_text() genre_uris <- record |>xml_find_all("mods:genre/@valueURI", mods_ns) |>xml_text() genres_links <-if (length(genres) >0) {sapply(seq_along(genres), function(i) {paste0("<a href='", genre_uris[i], "'>", genres[i], "</a>") }) |>paste(collapse ="; ") } else {"-" }# Element //recordContentSource mit URI extrahieren (Bestandshaltende Einrichtung) record_content_source <-extract_text("mods:recordInfo/mods:recordContentSource", record, mods_ns) record_content_source_uri <-xml_find_first(record, "mods:recordInfo/mods:recordContentSource/@valueURI", mods_ns) |>xml_text() record_content_source_link <-if (record_content_source !="-"&& record_content_source_uri !="") {paste0("<a href='", record_content_source_uri, "'>", record_content_source, "</a>") } else { record_content_source }# Identifier (URI) extrahieren identifier <-extract_text("mods:identifier[@type='uri']", record, mods_ns) identifier_link <-if (identifier !="-") {paste0("<a href='", identifier, "'>", identifier, "</a>") } else { identifier }# Datenframe für HTML-Ausgabe definieren temp_df <-data.frame(Name = name_parts_links,Title = title,Signatur = shelf_location,Beschreibung = abstract,Umfang = physical_description,Ressourcentyp = genres_links,Katalog = identifier_link,Abteilung = record_content_source_link )# Datenframe für CSV-Ausgabe definieren temp_df_plain <-data.frame(Name =if (length(name_parts) >0) paste(name_parts, collapse =", ") else"-",Title = title,Signatur = shelf_location,Beschreibung = abstract,Umfang = physical_description,Ressourcentyp =if (length(genres) >0) paste(genres, collapse =", ") else"-",Katalog = identifier,Abteilung = record_content_source )# Ergebnisse den vordefinierten Listen für HTML und CSV zuweisen result_list[[length(result_list) +1]] <- temp_df result_list_plain[[length(result_list_plain) +1]] <- temp_df_plain }# Vorläufige Data Frame zusammenfassen result <-bind_rows(result_list) result_plain <-bind_rows(result_list_plain)# CSV-Datei im Ordner "Download" speichernwrite.csv(result_plain, "~/Downloads/output_plain.csv", row.names =FALSE, fileEncoding ="UTF-8")# HTML-Datei im Ordner "Download" Speichern - hier: zunächst Definition des HTML-Formats html_content <-paste0("<html> <head> <title>Bestandsübersicht</title> <meta charset=\"UTF-8\"/> <link rel=\"stylesheet\" href=\"https://cdn.datatables.net/2.0.8/css/dataTables.dataTables.css\"/> <script src=\"https://code.jquery.com/jquery-3.7.1.min.js\" integrity=\"sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=\" crossorigin=\"anonymous\"></script> <script src=\"https://cdn.datatables.net/2.0.8/js/dataTables.js\"></script> <style> .table-container { overflow-x: auto; -webkit-overflow-scrolling: touch; } .filter-dropdown { margin: 10px; } #kalliope_bestand { border-collapse: collapse; border: 1px solid black; } #kalliope_bestand th, #kalliope_bestand td { border: 1px solid black; word-wrap: break-word; word-break: break-word; hyphens: auto; white-space: normal; vertical-align: top; text-align: left; padding: 10px; margin: 0; width: 16.66%; } #kalliope_bestand thead { position: sticky; top: 0; background-color: #f2f2f2; z-index: 1; } </style> </head> <body> <div class=\"table-container\"> <table id=\"kalliope_bestand\" class=\"display\"> <thead> <tr> <th>Name</th> <th>Titel</th> <th>Beschreibung</th> <th>Umfang</th> <th>Katalog</th> <th>Abteilung</th> </tr> </thead> <tbody>",apply(result, 1, function(row) {paste0("<tr> <td>", row['Name'], "</td> <td>", row['Title'], "</td> <td>", row['Beschreibung'], "</td> <td>", row['Umfang'], "</td> <td>", row['Katalog'], "</td> <td>", row['Abteilung'], "</td> </tr>" ) }) |>paste0(collapse ="\n"),"</tbody> </table> </div> <!-- JavaScript for toggling description preview --> <script> $(document).ready(function() { $('#kalliope_bestand').DataTable({ pageLength: 20, dom: '<\"top\"f><\"filter-dropdown\"l>rt<\"bottom\"ip>', language: { search: \"\", zeroRecords: \"Keine Treffer gefunden\", emptyTable: \"Keine Daten verfügbar\", info: \"Bestand _START_ bis _END_ von _TOTAL_ Beständen\", infoEmpty: \"0 Treffer in _TOTAL_ Beständen\", infoFiltered: \"(gefiltert aus insgesamt _MAX_ Beständen)\", paginate: { first: \"Erste\", last: \"Letzte\", next: \"Nächste\", previous: \"Vorherige\" } }, pagingType: \"simple_numbers\", order: [[0, 'asc']] }); }); </script> </body> </html>" )# HTML-Datei im Download-Ordner speichernwrite(html_content, file ="~/Downloads/output.html")cat("Das Ergebnis wurde gespeichert in '~/Downloads/output.html' and '~/Downloads/output_plain.csv'")} else {warning("Fehler der Datenabfrage: ", status_code(response))}
Gefundene Datensätze: 1793
Das Ergebnis wurde gespeichert in '~/Downloads/output.html' and '~/Downloads/output_plain.csv'