aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-24 07:37:17 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-24 07:44:51 +0200
commit9feaf3b9eebd79a37a252c2ffdf241f4190b2637 (patch)
tree3775b5196b2a50d2d6dc200b6211851265d248aa
parente13b58a5e3b602b475c46e6d595749d375148836 (diff)
Der Browser bekommt den Notausgang, den die Befehlszeile schon hatte
Die Browserfassung galt als die vollwertige zweite Fassung des Erzeugnisses. Zwei Angaben der Befehlszeile fehlten ihr aber: die Beschränkung auf einen einzelnen Artikel und die Ausgabe des maschinell gelesenen Textes. Die erste war im Vordruck nicht vorgesehen, obwohl Arbeiter und Rechenwerk den Wert längst durchreichten — das Formular sandte an ihrer Stelle hart einen leeren Wert. Die zweite war überhaupt nicht erreichbar. Das Fehlende wiegt ungleich. Die Artikelangabe erspart Mühe; die Textausgabe dagegen ist der einzige Weg, einem unerklärlich unangewandten Befehl auf den Grund zu gehen. Wer im Browser arbeitete, stand vor einem Ergebnis, das er nicht nachprüfen konnte, und war auf die Befehlszeile verwiesen — also gerade auf das, was die Browserfassung entbehrlich machen sollte. Die Textgewinnung wandert deshalb aus der Befehlszeilenklasse in den gemeinsamen Kern (Pipeline.extrahiereText); beide Fassungen nehmen fortan denselben Weg. Der Vordruck erhält das Feld „Anzuwendender Artikel“ und das Ankreuzfeld „Statt der Synopse nur den maschinell gelesenen Text der Änderungsdokumente ausgeben“; das Ergebnis kommt wie die Synopse als Verweis zum Öffnen und Sichern. Nebenbei verträgt die Umsetzung nach Wahrheitswerten nun ein nicht gesetztes Feld, das als „undefined“ herüberkommt. Das Handbuch zählt die Absätze des § 14 wieder fortlaufend — es führte (6) und (7) zweimal — und stellt die Angaben des Vordrucks den Schaltern des § 6 Absatz 2 gegenüber. Eine neue Prüfung hält die Ausgabe des Notausgangs gegen den Text, den der Parser bekommt: Führte sie einen anderen vor, so wiese sie in die Irre. Der Prüfbestand umfasst hiernach 335 Prüfungen, sämtlich grün; REUSE meldet 174/174. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Change-Id: I2a9aee042b6454f8d591bc415a5c6f1c1e7b40b6
-rw-r--r--FASSUNGEN.txt59
-rw-r--r--README.md31
-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
10 files changed, 221 insertions, 28 deletions
diff --git a/FASSUNGEN.txt b/FASSUNGEN.txt
index 73665fb..7d13d8d 100644
--- a/FASSUNGEN.txt
+++ b/FASSUNGEN.txt
@@ -21,6 +21,65 @@ Vorbemerkung zur Führung dieses Verzeichnisses
════════════════════════════════════════════════════════════════════════════════
+ Fassung vom 24. August 2026,
+ zuletzt geändert durch die am 24. August 2026 vorgenommenen Änderungen
+════════════════════════════════════════════════════════════════════════════════
+
+Die Browserfassung ist seit ihrer Einführung als die vollwertige zweite Fassung
+des Erzeugnisses ausgegeben worden; sie war es nicht. Zwei Angaben, die die
+Befehlszeile seit jeher kennt, fehlten ihr: die Beschränkung auf einen einzelnen
+Artikel und die Ausgabe des maschinell gelesenen Textes. Die erste war im
+Vordruck nicht vorgesehen, obgleich Arbeiter und Rechenwerk den Wert längst
+durchreichten — das Formular sandte an ihrer Stelle einen leeren Wert. Die zweite
+war überhaupt nicht erreichbar.
+
+Das Fehlende wiegt ungleich: Die Artikelangabe erspart Mühe, die Textausgabe
+dagegen ist der einzige Weg, einem unerklärlich unangewandten Befehl auf den
+Grund zu gehen. Wer im Browser arbeitet, stand vor einem Ergebnis, das er nicht
+nachprüfen konnte, und war auf die Befehlszeile verwiesen — also gerade auf das,
+was die Browserfassung entbehrlich machen sollte.
+
+
+Artikel 1
+Gemeinsame Textgewinnung
+
+(1) Die Gewinnung des Änderungstextes wird aus der Befehlszeilenklasse in den
+ gemeinsamen Kern verlegt („Pipeline.extrahiereText“). Befehlszeile und
+ Browser nehmen fortan denselben Weg; die Ausgabe des Schalters
+ „--extract-only“ bleibt unverändert.
+
+(2) Eine Prüfung hält die Ausgabe gegen das, was der Parser bekommt. Führte der
+ Notausgang einen anderen Text vor, so wiese er in die Irre.
+
+
+Artikel 2
+Ergänzung des Vordrucks
+
+(1) Der Vordruck erhält das Feld „Anzuwendender Artikel“; ohne Eintragung bleibt
+ es beim bisherigen Verhalten, dass alle Artikel angewandt werden, deren
+ Einleitung das Stammgesetz nennt.
+
+(2) Er erhält ferner das Ankreuzfeld „Statt der Synopse nur den maschinell
+ gelesenen Text der Änderungsdokumente ausgeben“. Der Text wird als Verweis
+ zum Öffnen und Sichern bereitgestellt; er lässt sich von Hand berichtigen und
+ als Klartextdatei wieder einreichen.
+
+(3) Ein nicht gesetztes Feld kommt aus dem Fenster als „undefined“ herüber; die
+ Umsetzung nach Wahrheitswerten trägt dem nunmehr Rechnung, statt daran zu
+ scheitern.
+
+
+Artikel 3
+Berichtigung des Handbuchs
+
+(1) § 14 führte die Absatznummern (6) und (7) zweimal. Die Absätze werden
+ fortlaufend gezählt; der Verweis in § 4 wird angepasst.
+
+(2) In § 14 wird ein Absatz eingefügt, der die Angaben des Vordrucks den
+ Schaltern des § 6 Absatz 2 gegenüberstellt.
+
+
+════════════════════════════════════════════════════════════════════════════════
Fassung vom 23. August 2026,
zuletzt geändert durch die am 23. August 2026 vorgenommenen Änderungen
════════════════════════════════════════════════════════════════════════════════
diff --git a/README.md b/README.md
index 3d69818..3341f47 100644
--- a/README.md
+++ b/README.md
@@ -157,7 +157,7 @@ Fassungen (`FASSUNGEN.txt`) geführt.
`mvnw` abweichend bestimmt wird.
(3) Vorausgesetzt wird ein Java-Entwicklungsbausatz der Fassung 21 oder neuer.
-Für die Herstellung der Browserfassung gilt abweichend § 14 Absatz 3.
+Für die Herstellung der Browserfassung gilt abweichend § 14 Absatz 4.
## § 5 Zulässige Eingaben
@@ -476,7 +476,18 @@ Pipeline (`eu.mulk.aendggner.Pipeline`, § 16 Absatz 1) über ein
Upload-Formular zugänglich macht: Stammgesetz- und Änderungsgesetz-Datei(en)
wählen, Synopse erhalten.
-(2) Über dem Formular steht ein zugeklappter Einführungsblock, der zwei
+(2) Der Vordruck stellt außer den beiden Dateifeldern dieselben Angaben zur
+Verfügung wie die Befehlszeile nach § 6 Absatz 2, nämlich das Feld
+„Anzuwendender Artikel“ (`--artikel`), das Ankreuzfeld „Unveränderte
+Vorschriften mit in die Synopse aufnehmen“ (`--vollstaendig`) und das
+Ankreuzfeld „Statt der Synopse nur den maschinell gelesenen Text der
+Änderungsdokumente ausgeben“ (`--extract-only`). Das letztgenannte ist kein
+Zierat: Ohne es hätte, wer im Browser arbeitet, keine Möglichkeit, einem
+unerklärlich unangewandten Befehl auf den Grund zu gehen. Der ausgegebene Text
+ist derselbe, den der Parser bekommt; er lässt sich sichern, von Hand
+berichtigen und als Klartextdatei wieder einreichen.
+
+(3) Über dem Formular steht ein zugeklappter Einführungsblock, der zwei
durchgerechnete Fälle mit Verweisen auf die amtlichen Fundstellen anbietet,
nämlich das UWG (`gesetze-im-internet.de/uwg_2004/xml.zip`) mit dem Dritten Gesetz
zu seiner Änderung (BGBl. 2026 I Nr. 43) und das AGG
@@ -484,7 +495,7 @@ zu seiner Änderung (BGBl. 2026 I Nr. 43) und das AGG
Mitausgeliefert wird nichts; die Seite ruft die Dateien auch nicht selbst ab, sie
verweist nur darauf.
-(3) Ein Server ist nicht erforderlich: Die vollständige Verarbeitung —
+(4) Ein Server ist nicht erforderlich: Die vollständige Verarbeitung —
PDF-Textgewinnung mit PDFBox eingeschlossen — läuft als WebAssembly-Modul im
Browser, übersetzt mit GraalVM Web Image aus demselben Java-Quelltext.
Ausgeliefert werden nur statische Dateien; die gewählten Dokumente verlassen den
@@ -496,7 +507,7 @@ nicht in der Community Edition:
JAVA_HOME=/pfad/zu/oracle-graalvm ./mvnw -Pwasm package
```
-(4) Ergebnis ist `target/web/` mit `index.html`, `app.js`, `worker.js`,
+(5) Ergebnis ist `target/web/` mit `index.html`, `app.js`, `worker.js`,
`style.css`, `favicon.svg`, `aendggner.js` und `aendggner.js.wasm` (rund 17 MB,
komprimiert etwa 5 MB). Das Verzeichnis ist so, wie es dasteht, auslieferbar:
`deploy/webpaket.sh` läuft am Ende desselben Befehls, wirft den mehrere hundert
@@ -504,7 +515,7 @@ Megabyte großen Textzwischenschritt `aendggner.js.wat` fort, schickt das Modul
durch `wasm-opt -Oz` und legt den Quelltext der gebauten Fassung als
`aendggner-quelltext.tar.gz` samt `quelltext-fassung.txt` bei.
-(5) Die Größe des Moduls beruht auf vier Vorkehrungen; wer eine davon zurücknimmt,
+(6) Die Größe des Moduls beruht auf vier Vorkehrungen; wer eine davon zurücknimmt,
handelt sich die Megabyte wieder ein:
1. `-Os` beim Übersetzen statt der auf Durchsatz gerichteten Voreinstellung;
@@ -522,7 +533,7 @@ handelt sich die Megabyte wieder ein:
nutzt Binaryen auch Vorschläge, die noch kein Browser annimmt, und das Modul
scheitert erst beim Instanziieren.
-(6) Im Quelltextarchiv fehlt der Beispielkorpus; `.gitattributes` nimmt
+(7) Im Quelltextarchiv fehlt der Beispielkorpus; `.gitattributes` nimmt
`src/test/resources/sampledata` von `git archive` aus. Es sind Gesetzes- und
Drucksachentexte fremder Urheberschaft, an denen allein die Tests messen —
Quelltext im Sinne der AGPLv3 sind sie nicht, gebaut wird ohne sie, und sie
@@ -532,18 +543,18 @@ die Anschrift nach § 3 Absatz 2. Wächst das Archiv wieder über 8 MiB, so bric
`webpaket.sh` ab; alsdann sind Massendaten ins Repository geraten, die dort nicht
hingehören.
-(7) Trägt der Arbeitsbaum uneingecheckte Änderungen, so bricht die Herstellung
-nach Absatz 3 ab, denn der beigelegte Quelltext wäre alsdann nicht der gebaute.
+(8) Trägt der Arbeitsbaum uneingecheckte Änderungen, so bricht die Herstellung
+nach Absatz 4 ab, denn der beigelegte Quelltext wäre alsdann nicht der gebaute.
Für einen Probelauf hilft `QUELLTEXT_UNGEPRUEFT=1`.
-(6) Zur örtlichen Ansicht genügt `file://` nicht; Browser laden Wasm-Module und
+(9) Zur örtlichen Ansicht genügt `file://` nicht; Browser laden Wasm-Module und
Worker nur über HTTP (<http://localhost:8000/>):
```shell
jwebserver -d target/web
```
-(7) Die Befehlszeilenfassung (§ 6) bleibt hiervon unberührt und ist weiterhin der
+(10) Die Befehlszeilenfassung (§ 6) bleibt hiervon unberührt und ist weiterhin der
Weg für Massenläufe.
## § 15 Ausrollen
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;
}