blob: 24083c78f9ae00e6ad1816c4132abb7bf0265db5 [file] [log] [blame]
= ÄndGgner -- Programmbibliothek zur Konsolidierung von Änderungsgesetzen des Bundes und der Länder
Matthias Andreas Benkard
// Meta
:experimental:
:data-uri:
:sectnums:
:toc:
:stem:
:toclevels: 2
:description: ÄndGgner Manual
:keywords: mulk
// Settings
:icons: font
:source-highlighter: pygments
== Resources
|===
|Resource |Links
|Public Artifact
|https://bintray.com/mulk/maven/aendggner[Bintray]
|Bug Tracker
|MantisBT
|===
[[building]]
== Building a JAR
To build an executable JAR at `target/aendggner-${REVISION}.jar`:
[source,shell script]
----
./mvnw package
----
`${REVISION}` defaults to `0.1.0-SNAPSHOT` unless you override it by
passing the `-Drevision` flag to `mvnw`.
== Running the Command Line Application
To run the command line application after <<building,building>> it:
[source,shell script]
----
./mvnw exec:java
----
[[usage]]
== Usage
ÄndGgner erzeugt aus einem Stammgesetz und einem Änderungsgesetz eine
zweispaltige HTML-Synopse (alte Fassung links, neue rechts, Änderungen
wortweise hervorgehoben).
Eingaben:
* Stammgesetz (Bundesrecht): XML im gii-norm-Format von
https://www.gesetze-im-internet.de/[gesetze-im-internet.de]
* Stammgesetz (Landesrecht): konsolidierte Fassung als PDF oder als
kanonischer Klartext im Format der `--extract-only`-Ausgabe. Eine Zeile
Inhaltsübersicht eröffnet darin die gleichnamige Norm, auf die die
Angabe-Befehle zielen; ihre Zeilen tragen das Übersichtsformat
„§ N | Titel“. Amtliche
Satznummern und Fußnotenmarker stehen als Unicode-Superskripte im
Text („¹Die freilebende Tierwelt …“, Enteignung⁶)“); ob sie erhalten
bleiben, ergibt sich aus der geladenen Stammfassung, nicht aus einer
Länderkennung. Ebenso folgt das Zitiersigel oder Art.) aus den
Normköpfen der Stammfassung.
* Änderungsdokument: BGBl-, GVBl- oder Drucksachen-PDF (Bundestag,
Bundesrat, Landtage) oder Klartext. Die Dokumentart wird aus dem Text
erschlossen, nicht aus dem Dateinamen (siehe <<quellformate>>);
Änderungsanträge dürfen zusammen mit dem Entwurf angegeben werden, den
sie ändern.
[source,shell script]
----
java -jar target/aendggner-0.1.0-SNAPSHOT.jar \
stammgesetz.xml aenderungsgesetz.pdf -o synopse.html
----
Wichtige Optionen:
`-o, --output <file>`::
Ausgabedatei (Default `synopse.html`; `-` = Standardausgabe).
`--vollstaendig`::
Auch unveränderte Normen in die Synopse aufnehmen.
`--artikel <n>`::
Nur diesen Artikel des Änderungsgesetzes anwenden (Default: alle
Artikel, deren Einleitung das Stammgesetz nennt).
`--extract-only`::
Nur den bereinigten Lineartext des Änderungsgesetzes ausgeben. Nützlich,
wenn die PDF-Extraktion fehlerhaft ist: Text prüfen, von Hand
korrigieren und als Klartextdatei wieder einspeisen.
Erkannt werden die gebräuchlichsten Änderungsbefehle des Handbuchs der
Rechtsförmlichkeit (Ersetzen, Neufassung, Einfügen, Anfügen, Aufheben,
Streichen, Umnummerierung) einschließlich zahlreicher Sonderformen:
Bereichs- und Koordinationsziele („Die Absätze 8 und 9 werden durch die
folgenden Absätze 8 bis 10 ersetzt“), strukturelle Streichungen ganzer
Einheiten („§ 9 wird gestrichen“), §- und Gliederungs-Umnummerierungen
(„§ 9a wird zu § 9“, Der bisherige Abschnitt 2 wird zu Abschnitt 3“),
das Einfügen/Ersetzen ganzer §-Blöcke, Chapeau-Lokatoren („Im
Satzteil vor Nummer 1 …“), Änderungen an Anhängen und Anlagen („Der
Anhang wird wie folgt geändert: Nach Nummer 2 wird die folgende
Nummer 2a eingefügt“), Angabe-Befehle auf die Inhaltsübersicht (gefasst,
ersetzt, eingefügt, gestrichen sie werden auf die
Inhaltsübersichts-Norm angewandt), das Einfügen und Ersetzen von
Gliederungsberschriften („Nach § 33 werden die folgenden Überschriften
zu Teil 3 eingefügt“), Voranstellungen, Mehrfach-Ersetzungs- und
-Einfügepaare, Einfügungen, deren Position ein Wortanker statt einer
Stellenangabe bestimmt („Vor den Wörtern Aus dem Bereich Verkehr:“ wird
folgender Absatz 5 eingefügt“), Verbünde aus Umnummerierung und
Folgeänderung („§ 50 wird zu § 38 und wird wie folgt geändert“, Die
bisherige Nr. 7 wird Nr. 5 und das Komma wird durch das Wort und
ersetzt eine Satzzeichen-Operation meint dabei stets die soeben
umnummerierte Einheit, weil ihr Zieltext nichts unterscheidet),
Verb-Rahmen, deren Unterpunkte allein die Fundstelle tragen („Es werden
ersetzt: in § 35 Absatz 3 die Angabe X jeweils durch die Angabe
Y“,“) sowie die Neufassung der Gesetzesüberschrift.
Angewandt wird nicht stur in Dokumentreihenfolge: Umnummerierungen
beziehen sich stets auf die ursprüngliche Zählung, nicht auf den Stand
nach den vorangegangenen Punkten. Wer eine Bezeichnung räumt, kommt
deshalb vor den, der sie neu besetzt. Aus dieser einen Regel folgt die
absteigende Abarbeitung einer aufsteigenden Kaskade („Der bisherige
Absatz 3 wird Absatz 4“, Der bisherige Absatz 4 wird Absatz 5“, …)
ebenso wie der Vorrang einer Umnummerierung vor der Einfügung, die deren
Bezeichnung neu vergibt. Verschoben wird dabei nur nach vorn, sodass
jede Folgeänderung hinter der Umnummerierung bleibt, auf deren neue
Bezeichnung sie zeigt. Die PDF-Aufbereitung toleriert dabei
Drucksachen-Artefakte (Seitenköpfe und -füße, Vorabfassungs-Wasserzeichen,
vertauschte oder gerade Anführungszeichen, verklebte Wortgrenzen, zerlegt
kodierte Umlaute) und bestimmt die Brotschrift seitenweise, sodass auch
Ministeriumsentwürfe mit gemischten Layouts vollständig extrahiert
werden. Fehlt im amtlichen Satz ein schließendes Anführungszeichen, endet
das Zitat an der nächsten Strukturgrenze an einer Artikelberschrift
oder, wo die Anführungszeichen eines Artikels nachweislich nicht
aufgehen, am nächsten Aufzählungspunkt des Änderungsgesetzes; gemeldet
wird das als Warnung. Auf den
Beispieldaten werden damit alle Befehle der BGBl-Fassungen und der
aktuellen Entwürfe angewandt (UWG/AGG/ProdHaftG: 0 manuell); was
unsicher bleibt etwa Befehle gegen eine ältere Gesetzesfassung, deren
Zieltext nicht mehr existiert landet mit Begründung im Abschnitt
*Manuell prüfen* der Synopse und wird niemals stillschweigend verworfen.
Für das bayerische Landesrecht versteht ÄndGgner die dortigen
Konventionen: Stammgesetze gliedern sich in Artikel („Art. 6 Abs. 2
Satz 1 Nr. 2“, durchgängig abgekürzt zitiert), Änderungsgesetze dagegen
in Paragraphen auch mehrere Gesetze in einem GVBl-Heft, aus denen die
auf das Stammgesetz zielenden §§ (einschließlich Weitere Änderung“)
anhand des Einleitungssatzes ausgewählt werden. Amtliche Satznummern
bleiben als Superskripte erhalten und dienen als exakte Satzgrenzen;
zusätzlich erkannt werden die bayerischen Befehlsformen („Fußnote 1
wird aufgehoben“, In Satz 1 wird die Satznummerierung 1 gestrichen“,
Dem Wortlaut werden die folgenden Abs. 1 bis 4 vorangestellt“, Der
bisherige Wortlaut wird Abs. 5“, Halbsatz-Ziele, Klauselketten mit
gemeinsamem Schlussverb) sowie das Fortführungszeichen des GVBl (jedes
neugefasste Aufzählungsglied öffnet erneut mit „).
Die übrigen Länder gliedern ihre Stammgesetze wie der Bund in
Paragraphen; die Unterschiede liegen im Gesetzblatt-Satz und in
Befehlsidiomen. Belegt sind Sachsen (SächsBeamtVG), Niedersachsen
(NEFG) und Nordrhein-Westfalen dort alle vier ändernden Artikel eines
Heftes: Telemedienzuständigkeitsgesetz, Landesmediengesetz,
Ausführungsgesetz zum 17. Rundfunkänderungsstaatsvertrag und, mit 101
Befehlen an 31 Normen der größte Landesrechtsfall, das WDR-Gesetz
mit Akzeptanztests gegen die
amtlichen Nachfassungen; für Schleswig-Holstein, Berlin und Hessen
reicht die Prüfung bis zur Befehlserkennung dort vollständig, aber ohne
Anwendung —, weil deren Landesportale ihre Stammfassungen nur über eine
anmeldepflichtige Schnittstelle ausgeben. Welche Konvention welches Land beisteuert, welche
Stammfassungen woher stammen und was noch offen ist, verzeichnet
`src/main/resources/sampledata/Landesrecht-Beispiele.adoc`.
[[quellformate]]
== Quellformate: Gesetz, Entwurf, Antrag
Ein Änderungsbefehl steht nicht nur im verkündeten Gesetzblatt. Dasselbe
Vorhaben durchläuft als Referenten-, Regierungs- und Fraktionsentwurf,
als Änderungsantrag und als Beschlussempfehlung mehrere Fassungen, und
die Frage was gälte, wenn das durchkommt?“ stellt sich in jeder davon.
ÄndGgner erschließt die Art eines Dokuments deshalb aus seinem Kopf
nie aus dem Dateinamen, der lügen kann (im Beispielkorpus heißt ein
Entschließungsantrag `BT-Drs-21-7071_Beschlussempfehlung.pdf`) und nie
aus einer Kennung, die von außen mitzugeben wäre.
Unterschieden werden:
Änderungsgesetz::
Das verkündete Artikelgesetz aus BGBl, GVBl oder GVOBl. Der Regelfall.
Gesetzentwurf::
Referenten-, Regierungs- und Fraktionsentwürfe, auch als Drucksache von
Bundestag, Bundesrat oder Landtag. Der Begründungsteil hinter dem
Regelungstext erzeugt keine Befehle; erkannt wird er an Begründung
ebenso wie an den Entwurfsvarianten („A. Allgemeiner Teil“, Zu
Artikel 1“).
Änderungsantrag::
Ändert nicht das Stammgesetz, sondern eine *Drucksache* siehe unten.
Beschlussempfehlung::
Trägt ihre Fassung in einer zweispaltigen Zusammenstellung. Erkannt,
aber noch nicht angewandt (siehe unten).
Dokument ohne Änderungsbefehle::
Entschließungs- und schlichter Antrag, Plenarprotokoll, Bericht. Sie
werden übergangen und gemeldet nicht stillschweigend zu null
Befehlen verarbeitet.
Sobald ein Entwurf, ein Antrag oder eine Beschlussempfehlung beteiligt
ist, trägt die Synopse den Hinweis *Entwurfsfassung nicht geltendes
Recht*; die Quellenzeile nennt je Datei die erkannte Art.
=== Änderungsanträge
Ein Änderungsantrag ist eine Metanderung: Er ändert den Entwurf, nicht
das Gesetz. Sein Rahmensatz adressiert deshalb zwei Ebenen zugleich
In § 3 Nr. 22 wird § 18 Nr. 1 wie folgt geändert:“ nennt erst die
Stelle _in der Drucksache_ (den 22. Änderungsbefehl ihres dritten
Paragraphen) und dann die Stelle _in dem Text, den dieser Befehl
zitiert_. Angegeben wird der Antrag zusammen mit seinem Entwurf:
[source,shell script]
----
java -jar target/aendggner-0.1.0-SNAPSHOT.jar \
BayJG-alt.txt Ltg-Drs-19-9707_Gesetzentwurf.pdf \
Ltg-Drs-19-10365_Aenderungsantrag-Gruene.pdf -o synopse.html
----
ÄndGgner wendet dann erst den Antrag auf den Entwurf an und danach den
so geänderten Entwurf auf das Stammgesetz; die Synopse zeigt also, was
gälte, wenn Entwurf _und_ Antrag durchkämen. Welcher Entwurf gemeint
ist, entscheidet die Drucksachennummer, die der Antrag selbst nennt
(„(Drs. 19/9707)“), nicht die Reihenfolge der Argumente. Fehlt der
Entwurf, bleibt der Antrag unangewandt und wird gemeldet ihn
ersatzweise auf das Stammgesetz loszulassen wäre falsch, denn seine
Stellenangaben zielen auf die Drucksache. Erkannt wird auch die
elliptische Antragsform, die das Hilfsverb nur einmal in der
Beschlussformel führt („1. In Nr. 1.29 die Angabe „,“ am Ende durch die
Angabe „;“ ersetzt.“).
=== Beschlussempfehlungen: erkannt, noch nicht angewandt
Die maßgebliche Fassung einer Beschlussempfehlung steht in einer
zweispaltigen Zusammenstellung: links der Entwurf, rechts die Beschlüsse
des Ausschusses. Die Spaltentrennung ist erledigt und belegt anders
als beim alten BGBl und beim Berliner GVBl stehen die Spalten *nicht*
nacheinander im Inhaltsstrom, sondern zeilenweise verschränkt, weshalb
sie erstmals koordinatenbasiert getrennt werden (`PatchTextExtraktor.
extrahiereSpalten`, Schnitt an der Blattmitte, aber nur an einem
tatsächlichen Spaltensteg, damit ganzseitenbreite Zeilen ungeschnitten
bleiben). Der Akzeptanztest hält fest, dass die linke Spalte Befehl für
Befehl den Regierungsentwurf ergibt, aus dem die Zusammenstellung gebaut
ist.
Was fehlt, ist die Auflösung der rechten Spalte. Sie druckt Unverändertes
nicht ab, sondern vermerkt bloß unverändert und zwar nicht nur je
Gliederungspunkt, sondern auch zeilenweise innerhalb zitierter Blöcke.
Dadurch ist die rechte Spalte für sich genommen kein vollständiges
Dokument: Ihre Anführungszeichen gehen nicht auf. Eine Auflösung über
Gliederungspfade allein scheitert daran nachweislich; nötig ist die
zeilenweise Zuordnung beider Spalten über ihre gemeinsame Grundlinie im
PDF. Bis dahin wird eine Beschlussempfehlung erkannt und mit Begründung
übergangen samt Hinweis auf die Drucksachennummer des Entwurfs, der
sich stattdessen eignet. Eine halb aufgelöste Fassung auszugeben wäre
schlimmer als keine.
[[web]]
== Web-App
Neben der CLI gibt es eine Browserfassung, die dieselbe Pipeline
(`eu.mulk.aendggner.Pipeline`) über ein Upload-Formular zugänglich macht:
Stammgesetz- und Änderungsgesetz-Datei(en) wählen, Synopse erhalten.
Sie braucht keinen Server: Die vollständige Verarbeitung PDF-Textgewinnung
mit PDFBox eingeschlossen läuft als WebAssembly-Modul im Browser, übersetzt
mit GraalVM Web Image aus demselben Java-Quelltext. Ausgeliefert werden nur
statische Dateien; die gewählten Dokumente verlassen den Rechner der
Nutzer:innen nicht.
Bauen (verlangt Oracle GraalVM 25.1 oder neuer Web Image ist dort enthalten,
in der Community Edition nicht):
[source,shell script]
----
JAVA_HOME=/pfad/zu/oracle-graalvm ./mvnw -Pwasm package
----
Ergebnis ist `target/web/` mit `index.html`, `app.js`, `worker.js`,
`style.css`, `aendggner.js` und `aendggner.js.wasm` (rund 20 MB, komprimiert
etwa 6 MB). Die daneben liegende `aendggner.js.wat` ist ein Zwischenschritt des
Übersetzers und gehört nicht auf den Server.
Lokal ansehen `file://` genügt nicht, Browser laden Wasm-Module und Worker
nur über HTTP:
[source,shell script]
----
python3 -m http.server --directory target/web 8000
----
Der öffentliche Betrieb braucht nur einen Webserver für statische Dateien;
eine `nginx`-Vorlage liegt unter `deploy/nginx-aendggner.conf`. Die
Befehlszeilenfassung bleibt davon unberührt und ist weiterhin der Weg für
Massenläufe.
=== Warum WebAssembly und nicht ein Java-Server
Der Kern ist reines Java ohne Dateisystem- oder Netzzugriff; nur vier Stellen
berührten die Plattform (PDFBox, MIME-Erkennung, XML-Parser, Dateizugriff).
Sie sind hinter `eu.mulk.aendggner.Quelle` (Name + Bytes) und
`eu.mulk.aendggner.DateiTyp` (Signaturbytes statt Tika) gebündelt, sodass
Befehlszeile und Browser dieselbe Pipeline speisen.
Zwei Eigenheiten von Web Image sind dabei zu beachten und im Quelltext
vermerkt:
* Die nativen zlib-Bindungen des JDK fehlen (`java.util.zip.Inflater`), ohne
die kein PDF lesbar ist. `src/wasm/java/.../InflaterErsatz.java` ersetzt sie
durch die reine Java-Umsetzung von jzlib.
* Typisierte Felder lassen sich derzeit nicht nach `byte[]` umsetzen; der
Dateiinhalt wandert deshalb als Base64-Text über die JS-Grenze.
Vor dem produktiven, öffentlichen Betrieb sind zwingend zu erledigen:
* `impressum.html` und `datenschutz.html`
(`src/main/resources/eu/mulk/aendggner/web/`) mit den tatsächlich
zutreffenden Angaben statt der `TODO`-Platzhalter füllen (Pflicht
nach § 5 DDG bzw. DSGVO für öffentlich erreichbare Dienste).
* Den Quellcode-Link im Footer von `index.html` auf das tatsächlich
öffentlich erreichbare Repository setzen das Projekt steht unter
AGPLv3 (`COPYING`), dessen §13 bei Netzwerkbetrieb einen
Quellcode-Zugriff für Nutzer:innen verlangt.
== Running the Tests
To build and run the tests:
[source,shell script]
----
./mvnw verify
----