aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/eu/mulk/aendggner/AendGgner.java8
-rw-r--r--src/main/java/eu/mulk/aendggner/Pipeline.java25
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/app.js34
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/index.html17
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/style.css24
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/worker.js5
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java28
-rw-r--r--src/wasm/java/eu/mulk/aendggner/wasm/BrowserMain.java18
8 files changed, 141 insertions, 18 deletions
diff --git a/src/main/java/eu/mulk/aendggner/AendGgner.java b/src/main/java/eu/mulk/aendggner/AendGgner.java
index a12515a..58b0d50 100644
--- a/src/main/java/eu/mulk/aendggner/AendGgner.java
+++ b/src/main/java/eu/mulk/aendggner/AendGgner.java
@@ -12,6 +12,7 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.concurrent.Callable;
@@ -129,12 +130,11 @@ public class AendGgner implements Callable<Integer> {
}
if (extractOnly) {
- var gesetz = Pipeline.ladeStammgesetz(baseFile);
- var extraktor = new PatchTextExtraktor(Pipeline.superskriptModus(gesetz));
+ var quellen = new ArrayList<Quelle>();
for (var file : patches) {
- var text = extraktor.extrahiere(file);
- System.out.println(raw ? text : TextBereiniger.bereinige(text));
+ quellen.add(Quelle.lies(file));
}
+ System.out.print(Pipeline.extrahiereText(Quelle.lies(baseFile), quellen, raw));
return 0;
}
diff --git a/src/main/java/eu/mulk/aendggner/Pipeline.java b/src/main/java/eu/mulk/aendggner/Pipeline.java
index 76b492f..7949e7f 100644
--- a/src/main/java/eu/mulk/aendggner/Pipeline.java
+++ b/src/main/java/eu/mulk/aendggner/Pipeline.java
@@ -275,6 +275,31 @@ public final class Pipeline {
return dokument.kopf().art().istEntwurfsfassung();
}
+ /**
+ * Der Text der Änderungsdokumente, so wie das Erzeugnis ihn liest — der Notausgang des § 6 Absatz
+ * 2 des Handbuchs.
+ *
+ * <p>Bleibt die PDF-Aufbereitung im Einzelfall fehlerhaft, so ist nicht das Ergebnis zu
+ * beargwöhnen, sondern der Text nachzusehen, von Hand zu berichtigen und als Klartextdatei wieder
+ * einzuspeisen. Dass dieser Weg beiden Fassungen offensteht, ist kein Zierat: Wer im Browser
+ * arbeitet, hat sonst keine Möglichkeit, einem unerklärlichen Rest auf den Grund zu gehen.
+ *
+ * <p>Das Stammgesetz wird auch hier gebraucht, denn seine Schreibweise bestimmt den
+ * Superskriptmodus der Textgewinnung ({@link #superskriptModus}).
+ *
+ * @param roh die Bereinigung übergehen; nur zur Fehlersuche an der Textgewinnung selbst.
+ */
+ public static String extrahiereText(Quelle baseFile, List<Quelle> patches, boolean roh)
+ throws Exception {
+ var extraktor = new PatchTextExtraktor(superskriptModus(ladeStammgesetz(baseFile)));
+ var text = new StringBuilder();
+ for (var patch : patches) {
+ var roher = extraktor.extrahiere(patch);
+ text.append(roh ? roher : TextBereiniger.bereinige(roher)).append('\n');
+ }
+ return text.toString();
+ }
+
/** Bequemlichkeit für Befehlszeile und Tests; im Browser gibt es keine {@link Path}e. */
static Gesetz ladeStammgesetz(Path baseFile) throws Exception {
return ladeStammgesetz(Quelle.lies(baseFile));
diff --git a/src/main/resources/eu/mulk/aendggner/web/app.js b/src/main/resources/eu/mulk/aendggner/web/app.js
index 3a2957c..97eca52 100644
--- a/src/main/resources/eu/mulk/aendggner/web/app.js
+++ b/src/main/resources/eu/mulk/aendggner/web/app.js
@@ -30,6 +30,15 @@ async function alsBase64(datei) {
return { name: datei.name, base64: btoa(roh) };
}
+/** Hängt der Meldung einen Verweis auf das Ergebnis an; ein Blob, kein Server. */
+function oeffne(inhalt, art, aufschrift) {
+ const verweis = document.createElement("a");
+ verweis.href = URL.createObjectURL(new Blob([inhalt], { type: art }));
+ verweis.target = "_blank";
+ verweis.textContent = aufschrift;
+ meldung.append(verweis);
+}
+
formular.addEventListener("submit", async (ereignis) => {
ereignis.preventDefault();
@@ -40,6 +49,9 @@ formular.addEventListener("submit", async (ereignis) => {
return;
}
+ const artikel = document.querySelector("#artikel").value.trim();
+ const nurText = document.querySelector("#nurtext").checked;
+
knopf.disabled = true;
zeige("Lade das Rechenwerk (einmalig einige Megabyte) und werte aus …", "arbeit");
@@ -60,7 +72,8 @@ formular.addEventListener("submit", async (ereignis) => {
stamm,
patches,
vollstaendig: document.querySelector("#vollstaendig").checked,
- artikel: null,
+ artikel: artikel === "" ? null : artikel,
+ nurText,
});
});
@@ -69,22 +82,25 @@ formular.addEventListener("submit", async (ereignis) => {
return;
}
- const blob = new Blob([ergebnis.html], { type: "text/html;charset=utf-8" });
- const adresse = URL.createObjectURL(blob);
-
// Kein window.open: Der Aufruf käme nach dem Warten auf den Worker und gälte dem Browser
// nicht mehr als Nutzerhandlung — er würde als Popup blockiert. Stattdessen ein Link, der
// sich öffnen und ebenso gut speichern lässt.
+ if (nurText) {
+ zeige(
+ `${ergebnis.text.length.toLocaleString("de-DE")} Zeichen gelesen; ` +
+ `keine Synopse erstellt. `,
+ "fertig",
+ );
+ oeffne(ergebnis.text, "text/plain;charset=utf-8", "Gelesenen Text öffnen");
+ return;
+ }
+
zeige(
`${ergebnis.angewandt} Befehle angewandt, ${ergebnis.manuell} manuell zu prüfen, ` +
`${ergebnis.normen} geänderte Normen. `,
"fertig",
);
- const verweis = document.createElement("a");
- verweis.href = adresse;
- verweis.target = "_blank";
- verweis.textContent = "Synopse öffnen";
- meldung.append(verweis);
+ oeffne(ergebnis.html, "text/html;charset=utf-8", "Synopse öffnen");
} catch (e) {
zeige("Verarbeitung fehlgeschlagen: " + (e && e.message ? e.message : e), "fehler");
} finally {
diff --git a/src/main/resources/eu/mulk/aendggner/web/index.html b/src/main/resources/eu/mulk/aendggner/web/index.html
index 680aa8c..edca09e 100644
--- a/src/main/resources/eu/mulk/aendggner/web/index.html
+++ b/src/main/resources/eu/mulk/aendggner/web/index.html
@@ -132,12 +132,29 @@
gewählten Reihenfolge nacheinander angewendet)</span></label>
<input type="file" id="aenderung" name="aenderung" multiple required>
</div>
+ <div class="feld">
+ <label for="artikel">Anzuwendender Artikel
+ <span class="erl">(nur auszufüllen, wenn abweichend: ohne Angabe werden alle
+ Artikel angewandt, deren Einleitung das oben bezeichnete Stammgesetz
+ nennt)</span></label>
+ <input type="text" id="artikel" name="artikel" inputmode="numeric"
+ autocomplete="off" placeholder="z. B. 3">
+ </div>
<div class="feld ankreuzfeld">
<label>
<input type="checkbox" id="vollstaendig" name="vollstaendig">
Unveränderte Vorschriften mit in die Synopse aufnehmen
</label>
</div>
+ <div class="feld ankreuzfeld">
+ <label>
+ <input type="checkbox" id="nurtext" name="nurtext">
+ Statt der Synopse nur den maschinell gelesenen Text der Änderungsdokumente ausgeben
+ </label>
+ <p class="erl nachsatz">Zur Nachschau, wenn ein Befehl unerklärlich unangewandt
+ bleibt: Der Text lässt sich sichern, von Hand berichtigen und als Klartextdatei
+ oben wieder einreichen.</p>
+ </div>
</div>
</div>
diff --git a/src/main/resources/eu/mulk/aendggner/web/style.css b/src/main/resources/eu/mulk/aendggner/web/style.css
index 36c2198..80479ff 100644
--- a/src/main/resources/eu/mulk/aendggner/web/style.css
+++ b/src/main/resources/eu/mulk/aendggner/web/style.css
@@ -215,6 +215,24 @@ input[type="file"] {
font-size: 0.75rem;
}
+/* Die Eintragung von Hand steht auf einer Linie, wie im gedruckten Vordruck; sie ist kurz,
+ weil dort nur eine Artikelnummer hingehört. */
+input[type="text"] {
+ width: 8rem;
+ margin-top: 0.35rem;
+ padding: 0.1rem 0.2rem;
+ border: none;
+ border-bottom: 1px solid var(--linie);
+ background: transparent;
+ color: inherit;
+ font: inherit;
+ font-size: 0.8125rem;
+}
+
+input[type="text"]::placeholder {
+ color: var(--muted);
+}
+
/* Der Dateiwähler ist der einzige Knopf im Feld und trägt deshalb die Form der übrigen
Eintragungsfelder: eckig, haarfein umrandet, weiß. */
input[type="file"]::file-selector-button {
@@ -232,6 +250,12 @@ input[type="file"]::file-selector-button:hover {
background: var(--grund);
}
+/* Der Nachsatz zu einem Ankreuzfeld steht unter dem Text, nicht neben ihm, und rückt bis
+ unter die Aufschrift ein. */
+.nachsatz {
+ margin: 0.15rem 0 0 1.6rem;
+}
+
/* Das Ankreuzfeld steht vor dem Text und wird angekreuzt, nicht angehakt. */
.ankreuzfeld label {
display: flex;
diff --git a/src/main/resources/eu/mulk/aendggner/web/worker.js b/src/main/resources/eu/mulk/aendggner/web/worker.js
index f20e873..827358e 100644
--- a/src/main/resources/eu/mulk/aendggner/web/worker.js
+++ b/src/main/resources/eu/mulk/aendggner/web/worker.js
@@ -19,7 +19,7 @@ const gestartet = GraalVM.run([], Object.assign(new GraalVM.Config(), {
}));
self.onmessage = async (nachricht) => {
- const { stamm, patches, vollstaendig, artikel } = nachricht.data;
+ const { stamm, patches, vollstaendig, artikel, nurText } = nachricht.data;
try {
await gestartet;
await bereit;
@@ -28,9 +28,12 @@ self.onmessage = async (nachricht) => {
patches,
artikel: artikel ?? null,
vollstaendig: Boolean(vollstaendig),
+ nurText: Boolean(nurText),
});
if (ergebnis.fehler) {
self.postMessage({ fehler: String(ergebnis.fehler) });
+ } else if (nurText) {
+ self.postMessage({ text: String(ergebnis.text) });
} else {
self.postMessage({
html: String(ergebnis.html),
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 2698fa5..fe98b84 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -99,6 +99,34 @@ class EndToEndTest {
* die Rechnung vollständig auf: <b>75 von 75</b>, kein Rest. Das ist zugleich der Beleg, dass die
* 27 nie ein Mangel des Werkzeugs waren.
*/
+ /**
+ * Der Notausgang (§ 6 Absatz 2 des Handbuchs) gibt aus, was das Erzeugnis gelesen hat, und geht
+ * dabei denselben Weg wie die Synopse — auf ihn verlässt sich, wer einem unerklärlichen Rest
+ * nachgeht, und seit Welle 21 tut das auch die Browserfassung.
+ */
+ @Test
+ void notausgangLiefertDenGelesenenText() throws Exception {
+ assumeTrue(Files.exists(STAMMGESETZ), "IfSG-Beispieldaten fehlen");
+ assumeTrue(Files.exists(AENDERUNGSGESETZ), "BGBl-Beispiel-PDF fehlt");
+
+ var stamm = Quelle.lies(STAMMGESETZ);
+ var patches = List.of(Quelle.lies(AENDERUNGSGESETZ));
+
+ var bereinigt = Pipeline.extrahiereText(stamm, patches, false);
+ var roh = Pipeline.extrahiereText(stamm, patches, true);
+
+ // Wortgleich mit dem, was der Parser bekommt — sonst führte der Notausgang in die Irre.
+ assertThat(bereinigt)
+ .isEqualTo(
+ TextBereiniger.bereinige(new PatchTextExtraktor().extrahiere(AENDERUNGSGESETZ)) + "\n");
+ assertThat(bereinigt).contains("Nach § 28 wird folgender § 28a eingefügt");
+ // Die Bereinigung nimmt fort, was der Satz beisteuert (Kolumnentitel, Trennstriche); der
+ // Rohtext ist deshalb länger und trägt den Kopf des Gesetzblatts noch.
+ assertThat(roh).hasSizeGreaterThan(bereinigt.length());
+ assertThat(roh).contains("Bundesgesetzblatt");
+ assertThat(bereinigt).doesNotContain("Bundesgesetzblatt Jahrgang 2020 Teil I Nr.");
+ }
+
@Test
void ifsgGegenZeitrichtigenStamm() throws Exception {
var xml = SAMPLEDATA.resolve("IfSG/BJNR104510000-2020.xml");
diff --git a/src/wasm/java/eu/mulk/aendggner/wasm/BrowserMain.java b/src/wasm/java/eu/mulk/aendggner/wasm/BrowserMain.java
index 7df8ecb..c318f86 100644
--- a/src/wasm/java/eu/mulk/aendggner/wasm/BrowserMain.java
+++ b/src/wasm/java/eu/mulk/aendggner/wasm/BrowserMain.java
@@ -22,9 +22,10 @@ import org.graalvm.webimage.api.JSValue;
* damit dieselbe {@link Pipeline} auf wie Befehlszeile und Tests.
*
* <p>Erwartet ein JS-Objekt <code>{stamm: {name, base64}, patches: [{name, base64}], artikel,
- * vollstaendig}</code> und liefert <code>{html, angewandt, manuell, normen}</code> oder <code>
- * {fehler}</code> zurück. Geworfen wird nichts: Eine Ausnahme im Wasm hinterlässt auf der JS-Seite
- * nur einen unlesbaren Stapel, also wird jeder Fehler als Text zurückgereicht.
+ * vollstaendig, nurText}</code> und liefert <code>{html, angewandt, manuell, normen}</code> — bei
+ * <code>nurText</code> stattdessen <code>{text}</code> — oder <code>{fehler}</code> zurück.
+ * Geworfen wird nichts: Eine Ausnahme im Wasm hinterlässt auf der JS-Seite nur einen unlesbaren
+ * Stapel, also wird jeder Fehler als Text zurückgereicht.
*
* <p>Der Dateiinhalt wandert als Base64-Text über die Grenze, nicht als {@code Uint8Array}: Die
* Umsetzung typisierter Felder nach {@code byte[]} ist in Web Image derzeit defekt („byteArrayHub
@@ -71,6 +72,14 @@ public final class BrowserMain {
var artikel = text(eingabe.get("artikel"));
var vollstaendig = Boolean.TRUE.equals(wahrheitswert(eingabe.get("vollstaendig")));
+ if (Boolean.TRUE.equals(wahrheitswert(eingabe.get("nurText")))) {
+ // Der Notausgang der Befehlszeile (--extract-only) steht auch hier offen: Wer einem Rest
+ // nachgehen will, muss sehen können, was das Erzeugnis gelesen hat.
+ antwort.set(
+ "text", JSString.of(Pipeline.extrahiereText(stamm, List.copyOf(patches), false)));
+ return antwort;
+ }
+
var ergebnis =
Pipeline.erzeugeSynopse(
stamm,
@@ -132,7 +141,8 @@ public final class BrowserMain {
return b;
}
if (wert instanceof JSValue v) {
- return v.asBoolean();
+ // Ein nicht gesetztes Feld kommt als „undefined“ herüber; asBoolean() bräche daran.
+ return "undefined".equals(v.typeof()) ? Boolean.FALSE : v.asBoolean();
}
return Boolean.FALSE;
}