diff options
| author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2026-07-26 08:53:10 +0200 |
|---|---|---|
| committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2026-07-26 08:53:10 +0200 |
| commit | 81777013dfa582053489efea874e84379f1f0805 (patch) | |
| tree | 83f227c65b91152d4a8aaca67d418da04d601e9e /src/main/java | |
| parent | c86a73a6e1da7dd28076054936458b850eda2947 (diff) | |
Support unnumbered named Anlagen; strip control characters
Investigating "Anlagen as amendment target" corrected the diagnosis from
yesterday: Anlagen are supported. In gii-XML they are norms of their own
("Anlage 8", "Anhang"), Stelle.anlagenEnbez() resolves them, and the GEG
sample applies annex-targeted commands — only two of its fifty manual
entries touch annexes, both for content reasons.
What actually failed in Berlin was narrower, and is fixed:
* A law with a single annex names it after the provision it belongs to
("Die Anlage zu § 2 Absatz 4 Satz 1"). StellenParser demanded a
number after "Anlage" and rejected the phrase, so the article's frame
command went unrecognised and its items lost their context. The
annex now carries the designation "Anlage", like "Anhang". The naming
suffix is skipped deliberately: read along, the "§ 2" inside it would
become the target and the wrong norm would be amended.
* C0 control characters are now stripped. Berlin's enumeration indent
carries a U+0007 that sits invisibly in front of the command and made
its start-of-line anchor miss.
* Command verbs torn apart by a stray space in the official typesetting
("ein gefügt" for "eingefügt") are rejoined, restricted to sequences
that are not valid German word order.
Three of Article 1's four commands are now recognised. The fourth places
the new unit by a word anchor ("Vor den Wörtern … wird folgender Absatz 5
eingefügt"), which StrukturEinfuegung cannot express; that and the
inability of the plain-text stem format to carry an Anlage at all are
recorded in Landesrecht-Beispiele.adoc.
231 tests green; all pinned figures unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Change-Id: Ic5804bf343dd5d8ce435c07b8f882ca0372890d2
Diffstat (limited to 'src/main/java')
| -rw-r--r-- | src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java | 19 | ||||
| -rw-r--r-- | src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java | 13 |
2 files changed, 31 insertions, 1 deletions
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java b/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java index 473eb14..786c812 100644 --- a/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java +++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java @@ -133,7 +133,7 @@ public final class StellenParser { i++; } case "Teil", "Teils", "Buch", "Buches", "Kapitel", "Kapitels", "Abschnitt", "Abschnitts", - "Unterabschnitt", "Unterabschnitts", "Anlage", "Anlagen" -> { + "Unterabschnitt", "Unterabschnitts" -> { var wert = naechstesWort(woerter, i); if (wert == null || !NUMMER_WERT.matcher(wert).matches()) { return Optional.empty(); @@ -141,6 +141,23 @@ public final class StellenParser { komponenten.add(new Stelle.Gliederungseinheit(gliederungsArt(wort), wert)); i++; } + case "Anlage", "Anlagen" -> { + var wert = naechstesWort(woerter, i); + if (wert != null && NUMMER_WERT.matcher(wert).matches()) { + komponenten.add(new Stelle.Gliederungseinheit("Anlage", wert)); + i++; + continue; + } + // „die Anlage zu § 2 Absatz 4 Satz 1“ — ein Gesetz mit einer einzigen Anlage benennt sie + // nach der Vorschrift, zu der sie gehört. Wie beim „Anhang“ trägt sie dann die enbez + // „Anlage“; der Zusatz identifiziert sie nur und ist nicht selbst Änderungsziel. Er + // reicht bis zum Ende der Stellenangabe und wird deshalb übersprungen — sonst läse der + // Parser das darin genannte „§ 2“ als Ziel und änderte die falsche Norm. + komponenten.add(new Stelle.Gliederungseinheit("Anlage", "")); + if ("zu".equals(wert)) { + i = woerter.length; + } + } case "Anhang" -> komponenten.add(new Stelle.Gliederungseinheit("Anhang", "")); case "Inhaltsübersicht" -> komponenten.add(new Stelle.Inhaltsuebersicht()); case "Überschrift" -> komponenten.add(new Stelle.Ueberschrift()); 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 d0e37f8..fc54554 100644 --- a/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java +++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java @@ -206,12 +206,19 @@ public final class TextBereiniger { private static final Pattern SACHNUMMER_MIT_LEERZEICHEN = Pattern.compile("(§|Art\\.) (\\d+) ([a-z])(?![a-zäöüß).])"); + /** C0-Steuerzeichen außer Tabulator und Zeilenumbruch; im Fließtext stets Extraktionsmüll. */ + private static final Pattern STEUERZEICHEN = Pattern.compile("[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]"); + private TextBereiniger() {} public static String bereinige(String rohText) { // Geschützte Leerzeichen (GVBl-Satz: „§ 1“, „Abs. 2“) sind für Javas \s und // String.strip unsichtbar — früh auf gewöhnliche Leerzeichen normalisieren. var text = rohText.replace(' ', ' ').replace(' ', ' '); + // Steuerzeichen aus fehlgeleiteten Glyphenzuordnungen (im GVBl. für Berlin trägt der Einzug + // der Aufzählungsglieder ein U+0007). Sie sind unsichtbar, stehen aber vor dem Befehlstext und + // ließen dessen Zeilenanfangs-Anker ins Leere greifen. + text = STEUERZEICHEN.matcher(text).replaceAll(""); text = normalisiereAnfuehrungszeichen(text); text = INVERTIERTES_ZITAT.matcher(text).replaceAll("$1„($2) "); text = INVERTIERTES_PARAGRAPH_ZITAT.matcher(text).replaceAll("$1„$2"); @@ -290,6 +297,12 @@ public final class TextBereiniger { .replace("undwird ", "und wird ") .replace("Kommaeingefügt", "Komma eingefügt") .replace("Kommaersetzt", "Komma ersetzt") + // Umgekehrter Satzfehler: ein Leerzeichen mitten in der Befehlsvokabel („ein ge-“ + + // „fügt“ → „ein gefügt“, GVBl. Berlin 2026/17). Nur Fügungen, die als Wortfolge im + // Deutschen nicht vorkommen — „er setzt“ etwa bliebe unangetastet. + .replace("ein gefügt", "eingefügt") + .replace("an gefügt", "angefügt") + .replace("auf gehoben", "aufgehoben") // Kontextrahmen, an den der folgende Unterpunkt geklebt wurde („geändertaa) In …“). .replaceAll("(wie folgt geändert:?)(?=[a-z]{1,3}\\)|\\d+[a-z]?\\.)", "$1\n"); } |
