Layout: Formatieren, Festbreiten-Plugin, Lesen von Satzarten-Dateien
This commit is contained in:
@@ -179,8 +179,8 @@ Alle Beispiele liegen in [examples/](examples/), arbeiten mit den Daten in `exam
|
||||
|
||||
**Datei mit Satzarten** – Rechnungen aus verschachteltem JSON werden zu einem Kopfsatz mit Positionssätzen; Datei-
|
||||
und Schlusssatz zählen die Datensätze. Das Layout legt Reihenfolge, Typ und Länge der Felder je Satzart fest; die
|
||||
Datentypen `AN`, `N`, `DAT`, `TIM` und ihre Prüfung stehen in 2.9. Das Layout formatiert nicht, deshalb bringt das
|
||||
`map` Datum und Beträge selbst ins Zielformat (`delete('-')`, `format('%.2f', …)`).
|
||||
Datentypen `AN`, `N`, `DAT`, `TIM` und ihre Prüfung stehen in 2.9. Ohne `format: true` formatiert das Layout nicht,
|
||||
deshalb bringt das `map` hier Datum und Beträge selbst ins Zielformat (`delete('-')`, `format('%.2f', …)`).
|
||||
|
||||
<!-- beispiel: examples/rechnungen_satzarten.rb -->
|
||||
```ruby
|
||||
@@ -586,6 +586,7 @@ flowchart LR
|
||||
| [lib/datapipeline/storage/sqlite.rb](lib/datapipeline/storage/sqlite.rb) | Quelle/Ziel SQLite (`sqlite3`) |
|
||||
| [lib/datapipeline/storage/excel.rb](lib/datapipeline/storage/excel.rb) | Quelle/Ziel Excel xlsx (`roo`, `caxlsx`) |
|
||||
| [lib/datapipeline/storage/rest.rb](lib/datapipeline/storage/rest.rb) | Quelle/Ziel REST-API (`http`) |
|
||||
| [lib/datapipeline/storage/fixed.rb](lib/datapipeline/storage/fixed.rb) | Quelle/Ziel Festbreiten-Datei nach Layout |
|
||||
| [lib/datapipeline/notification/slack.rb](lib/datapipeline/notification/slack.rb) | Benachrichtigung, z. B. aus `on_finish` |
|
||||
| [lib/datapipeline/version.rb](lib/datapipeline/version.rb) | `DataPipeline::VERSION` |
|
||||
|
||||
@@ -740,6 +741,7 @@ write container(:csv, file: 'out/cudo.txt', encoding: 'Windows-1252', layout: LA
|
||||
| `:json` | `DataPipeline::JsonStorage` | – | `file`, `format: :array \| :lines` (Default aus Endung: `.ndjson`/`.jsonl` → Zeilen), `root: 'data.items'`, `pretty`; ein einzelnes Objekt wird als eine Zeile gelesen |
|
||||
| `:sqlite`, `:sqlite3` | `DataPipeline::SqliteStorage` | `sqlite3` | `database` (Datei oder `':memory:'`), `query`, `params` (Array für `?`, Hash für `:name`), `table`, `create`, `truncate`, `upsert` |
|
||||
| `:excel`, `:xlsx` | `DataPipeline::ExcelStorage` | `roo` (lesen), `caxlsx` (schreiben) | `file`, `sheet` (Name/Index), `headers`, `columns`, `skip` (Titelzeilen) |
|
||||
| `:fixed` | `DataPipeline::FixedStorage` | – | `file`, `layout` (Pflicht), `encoding`, `replace`, `line_end`; Festbreiten-Datei, siehe 2.9 |
|
||||
| `:rest` | `DataPipeline::RestStorage` | `http` | `url`, `auth`, `headers`, `params`, `root`, Paging `page_param` oder `next: ->(body) { … }`, `retries`, `timeout`, `batch_size`, `write_method`, `write_url` (auch als Lambda je Zeile), `ssl_verify` |
|
||||
|
||||
**DataPipeline::JsonStorage** – NDJSON wird zeilenweise gelesen und geschrieben. Ein JSON-Array wird beim Streaming als
|
||||
@@ -1025,17 +1027,62 @@ wird nur auf Pflicht geprüft, nie auf den Typ.
|
||||
| `DAT` | Datum | genau 8 Ziffern `YYYYMMDD`, gültiges Kalenderdatum | wird nicht geprüft | `NAME: not a date YYYYMMDD` |
|
||||
| `TIM` | Uhrzeit | genau 6 Ziffern `HHMMSS`, 00:00:00 bis 23:59:59 | wird nicht geprüft | `NAME: not a time HHMMSS` |
|
||||
|
||||
Ein unbekannter Typ wird wie `AN` behandelt. Das Layout **prüft nur, es formatiert nicht**: Werte werden beim
|
||||
Schreiben unverändert ausgegeben, ohne Auffüllen mit Nullen oder Leerzeichen. Zahlen, Datum und Uhrzeit müssen
|
||||
deshalb vorher in einem `map` als Text in das Zielformat gebracht werden:
|
||||
Ein unbekannter Typ wird wie `AN` behandelt.
|
||||
|
||||
**Formatieren** – Ohne Option prüft das Layout nur: Werte werden unverändert geschrieben, ein `Date` würde also als
|
||||
`2026-10-09` geprüft und abgelehnt. Mit `Layout.new(format: true)` bringt das Layout die Werte vor dem Prüfen und
|
||||
Schreiben selbst in das Textformat ihres Typs. Geprüft wird dann genau der Text, der in die Datei kommt.
|
||||
|
||||
| Typ | Eingabe | Ergebnis |
|
||||
| --- | --- | --- |
|
||||
| `N` | Integer, Float, Rational, BigDecimal | Nachkommastellen aus der Länge (`11.2` → 2), kaufmännisch gerundet: `12.345` → `12.35`, `-0.005` → `-0.01`, `7` → `7.00`; ein Float zählt mit seinem kürzesten Text, `12.345` also nicht als `12.3449999…` |
|
||||
| `N` | Zahl als Text | auf die Nachkommastellen aufgefüllt (`'3.5'` → `3.50`); zu viele Nachkommastellen bleiben stehen und werden gemeldet, Text wird nie gerundet |
|
||||
| `DAT` | `Date`, `Time`, `'2026-10-09'` | `20261009` |
|
||||
| `TIM` | `Time`, `'07:05:03'` | `070503` |
|
||||
| alle | `nil` | bleibt leer |
|
||||
|
||||
Anderes bleibt unverändert und wird wie gewohnt geprüft. Führende Nullen ergänzt erst die Festbreiten-Datei (unten);
|
||||
in CSV braucht es dafür weiterhin ein `map` (`format('%06d', …)`).
|
||||
|
||||
```ruby
|
||||
map do |row|
|
||||
row.merge('datum' => row['datum'].strftime('%Y%m%d'), # Date -> '20261009'; Date#to_s wäre '2026-10-09'
|
||||
'zeit' => row['zeit'].strftime('%H%M%S'),
|
||||
'betrag' => format('%.2f', row['betrag']), # BigDecimal#to_s wäre '0.1e2', Float evtl. '1.0e-05'
|
||||
'lfdnr' => format('%06d', row['lfdnr'])) # führende Nullen, falls das Zielsystem sie verlangt
|
||||
LAYOUT = DataPipeline::Layout.new(format: true) do
|
||||
record 'KOPF', [['SATYP', 'AN', 4, true], ['DATUM', 'DAT', 8, true], ['SUMME', 'N', '11.2', true]]
|
||||
end
|
||||
LAYOUT.values({ 'satyp' => 'KOPF', 'datum' => Date.new(2026, 10, 9), 'summe' => 1234.5 })
|
||||
# => ["KOPF", "20261009", "1234.50"]
|
||||
```
|
||||
|
||||
**Festbreiten-Dateien** – `container(:fixed, file:, layout:)` (`DataPipeline::FixedStorage`) schreibt und liest
|
||||
Dateien ohne Trennzeichen, in denen jedes Feld eine feste Anzahl Zeichen hat. Jedes `AN`- und `N`-Feld braucht dafür
|
||||
eine Länge, sonst bricht schon `container` mit `LayoutError` ab.
|
||||
|
||||
| Typ | Breite | Ausrichtung |
|
||||
| --- | --- | --- |
|
||||
| `AN` | Länge | links, rechts mit Leerzeichen aufgefüllt |
|
||||
| `N` | Vor- + Nachkommastellen + Punkt: `'11.2'` → 12, `5` → 5 | rechts, links mit Nullen aufgefüllt, Vorzeichen vorne: `-0000012.50` |
|
||||
| `DAT`, `TIM` | 8 bzw. 6 | genau so viele Ziffern |
|
||||
| leeres Feld | Breite des Felds | nur Leerzeichen |
|
||||
|
||||
Beim Schreiben werden die Werte immer formatiert (wie `format: true`). Ein Wert, der länger als sein Feld ist, wird
|
||||
nicht abgeschnitten, sondern bricht mit `LayoutError` ab; mit `validate LAYOUT` vorher fällt er schon als Zeilenfehler
|
||||
auf. Beim Lesen erkennt das Plugin die Satzart an ihrem Feld, die Schlüssel sind die Feldnamen klein geschrieben, die
|
||||
Werte ohne Auffüllung (Zahlen behalten ihre führenden Nullen), leere Felder sind `nil`. Breiten zählen Zeichen, nicht
|
||||
Bytes. Weitere Optionen: `encoding`, `replace` (wie CSV, 2.5) und `line_end: "\r\n"`.
|
||||
|
||||
```ruby
|
||||
write container(:fixed, file: 'out/cudo.txt', layout: LAYOUT, encoding: 'Windows-1252')
|
||||
# KOPF20261009000001234.50
|
||||
|
||||
read container(:fixed, file: 'in/rueckmeldung.txt', layout: LAYOUT)
|
||||
# { 'satyp' => 'KOPF', 'datum' => '20261009', 'summe' => '000001234.50' }
|
||||
```
|
||||
|
||||
**Lesen mit Layout** – Auch eine CSV-Datei mit Satzarten lässt sich mit `layout:` wieder einlesen (nur mit
|
||||
`headers: false`). Jede Zeile bekommt die Feldnamen ihrer Satzart als Schlüssel; eine unbekannte Satzart oder mehr
|
||||
Werte als Felder bricht mit `LayoutError` ab.
|
||||
|
||||
```ruby
|
||||
read container(:csv, file: 'in/cudo.ok', layout: LAYOUT, csv: { headers: false, col_sep: '|' })
|
||||
```
|
||||
|
||||
**Verhältnis zu `validate`-Regeln** – Layout-Typen beschreiben das Textformat einer Datei mit festen Feldern, die
|
||||
@@ -1052,6 +1099,7 @@ sich kombinieren, z. B. Rohdaten mit Regeln prüfen und die fertigen Sätze gege
|
||||
**Optionen** – `Layout.new(type_field: 'satyp')` nimmt die Satzart aus dieser Spalte statt aus dem ersten Wert der
|
||||
Zeile; eine Satzart, die nicht im Layout steht, ist ein Fehler (`record type "X" not in layout (…)`).
|
||||
`strict: false` lässt beim Schreiben Felder zu, die nicht im Layout stehen (sie werden weggelassen).
|
||||
`format: true` formatiert die Werte (siehe oben).
|
||||
|
||||
Beim Schreiben stehen die Werte in der Reihenfolge des Layouts, fehlende Felder bleiben leer, ein unbekanntes Feld
|
||||
bricht mit `DataPipeline::LayoutError` ab. Eine falsch benannte SQL-Spalte verschiebt also nicht mehr still alle
|
||||
@@ -1066,5 +1114,5 @@ Ziel: aus dem Exportwerkzeug ein generisches, testbares ETL-Framework machen. Re
|
||||
|
||||
### 3.1 Erweiterbarkeit
|
||||
|
||||
1. **Weitere Plugins:** Festbreiten-Format (feste Spaltenbreiten, nutzt die Längen aus dem Layout), SFTP-Ablage.
|
||||
1. **Weitere Plugins:** SFTP-Ablage.
|
||||
2. **Veröffentlichung:** eigenes Remote-Repository für das Gem, Lizenz festlegen, Versionen taggen.
|
||||
|
||||
Reference in New Issue
Block a user