aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/eu
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-07-25 10:00:36 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-07-25 10:00:36 +0200
commit35e90f4ab0f9b40d4752c9b8cba57c9452a8189f (patch)
tree271b8ce1f0537bc8546e3939d99df6e47d79369b /src/main/java/eu
parent519c1e7b769c37769d1b5d1ffd6e13566b26c86d (diff)
Add North Rhine-Westphalia TMZ-Gesetz acceptance case
Article 3 of the 22. Rundfunkänderungsgesetz (GV. NRW. 2026 S. 202) amends the Telemedienzuständigkeitsgesetz. All four commands apply cleanly, and the result matches the official consolidated version of 1 April 2026. recht.nrw.de turned out to be the one remaining state portal that serves consolidated laws as server-rendered HTML, with the version history encoded in the URL date prefix — so the pre-amendment stem (27 Apr 2022) was directly obtainable. Two fixes were needed: * LandesRechtTextParser read the parenthesised title suffix wholly as the abbreviation. State laws routinely carry both short title and abbreviation there ("(Telemedienzuständigkeitsgesetz – TMZ-Gesetz)"), and the amendment act cites the short title, so article selection never matched. The parser now splits at the dash. * ZitatExtraktor swallowed the rest of the document when a closing quotation mark is missing in the official text (Article 2 no. 11 here), which hid Articles 3 to 5 entirely. An amendment act never quotes across an article heading, so an open quotation is now closed before a bare "Artikel N" line, with a warning. Quoted paragraphs are unaffected: Bavarian norm heads read "Art. N". 223 tests green; all previously pinned figures unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Change-Id: I78470075d9be2433966b821b6fe1e21808996c46
Diffstat (limited to 'src/main/java/eu')
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/ZitatExtraktor.java34
-rw-r--r--src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java17
2 files changed, 48 insertions, 3 deletions
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/parse/ZitatExtraktor.java b/src/main/java/eu/mulk/aendggner/aenderung/parse/ZitatExtraktor.java
index e116056..dc7dac8 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/ZitatExtraktor.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/ZitatExtraktor.java
@@ -91,7 +91,23 @@ public final class ZitatExtraktor {
aktuellesZitat.append(c);
}
} else if (tiefe > 0) {
- aktuellesZitat.append(c);
+ if (c == '\n' && beginntArtikelUeberschrift(text, i + 1)) {
+ // Ein Änderungsgesetz zitiert nie über eine Artikel-Überschrift hinweg: Hier fehlt im
+ // amtlichen Satz ein schließendes Anführungszeichen (kommt vor, z.B. GV. NRW. 2026
+ // S. 202 Artikel 2 Nr. 11). Ohne diese Grenze verschlänge das offene Zitat alle
+ // folgenden Artikel und der Parser fände überhaupt keine Änderungsbefehle mehr.
+ warnungen.add(
+ "Zitat vor einer Artikel-Überschrift nicht geschlossen: …"
+ + kontextAuszug(text, i)
+ + "… — dort geschlossen.");
+ ausgabe.append('«').append(zitate.size()).append('»');
+ zitate.add(aktuellesZitat.toString());
+ aktuellesZitat.setLength(0);
+ tiefe = 0;
+ ausgabe.append(c);
+ } else {
+ aktuellesZitat.append(c);
+ }
} else {
ausgabe.append(c);
}
@@ -131,6 +147,22 @@ public final class ZitatExtraktor {
return FORTFUEHRUNGS_MARKER.matcher(fenster).find();
}
+ /**
+ * Strukturgrenze eines Änderungsgesetzes: eine Zeile, die nur aus „Artikel N“ besteht. Bayerische
+ * Stammgesetze zitieren ihre Normköpfe als „Art. N“, nicht als „Artikel N“ — zitierter Inhalt
+ * läuft hier also nicht versehentlich auf.
+ */
+ private static final Pattern ARTIKEL_GRENZE = Pattern.compile("^Artikel[ \\t]+\\d+[a-z]?[ \\t]*$");
+
+ /** Wahr, wenn die bei {@code von} beginnende Zeile eine Artikel-Überschrift ist. */
+ private static boolean beginntArtikelUeberschrift(String text, int von) {
+ if (von >= text.length()) {
+ return false;
+ }
+ int ende = text.indexOf('\n', von);
+ return ARTIKEL_GRENZE.matcher(text.substring(von, ende < 0 ? text.length() : ende)).matches();
+ }
+
private static String kontextAuszug(String text, int position) {
var von = Math.max(0, position - 60);
var bis = Math.min(text.length(), position + 20);
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 ada273c..a3397cf 100644
--- a/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
+++ b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
@@ -41,6 +41,9 @@ final class LandesRechtTextParser {
// geprüft, sodass spätere Fundstellen-Klammern („(BayRS V S. 595)“) nicht getroffen werden.
private static final Pattern JURABK_ZEILE = Pattern.compile("^\\(([^()]+)\\)$");
+ /** Trennt im Klammerzusatz den Kurztitel von der Abkürzung („Kurztitel – Abk“). */
+ private static final Pattern KURZTITEL_TRENNER = Pattern.compile("\\s+[–—]\\s+");
+
// Kopf der Druckfassung („BayJG: Bayerisches Jagdgesetz … (Art. 1–64)“), ggf. mit
// umbrochenem Rest („1–64)“) auf der Folgezeile.
private static final Pattern DRUCKKOPF = Pattern.compile("^\\S{1,20}: .+$");
@@ -95,10 +98,20 @@ final class LandesRechtTextParser {
}
var langue = zeilen.get(i++).strip();
String jurabk = null;
+ String kurzue = null;
if (i < zeilen.size()) {
var m = JURABK_ZEILE.matcher(zeilen.get(i).strip());
if (m.matches()) {
- jurabk = m.group(1);
+ // Landesgesetze führen im Klammerzusatz häufig Kurztitel und Abkürzung nebeneinander
+ // („Telemedienzuständigkeitsgesetz – TMZ-Gesetz“, „Gemeindeordnung – GO –“). Das
+ // Änderungsgesetz zitiert dann den Kurztitel, nicht die Abkürzung.
+ var teile = KURZTITEL_TRENNER.split(m.group(1).strip(), 2);
+ if (teile.length == 2) {
+ kurzue = teile[0].strip();
+ jurabk = teile[1].strip().replaceAll("\\s*[–—-]\\s*$", "");
+ } else {
+ jurabk = m.group(1).strip();
+ }
i++;
}
}
@@ -190,7 +203,7 @@ final class LandesRechtTextParser {
"Kein „§ N“- oder „Art. N“-Normkopf gefunden — ist das eine konsolidierte Fassung im"
+ " kanonischen Klartextformat?");
}
- return new Gesetz(jurabk != null ? jurabk : langue, langue, null, normen, gliederungen);
+ return new Gesetz(jurabk != null ? jurabk : langue, langue, kurzue, normen, gliederungen);
}
/** Überspringt die Kopfzeile der Druckfassung samt umbrochenem Rest. */