Generalize Bavarian loader to a shared Landesrecht loader

Prepare wave six (Landesrecht of the remaining Länder) by lifting the
Bavaria-specific stem-law loader to a shared one. Unlike Bavaria (Art./§
inversion), the other Länder structure their stem laws in §, so the loader must
handle both sigils.

- Move gesetz/bayern/{BayRechtLoader,BayRechtTextParser} to
  gesetz/land/{LandesRechtLoader,LandesRechtTextParser}; the norm head now
  matches "§ N" as well as "Art. N" and derives the sigil per norm from the
  match. Cross-reference keywords in the norm head are excluded only as whole
  words (so a title "Satzungen" no longer trips on "Satz"), and the juris
  abbreviation may be multi-token ("(GO NRW)").
- Derive the superscript mode data-drivenly from the loaded stem law
  (Superskript.traegtSatznummern) instead of from "is it gii-XML": Bavaria and
  Lower Saxony keep their amtliche Satznummern, the Bund and Länder without
  official sentence numbering drop them.
- Recognize the neufassung idiom "erhält/erhalten folgende Fassung" (Schleswig-
  Holstein, Niedersachsen) via a NEUFASSUNG_VERB building block, additive to
  "wird/werden wie folgt gefasst".

Federal and Bavarian behaviour is unchanged (219 tests green, pinned acceptance
numbers UWG 19/0, GEG 66/53, GEG-GModG 90/9, IfSG 42/24, BayJG 149/154 hold).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Change-Id: I55a70a7932bf657a2346ca70f3fa05e173bf80ad
diff --git a/FASSUNGEN.txt b/FASSUNGEN.txt
index a1361f4..52a4215 100644
--- a/FASSUNGEN.txt
+++ b/FASSUNGEN.txt
@@ -21,6 +21,59 @@
 
 
 ════════════════════════════════════════════════════════════════════════════════
+ Fassung vom 19. Juli 2026,
+ zuletzt geändert durch die am 19. Juli 2026 vorgenommenen Änderungen
+════════════════════════════════════════════════════════════════════════════════
+
+In einer sechsten Ertüchtigungswelle wird das Erzeugnis über das bayerische
+Landesrecht hinaus für das Landesrecht der übrigen Länder vorbereitet. Da diese
+ihre Stammgesetze — anders als Bayern — in Paragraphen gliedern, wird der
+bayerische Lader zu einem gemeinsamen Landesrecht-Lader verallgemeinert, der das
+Sigel (§ oder Art.) je Norm aus dem Text ableitet; der Superskript-Modus folgt
+nunmehr datengetrieben der Schreibweise des Stammgesetzes. Ferner wird die in
+mehreren Ländern übliche Neufassungsform „erhält folgende Fassung“ erkannt. Als
+Beispieldaten sind bereits Änderungsgesetze aus acht Ländern hinterlegt
+(sampledata: Berlin, Schleswig-Holstein, Hessen, Niedersachsen, Baden-Württem-
+berg, Nordrhein-Westfalen, Sachsen, Thüringen); ihre konsolidierten Stammfassungen
+und die dazugehörigen Akzeptanzfälle folgen in weiteren Fassungen dieser Welle.
+
+
+Artikel 1
+Verallgemeinerung des bayerischen Laders zu einem Landesrecht-Lader
+
+Die Klassen BayRechtLoader und BayRechtTextParser (Paket gesetz.bayern) werden zu
+LandesRechtLoader und LandesRechtTextParser (Paket gesetz.land) verallgemeinert.
+Der Normkopf erkennt statt allein „Art. N“ nunmehr „§ N“ wie „Art. N“ und leitet
+das Sigel je Norm aus dem Treffer ab (Norm.enbez), sodass die Gliederung des
+jeweiligen Landes ohne ein eigenes Land-Merkmal folgt. Das Querverweis-
+Schlüsselwort im Normkopf wird nur noch als ganzes Wort ausgeschlossen, damit ein
+Normtitel wie „Satzungen“ nicht am Verweis-Wort „Satz“ scheitert; die
+Juris-Abkürzung darf mehrteilig sein („(GO NRW)“). Das bayerische Verhalten
+bleibt unverändert.
+
+
+Artikel 2
+Datengetriebener Superskript-Modus aus der Stammfassung
+
+Ob ein Änderungsgesetz mit Erhalt der hochgestellten amtlichen Satznummern
+extrahiert wird, richtet sich nicht mehr danach, ob das Stammgesetz als gii-XML
+vorliegt, sondern danach, ob die geladene Stammfassung selbst amtliche Satznummern
+am Satzanfang trägt (Superskript.traegtSatznummern). Damit erhalten das bayerische
+Landesrecht und das niedersächsische Recht ihre Superskripte, das Bundesrecht und
+die Länder ohne amtliche Satzzählung verwerfen sie wie bisher.
+
+
+Artikel 3
+Erkennung der Neufassungsform „erhält folgende Fassung“
+
+Der Befehlserkenner erkennt die Neufassung nunmehr auch in der in Schleswig-
+Holstein und Niedersachsen üblichen Form „… erhält (erhalten) folgende Fassung:“,
+neben der bisherigen Form „… wird (werden) wie folgt gefasst:“. Die Erweiterung
+ist rein zusätzlich (Verb-Baustein NEUFASSUNG_VERB) und lässt die Muster des
+Bundes- und des bayerischen Rechts unberührt.
+
+
+════════════════════════════════════════════════════════════════════════════════
  Fassung vom 17. Juli 2026,
  zuletzt geändert durch die am 17. Juli 2026 vorgenommenen Änderungen
 ════════════════════════════════════════════════════════════════════════════════
