Kapitel 3 ergänzt
This commit is contained in:
parent
70b9ec18b0
commit
f5e29f41cc
|
@ -3,6 +3,6 @@
|
|||
Ziel: Metadatenformate und Möglichkeiten zum freien Austausch von Metadaten kennenlernen
|
||||
|
||||
Inhalte:
|
||||
* [2.1.1 Metadatenformate und -standards](2-1-1-metadatenformate-und-standards.md)
|
||||
* [2.1.2 Initiativen zum Austausch von Metadaten](2-1-2-initiativen-zum-austausch-von-metadaten.md)
|
||||
* [2.1.3 Schnittstellen des Bibliotheksverbunds GBV](2-1-3-schnittstellen-des-bibliotheksverbunds-gbv.md)
|
||||
* [Metadatenformate und -standards](2-1-1-metadatenformate-und-standards.md)
|
||||
* [Initiativen zum Austausch von Metadaten](2-1-2-initiativen-zum-austausch-von-metadaten.md)
|
||||
* [Schnittstellen des Bibliotheksverbunds GBV](2-1-3-schnittstellen-des-bibliotheksverbunds-gbv.md)
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
Ziel: Ausgewählte Metadaten der Bibliothek in verschiedenen Formaten über eine Schnittstelle laden und vergleichen. Anschließend Download der gesamten Daten im gewünschten Format.
|
||||
|
||||
Inhalte:
|
||||
* [2.2.1 Übung: unAPI-Schnittstelle des GBV](2-2-1-uebung-unapi-schnittstelle-des-gbv.md)
|
||||
* [2.2.2 Übung: Vergleich der verschiedenen Formate](2-2-2-uebung-vergleich-der-verschiedenen-formate.md)
|
||||
* [2.2.3 Download der Metadaten](2-2-3-download-der-metadaten.md)
|
||||
* [Übung: unAPI-Schnittstelle des GBV](2-2-1-uebung-unapi-schnittstelle-des-gbv.md)
|
||||
* [Übung: Vergleich der verschiedenen Formate](2-2-2-uebung-vergleich-der-verschiedenen-formate.md)
|
||||
* [Download der Metadaten](2-2-3-download-der-metadaten.md)
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
Ziel: Heruntergeladene Metadaten mit OpenRefine in ein Tabellenformat transformieren.
|
||||
|
||||
Inhalte:
|
||||
* [2.3.1 Installation OpenRefine](2-3-1-installation-openrefine.md)
|
||||
* [2.3.2 OpenRefine starten und Daten laden](2-3-2-openrefine-starten-und-daten-laden.md)
|
||||
* [2.3.3 Facetten und Text Filter](2-3-3-facetten-und-text-filter.md)
|
||||
* [2.3.4 Records bilden](2-3-4-records-bilden.md)
|
||||
* [2.3.5 Für jedes MARC-Feld eine Spalte](2-3-5-fuer-jedes-marc-feld-eine-spalte.md)
|
||||
* [Installation OpenRefine](2-3-1-installation-openrefine.md)
|
||||
* [OpenRefine starten und Daten laden](2-3-2-openrefine-starten-und-daten-laden.md)
|
||||
* [Facetten und Text Filter](2-3-3-facetten-und-text-filter.md)
|
||||
* [Records bilden](2-3-4-records-bilden.md)
|
||||
* [Für jedes MARC-Feld eine Spalte](2-3-5-fuer-jedes-marc-feld-eine-spalte.md)
|
||||
|
|
|
@ -10,9 +10,3 @@ Hinweise:
|
|||
* Schauen Sie sich die Einführungsvideos zu OpenRefine an, insbesondere die ca. [7-Minuten-Kurzeinführung bei YouTube](https://www.youtube.com/watch?v=B70J_H_zAWM)
|
||||
* Konsultieren Sie die [Seite zur Facettierung in der Dokumentation von OpenRefine](https://github.com/OpenRefine/OpenRefine/wiki/Faceting) und probieren Sie verschiedene Facetten aus.
|
||||
* Gute Übungen zum Einstieg bietet auch Library Carpentry OpenRefine: [Basic OpenRefine Functions I: Working with columns, sorting, faceting, filtering and clustering](https://data-lessons.github.io/library-openrefine/03-basic-functions-I/)
|
||||
|
||||
## Bonusaufgabe: Laden Sie die Arbeitstabelle der DNB in OpenRefine
|
||||
|
||||
Die Dokumentation der DNB steht auch als Tabellendokument zur Verfügung. Da OpenRefine auf die Bearbeitung von tabellarischen Daten ausgelegt ist, können Sie die Arbeitstabelle anstatt in Excel/LibreOffice auch gleich in OpenRefine als neues Projekt anlegen.
|
||||
|
||||
[Arbeitstabelle der Deutschen Nationalbibliothek](http://www.dnb.de/SharedDocs/Downloads/DE/DNB/standardisierung/marc21FeldbeschreibungTitelExcel032016.zip)
|
||||
|
|
|
@ -35,8 +35,8 @@ Schritt 4: MARC-Feld mit Feld MARC-Code zusammenfassen
|
|||
Schritt 5: Sortieren und Aufräumen
|
||||
|
||||
* Spalte "PPN" / Edit cells / Fill down
|
||||
* Spalte "PPN" / Sort...
|
||||
* Spalte "record - datafield - tag" / Sort...
|
||||
* Spalte "PPN" / Sort... / OK
|
||||
* Spalte "record - datafield - tag" / Sort... / OK
|
||||
* Im neu verfügbaren Menü "Sort" den Menüpunkt "Reorder rows permanently" auswählen
|
||||
* Spalte "PPN" / Edit column / Remove this column
|
||||
|
||||
|
@ -47,7 +47,7 @@ Schritt 6: Felder mit Mehrfachbelegungen zusammenführen
|
|||
|
||||
Schritt 7: Transpose
|
||||
|
||||
* Spalte "record - datafield - tag" / Transpose / Columnize by key/value columns...
|
||||
* Spalte "record - datafield - tag" / Transpose / Columnize by key/value columns... / OK
|
||||
|
||||
## Literatur zu den genutzten Funktionen
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# 3 Transformierte Metadaten mit Solr indexieren
|
||||
|
||||
Ziel: ...
|
||||
Ziel: Metadaten mit OpenRefine weiter bearbeiten, exportieren und in einen Suchindex laden
|
||||
|
||||
Bitte nehmen Sie sich für die folgenden Abschnitte insgesamt etwa 4 Stunden Zeit:
|
||||
|
||||
* [...]()
|
||||
* [...]()
|
||||
* [...]()
|
||||
* [3.1 Daten mit OpenRefine für Indexierung vorbereiten](3-1-0-daten-mit-openrefine-fuer-indexierung-vorbereiten.md)
|
||||
* [3.2 Suchmaschinenindex Solr](3-2-0-suchmaschinenindex-solr.md)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# 3.1 Daten mit OpenRefine für Indexierung vorbereiten
|
||||
|
||||
Ziel: Gesamte Metadaten mit OpenRefine automatisiert verarbeiten, für die Indexierung vorbereiten und exportieren
|
||||
|
||||
Inhalte:
|
||||
* [Transformationshistorie anwenden](3-1-1-transformationshistorie-anwenden.md)
|
||||
* [Alle Daten automatisiert verarbeiten](3-1-2-alle-daten-automatisiert-verarbeiten.md)
|
||||
* [Felder definieren und Daten exportieren](3-1-3-felder-definieren-und-daten-exportieren.md)
|
|
@ -0,0 +1,29 @@
|
|||
# 3.1.1 Transformationshistorie anwenden
|
||||
|
||||
## Aufgabe: Speichern Sie die bisher durchgeführten Transformationsregeln und wenden Sie diese auf eine andere MARCXML-Datei an
|
||||
|
||||
OpenRefine verfügt über hilfreiche Undo/Redo-Funktionen, mit denen Sie auch alle bisher in einem Projekt durchgeführten Transformationsregeln speichern und auf ein anderes Projekt anwenden können.
|
||||
|
||||
Hinweise:
|
||||
|
||||
* Nutzen Sie die Funktion "Extract" im Bereich Undo/Redo und speichern Sie die Regeln in einer Textdatei zwischen (z.B. dem Texteditor Pluma, erreichbar im Startmenü unter Anwendungen/Zubehör/Pluma Text Editor).
|
||||
* Erstellen Sie ein neues Projekt mit einer zweiten MARCXML-Datei (vgl. [Kapitel 2.3.2](2-3-2-openrefine-starten-und-daten-laden.md)).
|
||||
* Wenden Sie die zwischengespeicherten Transformationsregeln über die Funktion "Apply" an.
|
||||
|
||||
## Lösung
|
||||
|
||||
Transformationsregeln extrahieren (altes Projekt):
|
||||
|
||||
* {%s%}Oben links Menü Undo / Redo den Button Extract... drücken{%ends%}
|
||||
* {%s%}Alles im rechten Textfeld in die Zwischenablage kopieren (z.B. mit STRG+A und STRG+C){%ends%}
|
||||
|
||||
Transformationsregeln anwenden (neues Projekt):
|
||||
|
||||
* Neues Projekt erstellen wie in [Kapitel 2.3.2](2-3-2-openrefine-starten-und-daten-laden.md) beschrieben
|
||||
* {%s%}Oben links Menü Undo / Redo den Button Apply... drücken{%ends%}
|
||||
* {%s%}Den Inhalt der Zwischenablage einfügen (z.B. mit STRG+V) und den Button Perform Operations drücken.{%ends%}
|
||||
|
||||
## Literatur
|
||||
|
||||
* [History-Funktionen in OpenRefine Dokumentation](https://github.com/OpenRefine/OpenRefine/wiki/History)
|
||||
* [JSON and my notepad or how to write script in google refine](http://kb.refinepro.com/2012/06/google-refine-json-and-my-notepad-or.html)
|
|
@ -0,0 +1,95 @@
|
|||
# 3.1.2 Alle Daten automatisiert verarbeiten
|
||||
|
||||
Die Systemarchitektur von OpenRefine macht es möglich, die Anwendung nicht nur über die grafische Oberfläche, sondern auch über eine API "fernzusteuern". Für die [HTTP-API von OpenRefine](https://github.com/OpenRefine/OpenRefine/wiki/OpenRefine-API) gibt es Clients in den Programmiersprachen Python, Ruby, node.js, PHP und für R. Am ausgereiftesten ist der [Python-Client von Paul Makepeace](https://github.com/PaulMakepeace/refine-client-py/).
|
||||
|
||||
Darauf aufbauend habe ich ein Shell-Script geschrieben, das gespeicherte Transformationsregeln auf eine Vielzahl von Dateien anwenden kann. Es lädt sich alle benötigten Komponenten wie beispielsweise den Python-Client automatisch aus dem Internet. Die zu verarbeitenden Daten und die Transformationsregeln (als JSON-Dateien) müssen vorab in Dateiordnern bereitgestellt werden. Das Script ist bei GitHub frei verfügbar: [felixlohmeier/openrefine-batch](https://github.com/felixlohmeier/openrefine-batch).
|
||||
|
||||
## Schritt 1: Alle Daten automatisiert verarbeiten
|
||||
|
||||
Wir wenden jetzt die Transformationsregeln aus Kapitel 2.3.4 und 2.3.5 auf alle(!) MARCXML-Dateien an. Dazu sind folgende Teilschritte notwendig.
|
||||
|
||||
### 1.1 Download des Shell-Scripts
|
||||
|
||||
* Falls OpenRefine im Terminal noch läuft, beenden Sie es durch die Tastenkombination ```STRG```+```C``` und schließen Sie das Terminal.
|
||||
* Öffnen Sie jetzt die Dateiverwaltung (Startmenü/Anwendungen/Systemwerkzeuge/Caja oder einen Ordner auf dem Desktop doppelklicken) und navigieren Sie in das Verzeichnis in dem der Ordner "download" mit den in [Kapitel 2.2.3](2-2-3-download-der-metadaten.md) heruntergeladenen MARCXML-Dateien liegt. Klicken Sie mit der rechten Maustaste in das Fenster und wählen Sie den Punkt "im Terminal öffnen" im Kontextmenü. Im Terminal befinden Sie sich jetzt im gewünschten Ordner, der entsprechende Pfad steht vor dem Dollarzeichen.
|
||||
* Geben Sie im Terminal folgenden Befehl ein:
|
||||
|
||||
```wget https://github.com/felixlohmeier/openrefine-batch/raw/master/openrefine-batch.sh && chmod +x openrefine-batch.sh```
|
||||
|
||||
### 1.2 Zielverzeichnis anlegen
|
||||
|
||||
* Ordner anlegen: ```mkdir output```
|
||||
|
||||
### 1.3 Transformationsregeln bereitstellen
|
||||
|
||||
* Ordner anlegen: ```mkdir config```
|
||||
* Transformationsregeln in Ordner config herunterladen: ```wget https://github.com/felixlohmeier/seminar-praxis-der-digitalen-bibliothek/raw/master/openrefine/2-3-5.json -O config/2-3-5.json``` (alternativ könnten Sie auch Ihre selbst zwischengespeicherten Transformationsregeln verwenden und diese als Datei mit der Dateiendung ".json" in dem Ordner config speichern)
|
||||
|
||||
### 1.4 Quelldateien bereitstellen
|
||||
|
||||
Wenn Sie allen Schritten im Skript gefolgt sind, dann sollten die MARCXML-Dateien im Unterordner download liegen. Sie können das mit dem Befehl ```ls``` prüfen:
|
||||
* Der Befehl ```ls``` sollte folgende Dateien und Verzeichnisse ausgeben: ```config download openrefine-batch.sh output```
|
||||
* Der Befehl ```ls download``` sollte nach einem Moment alle MARCXML-Dateien ausspucken.
|
||||
|
||||
Da es sehr zeitaufwendig ist, so viele kleine Einzeldateien zu verarbeiten, erstellen wir zunächst Zip-Archive mit jeweils 100 Dateien und stellen diese im Ordner input bereit:
|
||||
|
||||
```
|
||||
find download/ -type f -name '*.xml' > marcxml
|
||||
split -l 100 marcxml marcxml-
|
||||
rm -f marcxml
|
||||
for i in marcxml*; do cat $i | zip $i -@; done
|
||||
mkdir input
|
||||
mv marcxml*.zip input/
|
||||
rm -f marcxml*
|
||||
```
|
||||
|
||||
### 1.5 Automatische Verarbeitung starten
|
||||
|
||||
Das Script benötigt eine Reihe von Parametern, darunter das Quellverzeichnis, das Verzeichnis mit den Transformationsdateien und das Zielverzeichnis. Bei der Verarbeitung von XML Dateien ist zusätzlich das Format und der XML-Pfad (analog zum Klick in der GUI beim Erstellen der Projekte) anzugeben. Die abschließenden Parameter -m und -R sind technischer Natur und sorgen dafür, dass OpenRefine bis zu 3GB Arbeitsspeicher verwenden darf und unnötige Neustarts vermieden werden.
|
||||
|
||||
Geben Sie den folgenden Befehl im Terminal ein:
|
||||
|
||||
```
|
||||
./openrefine-batch.sh -a input -b config -c output -f xml -i recordPath=zs:searchRetrieveResponse -i recordPath=zs:records -i recordPath=zs:record -i recordPath=zs:recordData -i recordPath=record -m 3G -R
|
||||
```
|
||||
|
||||
Das Script lädt zunächst OpenRefine und den Python-Client in einen Unterordner und führt dann für jede Datei die Transformation aus und speichert die verarbeiteten Dateien im Zielverzeichnis im Format TSV.
|
||||
|
||||
|
||||
## Schritt 2: Spalten einheitlich sortieren (und nicht benötigte MARC-Felder löschen)
|
||||
|
||||
Prinzipiell könnten die TSV-Daten jetzt auch direkt in OpenRefine geladen werden, aber die Spalten wären dann nicht alphabetisch sortiert und es wären auch zahlreiche Spalten enthalten, die nur extrem selten (weniger als 10 Werte in 580.000 Datensätzen) belegt sind. Das macht die Arbeit mit den Daten unübersichtlich und langsam. Also sollten wir als erstes nicht benötigte Spalten löschen und die Spalten sortieren.
|
||||
|
||||
Wir nutzen dazu die Software [csvkit](https://csvkit.readthedocs.io).
|
||||
|
||||
Installation:
|
||||
* ```sudo apt-get install python-dev python-pip python-setuptools build-essential```
|
||||
* ```pip install csvkit```
|
||||
|
||||
Dateien zusammenführen:
|
||||
* ```csvstack -t *.tsv > stacked.csv```
|
||||
|
||||
Spalten sortieren:
|
||||
* ```columns=$(head -n1 stacked.csv | sort)```
|
||||
* ```csvcut -c $columns stacked.csv > sorted.csv```
|
||||
|
||||
Spalten mit sehr vielen leeren Werten ausgeben:
|
||||
* ```csvstat --nulls```
|
||||
* Sie können die [Dokumentation des MARC21 Formats](http://www.dnb.de/DE/Standardisierung/Formate/MARC21/marc21_node.html) konsultieren, um zu prüfen, ob Sie die Informationen aus den Spalten mit sehr vielen (>570.000) Null-Werten wirklich benötigen.
|
||||
|
||||
Nicht benötigte Spalten löschen (hier ein Vorschlag):
|
||||
* ```csvcut -C spalte1,spalte2 -x > hsh-ksf.csv```
|
||||
|
||||
|
||||
## Schritt 3: Daten in OpenRefine laden
|
||||
|
||||
Für das Laden der gesamten rund 580.000 Datensätze werden etwa XXX GB freier Arbeitsspeicher benötigt.
|
||||
|
||||
Starten Sie OpenRefine mit dem zusätzlichen Parameter ```-m 4G```, damit OpenRefine über mehr Speicher verfügen kann:
|
||||
```
|
||||
~/openrefine-2.7-rc.2/refine -m 4G
|
||||
```
|
||||
|
||||
Sollten Sie auf Ihrer virtuellen Maschine nicht über genügend freien Arbeitsspeicher verfügen, dann reduzieren Sie den Wert im Parameter ```-m``` und laden Sie nur einen Teil der Daten.
|
||||
|
||||
Erstellen Sie ein neues Projekt und laden Sie die im vorigen Schritt präparierte CSV-Datei ```hsh-ksf.csv``` hoch. Damit haben Sie nun endlich alle Daten in einem einzelnen Projekt und die Spalten alphabetisch sortiert. Das ist die Grundlage, auf der das nächste Kapitel aufsetzt.
|
|
@ -0,0 +1,103 @@
|
|||
# 3.1.3 Felder definieren und Daten exportieren
|
||||
|
||||
Jetzt wird es konkret: Welche Informationen wollen Sie in Ihrem Bibliothekskatalog anbieten? Welche Kurzinformationen sollen in der Trefferliste stehen? Welche Informationen sollen in der Vollanzeige dargestellt werden? Wir bilden jetzt aus den Rohdaten die gewünschten Felder.
|
||||
|
||||
## Aufgabe 1: Bilden Sie in OpenRefine das Feld "Titel" durch die Kombination verschiedener MARC-Felder
|
||||
|
||||
Hinweise:
|
||||
|
||||
* Die Expression zur Kombination von mehreren Feldern lautet (am Beispiel der Spalten 245 : a und 245 : b): ```cells["245 : a"].value + cells["245 : b"].value```. Die Werte der Zellen werden also schlicht mit einem ```+``` verbunden.
|
||||
* Leider schlägt diese Expression fehl, wenn die zu ergänzende Zelle leer ist. Daher muss eine if-Abfrage ergänzt werden, damit die Transformation nur für nicht-leere Zellen durchgeführt wird: ```cells["245 : a"].value + if(isNonBlank(cells["245 : b"].value,cells["245 : b"].value,"")```
|
||||
* Üblicherweise werden die Bestandteile des Titels durch unterschiedliche Trennzeichen getrennt. Die Trennzeichen sollten allerdings nur ergänzt werden, wenn danach auch ein Wert folgt. Daher muss die Ergänzung des Trennzeichens mit einer if-Abfrage von der folgenden Zelle abhängig gemacht werden. Beispiel für 245 : a und 245 : b mit Trennzeichen: ```cells["245 : a"].value + if(isNonBlank(cells["245 : b"].value),". ","") + if(isNonBlank(cells["245 : b"].value),cells["245 : b"].value,"")
|
||||
* Versuchen Sie auf diese Weise alle relevanten MARC-Felder zu einem Titel-Feld zu kombinieren.
|
||||
|
||||
## Lösung
|
||||
|
||||
* {%s%}Spalte "245 : a" / Edit column / Add column based on this column...{%ends%}
|
||||
* {%s%}New column name: Titel{%ends%}
|
||||
* {%s%}Expression: value + if(isNonBlank(cells["245 : b"].value),". ","") + if(isNonBlank(cells["245 : b"].value),cells["245 : b"].value,"") + if(isNonBlank(cells["245 : n"].value)," - ","") + if(isNonBlank(cells["245 : n"].value),cells["245 : n"].value,"") + if(isNonBlank(cells["245 : p"].value)," - ","") + if(isNonBlank(cells["245 : p"].value),cells["245 : p"].value,"") + if(isNonBlank(cells["246 : a"].value)," - ","") + if(isNonBlank(cells["246 : a"].value),cells["246 : a"].value,""){%ends%}
|
||||
|
||||
** Als JSON-Datei: [3-1-3-1.json](3-1-3-1.json)**
|
||||
|
||||
|
||||
## Aufgabe 2: Löschen Sie alle Datensätze, in denen das Feld "Titel" nicht belegt ist
|
||||
|
||||
Hinweise:
|
||||
|
||||
* In den rund 580.000 Datensätzen sind viele Fremd- und Normdaten enthalten, die wir zunächst nicht berücksichtigen wollen, um es nicht zu kompliziert zu machen.
|
||||
* Vereinfachend lässt sich annehmen, dass alle Datensätze, die nach Erledigung der Aufgabe 4 noch keinen Titel haben, gelöscht werden können.
|
||||
|
||||
## Lösung
|
||||
|
||||
* {%s%}Spalte "Titel" / Facet / Customized facets / Facet by blank / true{%ends%}
|
||||
* {%s%}All / Edit rows / Remove all matching rows{%ends%}
|
||||
* {%s%}Facette schließen{%ends%}
|
||||
|
||||
** Als JSON-Datei: [3-1-3-2.json](3-1-3-2.json)**
|
||||
|
||||
|
||||
## Aufgabe 3: Generieren Sie einheitliche ISBN-Nummern mit 13 Ziffern
|
||||
|
||||
Hinweise:
|
||||
|
||||
* Im MARC-Feld "020 : a" stehen teilweise 10-stellige, teilweise 13-stellige ISBN-Nummern.
|
||||
* Suchen Sie nach einem Weg, um diese auf 13 Ziffern zu vereinheitlichen.
|
||||
* Da es hier viele Mehrfachbelegungen gibt, müssen Sie die Zellen zunächst aufsplitten und nach erledigter Transformation wieder zusammenführen. Dieser Prozess ist speicherintensiv, also starten Sie OpenRefine vor und nach dieser Aufgabe besser neu.
|
||||
|
||||
## Lösung
|
||||
|
||||
* {%s%}Spalte "020 : a" / Edit cells / Split multi-valued cells... / ␟{%ends%}
|
||||
* {%s%}Spalte "020 : a" / Facet / Custom text facet... / Expression: value.length() / 13{%ends%}
|
||||
* {%s%}Spalte "020 : a" / Edit column / Add column based on this column... / New column name: ISBN /Expression: value{%ends%}
|
||||
* {%s%}Spalte "020 : a" / Facet / Custom text facet... / Expression: value.length() / 10{%ends%}
|
||||
* {%s%}Spalte "ISBN" / Edit cells / Transform... / Expression: with('978'+cells["020 : a"].value[0,9],v,v+((10-(sum(forRange(0,12,1,i,toNumber(v[i])*(1+(i%2*2)) )) %10)) %10).toString()[0] ){%ends%}
|
||||
* {%s%}Facette schließen{%ends%}
|
||||
* {%s%}Spalte "ISBN" / Edit cells / Join multi-valued cells... / ␟{%ends%}
|
||||
* {%s%}Spalte "020 : a" / Edit cells / Join multi-valued cells... / ␟{%ends%}
|
||||
|
||||
Anmerkung: Alle Sonderfälle, in denen noch Text hinter den ISBN-Nummern steht, sind mit diesen Transformationsregeln noch nicht behandelt. Dafür liegt aber zumindest für einen Teil der Datensätze eine einheitliche ISBN13-Kodierung vor.
|
||||
|
||||
** Als JSON-Datei: [3-1-3-3.json](3-1-3-3.json)**
|
||||
|
||||
|
||||
## Aufgabe 4: Ergänzen Sie ein Feld "id" für den Suchindex
|
||||
|
||||
Der Suchindex Solr erwartet ein Feld "id" mit eindeutiger Kennung in der ersten Spalte.
|
||||
|
||||
## Lösung
|
||||
|
||||
* {%s%}Spalte "001" / Edit column / Add column based on this column... / New column name: id{%ends%}
|
||||
|
||||
** Als JSON-Datei: [3-1-3-4.json](3-1-3-4.json)**
|
||||
|
||||
|
||||
## Aufgabe 5: Wenden Sie die vorbereitete Transformationsdatei zur Generierung weiterer Felder an
|
||||
|
||||
Das identifizieren wichtiger Felder wie Titel, Urheber, Ort, Erscheinungsjahr, Medientyp in den MARC-Daten ist mühsam und sprengt den Rahmen dieses Seminars. Ich habe daher eine Transformationsdatei erstellt, die Regeln zur Generierung weiterer Felder enthält. Diese unvollständige Empfehlung bildet auch die Grundlage für die folgenden Aufgaben. Diese haben nicht zum Ziel ein perfektes Mapping zu erstellen, sondern sollen ein paar Problemfelder illustrieren und sinnvolle Beispieldaten für den Suchindex (Kapitel 8) und die Kataloganzeige (Kapitel 9) bilden.
|
||||
|
||||
Hinweise:
|
||||
* Verwenden Sie die **JSON-Datei ** Als JSON-Datei: [3-1-3-5.json](3-1-3-5.json)****
|
||||
|
||||
## Lösung
|
||||
|
||||
* {%s%}Menü oben links "Undo / Redo" aufrufen und Button "Apply..." drücken {%ends%}
|
||||
* {%s%}Den Inhalt aus der Datei 3-1-3-5.json (siehe Link oben) in die Zwischenablage kopieren und in das Textfeld von "Apply" einfügen und Button "Perform Operations" drücken{%ends%}
|
||||
|
||||
|
||||
## Aufgabe 6: Daten exportieren
|
||||
|
||||
OpenRefine bietet viele Möglichkeiten die Daten in verschiedene Formate zu exportieren. Wir verwenden das Format TSV, das sehr einfach ist und sich später gut in den Suchindex spielen lässt.
|
||||
|
||||
* Alle Spalten löschen außer diejenigen, die in Kapitel 7.6 angelegt wurden: id, ISBN, ISSN, Sprache, LCC, DDC, Urheber, Medientyp, Ort, Verlag, Jahr, Datum, Beschreibung, Schlagwoerter, Beitragende, Reihe, Vorgaenger, Nachfolger, Link, Titel
|
||||
* Export / Tab-separated value
|
||||
|
||||
** Als JSON-Datei: ** Als JSON-Datei: [3-1-3-6.json](3-1-3-6.json)****
|
||||
|
||||
Zur Prüfung der exportierten Datei können Sie ```csvstat``` verwenden (vgl. [Kapitel 3.1.2, Schritt 2](3-1-2-alle-daten-automatisiert-verarbeiten.md)).
|
||||
|
||||
|
||||
## Literatur
|
||||
|
||||
* Owen Stephens: [A worked example of fixing problem MARC data: Part 4 – OpenRefine](http://www.meanboyfriend.com/overdue_ideas/2015/07/worked-example-fixing-marc-data-4/)
|
||||
* Library Carpentry OpenRefine: [Basic OpenRefine functions II](https://data-lessons.github.io/library-openrefine/04-basic-functions-II/)
|
||||
* [Exporter in der OpenRefine Dokumentation](https://github.com/OpenRefine/OpenRefine/wiki/Exporters)
|
|
@ -0,0 +1,9 @@
|
|||
# 3.2 Installation und Konfiguration des Suchmaschinenindex Solr
|
||||
|
||||
Ziel: Suchindex installieren, konfigurieren und mit den Daten befüllen. Suchmaschinentechnologie am Beispiel von Solr ein wenig kennenlernen.
|
||||
|
||||
Inhalte:
|
||||
|
||||
1. [Installation von Solr](3-2-1-installation-von-solr.md)
|
||||
2. [Konfiguration des Solr Schemas](3-2-2-konfiguration-des-solr-schemas.md)
|
||||
3. [TSV-Dateien in Solr laden](3-2-3-tsv-dateien-in-solr-laden.md)
|
|
@ -0,0 +1,32 @@
|
|||
# 3.2.1 Installation von Solr
|
||||
|
||||
## Installation
|
||||
|
||||
* Geben Sie im Terminal folgende Befehle ein:
|
||||
|
||||
```
|
||||
java -version
|
||||
wget http://archive.apache.org/dist/lucene/solr/6.5.0/solr-6.5.0.tgz
|
||||
tar zxf solr-6.5.0.tgz
|
||||
```
|
||||
|
||||
siehe auch: [Offizielle Installationsanleitung](https://cwiki.apache.org/confluence/display/solr/Installing+Solr)
|
||||
|
||||
## Solr mit Beispieldaten starten
|
||||
|
||||
* Geben Sie im Terminal folgende Befehle ein:
|
||||
|
||||
```
|
||||
cd solr-6.5.0
|
||||
bin/solr -e techproducts
|
||||
```
|
||||
|
||||
siehe auch: [Offizielle Anleitung "Running Solr"](https://cwiki.apache.org/confluence/display/solr/Running+Solr)
|
||||
|
||||
|
||||
## Administrationsoberfläche
|
||||
|
||||
Nach einer kurzen Wartezeit (max. 1 Minute) sollten folgende Oberflächen im Browser erreichbar sein:
|
||||
|
||||
* Administrationsoberfläche: http://localhost:8983/
|
||||
* Integrierte Suchoberfläche: http://localhost:8983/solr/gettingstarted/browse
|
|
@ -0,0 +1,45 @@
|
|||
# 3.2.2 Konfiguration des Solr Schemas
|
||||
|
||||
Ab Solr Version 6.0 ist das sogenannte "managed schema" (auch "schemaless mode" genannt) voreingestellt. Solr analysiert bei der Indexierung die Daten und versucht das Schema selbst zu generieren. Felder können aber weiterhin zusätzlich manuell definiert werden.
|
||||
|
||||
## Aufgabe: Schema über Admin-Oberfläche konfigurieren
|
||||
|
||||
Hinweise:
|
||||
|
||||
* Prinzipiell muss für alle Spalten in den TSV-Daten ein Feld im Schema definiert werden.
|
||||
* Im folgenden Abschnitt werden wir die Daten in Solr indexieren. Dabei erkennt Solr die allermeisten Felder automatisch. Es müssen nur die Felder ```ISBN``` und ```DDC``` manuell definiert werden, weil die automatische Erkennung hier Fehler produziert. Alle anderen Felder sollte Solr automatisch erkennen. Wenn Sie lieber auf Nummer sicher gehen wollen, dann legen Sie alle Felder manuell an.
|
||||
* Anlegen von Feldern: Admin-Oberfläche aufrufen. Im Menü "Core Selector" den Index "gettingstarted" auswählen. Dann im zweiten Menü "Schema" aufrufen.
|
||||
* Groß- und Kleinschreibung ist wichtig.
|
||||
|
||||
## Lösung
|
||||
|
||||
Minimal:
|
||||
|
||||
* Administrationsoberfläche: {%s%}http://localhost:8983/solr/#/gettingstarted/schema{%ends%}
|
||||
* Feld ISBN ergänzen: {%s%}Button "Add Field" drücken, ISBN in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld DDC ergänzen: {%s%}Button "Add Field" drücken, DDC in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
|
||||
Vollständig:
|
||||
|
||||
* Feld Sprache ergänzen: {%s%}Button "Add Field" drücken, Sprache in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld LCC ergänzen: {%s%}Button "Add Field" drücken, LCC in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld ISSN ergänzen: {%s%}Button "Add Field" drücken, ISSN in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Urheber ergänzen: {%s%}Button "Add Field" drücken, Urheber in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Titel ergänzen: {%s%}Button "Add Field" drücken, Titel in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Medientyp ergänzen: {%s%}Button "Add Field" drücken, Medientyp in das Feld name eingeben, als field type "string" auswählen und NICHT als multiValued markieren{%ends%}
|
||||
* Feld Ort ergänzen: {%s%}Button "Add Field" drücken, Ort in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Verlag ergänzen: {%s%}Button "Add Field" drücken, Verlag in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Jahr ergänzen: {%s%}Button "Add Field" drücken, Jahr in das Feld name eingeben, als field type "TrieLong" auswählen und NICHT als multiValued markieren{%ends%}
|
||||
* Feld Datum ergänzen: {%s%}Button "Add Field" drücken, Datum in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Beschreibung ergänzen: {%s%}Button "Add Field" drücken, Beschreibung in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Schlagwoerter ergänzen: {%s%}Button "Add Field" drücken, Schlagwoerter in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Beitragende ergänzen: {%s%}Button "Add Field" drücken, Beitragende in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Reihe ergänzen: {%s%}Button "Add Field" drücken, Reihe in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Vorgaenger ergänzen: {%s%}Button "Add Field" drücken, Vorgaenger in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Nachfolger ergänzen: {%s%}Button "Add Field" drücken, Nachfolger in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
* Feld Link ergänzen: {%s%}Button "Add Field" drücken, Link in das Feld name eingeben, als field type "string" auswählen und als multiValued markieren{%ends%}
|
||||
|
||||
## Literatur
|
||||
|
||||
* [Einführungsartikel zu "Managed Schema"](https://support.lucidworks.com/hc/en-us/articles/221618187-What-is-Managed-Schema-)
|
||||
* [Einführungsartikel zur Definition von Feldern im Schema](http://www.solrtutorial.com/schema-xml.html)
|
|
@ -0,0 +1,40 @@
|
|||
# 3.2.3 TSV-Dateien in Solr laden
|
||||
|
||||
## Konfiguration neu einlesen
|
||||
|
||||
* Menü "Core Admin" http://localhost:8983/solr/#/~cores/gettingstarted
|
||||
* Button "Reload" drücken
|
||||
|
||||
## Index leeren (im Terminal)
|
||||
|
||||
Der folgende Befehl löscht alle Daten im Index ```gettingstarted```
|
||||
|
||||
```
|
||||
curl "http://localhost:8983/solr/gettingstarted/update?commit=true&stream.body=%3Cdelete%3E%3Cquery%3E*%3A*%3C/query%3E%3C/delete%3E"
|
||||
```
|
||||
|
||||
## Daten laden (im Terminal)
|
||||
|
||||
Der folgende Befehl indexiert die Daten aus der Datei ```hsh-ksf.tsv```. Der Befehl ist so lang, weil Solr mitgeteilt werden muss, welche Felder mehrfachbelegt sind und mit welchem Zeichen diese getrennt sind. Die Laufzeit beträgt etwa 5 Minuten. Währenddessen kommt keine Statusmeldung, also haben Sie ein wenig Geduld.
|
||||
|
||||
```
|
||||
curl "http://localhost:8983/solr/gettingstarted/update/csv?commit=true&separator=%09&f.ISBN.split=true&f.ISBN.separator=%E2%90%9F&f.ISSN.split=true&f.ISSN.separator=%E2%90%9F&f.Sprache.split=true&f.Sprache.separator=%E2%90%9F&f.LCC.split=true&f.LCC.separator=%E2%90%9F&f.DDC.split=true&f.DDC.separator=%E2%90%9F&f.Urheber.split=true&f.Urheber.separator=%E2%90%9F&f.Ort.split=true&f.Ort.separator=%E2%90%9F&f.Verlag.split=true&f.Verlag.separator=%E2%90%9F&f.Datum.split=true&f.Datum.separator=%E2%90%9F&f.Beschreibung.split=true&f.Beschreibung.separator=%E2%90%9F&f.Schlagwoerter.split=true&f.Schlagwoerter.separator=%E2%90%9F&f.Beitragende.split=true&f.Beitragende.separator=%E2%90%9F&f.Reihe.split=true&f.Reihe.separator=%E2%90%9F&f.Vorgaenger.split=true&f.Vorgaenger.separator=%E2%90%9F&f.Nachfolger.split=true&f.Nachfolger.separator=%E2%90%9F&f.Link.split=true&f.Link.separator=%E2%90%9F&f.Titel.split=true&f.Titel.separator=%E2%90%9F" --data-binary @hsh-ksf.tsv -H 'Content-type:text/plain; charset=utf-8'
|
||||
```
|
||||
|
||||
## Prüfen Sie das Ergebnis
|
||||
|
||||
Rufen Sie die Browsing-Oberfläche auf (http://localhost:8983/solr/gettingstarted/browse). Es sollten über 200.000 Dokumente gefunden werden. Machen Sie ein paar Beispielsuchen, um sicherzugehen, dass die Daten richtig indexiert wurden.
|
||||
|
||||
## Solr beenden und starten
|
||||
|
||||
Solr wurde als Prozess gestartet, der bis zum nächsten Neustart des Rechners weiterlaufen sollte. Sie können Solr jederzeit manuell beenden und starten. Vor der Ausführung der Befehle müssen Sie in das Verzeichnis von Solr wechseln.
|
||||
|
||||
* In Verzeichnis wechseln: ```cd ~/solr-6.5.0```
|
||||
* Solr beenden:```bin/solr stop```
|
||||
* Solr starten:```bin/solr start```
|
||||
|
||||
Etwa 15-30 Sekunden nach dem Startbefehl sollte die Administrations- und die Browsingoberfläche unter den gewohnten Adressen erreichbar sein.
|
||||
|
||||
## Literatur
|
||||
|
||||
* [Offizielle Anleitung zum Einspielen von CSV-Daten](https://wiki.apache.org/solr/UpdateCSV#Updating_a_Solr_Index_with_CSV)
|
|
@ -31,10 +31,12 @@ Kapitel 2: Metadaten laden und mit OpenRefine transformieren
|
|||
* [2.3 Metadaten transformieren mit OpenRefine](2-3-0-metadaten-transformieren-mit-openrefine.md)
|
||||
|
||||
Kapitel 3: Transformierte Metadaten mit Solr indexieren
|
||||
* ...
|
||||
* [3.1 Daten mit OpenRefine für Indexierung vorbereiten](3-1-0-daten-mit-openrefine-fuer-indexierung-vorbereiten.md)
|
||||
* [3.2 Suchmaschinenindex Solr](3-2-0-suchmaschinenindex-solr.md)
|
||||
|
||||
Kapitel 4: Prototyp eines Katalogs mit TYPO3-find bauen
|
||||
* ...
|
||||
* [4.1 Katalogoberfläche TYPO3-find](4-1-0-katalogoberflaeche-typo3-find.md)
|
||||
* [4.2 Zwischenfazit](4-2-0-zwischenfazit.md)
|
||||
|
||||
Kapitel 5: Erweiterungsmöglichkeiten
|
||||
* ...
|
||||
|
|
15
SUMMARY.md
15
SUMMARY.md
|
@ -30,7 +30,22 @@
|
|||
* [2.3.4 Records bilden](2-3-4-records-bilden.md)
|
||||
* [2.3.5 Für jedes MARC-Feld eine Spalte](2-3-5-fuer-jedes-marc-feld-eine-spalte.md)
|
||||
* [Kapitel 3](3-0-transformierte-metadaten-mit-solr-indexieren.md)
|
||||
* [3.1 Daten mit OpenRefine für Indexierung vorbereiten](3-1-0-daten-mit-openrefine-fuer-indexierung-vorbereiten.md)
|
||||
* [3.1.1 Transformationshistorie anwenden](3-1-1-transformationshistorie-anwenden.md)
|
||||
* [3.1.2 Alle Daten automatisiert verarbeiten](3-1-2-alle-daten-automatisiert-verarbeiten.md)
|
||||
* [3.1.3 Felder definieren und Daten exportieren](3-1-3-felder-definieren-und-daten-exportieren.md)
|
||||
* [3.2 Suchmaschinenindex Solr](3-2-0-suchmaschinenindex-solr.md)
|
||||
* [3.2.1 Installation von Solr](3-2-1-installation-von-solr.md)
|
||||
* [3.2.2 Konfiguration des Solr Schemas](3-2-2-konfiguration-des-solr-schemas.md)
|
||||
* [3.2.3 TSV-Dateien in Solr laden](3-2-3-tsv-dateien-in-solr-laden.md)
|
||||
* [Kapitel 4](4-0-prototyp-eines-katalogs-mit-typo3-find-bauen.md)
|
||||
* [4.1 Katalogoberfläche TYPO3-find](4-1-0-katalogoberflaeche-typo3-find.md)
|
||||
* [4.1.1 Installation von TYPO3](4-1-1-installation-von-typo3.md)
|
||||
* [4.1.2 Erweiterung TYPO3-find installieren](4-1-2-erweiterung-typo3-find-installieren.md)
|
||||
* [4.1.3 TYPO3-find anpassen](4-1-3-typo3-find-anpassen.md)
|
||||
* [4.2 Zwischenfazit](4-2-0-zwischenfazit.md)
|
||||
* [4.2.1 Was haben wir im Seminar erreicht?](4-2-1-was-haben-wir-im-seminar-erreicht.md)
|
||||
* [4.2.2 Was fehlt noch für einen Produktivbetrieb?](4-2-2-was-fehlt-noch-fuer-einen-produktivbetrieb.md)
|
||||
* [Kapitel 5](5-0-erweiterungsmoeglichkeiten.md)
|
||||
* [Lerntagebücher](lerntagebuecher.md)
|
||||
* [Prüfungsleistungen](pruefungsleistungen.md)
|
||||
|
|
|
@ -10,11 +10,14 @@ Die öffentlichen Lerntagebücher haben zwei Ziele:
|
|||
|
||||
* Maren Berger: https://derversuchweb.wordpress.com
|
||||
* Roman Berthold: http://www.berthold.wp.hs-hannover.de/test/
|
||||
* Franziska Christiansen: https://digibibweb.wordpress.com/blog/
|
||||
* Stefan Dombek: http://bib14dombek.wp.hs-hannover.de/test/?page_id=13
|
||||
* Nastasia Forg: https://nastasiaforg.wordpress.com
|
||||
* Jan Jäger: http://jaegerja.wp.hs-hannover.de/wordpress/author/jaegerja
|
||||
* Ramona Lenz: https://ramonasdibi.wordpress.com
|
||||
* Carolin Marx: https://somebib.wordpress.com
|
||||
* Peggy Semper: https://peggysemper.wordpress.com
|
||||
* Melf Sorgenfrei: http://melfspace.wp.hs-hannover.de/test/
|
||||
* Romy Stelter: https://digibibmadness.wordpress.com
|
||||
* Iris Tannhäuser: https://digibibsite.wordpress.com
|
||||
* Tabea Weinberg: https://gazelleauftour.wordpress.com
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
[
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Titel at index 48 based on column 245 : a using expression grel:value + if(isNonBlank(cells[\"245 : b\"].value),\". \",\"\") + if(isNonBlank(cells[\"245 : b\"].value),cells[\"245 : b\"].value,\"\") + if(isNonBlank(cells[\"245 : n\"].value),\" - \",\"\") + if(isNonBlank(cells[\"245 : n\"].value),cells[\"245 : n\"].value,\"\") + if(isNonBlank(cells[\"245 : p\"].value),\" - \",\"\") + if(isNonBlank(cells[\"245 : p\"].value),cells[\"245 : p\"].value,\"\") + if(isNonBlank(cells[\"246 : a\"].value),\" - \",\"\") + if(isNonBlank(cells[\"246 : a\"].value),cells[\"246 : a\"].value,\"\")",
|
||||
"engineConfig": {
|
||||
"mode": "row-based",
|
||||
"facets": []
|
||||
},
|
||||
"newColumnName": "Titel",
|
||||
"columnInsertIndex": 48,
|
||||
"baseColumnName": "245 : a",
|
||||
"expression": "grel:value + if(isNonBlank(cells[\"245 : b\"].value),\". \",\"\") + if(isNonBlank(cells[\"245 : b\"].value),cells[\"245 : b\"].value,\"\") + if(isNonBlank(cells[\"245 : n\"].value),\" - \",\"\") + if(isNonBlank(cells[\"245 : n\"].value),cells[\"245 : n\"].value,\"\") + if(isNonBlank(cells[\"245 : p\"].value),\" - \",\"\") + if(isNonBlank(cells[\"245 : p\"].value),cells[\"245 : p\"].value,\"\") + if(isNonBlank(cells[\"246 : a\"].value),\" - \",\"\") + if(isNonBlank(cells[\"246 : a\"].value),cells[\"246 : a\"].value,\"\")",
|
||||
"onError": "set-to-blank"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,30 @@
|
|||
[
|
||||
{
|
||||
"op": "core/row-removal",
|
||||
"description": "Remove rows",
|
||||
"engineConfig": {
|
||||
"mode": "row-based",
|
||||
"facets": [
|
||||
{
|
||||
"omitError": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectBlank": false,
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": true,
|
||||
"l": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"selectError": false,
|
||||
"invert": false,
|
||||
"name": "Titel",
|
||||
"omitBlank": false,
|
||||
"type": "list",
|
||||
"columnName": "Titel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,90 @@
|
|||
[
|
||||
{
|
||||
"op": "core/multivalued-cell-split",
|
||||
"description": "Split multi-valued cells in column 020 : a",
|
||||
"columnName": "020 : a",
|
||||
"keyColumnName": "001",
|
||||
"separator": "␟",
|
||||
"mode": "plain"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column ISBN at index 2 based on column 020 : a using expression grel:value",
|
||||
"engineConfig": {
|
||||
"mode": "row-based",
|
||||
"facets": [
|
||||
{
|
||||
"omitError": false,
|
||||
"expression": "grel:value.length()",
|
||||
"selectBlank": false,
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": 13,
|
||||
"l": "13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"selectError": false,
|
||||
"invert": false,
|
||||
"name": "020 : a",
|
||||
"omitBlank": false,
|
||||
"type": "list",
|
||||
"columnName": "020 : a"
|
||||
}
|
||||
]
|
||||
},
|
||||
"newColumnName": "ISBN",
|
||||
"columnInsertIndex": 2,
|
||||
"baseColumnName": "020 : a",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/text-transform",
|
||||
"description": "Text transform on cells in column ISBN using expression grel:with('978'+cells[\"020 : a\"].value[0,9],v,v+((10-(sum(forRange(0,12,1,i,toNumber(v[i])*(1+(i%2*2)) )) %10)) %10).toString()[0] )",
|
||||
"engineConfig": {
|
||||
"mode": "row-based",
|
||||
"facets": [
|
||||
{
|
||||
"omitError": false,
|
||||
"expression": "grel:value.length()",
|
||||
"selectBlank": false,
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": 10,
|
||||
"l": "10"
|
||||
}
|
||||
}
|
||||
],
|
||||
"selectError": false,
|
||||
"invert": false,
|
||||
"name": "020 : a",
|
||||
"omitBlank": false,
|
||||
"type": "list",
|
||||
"columnName": "020 : a"
|
||||
}
|
||||
]
|
||||
},
|
||||
"columnName": "ISBN",
|
||||
"expression": "grel:with('978'+cells[\"020 : a\"].value[0,9],v,v+((10-(sum(forRange(0,12,1,i,toNumber(v[i])*(1+(i%2*2)) )) %10)) %10).toString()[0] )",
|
||||
"onError": "keep-original",
|
||||
"repeat": false,
|
||||
"repeatCount": 10
|
||||
},
|
||||
{
|
||||
"op": "core/multivalued-cell-join",
|
||||
"description": "Join multi-valued cells in column ISBN",
|
||||
"columnName": "ISBN",
|
||||
"keyColumnName": "001",
|
||||
"separator": "␟"
|
||||
},
|
||||
{
|
||||
"op": "core/multivalued-cell-join",
|
||||
"description": "Join multi-valued cells in column 020 : a",
|
||||
"columnName": "020 : a",
|
||||
"keyColumnName": "001",
|
||||
"separator": "␟"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,15 @@
|
|||
[
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column id at index 1 based on column 001 using expression grel:value",
|
||||
"engineConfig": {
|
||||
"mode": "row-based",
|
||||
"facets": []
|
||||
},
|
||||
"newColumnName": "id",
|
||||
"columnInsertIndex": 1,
|
||||
"baseColumnName": "001",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,517 @@
|
|||
[
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column ISSN at index 5 based on column 022 : a using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "ISSN",
|
||||
"columnInsertIndex": 5,
|
||||
"baseColumnName": "022 : a",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Sprache at index 7 based on column 041 : a using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Sprache",
|
||||
"columnInsertIndex": 7,
|
||||
"baseColumnName": "041 : a",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column LCC at index 9 based on column 050 : a using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "LCC",
|
||||
"columnInsertIndex": 9,
|
||||
"baseColumnName": "050 : a",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column DDC at index 11 based on column 082 : a using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "DDC",
|
||||
"columnInsertIndex": 11,
|
||||
"baseColumnName": "082 : a",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Urheber at index 13 based on column 100 : a using expression grel:if(isNonBlank(value),value,\"\") + if(isNonBlank(cells[\"100 : d\"].value),\" (\",\"\") + if(isNonBlank(cells[\"100 : d\"].value),cells[\"245 : d\"].value,\"\") + if(isNonBlank(cells[\"100 : d\"].value),\")\",\"\") + if(isBlank(value),cells[\"245 : c\"].value,\"\")",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Urheber",
|
||||
"columnInsertIndex": 13,
|
||||
"baseColumnName": "100 : a",
|
||||
"expression": "grel:if(isNonBlank(value),value,\"\") + if(isNonBlank(cells[\"100 : d\"].value),\" (\",\"\") + if(isNonBlank(cells[\"100 : d\"].value),cells[\"245 : d\"].value,\"\") + if(isNonBlank(cells[\"100 : d\"].value),\")\",\"\") + if(isBlank(value),cells[\"245 : c\"].value,\"\")",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/text-transform",
|
||||
"description": "Text transform on cells in column Urheber using expression grel:cells[\"110 : a\"].value + if(isNonBlank(cells[\"110 : b\"].value),\". \",\"\") + if(isNonBlank(cells[\"110 : b\"].value),cells[\"110 : b\"].value,\"\")",
|
||||
"engineConfig": {
|
||||
"facets": [
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "Urheber",
|
||||
"omitBlank": false,
|
||||
"columnName": "Urheber",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": true,
|
||||
"l": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "110 : a",
|
||||
"omitBlank": false,
|
||||
"columnName": "110 : a",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": false,
|
||||
"l": "false"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"columnName": "Urheber",
|
||||
"expression": "grel:cells[\"110 : a\"].value + if(isNonBlank(cells[\"110 : b\"].value),\". \",\"\") + if(isNonBlank(cells[\"110 : b\"].value),cells[\"110 : b\"].value,\"\")",
|
||||
"onError": "keep-original",
|
||||
"repeat": false,
|
||||
"repeatCount": 10
|
||||
},
|
||||
{
|
||||
"op": "core/text-transform",
|
||||
"description": "Text transform on cells in column Urheber using expression grel:cells[\"100 : a\"].value",
|
||||
"engineConfig": {
|
||||
"facets": [
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "Urheber",
|
||||
"omitBlank": false,
|
||||
"columnName": "Urheber",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": true,
|
||||
"l": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "100 : a",
|
||||
"omitBlank": false,
|
||||
"columnName": "100 : a",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": false,
|
||||
"l": "false"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"columnName": "Urheber",
|
||||
"expression": "grel:cells[\"100 : a\"].value",
|
||||
"onError": "keep-original",
|
||||
"repeat": false,
|
||||
"repeatCount": 10
|
||||
},
|
||||
{
|
||||
"op": "core/text-transform",
|
||||
"description": "Text transform on cells in column Urheber using expression grel:cells[\"700 : a\"].value",
|
||||
"engineConfig": {
|
||||
"facets": [
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "Urheber",
|
||||
"omitBlank": false,
|
||||
"columnName": "Urheber",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": true,
|
||||
"l": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "700 : a",
|
||||
"omitBlank": false,
|
||||
"columnName": "700 : a",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": false,
|
||||
"l": "false"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"columnName": "Urheber",
|
||||
"expression": "grel:cells[\"700 : a\"].value",
|
||||
"onError": "keep-original",
|
||||
"repeat": false,
|
||||
"repeatCount": 10
|
||||
},
|
||||
{
|
||||
"op": "core/text-transform",
|
||||
"description": "Text transform on cells in column Urheber using expression grel:cells[\"710 : a\"].value + if(isNonBlank(cells[\"710 : b\"].value),\". \",\"\") + if(isNonBlank(cells[\"710 : b\"].value),cells[\"710 : b\"].value,\"\")",
|
||||
"engineConfig": {
|
||||
"facets": [
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "Urheber",
|
||||
"omitBlank": false,
|
||||
"columnName": "Urheber",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": true,
|
||||
"l": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "710 : a",
|
||||
"omitBlank": false,
|
||||
"columnName": "710 : a",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": false,
|
||||
"l": "false"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"columnName": "Urheber",
|
||||
"expression": "grel:cells[\"710 : a\"].value + if(isNonBlank(cells[\"710 : b\"].value),\". \",\"\") + if(isNonBlank(cells[\"710 : b\"].value),cells[\"710 : b\"].value,\"\")",
|
||||
"onError": "keep-original",
|
||||
"repeat": false,
|
||||
"repeatCount": 10
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Medientyp at index 22 based on column 245 : h using expression grel:replace(value,\" =\",\"\")",
|
||||
"engineConfig": {
|
||||
"facets": [
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "245 : h",
|
||||
"omitBlank": false,
|
||||
"columnName": "245 : h",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": false,
|
||||
"l": "false"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Medientyp",
|
||||
"columnInsertIndex": 22,
|
||||
"baseColumnName": "245 : h",
|
||||
"expression": "grel:replace(value,\" =\",\"\")",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/text-transform",
|
||||
"description": "Text transform on cells in column Medientyp using expression grel:\"Elektronische Ressource\"",
|
||||
"engineConfig": {
|
||||
"facets": [
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "value",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "Medientyp",
|
||||
"omitBlank": false,
|
||||
"columnName": "Medientyp",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": "Elektronische Ressource␟Elektronische Ressource",
|
||||
"l": "Elektronische Ressource␟Elektronische Ressource"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"columnName": "Medientyp",
|
||||
"expression": "grel:\"Elektronische Ressource\"",
|
||||
"onError": "keep-original",
|
||||
"repeat": false,
|
||||
"repeatCount": 10
|
||||
},
|
||||
{
|
||||
"op": "core/text-transform",
|
||||
"description": "Text transform on cells in column Medientyp using expression grel:\"undefiniert\"",
|
||||
"engineConfig": {
|
||||
"facets": [
|
||||
{
|
||||
"invert": false,
|
||||
"expression": "isBlank(value)",
|
||||
"selectError": false,
|
||||
"omitError": false,
|
||||
"selectBlank": false,
|
||||
"name": "Medientyp",
|
||||
"omitBlank": false,
|
||||
"columnName": "Medientyp",
|
||||
"type": "list",
|
||||
"selection": [
|
||||
{
|
||||
"v": {
|
||||
"v": true,
|
||||
"l": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"columnName": "Medientyp",
|
||||
"expression": "grel:\"undefiniert\"",
|
||||
"onError": "keep-original",
|
||||
"repeat": false,
|
||||
"repeatCount": 10
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Ort at index 27 based on column 260 : a using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Ort",
|
||||
"columnInsertIndex": 27,
|
||||
"baseColumnName": "260 : a",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Verlag at index 29 based on column 260 : b using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Verlag",
|
||||
"columnInsertIndex": 29,
|
||||
"baseColumnName": "260 : b",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Datum at index 31 based on column 260 : c using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Datum",
|
||||
"columnInsertIndex": 31,
|
||||
"baseColumnName": "260 : c",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Jahr at index 31 based on column 260 : c using expression grel:value.toDate().datePart(\"year\")",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Jahr",
|
||||
"columnInsertIndex": 31,
|
||||
"baseColumnName": "260 : c",
|
||||
"expression": "grel:value.toDate().datePart(\"year\")",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Beschreibung at index 34 based on column 500 : a using expression grel:if(isNonBlank(value),value,\"\") + if(isNonBlank(cells[\"502 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"502 : a\"].value),cells[\"502 : a\"].value,\"\") + if(isNonBlank(cells[\"515 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"515 : a\"].value),cells[\"515 : a\"].value,\"\") + if(isNonBlank(cells[\"520 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"520 : a\"].value),cells[\"520 : a\"].value,\"\")",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Beschreibung",
|
||||
"columnInsertIndex": 34,
|
||||
"baseColumnName": "500 : a",
|
||||
"expression": "grel:if(isNonBlank(value),value,\"\") + if(isNonBlank(cells[\"502 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"502 : a\"].value),cells[\"502 : a\"].value,\"\") + if(isNonBlank(cells[\"515 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"515 : a\"].value),cells[\"515 : a\"].value,\"\") + if(isNonBlank(cells[\"520 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"520 : a\"].value),cells[\"520 : a\"].value,\"\")",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Schlagwoerter at index 39 based on column 650 : a using expression grel:if(isNonBlank(value),value,\"\") + if(isNonBlank(cells[\"650 : x\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"650 : x\"].value),cells[\"650 : x\"].value,\"\") + if(isNonBlank(cells[\"653 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"653 : a\"].value),cells[\"653 : a\"].value,\"\") + if(isNonBlank(cells[\"655 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"655 : a\"].value),cells[\"655 : a\"].value,\"\")",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Schlagwoerter",
|
||||
"columnInsertIndex": 39,
|
||||
"baseColumnName": "650 : a",
|
||||
"expression": "grel:if(isNonBlank(value),value,\"\") + if(isNonBlank(cells[\"650 : x\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"650 : x\"].value),cells[\"650 : x\"].value,\"\") + if(isNonBlank(cells[\"653 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"653 : a\"].value),cells[\"653 : a\"].value,\"\") + if(isNonBlank(cells[\"655 : a\"].value),\"␟\",\"\") + if(isNonBlank(cells[\"655 : a\"].value),cells[\"655 : a\"].value,\"\")",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Beitragende at index 45 based on column 700 : a using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Beitragende",
|
||||
"columnInsertIndex": 45,
|
||||
"baseColumnName": "700 : a",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Reihe at index 49 based on column 773 : t using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Reihe",
|
||||
"columnInsertIndex": 49,
|
||||
"baseColumnName": "773 : t",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Vorgaenger at index 51 based on column 780 : t using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Vorgaenger",
|
||||
"columnInsertIndex": 51,
|
||||
"baseColumnName": "780 : t",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Nachfolger at index 53 based on column 785 : t using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Nachfolger",
|
||||
"columnInsertIndex": 53,
|
||||
"baseColumnName": "785 : t",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
},
|
||||
{
|
||||
"op": "core/column-addition",
|
||||
"description": "Create column Link at index 55 based on column 856 : u using expression grel:value",
|
||||
"engineConfig": {
|
||||
"facets": [],
|
||||
"mode": "row-based"
|
||||
},
|
||||
"newColumnName": "Link",
|
||||
"columnInsertIndex": 55,
|
||||
"baseColumnName": "856 : u",
|
||||
"expression": "grel:value",
|
||||
"onError": "set-to-blank"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
[ { "op": "core/column-reorder", "description": "Reorder columns", "columnNames": [
|
||||
"id",
|
||||
"ISBN",
|
||||
"ISSN",
|
||||
"Sprache",
|
||||
"LCC",
|
||||
"DDC",
|
||||
"Urheber",
|
||||
"Medientyp",
|
||||
"Ort",
|
||||
"Verlag",
|
||||
"Jahr",
|
||||
"Datum",
|
||||
"Beschreibung",
|
||||
"Schlagwoerter",
|
||||
"Beitragende",
|
||||
"Reihe",
|
||||
"Vorgaenger",
|
||||
"Nachfolger",
|
||||
"Link",
|
||||
"Titel"
|
||||
] } ]
|
Loading…
Reference in New Issue