From 9a29b384b84fac20f3c417ff9dcf3d461627e185 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Mon, 13 Jul 2026 21:33:43 +0200 Subject: Support range, renumbering, and punctuation commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the parser and applier for several command families that were falling back to UnbekannterBefehl: - Range/coordinated Aufhebung ("Die Nummern 1 bis 3 werden aufgehoben.", "Die Absätze 4 und 5 werden aufgehoben.") via bis-range expansion in StellenParser.parseMehrfach and ausStellen. - Range renumbering without "zu den" and for Nummern/Buchstaben ("Die bisherigen Nummern 4 bis 6 werden die Nummern 8 bis 10."); single renumbering now also covers Nummer/Buchstabe and "Die bisherige". - §-range Neufassung ("Die §§ 52 bis 56 werden wie folgt gefasst: …"), splitting the quoted block at "§ N" boundaries. - "Der Wortlaut wird Absatz N." — a new WortlautZuAbsatz command that numbers the previously unnumbered body. - Word-to-punctuation replacement ("… das Wort „oder" am Ende durch ein Komma ersetzt") and comma+words insert/replace variants. StellenParser gains plural component words (Absätze/Sätze/Nummern/ Buchstaben) and bis-range expansion. Cuts UnbekannterBefehl further: GEG 36->21, IfSG 7->3. Co-Authored-By: Claude Opus 4.8 Change-Id: I4972d4eb1d6062521a55710c1f23822e8e22b0c4 --- .../aenderung/parse/BefehlErkennerTest.java | 68 ++++++++++++++++++++++ .../aendggner/anwendung/BefehlAnwenderTest.java | 38 ++++++++++++ 2 files changed, 106 insertions(+) (limited to 'src/test/java') diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java index ba0dd05..2d0db8a 100644 --- a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java +++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java @@ -14,6 +14,7 @@ import eu.mulk.aendggner.aenderung.Aenderungsbefehl.StrukturEinfuegung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Umnummerierung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.WoerterEinfuegung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.WortAnker; +import eu.mulk.aendggner.aenderung.Aenderungsbefehl.WortlautZuAbsatz; import eu.mulk.aendggner.aenderung.Provenienz; import eu.mulk.aendggner.aenderung.Stelle; import java.util.List; @@ -450,6 +451,73 @@ class BefehlErkennerTest { .isEmpty(); } + @Test + void bereichsAufhebungWirdSammelbefehl() { + var teile = + ((Sammelbefehl) erkenne("Die Nummern 1 bis 3 werden aufgehoben.", Stelle.LEER).orElseThrow()) + .teilbefehle(); + assertThat(teile).hasSize(3).allMatch(t -> t instanceof Aufhebung); + assertThat(teile).extracting(t -> t.stelle().anzeigeText()) + .containsExactly("Nummer 1", "Nummer 2", "Nummer 3"); + } + + @Test + void koordinierteAufhebungWirdSammelbefehl() { + var teile = + ((Sammelbefehl) erkenne("Die Absätze 4 und 5 werden aufgehoben.", Stelle.LEER).orElseThrow()) + .teilbefehle(); + assertThat(teile).extracting(t -> t.stelle().anzeigeText()) + .containsExactly("Absatz 4", "Absatz 5"); + } + + @Test + void bereichsUmnummerierungNummernOhneZuDen() { + var teile = + ((Sammelbefehl) + erkenne("Die bisherigen Nummern 4 bis 6 werden die Nummern 8 bis 10.", Stelle.LEER) + .orElseThrow()) + .teilbefehle(); + assertThat(teile).hasSize(3).allMatch(t -> t instanceof Umnummerierung); + // Absteigend: 6→10, 5→9, 4→8. + assertThat(teile).extracting(t -> t.stelle().anzeigeText()) + .containsExactly("Nummer 6", "Nummer 5", "Nummer 4"); + assertThat(teile).extracting(t -> ((Umnummerierung) t).neu().anzeigeText()) + .containsExactly("Nummer 10", "Nummer 9", "Nummer 8"); + } + + @Test + void paragraphBereichNeufassungWirdSammelbefehl() { + var teile = + ((Sammelbefehl) + erkenne( + "Die §§ 52 bis 56 werden wie folgt gefasst: „§ 52 (weggefallen) § 53" + + " (weggefallen) § 54 (weggefallen)“.", + Stelle.LEER) + .orElseThrow()) + .teilbefehle(); + assertThat(teile).hasSize(3).allMatch(t -> t instanceof Neufassung); + assertThat(teile).extracting(t -> t.stelle().anzeigeText()) + .containsExactly("§ 52", "§ 53", "§ 54"); + } + + @Test + void wortlautWirdAbsatz() { + var befehl = + erkenne("Der Wortlaut wird Absatz 1.", new Stelle(List.of(new Stelle.Paragraph("5")))); + assertThat(befehl).get().isInstanceOf(WortlautZuAbsatz.class); + assertThat(((WortlautZuAbsatz) befehl.orElseThrow()).nummer()).isEqualTo("1"); + } + + @Test + void wortDurchSatzzeichen() { + var befehl = + erkenne("In Nummer 7 wird das Wort „oder“ am Ende durch ein Komma ersetzt.", Stelle.LEER); + var e = (Ersetzung) befehl.orElseThrow(); + assertThat(e.alt()).isEqualTo("oder"); + assertThat(e.neu()).isEqualTo(","); + assertThat(e.amEnde()).isTrue(); + } + @Test void mehrfachErsetzungWirdSammelbefehl() { // Mehrere Ersetzungspaare unter einem gemeinsamen „ersetzt“. diff --git a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java index a854632..8a1b1e0 100644 --- a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java +++ b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java @@ -459,6 +459,44 @@ class BefehlAnwenderTest { false))); } + @Test + void wendetWortlautZuAbsatzAn() { + // § 2 hat einen unnummerierten Wortlaut, der zu Absatz 1 wird. + var befehl = + new eu.mulk.aendggner.aenderung.Aenderungsbefehl.WortlautZuAbsatz( + stelle(new Stelle.Paragraph("2")), "1", PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetz(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + var norm = ergebnis.neu().norm("§ 2").orElseThrow(); + assertThat(norm.absaetze()).hasSize(1); + assertThat(norm.absaetze().get(0).nummer()).isEqualTo("1"); + assertThat(norm.absaetze().get(0).anzeigeText()).startsWith("(1) Erprobung ist"); + } + + @Test + void wendetBereichsAufhebungAn() { + // „Die Nummern 1 bis 3 werden aufgehoben.“ innerhalb von § 1 Absatz 2. + var teile = + List.of( + new Aufhebung( + stelle(new Stelle.Paragraph("1"), new Stelle.AbsatzNr("2"), new Stelle.NummerNr("1")), + PROV), + new Aufhebung( + stelle(new Stelle.Paragraph("1"), new Stelle.AbsatzNr("2"), new Stelle.NummerNr("2")), + PROV), + new Aufhebung( + stelle(new Stelle.Paragraph("1"), new Stelle.AbsatzNr("2"), new Stelle.NummerNr("3")), + PROV)); + + var ergebnis = BefehlAnwender.anwenden(gesetz(), List.of(new Sammelbefehl(teile))); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + var text = absatzText(ergebnis.neu(), "§ 1", 1); + assertThat(text).contains("1. (weggefallen)").contains("2. (weggefallen)").contains("3. (weggefallen)"); + } + private static String absatzText(Gesetz gesetz, String enbez, int index) { return gesetz.norm(enbez).orElseThrow().absaetze().get(index).text(); } -- cgit v1.2.1