Skip to content

Commit

Permalink
Add explode command (#694)
Browse files Browse the repository at this point in the history
  • Loading branch information
nwagner84 authored Aug 30, 2023
1 parent f153c97 commit dec0c87
Show file tree
Hide file tree
Showing 23 changed files with 566 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [completions](./referenz/kommandos/completions.md)
- [convert](./referenz/kommandos/convert.md)
- [count](./referenz/kommandos/count.md)
- [explode](./referenz/kommandos/explode.md)
- [filter](./referenz/kommandos/filter.md)
- [frequency](./referenz/kommandos/frequency.md)
- [hash](./referenz/kommandos/hash.md)
Expand Down
Binary file not shown.
180 changes: 180 additions & 0 deletions docs/book/src/referenz/kommandos/explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# `explode`

![stability-badge](https://img.shields.io/badge/stability-unstable-red?style=flat-square)

Mithilfe des `explode`-Kommandos lassen sich Datensätze in Lokal- bzw.
Exemplardatensätze aufteilen.

> **Hinweis:** Das `explode`-Kommando befindet sich in der aktiven
> Entwicklung. Funktionalitäten können unvollständig oder fehlerhaft
> sein. Änderungen am _command-line interface_ (CLI) sind nicht
> ausgeschlossen.
## Beschreibung

<!-- TODO: Seite über den Aufbau eines PICA+-Datensatzes erstellen -->

Die Verarbeitung und Analyse von Datensätzen auf Lokal- bzw.
Exemplarebene ist mitunter nur unzureichend möglich, da Filterausdrücke
die Grenzen von untergeordneten Ebenen nicht respektiert. Abhilfe kann
das `explode`-Kommando schaffen, das einen Datensatz in einzelne Lokal-
bzw. Exemplardatensätze aufteilen kann. Dabei werden alle Felder der
übergeordneten Ebenen mit in die Datensätze übernommen.

Das Aufteilen der Datensätze erfolgt durch die Angabe der Ebene
(_level_) an der der Datensatz geteilt werden soll. Es können folgende
Werte ausgewählt werden:

* `main` (Aufteilen auf Ebene der Titeldaten),
* `local` (Aufteilen auf Ebene der Lokaldaten),
* sowie `copy` (Aufteilen auf Ebene der Exemplardaten).

Sollen ein Datensatz in alle Lokaldatensätze aufgeteilt werden, muss die
Ebene `local` ausgewählt werden. Die neu erstellten Datensätze enthalten
alle Titeldaten (Felder der Ebene 0), den Identifikator des
Lokaldatensatzes (Feld `[email protected]`) sowie alle Exemplare, die diesem
Lokaldatensatz zugeordnet werden.

Soll darüber hinaus für jedes Exemplar ein eigenständiger Datensatz
erzeugt werden, muss die Ebene `copy` angegeben werden. Jeder erzeugte
Datensatz enthält die Titeldaten (Felder der Ebene 0), den Identifikator
des Lokaldatensatzes (Feld `[email protected]`) und nur die Felder, die zum
jeweiligen Exemplar gehören.

Schließlich kann ein Datensatz auch auf Ebene der Titeldaten (`main`)
aufgeteilt werden. Diese Aufwahl verändert nichts am Datensatz und gibt
den vollständigen Datensatz mit allen Feldern aus.

Als Beispiel soll folgender (reduzierter) Datensatz dienen:

```console
$ pica print COPY.dat.gz
003@ $0 123456789
002@ $0 Abvz
101@ $a 1
203@/01 $0 0123456789
203@/02 $0 1234567890
101@ $a 2
203@/01 $0 345678901


```

Dieser Datensatz lässt sich in zwei Datensätze auf Ebene der Lokaldaten
aufteilen:

```console
$ pica explode -s local COPY.dat.gz -o local.dat
$ pica print local.dat
003@ $0 123456789
002@ $0 Abvz
101@ $a 1
203@/01 $0 0123456789
203@/02 $0 1234567890

003@ $0 123456789
002@ $0 Abvz
101@ $a 2
203@/01 $0 345678901


```

Soll jedes Exemplar ein eigenständiger Datensatz werden, wird dies durch
Angabe von `copy` erzielt:

```console
$ pica explode -s copy COPY.dat.gz -o copy.dat
$ pica print copy.dat
003@ $0 123456789
002@ $0 Abvz
101@ $a 1
203@/01 $0 0123456789

003@ $0 123456789
002@ $0 Abvz
101@ $a 1
203@/02 $0 1234567890

003@ $0 123456789
002@ $0 Abvz
101@ $a 2
203@/01 $0 345678901


```


## Optionen

* `-s`, `--skip-invalid` — Überspringt jene Zeilen aus der Eingabe, die
nicht dekodiert werden konnten.
* `-i`, `--ignore-case` — Groß- und Kleinschreibung wird bei Vergleichen
ignoriert.
* `--strsim-threshold <value>` — Festlegen des Schwellenwerts beim
Ähnlichkeitsvergleich von Zeichenketten mittels `=*`.
* `--where` `<filter>` — Angabe eines Filters, der auf die erzeugten
Datensätze angewandt wird.
* `--and` `<expr>` — Hinzufügen eines zusätzlichen Filters mittels der
booleschen `&&`-Verknüpfung. Der ursprüngliche Filterausdruck
`<filter>` wird zum Ausdruck `<filter> && <expr>`.
* `--or` `<expr>` — Hinzufügen eines zusätzlichen Filters mittels der
booleschen `||`-Verknüpfung. Der ursprüngliche Filterausdruck
`<filter>` wird zum Ausdruck `<filter> || <expr>`.
* `--not` `<expr>` — Hinzufügen eines zusätzlichen Filters. Der
ursprüngliche Filterausdruck `<filter>` wird zum Ausdruck `<filter> &&
!(<expr>)`.
* `-g`, `--gzip` — Komprimieren der Ausgabe im [Gzip]-Format.
* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der
eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das
Aktivieren der Option erfordert das Schreiben der Datensätze in eine
Datei mittels `-o` bzw. `--output`.
* `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben
werden soll. Standardmäßig wird die Ausgabe in die Standardausgabe
`stdout` geschrieben. Endet der Dateiname mit dem Suffix `.gz`, wird
die Ausgabe automatisch im gzip-Format komprimiert.


## Konfiguration

<!-- TODO: Link zum allgemeinen Kapitel über die Konfigurationsdatei -->

Einige Kommandozeilen-Optionen lassen sich per Konfigurationsdatei
(`Pica.toml`) einstellen:

```toml
[explode]
skip-invalid = true
gzip = true
```

## Beispiele

### Eingrenzen der Datensätze

Ist nur eine Teilmenge der erzeugten Datensätze von Interesse, lässt
sich die Ergebnismenge durch Hinzufügen eines Filterausdrucks
eingrenzen.

Werden bspw. nur die Exemplare mit dem Identifikator `[email protected] == "1"`
benötigt, kann die Eingrenzung durch Angabe der `--where`-Option
eingegrenzt werden:

```console
$ pica explode -s copy --where '[email protected] == "1"' COPY.dat.gz -o copy.dat
$ pica print copy.dat
003@ $0 123456789
002@ $0 Abvz
101@ $a 1
203@/01 $0 0123456789

003@ $0 123456789
002@ $0 Abvz
101@ $a 1
203@/02 $0 1234567890


```


[Gzip]: https://de.wikipedia.org/wiki/Gzip
2 changes: 2 additions & 0 deletions docs/book/src/referenz/kommandos/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* [completions](./completions.md) — Erzeugung von Shell-Skripten zur
Autovervollständigung
* [count](./count.md) — Zählen von Datensätzen, Feldern und Unterfeldern
* [explode](./explode.md) — Teilt Datensätzen in Lokal- oder
Exemplardatensätze auf
* [filter](./filter.md) — Filtert Datensätze anhand eines Kriteriums
* [frequency](./frequency.md) — Ermitteln einer Häufigkeitsverteilung
über ein oder mehrere Unterfelder
Expand Down
7 changes: 6 additions & 1 deletion pica-record/src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::occurrence::parse_occurrence;
use crate::parser::{ParseResult, RS, SP};
use crate::subfield::parse_subfield;
use crate::tag::parse_tag;
use crate::{Occurrence, ParsePicaError, Subfield, Tag};
use crate::{Level, Occurrence, ParsePicaError, Subfield, Tag};

/// A PICA+ field.
#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -229,6 +229,11 @@ impl<'a, T: AsRef<[u8]> + From<&'a BStr> + Display> Field<T> {
}
write!(out, "\x1e")
}

#[inline]
pub fn level(&self) -> Level {
self.tag.level()
}
}

impl<'a, T: AsRef<[u8]>> IntoIterator for &'a Field<T> {
Expand Down
11 changes: 10 additions & 1 deletion pica-record/src/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use nom::sequence::tuple;
use nom::Finish;

use crate::parser::ParseResult;
use crate::ParsePicaError;
use crate::{Level, ParsePicaError};

/// A PICA+ tag.
#[derive(Eq, Debug, Clone)]
Expand Down Expand Up @@ -76,6 +76,15 @@ impl<'a, T: AsRef<[u8]> + From<&'a BStr> + Display> Tag<T> {
pub fn from_unchecked(value: impl Into<T>) -> Self {
Self(value.into())
}

pub fn level(&self) -> Level {
match self.as_ref().first().expect("valid tag") {
b'0' => Level::Main,
b'1' => Level::Local,
b'2' => Level::Copy,
_ => unreachable!(),
}
}
}

/// Parse a PICA+ tag.
Expand Down
Loading

0 comments on commit dec0c87

Please sign in to comment.