From b7e9f0f4756bf87ef98500d5fd0ce87d2916c3c4 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Thu, 16 Jul 2026 21:12:08 +0200 Subject: =?UTF-8?q?Support=20Anhang/Anlage=20targets,=20Inhalts=C3=BCbersi?= =?UTF-8?q?cht=20Angabe=20commands,=20and=20Gliederungs-=C3=9Cberschriften?= =?UTF-8?q?=20insertion/replacement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fourth enablement wave: Anhänge/Anlagen resolve as ordinary norm targets with nested Nummer/Buchstabe block resolution, InhaltsuebersichtAnwender applies Angabe commands automatically, GliederungsUeberschriften handles inserting and replacing structural headings, the law's own heading can be recast, and FontgroessenFilter now determines body text per page instead of document-wide. Sample data (UWG/AGG/ProdHaftG) now applies with 0 manual cases. Co-Authored-By: Claude Sonnet 5 Change-Id: I1a76750afbecfa1aae734bcdabc91470a28420ca --- src/test/java/eu/mulk/aendggner/EndToEndTest.java | 5 +- .../aenderung/parse/BefehlErkennerTest.java | 277 ++++++++++++++++- .../aendggner/anwendung/BefehlAnwenderTest.java | 339 +++++++++++++++++++++ 3 files changed, 616 insertions(+), 5 deletions(-) (limited to 'src/test/java/eu') diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java index 483f06a..d4b0b17 100644 --- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java +++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java @@ -89,9 +89,12 @@ class EndToEndTest { assertThat(parseErgebnis.befehle().size()).isGreaterThanOrEqualTo(10); var typisiert = parseErgebnis.befehle().stream().filter(b -> !(b instanceof UnbekannterBefehl)).count(); - assertThat(typisiert).isGreaterThan(parseErgebnis.befehle().size() / 2); + assertThat(typisiert).isEqualTo(parseErgebnis.befehle().size()); + // Referenzfall des Ausbaus: alle Befehle (auch die Anhang-Änderungen) werden angewandt. var anwendung = BefehlAnwender.anwenden(gesetz, parseErgebnis.befehle()); + assertThat(anwendung.anzahlManuell()).isZero(); + assertThat(anwendung.anzahlAngewandt()).isEqualTo(parseErgebnis.befehle().size()); var synopse = SynopseBuilder.baue(gesetz, anwendung, parseErgebnis.warnungen(), false); assertThat(HtmlRenderer.rendere(synopse, "E2E-Test")).contains("Neue Fassung"); } 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 bbee916..f8cfe82 100644 --- a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java +++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java @@ -11,6 +11,7 @@ import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Neufassung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Sammelbefehl; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Streichung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.StrukturEinfuegung; +import eu.mulk.aendggner.aenderung.Aenderungsbefehl.StrukturErsetzung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Umnummerierung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.WoerterEinfuegung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.WortAnker; @@ -222,8 +223,12 @@ class BefehlErkennerTest { + " „§ 28a Besondere Schutzmaßnahmen“.", Stelle.LEER); - assertThat(befehl).containsInstanceOf(WoerterEinfuegung.class); - assertThat(befehl.orElseThrow().stelle().betrifftInhaltsuebersicht()).isTrue(); + assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class); + var einfuegung = (StrukturEinfuegung) befehl.orElseThrow(); + assertThat(einfuegung.stelle().betrifftInhaltsuebersicht()).isTrue(); + assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 28"); + assertThat(einfuegung.vorher()).isFalse(); + assertThat(einfuegung.text()).isEqualTo("§ 28a Besondere Schutzmaßnahmen"); } @Test @@ -344,8 +349,11 @@ class BefehlErkennerTest { "Nach der Angabe zu § 9 wird folgende Angabe eingefügt: „§ 9a" + " Länderregelung“.", kontext); - assertThat(befehl).containsInstanceOf(WoerterEinfuegung.class); - assertThat(befehl.orElseThrow().stelle().betrifftInhaltsuebersicht()).isTrue(); + assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class); + var einfuegung = (StrukturEinfuegung) befehl.orElseThrow(); + assertThat(einfuegung.stelle().betrifftInhaltsuebersicht()).isTrue(); + assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 9"); + assertThat(einfuegung.text()).isEqualTo("§ 9a Länderregelung"); } @Test @@ -757,4 +765,265 @@ class BefehlErkennerTest { assertThat(teile).extracting(t -> ((Ersetzung) t).alt()).containsExactly("a", "c"); assertThat(teile).extracting(t -> ((Ersetzung) t).neu()).containsExactly("b", "d"); } + // --- Welle-4-Formen -------------------------------------------------------------------------- + + @Test + void erkenntAngabenBereichsErsetzungInDerInhaltsuebersicht() { + var kontext = new Stelle(List.of(new Stelle.Inhaltsuebersicht())); + var befehl = + erkenne( + "Die Angaben zu den §§ 34 bis § 45 werden durch die folgenden Angaben ersetzt:" + + " „§ 34 (weggefallen) § 35 (weggefallen)“.", + kontext); + + assertThat(befehl).containsInstanceOf(StrukturErsetzung.class); + var ersetzung = (StrukturErsetzung) befehl.orElseThrow(); + assertThat(ersetzung.stelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 34"); + assertThat(ersetzung.bisStelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 45"); + } + + @Test + void erkenntAngabeStreichungInDerInhaltsuebersicht() { + var befehl = + erkenne("In der Inhaltsübersicht wird die Angabe zu § 5a gestrichen.", Stelle.LEER); + + assertThat(befehl).containsInstanceOf(Aufhebung.class); + assertThat(befehl.orElseThrow().stelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 5a"); + } + + @Test + void erkenntAngabeMitOrdinalerGliederung() { + var kontext = new Stelle(List.of(new Stelle.Inhaltsuebersicht())); + var befehl = + erkenne( + "Die Angabe zum zweiten Abschnitt wird wie folgt gefasst: „2. Abschnitt" + + " Koordinierung und epidemische Lage von nationaler Tragweite“.", + kontext); + + assertThat(befehl).containsInstanceOf(Neufassung.class); + assertThat(befehl.orElseThrow().stelle().anzeigeText()).isEqualTo("Inhaltsübersicht Abschnitt 2"); + } + + @Test + void erkenntVoranstellung() { + var befehl = + erkenne( + "Der Nummer 1 wird folgende Nummer 1 vorangestellt: „1. eine Umwälzpumpe nach § 64" + + " Absatz 2 auszutauschen ist,“.", + new Stelle(List.of(new Stelle.Paragraph("64")))); + + assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class); + var einfuegung = (StrukturEinfuegung) befehl.orElseThrow(); + assertThat(einfuegung.vorher()).isTrue(); + assertThat(einfuegung.ebene()).isEqualTo(Ebene.NUMMER); + assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("§ 64 Nummer 1"); + } + + @Test + void erkenntVoranstellungOhneAnker() { + var befehl = + erkenne( + "Folgende Nummer 1 wird vorangestellt: „1. einer vollziehbaren Anordnung nach § 5" + + " Absatz 2 Nummer 1 oder 2 zuwiderhandelt,“.", + Stelle.LEER); + + assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class); + assertThat(((StrukturEinfuegung) befehl.orElseThrow()).vorher()).isTrue(); + } + + @Test + void erkenntNummernBereichsErsetzungMitKardinalitaetswechsel() { + var befehl = + erkenne( + "Satz 1 Nummer 3 bis 6 wird durch die folgenden Nummern 3 und 4 ersetzt: 3. „ bei" + + " Wärmeverteilungs- und Warmwasserleitungen die Wärmeabgabe begrenzt ist und" + + " 4. die Anforderungen eingehalten werden.“.", + new Stelle(List.of(new Stelle.Paragraph("61")))); + + assertThat(befehl).containsInstanceOf(StrukturErsetzung.class); + var ersetzung = (StrukturErsetzung) befehl.orElseThrow(); + assertThat(ersetzung.ebene()).isEqualTo(Ebene.NUMMER); + assertThat(ersetzung.stelle().anzeigeText()).isEqualTo("§ 61 Satz 1 Nummer 3"); + assertThat(ersetzung.bisStelle().anzeigeText()).isEqualTo("§ 61 Satz 1 Nummer 6"); + assertThat(ersetzung.text()).startsWith("3. "); + } + + @Test + void erkenntKommaMehrfachErsetzung() { + var befehl = + erkenne( + "In Satz 1 wird die Angabe „2025“ durch die Angabe „2030“, die Angabe „§ 50 Absatz 1" + + " in Verbindung mit § 48“ durch die Angabe „§ 38 Absatz 1 in Verbindung mit" + + " § 36“ und die Angabe „§ 50 Absatz 1“ durch die Angabe „§ 38 Absatz 1“" + + " ersetzt.", + new Stelle(List.of(new Stelle.Paragraph("109")))); + + assertThat(befehl).containsInstanceOf(Sammelbefehl.class); + var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle(); + assertThat(teile).hasSize(3).allMatch(t -> t instanceof Ersetzung); + assertThat(((Ersetzung) teile.get(0)).alt()).isEqualTo("2025"); + assertThat(((Ersetzung) teile.get(0)).neu()).isEqualTo("2030"); + } + + @Test + void erkenntMehrfachEinfuegepaare() { + var befehl = + erkenne( + "In Nummer 24 werden nach den Wörtern „einer Rechtsverordnung nach“ die Wörter" + + " „§ 5 Absatz 2 Nummer 4,“ und nach der Angabe „§ 23 Absatz 8 Satz 1“ ein" + + " Komma und die Angabe „§ 32 Satz 1“ eingefügt.", + new Stelle(List.of(new Stelle.Paragraph("73")))); + + assertThat(befehl).containsInstanceOf(Sammelbefehl.class); + var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle(); + assertThat(teile).hasSize(2).allMatch(t -> t instanceof WoerterEinfuegung); + assertThat(((WoerterEinfuegung) teile.get(1)).woerter()).isEqualTo(", § 32 Satz 1"); + } + + @Test + void erkenntKommaUndWoerterVorDemPunktAmEnde() { + var befehl = + erkenne( + "In Absatz 3 Satz 1 wird vor dem Punkt am Ende ein Komma und werden die Wörter" + + " „oder wenn der Nachweis erfolgt ist“ eingefügt.", + new Stelle(List.of(new Stelle.Paragraph("8")))); + + assertThat(befehl).containsInstanceOf(Ersetzung.class); + var ersetzung = (Ersetzung) befehl.orElseThrow(); + assertThat(ersetzung.alt()).isEqualTo("."); + assertThat(ersetzung.neu()).isEqualTo(", oder wenn der Nachweis erfolgt ist."); + assertThat(ersetzung.amEnde()).isTrue(); + } + + @Test + void erkenntKoordinierteUmnummerierung() { + var befehl = + erkenne( + "Die bisherigen Absätze 6 und 7 werden die Absätze 1 und 2.", + new Stelle(List.of(new Stelle.Paragraph("5")))); + + assertThat(befehl).containsInstanceOf(Sammelbefehl.class); + var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle(); + assertThat(teile).hasSize(2).allMatch(t -> t instanceof Umnummerierung); + // Absteigend: 7 → 2 zuerst, damit die Labels nicht kollidieren. + assertThat(teile.get(0).stelle().anzeigeText()).isEqualTo("§ 5 Absatz 7"); + } + + @Test + void erkenntVerbundMitUmnummerierungUndRueckbezug() { + var befehl = + erkenne( + "Die bisherige Nummer 1 wird Nummer 2 und in ihr werden die Wörter „§ 72 Absatz 1" + + " bis 3,“ durch die Wörter „Ablauf der Übergangsfristen,“ ersetzt.", + new Stelle(List.of(new Stelle.Paragraph("96")))); + + assertThat(befehl).containsInstanceOf(Sammelbefehl.class); + var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle(); + assertThat(teile).hasSize(2); + assertThat(teile.get(0)).isInstanceOf(Umnummerierung.class); + assertThat(teile.get(1)).isInstanceOf(Ersetzung.class); + assertThat(teile.get(1).stelle().anzeigeText()).isEqualTo("§ 96 Nummer 2"); + } + + @Test + void erkenntVerbundMitUmnummerierungUndNeufassung() { + var befehl = + erkenne( + "Die bisherige Nummer 3 wird Nummer 4 und wird wie folgt gefasst: 4. „ die" + + " Abrechnungen und Bestätigungen nach § 96 Absatz 5 vorliegen.“", + new Stelle(List.of(new Stelle.Paragraph("96")))); + + assertThat(befehl).containsInstanceOf(Sammelbefehl.class); + var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle(); + assertThat(teile.get(0)).isInstanceOf(Umnummerierung.class); + assertThat(teile.get(1)).isInstanceOf(Neufassung.class); + assertThat(((Neufassung) teile.get(1)).neuerText()).startsWith("4. "); + } + + @Test + void erkenntParagraphAnfuegungNachAnker() { + var befehl = + erkenne( + "Nach § 114 wird folgender § 115 angefügt: „§ 115 Übergangsvorschriften für" + + " Geldbußen Text.“", + Stelle.LEER); + + assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class); + var einfuegung = (StrukturEinfuegung) befehl.orElseThrow(); + assertThat(einfuegung.ebene()).isEqualTo(Ebene.PARAGRAPH); + assertThat(einfuegung.bezeichnung()).isEqualTo("115"); + } + + @Test + void erkenntAnkerloseAbsatzEinfuegung() { + var befehl = + erkenne( + "Folgender Absatz 2 wird eingefügt: „(2) In einem Wohngebäude gilt dies nicht.“", + new Stelle(List.of(new Stelle.Paragraph("72")))); + + assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class); + var einfuegung = (StrukturEinfuegung) befehl.orElseThrow(); + assertThat(einfuegung.vorher()).isFalse(); + assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("§ 72 Absatz 1"); + } + + @Test + void erkenntNummernBlockAnfuegung() { + var befehl = + erkenne( + "Die folgenden Nummern 9 bis 11 werden angefügt: 9. „ Durchführung hydraulischer" + + " Abgleiche, 10. Einbau von Messausstattungen, 11. Sonstiges.“", + new Stelle(List.of(new Stelle.Paragraph("60")))); + + assertThat(befehl).containsInstanceOf(Anfuegung.class); + var anfuegung = (Anfuegung) befehl.orElseThrow(); + assertThat(anfuegung.ebene()).isEqualTo(Ebene.NUMMER); + assertThat(anfuegung.text()).startsWith("9. "); + } + + @Test + void erkenntPunktErsetzungDurchFolgendeWoerter() { + var befehl = + erkenne( + "In Absatz 1 Satz 2 wird der Punkt am Ende durch folgende Wörter ersetzt: „, das" + + " heißt, wenn die Investitionen unangemessen sind.“", + new Stelle(List.of(new Stelle.Paragraph("102")))); + + assertThat(befehl).containsInstanceOf(Ersetzung.class); + var ersetzung = (Ersetzung) befehl.orElseThrow(); + assertThat(ersetzung.alt()).isEqualTo("."); + assertThat(ersetzung.amEnde()).isTrue(); + } + + @Test + void erkenntWortVoranstellungImVerbund() { + var befehl = + erkenne( + "In Buchstabe a werden die Wörter „des § 10“ durch die Wörter „der §§ 71 bis 71h“" + + " ersetzt, wird dem Wort „Anforderungen“ das Wort „dortigen“ vorangestellt und" + + " werden die Wörter „nach den §§ 35 bis 41“ gestrichen.", + new Stelle(List.of(new Stelle.Paragraph("105")))); + + assertThat(befehl).containsInstanceOf(Sammelbefehl.class); + var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle(); + assertThat(teile).hasSize(3); + assertThat(teile.get(1)).isInstanceOf(WoerterEinfuegung.class); + assertThat(((WoerterEinfuegung) teile.get(1)).woerter()).isEqualTo("dortigen"); + assertThat(teile.get(2)).isInstanceOf(Streichung.class); + } + + @Test + void erkenntErsetzungMitPositionsanker() { + var befehl = + erkenne( + "In Nummer 2 werden nach den Wörtern „jeweils auch in Verbindung mit“ die Wörter" + + " „einer Rechtsverordnung nach § 14,“ durch die Wörter „§ 14 Absatz 8,“" + + " ersetzt.", + new Stelle(List.of(new Stelle.Paragraph("73")))); + + assertThat(befehl).containsInstanceOf(Ersetzung.class); + var ersetzung = (Ersetzung) befehl.orElseThrow(); + assertThat(ersetzung.alt()).isEqualTo("einer Rechtsverordnung nach § 14,"); + assertThat(ersetzung.neu()).isEqualTo("§ 14 Absatz 8,"); + } } diff --git a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java index 9b9c18d..aa658dd 100644 --- a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java +++ b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java @@ -6,6 +6,7 @@ import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Anfuegung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Aufhebung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Ebene; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Ersetzung; +import eu.mulk.aendggner.aenderung.Aenderungsbefehl.GliederungsUeberschriften; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Neufassung; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Sammelbefehl; import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Streichung; @@ -642,6 +643,344 @@ class BefehlAnwenderTest { assertThat(text).contains("1. (weggefallen)").contains("2. (weggefallen)").contains("3. (weggefallen)"); } + // --- Anhang/Anlage als Norm-Ziel ----------------------------------------------------------- + + /** Ein Gesetz mit Anhang-Norm nach dem Muster des UWG (mehrere Absätze, Nummern mit Kindern). */ + private static Gesetz gesetzMitAnhang() { + return new Gesetz( + "TestG", + null, + null, + List.of( + new Norm( + "§ 1", + "Zweck", + null, + List.of(new Absatz("1", "Es gilt der Anhang.")), + false), + new Norm( + "Anhang", + "(zu § 1)", + null, + List.of( + new Absatz(null, "Folgende Handlungen sind stets unzulässig:"), + new Absatz( + null, + " 1. die erste Handlung;\n" + + " 2. die zweite Handlung,\n" + + " a) wenn sie morgens geschieht, oder\n" + + " b) wenn sie abends geschieht;\n" + + " 3. die dritte Handlung;"), + new Absatz( + null, + " 31. die aggressive Handlung,\n" + + " a) wenn sie laut geschieht, oder\n" + + " b) wenn sie leise gemacht wird.\n" + + " 32. die letzte Handlung;")), + false))); + } + + private static Stelle anhangStelle(Stelle.Komponente... feinere) { + var komponenten = new java.util.ArrayList(); + komponenten.add(new Stelle.Gliederungseinheit("Anhang", "")); + komponenten.addAll(List.of(feinere)); + return new Stelle(komponenten); + } + + @Test + void fuegtNummerImAnhangNachNummerMitKindernEin() { + // „Nach Nummer 2 wird die folgende Nummer 2a eingefügt“ — Nummer 2 hat Buchstaben a)/b); + // die neue Nummer muss hinter deren Block landen, nicht zwischen Nummer und Buchstaben. + var befehl = + new StrukturEinfuegung( + anhangStelle(new Stelle.NummerNr("2")), + false, + Ebene.NUMMER, + "2a", + "2a. die eingeschobene Handlung;", + PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetzMitAnhang(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(absatzText(ergebnis.neu(), "Anhang", 1)) + .isEqualTo( + " 1. die erste Handlung;\n" + + " 2. die zweite Handlung,\n" + + " a) wenn sie morgens geschieht, oder\n" + + " b) wenn sie abends geschieht;\n" + + " 2a. die eingeschobene Handlung;\n" + + " 3. die dritte Handlung;"); + } + + @Test + void fuegtNummernBlockImAnhangEin() { + // Mehrere Einheiten in einem Einfügeblock bleiben eigene Zeilen. + var befehl = + new StrukturEinfuegung( + anhangStelle(new Stelle.NummerNr("3")), + false, + Ebene.NUMMER, + null, + "3a. die vierte Handlung;\n3b. die fünfte Handlung;", + PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetzMitAnhang(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(absatzText(ergebnis.neu(), "Anhang", 1)) + .endsWith( + " 3. die dritte Handlung;\n" + + " 3a. die vierte Handlung;\n" + + " 3b. die fünfte Handlung;"); + } + + @Test + void loestNummerBuchstabeKetteImAnhangAuf() { + // „b)“ existiert in Nummer 2 und Nummer 31 — die Kette „Nummer 31 Buchstabe b“ ist trotzdem + // eindeutig, weil der Buchstabe im Block der Nummer 31 gesucht wird. + var befehl = + new Ersetzung( + anhangStelle(new Stelle.NummerNr("31"), new Stelle.BuchstabeNr("b")), + "gemacht wird.", + "gemacht wird;", + false, + false, + PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetzMitAnhang(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(absatzText(ergebnis.neu(), "Anhang", 2)) + .contains("wenn sie leise gemacht wird;") + .contains(" 32. die letzte Handlung;"); + // Nummer 2 Buchstabe b bleibt unangetastet. + assertThat(absatzText(ergebnis.neu(), "Anhang", 1)).contains("wenn sie abends geschieht;"); + } + + @Test + void streichtWoerterInDerUeberschriftEinerNorm() { + var befehl = + new Streichung( + stelle(new Stelle.Paragraph("3"), new Stelle.Ueberschrift()), + "Schlussvorschriften", + PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetz(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(ergebnis.neu().norm("§ 3").orElseThrow().titel()).isEmpty(); + } + + @Test + void meldetMehrdeutigeNummerOhneAbsatzangabeImAnhang() { + // Gäbe es dieselbe Nummer in mehreren Absätzen, bliebe der Befehl manuell. + var gesetz = + new Gesetz( + "TestG", + null, + null, + List.of( + new Norm( + "Anhang", + null, + null, + List.of( + new Absatz(null, " 1. erstens;"), + new Absatz(null, " 1. nochmal erstens;")), + false))); + var befehl = + new Ersetzung( + anhangStelle(new Stelle.NummerNr("1")), "erstens", "zuerst", false, false, PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetz, List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.MANUELL_PRUEFEN); + assertThat(ergebnis.protokoll().get(0).begruendung()).contains("nicht eindeutig"); + } + + // --- Inhaltsübersicht ------------------------------------------------------------------------ + + private static Gesetz gesetzMitInhaltsuebersicht() { + return new Gesetz( + "TestG", + null, + null, + List.of( + new Norm( + "Inhaltsübersicht", + null, + null, + List.of( + new Absatz( + null, + "Teil 1 | Allgemeines\n" + + "§ 1 | Zweck\n" + + "§ 2 | Begriffe\n" + + "Teil 2 | Verfahren\n" + + "Abschnitt 1 | Grundsätze\n" + + "§ 3 | Ablauf\n" + + "§ 4 | Fristen\n" + + "§ 5 | Schluss")), + false), + new Norm("§ 1", "Zweck", null, List.of(new Absatz(null, "Text.")), false))); + } + + private static Stelle iuStelle(Stelle.Komponente... feinere) { + var komponenten = new java.util.ArrayList(); + komponenten.add(new Stelle.Inhaltsuebersicht()); + komponenten.addAll(List.of(feinere)); + return new Stelle(komponenten); + } + + @Test + void fasstAngabeInDerInhaltsuebersichtNeu() { + var befehl = + new Neufassung(iuStelle(new Stelle.Paragraph("2")), "§ 2 Begriffsbestimmungen", PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetzMitInhaltsuebersicht(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(absatzText(ergebnis.neu(), "Inhaltsübersicht", 0)) + .contains("§ 2 | Begriffsbestimmungen") + .doesNotContain("§ 2 | Begriffe\n"); + } + + @Test + void fuegtAngabeInDerInhaltsuebersichtEin() { + var befehl = + new StrukturEinfuegung( + iuStelle(new Stelle.Paragraph("2")), + false, + Ebene.PARAGRAPH, + null, + "§ 2a Anwendungsbereich", + PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetzMitInhaltsuebersicht(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(absatzText(ergebnis.neu(), "Inhaltsübersicht", 0)) + .contains("§ 2 | Begriffe\n§ 2a | Anwendungsbereich\nTeil 2 | Verfahren"); + } + + @Test + void ersetztAngabenBereichInDerInhaltsuebersicht() { + // „Die Angaben zu den §§ 3 bis 4 werden durch die folgenden Angaben ersetzt: …“ + var befehl = + new StrukturErsetzung( + iuStelle(new Stelle.Paragraph("3")), + iuStelle(new Stelle.Paragraph("4")), + Ebene.PARAGRAPH, + "§ 3 (weggefallen) § 4 (weggefallen)", + PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetzMitInhaltsuebersicht(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(absatzText(ergebnis.neu(), "Inhaltsübersicht", 0)) + .contains("§ 3 | (weggefallen)\n§ 4 | (weggefallen)") + .doesNotContain("Ablauf"); + } + + @Test + void streichtAngabeUndLoestGliederungsKetteAuf() { + // „Die Angabe zu Teil 2 Abschnitt 1 wird gestrichen.“ — Kette grenzt das Fenster ein. + var befehl = + new Aufhebung( + iuStelle( + new Stelle.Gliederungseinheit("Teil", "2"), + new Stelle.Gliederungseinheit("Abschnitt", "1")), + PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetzMitInhaltsuebersicht(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(absatzText(ergebnis.neu(), "Inhaltsübersicht", 0)) + .doesNotContain("Abschnitt 1 | Grundsätze") + .contains("Teil 2 | Verfahren\n§ 3 | Ablauf"); + } + + // --- Gliederungs-Überschriften --------------------------------------------------------------- + + private static Gesetz gesetzMitGliederungen() { + var teil1 = new Gliederung("010", "Teil 1", "Allgemeines"); + var teil2 = new Gliederung("020", "Teil 2", "Anforderungen"); + return new Gesetz( + "TestG", + null, + null, + List.of( + new Norm("§ 1", "Zweck", teil1, List.of(new Absatz(null, "Eins.")), false), + new Norm("§ 2", "Begriffe", teil1, List.of(new Absatz(null, "Zwei.")), false), + new Norm("§ 3", "Pflichten", teil2, List.of(new Absatz(null, "Drei.")), false), + new Norm("§ 4", "Nachweise", teil2, List.of(new Absatz(null, "Vier.")), false)), + List.of(teil1, teil2)); + } + + @Test + void fuegtGliederungsUeberschriftenEin() { + // „Nach § 2 werden die folgenden Überschriften zu Teil 3 und zu Teil 3 Abschnitt 1 + // eingefügt: „Teil 3 Modernisierung Abschnitt 1 Grundpflichten“.“ + var befehl = + new GliederungsUeberschriften( + stelle(new Stelle.Paragraph("2")), + List.of( + new Stelle.Gliederungseinheit("Teil", "3"), + new Stelle.Gliederungseinheit("Abschnitt", "1")), + List.of(), + "Teil 3 Modernisierung Abschnitt 1 Grundpflichten", + PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetzMitGliederungen(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(ergebnis.neu().gliederungen()) + .extracting(Gliederung::bezeichnung) + .containsExactly("Teil 1", "Teil 3", "Abschnitt 1", "Teil 2"); + var abschnitt1 = ergebnis.neu().gliederungen().get(2); + assertThat(abschnitt1.titel()).isEqualTo("Grundpflichten"); + // §§ 3 und 4 (der zusammenhängende Block nach dem Anker) hängen jetzt unter Abschnitt 1. + assertThat(ergebnis.neu().norm("§ 3").orElseThrow().gliederung()).isEqualTo(abschnitt1); + assertThat(ergebnis.neu().norm("§ 4").orElseThrow().gliederung()).isEqualTo(abschnitt1); + assertThat(ergebnis.neu().norm("§ 2").orElseThrow().gliederung().bezeichnung()) + .isEqualTo("Teil 1"); + } + + @Test + void ersetztGliederungsUeberschriften() { + // „Die bisherigen Überschriften zu Teil 2 werden durch die folgende Überschrift zu + // Abschnitt 2 ersetzt: „Abschnitt 2 Neue Anforderungen“.“ + var befehl = + new GliederungsUeberschriften( + Stelle.LEER, + List.of(new Stelle.Gliederungseinheit("Abschnitt", "2")), + List.of(List.of(new Stelle.Gliederungseinheit("Teil", "2"))), + "Abschnitt 2 Neue Anforderungen", + PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetzMitGliederungen(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(ergebnis.neu().gliederungen()) + .extracting(Gliederung::bezeichnung) + .containsExactly("Teil 1", "Abschnitt 2"); + assertThat(ergebnis.neu().norm("§ 3").orElseThrow().gliederung().titel()) + .isEqualTo("Neue Anforderungen"); + } + + @Test + void ersetztGesetzesUeberschrift() { + var befehl = + new Neufassung( + stelle(new Stelle.Ueberschrift()), "Gesetz zur gründlichen Erprobung", PROV); + + var ergebnis = BefehlAnwender.anwenden(gesetz(), List.of(befehl)); + + assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT); + assertThat(ergebnis.neu().langue()).isEqualTo("Gesetz zur gründlichen Erprobung"); + } + private static String absatzText(Gesetz gesetz, String enbez, int index) { return gesetz.norm(enbez).orElseThrow().absaetze().get(index).text(); } -- cgit v1.2.1