aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FASSUNGEN.txt39
-rw-r--r--README.md10
-rw-r--r--src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java36
-rw-r--r--src/main/java/eu/mulk/aendggner/anwendung/Satznummerierung.java90
-rw-r--r--src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java43
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java87
6 files changed, 265 insertions, 40 deletions
diff --git a/FASSUNGEN.txt b/FASSUNGEN.txt
index b739390..393f6e9 100644
--- a/FASSUNGEN.txt
+++ b/FASSUNGEN.txt
@@ -154,6 +154,45 @@ Tage gegolten hat. Bislang tat das Erzeugnis eben dies, und zwar schweigend.
(8) Der Prüfbestand umfasst dreihundertachtundvierzig Prüfungen.
+Artikel 6
+Beseitigung dreier benannter Mängel
+
+Die nachstehenden drei Mängel waren in den Prüfungen einzeln begründet festge-
+halten. Sie sind sämtlich allgemeiner Natur und nicht Eigenheiten ihrer Belegfälle.
+
+(1) Auch eine Aufhebung räumt eine Bezeichnung. Die Ordnung der Anwendungs-
+ schritte kannte bislang allein Umnummerierungen als Räumende. Im Gebäude-
+ energiegesetz (§ 108 Absatz 1) macht ein Punkt der Kaskade aus den bisherigen
+ Nummern 4 bis 6 die Nummern 8 bis 10, während ein anderer die bisherige
+ Nummer 9 aufhebt; die Aufhebung suchte danach eine Nummer 9, die inzwischen
+ eine andere Einheit war. Sie läuft fortan vor der Umnummerierung. Dass eine
+ Aufhebung mitunter einen Platzhalter hinterlässt und dann nichts freigibt,
+ steht erst am Bestand fest und schadet nicht: Vorrang erhält sie nur gegenüber
+ Schritten, die dieselbe Bezeichnung neu vergeben.
+
+(2) Ein Zwischentitel ist kein Teil des vorangehenden Absatzes. Eine Zeile, die
+ auf einen Doppelpunkt endet, keine Absatzbezeichnung trägt und der ein
+ bezeichneter Absatz folgt, bildet fortan einen eigenen, bezeichnungslosen
+ Absatz (LandesRechtTextParser). Bislang wurde sie dem vorangehenden Absatz
+ zugeschlagen; dessen Text endete dann nicht dort, wo er endet, und die
+ Anweisung „In Absatz 4a wird der Punkt am Ende durch ein Semikolon ersetzt“
+ fand ihren Punkt nicht.
+
+(3) Neu eingesetzter Wortlaut erbt die amtliche Satzzählung (Satznummerierung).
+ Führt das Stammgesetz sie, so trägt der aus dem Gesetzblatt zitierte Text sie
+ gleichwohl nicht — sie gehört zur Fassung, nicht zum Verkündungstext.
+ Angefasst wird nur, was angefasst werden muss: ein geänderter Absatz, der
+ keine Zählung trägt, aus mehr als einem Satz besteht und auf einer Zeile
+ steht; die Mehrzeiligkeit schließt Aufzählungen aus.
+
+(4) Damit sind das Gebäudeenergiegesetz mit einhundertsiebzehn angewandten
+ Befehlen und zwei Resten (zuvor einhundertsechzehn und drei) und das Berliner
+ Allgemeine Sicherheits- und Ordnungsgesetz mit sämtlichen sechs Befehlen und
+ allen einhunderteinundsiebzig Normen gleich der amtlichen Nachfassung
+ ausgewiesen; die Beschlussempfehlung zum Gebäudeenergiegesetz steigt von
+ neunundsechzig auf siebzig angewandte Befehle.
+
+
════════════════════════════════════════════════════════════════════════════════
Fassung vom 23. August 2026,
zuletzt geändert durch die am 23. August 2026 vorgenommenen Änderungen
diff --git a/README.md b/README.md
index 1dcbb29..5a0e64c 100644
--- a/README.md
+++ b/README.md
@@ -275,9 +275,15 @@ sie neu besetzt.
(2) Aus Absatz 1 folgt
1. die absteigende Abarbeitung einer aufsteigenden Kaskade („Der bisherige
- Absatz 3 wird Absatz 4“, „Der bisherige Absatz 4 wird Absatz 5“, …) sowie
+ Absatz 3 wird Absatz 4“, „Der bisherige Absatz 4 wird Absatz 5“, …),
2. der Vorrang einer Umnummerierung vor derjenigen Einfügung, die deren
- Bezeichnung neu vergibt.
+ Bezeichnung neu vergibt, sowie
+3. der Vorrang einer Aufhebung vor demjenigen Schritt, der die aufgehobene
+ Bezeichnung neu vergibt. Auch die Aufhebung räumt nämlich eine Bezeichnung;
+ dass sie mitunter einen Platzhalter hinterlässt („9. (weggefallen)“) und dann
+ gerade nichts freigibt, steht erst am Bestand fest und schadet nicht: Vorrang
+ erhält sie nur gegenüber Schritten, die dieselbe Bezeichnung neu vergeben, und
+ eine zugleich weggefallene und neu vergebene Bezeichnung gibt es nicht.
Wer vorrückt, nimmt dabei seine eigenen Räumer mit: Auch eine über mehrere Punkte
verschränkte Kaskade („Die bisherige Nr. 15 wird Nr. 16“, „Die bisherigen Nrn. 13
diff --git a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
index cccba63..15fe2ed 100644
--- a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
+++ b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
@@ -111,7 +111,13 @@ public final class BefehlAnwender {
}
protokoll.addAll(verdichte(befehle, schritte, ergebnisse));
- var neu = alt.mitNormen(normen).mitGliederungen(gliederungen);
+ // Führt das Gesetz amtliche Satznummern, so erbt sie auch der neu eingesetzte Wortlaut: Das
+ // Gesetzblatt zitiert ohne Zählung, die Fassung führt sie.
+ var fortgeschrieben =
+ Satznummerierung.fuehrtSatznummern(alt)
+ ? Satznummerierung.schreibeFort(alt, normen)
+ : normen;
+ var neu = alt.mitNormen(fortgeschrieben).mitGliederungen(gliederungen);
if (neuerLangtitel != null) {
neu = neu.mitLangue(neuerLangtitel);
}
@@ -359,15 +365,41 @@ public final class BefehlAnwender {
befehl, Status.MANUELL_PRUEFEN, String.join(" ", fehler), betroffene, ersterGrund);
}
- /** Bezeichnungen, die ein Befehl freigibt — die Ausgangsstellen seiner Umnummerierungen. */
+ /**
+ * Bezeichnungen, die ein Befehl freigibt — die Ausgangsstellen seiner Umnummerierungen und die
+ * Stellen seiner Aufhebungen.
+ *
+ * <p>Dass auch eine Aufhebung räumt, ist nicht selbstverständlich, denn sie hinterlässt vielfach
+ * einen Platzhalter („9. (weggefallen)“) und gibt die Bezeichnung dann gerade nicht frei. Ob das
+ * geschieht, steht erst am Bestand fest ({@code haeltPlatz}), also nach der Ordnung. Vorgezogen
+ * wird die Aufhebung gleichwohl: Sie rückt nur gegenüber Schritten vor, die dieselbe Bezeichnung
+ * neu vergeben — und wo eine Bezeichnung zugleich weggefallen und neu vergeben sein soll,
+ * widerspräche das Gesetz sich selbst. So ist es im GEG (§ 108 Absatz 1): Die Kaskade macht die
+ * bisherigen Nummern 4 bis 6 zu 8 bis 10, während die bisherige Nummer 9 aufgehoben wird. Läuft
+ * die Aufhebung nach der Umnummerierung, sucht sie eine Nummer 9, die inzwischen eine andere
+ * Einheit ist.
+ */
private static Set<String> geraeumteBezeichnungen(Aenderungsbefehl befehl) {
var raeumt = new LinkedHashSet<String>();
for (var u : umnummerierungen(befehl)) {
raeumt.add(u.stelle().anzeigeText());
}
+ for (var a : aufhebungen(befehl)) {
+ raeumt.add(a.stelle().anzeigeText());
+ }
return raeumt;
}
+ /** Die Aufhebungen eines Befehls — auch die in einem Verbund. */
+ private static List<Aufhebung> aufhebungen(Aenderungsbefehl befehl) {
+ return switch (befehl) {
+ case Aufhebung a -> List.of(a);
+ case Sammelbefehl s ->
+ s.teilbefehle().stream().flatMap(t -> aufhebungen(t).stream()).toList();
+ default -> List.of();
+ };
+ }
+
/** Bezeichnungen, die ein Befehl neu vergibt — Umnummerierungsziele und eingefügte Einheiten. */
private static Set<String> belegteBezeichnungen(Aenderungsbefehl befehl) {
var belegt = new LinkedHashSet<String>();
diff --git a/src/main/java/eu/mulk/aendggner/anwendung/Satznummerierung.java b/src/main/java/eu/mulk/aendggner/anwendung/Satznummerierung.java
new file mode 100644
index 0000000..5165546
--- /dev/null
+++ b/src/main/java/eu/mulk/aendggner/anwendung/Satznummerierung.java
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: 2020 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+package eu.mulk.aendggner.anwendung;
+
+import eu.mulk.aendggner.gesetz.Absatz;
+import eu.mulk.aendggner.gesetz.Gesetz;
+import eu.mulk.aendggner.gesetz.Norm;
+import eu.mulk.aendggner.gesetz.Superskript;
+import java.util.ArrayList;
+import java.util.List;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Amtliche Satznummern für neu gesetzten Text.
+ *
+ * <p>Ein Teil der Länder zählt die Sätze eines Absatzes amtlich mit („(2) ¹Gegen einen … ²In diesem
+ * Fall …“). Das Gesetzblatt, aus dem der neue Wortlaut zitiert wird, setzt diese Zählung nicht —
+ * sie gehört zur Fassung, nicht zum Verkündungstext. Wer den Zitatwortlaut unverändert einsetzt,
+ * erhält deshalb einen Absatz, der als einziger seines Gesetzes ohne Satzzählung dasteht; die
+ * amtliche Nachfassung führt sie (Berlin, ASOG § 67 Absatz 2).
+ *
+ * <p>Angefasst wird nur, was angefasst werden muss: ein Absatz, der sich geändert hat, keine
+ * Zählung trägt, aus mehr als einem Satz besteht und auf einer Zeile steht. Die Mehrzeiligkeit
+ * schließt Aufzählungen aus — dort ist der Chapeau ein Satz und jedes Glied ein eigener Block, und
+ * eine fortlaufende Satzzählung wäre falsch.
+ */
+final class Satznummerierung {
+
+ private Satznummerierung() {}
+
+ /** Wahr, wenn das Gesetz die Sätze seiner Absätze amtlich zählt. */
+ static boolean fuehrtSatznummern(Gesetz gesetz) {
+ for (var norm : gesetz.normen()) {
+ for (var absatz : norm.absaetze()) {
+ if (Superskript.traegtSatznummern(absatz.text())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Schreibt die Satzzählung in den geänderten Absätzen fort. */
+ static List<Norm> schreibeFort(Gesetz alt, List<Norm> normen) {
+ var ergebnis = new ArrayList<Norm>(normen.size());
+ for (var norm : normen) {
+ var alteNorm = alt.norm(norm.enbez()).orElse(null);
+ var absaetze = new ArrayList<Absatz>(norm.absaetze().size());
+ boolean geaendert = false;
+ for (var absatz : norm.absaetze()) {
+ var neuerText = fortgeschrieben(absatz, alteNorm);
+ geaendert |= !neuerText.equals(absatz.text());
+ absaetze.add(neuerText.equals(absatz.text()) ? absatz : absatz.mitText(neuerText));
+ }
+ ergebnis.add(geaendert ? norm.mitAbsaetzen(absaetze) : norm);
+ }
+ return ergebnis;
+ }
+
+ private static String fortgeschrieben(Absatz absatz, @Nullable Norm alteNorm) {
+ var text = absatz.text();
+ if (text.contains("\n") || Superskript.traegtSatznummern(text)) {
+ return text;
+ }
+ if (alteNorm != null && trugDenselbenText(alteNorm, absatz)) {
+ return text;
+ }
+ var saetze = SatzTeiler.teileTexte(text);
+ if (saetze.size() < 2) {
+ return text;
+ }
+ var sb = new StringBuilder();
+ for (int i = 0; i < saetze.size(); i++) {
+ if (i > 0) {
+ sb.append(' ');
+ }
+ sb.append(Superskript.zuSuperskript(String.valueOf(i + 1))).append(saetze.get(i));
+ }
+ return sb.toString();
+ }
+
+ private static boolean trugDenselbenText(Norm alteNorm, Absatz absatz) {
+ for (var alterAbsatz : alteNorm.absaetze()) {
+ if (java.util.Objects.equals(alterAbsatz.nummer(), absatz.nummer())) {
+ return alterAbsatz.text().equals(absatz.text());
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
index ae1e861..d1d4002 100644
--- a/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
+++ b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
@@ -411,6 +411,34 @@ final class LandesRechtTextParser {
return false;
}
+ /**
+ * Ein Zwischentitel gliedert einen Katalog, ohne selbst eine Vorschrift zu sein: „Aus dem Bereich
+ * Verkehr:“. Er steht für sich auf einer Zeile und endet auf einem Doppelpunkt.
+ */
+ private static boolean istZwischentitel(String zeile) {
+ return zeile.endsWith(":")
+ && !ABSATZ_MARKER.matcher(zeile).find()
+ && !AUFZAEHLUNGS_MARKE.matcher(zeile).find();
+ }
+
+ /**
+ * Wahr, wenn auf die Zeile {@code index} ein bezeichneter Absatz folgt. Nur dann ist ein
+ * Doppelpunkt am Zeilenende ein Zwischentitel und kein Chapeau, der seine Aufzählung einleitet.
+ */
+ private static boolean folgtAbsatz(List<String> zeilen, int index) {
+ for (int i = index + 1; i < zeilen.size(); i++) {
+ var naechste = zeilen.get(i).strip();
+ if (naechste.isEmpty()) {
+ continue;
+ }
+ return ABSATZ_MARKER.matcher(naechste).find();
+ }
+ return false;
+ }
+
+ private static final Pattern AUFZAEHLUNGS_MARKE =
+ Pattern.compile("^(?:\\d+[a-z]?\\.|[a-z]{1,2}\\))\\s");
+
private static Norm baueNorm(
String enbez, @Nullable String titel, @Nullable Gliederung gliederung, List<String> zeilen) {
boolean weggefallen = titel != null && WEGGEFALLEN_TITEL.matcher(titel).matches();
@@ -418,11 +446,24 @@ final class LandesRechtTextParser {
var absaetze = new ArrayList<Absatz>();
String absatzNummer = null;
var absatzZeilen = new ArrayList<String>();
- for (var zeile : zeilen) {
+ for (int i = 0; i < zeilen.size(); i++) {
+ var zeile = zeilen.get(i);
var gestutzt = zeile.strip();
if (gestutzt.isEmpty()) {
continue;
}
+ if (istZwischentitel(gestutzt) && !absatzZeilen.isEmpty() && folgtAbsatz(zeilen, i)) {
+ // Ein Zwischentitel trägt keine Absatzbezeichnung und gehört doch nicht zum
+ // vorangehenden Absatz: Dessen Text endete sonst nicht dort, wo er endet — und ein
+ // Befehl wie „In Absatz 4a wird der Punkt am Ende durch ein Semikolon ersetzt“ fände
+ // seinen Punkt nicht. Er wird deshalb ein eigener, bezeichnungsloser Absatz, wie ihn
+ // der Vorspann einer Norm ohnehin bildet.
+ absaetze.add(new Absatz(absatzNummer, String.join("\n", absatzZeilen)));
+ absatzZeilen.clear();
+ absatzNummer = null;
+ absaetze.add(new Absatz(null, gestutzt));
+ continue;
+ }
var fussnote = FUSSNOTE.matcher(gestutzt);
if (fussnote.matches()) {
// Fußnoten verbleiben superskript-normalisiert als eigene Zeile im tragenden Absatz.
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 10cfb8d..e1da584 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -251,16 +251,15 @@ class EndToEndTest {
*
* <p>Gegen die heutige konsolidierte Fassung ({@link #gegGrossesAenderungsgesetz}) blieben 51 von
* 119 Befehlen liegen, und der Test konnte deshalb keine Sollzahl pinnen. Gegen den Stand vom 24.
- * April 2023 sind es <b>116 angewandte und drei Reste</b> — und diese drei sind erstmals echte
- * Befunde, die hier einzeln festgehalten sind:
+ * April 2023 sind es <b>117 angewandte und zwei Reste</b>, und diese zwei sind echte Befunde:
+ *
+ * <p>Der dritte Rest ist keiner mehr: {@code 40. a) ff)} („Die bisherige Nummer 9 wird
+ * aufgehoben.“) scheiterte daran, dass Punkt bb) derselben Kaskade aus den bisherigen Nummern 4
+ * bis 6 die Nummern 8 bis 10 macht — vorübergehend trugen zwei Einheiten die Bezeichnung 9. Seit
+ * auch eine Aufhebung als Räumende zählt, läuft sie vor der Umnummerierung, und § 108 Absatz 1
+ * trägt danach die lückenlose Folge der Nummern 1 bis 32.
*
* <ol>
- * <li>{@code 40. a) ff)} „Die bisherige Nummer 9 wird aufgehoben.“ — In derselben Kaskade macht
- * Punkt bb) aus den bisherigen Nummern 4 bis 6 die Nummern 8 bis 10. Damit tragen
- * vorübergehend zwei Einheiten die Bezeichnung 9, und die Aufhebung findet keine
- * eindeutige. Die Schritt-Ordnung kennt bislang nur Umnummerierungen als Räumende; ein
- * Schritt, dessen <em>Ziel</em> eine Bezeichnung ist, die ein anderer neu vergibt, müsste
- * ebenso vorgezogen werden.
* <li>{@code 40. a) hh)} „Die bisherige Nummer 18 wird Nummer 29 und nach der Angabe ‚Absatz 1‘
* werden die Wörter ‚oder Absatz 4‘ eingefügt.“ — Die Begleitklausel löst norm-weit auf
* (bewusst so, siehe {@code BefehlErkenner}) und trifft dort 21 Vorkommen. Gemeint ist die
@@ -291,8 +290,23 @@ class EndToEndTest {
.filter(a -> a.status() == BefehlAnwender.Status.MANUELL_PRUEFEN)
.map(a -> a.befehl().provenienz().gliederungsPfad())
.toList();
- assertThat(manuellPfade).containsExactly("40. a) ff)", "40. a) hh)", "43. b) aa)");
- assertThat(anwendung.anzahlAngewandt()).isEqualTo(116);
+ assertThat(manuellPfade).containsExactly("40. a) hh)", "43. b) aa)");
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(117);
+
+ // Die Kaskade des § 108 Absatz 1 geht lückenlos auf: 32 Nummern, keine doppelt, keine fehlend.
+ var nummern =
+ anwendung
+ .neu()
+ .norm("§ 108")
+ .orElseThrow()
+ .absaetze()
+ .get(0)
+ .text()
+ .lines()
+ .map(z -> z.strip().split("\\.")[0])
+ .filter(z -> z.matches("\\d+"))
+ .toList();
+ assertThat(nummern).hasSize(32).endsWith("32");
var neu = anwendung.neu();
// Der neue § 9a steht im Gesetz und in der Inhaltsübersicht.
@@ -929,12 +943,11 @@ class EndToEndTest {
* Spaltenspikes fest — die Spaltenreihenfolge des Inhaltsstroms ist bereits die Lesereihenfolge,
* eine koordinatenbasierte Spaltenerkennung ist dafür nicht nötig.
*
- * <p>Artikel 2 (LAF-Errichtungsgesetz) ist ein voller Akzeptanzfall: beide Befehle angewandt,
- * alle fünf Normen gleich der amtlichen Nachfassung. Für Artikel 1 (ASOG) reicht die Prüfung bis
- * zur Befehlserkennung. Fünf seiner sechs Befehle zielen auf die <em>Anlage</em>, deren Einheiten
- * dort nicht als Aufzählungsmarken, sondern als Überschriften gesetzt sind („Nummer 6“, „Nummer
- * 23“); sie brauchen einen eigenen Rang zwischen Norm und Absatz, den das Datenmodell noch nicht
- * führt. Die Begründung im einzelnen steht in {@code Berlin/SOURCES}.
+ * <p>Beide Artikel sind volle Akzeptanzfälle: Artikel 2 (LAF-Errichtungsgesetz) mit beiden
+ * Befehlen und allen fünf Normen, Artikel 1 (ASOG) mit allen sechs Befehlen und allen 171 Normen
+ * gleich der amtlichen Nachfassung. Fünf der sechs Befehle zielen auf die <em>Anlage</em>, deren
+ * Einheiten dort nicht als Aufzählungsmarken, sondern als Überschriften gesetzt sind („Nummer 6“,
+ * „Nummer 23“) — sie werden als eigene Normen geführt.
*/
@Test
void asogLafAendGBerlin() throws Exception {
@@ -1019,20 +1032,23 @@ class EndToEndTest {
.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);
+ // Kein Rest mehr: Der Zwischentitel „Aus dem Bereich Verkehr:“ trägt keine Absatz-
+ // bezeichnung und wurde bislang dem vorangehenden Absatz zugeschlagen; dessen Text endete
+ // dann nicht auf den Punkt, den „In Absatz 4a wird der Punkt am Ende durch ein Semikolon
+ // ersetzt“ meint. Er ist jetzt ein eigener, bezeichnungsloser Absatz.
+ assertThat(manuellPfade).isEmpty();
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(6);
// 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();
+ // Die bezeichnungslosen Einträge sind der Vorspann und die Zwischentitel des Katalogs („Aus
+ // dem Bereich Verkehr:“); sie tragen keine Absatzbezeichnung und stehen deshalb für sich.
assertThat(nummer23.absaetze())
.extracting(Absatz::nummer)
- .containsExactly(null, "1", "2", "3", "4", "4a", "5", "6", "7", "8", "9", "10");
+ .containsExactly(
+ null, null, "1", "2", "3", "4", "4a", "5", null, "6", "7", null, "8", "9", null, "10");
// Norm für Norm gegen die amtliche Nachfassung vom 12. Juni 2026. Es bleiben genau zwei
// benannte Abweichungen — keine davon in der Anwendung begründet:
@@ -1050,13 +1066,14 @@ class EndToEndTest {
abweichend.add(normSoll.enbez());
}
}
- // Zwei benannte Abweichungen, keine davon in der Anwendung begründet:
+ // Keine Abweichung mehr: Alle 171 Normen gleichen der amtlichen Nachfassung.
//
- // § 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.
- assertThat(abweichend).containsExactly("§ 67", "Anlage Nummer 23");
+ // § 67 trug die beiden zuvor benannten Abweichungen: Das Portal setzt in der neuen Fassung
+ // amtliche Satznummern („(2) ¹Gegen einen …“), das Gesetzblatt, aus dem der Wortlaut
+ // stammt, setzt keine — die Zählung wird jetzt beim Einsetzen fortgeschrieben. Und in der
+ // Anlage Nummer 23 steht das Semikolon, weil der Zwischentitel den Absatz nicht mehr
+ // verlängert.
+ assertThat(abweichend).isEmpty();
// 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
@@ -1596,7 +1613,7 @@ class EndToEndTest {
// 69 statt der früheren 68: Nennt der Rahmen dieselbe Gliederungseinheit wie der Befehl („…
// Teil 2 wird wie folgt geändert: … die Angabe zur Überschrift von Teil 2 Abschnitt 4 …“),
// so gilt sie jetzt einmal statt zweimal (InhaltsuebersichtAnwender.zielKette).
- assertThat(ausEmpfehlung.anzahlAngewandt()).isEqualTo(69);
+ assertThat(ausEmpfehlung.anzahlAngewandt()).isEqualTo(70);
assertThat(ausEmpfehlung.anzahlAngewandt() + ausEmpfehlung.anzahlManuell())
.as("die Ausschussfassung trägt mehr Befehle als der Entwurf")
.isGreaterThan(ausEntwurf.anzahlAngewandt() + ausEntwurf.anzahlManuell());
@@ -1704,20 +1721,20 @@ class EndToEndTest {
assumeTrue(Files.exists(xml) && Files.exists(pdf), "GEG-Beispieldaten fehlen");
var vollstaendig = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false);
- assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(116);
- assertThat(vollstaendig.anzahlManuell()).isEqualTo(3);
+ assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(117);
+ assertThat(vollstaendig.anzahlManuell()).isEqualTo(2);
var anfang2024 =
Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2024, 1, 1));
- assertThat(anfang2024.anzahlAngewandt()).isEqualTo(115);
- assertThat(anfang2024.anzahlManuell()).isEqualTo(3);
+ assertThat(anfang2024.anzahlAngewandt()).isEqualTo(116);
+ assertThat(anfang2024.anzahlManuell()).isEqualTo(2);
assertThat(anfang2024.html())
.contains("Am Stichtag noch nicht in Kraft")
.contains("Tritt erst am 1. Oktober 2024 in Kraft");
var oktober2024 =
Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2024, 10, 1));
- assertThat(oktober2024.anzahlAngewandt()).isEqualTo(116);
+ assertThat(oktober2024.anzahlAngewandt()).isEqualTo(117);
}
/**