diff options
| author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2026-07-24 05:34:32 +0200 |
|---|---|---|
| committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2026-07-24 05:34:32 +0200 |
| commit | 3f81addcfbeae7aabcdd823340397f66e394f02f (patch) | |
| tree | cfc08069f1da4bff2c8f1b67de85e70defad2fa2 /src/main/java | |
| parent | c2b57decf3d57d4808dd37723c562b8e001cc342 (diff) | |
Add Saxony SächsBeamtVG acceptance case (first non-Bavarian Landesrecht)
Complete the first §-structured Landesrecht acceptance case for wave six: the
Saxon Civil Servants' Pension Act (SächsBeamtVG) in its 1 Feb 2025 version plus
the amending act of 13 April 2026 (SächsGVBl. S. 134). All five commands are
recognised and applied without residue — the amount replacements in § 47 and the
re-enactment of § 80(1) sentence 2.
- LandesRechtTextParser: recognise arabic, keyword-first structural headings
("Abschnitt 1", "Unterabschnitt 2", "Teil 3") via GLIEDERUNG_ARABISCH, in
addition to Bavaria's roman "I. Abschnitt" form. Bavaria behaviour unchanged.
- TextBereiniger: strip the footer of revosax full-text output (REVOSAX_FUSS,
"https://…revosax… Fassung vom … Seite N von M").
- Add the consolidated pre-amendment stem as canonical plaintext
(sampledata/Sachsen/SaechsBeamtVG-alt.txt, derived from the archived revosax
full text) and EndToEndTest.saechsBeamtVgAcceptance (5 applied, 0 manual).
220 tests green; federal and Bavarian pinned numbers unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Change-Id: I8e878bc9506f24028cf643a873de000f3d066d64
Diffstat (limited to 'src/main/java')
| -rw-r--r-- | src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java | 9 | ||||
| -rw-r--r-- | src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java | 20 |
2 files changed, 28 insertions, 1 deletions
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java b/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java index 4016256..d79df60 100644 --- a/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java +++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java @@ -117,6 +117,12 @@ public final class TextBereiniger { "^\\s*\\d+\\. Wahlperiode\\s+(?:\\d{2}\\.\\d{2}\\.\\d{4}\\s+)?Drucksache \\d+/\\d+\\s*$"); private static final Pattern LANDTAG_MARKE = Pattern.compile("^\\s*Bayerischer Landtag\\s*$"); + // Fußzeile der revosax-Volltextausgabe (Sachsen), z.B. „https://www.revosax.sachsen.de + // Fassung vom 13.04.2026 Seite 1 von 1“ — Herausgeber-URL, Fassungsdatum und Seitenangabe. + private static final Pattern REVOSAX_FUSS = + Pattern.compile( + "^\\s*https?://\\S+\\s+Fassung vom \\d{1,2}\\.\\d{1,2}\\.\\d{4}\\s+Seite \\d+ von \\d+\\s*$"); + /** Konjunktionen, die typischerweise auf einen Suspensivstrich folgen („Wirk- und …“). */ private static final Pattern KONJUNKTION = Pattern.compile("^(und|oder|sowie|bzw\\.|beziehungsweise)\\b.*"); @@ -285,7 +291,8 @@ public final class TextBereiniger { || GVBL_KOPF.matcher(zeile).matches() || LANDTAG_SEITENKOPF.matcher(zeile).matches() || LANDTAG_TITELKOPF.matcher(zeile).matches() - || LANDTAG_MARKE.matcher(zeile).matches(); + || LANDTAG_MARKE.matcher(zeile).matches() + || REVOSAX_FUSS.matcher(zeile).matches(); } /** 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 b1da42d..23260de 100644 --- a/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java +++ b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java @@ -49,6 +49,13 @@ final class LandesRechtTextParser { private static final Pattern GLIEDERUNG = Pattern.compile("^([IVXLCDM]+)\\.\\s+(Abschnitt|Teil|Kapitel)\\b\\s*(.*)$"); + // Gliederungs-Überschrift in keyword-erster, arabischer Form („Abschnitt 1“, „Unterabschnitt 2“, + // „Teil 3“). Die meisten Länder (z.B. Sachsen) gliedern so; Bayern dagegen römisch mit + // nachgestelltem Schlüsselwort („I. Abschnitt“, Muster GLIEDERUNG). Der Titel steht — nach der + // kanonischen Aufbereitung — mit doppeltem Leerzeichen auf derselben Zeile. + private static final Pattern GLIEDERUNG_ARABISCH = + Pattern.compile("^(Buch|Teil|Kapitel|Abschnitt|Unterabschnitt|Titel) (\\d+[a-z]?)\\s+(\\S.*)$"); + private static final Pattern UNTER_GLIEDERUNG = Pattern.compile("^(\\d+[a-z]?)\\.\\s+(\\S.*)$"); // Normkopf: „§ N“ bzw. „Art. N“ plus Titel auf derselben Zeile. Das Sigel steht in Gruppe 1, die @@ -95,6 +102,7 @@ final class LandesRechtTextParser { // Rest des Titelblocks (Datum, Fundstellen, Vollzitat) bis zur ersten Struktur überspringen. while (i < zeilen.size() && !GLIEDERUNG.matcher(zeilen.get(i).strip()).matches() + && !GLIEDERUNG_ARABISCH.matcher(zeilen.get(i).strip()).matches() && !NORM_KOPF.matcher(zeilen.get(i).strip()).matches()) { i++; } @@ -115,11 +123,13 @@ final class LandesRechtTextParser { var zeile = i < zeilen.size() ? zeilen.get(i).strip() : null; var gliederung = zeile != null ? GLIEDERUNG.matcher(zeile) : null; + var arabisch = zeile != null ? GLIEDERUNG_ARABISCH.matcher(zeile) : null; var unterGliederung = zeile != null ? UNTER_GLIEDERUNG.matcher(zeile) : null; var normKopf = zeile != null ? NORM_KOPF.matcher(zeile) : null; if (zeile == null || gliederung.matches() + || arabisch.matches() || (normKopf.matches() && istNeuerNormKopf(normKopf.group(2), letzteNormNummer)) || (unterGliederung.matches() && istUnterGliederung(unterGliederung, zeilen, i))) { // Laufende Norm abschließen. @@ -142,6 +152,16 @@ final class LandesRechtTextParser { gliederung.group(1) + ". " + gliederung.group(2), titel.isEmpty() ? null : titel); gliederungen.add(aktuelleGliederung); + } else if (arabisch.matches()) { + gliederungsZaehler++; + elternKennzahl = String.format("%03d", gliederungsZaehler); + var titel = arabisch.group(3).strip(); + aktuelleGliederung = + new Gliederung( + elternKennzahl, + arabisch.group(1) + " " + arabisch.group(2), + titel.isEmpty() ? null : titel); + gliederungen.add(aktuelleGliederung); } else if (normKopf.matches() && istNeuerNormKopf(normKopf.group(2), letzteNormNummer)) { normSigel = normKopf.group(1); normNummer = normKopf.group(2); |
