aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/eu
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu')
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java53
-rw-r--r--src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java3
2 files changed, 53 insertions, 3 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 6fb511d..d9ecf8a 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/TextBereiniger.java
@@ -73,6 +73,17 @@ public final class TextBereiniger {
private static final Pattern KONJUNKTION =
Pattern.compile("^(und|oder|sowie|bzw\\.|beziehungsweise)\\b.*");
+ /** Perzentil der Zeilenlängen, das als „volle Spaltenbreite“ gilt (siehe {@link #verbindeUmbrueche}). */
+ private static final double VOLLZEILE_PERZENTIL = 0.9;
+
+ /** Mindestanteil der vollen Spaltenbreite, ab dem ein markerloser Umbruch als Silbentrennung
+ * statt als bewusster Wortgrenzen-Umbruch gilt. */
+ private static final double VOLLZEILE_MINDESTANTEIL = 0.7;
+
+ /** Anzahl Zeilen vor/nach einer Kandidatenzeile, die für die lokale Spaltenbreiten-Schätzung
+ * herangezogen werden (siehe {@link #typischeZeilenlaenge}). */
+ private static final int VOLLZEILE_FENSTER = 20;
+
// BMJV-Entwurfsvorlagen zeichnen das hängende öffnende Anführungszeichen im Content-Stream
// NACH dem ersten Element der zitierten Passage: „(1) „ Ungeachtet…“ statt „„(1) Ungeachtet…“,
// „§ 19„“ statt „„§ 19“.
@@ -183,7 +194,11 @@ public final class TextBereiniger {
* <li><b>Markerlos</b> (Bundestags-Drucksachen: „Schwel“ + „lenwertes“): Reguläre Umbrüche
* enden dort mit Leerzeichen vor dem Zeilenumbruch; endet eine Zeile direkt mit einem
* Buchstaben und beginnt die Folgezeile klein, ist es eine Trennung → ohne Leerzeichen
- * zusammenziehen.
+ * zusammenziehen. Das trifft aber nur zu, wenn die Zeile (fast) die volle Spaltenbreite
+ * ausnutzt — sonst wäre der Umbruch dort nicht nötig gewesen. Kurze, bewusst
+ * abgebrochene Zeilen (z.B. ein Stichwort vor einer hängend eingerückten Definition:
+ * „…Nachhaltigkeitssiegels“ + „das Anbringen …“) werden deshalb ausgenommen — sie sind
+ * ein Wortgrenzen-Umbruch, keine Silbentrennung, auch wenn das Trailing-Space-Signal fehlt.
* </ul>
*/
private static ArrayList<String> verbindeUmbrueche(List<String> zeilen) {
@@ -198,7 +213,10 @@ public final class TextBereiniger {
while (true) {
var gestutzt = zeile.stripTrailing();
var mitTrennstrich = endetMitSilbentrennung(gestutzt);
- var markerlos = markerlosAktiv && endetMarkerlos(zeile);
+ var markerlos =
+ markerlosAktiv
+ && endetMarkerlos(zeile)
+ && gestutzt.length() >= typischeZeilenlaenge(zeilen, i) * VOLLZEILE_MINDESTANTEIL;
if (!mitTrennstrich && !markerlos) {
break;
}
@@ -266,6 +284,37 @@ public final class TextBereiniger {
return mitTrailingSpace > 0 && mitTrailingSpace * 4 >= nichtLeer;
}
+ /**
+ * Typische „volle“ Zeilenlänge im Umfeld von {@code zentrum} ({@link #VOLLZEILE_PERZENTIL}-
+ * Perzentil der gestutzten Längen nichtleerer Zeilen in einem Fenster von {@link
+ * #VOLLZEILE_FENSTER} Zeilen davor/danach) — ein grober Näherungswert für die lokale
+ * Spaltenbreite, ohne auf PDF-Positionsdaten zugreifen zu müssen. Lokal statt dokumentweit, weil
+ * ein einziges Dokument Abschnitte mit unterschiedlicher Spaltenbreite mischen kann (z.B.
+ * schmalerer Regelungstext vs. breitere Begründung in Regierungsentwürfen) — eine dokumentweite
+ * Kennzahl würde dort die kürzere Spalte systematisch benachteiligen. Vereinzelte überlange
+ * Zeilen (z.B. selbst fälschlich verklebte Umbrüche) dürfen den Wert nicht verzerren, daher ein
+ * hohes Perzentil statt des reinen Maximums.
+ */
+ private static int typischeZeilenlaenge(List<String> zeilen, int zentrum) {
+ var laengen = new ArrayList<Integer>();
+ int von = Math.max(0, zentrum - VOLLZEILE_FENSTER);
+ int bis = Math.min(zeilen.size(), zentrum + VOLLZEILE_FENSTER + 1);
+ for (int i = von; i < bis; i++) {
+ var zeile = zeilen.get(i);
+ if (zeile.isBlank()) {
+ continue;
+ }
+ laengen.add(zeile.stripTrailing().length());
+ }
+ if (laengen.isEmpty()) {
+ return 0;
+ }
+ laengen.sort(null);
+ int index = (int) (laengen.size() * VOLLZEILE_PERZENTIL);
+ index = Math.min(index, laengen.size() - 1);
+ return laengen.get(index);
+ }
+
private static String strippeZeilenenden(List<String> zeilen) {
var sb = new StringBuilder();
for (var zeile : zeilen) {
diff --git a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
index 4140357..68fe2b8 100644
--- a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
+++ b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
@@ -460,7 +460,8 @@ public final class BefehlAnwender {
}
// Neufassung eines Satzes / einer Nummer / eines Buchstabens: Bereich ersetzen.
- return bearbeiteText(normen, befehl, text -> TextErgebnis.ok(befehl.neuerText().strip()));
+ return bearbeiteText(
+ normen, befehl, text -> TextErgebnis.ok(normalisiereZitatText(befehl.neuerText())));
}
/** Ein Ziel (Absatz, Satz, Nummer, Buchstabe) wird durch einen Block ersetzt (ggf. 1 → N). */