aboutsummaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-25 22:33:11 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-25 22:33:11 +0200
commitf3e9ae405ee64c98684d99966d75bb90367adc44 (patch)
tree03d455fdde06b81611d81418b452416cba1d1f65 /src/test
parent22f19c3afc4d51d7714647c5635bb068cf32d433 (diff)
Die Überschrift einer Nummer ist die Kopfzeile ihres Blocks
Für die Anlagen-Nummern des Bundesrechts war eine offene Modellfrage vermerkt: Ob die Nummern einer Anlage im gii-XML ebenso zu eigenen Normen zu zerlegen seien wie im Landesrecht. Sie war falsch gestellt. Der Stellenaufloeser probiert längst beide Wege — die Nummer als eigene Norm und, wenn es die nicht gibt, als Marke im Wortlaut —, und beide tragen. Der Fehler saß anderswo, und er war schlimmer als eine Lücke. Der Überschrift-Zweig verzweigte bei jeder Stelle, die eine Überschrift nennt, unbedingt auf den Titel der Norm und verwarf dabei jede feinere Komponente schweigend. „In der Überschrift der Nummer 1 der Anlage 8 werden die Wörter … gestrichen“ arbeitete deshalb am Titel der Anlage 8. Dass es hier beim Rest blieb, lag allein daran, dass der Zieltext dort zufällig nicht stand; stünde er dort, so hätte das Werkzeug die falsche Einheit geändert, ohne dass es jemand erfahren hätte. Welche Überschrift gemeint ist, entscheidet fortan nicht der Wortlaut des Befehls, sondern die Auflösung der Stelle ohne die Überschrift-Komponente. Löst sie auf eine ganze Norm auf, so ist deren Titel gemeint — der Regelfall, und ebenso dort, wo die Nummer einer Anlage eine eigene Norm ist. Löst sie auf einen Textbereich auf, so ist dessen Kopfzeile gemeint: Sie trägt Marke und Überschrift, die Kindzeilen sind ihr Inhalt. Ein Block aus einer einzigen Zeile hat keine Überschrift, sondern nur Text; dort wird gerügt statt angewandt. Die Neufassung einer solchen Kopfzeile bleibt eine bewusst gezogene Grenze und wird nunmehr als solche gerügt: Die Kopfzeile trägt zugleich die Aufzählungsmarke, die eine Neufassung nicht mitliefert. Bisher bog dieser Zweig still auf den Normtitel um. Geprüft: 378 Tests (zuvor 375). GEG 118 von 119 Befehlen (zuvor 117); die Überschrift der Nummer 1 der Anlage 8 ist gestutzt, der Titel der Anlage und der gleichlautende Buchstabe a) darunter sind es nicht. Change-Id: I2b8a714b1e721d01053e314f52700ca95966fc4e
Diffstat (limited to 'src/test')
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java48
-rw-r--r--src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java111
2 files changed, 140 insertions, 19 deletions
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 91a0cca..7a6dcee 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -261,16 +261,17 @@ class EndToEndTest {
* 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) 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
- * soeben umnummerierte Einheit.
- * <li>{@code 43. b) aa)} „In der Überschrift werden die Wörter … gestrichen.“ — Ziel ist die
- * Überschrift der <em>Nummer 1 der Anlage 8</em>. Das ist dieselbe offene Modellfrage wie
- * bei Berlins Anlage: Die Nummern einer Anlage brauchen einen eigenen Rang zwischen Norm
- * und Absatz.
- * </ol>
+ * <p>Auch {@code 43. b) aa)} ist keiner mehr („In der Überschrift werden die Wörter … in den
+ * Fällen des § 69 und § 71 Absatz 1 … gestrichen.“). Ziel ist die Überschrift der <em>Nummer 1
+ * der Anlage 8</em>, und es brauchte dafür keinen eigenen Rang zwischen Norm und Absatz: Im
+ * gii-XML ist die Überschrift einer Anlagen-Nummer die Kopfzeile ihres Aufzählungsblocks. Der
+ * Fehler saß darin, dass der Überschrift-Zweig die feinere Angabe schweigend verwarf und stets
+ * auf den Normtitel griff.
+ *
+ * <p>Ein Rest bleibt: {@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 soeben umnummerierte Einheit.
*/
@Test
void gegGegenZeitrichtigenStamm() throws Exception {
@@ -292,8 +293,8 @@ class EndToEndTest {
.filter(a -> a.status() == BefehlAnwender.Status.MANUELL_PRUEFEN)
.map(a -> a.befehl().provenienz().gliederungsPfad())
.toList();
- assertThat(manuellPfade).containsExactly("40. a) hh)", "43. b) aa)");
- assertThat(anwendung.anzahlAngewandt()).isEqualTo(117);
+ assertThat(manuellPfade).containsExactly("40. a) hh)");
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(118);
// Die Kaskade des § 108 Absatz 1 geht lückenlos auf: 32 Nummern, keine doppelt, keine fehlend.
var nummern =
@@ -311,6 +312,15 @@ class EndToEndTest {
assertThat(nummern).hasSize(32).endsWith("32");
var neu = anwendung.neu();
+ // Die Überschrift der Nummer 1 der Anlage 8 ist geändert — und nur sie: Der Titel der Anlage
+ // trägt seinen eigenen Wortlaut unversehrt, den Punkt 43. a) ihm gegeben hat.
+ var anlage8 = neu.norm("Anlage 8").orElseThrow();
+ assertThat(anlage8.gesamtText())
+ .contains("1. Wärmedämmung von Wärmeverteilungs- und Warmwasserleitungen sowie Armaturen")
+ .doesNotContain("sowie Armaturen in den Fällen des § 69 und § 71 Absatz 1");
+ assertThat(anlage8.titel())
+ .contains("Anforderungen an die Wärmedämmung von Rohrleitungen und Armaturen");
+
// Der neue § 9a steht im Gesetz und in der Inhaltsübersicht.
assertThat(neu.norm("§ 9a")).isPresent();
assertThat(neu.norm("Inhaltsübersicht").orElseThrow().gesamtText())
@@ -1702,8 +1712,8 @@ class EndToEndTest {
/**
* Dasselbe am großen Fall, und zugleich die Probe auf die Schritt-Ordnung: Im GEG bleibt am 1.
* Januar 2024 allein der Befehl der Nummer 22 zurück. Er steht in keiner Umnummerierungs-Kaskade,
- * sodass die übrigen 115 unverändert durchlaufen — die drei liegengebliebenen Befehle sind
- * dieselben wie ohne Stichtag und werden von der Auswahl nicht vermehrt.
+ * sodass die übrigen 116 unverändert durchlaufen — die liegengebliebenen Befehle sind dieselben
+ * wie ohne Stichtag und werden von der Auswahl nicht vermehrt.
*/
@Test
void gegStichtagLaesstDieUebrigenBefehleUnberuehrt() throws Exception {
@@ -1712,14 +1722,14 @@ class EndToEndTest {
assumeTrue(Files.exists(xml) && Files.exists(pdf), "GEG-Beispieldaten fehlen");
var vollstaendig = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(xml, List.of(pdf)));
- assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(117);
- assertThat(vollstaendig.anzahlManuell()).isEqualTo(2);
+ assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(118);
+ assertThat(vollstaendig.anzahlManuell()).isEqualTo(1);
var anfang2024 =
Pipeline.erzeugeSynopse(
Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2024, 1, 1)));
- assertThat(anfang2024.anzahlAngewandt()).isEqualTo(116);
- assertThat(anfang2024.anzahlManuell()).isEqualTo(2);
+ assertThat(anfang2024.anzahlAngewandt()).isEqualTo(117);
+ assertThat(anfang2024.anzahlManuell()).isEqualTo(1);
assertThat(anfang2024.html())
.contains("Am Stichtag noch nicht in Kraft")
.contains("Tritt erst am 1. Oktober 2024 in Kraft");
@@ -1727,7 +1737,7 @@ class EndToEndTest {
var oktober2024 =
Pipeline.erzeugeSynopse(
Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2024, 10, 1)));
- assertThat(oktober2024.anzahlAngewandt()).isEqualTo(117);
+ assertThat(oktober2024.anzahlAngewandt()).isEqualTo(118);
}
/**
diff --git a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
index 629ec49..0e186ad 100644
--- a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
+++ b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
@@ -1666,4 +1666,115 @@ class BefehlAnwenderTest {
assertThat(ergebnis.anzahlManuell()).isEqualTo(1);
assertThat(ergebnis.protokoll().get(0).begruendung()).contains("nicht so viele Einheiten");
}
+
+ /**
+ * „In der Überschrift der Nummer 1 …“ meint die Kopfzeile jenes Aufzählungsblocks, nicht den
+ * Titel der Norm. Zuvor verwarf der Überschrift-Zweig die Nummer schweigend und griff auf den
+ * Normtitel — stand der Zieltext zufällig dort, so änderte er die falsche Einheit.
+ */
+ @Test
+ void ueberschriftEinerNummerIstDieKopfzeileIhresBlocks() {
+ var anlage =
+ new Norm(
+ "Anlage 8",
+ "Anforderungen an die Wärmedämmung in den Fällen des § 69",
+ null,
+ List.of(
+ new Absatz(
+ null,
+ "1. Wärmedämmung von Leitungen in den Fällen des § 69\n"
+ + " a) Leitungen sind in den Fällen des § 69 zu dämmen.\n"
+ + "2. Sonstiges\n"
+ + " a) Im Übrigen gilt nichts.")),
+ false);
+ var gesetz = new Gesetz("TestG", "Testgesetz", "TestG", List.of(anlage));
+
+ var befehl =
+ new Streichung(
+ stelle(
+ new Stelle.Gliederungseinheit("Anlage", "8"),
+ new Stelle.NummerNr("1"),
+ new Stelle.Ueberschrift()),
+ " in den Fällen des § 69",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz, List.of(befehl));
+ assertThat(ergebnis.protokoll()).allMatch(a -> a.status() == Status.ANGEWANDT);
+
+ var text = ergebnis.neu().norm("Anlage 8").orElseThrow();
+ // Die Kopfzeile ist gestutzt …
+ assertThat(text.absaetze().get(0).text()).startsWith("1. Wärmedämmung von Leitungen\n");
+ // … der Buchstabe a) darunter und der Titel der Anlage dagegen unberührt: Beide führen
+ // denselben Wortlaut, und eine norm-weite Streichung hätte sie mitgenommen.
+ assertThat(text.absaetze().get(0).text())
+ .contains("a) Leitungen sind in den Fällen des § 69 zu dämmen.");
+ assertThat(text.titel()).isEqualTo("Anforderungen an die Wärmedämmung in den Fällen des § 69");
+ }
+
+ /**
+ * Ein Aufzählungsglied aus einer einzigen Zeile hat keine Überschrift, sondern nur Text. Dort
+ * wird nichts angewandt, sondern gerügt: Andernfalls träfe die Operation den ganzen Wortlaut und
+ * gäbe vor, eine Überschrift geändert zu haben.
+ */
+ @Test
+ void einzeiligesAufzaehlungsgliedTraegtKeineUeberschrift() {
+ var anlage =
+ new Norm(
+ "Anlage 8",
+ "Anforderungen",
+ null,
+ List.of(new Absatz(null, "1. Kurz und bündig\n2. Ebenso kurz")),
+ false);
+ var gesetz = new Gesetz("TestG", "Testgesetz", "TestG", List.of(anlage));
+
+ var befehl =
+ new Streichung(
+ stelle(
+ new Stelle.Gliederungseinheit("Anlage", "8"),
+ new Stelle.NummerNr("1"),
+ new Stelle.Ueberschrift()),
+ " und bündig",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz, List.of(befehl));
+ assertThat(ergebnis.protokoll()).hasSize(1);
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.MANUELL_PRUEFEN);
+ assertThat(ergebnis.protokoll().get(0).grund()).isEqualTo(Grund.STELLE_NICHT_AUFLOESBAR);
+ assertThat(ergebnis.protokoll().get(0).begruendung())
+ .contains("trägt keine eigene Überschrift");
+ assertThat(ergebnis.neu().norm("Anlage 8").orElseThrow().absaetze().get(0).text())
+ .isEqualTo("1. Kurz und bündig\n2. Ebenso kurz");
+ }
+
+ /**
+ * Die Neufassung der Überschrift einer Aufzählungseinheit bleibt eine bewusst gezogene Grenze:
+ * Deren Kopfzeile trägt zugleich die Aufzählungsmarke, die eine Neufassung nicht mitliefert.
+ * Gerügt wird sie deshalb — nicht auf den Normtitel umgebogen.
+ */
+ @Test
+ void neufassungDerUeberschriftEinerNummerWirdGeruegt() {
+ var anlage =
+ new Norm(
+ "Anlage 8",
+ "Anforderungen",
+ null,
+ List.of(new Absatz(null, "1. Wärmedämmung\n a) Näheres.")),
+ false);
+
+ var gesetz = new Gesetz("TestG", "Testgesetz", "TestG", List.of(anlage));
+
+ var befehl =
+ new Neufassung(
+ stelle(
+ new Stelle.Gliederungseinheit("Anlage", "8"),
+ new Stelle.NummerNr("1"),
+ new Stelle.Ueberschrift()),
+ "Wärmedämmung und Kältedämmung",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz, List.of(befehl));
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.MANUELL_PRUEFEN);
+ assertThat(ergebnis.protokoll().get(0).grund()).isEqualTo(Grund.NICHT_UNTERSTUETZT);
+ assertThat(ergebnis.neu().norm("Anlage 8").orElseThrow().titel()).isEqualTo("Anforderungen");
+ }
}