diff options
Diffstat (limited to 'src/test')
3 files changed, 139 insertions, 34 deletions
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java index fe98b84..d9f36d7 100644 --- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java +++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java @@ -1034,7 +1034,7 @@ class EndToEndTest { .containsExactly(null, "1", "2", "3", "4", "4a", "5", "6", "7", "8", "9", "10"); // Norm für Norm gegen die amtliche Nachfassung vom 12. Juni 2026. Es bleiben genau zwei - // benannte Abweichungen — beide nicht in der Anwendung begründet: + // benannte Abweichungen — keine davon in der Anwendung begründet: var sollAsog = SAMPLEDATA.resolve("Berlin/ASOG-Bln-neu.txt"); assumeTrue(Files.exists(sollAsog), "ASOG-Nachfassung fehlt"); var soll = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(sollAsog); @@ -1049,27 +1049,23 @@ class EndToEndTest { abweichend.add(normSoll.enbez()); } } - // Drei benannte Abweichungen, keine davon in der Anwendung begründet: + // Zwei benannte Abweichungen, keine davon in der Anwendung begründet: // // § 67 — Das Portal setzt in der neuen Fassung amtliche Satznummern („(2) ¹Gegen einen …“); // das Gesetzblatt, aus dem der Wortlaut stammt, setzt keine. // Anlage Nummer 23 — der oben benannte Rest: dort steht der Punkt, wo die Nachfassung ein // Semikolon führt. - // Anlage Nummer 31 — das Zitat läuft im Gesetzblatt über einen Seitenwechsel, und der - // ganzseitenbreite Titelblock des Änderungsgesetzes steht im Inhaltsstrom mitten darin - // („… zur Sicherung des Be-“ / Titelblock / „triebs von Unterkünften …“). Das ist die - // bekannte Berliner Layout-Frage, die erst eine geometrische Lesereihenfolge löst; sie - // betrifft die Extraktion, nicht die Anwendung. - assertThat(abweichend).containsExactly("§ 67", "Anlage Nummer 23", "Anlage Nummer 31"); - assertThat( - anwendung - .neu() - .norm("Anlage Nummer 31") - .orElseThrow() - .gesamtText() - .replaceAll("\\s+", " ")) - .as("der Titelblock steht im Zitat — Beleg der offenen Layout-Frage") - .contains("Gesetz zur Änderung des Allgemeinen Sicherheits- und Ordnungsgesetzes"); + assertThat(abweichend).containsExactly("§ 67", "Anlage Nummer 23"); + // Die Nummer 31 war die dritte: Ihr Zitat läuft über einen Seitenwechsel, und der + // ganzseitenbreite Titelblock des Änderungsgesetzes steht im Inhaltsstrom mitten darin + // („… zur Sicherung des Be-“ / Titelblock / „triebs von Unterkünften …“). Seit die + // Lesereihenfolge dem Satzbild folgt, steht der Titel, wo er hingehört, und das Zitat liest + // sich durch. + var nummer31 = + anwendung.neu().norm("Anlage Nummer 31").orElseThrow().gesamtText().replaceAll("\\s+", " "); + assertThat(nummer31) + .doesNotContain("Gesetz zur Änderung des Allgemeinen Sicherheits- und Ordnungsgesetzes") + .contains("die Ordnungsaufgaben zur Sicherung des Betriebs von Unterkünften"); assertThat(anwendung.neu().norm("§ 67").orElseThrow().gesamtText()) .contains("Gegen einen straßenverkehrsrechtlichen Verwaltungsakt"); } diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/LesereihenfolgeTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/LesereihenfolgeTest.java new file mode 100644 index 0000000..f213d07 --- /dev/null +++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/LesereihenfolgeTest.java @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2020 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// SPDX-License-Identifier: AGPL-3.0-or-later +package eu.mulk.aendggner.aenderung.parse; + +import static org.assertj.core.api.Assertions.assertThat; + +import eu.mulk.aendggner.aenderung.parse.FontgroessenFilter.Zeile; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** Die Lesereihenfolge am nachgestellten Satzbild eines Gesetzblatts. */ +class LesereihenfolgeTest { + + /** Der Satzspiegel des Berliner Blattes: zwei Spalten von 45 bis 289 und von 332 bis 547. */ + private static Zeile links(int seite, float y, String text) { + return new Zeile(seite, y, 45f, 289f, text); + } + + private static Zeile rechts(int seite, float y, String text) { + return new Zeile(seite, y, 332f, 547f, text); + } + + private static Zeile breit(int seite, float y, String text) { + return new Zeile(seite, y, 127f, 465f, text); + } + + private static List<String> texte(List<Zeile> zeilen) { + var namen = new ArrayList<String>(); + for (var zeile : zeilen) { + namen.add(zeile.text()); + } + return namen; + } + + /** + * Der Fall, der den Anstoß gab: Das Blatt zeichnet den ganzseitenbreiten Titelblock zuletzt, + * obgleich er über beiden Spalten steht. Er gehört vor sie — und die Spalten bleiben ganz. + */ + @Test + void derZuletztGezeichneteTitelblockGehoertNachOben() { + var seite = new ArrayList<Zeile>(); + seite.add(breit(1, 51, "Kolumnentitel")); + for (int i = 0; i < 5; i++) { + seite.add(links(1, 345 + 10 * i, "links " + i)); + } + for (int i = 0; i < 5; i++) { + seite.add(rechts(1, 345 + 10 * i, "rechts " + i)); + } + seite.add(breit(1, 280, "Gesetz zur Änderung")); + seite.add(breit(1, 293, "Vom 3. Juni 2026")); + + assertThat(texte(Lesereihenfolge.ordne(seite))) + .containsExactly( + "Kolumnentitel", + "Gesetz zur Änderung", + "Vom 3. Juni 2026", + "links 0", + "links 1", + "links 2", + "links 3", + "links 4", + "rechts 0", + "rechts 1", + "rechts 2", + "rechts 3", + "rechts 4"); + } + + /** + * Die Falle des einfachen XY-Schnitts: Läge zwischen den Zeilen 4 und 5 ein weites Band, so + * zerfiele die Seite erst in oben und unten — gelesen würde links oben, rechts oben, links unten, + * rechts unten. Die Spalte hat Vorrang vor dem Band. + */ + @Test + void eineLueckeAufGleicherHoeheZerreisstDieSpaltenNicht() { + var seite = new ArrayList<Zeile>(); + seite.add(breit(1, 51, "Kolumnentitel")); + float[] hoehen = {100, 110, 120, 130, 300, 310, 320, 330}; + for (float y : hoehen) { + seite.add(links(1, y, "links " + (int) y)); + } + for (float y : hoehen) { + seite.add(rechts(1, y, "rechts " + (int) y)); + } + + var geordnet = texte(Lesereihenfolge.ordne(seite)); + assertThat(geordnet).startsWith("Kolumnentitel", "links 100"); + assertThat(geordnet.subList(1, 9)).allMatch(t -> t.startsWith("links")); + assertThat(geordnet.subList(9, 17)).allMatch(t -> t.startsWith("rechts")); + } + + /** Einspaltiger Satz hat keine Rinne; da bleibt es beim Inhaltsstrom. */ + @Test + void einspaltigerSatzBleibtUnangetastet() { + var seite = new ArrayList<Zeile>(); + for (int i = 0; i < 10; i++) { + seite.add(breit(1, 100 + 10 * i, "zeile " + i)); + } + assertThat(Lesereihenfolge.ordne(seite)).isEqualTo(seite); + } + + /** Fehlt einer Zeile die Geometrie, so ist die ganze Seite nicht zu vermessen. */ + @Test + void ohneGeometrieBleibtDieSeiteWieSieIst() { + var seite = new ArrayList<Zeile>(); + seite.add(new Zeile(0, Float.NaN, Float.NaN, Float.NaN, "unbekannt")); + for (int i = 0; i < 5; i++) { + seite.add(links(0, 100 + 10 * i, "links " + i)); + seite.add(rechts(0, 100 + 10 * i, "rechts " + i)); + } + assertThat(Lesereihenfolge.ordne(seite)).isEqualTo(seite); + } +} diff --git a/src/test/resources/sampledata/Landesrecht-Beispiele.adoc b/src/test/resources/sampledata/Landesrecht-Beispiele.adoc index fef01d9..ea218a0 100644 --- a/src/test/resources/sampledata/Landesrecht-Beispiele.adoc +++ b/src/test/resources/sampledata/Landesrecht-Beispiele.adoc @@ -26,11 +26,12 @@ Stammfassung kommt je Land aus dem Portal des Landes oder als handgepflegter Kla |Berlin |`Berlin/GVBl-2026-17_ASOG-LAF-AendG.pdf` — GVBl. für Berlin Nr. 17 vom 11.06.2026, S. 234 (Gesetz zur Änderung des ASOG und des LAF-Errichtungsgesetzes vom 03.06.2026) -|*Zweispaltiges Layout* (Herausgabe über Wolters Kluwer). Der Spaltenspike ist erledigt: Die -Reihenfolge des Inhaltsstroms *ist* bereits die Lesereihenfolge, eine koordinatenbasierte -Spaltenerkennung ist nicht nötig. Verbleibende Layout-Eigenheit sind ganzseitenbreite Rahmen -(Titelblock, laufender Seitenkopf), die an falscher Stelle im Strom stehen — der Seitenkopf wird -herausgeschnitten, der Titelblock landet weiterhin mitten im Text. Tiefe Verschachtelung +|*Zweispaltiges Layout* (Herausgabe über Wolters Kluwer). Innerhalb einer Spalte ist die +Reihenfolge des Inhaltsstroms bereits die Lesereihenfolge; die ganzseitenbreiten Rahmen dagegen +stehen an falscher Stelle im Strom — der Titelblock wird *zuletzt* gezeichnet, obgleich er über +beiden Spalten steht, und landete damit mitten im Fließtext. Seit Welle 21 folgt die Reihenfolge +dem Satzbild (Rinne zwischen den Spalten, breite Zeilen als Bandgrenzen), und der Titelblock steht, +wo er hingehört. Tiefe Verschachtelung a)/aa)/bb)/cc); Artikel 1 ändert eine *Anlage*, deren Einheiten als *Überschriften* („Nummer 6") statt als Aufzählungsmarken gesetzt sind. Artikel 2 (LAF-Errichtungsgesetz) ist ein voller Akzeptanzfall; das Portal setzt die amtlichen *Satznummern als angeklebte Ziffern* („(2) 1Gegen …"). @@ -152,16 +153,17 @@ vollständig belegt. * *Berlin* ist umgesetzt (`EndToEndTest.asogLafAendGBerlin`). Artikel 2 (LAF-Errichtungsgesetz) ist ein *voller* Akzeptanzfall: beide Befehle angewandt, alle fünf Normen gleich der amtlichen Nachfassung vom 12. Juni 2026. Artikel 1 (ASOG) wird vollständig - erkannt und zu fünf Sechsteln angewandt; 168 der 171 Normen gleichen danach der Nachfassung. + erkannt und zu fünf Sechsteln angewandt; 169 der 171 Normen gleichen danach der Nachfassung. Die Nummern der Anlage sind seit dieser Welle *eigene Normen* („Anlage Nummer 23“), wie das Portal sie führt — damit trägt die vorhandene Absatz-Maschinerie ihre Befehle. Beide Stammfassungen sind über die Browsersteuerung beschafft. + -- -Die drei Abweichungen sind benannt und liegen nicht in der Anwendung: § 67 (das Portal setzt -amtliche Satznummern, das Gesetzblatt nicht), Anlage Nummer 23 (der eine Rest, siehe unten) und -Anlage Nummer 31 (der ganzseitenbreite Titelblock steht im Inhaltsstrom mitten im Zitat — die -offene Layout-Frage). Der eine liegengebliebene Befehl benennt eine Grenze des Klartextformats: +Die zwei Abweichungen sind benannt und liegen nicht in der Anwendung: § 67 (das Portal setzt +amtliche Satznummern, das Gesetzblatt nicht) und Anlage Nummer 23 (der eine Rest, siehe unten). +Die Anlage Nummer 31 war die dritte, bis die Lesereihenfolge dem Satzbild folgte: Der +ganzseitenbreite Titelblock stand im Inhaltsstrom mitten in ihrem Zitat. Der eine liegengebliebene +Befehl benennt eine Grenze des Klartextformats: Ein Zwischentitel ohne Absatzbezeichnung („Aus dem Bereich Verkehr:“) wird dem vorangehenden Absatz zugeschlagen, weshalb dessen Text nicht auf den Punkt endet, den der Befehl meint. -- @@ -253,9 +255,6 @@ Klartext-Zweig des Loaders normalisiert zur Sicherheit mit. dagegen als Aufzählungsmarken im Wortlaut der Anlage; dort bleibt es beim Markensuchen, und ein Befehl auf „die Überschrift der Nummer 1 der Anlage 8“ (GEG, Artikel 1 Nr. 43 b) aa)) findet seine Stelle nicht. Ob die Anlagen des Bundes ebenso zu zerlegen sind, ist offen. -* *Berlin, ganzseitenbreite Rahmen*: Titelblock und Impressum stehen im Inhaltsstrom an anderer - Stelle als im Satzbild (der Titelblock mitten im Fließtext der ersten Seite). Das zu beheben - verlangt eine geometrische Lesereihenfolge (XY-Cut), nicht bloß eine Spaltenerkennung. * *Rheinland-Pfalz*: Die Verkündungsplattform `verkuendung.rlp.de` verkündet erst seit dem 01.07.2026 elektronisch; ältere GVBl-Hefte liegen nicht als freie Direkt-PDF vor. * Bremen, Hamburg, Mecklenburg-Vorpommern, Saarland, Brandenburg, Sachsen-Anhalt: noch nicht @@ -266,10 +265,6 @@ Klartext-Zweig des Loaders normalisiert zur Sicherheit mit. Kein Land scheitert noch an reiner Extraktionsschwäche, und jedes Land mit einem Änderungs- dokument trägt nunmehr einen Belegfall bis zur Anwendung. Nach absteigendem Nutzen: -. *Berlin, ganzseitenbreite Rahmen* (XY-Cut) — die letzte offene Layout-Frage, siehe „Noch - offen“. Sie ist seit dieser Welle *belegt*: Die Anlage Nummer 31 des ASOG weicht allein - deshalb von der Nachfassung ab, weil der Titelblock des Änderungsgesetzes im Inhaltsstrom - mitten in ihrem Zitat steht. . *Weitere Länder erfassen* — Rheinland-Pfalz (Verkündungsplattform erst ab 01.07.2026), Bremen, Hamburg, Mecklenburg-Vorpommern, Saarland, Brandenburg, Sachsen-Anhalt. |