diff --git a/src/main/java/eu/mulk/aendggner/AendGgner.java b/src/main/java/eu/mulk/aendggner/AendGgner.java
index e647b75..efa95a9 100644
--- a/src/main/java/eu/mulk/aendggner/AendGgner.java
+++ b/src/main/java/eu/mulk/aendggner/AendGgner.java
@@ -6,8 +6,9 @@
 import eu.mulk.aendggner.aenderung.parse.TextBereiniger;
 import eu.mulk.aendggner.anwendung.BefehlAnwender;
 import eu.mulk.aendggner.gesetz.Gesetz;
-import eu.mulk.aendggner.gesetz.bayern.BayRechtLoader;
+import eu.mulk.aendggner.gesetz.Superskript;
 import eu.mulk.aendggner.gesetz.gii.GiiXmlLoader;
+import eu.mulk.aendggner.gesetz.land.LandesRechtLoader;
 import eu.mulk.aendggner.synopse.HtmlRenderer;
 import eu.mulk.aendggner.synopse.SynopseBuilder;
 import java.io.IOException;
@@ -125,7 +126,8 @@
     }
 
     if (extractOnly) {
-      var extraktor = new PatchTextExtraktor(superskriptModus());
+      var gesetz = ladeStammgesetz();
+      var extraktor = new PatchTextExtraktor(superskriptModus(gesetz));
       for (var file : patches) {
         var text = extraktor.extrahiere(file);
         System.out.println(raw ? text : TextBereiniger.bereinige(text));
@@ -135,7 +137,7 @@
 
     if (dumpBefehle) {
       var gesetz = ladeStammgesetz();
-      var extraktor = new PatchTextExtraktor(superskriptModus());
+      var extraktor = new PatchTextExtraktor(superskriptModus(gesetz));
       var parser = new AenderungsgesetzParser();
       for (var file : patches) {
         var text = TextBereiniger.bereinige(extraktor.extrahiere(file));
@@ -177,7 +179,7 @@
 
     // Pipeline: Stammgesetz laden → Befehle parsen → anwenden → Synopse rendern.
     var altesGesetz = ladeStammgesetz();
-    var extraktor = new PatchTextExtraktor(superskriptModus());
+    var extraktor = new PatchTextExtraktor(superskriptModus(altesGesetz));
     var parser = new AenderungsgesetzParser();
 
     var gesetz = altesGesetz;
@@ -222,16 +224,27 @@
     return gesamtErgebnis.anzahlAngewandt() == 0 && !protokoll.isEmpty() ? 2 : 0;
   }
 
-  /** Gii-XML → {@link GiiXmlLoader}; PDF/Klartext (bayerisches Landesrecht) → {@link
-   * BayRechtLoader}. */
+  /** Gii-XML → {@link GiiXmlLoader}; PDF/Klartext (Landesrecht) → {@link LandesRechtLoader}. */
   private Gesetz ladeStammgesetz() throws Exception {
-    return istGiiXml() ? new GiiXmlLoader().load(baseFile) : new BayRechtLoader().load(baseFile);
+    return istGiiXml() ? new GiiXmlLoader().load(baseFile) : new LandesRechtLoader().load(baseFile);
   }
 
-  /** Bayerische Stammtexte tragen amtliche Satznummern — auch die Änderungsgesetze werden dann
-   * mit Superskript-Erhalt extrahiert, damit Zitate und Stammtext dieselbe Schreibweise tragen. */
-  private SuperskriptModus superskriptModus() throws IOException {
-    return istGiiXml() ? SuperskriptModus.ENTFERNEN : SuperskriptModus.BEHALTEN;
+  /**
+   * Der Superskriptmodus folgt der Schreibweise des Stammgesetzes: Trägt es amtliche Satznummern
+   * (bayerisches Landesrecht, Niedersachsen u.a.), werden auch die Änderungsgesetze mit
+   * Superskript-Erhalt extrahiert, damit Zitate und Stammtext dieselbe Schreibweise tragen; sonst
+   * (Bundesrecht, Länder ohne amtliche Satzzählung) sind hochgestellte Ziffern bloße
+   * Fußnotenmarker und werden verworfen.
+   */
+  private SuperskriptModus superskriptModus(Gesetz gesetz) {
+    for (var norm : gesetz.normen()) {
+      for (var absatz : norm.absaetze()) {
+        if (Superskript.traegtSatznummern(absatz.text())) {
+          return SuperskriptModus.BEHALTEN;
+        }
+      }
+    }
+    return SuperskriptModus.ENTFERNEN;
   }
 
   private boolean istGiiXml() throws IOException {
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 edbe742..fb578cc 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
@@ -50,8 +50,14 @@
   // Aufzählungslabel, das in Entwürfen/Drucksachen vor dem Zitat steht („… gefasst: 3. „…““).
   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.
+  // Rein zusätzliche Alternation ohne eigene Fanggruppe, damit die Gruppennummern gleich bleiben.
+  private static final String NEUFASSUNG_VERB =
+      "(?:(?:wird|werden) wie folgt gefasst|(?:erhält|erhalten) folgende Fassung)";
+
   private static final Pattern NEUFASSUNG =
-      Pattern.compile("^(.+?) (?:wird|werden) wie folgt gefasst: " + ENUM + Z + "\\.?$");
+      Pattern.compile("^(.+?) " + NEUFASSUNG_VERB + ": " + ENUM + Z + "\\.?$");
 
   // „§ 2 Absatz 2 wird durch die folgenden Absätze 2 und 3 ersetzt: „…““ (neues BGBl-Format);
   // auch „Die Überschrift wird durch die folgende Überschrift ersetzt: „…““ (Entwürfe).
@@ -340,9 +346,7 @@
   // der Zitatblock wird an „§ N“-Grenzen in Einzel-Neufassungen zerlegt.
   private static final Pattern PARAGRAPH_BEREICH_NEUFASSUNG =
       Pattern.compile(
-          "^Die (?:§§|Artt?\\.) (\\d+[a-z]?) bis (\\d+[a-z]?) (?:wird|werden) wie folgt gefasst: "
-              + Z
-              + "\\.?$");
+          "^Die (?:§§|Artt?\\.) (\\d+[a-z]?) bis (\\d+[a-z]?) " + NEUFASSUNG_VERB + ": " + Z + "\\.?$");
 
   // „Der Wortlaut wird Absatz 1.“, bayerisch auch „Der bisherige Wortlaut wird Abs. 5.“ und
   // „Der Wortlaut wird Satz 1.“
diff --git a/src/main/java/eu/mulk/aendggner/gesetz/Superskript.java b/src/main/java/eu/mulk/aendggner/gesetz/Superskript.java
index 777fe35..733c1f7 100644
--- a/src/main/java/eu/mulk/aendggner/gesetz/Superskript.java
+++ b/src/main/java/eu/mulk/aendggner/gesetz/Superskript.java
@@ -46,6 +46,21 @@
   }
 
   /**
+   * Wahr, wenn {@code text} mindestens eine amtliche Satznummer am Satzanfang trägt. Dient dazu, die
+   * Schreibweise eines Stammgesetzes zu erkennen (Landesrecht mit amtlicher Satzzählung behält seine
+   * Superskripte, Bundesrecht ohne solche verwirft sie).
+   */
+  public static boolean traegtSatznummern(String text) {
+    var m = LAUF.matcher(text);
+    while (m.find()) {
+      if (istSatzanfang(text, m.start(), m.end())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
    * Wahr, wenn der Superskript-Lauf {@code [start, ende)} in {@code text} eine amtliche Satznummer
    * am Satzanfang ist — im Unterschied zum Fußnotenmarker, der einem Wort anhängt und auf den eine
    * schließende Klammer folgt („Enteignung⁶)“).
diff --git a/src/main/java/eu/mulk/aendggner/gesetz/bayern/BayRechtLoader.java b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtLoader.java
similarity index 60%
rename from src/main/java/eu/mulk/aendggner/gesetz/bayern/BayRechtLoader.java
rename to src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtLoader.java
index 4150af4..6bd72ce 100644
--- a/src/main/java/eu/mulk/aendggner/gesetz/bayern/BayRechtLoader.java
+++ b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtLoader.java
@@ -1,4 +1,4 @@
-package eu.mulk.aendggner.gesetz.bayern;
+package eu.mulk.aendggner.gesetz.land;
 
 import eu.mulk.aendggner.aenderung.parse.PatchTextExtraktor;
 import eu.mulk.aendggner.aenderung.parse.SuperskriptModus;
@@ -12,19 +12,21 @@
 import org.jboss.logging.Logger;
 
 /**
- * Liest ein Stammgesetz des bayerischen Landesrechts aus der konsolidierten Fassung von
- * gesetze-bayern.de — als PDF oder als kanonischer Klartext.
+ * Liest ein Stammgesetz des Landesrechts aus der konsolidierten Fassung — als PDF oder als
+ * kanonischer Klartext. Deckt bayerisches Landesrecht (Gliederung in {@code Art.}, amtliche
+ * Satznummern) ebenso ab wie die {@code §}-gegliederten Gesetze der übrigen Länder; das Sigel
+ * folgt je Norm aus dem Text (siehe {@link LandesRechtTextParser}).
  *
  * <p>PDFs durchlaufen dieselbe Aufbereitung wie Änderungsgesetze (Fontgrößen-Filter mit
  * Superskript-Erhalt, Textbereinigung); der bereinigte Lineartext ist zugleich das dokumentierte
- * Klartext-Format (siehe {@link BayRechtTextParser}): Wo die PDF-Extraktion versagt oder nur eine
- * andere Quelle verfügbar ist (etwa eine archivierte HTML-Fassung), kann der Text von Hand
- * aufbereitet und als {@code .txt} eingespeist werden. Amtliche Satznummern und Fußnotenmarker
- * stehen dabei als Unicode-Superskripte im Text (¹Die freilebende Tierwelt …, Enteignung⁶)).
+ * Klartext-Format: Wo die PDF-Extraktion versagt oder nur eine andere Quelle verfügbar ist (etwa
+ * eine archivierte HTML-Fassung), kann der Text von Hand aufbereitet und als {@code .txt}
+ * eingespeist werden. Amtliche Satznummern und Fußnotenmarker stehen dabei als Unicode-Superskripte
+ * im Text (¹Die freilebende Tierwelt …, Enteignung⁶)).
  */
-public final class BayRechtLoader {
+public final class LandesRechtLoader {
 
-  private static final Logger log = Logger.getLogger(BayRechtLoader.class);
+  private static final Logger log = Logger.getLogger(LandesRechtLoader.class);
 
   private final Tika tika = new Tika();
 
@@ -44,15 +46,15 @@
                   "Nicht unterstützter Dateityp %s für Stammgesetz %s (unterstützt: PDF, Klartext)"
                       .formatted(mimeType, datei));
         };
-    return BayRechtTextParser.parse(text);
+    return LandesRechtTextParser.parse(text);
   }
 
   /**
    * Stellt einen vom Zeilen-Reflow an das Satzende der Vornorm geklebten Normkopf („… verlangen.
    * Art. 17  Jagderlaubnis“) wieder auf eine eigene Zeile. Das doppelte Leerzeichen zwischen
-   * Artikelnummer und Titel unterscheidet den Normkopf von gewöhnlichen Querverweisen.
+   * Norm-Nummer und Titel unterscheidet den Normkopf von gewöhnlichen Querverweisen.
    */
   private static String nachSatzendeGetrennteNormkoepfe(String text) {
-    return text.replaceAll("(?<=[.“?!])[ \\t]+(?=Art\\.\\s\\d+[a-z]?[ \\t]{2})", "\n");
+    return text.replaceAll("(?<=[.“?!])[ \\t]+(?=(?:§|Art\\.)\\s\\d+[a-z]?[ \\t]{2})", "\n");
   }
 }
diff --git a/src/main/java/eu/mulk/aendggner/gesetz/bayern/BayRechtTextParser.java b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
similarity index 75%
rename from src/main/java/eu/mulk/aendggner/gesetz/bayern/BayRechtTextParser.java
rename to src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
index 0744086..b1da42d 100644
--- a/src/main/java/eu/mulk/aendggner/gesetz/bayern/BayRechtTextParser.java
+++ b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
@@ -1,4 +1,4 @@
-package eu.mulk.aendggner.gesetz.bayern;
+package eu.mulk.aendggner.gesetz.land;
 
 import eu.mulk.aendggner.gesetz.Absatz;
 import eu.mulk.aendggner.gesetz.Gesetz;
@@ -11,9 +11,12 @@
 import org.jspecify.annotations.Nullable;
 
 /**
- * Parst den kanonischen Lineartext eines bayerischen Stammgesetzes (gesetze-bayern.de) zu einem
- * {@link Gesetz}. Das Layout entspricht der {@code --extract-only}-Ausgabe der konsolidierten
- * PDF-Fassung (siehe {@link BayRechtLoader}):
+ * Parst den kanonischen Lineartext eines Landesrecht-Stammgesetzes zu einem {@link Gesetz}. Das
+ * Layout entspricht der {@code --extract-only}-Ausgabe der konsolidierten PDF-Fassung (siehe {@link
+ * LandesRechtLoader}). Die Norm-Gliederung folgt dem jeweiligen Land: der Bund und die meisten
+ * Länder zitieren in {@code §}, Bayern in {@code Art.} — das Sigel wird je Norm aus dem Normkopf
+ * abgeleitet und nach {@link Norm#enbez()} durchgereicht, ein zentrales „Land“-Merkmal ist nicht
+ * nötig.
  *
  * <pre>
  * Bayerisches Jagdgesetz          ← Langtitel
@@ -23,17 +26,20 @@
  * BayRS 792-1-W
  * Vollzitat nach RedR: …          ← übersprungen (ggf. mehrzeilig)
  * I. Abschnitt Grundsätze         ← Gliederungs-Überschrift
- * 1. Allgemeine Vorschriften      ← nummerierte Unter-Überschrift (vor einem Art.-Kopf)
- * Art. 1  Gesetzeszweck           ← Normkopf; „Art. 60  (aufgehoben)“ → weggefallen
+ * 1. Allgemeine Vorschriften      ← nummerierte Unter-Überschrift (vor einem Normkopf)
+ * Art. 1  Gesetzeszweck           ← Normkopf (§/Art.); „Art. 60  (aufgehoben)“ → weggefallen
  * (1) ¹Die freilebende Tierwelt … ← Absätze mit amtlichen Satznummern als Superskript
  * ⁶) [Amtl. Anm.:] …              ← Fußnotenzeile, verbleibt im Text des tragenden Absatzes
  * </pre>
  */
-final class BayRechtTextParser {
+final class LandesRechtTextParser {
 
   private static final Pattern ABSATZ_MARKER = Pattern.compile("^\\((\\d+[a-z]?)\\)\\s+");
 
-  private static final Pattern JURABK_ZEILE = Pattern.compile("^\\((\\S+)\\)$");
+  // Juris-Abkürzung direkt hinter dem Langtitel. Bayern führt einteilige Kürzel („(BayJG)“), die
+  // übrigen Länder oft mehrteilige („(GO NRW)“); Nur die Zeile unmittelbar nach dem Titel wird
+  // geprüft, sodass spätere Fundstellen-Klammern („(BayRS V S. 595)“) nicht getroffen werden.
+  private static final Pattern JURABK_ZEILE = Pattern.compile("^\\(([^()]+)\\)$");
 
   // Kopf der Druckfassung („BayJG: Bayerisches Jagdgesetz … (Art. 1–64)“), ggf. mit
   // umbrochenem Rest („1–64)“) auf der Folgezeile.
@@ -45,13 +51,17 @@
 
   private static final Pattern UNTER_GLIEDERUNG = Pattern.compile("^(\\d+[a-z]?)\\.\\s+(\\S.*)$");
 
-  // Normkopf: „Art. N“ plus Titel auf derselben Zeile. Die Negativliste schließt
-  // Querverweise am Zeilenanfang aus („Art. 4 Abs. 3 …“).
+  // Normkopf: „§ N“ bzw. „Art. N“ plus Titel auf derselben Zeile. Das Sigel steht in Gruppe 1, die
+  // Nummer in Gruppe 2, der Titel in Gruppe 3. Die Negativliste schließt Querverweise am
+  // Zeilenanfang aus („Art. 4 Abs. 3 …“, „§ 5 Absatz 2 …“).
   private static final Pattern NORM_KOPF =
       Pattern.compile(
-          "^Art\\.\\s+(\\d+[a-z]?)\\s+"
-              + "(?!Absatz|Abs\\.|Satz|Sätze|Nummer|Nr\\.|Buchstabe|Buchst\\."
-              + "|und|bis|oder|sowie|des|der|dieses)"
+          "^(§|Art\\.)\\s+(\\d+[a-z]?)\\s+"
+              // Querverweis-Schlüsselwörter nur als ganzes Wort ausschließen: „§ 4 Satz 2“ ist ein
+              // Verweis, „§ 4 Satzungen“ dagegen ein Normtitel. Der Schutz „(?![a-zäöüß])“ verhindert,
+              // dass „Satz“ auch „Satzungen“, „Nummer“ auch „Nummerierung“ trifft.
+              + "(?!(?:Absatz|Abs\\.|Satz|Sätze|Nummer|Nr\\.|Buchstabe|Buchst\\."
+              + "|und|bis|oder|sowie|des|der|dieses)(?![a-zäöüß]))"
               + "((?:\\p{Lu}|\\().*)$");
 
   private static final Pattern WEGGEFALLEN_TITEL =
@@ -60,7 +70,7 @@
   private static final Pattern FUSSNOTE =
       Pattern.compile("^([⁰¹²³⁴⁵⁶⁷⁸⁹]+|\\d{1,3})\\)\\s*(\\[Amtl\\. Anm\\.:\\].*)$");
 
-  private BayRechtTextParser() {}
+  private LandesRechtTextParser() {}
 
   static Gesetz parse(String text) {
     var zeilen = text.lines().toList();
@@ -95,6 +105,7 @@
     String elternKennzahl = null;
     int gliederungsZaehler = 0;
 
+    String normSigel = null;
     String normNummer = null;
     String normTitel = null;
     var normZeilen = new ArrayList<String>();
@@ -109,11 +120,11 @@
 
       if (zeile == null
           || gliederung.matches()
-          || (normKopf.matches() && istNeuerNormKopf(normKopf.group(1), letzteNormNummer))
+          || (normKopf.matches() && istNeuerNormKopf(normKopf.group(2), letzteNormNummer))
           || (unterGliederung.matches() && istUnterGliederung(unterGliederung, zeilen, i))) {
         // Laufende Norm abschließen.
         if (normNummer != null) {
-          normen.add(baueNorm(normNummer, normTitel, aktuelleGliederung, normZeilen));
+          normen.add(baueNorm(normSigel, normNummer, normTitel, aktuelleGliederung, normZeilen));
         }
         normNummer = null;
         normZeilen.clear();
@@ -131,9 +142,10 @@
                   gliederung.group(1) + ". " + gliederung.group(2),
                   titel.isEmpty() ? null : titel);
           gliederungen.add(aktuelleGliederung);
-        } else if (normKopf.matches() && istNeuerNormKopf(normKopf.group(1), letzteNormNummer)) {
-          normNummer = normKopf.group(1);
-          normTitel = normKopf.group(2).strip();
+        } else if (normKopf.matches() && istNeuerNormKopf(normKopf.group(2), letzteNormNummer)) {
+          normSigel = normKopf.group(1);
+          normNummer = normKopf.group(2);
+          normTitel = normKopf.group(3).strip();
           letzteNormNummer = numerisch(normNummer);
         } else {
           var kennzahl =
@@ -152,8 +164,8 @@
 
     if (normen.isEmpty()) {
       throw new IllegalArgumentException(
-          "Kein „Art. N“-Normkopf gefunden — ist das eine konsolidierte Fassung von"
-              + " gesetze-bayern.de?");
+          "Kein „§ N“- oder „Art. N“-Normkopf gefunden — ist das eine konsolidierte Fassung im"
+              + " kanonischen Klartextformat?");
     }
     return new Gesetz(jurabk != null ? jurabk : langue, langue, null, normen, gliederungen);
   }
@@ -211,8 +223,12 @@
   }
 
   private static Norm baueNorm(
-      String nummer, @Nullable String titel, @Nullable Gliederung gliederung, List<String> zeilen) {
-    var enbez = "Art. " + nummer;
+      String sigel,
+      String nummer,
+      @Nullable String titel,
+      @Nullable Gliederung gliederung,
+      List<String> zeilen) {
+    var enbez = sigel + " " + nummer;
     boolean weggefallen = titel != null && WEGGEFALLEN_TITEL.matcher(titel).matches();
 
     var absaetze = new ArrayList<Absatz>();
@@ -245,6 +261,7 @@
       absaetze.add(new Absatz(absatzNummer, String.join("\n", absatzZeilen)));
     }
 
-    return new Norm(enbez, titel == null || titel.isEmpty() ? null : titel, gliederung, absaetze, weggefallen);
+    return new Norm(
+        enbez, titel == null || titel.isEmpty() ? null : titel, gliederung, absaetze, weggefallen);
   }
 }
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 2e19090..0e90584 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -230,7 +230,7 @@
     assumeTrue(Files.exists(alt) && Files.exists(pdf), "BayJG-Beispieldaten fehlen");
 
     // 1. Stammgesetz laden (Art.-gegliedert, amtliche Satznummern als Superskripte).
-    var gesetz = new eu.mulk.aendggner.gesetz.bayern.BayRechtLoader().load(alt);
+    var gesetz = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(alt);
     assertThat(gesetz.jurabk()).isEqualTo("BayJG");
     assertThat(gesetz.langue()).isEqualTo("Bayerisches Jagdgesetz");
     assertThat(gesetz.norm("Art. 1").orElseThrow().absaetze().get(0).text())
@@ -295,7 +295,7 @@
     var pdf = SAMPLEDATA.resolve("BayJG/Ltg-Drs-19-9707_Gesetzentwurf.pdf");
     assumeTrue(Files.exists(alt) && Files.exists(pdf), "BayJG-Beispieldaten fehlen");
 
-    var gesetz = new eu.mulk.aendggner.gesetz.bayern.BayRechtLoader().load(alt);
+    var gesetz = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(alt);
     var text =
         TextBereiniger.bereinige(
             new PatchTextExtraktor(eu.mulk.aendggner.aenderung.parse.SuperskriptModus.BEHALTEN)
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 8cf9eea..d89c898 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
@@ -91,6 +91,21 @@
   }
 
   @Test
+  void erkenntNeufassungMitErhaeltFolgendeFassung() {
+    // „… erhält folgende Fassung:“ ist die Neufassungsform in Schleswig-Holstein und Niedersachsen.
+    var befehl =
+        erkenne(
+            "§ 34a Absatz 1 erhält folgende Fassung: „(1) Durch Hauptsatzung kann bestimmt"
+                + " werden, dass Gemeindevertreterinnen und Gemeindevertreter teilnehmen.“",
+            Stelle.LEER);
+
+    assertThat(befehl).containsInstanceOf(Neufassung.class);
+    var neufassung = (Neufassung) befehl.orElseThrow();
+    assertThat(neufassung.stelle().anzeigeText()).isEqualTo("§ 34a Absatz 1");
+    assertThat(neufassung.neuerText()).startsWith("(1) Durch Hauptsatzung");
+  }
+
+  @Test
   void erkenntParagraphEinfuegung() {
     // Echter Befehl aus Art. 1 Nr. 17 des Dritten Bevölkerungsschutzgesetzes.
     var befehl =
diff --git a/src/test/java/eu/mulk/aendggner/gesetz/bayern/BayRechtTextParserTest.java b/src/test/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParserTest.java
similarity index 62%
rename from src/test/java/eu/mulk/aendggner/gesetz/bayern/BayRechtTextParserTest.java
rename to src/test/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParserTest.java
index a5fc67a..3354255 100644
--- a/src/test/java/eu/mulk/aendggner/gesetz/bayern/BayRechtTextParserTest.java
+++ b/src/test/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParserTest.java
@@ -1,13 +1,14 @@
-package eu.mulk.aendggner.gesetz.bayern;
+package eu.mulk.aendggner.gesetz.land;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 import eu.mulk.aendggner.gesetz.Gliederung;
 import org.junit.jupiter.api.Test;
 
-class BayRechtTextParserTest {
+class LandesRechtTextParserTest {
 
-  private static final String BEISPIEL =
+  // Bayern: Gliederung in „Art.“, amtliche Satznummern als Superskript.
+  private static final String BAYERN =
       """
       BayJG: Bayerisches Jagdgesetz (BayJG) Vom 13. Oktober 1978 (BayRS V S. 595) BayRS 792-1-W (Art.
       1–64)
@@ -51,17 +52,38 @@
       (1) Dieses Gesetz tritt am 1. Januar 1979 in Kraft.
       """;
 
+  // Übriges Landesrecht: Gliederung in „§“ (wie der Bund), hier mit amtlichen Satznummern.
+  private static final String PARAGRAPHEN =
+      """
+      Gemeindeordnung für das Land Nordrhein-Westfalen
+      (GO NRW)
+      Vom 14. Juli 1994
+      I. Teil Grundlagen der Gemeindeverfassung
+      § 1  Wesen der Gemeinde
+
+      (1) ¹Die Gemeinden sind die Grundlage des demokratischen Staatsaufbaus. ²Sie fördern das Wohl der Einwohner.
+      (2) Die Gemeinden verwalten ihre Angelegenheiten selbst.
+      § 2  Aufgaben
+
+      Die Gemeinden erfüllen die Aufgaben der örtlichen Gemeinschaft.
+      § 3  (weggefallen)
+
+      § 4  Satzungen
+
+      (1) Die Gemeinden können ihre Angelegenheiten durch Satzung regeln.
+      """;
+
   @Test
-  void parstTitelblock() {
-    var gesetz = BayRechtTextParser.parse(BEISPIEL);
+  void parstBayerischenTitelblock() {
+    var gesetz = LandesRechtTextParser.parse(BAYERN);
     assertThat(gesetz.jurabk()).isEqualTo("BayJG");
     assertThat(gesetz.langue()).isEqualTo("Bayerisches Jagdgesetz");
     assertThat(gesetz.kurzue()).isNull();
   }
 
   @Test
-  void parstNormenMitTitelnUndAbsaetzen() {
-    var gesetz = BayRechtTextParser.parse(BEISPIEL);
+  void parstBayerischeNormenMitTitelnUndAbsaetzen() {
+    var gesetz = LandesRechtTextParser.parse(BAYERN);
     assertThat(gesetz.normen())
         .extracting(n -> n.enbez())
         .containsExactly("Art. 1", "Art. 3", "Art. 8", "Art. 59", "Art. 60", "Art. 63", "Art. 64");
@@ -82,7 +104,7 @@
   @Test
   void querverweiseAmZeilenanfangEroeffnenKeineNorm() {
     // „Art. 11 Abs. 6 …“ am Zeilenanfang in Art. 8 ist ein Querverweis, kein Normkopf.
-    var gesetz = BayRechtTextParser.parse(BEISPIEL);
+    var gesetz = LandesRechtTextParser.parse(BAYERN);
     var art8 = gesetz.norm("Art. 8").orElseThrow();
     assertThat(art8.absaetze().get(0).text()).contains("Art. 11 Abs. 6 sind entsprechend");
     assertThat(gesetz.norm("Art. 11")).isEmpty();
@@ -90,7 +112,7 @@
 
   @Test
   void parstGliederungenMitUnterUeberschriften() {
-    var gesetz = BayRechtTextParser.parse(BEISPIEL);
+    var gesetz = LandesRechtTextParser.parse(BAYERN);
     assertThat(gesetz.gliederungen())
         .extracting(Gliederung::anzeigeText)
         .containsExactly(
@@ -107,7 +129,7 @@
 
   @Test
   void erkenntWeggefalleneNormenUndFussnoten() {
-    var gesetz = BayRechtTextParser.parse(BEISPIEL);
+    var gesetz = LandesRechtTextParser.parse(BAYERN);
     assertThat(gesetz.norm("Art. 60").orElseThrow().weggefallen()).isTrue();
     assertThat(gesetz.norm("Art. 63").orElseThrow().weggefallen()).isFalse();
 
@@ -117,4 +139,26 @@
         .contains("Enteignung⁶)")
         .contains("⁶) [Amtl. Anm.:] BayRS 2141-1-I");
   }
+
+  @Test
+  void parstParagraphengegliedertesLandesrecht() {
+    // Die übrigen Länder zitieren in „§“; das Sigel folgt aus dem Normkopf, ein Land-Merkmal ist
+    // nicht nötig. Amtliche Satznummern werden ebenso wie bei Bayern erhalten.
+    var gesetz = LandesRechtTextParser.parse(PARAGRAPHEN);
+    assertThat(gesetz.jurabk()).isEqualTo("GO NRW");
+    assertThat(gesetz.langue()).isEqualTo("Gemeindeordnung für das Land Nordrhein-Westfalen");
+    assertThat(gesetz.normen())
+        .extracting(n -> n.enbez())
+        .containsExactly("§ 1", "§ 2", "§ 3", "§ 4");
+
+    var p1 = gesetz.norm("§ 1").orElseThrow();
+    assertThat(p1.titel()).isEqualTo("Wesen der Gemeinde");
+    assertThat(p1.absaetze()).hasSize(2);
+    assertThat(p1.absaetze().get(0).text()).startsWith("¹Die Gemeinden sind die Grundlage");
+
+    // Unnummerierter Einzelabsatz und weggefallene Norm wie im bayerischen Fall.
+    assertThat(gesetz.norm("§ 2").orElseThrow().absaetze().get(0).nummer()).isNull();
+    assertThat(gesetz.norm("§ 3").orElseThrow().weggefallen()).isTrue();
+    assertThat(gesetz.gliederungen()).extracting(Gliederung::anzeigeText).containsExactly("I. Teil — Grundlagen der Gemeindeverfassung");
+  }
 }