From 81777013dfa582053489efea874e84379f1f0805 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Sun, 26 Jul 2026 08:53:10 +0200 Subject: Support unnumbered named Anlagen; strip control characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Investigating "Anlagen as amendment target" corrected the diagnosis from yesterday: Anlagen are supported. In gii-XML they are norms of their own ("Anlage 8", "Anhang"), Stelle.anlagenEnbez() resolves them, and the GEG sample applies annex-targeted commands — only two of its fifty manual entries touch annexes, both for content reasons. What actually failed in Berlin was narrower, and is fixed: * A law with a single annex names it after the provision it belongs to ("Die Anlage zu § 2 Absatz 4 Satz 1"). StellenParser demanded a number after "Anlage" and rejected the phrase, so the article's frame command went unrecognised and its items lost their context. The annex now carries the designation "Anlage", like "Anhang". The naming suffix is skipped deliberately: read along, the "§ 2" inside it would become the target and the wrong norm would be amended. * C0 control characters are now stripped. Berlin's enumeration indent carries a U+0007 that sits invisibly in front of the command and made its start-of-line anchor miss. * Command verbs torn apart by a stray space in the official typesetting ("ein gefügt" for "eingefügt") are rejoined, restricted to sequences that are not valid German word order. Three of Article 1's four commands are now recognised. The fourth places the new unit by a word anchor ("Vor den Wörtern … wird folgender Absatz 5 eingefügt"), which StrukturEinfuegung cannot express; that and the inability of the plain-text stem format to carry an Anlage at all are recorded in Landesrecht-Beispiele.adoc. 231 tests green; all pinned figures unchanged. Co-Authored-By: Claude Opus 5 Change-Id: Ic5804bf343dd5d8ce435c07b8f882ca0372890d2 --- src/test/java/eu/mulk/aendggner/EndToEndTest.java | 22 ++++++++++++++++++++++ .../aenderung/parse/StellenParserTest.java | 15 +++++++++++++++ .../aenderung/parse/TextBereinigerTest.java | 9 +++++++++ 3 files changed, 46 insertions(+) (limited to 'src/test') diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java index 19a7c8c..b3bea08 100644 --- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java +++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java @@ -597,5 +597,27 @@ class EndToEndTest { assertThat(ergebnis.befehle()) .extracting(b -> b.stelle().anzeigeText()) .containsExactly("§ 2 Satz 1 Nummer 2", "§ 2 Satz 1 Nummer 2"); + + // 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 artikel1 = new AenderungsgesetzParser().parse(text, asog, null); + assertThat(artikel1.artikel()).containsExactly("1"); + assertThat(artikel1.befehle()) + .extracting(b -> b.stelle().anzeigeText()) + .containsExactly( + "§ 67 Absatz 2", + "Anlage Nummer 6 Absatz 2", + "Anlage Nummer 23 Absatz 4a", + "Anlage Nummer 23"); + // Bekannte Grenze: „Vor den Wörtern „…“ wird folgender Absatz 5 eingefügt“ — eine + // Struktureinfügung, deren Position ein Wortanker statt einer Stelle bestimmt. + assertThat(artikel1.befehle().stream().filter(b -> b instanceof UnbekannterBefehl).toList()) + .singleElement() + .extracting(b -> b.provenienz().gliederungsPfad()) + .isEqualTo("2. b) bb)"); } } diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java index f15b98f..d5b960a 100644 --- a/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java +++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java @@ -109,6 +109,21 @@ class StellenParserTest { assertThat(StellenParser.istNurChapeau("in dem Satzteil nach Nr. 3")).isTrue(); } + @Test + void parstUnnummerierteBenannteAnlage() { + // Ein Gesetz mit einer einzigen Anlage benennt sie nach der Vorschrift, zu der sie gehört. + // Der Zusatz identifiziert die Anlage und ist nicht selbst Ziel — sonst würde das darin + // genannte „§ 2“ als Ziel gelesen und die falsche Norm geändert. + var anlage = StellenParser.parse("Die Anlage zu § 2 Absatz 4 Satz 1").orElseThrow(); + assertThat(anlage.anzeigeText()).isEqualTo("Anlage"); + assertThat(anlage.anlagenEnbez()).contains("Anlage"); + assertThat(anlage.paragraph()).isEmpty(); + + // Nummerierte Anlagen bleiben unverändert. + assertThat(StellenParser.parse("Anlage 8 Nummer 1 Buchstabe b").orElseThrow().anzeigeText()) + .isEqualTo("Anlage 8 Nummer 1 Buchstabe b"); + } + @Test void ignoriertChapeauZusatz() { // „in der Angabe vor Nummer 1“ ist ein verfeinernder Zusatz ohne eigene Komponente. diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/TextBereinigerTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/TextBereinigerTest.java index 5ed8b93..5b7e850 100644 --- a/src/test/java/eu/mulk/aendggner/aenderung/parse/TextBereinigerTest.java +++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/TextBereinigerTest.java @@ -223,6 +223,15 @@ class TextBereinigerTest { .isEqualTo("Artikel 2\nDie Kreisordnung"); } + @Test + void entferntSteuerzeichenUndRepariertZerrisseneBefehlsvokabeln() { + // Im GVBl. für Berlin trägt der Einzug der Aufzählungsglieder ein U+0007; es steht unsichtbar + // vor dem Befehl und ließ dessen Zeilenanfangs-Anker ins Leere greifen. Im selben Heft ist + // „eingefügt“ als „ein ge-“ + „fügt“ gesetzt — ein Leerzeichen mitten in der Befehlsvokabel. + assertThat(TextBereiniger.bereinige("a)\t Nach Nummer 6 wird folgender Absatz ein ge-\nfügt:")) + .isEqualTo("a)\t Nach Nummer 6 wird folgender Absatz eingefügt:"); + } + @Test void schneidetDenBerlinerSeitenkopfAusDemFliesstext() { // Im GVBl. für Berlin steht der laufende Seitenkopf nicht auf einer eigenen Zeile, sondern -- cgit v1.2.1