aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-29 18:16:18 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-29 18:16:18 +0200
commit86c7d994e2fc7b469b660e8adee3d5f1a4e0f11c (patch)
tree0a62598372af06fe69742b89a9675ec734b47ec1 /src/main
parentc1013fc0ec0b1e3276cdf92e692f4333b1d5eb3a (diff)
Der Länderkreis schließt sich mit Saarland und Sachsen-Anhalt
Sechzehn Länder hat die Bundesrepublik, vierzehn waren erfaßt. Die beiden letzten fehlten aus demselben Grund: Ihre Landesrechtsportale führen keine Fassungsliste, und ohne Vorfassung gibt es keinen Belegfall. Für das Saarland hilft die Änderungshistorie aus. Weist sie für das fragliche Jahr genau eine Änderung aus, so ist die Vorfassung die Gesamtausgabe ohne diese eine. Für das Gesetz über den Saarlandpakt und die Verordnung zu seiner Ausführung — beide geändert durch das Gesetz Nr. 2211 vom 24. Juni 2026 — ist das so hergeleitet und aus den Gesetzblättern von 2019, 2020 und 2024 Norm für Norm gegengeprüft worden: Die Ursprungsfassung nebst den beiden zwischenzeitlichen Änderungen ergibt genau die heutige Gesamtausgabe. Ein Heft, zwei Stammwerke, zwei Aufträge; zehn von zehn Normen der Verordnung und neunzehn von zwanzig des Gesetzes stimmen zeichengenau. Sachsen-Anhalt bleibt beim rheinland-pfälzischen Weg, und zwar doppelt begründet: Das Portal führt keine Vorfassung, und das Gesetz- und Verordnungsblatt des Landes ist überhaupt nicht frei. Frei ist die Parlamentsdokumentation, weshalb hier die Drucksache 8/6357 des Landtags das Änderungsdokument ist; aussagekräftig ist allein die Zahl der erschlossenen Befehle, vierzehn an der Zahl. Fünf Funde sind allgemein und stehen im Produktivcode: der Kolumnentitel des saarländischen Amtsblattes; die Aufzählungsmarke, die Kennzeichnung ist und nicht Wortlaut, sodaß „in Nummer 8 den Punkt ersetzen“ nicht mehr am eigenen Markenpunkt scheitert; die Artikelwahl bei Ausführungsverordnungen, die ihren eigenen Namen nicht mehr wegstreicht; die Wortfuge am markerlosen Umbruch, die der Wortbestand ohne die zeilenletzten Wörter beweist; und die Buchstabengliederung mit Punkt nebst den kleinen römischen Zahlen, bei denen „i.“ aus dem Stand der Gliederung entschieden wird. Nicht selbsttätig verarbeitbar bleiben die auslassende Anfügung, die Anfügung an die Überschrift des Werkes selbst, der Verbund aus Bereichsumnummerierung und Wortstreichung sowie die kurze Wortfuge; sie sind im Handbuch und in den Herkunftsnachweisen als Grenzen benannt. Die Prüfung geht auf: 456 Testfälle, „mvnw verify“ grün, „reuse lint“ 223 von 223, und der Korpuslauf hält seine Grundlinie bei vierunddreißig Aufträgen, 1429 Befehlen, 1295 angewandt, 661 von 671 gleichen Normen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014dzFZB6rfFjpVMxhF5mQWN Change-Id: I046353512a672afda7b908f6aee425e4daba507c
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/AenderungsgesetzParser.java17
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java11
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/GliederungsScanner.java56
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java74
-rw-r--r--src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java28
5 files changed, 175 insertions, 11 deletions
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/parse/AenderungsgesetzParser.java b/src/main/java/eu/mulk/aendggner/aenderung/parse/AenderungsgesetzParser.java
index 812d067..b5865ac 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/AenderungsgesetzParser.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/AenderungsgesetzParser.java
@@ -417,6 +417,12 @@ public final class AenderungsgesetzParser {
* nennt. Der Vergleich ist deklinationstolerant („Das Allgemeine Gleichbehandlungsgesetz“ matcht
* die amtliche Bezeichnung „Allgemeines Gleichbehandlungsgesetz“).
*/
+ private static final Pattern AUSFUEHRUNGS_TITEL =
+ Pattern.compile("\\b(?:zur Ausführung|zur Durchführung|zum Vollzug) des\\b");
+
+ private static final Pattern AUSFUEHRUNGS_TITEL_MIT_GENITIV =
+ Pattern.compile("\\b(?:zur Ausführung|zur Durchführung|zum Vollzug) des [^,()]*");
+
private static boolean betrifft(
ArtikelBlock artikel, Gesetz ziel, ZitatExtraktor.Ergebnis zitate) {
var scan = GliederungsScanner.scanne(artikel.zeilen);
@@ -426,9 +432,14 @@ public final class AenderungsgesetzParser {
}
// Ausführungs-/Durchführungstitel nennen das Stammgesetz nur als Genitiv-Attribut („Die
// Verordnung zur Ausführung des Bayerischen Jagdgesetzes (AVBayJG) … wird wie folgt
- // geändert“) — solche Nennungen zählen nicht als Treffer.
- vorspann =
- vorspann.replaceAll("\\b(?:zur Ausführung|zur Durchführung|zum Vollzug) des [^,()]*", "");
+ // geändert“) — solche Nennungen zählen nicht als Treffer. Ist das Ziel aber selbst eine solche
+ // Verordnung (Verordnung zur Ausführung des Gesetzes über den Saarlandpakt, Amtsbl. I 2026
+ // S. 756 Artikel 2), so stünde nach dem Streichen gerade ihr eigener Name nicht mehr da und
+ // kein Artikel würde gewählt. Dann bleibt der Vorspann unangetastet; getroffen wird nur, wer
+ // den vollen Titel führt.
+ if (ziel.langue() == null || !AUSFUEHRUNGS_TITEL.matcher(ziel.langue()).find()) {
+ vorspann = AUSFUEHRUNGS_TITEL_MIT_GENITIV.matcher(vorspann).replaceAll("");
+ }
var vorspannStamm = stammForm(vorspann);
return (ziel.kurzue() != null && vorspannStamm.contains(stammForm(ziel.kurzue())))
|| (ziel.langue() != null && vorspannStamm.contains(stammForm(ziel.langue())))
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java b/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
index 1d0685b..d3c8cc4 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
@@ -65,16 +65,21 @@ final class BefehlErkenner {
private static final String ENUM = "((?:\\d+[a-z]?\\.|[a-z]{1,3}\\))\\s*)?";
// Verb der Neufassung. Neben „wird/werden wie folgt gefasst“ (Bund/Bayern) auch „erhält/erhalten
- // folgende Fassung“ — die in mehreren Ländern (Schleswig-Holstein, Niedersachsen) übliche Form.
+ // folgende Fassung“ und die bestimmte Nebenform „erhält die Fassung“ (Ltg-Drs. 8/6357 des
+ // Landtags von Sachsen-Anhalt) — die in mehreren Ländern (Schleswig-Holstein, Niedersachsen)
+ // übliche Form.
// Das pleonastische „neu“ („wird wie folgt neu gefasst“, NRW) und das im amtlichen Satz
// gelegentlich fehlende „wird“ („Nummer 9 wie folgt gefasst: …“, GV. NRW. 2026 Nr. 7) sind
// mitgefasst. Rein zusätzliche Alternationen ohne eigene Fanggruppe, damit die Gruppennummern
// gleich bleiben.
private static final String NEUFASSUNG_VERB =
- "(?:(?:(?:wird|werden) )?wie folgt (?:neu )?gefasst|(?:erhält|erhalten) folgende Fassung)";
+ "(?:(?:(?:wird|werden) )?wie folgt (?:neu )?gefasst"
+ + "|(?:erhält|erhalten) (?:folgende|die|die folgende) Fassung)";
+ // Der Doppelpunkt vor dem Zitat ist wahlfrei: „Die Überschrift erhält die Fassung „…““ setzt
+ // ihn nicht (Ltg-Drs. 8/6357 des Landtags von Sachsen-Anhalt). Das Zitat selbst bleibt verlangt.
private static final Pattern NEUFASSUNG =
- Pattern.compile("^(.+?) " + NEUFASSUNG_VERB + ": " + ENUM + Z + "\\.?$");
+ Pattern.compile("^(.+?) " + NEUFASSUNG_VERB + ":? " + ENUM + Z + "\\.?$");
// „In Absatz 2 wird Satz 1 wie folgt gefasst: „…““ — die Fundstelle steht geteilt: der Rahmen vor
// dem Verb („In Absatz 2“), die adressierte Einheit dahinter („Satz 1“). Zusammengesetzt ergeben
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/parse/GliederungsScanner.java b/src/main/java/eu/mulk/aendggner/aenderung/parse/GliederungsScanner.java
index 39bffe5..6f1246f 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/GliederungsScanner.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/GliederungsScanner.java
@@ -42,6 +42,14 @@ final class GliederungsScanner {
private static final Pattern DREIFACHBUCHSTABE_MARKER =
Pattern.compile("^(([a-z])\\2\\2\\d*)\\)\\s+(.*)$");
+ // Sachsen-Anhalt gliedert seine Änderungsbefehle mit Punkt statt Klammer und setzt darunter
+ // kleine römische Zahlen: „1.“ / „a.“ / „i.“ (Ltg-Drs. 8/6357). Beide Formen sind mit den
+ // Klammerformen wesensgleich; nur das Satzzeichen unterscheidet sie.
+ private static final Pattern BUCHSTABE_PUNKT_MARKER =
+ Pattern.compile("^([a-z]{1,4}\\d*)\\.\\s+(.*)$");
+ private static final Pattern ROEMISCH =
+ Pattern.compile("i|ii|iii|iv|v|vi|vii|viii|ix|x|xi|xii|xiii|xiv|xv");
+
private GliederungsScanner() {}
static ScanErgebnis scanne(List<String> zeilen) {
@@ -51,7 +59,7 @@ final class GliederungsScanner {
for (var zeile : zeilen) {
var gestutzt = zeile.strip();
- var marker = erkenneMarker(gestutzt);
+ var marker = erkenneMarker(gestutzt, stapel);
if (marker != null && istAkzeptabel(marker, stapel)) {
// Tiefere offene Ebenen schließen.
@@ -79,7 +87,7 @@ final class GliederungsScanner {
private record Marker(String label, int ebene, String rest) {}
- private static Marker erkenneMarker(String zeile) {
+ private static Marker erkenneMarker(String zeile, ArrayDeque<MutablerPunkt> stapel) {
var dezimal = DEZIMAL_MARKER.matcher(zeile);
if (dezimal.matches()) {
var label = dezimal.group(1);
@@ -101,9 +109,48 @@ final class GliederungsScanner {
if (nummer.matches()) {
return new Marker(nummer.group(1), 1, nummer.group(2));
}
+ var buchstabePunkt = BUCHSTABE_PUNKT_MARKER.matcher(zeile);
+ if (buchstabePunkt.matches()) {
+ var label = buchstabePunkt.group(1);
+ // Mehrbuchstabige Marken mit Punkt gibt es nur als römische Zahl; „vgl.“ und dergleichen
+ // sind keine Gliederung.
+ if (label.length() > 1 && !ROEMISCH.matcher(label).matches()) {
+ return null;
+ }
+ return new Marker(label, istRoemisch(label, stapel) ? 3 : 2, buchstabePunkt.group(2));
+ }
return null;
}
+ /**
+ * „i.“ ist als Buchstabe die neunte und als römische Zahl die erste Marke. Entschieden wird aus
+ * dem Stand der Gliederung: Ist eine dritte Ebene mit römischer Zählung offen und passt das Label
+ * als deren Nachfolger, so ist es römisch; sonst eröffnet allein „i.“ unter einer offenen zweiten
+ * Ebene die dritte. In jedem anderen Fall bleibt es ein Buchstabe.
+ */
+ private static boolean istRoemisch(String label, ArrayDeque<MutablerPunkt> stapel) {
+ if (!ROEMISCH.matcher(label).matches()) {
+ return false;
+ }
+ for (var offen : stapel) {
+ if (offen.ebene == 3 && ROEMISCH.matcher(offen.label).matches()) {
+ return istRoemischerNachfolger(offen.label, label);
+ }
+ }
+ return label.equals("i") && !stapel.isEmpty() && stapel.peekLast().ebene == 2;
+ }
+
+ private static final List<String> ROEMISCHE_FOLGE =
+ List.of(
+ "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix", "x", "xi", "xii", "xiii", "xiv",
+ "xv");
+
+ private static boolean istRoemischerNachfolger(String vorher, String nachher) {
+ int v = ROEMISCHE_FOLGE.indexOf(vorher);
+ int n = ROEMISCHE_FOLGE.indexOf(nachher);
+ return v >= 0 && n == v + 1;
+ }
+
private static boolean istAkzeptabel(Marker marker, ArrayDeque<MutablerPunkt> stapel) {
// Nachfolger eines offenen Punkts derselben Ebene?
for (var offen : stapel) {
@@ -130,7 +177,7 @@ final class GliederungsScanner {
return switch (marker.ebene) {
case 1 -> marker.label.equals("1");
case 2 -> marker.label.equals("a");
- case 3 -> marker.label.equals("aa");
+ case 3 -> marker.label.equals("aa") || marker.label.equals("i");
case 4 -> marker.label.equals("aaa");
default -> false;
};
@@ -162,6 +209,9 @@ final class GliederungsScanner {
? nachherSuffix.equals("a")
: nachherSuffix.charAt(0) == vorherSuffix.charAt(0) + 1));
}
+ if (istRoemischerNachfolger(vorher, nachher)) {
+ return true;
+ }
if (vorher.matches("[a-z]+\\d*") && nachher.matches("[a-z]+\\d*")) {
var vorherBasis = vorher.replaceAll("\\d+$", "");
var vorherSuffix = vorher.substring(vorherBasis.length());
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 4a52a70..5fdb6cd 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java
@@ -335,6 +335,16 @@ public final class TextBereiniger {
"[ \\t]*\\d{1,4}[ \\t]+Gesetz- und Verordnungsblatt für Mecklenburg-Vorpommern"
+ " \\d{4}[ \\t]+Nr\\. \\d+[ \\t]*");
+ // Amtsbl. des Saarlandes: Der Kolumnentitel („Amtsblatt des Saarlandes Teil I vom 13. August
+ // 2026756“) steht im Inhaltsstrom und klebt an der Seitenzahl — mal ohne, mal mit Zwischenraum,
+ // und die Zahl steht ebenso gut davor wie dahinter. Er zerschnitte sonst den Befehlstext und das
+ // Zitat der eingefügten Vorschrift. Die Wendung „im Amtsblatt des Saarlandes bekannt gemacht“
+ // bleibt unberührt, weil das Muster „Teil I vom <Datum>“ verlangt.
+ private static final Pattern ABL_SL_KOPF =
+ Pattern.compile(
+ "[ \\t]*\\d{0,4}[ \\t]*Amtsblatt des Saarlandes Teil I{1,2} vom"
+ + " \\d{1,2}\\. \\p{L}+ \\d{4}[ \\t]*\\d{0,4}[ \\t]*");
+
// Die Blattzählung des GBl. BW („Seite 2 von 7“) steht im Inhaltsstrom mitunter für sich allein,
// getrennt vom übrigen Seitenfuß, und zwar zwischen zwei Gliederungspunkten — sie hinge sonst dem
// vorangehenden Befehl an und machte ihn unkenntlich.
@@ -393,6 +403,7 @@ public final class TextBereiniger {
text = GBL_HB_KOPF.matcher(text).replaceAll("\n");
text = GVOBL_MV_KOPF.matcher(text).replaceAll("\n");
text = GVOBL_MV_FUSS.matcher(text).replaceAll("\n");
+ text = ABL_SL_KOPF.matcher(text).replaceAll("\n");
var zeilen = entferneKolumnentitel(zerlegeInZeilen(text));
var verbunden = verbindeUmbrueche(zeilen);
// Falsch-positive markerlose Zusammenzüge („durch“ + „die“ → „durchdie“) reparieren — die
@@ -616,6 +627,7 @@ public final class TextBereiniger {
// Der Wortbestand des ganzen Dokuments entscheidet über Trennstriche vor Großbuchstaben
// (siehe #warTrennung).
var wortbestand = wortbestand(zeilen);
+ var ganzeWoerter = wortbestandOhneZeilenende(zeilen);
// Markerlose Trennungen sind nur erkennbar, wenn die Quelle die Trailing-Space-Konvention
// verwendet (PDF-Extraktion). Handgeschriebene Klartextdateien haben keine Trailing-Spaces —
// dort würde die Heuristik reguläre Umbrüche verschmelzen, also bleibt sie aus.
@@ -678,7 +690,15 @@ public final class TextBereiniger {
}
} else {
if (Character.isLowerCase(erstesZeichen) && !KONJUNKTION.matcher(naechste).matches()) {
- zeile = zeile + naechste;
+ // Der markerlose Umbruch meint im Regelfall eine Trennung ohne Strich; im engen
+ // Zweispaltensatz des Amtsbl. des Saarlandes verliert der Auszug aber auch an einer
+ // echten Wortfuge den Zwischenraum („zusätzlich“ + „jahresbezogene“). Wieder
+ // entscheidet der Wortbestand des Dokuments: Steht das Wort vor dem Umbruch anderswo
+ // für sich, das Zusammengezogene dagegen nirgends, so war es eine Fuge.
+ zeile =
+ warFuge(zeile, naechste, wortbestand, ganzeWoerter)
+ ? zeile + " " + naechste
+ : zeile + naechste;
} else {
break;
}
@@ -695,6 +715,25 @@ public final class TextBereiniger {
* Sammelt die Wörter des Dokuments (ohne Satzzeichen), um Trennstriche vor Großbuchstaben zu
* beurteilen.
*/
+ /**
+ * Der Wortbestand ohne die zeilenletzten Wörter. Ein durch den Umbruch abgerissenes Bruchstück
+ * („Schwel“ von „Schwellenwertes“) steht immer am Zeilenende; ein wirkliches Wort steht irgendwo
+ * auch mitten in einer Zeile. Nur dieser Bestand taugt darum als Beweis dafür, dass vor dem
+ * Umbruch ein ganzes Wort stand (siehe {@link #warFuge}).
+ */
+ private static java.util.Set<String> wortbestandOhneZeilenende(List<Zeile> zeilen) {
+ var woerter = new java.util.HashSet<String>();
+ for (var zeile : zeilen) {
+ var teile = WORTGRENZE.split(zeile.text().stripTrailing());
+ for (int i = 0; i + 1 < teile.length; i++) {
+ if (!teile[i].isEmpty()) {
+ woerter.add(teile[i]);
+ }
+ }
+ }
+ return woerter;
+ }
+
private static java.util.Set<String> wortbestand(List<Zeile> zeilen) {
var woerter = new java.util.HashSet<String>();
for (var zeile : zeilen) {
@@ -715,6 +754,9 @@ public final class TextBereiniger {
return woerter;
}
+ /** Kürzere Köpfe vor einem markerlosen Umbruch gelten nicht als eigenes Wort (Vorsilben). */
+ private static final int MINDESTLAENGE_FUGENKOPF = 4;
+
private static final Pattern WORTGRENZE = Pattern.compile("[^\\p{L}\\p{N}.-]+");
/**
@@ -739,6 +781,36 @@ public final class TextBereiniger {
return wortbestand.contains(kopf + rumpf);
}
+ /**
+ * War der markerlose Umbruch eine Wortfuge statt einer Trennung? Beweis ist wieder der
+ * Wortbestand: Das Wort vor dem Umbruch kommt anderswo für sich vor, die zusammengezogene Form
+ * dagegen nicht. Fehlt einer der beiden Belege, bleibt es beim bisherigen Zusammenziehen —
+ * geraten wird nicht.
+ */
+ private static boolean warFuge(
+ String zeile,
+ String naechste,
+ java.util.Set<String> wortbestand,
+ java.util.Set<String> ganzeWoerter) {
+ int anfang = zeile.length();
+ while (anfang > 0 && Character.isLetterOrDigit(zeile.charAt(anfang - 1))) {
+ anfang--;
+ }
+ var kopf = zeile.substring(anfang);
+ if (kopf.isEmpty()) {
+ return false;
+ }
+ var rumpf = WORTGRENZE.split(naechste, 2)[0];
+ if (rumpf.isEmpty()) {
+ return false;
+ }
+ // Kurze Köpfe sind die deutschen Vorsilben („aus“ + „geschlossen“, „zu“ + „ergreifen“); ob
+ // sie ein eigenes Wort oder die erste Silbe sind, sagt kein Bestand — geraten wird nicht.
+ return kopf.length() >= MINDESTLAENGE_FUGENKOPF
+ && ganzeWoerter.contains(kopf)
+ && !wortbestand.contains(kopf + rumpf);
+ }
+
private static boolean endetMitSilbentrennung(String gestutzteZeile) {
if (!gestutzteZeile.endsWith("-") || gestutzteZeile.length() < 2) {
return false;
diff --git a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
index a73ab77..ef4d3bb 100644
--- a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
+++ b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
@@ -2629,7 +2629,33 @@ public final class BefehlAnwender {
// wieder angehängt (ihre eigene Aufhebung läuft über FussnotenAufhebung).
private static final Pattern FUSSNOTEN_DEFINITION = Pattern.compile("(?m)^[⁰¹²³⁴⁵⁶⁷⁸⁹]+\\)");
- private static TextOperation ohneFussnoten(TextOperation operation) {
+ /**
+ * Die Aufzählungsmarke am Anfang einer Einheit („8. “, „a) “) ist deren Kennzeichnung und nicht
+ * ihr Wortlaut. Wer „in Nummer 8 den Punkt durch ein Komma ersetzt“, meint den Schlusspunkt des
+ * Gliedes und nicht den Punkt der Marke; ohne diese Schranke stünde der Punkt zweimal da und der
+ * Befehl bliebe als mehrdeutig liegen (Amtsbl. des Saarlandes 2020 S. 1339, Artikel 2 Nummer 1).
+ * Die Absatzbezeichnung „(1)“ bleibt unangetastet — sie ist anderswo eigens geregelt.
+ */
+ private static TextOperation ohneAufzaehlungsmarke(TextOperation operation) {
+ return text -> {
+ var m = AUFZAEHLUNGSMARKE.matcher(text);
+ if (!m.lookingAt()) {
+ return operation.wende(text);
+ }
+ int grenze = m.end();
+ var ergebnis = operation.wende(text.substring(grenze));
+ if (ergebnis.fehler() != null) {
+ return ergebnis;
+ }
+ return TextErgebnis.ok(text.substring(0, grenze) + ergebnis.text());
+ };
+ }
+
+ private static final Pattern AUFZAEHLUNGSMARKE =
+ Pattern.compile("\\s*(?:\\d+[a-z]?\\.|[a-z]{1,3}\\))[ \\t]+");
+
+ private static TextOperation ohneFussnoten(TextOperation rohOperation) {
+ var operation = ohneAufzaehlungsmarke(rohOperation);
return text -> {
var m = FUSSNOTEN_DEFINITION.matcher(text);
if (!m.find()) {