aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/eu
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-23 21:52:20 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-23 21:52:20 +0200
commit2ada4600cbb8dbd0dc8ac2fa0537f6853ba62359 (patch)
tree81b3c22e9c4f64070e219de2db3e8e98d2667cac /src/test/java/eu
parent2febdfd49776c473377ec08c0d4920ede38a242a (diff)
Die Nummer einer Anlage ist eine Norm, kein Aufzählungsglied
Der Zuständigkeitskatalog des Berliner Sicherheits- und Ordnungsgesetzes zählt seine Einheiten nicht als Aufzählungsglieder, sondern führt sie als „Nummer 6“, „Nummer 23“, „Nummer 31“ — jede mit eigener Absatzzählung, jede im Portal eine eigene Einzelnorm. Der Stellenauflöser suchte dagegen eine Marke „6.“ im Wortlaut der Anlage und fand dort vielerlei; von sechs Befehlen des Artikels 1 wurden zwei angewandt. Sie werden nunmehr geführt, wie sie sind: als eigene Normen, deren Bezeichnung die Anlage voranstellt. Damit trägt die vorhandene Absatz-Maschinerie ihre Befehle unverändert, und es braucht keinen neuen Rang im Modell. Wo ein Gesetz solche Normen nicht führt — im gii-XML des Bundes stehen die Nummern einer Anlage als Marken im Wortlaut —, bleibt es beim bisherigen Weg. Dabei kamen drei Mängel ans Licht. Ob die feinste Komponente einer Fundstelle ein Absatz ist, entschied das bloße Vorkommen einer feineren Komponente; die Nummer einer Anlage steht aber vor der Absatzangabe und gehört zur Bezeichnung der Norm. Die Umnummerierung eines solchen Absatzes galt deshalb als die einer Aufzählungseinheit, tauschte eine Marke, die es nicht gab, hieß angewandt und bewirkte nichts. Ein Absatz, der „vor den Wörtern …“ eingefügt wird, wurde eine Zeile im Nachbarabsatz statt eines Absatzes. Und die Bezeichnung eines so eingefügten Absatzes verlor die Nummer der Anlage, weshalb die Schritt-Ordnung nicht sah, dass eine Umnummerierung ihm den Platz erst räumt. Beigelegt sind die beiden Fassungen des ASOG, über die Browsersteuerung beschafft. Der Auszug ist dort bei fünfzigtausend Zeichen gekappt und verschluckt einzelne Leerzeichen; base64-kodierte Stücke kamen zeichengenau herüber. Fünf der sechs Befehle werden angewandt, und 168 der 171 Normen gleichen danach der amtlichen Nachfassung. Die drei Abweichungen sind benannt und liegen nicht in der Anwendung — eine von ihnen belegt erstmals, was die geometrische Lesereihenfolge kosten würde: Der Titelblock des Änderungsgesetzes steht im Inhaltsstrom mitten in einem Zitat. Geprüft mit „mvnw verify“: 333 Prüfungen, kein Fehlschlag; keine gepinnte Zahl eines anderen Belegfalls hat sich geändert. REUSE 171/171. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Change-Id: I24b94cb96e6728b1ff1b1fcb5bb4b8433594e240
Diffstat (limited to 'src/test/java/eu')
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java72
-rw-r--r--src/test/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParserTest.java37
2 files changed, 107 insertions, 2 deletions
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 97bcffb..d7e19f3 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -17,6 +17,7 @@ import eu.mulk.aendggner.aenderung.parse.SuperskriptModus;
import eu.mulk.aendggner.aenderung.parse.TextBereiniger;
import eu.mulk.aendggner.aenderung.parse.ZitatExtraktor;
import eu.mulk.aendggner.anwendung.BefehlAnwender;
+import eu.mulk.aendggner.gesetz.Absatz;
import eu.mulk.aendggner.gesetz.Gesetz;
import eu.mulk.aendggner.gesetz.gii.GiiXmlLoader;
import eu.mulk.aendggner.synopse.HtmlRenderer;
@@ -940,8 +941,13 @@ class EndToEndTest {
// Artikel 1 ändert die unnummerierte, nach ihrer Vorschrift benannte Anlage („Die Anlage zu
// § 2 Absatz 4 Satz 1 wird wie folgt geändert“). Der Zusatz benennt die Anlage nur — er darf
// nicht als Ziel „§ 2“ gelesen werden; die Punkte erben „Anlage“ als Kontext.
- var asog =
- new Gesetz("ASOG Bln", "Allgemeines Sicherheits- und Ordnungsgesetz", null, List.of());
+ var altAsog = SAMPLEDATA.resolve("Berlin/ASOG-Bln-alt.txt");
+ assumeTrue(Files.exists(altAsog), "ASOG-Stammfassung fehlt");
+ var asog = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(altAsog);
+ // 129 Paragraphen, die Anlage und ihre 40 Nummern — letztere sind eigene Normen mit eigener
+ // Absatzzählung, wie das Landesrechtsportal sie führt.
+ assertThat(asog.normen()).hasSize(171);
+ assertThat(asog.norm("Anlage Nummer 23")).isPresent();
var artikel1 = new AenderungsgesetzParser().parse(text, asog, null);
assertThat(artikel1.artikel()).containsExactly("1");
assertThat(artikel1.befehle())
@@ -976,6 +982,68 @@ class EndToEndTest {
// Artikel 2 wird vollständig angewandt und gleicht danach der amtlichen Nachfassung.
pruefeGegenNachfassung(laf, ergebnis, "Berlin/LAF-ErrG-neu.txt");
+
+ // Artikel 1: fünf der sechs Befehle werden angewandt.
+ var anwendung = BefehlAnwender.anwenden(asog, artikel1.befehle());
+ var manuellPfade =
+ anwendung.protokoll().stream()
+ .filter(x -> x.status() == BefehlAnwender.Status.MANUELL_PRUEFEN)
+ .map(x -> x.befehl().provenienz().gliederungsPfad())
+ .toList();
+ // Einziger Rest, und er benennt eine Grenze des Klartextformats: „In Absatz 4a wird der Punkt
+ // am Ende durch ein Semikolon ersetzt.“ Auf den Absatz 4a folgt der Zwischentitel „Aus dem
+ // Bereich Verkehr:“, der keine eigene Absatzbezeichnung trägt und deshalb dem vorangehenden
+ // Absatz zugeschlagen wird. Dessen Text endet damit nicht auf den Punkt, den der Befehl meint.
+ assertThat(manuellPfade).containsExactly("2. b) aa)");
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(5);
+
+ // Die Nummer 23 der Anlage trägt danach die Absatzfolge der amtlichen Nachfassung: Der neue
+ // Absatz 5 ist ein Absatz geworden (nicht eine Zeile im Absatz 4a), und die bisherigen
+ // Absätze 5 bis 9 sind zu 6 bis 10 aufgerückt.
+ var nummer23 = anwendung.neu().norm("Anlage Nummer 23").orElseThrow();
+ assertThat(nummer23.absaetze())
+ .extracting(Absatz::nummer)
+ .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:
+ 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);
+ var abweichend = new java.util.ArrayList<String>();
+ for (var normSoll : soll.normen()) {
+ var normIst = anwendung.neu().norm(normSoll.enbez()).orElseThrow();
+ if (!normIst
+ .gesamtText()
+ .replaceAll("\\s+", " ")
+ .strip()
+ .equals(normSoll.gesamtText().replaceAll("\\s+", " ").strip())) {
+ abweichend.add(normSoll.enbez());
+ }
+ }
+ // Drei 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(anwendung.neu().norm("§ 67").orElseThrow().gesamtText())
+ .contains("Gegen einen straßenverkehrsrechtlichen Verwaltungsakt");
}
/**
diff --git a/src/test/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParserTest.java b/src/test/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParserTest.java
index bed8f9e..e2a6086 100644
--- a/src/test/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParserTest.java
+++ b/src/test/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParserTest.java
@@ -4,6 +4,7 @@ package eu.mulk.aendggner.gesetz.land;
import static org.assertj.core.api.Assertions.assertThat;
+import eu.mulk.aendggner.gesetz.Absatz;
import eu.mulk.aendggner.gesetz.Gliederung;
import org.junit.jupiter.api.Test;
@@ -299,4 +300,40 @@ class LandesRechtTextParserTest {
.contains("§ 1 bleibt unberührt");
assertThat(gesetz.norm("Anlage 2").orElseThrow().gesamtText()).contains("Das Muster");
}
+
+ /**
+ * Trägt eine Anlage Nummern als eigene Einheiten — so der Zuständigkeitskatalog des Berliner ASOG
+ * —, so ist jede eine eigene Norm mit eigener Absatzzählung. Ihre Bezeichnung stellt die Anlage
+ * voran, damit sie im Gesetz eindeutig bleibt: „Anlage Nummer 6“.
+ */
+ @Test
+ void nummernEinerAnlageSindEigeneNormen() {
+ var gesetz =
+ LandesRechtTextParser.parse(
+ """
+ Beispielgesetz
+ (BeispG)
+ Vom 1. Januar 2020
+ § 1 Zweck
+ (1) Dieses Gesetz dient der Erprobung.
+ Anlage
+ (zu § 1 Absatz 1)
+ Nummer 6
+ Bezirksamt
+ (1) Die erste Aufgabe.
+ (2) Die zweite Aufgabe.
+ Nummer 23
+ Polizeipräsident
+ (1) Die dritte Aufgabe.
+ """);
+
+ assertThat(gesetz.normen())
+ .extracting(eu.mulk.aendggner.gesetz.Norm::enbez)
+ .containsExactly("§ 1", "Anlage", "Anlage Nummer 6", "Anlage Nummer 23");
+ var nummer6 = gesetz.norm("Anlage Nummer 6").orElseThrow();
+ assertThat(nummer6.absaetze()).extracting(Absatz::nummer).containsExactly(null, "1", "2");
+ // Der Vorspann der Anlage bleibt bei ihr; die Nummern nehmen ihn nicht mit.
+ assertThat(gesetz.norm("Anlage").orElseThrow().gesamtText()).contains("(zu § 1 Absatz 1)");
+ assertThat(nummer6.gesamtText()).contains("Bezirksamt").doesNotContain("zu § 1 Absatz 1");
+ }
}