diff options
Diffstat (limited to 'src/main')
3 files changed, 112 insertions, 8 deletions
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/Aenderungsbefehl.java b/src/main/java/eu/mulk/aendggner/aenderung/Aenderungsbefehl.java index 7fc5e8a..a80e09a 100644 --- a/src/main/java/eu/mulk/aendggner/aenderung/Aenderungsbefehl.java +++ b/src/main/java/eu/mulk/aendggner/aenderung/Aenderungsbefehl.java @@ -40,7 +40,11 @@ public sealed interface Aenderungsbefehl { /** „… werden die Wörter „A“ durch die Wörter „B“ ersetzt.“ */ record Ersetzung( Stelle stelle, String alt, String neu, boolean jeweils, boolean amEnde, Provenienz provenienz) - implements Aenderungsbefehl {} + implements Aenderungsbefehl { + public Ersetzung mitStelle(Stelle neueStelle) { + return new Ersetzung(neueStelle, alt, neu, jeweils, amEnde, provenienz); + } + } /** „§ X wird wie folgt gefasst: „…““ */ record Neufassung(Stelle stelle, String neuerText, Provenienz provenienz) @@ -66,7 +70,11 @@ public sealed interface Aenderungsbefehl { /** „… werden nach dem Wort „A“ die Wörter „B“ eingefügt.“ */ record WoerterEinfuegung(Stelle stelle, WortAnker anker, String woerter, Provenienz provenienz) - implements Aenderungsbefehl {} + implements Aenderungsbefehl { + public WoerterEinfuegung mitStelle(Stelle neueStelle) { + return new WoerterEinfuegung(neueStelle, anker, woerter, provenienz); + } + } /** * „Nach § 28 wird folgender § 28a eingefügt: „…““ @@ -111,7 +119,11 @@ public sealed interface Aenderungsbefehl { /** „… werden die Wörter „A“ gestrichen.“ */ record Streichung(Stelle stelle, String woerter, Provenienz provenienz) - implements Aenderungsbefehl {} + implements Aenderungsbefehl { + public Streichung mitStelle(Stelle neueStelle) { + return new Streichung(neueStelle, woerter, provenienz); + } + } /** „Der bisherige Absatz 2 wird Absatz 3.“ */ record Umnummerierung(Stelle stelle, Stelle neu, Provenienz provenienz) 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 192d244..c825a64 100644 --- a/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java +++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java @@ -1757,6 +1757,11 @@ final class BefehlErkenner { // Nach einer Umnummerierung wird die Wortoperation nicht auf die neue Stelle festgenagelt: sie // löst norm-weit auf (findet ihren Zieltext im umbenannten Absatz, ohne eine womöglich schon // bestehende Zielnummer fälschlich zu treffen) — das übernehmen die Zweige weiter unten. + // Bleibt die weite Auflösung mehrdeutig, so entscheidet erst beim Anwenden die soeben + // umnummerierte Einheit (siehe BefehlAnwender.rueckfall). Dort und nicht hier gehört die + // Regel hin: Ob ein Anker mehrdeutig ist, steht erst nach den vorangegangenen Punkten fest — + // der Erkenner sieht nur das Ausgangsgesetz, in dem es die neue Bezeichnung noch gar nicht + // gibt. if (REINE_WORT_OPERATION.matcher(rechts).lookingAt() && !(linksBefehl instanceof Umnummerierung)) { var praefix0 = lokativerPraefix(links); diff --git a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java index a46defb..56b610e 100644 --- a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java +++ b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java @@ -35,6 +35,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.jspecify.annotations.Nullable; /** @@ -107,7 +108,29 @@ public final class BefehlAnwender { ergebnisse[index] = angewandt(schritt.teil(), "(Gesetzesüberschrift)"); continue; } - ergebnisse[index] = wendeAn(normen, gliederungen, schritt.teil()); + var ergebnis = wendeAn(normen, gliederungen, schritt.teil()); + if (ergebnis.status() == Status.MANUELL_PRUEFEN + && ergebnis.grund() == Grund.MEHRDEUTIG + && schritt.rueckfallStelle() != null) { + var enger = mitStelle(schritt.teil(), schritt.rueckfallStelle()); + // Der zweite Versuch ist gefahrlos: Jeder Zweig meldet den Fehlschlag, bevor er schreibt. + // Gelingt er nicht, so bleibt der erste Protokolleintrag stehen — die Mehrdeutigkeit ist + // der wahre Befund und darf nicht durch ein „nicht auffindbar“ der zweiten Auflösung + // verdeckt werden. + if (enger != null) { + var zweiter = wendeAn(normen, gliederungen, enger); + if (zweiter.status() == Status.ANGEWANDT) { + ergebnis = + new AngewandteAenderung( + schritt.teil(), + Status.ANGEWANDT, + zweiter.begruendung(), + zweiter.betroffeneEnbez(), + null); + } + } + } + ergebnisse[index] = ergebnis; } protokoll.addAll(verdichte(befehle, schritte, ergebnisse)); @@ -129,8 +152,17 @@ public final class BefehlAnwender { * Protokolleintrag sein Ergebnis eingeht. Ein Verbund ({@link Sammelbefehl}) zerfällt in je einen * Schritt pro Teilbefehl; jeder andere Befehl ist sein eigener einziger Schritt ({@code * ganzerBefehl}). + * + * @param rueckfallStelle die Einheit, die eine vorangehende Umnummerierung desselben Verbunds + * soeben neu bezeichnet hat; {@code null} außerhalb eines Verbunds und vor der ersten + * Umnummerierung. Sie kommt nur zum Zuge, wenn die norm-weite Auflösung mehrdeutig bleibt + * (siehe {@link #anwenden}). */ - private record Schritt(int befehlIndex, Aenderungsbefehl teil, boolean ganzerBefehl) {} + private record Schritt( + int befehlIndex, + Aenderungsbefehl teil, + boolean ganzerBefehl, + @Nullable Stelle rueckfallStelle) {} /** Faltet die Befehlsliste zur Schrittliste auf, in Dokumentreihenfolge. */ private static List<Schritt> schritte(List<Aenderungsbefehl> befehle) { @@ -140,23 +172,78 @@ public final class BefehlAnwender { if (befehl instanceof Sammelbefehl s) { falte(s, i, schritte); } else { - schritte.add(new Schritt(i, befehl, true)); + schritte.add(new Schritt(i, befehl, true, null)); } } return schritte; } private static void falte(Sammelbefehl befehl, int befehlIndex, List<Schritt> ziel) { + // Innerhalb eines Verbunds merkt sich das Auffalten die zuletzt gesehene Umnummerierung: Ihre + // neue Bezeichnung ist der Rückfall für die nachfolgenden Wortoperationen desselben Verbunds. + // Nur vorwärts und nur innerhalb dieses Verbunds — ein späterer Punkt des Dokuments hat mit + // ihr nichts zu schaffen. + Stelle zuletztUmnummeriert = null; for (var teil : befehl.teilbefehle()) { if (teil instanceof Sammelbefehl geschachtelt) { falte(geschachtelt, befehlIndex, ziel); - } else { - ziel.add(new Schritt(befehlIndex, teil, false)); + continue; + } + ziel.add(new Schritt(befehlIndex, teil, false, rueckfall(zuletztUmnummeriert, teil))); + if (teil instanceof Umnummerierung um && !um.neu().istLeer()) { + zuletztUmnummeriert = um.neu(); } } } /** + * Die Stelle, auf die eine Begleitklausel zurückfällt, wenn ihre norm-weite Auflösung mehrdeutig + * bleibt: die soeben umnummerierte Einheit. + * + * <p>Der Vorrang bleibt bei der norm-weiten Auflösung, denn nicht jede Begleitklausel meint die + * umnummerierte Einheit — „Die bisherige Nr. 11 wird Nr. 12 und die Angabe ‚schriftliche‘ wird + * gestrichen“ kann sehr wohl anderswo greifen. Erst wenn die weite Suche mehrere Fundstellen + * findet, entscheidet der engere Skopus; findet sie gar keine, so meint der Befehl etwas anderes + * und bleibt zu Recht liegen. + * + * <p>Der Rückfall greift nur bei Wortoperationen — Strukturbefehle nennen ihren Skopus ohnehin — + * und nur, wenn die Klausel keine eigene feinere Einheit nennt: Wer „in Satz 3“ sagt, hat seinen + * Skopus bereits bestimmt. + */ + private static @Nullable Stelle rueckfall( + @Nullable Stelle zuletztUmnummeriert, Aenderungsbefehl teil) { + if (zuletztUmnummeriert == null + || !(teil instanceof Ersetzung + || teil instanceof Streichung + || teil instanceof WoerterEinfuegung)) { + return null; + } + if (zuletztUmnummeriert.paragraph().isPresent()) { + return zuletztUmnummeriert; + } + // Die neue Bezeichnung tritt zum Rahmen der Klausel hinzu — aber nur, wenn dieser nicht + // selbst schon eine Einheit derselben Art nennt. „In Absatz 3 …“ neben einer Umnummerierung + // auf Absatz 8 ergäbe „Absatz 3 Absatz 8“; wo das droht, wird nicht geraten. + var vorhandeneArten = + teil.stelle().komponenten().stream().map(k -> k.getClass()).collect(Collectors.toSet()); + if (zuletztUmnummeriert.komponenten().stream() + .anyMatch(k -> vorhandeneArten.contains(k.getClass()))) { + return null; + } + return teil.stelle().plus(zuletztUmnummeriert); + } + + /** Derselbe Befehl, auf eine engere Stelle festgelegt. */ + private static @Nullable Aenderungsbefehl mitStelle(Aenderungsbefehl befehl, Stelle stelle) { + return switch (befehl) { + case Ersetzung e -> e.mitStelle(stelle); + case Streichung st -> st.mitStelle(stelle); + case WoerterEinfuegung w -> w.mitStelle(stelle); + default -> null; + }; + } + + /** * Anwendungsreihenfolge der Schritte. Sie folgt dem Dokument, mit einer Ausnahme: * Umnummerierungen beziehen sich stets auf die ursprüngliche Zählung, nicht auf den Stand nach * den vorangegangenen Punkten. Wer eine Bezeichnung räumt, muss daher vor den kommen, der sie neu |
