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-Schnittstelle
base_url <- "https://kalliope-verbund.info/sru"

# Definition der Parameter der SRU-Schnittstelle
params <- 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ühren
sru.result.set <- GET(base_url, query = params)

# Test der Schnittstelle
if (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 selektieren
  for (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" speichern
  write.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 speichern
  write(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'