From 9feaf3b9eebd79a37a252c2ffdf241f4190b2637 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Mon, 24 Aug 2026 07:37:17 +0200 Subject: Der Browser bekommt den Notausgang, den die Befehlszeile schon hatte MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Change-Id: I2a9aee042b6454f8d591bc415a5c6f1c1e7b40b6 --- src/main/resources/eu/mulk/aendggner/web/app.js | 34 ++++++++++++++++------ .../resources/eu/mulk/aendggner/web/index.html | 17 +++++++++++ src/main/resources/eu/mulk/aendggner/web/style.css | 24 +++++++++++++++ src/main/resources/eu/mulk/aendggner/web/worker.js | 5 +++- 4 files changed, 70 insertions(+), 10 deletions(-) (limited to 'src/main/resources') 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) +
+ + +
+
+ +

Zur Nachschau, wenn ein Befehl unerklärlich unangewandt + bleibt: Der Text lässt sich sichern, von Hand berichtigen und als Klartextdatei + oben wieder einreichen.

+
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), -- cgit v1.2.1