aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/Aenderungsbefehl.java2
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java33
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java12
-rw-r--r--src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java34
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java31
-rw-r--r--src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java41
-rw-r--r--src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java14
-rw-r--r--src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java68
8 files changed, 212 insertions, 23 deletions
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/Aenderungsbefehl.java b/src/main/java/eu/mulk/aendggner/aenderung/Aenderungsbefehl.java
index a80e09a..47bca25 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/Aenderungsbefehl.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/Aenderungsbefehl.java
@@ -22,6 +22,8 @@ public sealed interface Aenderungsbefehl {
PARAGRAPH,
ABSATZ,
SATZ,
+ /** Die Hälfte eines am Strichpunkt geteilten Satzes; sie beginnt keinen neuen Satz. */
+ HALBSATZ,
NUMMER,
BUCHSTABE
}
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 c825a64..ef49b7b 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
@@ -180,7 +180,7 @@ final class BefehlErkenner {
// ersetzt: „…““ — der Ersatz steht als Zitatblock hinter dem Doppelpunkt.
private static final Pattern PUNKT_DURCH_WORTLAUT =
Pattern.compile(
- "^(?:In )?(.+?) (?:wird|werden) (der Punkt|das Komma|das Semikolon)"
+ "^(?:In )?(.+?) (?:wird|werden) (der Punkt|das Komma|das Semikolon|der Strichpunkt)"
+ " am Ende(?: des Satzes)? durch "
+ "(?:die |den )?folgende[n]? (?:Wörter|Wortlaut) ersetzt: "
+ Z
@@ -194,12 +194,12 @@ final class BefehlErkenner {
// prüft, statt es an das Einheitsende zu heften.
private static final Pattern SATZZEICHEN_ERSETZUNG_OHNE_STELLE =
Pattern.compile(
- "^(Der Punkt|Das Komma|Das Semikolon)(?: am Ende(?: des Satzes)?)? wird durch "
+ "^(Der Punkt|Das Komma|Das Semikolon|Der Strichpunkt)(?: am Ende(?: des Satzes)?)? wird durch "
+ "(ein Komma und "
+ WOERTER
+ " "
+ Z
- + "|ein Komma|einen Punkt|ein Semikolon|"
+ + "|ein Komma|einen Punkt|ein Semikolon|einen Strichpunkt|"
+ WOERTER
+ " "
+ Z
@@ -209,13 +209,13 @@ final class BefehlErkenner {
// „am Ende“ ist wie oben entbehrlich.
private static final Pattern SATZZEICHEN_ERSETZUNG =
Pattern.compile(
- "^(?:In )?(.+?) (?:wird|werden) (der Punkt|das Komma|das Semikolon)"
+ "^(?:In )?(.+?) (?:wird|werden) (der Punkt|das Komma|das Semikolon|der Strichpunkt)"
+ "(?: am Ende(?: des Satzes)?)? durch "
+ "(ein Komma und "
+ WOERTER
+ " "
+ Z
- + "|ein Komma|einen Punkt|ein Semikolon|"
+ + "|ein Komma|einen Punkt|ein Semikolon|einen Strichpunkt|"
+ WOERTER
+ " "
+ Z
@@ -473,7 +473,7 @@ final class BefehlErkenner {
+ WOERTER
+ " "
+ Z
- + "(?: am Ende)? durch (ein Komma|ein Semikolon|einen Punkt) ersetzt\\.$");
+ + "(?: am Ende)? durch (ein Komma|ein Semikolon|einen Strichpunkt|einen Punkt) ersetzt\\.$");
// „In Satz 2 wird nach dem Wort «1» ein Komma und werden die Wörter «2» eingefügt.“
private static final Pattern KOMMA_UND_WOERTER_EINFUEGUNG =
@@ -518,6 +518,8 @@ final class BefehlErkenner {
+ Z
+ "\\.?$");
+ private static final Pattern HALBSATZ = Pattern.compile("Halbsatz(?: \\d+[a-z]?)?");
+
private static final Pattern EBENE_BEZEICHNUNG =
Pattern.compile(
"^(?:(?:§|Art\\.) (\\d+[a-z]?)|(?:Absatz|Abs\\.) (\\d+[a-z]?)|Satz(?: (\\d+[a-z]?))?|Sätze"
@@ -583,7 +585,7 @@ final class BefehlErkenner {
+ WOERTER
+ " "
+ Z
- + "|(ein Komma|ein Semikolon))$");
+ + "|(ein Komma|ein Semikolon|einen Strichpunkt))$");
// „… ein Komma eingefügt und werden …“: Trennstellen eines Verbundbefehls sind „ und “ (ggf. mit
// Komma) bzw. „, “ direkt vor „wird/werden“. Innerhalb von Zitaten steht „ und “ als «n»
@@ -597,7 +599,7 @@ final class BefehlErkenner {
+ "|, (?=(?:die (?:Wörter|Worte)|das Wort|die Angabe|die Zahl) «)"
// Ebenso vor einer Satzzeichen-Klausel („… ersetzt, der Punkt am Ende durch ein
// Semikolon ersetzt und …“).
- + "|, (?=(?:der Punkt|das Komma|das Semikolon) am Ende)");
+ + "|, (?=(?:der Punkt|das Komma|das Semikolon|der Strichpunkt) am Ende)");
private static final Pattern WIRD_WERDEN = Pattern.compile(" (?:wird|werden) ");
// Rechte Verbundklausel, die ihre Fundstelle als Struktureinheit nennt („in Satz 1 wird …“,
@@ -622,7 +624,7 @@ final class BefehlErkenner {
"^(?:In )?(.+?) (?:wird|werden) (?:jeweils )?(nach|vor) "
+ "(?:dem Wort|den (?:Wörtern|Worten)|der Angabe|der Zahl|der Verweisung) "
+ Z
- + " (ein Komma|ein Semikolon|einen Punkt) eingefügt\\.$");
+ + " (ein Komma|ein Semikolon|einen Strichpunkt|einen Punkt) eingefügt\\.$");
// „Nach der Angabe «1» wird die Angabe «2» eingefügt.“ — Anker zuerst, ohne eigene Stelle (nutzt
// den Kontext). Tritt vor allem als rechte Klausel eines Verbundbefehls auf.
@@ -634,7 +636,7 @@ final class BefehlErkenner {
+ WOERTER
+ " "
+ Z
- + "|(ein Komma|ein Semikolon)) eingefügt\\.$");
+ + "|(ein Komma|ein Semikolon|einen Strichpunkt)) eingefügt\\.$");
private BefehlErkenner() {}
@@ -1646,7 +1648,7 @@ final class BefehlErkenner {
// Klausel, deren Subjekt ein Satzzeichen ist („das Komma wird durch … ersetzt“).
private static final Pattern SATZZEICHEN_SUBJEKT =
- Pattern.compile("(?i)(?:der Punkt|das Komma|das Semikolon)\\b");
+ Pattern.compile("(?i)(?:der Punkt|das Komma|das Semikolon|der Strichpunkt)\\b");
private static Optional<Aenderungsbefehl> erkenneVerbund(
String text, Stelle kontext, ZitatExtraktor.Ergebnis zitate, Provenienz provenienz) {
@@ -2032,6 +2034,11 @@ final class BefehlErkenner {
private record EbeneBezeichnung(Ebene ebene, String bezeichnung) {}
private static Optional<EbeneBezeichnung> ebeneUndBezeichnung(String phrase) {
+ // Der Halbsatz steht außerhalb des Musters, damit dessen Gruppennummern unberührt bleiben;
+ // eine Bezeichnung führt er nicht, denn angefügt wird stets hinter dem Strichpunkt.
+ if (HALBSATZ.matcher(phrase.strip()).matches()) {
+ return Optional.of(new EbeneBezeichnung(Ebene.HALBSATZ, null));
+ }
var m = EBENE_BEZEICHNUNG.matcher(phrase.strip());
if (!m.matches()) {
return Optional.empty();
@@ -2280,6 +2287,7 @@ final class BefehlErkenner {
case PARAGRAPH -> new Stelle.Paragraph(nummer);
case ABSATZ -> new Stelle.AbsatzNr(nummer);
case SATZ -> new Stelle.SatzNr(nummer);
+ case HALBSATZ -> new Stelle.HalbsatzNr(nummer);
case NUMMER -> new Stelle.NummerNr(nummer);
case BUCHSTABE -> new Stelle.BuchstabeNr(nummer);
};
@@ -2366,7 +2374,8 @@ final class BefehlErkenner {
return switch (Character.toLowerCase(phrase.charAt(0)) + phrase.substring(1)) {
case "der Punkt", "einen Punkt" -> ".";
case "das Komma", "ein Komma" -> ",";
- case "das Semikolon", "ein Semikolon" -> ";";
+ // „Strichpunkt“ ist die deutsche Nebenform des Semikolons; Rheinland-Pfalz schreibt sie.
+ case "das Semikolon", "ein Semikolon", "der Strichpunkt", "einen Strichpunkt" -> ";";
default -> throw new IllegalArgumentException("Unbekanntes Satzzeichen: " + phrase);
};
}
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 9176f89..466adfe 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java
@@ -54,10 +54,18 @@ public final class StellenParser {
// „in dem Satzteil vor Nummer 1“, „in der Angabe vor Nummer 1“, bayerisch auch „Satzteil nach
// Nr. 3“ — verfeinernde Chapeau-Angaben ohne eigene Stelle-Komponente; die Ersetzung sucht
// ohnehin im Text der umgebenden Stelle.
+ //
+ // Die zweite Alternative nennt keinen Bezugspunkt: „In der Einleitung“ (Rheinland-Pfalz) meint
+ // dasselbe wie „im Satzteil vor Nummer 1“, nämlich den Chapeau des Satzes, den der Rahmen
+ // bezeichnet. Auch sie trägt keine eigene Komponente. Kommt der Zieltext außer im Chapeau auch
+ // in einer Nummer vor, so meldet die Auflösung Mehrdeutigkeit — der richtige Ausgang; eine
+ // echte Verengung auf den Satzteil vor der ersten Marke wäre eine eigene Komponente und gehört
+ // nicht hierher.
private static final Pattern CHAPEAU_QUALIFIER =
Pattern.compile(
- "(?i)(?:im |in dem |in der |dem |der )?(?:Satzteil|Angabe) (?:vor|nach) "
- + "(?:Nummer|Nr\\.|Buchstabe|Buchst\\.|Satz|Absatz|Abs\\.) \\S+");
+ "(?i)(?:im |in dem |in der |dem |der )?(?:(?:Satzteil|Angabe) (?:vor|nach) "
+ + "(?:Nummer|Nr\\.|Buchstabe|Buchst\\.|Satz|Absatz|Abs\\.) \\S+"
+ + "|(?:Einleitungssatz|Eingangssatz|Einleitung)(?!\\p{L}))");
/**
* Wahr, wenn die Phrase ausschließlich aus einem Chapeau-Qualifier besteht (z.B. „im Satzteil vor
diff --git a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
index 56b610e..64c6564 100644
--- a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
+++ b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
@@ -591,6 +591,7 @@ public final class BefehlAnwender {
bezeichnung, s.stelle().paragraph().map(Stelle.Paragraph::sigel).orElse("§"));
case ABSATZ -> new Stelle.AbsatzNr(bezeichnung);
case SATZ -> new Stelle.SatzNr(bezeichnung);
+ case HALBSATZ -> new Stelle.HalbsatzNr(bezeichnung);
case NUMMER -> new Stelle.NummerNr(bezeichnung);
case BUCHSTABE -> new Stelle.BuchstabeNr(bezeichnung);
};
@@ -1276,6 +1277,15 @@ public final class BefehlAnwender {
normen.addAll(vonIndex, neue);
yield angewandt(befehl, neue.stream().map(Norm::enbez).toList());
}
+ // Ein Halbsatz ist keine selbständig bezeichnete Einheit: Er lässt sich anfügen
+ // (siehe wendeAnfuegungAn), aber nicht als Ziel ersetzen oder einfügen — welche Hälfte
+ // eines Satzes gemeint wäre, sagt keine Bezeichnung.
+ case HALBSATZ ->
+ manuell(
+ befehl,
+ Grund.NICHT_UNTERSTUETZT,
+ "Der Halbsatz ist keine selbständig bezeichnete Einheit; nur seine Anfügung ist"
+ + " umgesetzt.");
};
}
@@ -1474,6 +1484,15 @@ public final class BefehlAnwender {
}
return TextErgebnis.ok(neuerText);
});
+ // Ein Halbsatz ist keine selbständig bezeichnete Einheit: Er lässt sich anfügen
+ // (siehe wendeAnfuegungAn), aber nicht als Ziel ersetzen oder einfügen — welche Hälfte
+ // eines Satzes gemeint wäre, sagt keine Bezeichnung.
+ case HALBSATZ ->
+ manuell(
+ befehl,
+ Grund.NICHT_UNTERSTUETZT,
+ "Der Halbsatz ist keine selbständig bezeichnete Einheit; nur seine Anfügung ist"
+ + " umgesetzt.");
};
}
@@ -1636,6 +1655,21 @@ public final class BefehlAnwender {
normen,
befehl,
text -> TextErgebnis.ok(text.stripTrailing() + " " + befehl.text().strip()));
+ // Ein Halbsatz beginnt keinen neuen Satz, sondern setzt den bestehenden hinter dem
+ // Strichpunkt fort. Angehängt wird deshalb wie beim Satz — aber nur, wenn der Zieltext
+ // tatsächlich auf einen Strichpunkt endet: Sonst stünde der Halbsatz hinter einem Punkt und
+ // wäre gerade kein Halbsatz. Regelmäßig setzt ihn der Verbund unmittelbar zuvor.
+ case HALBSATZ ->
+ bearbeiteText(
+ normen,
+ befehl,
+ text ->
+ text.stripTrailing().endsWith(";")
+ ? TextErgebnis.ok(text.stripTrailing() + " " + befehl.text().strip())
+ : TextErgebnis.fehler(
+ "Der Halbsatz setzt einen Strichpunkt voraus; der Zieltext endet nicht"
+ + " darauf.",
+ Grund.BESTAND_WIDERSPRICHT));
case NUMMER, BUCHSTABE ->
bearbeiteText(
normen,
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 41f30e5..d0273ad 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -6,6 +6,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl;
+import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Ersetzung;
+import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Sammelbefehl;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.StrukturEinfuegung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.UnbekannterBefehl;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.WortAnker;
@@ -1878,20 +1880,31 @@ class EndToEndTest {
.filter(b -> b.provenienz().originalText().contains("die Worte"))
.toList();
assertThat(wortbefehle).hasSizeGreaterThanOrEqualTo(7);
- assertThat(wortbefehle).filteredOn(b -> b instanceof UnbekannterBefehl).hasSize(1);
-
- // Drei benannte Idiom-Grenzen, alle im Wortlaut festgehalten:
- // 5. a) aa) — Satzzeichen-Ersetzung im Verbund mit einer Halbsatz-Anfügung („wird der Punkt
- // durch einen Strichpunkt ersetzt und folgender Halbsatz angefügt“).
- // 7. a) aa) — „In der Einleitung“ als Ziel, also der Chapeau eines Satzes mit Nummern.
- // 12. — die Verweisung auf einen anderen Punkt desselben Artikels („Die Inhaltsübersicht wird
- // entsprechend der vorstehenden Nummer 8 Buchst. a geändert“).
+ assertThat(wortbefehle).noneMatch(b -> b instanceof UnbekannterBefehl);
+
+ // Der Strichpunkt-Verbund („wird der Punkt durch einen Strichpunkt ersetzt und folgender
+ // Halbsatz angefügt“) und der bezugspunktlose Chapeau („In der Einleitung“) werden gelesen;
+ // dass beide gleichwohl nicht greifen, liegt an der Quelle und nicht am Werkzeug: Der Stamm
+ // ist hier die Nachfassung, ihr Wortlaut trägt die Änderung bereits.
+ assertThat(befehlAn(parseErgebnis, "5. a) aa)")).isInstanceOf(Sammelbefehl.class);
+ assertThat(befehlAn(parseErgebnis, "7. a) aa)")).isInstanceOf(Ersetzung.class);
+
+ // Eine benannte Grenze bleibt: die Verweisung auf einen anderen Punkt desselben Artikels
+ // („Die Inhaltsübersicht wird entsprechend der vorstehenden Nummer 8 Buchst. a geändert“).
var unerkannt =
parseErgebnis.befehle().stream()
.filter(b -> b instanceof UnbekannterBefehl)
.map(b -> b.provenienz().gliederungsPfad())
.toList();
- assertThat(unerkannt).containsExactly("5. a) aa)", "7. a) aa)", "12.");
+ assertThat(unerkannt).containsExactly("12.");
+ }
+
+ private static Aenderungsbefehl befehlAn(
+ AenderungsgesetzParser.ParseErgebnis ergebnis, String gliederungsPfad) {
+ return ergebnis.befehle().stream()
+ .filter(b -> gliederungsPfad.equals(b.provenienz().gliederungsPfad()))
+ .findFirst()
+ .orElseThrow();
}
/**
diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
index 7742a3a..80234b8 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
@@ -1473,4 +1473,45 @@ class BefehlErkennerTest {
assertThat(BefehlErkenner.erkenne(text, Stelle.LEER, zitate, PROV))
.containsInstanceOf(Ersetzung.class);
}
+
+ /**
+ * Rheinland-Pfalz: „In Satz 2 wird der Punkt durch einen Strichpunkt ersetzt und folgender
+ * Halbsatz angefügt: „…““. Zwei Neuerungen in einem Satz — der Strichpunkt als deutsche Nebenform
+ * des Semikolons und der Halbsatz als Ebene der Anfügung. Die Verbundmechanik selbst bestand
+ * bereits.
+ */
+ @Test
+ void erkenntStrichpunktErsetzungMitHalbsatzAnfuegung() {
+ var befehl =
+ erkenne(
+ "In Satz 2 wird der Punkt durch einen Strichpunkt ersetzt und folgender Halbsatz"
+ + " angefügt: „die Bearbeitungszeit der Abschlussarbeit zählt zu den"
+ + " Fachstudien.“",
+ new Stelle(List.of(new Stelle.Paragraph("8"), new Stelle.AbsatzNr("1"))));
+
+ assertThat(befehl).containsInstanceOf(Sammelbefehl.class);
+ var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle();
+ assertThat(teile).hasSize(2);
+
+ assertThat(teile.get(0)).isInstanceOf(Ersetzung.class);
+ var ersetzung = (Ersetzung) teile.get(0);
+ assertThat(ersetzung.alt()).isEqualTo(".");
+ assertThat(ersetzung.neu()).isEqualTo(";");
+ assertThat(ersetzung.stelle().anzeigeText()).isEqualTo("§ 8 Absatz 1 Satz 2");
+
+ assertThat(teile.get(1)).isInstanceOf(Anfuegung.class);
+ var anfuegung = (Anfuegung) teile.get(1);
+ assertThat(anfuegung.ebene()).isEqualTo(Ebene.HALBSATZ);
+ assertThat(anfuegung.text()).contains("die Bearbeitungszeit der Abschlussarbeit");
+ }
+
+ /** Der Strichpunkt gilt auch als Subjekt („Der Strichpunkt wird durch ein Komma ersetzt“). */
+ @Test
+ void erkenntDenStrichpunktAlsSubjekt() {
+ var befehl = erkenne("Der Strichpunkt am Ende wird durch ein Komma ersetzt.", PARAGRAPH_5);
+ assertThat(befehl).containsInstanceOf(Ersetzung.class);
+ var ersetzung = (Ersetzung) befehl.orElseThrow();
+ assertThat(ersetzung.alt()).isEqualTo(";");
+ assertThat(ersetzung.neu()).isEqualTo(",");
+ }
}
diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java
index ee7e5c7..13e4442 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java
@@ -138,4 +138,18 @@ class StellenParserTest {
StellenParser.parse("Absatz 1 in der Angabe vor Nummer 1").orElseThrow().anzeigeText())
.isEqualTo("Absatz 1");
}
+
+ /**
+ * „In der Einleitung“ (Rheinland-Pfalz) ist ein Chapeau-Lokator wie „im Satzteil vor Nummer 1“:
+ * Er nennt keinen Bezugspunkt und trägt deshalb keine eigene Komponente — die Operation läuft auf
+ * der Stelle, die der Rahmen bezeichnet.
+ */
+ @Test
+ void einleitungIstEinChapeauLokator() {
+ assertThat(StellenParser.istNurChapeau("In der Einleitung")).isTrue();
+ assertThat(StellenParser.istNurChapeau("Im Eingangssatz")).isTrue();
+ assertThat(StellenParser.istNurChapeau("in dem Einleitungssatz")).isTrue();
+ // Ein Wort, das bloß so anfängt, ist keiner.
+ assertThat(StellenParser.istNurChapeau("In der Einleitungsformel")).isFalse();
+ }
}
diff --git a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
index a1f5cff..39c71cc 100644
--- a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
+++ b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
@@ -1891,4 +1891,72 @@ class BefehlAnwenderTest {
assertThat(ergebnis.neu().norm("§ 108").orElseThrow().absaetze().get(0).text())
.doesNotContain("oder Absatz 4");
}
+
+ /**
+ * Ein Halbsatz setzt den Satz hinter dem Strichpunkt fort; er beginnt keinen neuen. Angefügt wird
+ * er deshalb wie ein Satz — aber nur hinter einem Strichpunkt. Endet der Zieltext auf einen
+ * Punkt, so wäre das Angefügte gerade kein Halbsatz, und der Befehl bleibt liegen.
+ */
+ @Test
+ void halbsatzWirdHinterDemStrichpunktAngefuegt() {
+ var gesetz =
+ new Gesetz(
+ "TestG",
+ "Testgesetz",
+ "TestG",
+ List.of(
+ new Norm(
+ "§ 8",
+ null,
+ null,
+ List.of(new Absatz("1", "Erster Satz. Der zweite Satz endet hier;")),
+ false)));
+
+ var befehl =
+ new Anfuegung(
+ stelle(new Stelle.Paragraph("8"), new Stelle.AbsatzNr("1")),
+ Ebene.HALBSATZ,
+ null,
+ "die Bearbeitungszeit zählt zu den Fachstudien.",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz, List.of(befehl));
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(ergebnis.neu().norm("§ 8").orElseThrow().absaetze().get(0).text())
+ .isEqualTo(
+ "Erster Satz. Der zweite Satz endet hier; die Bearbeitungszeit zählt zu den"
+ + " Fachstudien.");
+ }
+
+ @Test
+ void halbsatzOhneStrichpunktBleibtLiegen() {
+ var gesetz =
+ new Gesetz(
+ "TestG",
+ "Testgesetz",
+ "TestG",
+ List.of(
+ new Norm(
+ "§ 8",
+ null,
+ null,
+ List.of(new Absatz("1", "Erster Satz. Der zweite Satz endet hier.")),
+ false)));
+
+ var befehl =
+ new Anfuegung(
+ stelle(new Stelle.Paragraph("8"), new Stelle.AbsatzNr("1")),
+ Ebene.HALBSATZ,
+ null,
+ "die Bearbeitungszeit zählt zu den Fachstudien.",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz, List.of(befehl));
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.MANUELL_PRUEFEN);
+ assertThat(ergebnis.protokoll().get(0).grund()).isEqualTo(Grund.BESTAND_WIDERSPRICHT);
+ assertThat(ergebnis.protokoll().get(0).begruendung())
+ .contains("setzt einen Strichpunkt voraus");
+ assertThat(ergebnis.neu().norm("§ 8").orElseThrow().absaetze().get(0).text())
+ .isEqualTo("Erster Satz. Der zweite Satz endet hier.");
+ }
}