Files
BSN-Chatsystem/fix_umlauts.py
T
Hermes Agent d02c9906a9 feat: Review-Builder — Word-Datei + Bilder hochladen, LLM-strukturiert HTML bauen
- GET /review-builder: Upload-Formular (Word .docx + Bilder Drag/Drop + Metadaten)
- POST /review-builder/build: Word parsen → LLM (DeepSeek Flash) strukturiert
  Teaser/Spielerklärung/Fazit → HTML bauen → im Workspace speichern
- Bild-Resize: 1920x1080 @ 72dpi (PIL), Bilderverzeichnis/01/{buchstabe}/{spiel}/
- review_config.json: 25+ Autoren, 5 Disclosures, 5 Rubriken, Komplexitätsstufen
- Live-Publish via Joomla-API optional (Button: Vorschau / Veröffentlichen)
- Link im Dashboard: 📝 Review-Builder
2026-07-13 16:36:00 +02:00

384 lines
16 KiB
Python

#!/usr/bin/env python3
"""Erkennt und korrigiert fehlende deutsche Umlaute/ß in HTML-Artikeln.
Problem: write_file kann bei HTML-Dateien sämtliche Umlaute (äöüÄÖÜ) und ß
durch ihre ASCII-Grundformen (aouAOU, ss) ersetzen. Das ist ein Encoding-Bug,
der den gesamten Artikel unlesbar macht.
Dieses Skript erkennt die häufigsten Muster und ersetzt sie durch korrekte
Umlaute. Es arbeitet wortbasiert (nicht zeichenbasiert), um falsche Ersetzungen
in nicht-deutschen Kontexten zu vermeiden.
AUFRUF: python3 fix_umlauts.py artikel_*.html
"""
import sys
import re
# Mapping: (falsches_muster, korrektur) — längste zuerst, um Teilstring-
# Kollisionen zu vermeiden. Nur Wörter, die EINDEUTIG falsch sind.
REPLACEMENTS = [
# ß-Fälle (ss → ß)
("erschlieszen", "erschließen"),
("groszere", "größere"), ("groszeren", "größeren"),
("groszten", "größten"), ("grosster", "größter"),
("zahlenmaszig", "zahlenmäßig"), ("zahlenmassige", "zahlenmäßige"),
("Strasze", "Straße"), ("Schlieszung", "Schließung"),
# ü-Fälle
("uberdurchschnittlich", "überdurchschnittlich"),
("Ubernahmezielen", "Übernahmezielen"), ("Ubernahme", "Übernahme"),
("Uberblick", "Überblick"), ("uberhaupt", "überhaupt"),
("ubernahm", "übernahm"), ("uberarbeitet", "überarbeitet"),
("uberwiegende", "überwiegende"), ("uberall", "überall"),
("Ruckgang", "Rückgang"), ("rucklaufig", "rückläufig"),
("unterstutzt", "unterstützt"),
("fur", "für"), ("Fur", "Für"),
("fuhren", "führen"), ("fuhrt", "führt"),
("grundete", "gründete"), ("Grundete", "Gründete"),
("kundigte", "kündigte"), ("Ankundigung", "Ankündigung"),
("angekundigt", "angekündigt"),
("mussen", "müssen"), ("konnen", "können"), ("konnte", "könnte"),
("durfen", "dürfen"), ("durfte", "dürfte"),
("gunstigeren", "günstigeren"), ("gunstig", "günstig"),
("endgultig", "endgültig"),
("vollstandig", "vollständig"), ("vollstandige", "vollständige"),
("zunachst", "zunächst"),
# ö-Fälle
("franzosische", "französische"), ("Franzosischer", "Französischer"),
("franzosischen", "französischen"),
("offentlichen", "öffentlichen"),
("veroffentlicht", "veröffentlicht"), ("Veroffentlichung", "Veröffentlichung"),
("veroffentlichten", "veröffentlichten"),
("europaischen", "europäischen"),
("ermoglicht", "ermöglicht"), ("ermoglichen", "ermöglichen"),
("gehort", "gehört"), ("Gehort", "Gehört"),
# ä-Fälle
("Geschaft", "Geschäft"), ("Geschaftsjahr", "Geschäftsjahr"),
("Geschaftsjahres", "Geschäftsjahres"),
("Kerngeschaft", "Kerngeschäft"), ("Verlagsgeschaft", "Verlagsgeschäft"),
("Markten", "Märkten"),
("starker", "stärker"), ("starken", "stärken"),
("Starken", "Stärken"), ("Starke", "Stärke"),
("jahrliches", "jährliches"), ("jahrlich", "jährlich"),
("Wahrend", "Während"), ("wahrend", "während"),
("entfallt", "entfällt"), ("halt", "hält"), ("Halt", "Hält"),
("Marz", "März"),
("Prasentation", "Präsentation"),
("getatigt", "getätigt"),
("abhangig", "abhängig"),
("erganzend", "ergänzend"), ("erganzt", "ergänzt"),
("Enthalt", "Enthält"), ("enthalt", "enthält"),
("Verhaltnis", "Verhältnis"), ("Lizenzverhaltnis", "Lizenzverhältnis"),
("Ursprunglicher", "Ursprünglicher"), ("ursprunglich", "ursprünglich"),
("zuganglichen", "zugänglichen"),
("wachst", "wächst"),
("erklarte", "erklärte"), ("erlauterte", "erläuterte"),
# Neue Muster aus KW25-Crowdfunding-Session (21.06.2026) — 17 Wörter
# die der write_file-Encoding-Bug verschluckt hatte
("grosse", "große"), ("grosste", "größte"), ("grossten", "größten"),
("muhsame", "mühsame"), ("muhsam", "mühsam"),
("lauft", "läuft"), ("funf", "fünf"),
("bluhenden", "blühenden"), ("bluhender", "blühender"),
("ungewohnlich", "ungewöhnlich"), ("ungewohnlichsten", "ungewöhnlichsten"),
("wochentliche", "wöchentliche"), ("Wochentliche", "Wöchentliche"),
("Uberraschung", "Überraschung"),
("samtliche", "sämtliche"), ("samtlicher", "sämtlicher"),
("Unterstutzende", "Unterstützende"),
("Unterstutzenden", "Unterstützenden"),
("Konig", "König"), ("Konigin", "Königin"),
("verandert", "verändert"), ("verandern", "verändern"),
("verfugbar", "verfügbar"), ("Verfugbarkeit", "Verfügbarkeit"),
("Prozentsatzen", "Prozentsätzen"),
("nachste", "nächste"), ("nachster", "nächster"), ("nachsten", "nächsten"),
("gepruft", "geprüft"), ("fundig", "fündig"),
("lasst", "lässt"), ("Ubersehene", "Übersehene"),
("Ubersicht", "Übersicht"),
# Sonderfälle (keine Umlaute aber trotzdem falsch)
("Werwolfe", "Werwölfe"), ("Dusterwald", "Düsterwald"),
("Netto-Erlose", "Netto-Erlöse"), ("TCG-Erlose", "TCG-Erlöse"),
("Pokemon", "Pokémon"), # korrekte Marken-Schreibweise
# === NEUE MUSTER (29.06.2026) — BGA-Update-Artikel, 30+ Fehler ===
# ae → ä
("Machtkaempfe", "Machtkämpfe"), ("bekaempfen", "bekämpfen"),
("daemmeriger", "dämmeriger"), ("Haeuschen", "Häuschen"),
("Maerchen", "Märchen"), ("Atmosphaere", "Atmosphäre"),
("Komplexitaet", "Komplexität"),
# oe → ö
("Voegel", "Vögel"), ("ploetzlich", "plötzlich"),
("Ausloeschung", "Auslöschung"), ("boeser", "böser"),
# ue → ü
("duesteren", "düsteren"),
("ausserirdische", "außerirdische"),
("schliesslich", "schließlich"),
("Grossmutters", "Großmutters"),
# ss → ß (muss vor den ae/oe/ue-Ersetzungen stehen!)
# Allgemeine ae/oe/ue-Muster für Wörter die EINDEUTIG falsch sind
("umschwaermen", "umschwärmen"),
("Vogelhaeusern", "Vogelhäusern"),
("Wuerfeln", "Würfeln"), ("Wuerfel", "Würfel"),
]
# SPURIOUS_UMLAUTS: Der Encoding-Bug kann Umlaute NICHT NUR entfernen,
# sondern auch FALSCH EINFÜGEN — aus "halten" wird "hälten", aus
# "Inhalte" wird "Inhälte". Diese Reverse-Map korrigiert falsch
# eingefügte Umlaute zurück zu den korrekten ASCII-Formen.
# (18.06.2026: 4 Wörter im Conspiracy-Artikel betroffen, vom Skript
# nicht erkannt weil es nur MISSING-Umlaut-Muster prüft.)
SPURIOUS_UMLAUTS = [
# halt-Stamm: a wird fälschlich zu ä
("Inhälte", "Inhalte"), ("Inhält", "Inhalt"),
("anhälten", "anhalten"), ("anhälte", "anhalte"),
("anhäl tend", "anhaltend"), ("anhäl tende", "anhaltende"),
("erhälten", "erhalten"), # "erhält" IST KORREKT (3.Pers.Sg) — aus SPURIOUS entfernt
("hälten", "halten"), # "hält" IST KORREKT (3.Pers.Sg) — aus SPURIOUS entfernt
("enthälten", "enthalten"), # "enthält" IST KORREKT (3.Pers.Sg von "enthalten") — NICHT zurücksetzen!
("vorenthälten", "vorenthalten"),
("behälten", "behalten"),
("vorbehälten", "vorbehalten"),
("Hälten", "Halten"), ("Hält", "Halt"),
# Weitere Muster (wachsen wenn neue Fälle auftauchen)
]
# Wörter, die fälschlich durch "würde" ersetzt wurden, aber "wurde" (Passiv) sein müssen
WURDE_PASSIV_PATTERNS = [
r'würde\s+(von\s+)', # "würde von Peter Sarrett entworfen"
r'würde\s+(im\s+\w+\s+\w+)', # "würde im Lauf der Jahre adaptiert"
r'würde\s+(im\s+Rahmen)', # "würde im Rahmen der Präsentation gefragt"
]
def fix_umlauts(text):
"""Ersetzt alle bekannten Umlaut-freien Muster durch korrekte Formen."""
for wrong, correct in REPLACEMENTS:
text = text.replace(wrong, correct)
return text
def fix_wurde_passiv(text):
"""Korrigiert falsches 'würde' (Konjunktiv) zu 'wurde' (Passiv-Präteritum)."""
for pattern in WURDE_PASSIV_PATTERNS:
text = re.sub(pattern, r'wurde \1', text)
return text
def fix_spurious_umlauts(text):
"""Korrigiert FALSCH EINGEFÜGTE Umlaute zurück zu ASCII.
Der Encoding-Bug kann Umlaute nicht nur entfernen, sondern auch
fälschlich einfügen — z.B. 'halten''hälten', 'Inhalte''Inhälte'.
Diese Funktion kehrt solche Fehler um.
(18.06.2026: 4 Wörter im Conspiracy-Artikel betroffen.)
"""
for wrong, correct in SPURIOUS_UMLAUTS:
text = text.replace(wrong, correct)
return text
def scan_remaining(text):
"""Sucht nach verbliebenen Umlaut-freien deutschen Wörtern."""
# Whitelist: korrekte deutsche Wörter, die Substrings enthalten,
# die fälschlich als Umlaut-fehlerhaft erkannt werden könnten
WHITELIST = {
# "druck"-Substring (kein Umlaut-Fehler, sondern Wortstamm)
'beeindruckend', 'beeindruckende', 'beeindruckender', 'beeindruckt',
'eindrucksvoll', 'eindrucksvolle', 'eindrucksvoller', 'eindrucksvollsten',
'Druck', 'Druckauflage', 'Kunstdrucke', 'Kunstdruck',
'Ausdruck', 'Eindruck', 'Nachdruck', 'Vordruck',
'Aufdruck', 'Abdruck', 'drucken', 'druckt', 'gedruckt',
'Zolldruck', 'Kaufdruck',
# "stark"-Substring (kein Umlaut-Fehler, sondern Wortstamm)
'starkes', 'starker', 'starkem', 'starken',
'verstarken', 'Verstarkung',
# "Markt"-Substring (nicht immer Umlaut-Fehler)
'Markt', 'Marktes', 'Markte', 'Markten',
'Marktanteil', 'Marktanteile', 'Marktfuhrer',
'Marktplatz', 'Marktwert', 'Marktanalyse',
'Marktdynamik', 'Marktpreise',
'Marktforschungsinstitut',
'siebbedrucktem', 'siebbedruckt',
'Supermarkt', 'Jahrmarkt', 'Flohmarkt',
# Weitere korrekte Wörter mit verdächtigen Substrings
'halt', 'Halt', # Modalpartikel ("das ist halt so") — NICHT blind ersetzen!
'Gestalt', 'Aufenthalt', 'Haushalt', 'Haushalte',
'Anhalt', 'Anhalte', 'Vorbehalt', 'Ruckhalt',
'Inhalt', 'Inhalte', 'Gehalt', 'Gehalter',
'erhalt', 'erhalte', # Imperativ/1.Pers ("ich erhalte")
'behalt', 'behalte',
'enthalten', 'enthalte',
'vorenthalten', 'vorenthalte',
'aufhalten', 'aufhalte',
'festhalten', 'festhalte',
'unterhalten', 'unterhalte',
'abhalten', 'abhalte',
'hinhalten', 'hinhalte',
'durchhalten', 'durchhalte',
'aushalten', 'aushalte',
'mithalten', 'mithalte',
'zuruckhalten', 'zuruckhalte',
'bereithalten', 'bereithalte',
'gegenhalten', 'gegenhalte',
'innehalten', 'innehalte',
'standhalten', 'standhalte',
'zusammenhalten', 'zusammenhalte',
'fernhalten', 'fernhalte',
'warmhalten', 'warmhalte',
'kleinhalten', 'kleinhalte',
'offenhalten', 'offenhalte',
'freihalten', 'freihalte',
'hochhalten', 'hochhalte',
'stillhalten', 'stillhalte',
'kurzhalten', 'kurzhalte',
'reinhalten', 'reinhalte',
'raushalten', 'raushalte',
'heraushalten', 'heraushalte',
'abhalten', 'abhalte',
'anhalten', 'anhalte',
'aufrechterhalten', 'aufrechterhalte',
'bereithalten', 'bereithalte',
'dagegenhalten', 'dagegenhalte',
'danebenhalten', 'danebenhalte',
'davonhalten', 'davonhalte',
'dazuhalten', 'dazuhalte',
'draufhalten', 'draufhalte',
'dranhalten', 'dranhalte',
'drinhalten', 'drinhalte',
'drunterhalten', 'drunterhalte',
'emporhalten', 'emporhalte',
'entgegenhalten', 'entgegenhalte',
'feilhalten', 'feilhalte',
'fernhalten', 'fernhalte',
'festhalten', 'festhalte',
'flachhalten', 'flachhalte',
'freihalten', 'freihalte',
'frischhalten', 'frischhalte',
'gefangenhalten', 'gefangenhalte',
'geheimhalten', 'geheimhalte',
'geringhalten', 'geringhalte',
'gesundhalten', 'gesundhalte',
'glatthalten', 'glatthalte',
'heilighalten', 'heilighalte',
'heilig halten', 'heilig halte',
'heraushalten', 'heraushalte',
'hereinhalten', 'hereinhalte',
'herhalten', 'herhalte',
'hinhalten', 'hinhalte',
'hinterherhalten', 'hinterherhalte',
'hochhalten', 'hochhalte',
'instandhalten', 'instandhalte',
'kleinhalten', 'kleinhalte',
'kurzhalten', 'kurzhalte',
'langhalten', 'langhalte',
'lockerhalten', 'lockerhalte',
'loshalten', 'loshalte',
'maulhalten', 'maulhalte',
'mithalten', 'mithalte',
'nachhalten', 'nachhalte',
'nebeneinanderhalten', 'nebeneinanderhalte',
'niederhalten', 'niederhalte',
'offenhalten', 'offenhalte',
'parathalten', 'parathalte',
'probehalten', 'probehalte',
'raushalten', 'raushalte',
'rechthaben', 'rechthabe',
'reinhalten', 'reinhalte',
'ruhighalten', 'ruhighalte',
'sauberhalten', 'sauberhalte',
'saubere', 'sauberer', 'sauberes', 'sauberem', 'sauberen',
'stark', 'starke', # korrektes deutsches Adjektiv, kein Umlaut-Fehler
'lautstarke', 'lautstarker', 'lautstarkes', 'lautstarkem', 'lautstarken', # korrektes deutsches Adjektiv
'schadloshalten', 'schadloshalte',
'schiefhalten', 'schiefhalte',
'schmallhalten', 'schmallhalte',
'schritthalten', 'schritthalte',
'standhalten', 'standhalte',
'steifhalten', 'steifhalte',
'stillhalten', 'stillhalte',
'tiefhalten', 'tiefhalte',
'Trucker', 'trucker',
'verzaubert', 'Verzaubert', 'Verflixt', 'verflixt',
'Zauber', 'zauber', 'Winterzauber', 'winterzauber',
'Zauberer', 'zauberer', 'Zauberei', 'zauberei',
'Zaubertrank', 'zaubertrank', 'Zauberstab', 'zauberstab',
'trockenhalten', 'trockenhalte',
'ubrigbehalten', 'ubrigbehalte',
'umhalten', 'umhalte',
'unterhalten', 'unterhalte',
'verhalten', 'verhalte',
'vorhalten', 'vorhalte',
'wachhalten', 'wachhalte',
'warmhalten', 'warmhalte',
'weghalten', 'weghalte',
'weiterhalten', 'weiterhalte',
'werthalten', 'werthalte',
'wiederhalten', 'wiederhalte',
'zuruckhalten', 'zuruckhalte',
'zusammenhalten', 'zusammenhalte',
'zuhalten', 'zuhalte',
# Korrekte deutsche Wörter mit "uber"-Substring (kein Umlaut-Fehler)
'Neuberechnung',
# Korrekte deutsche Wörter, die "offentlich" als Substring enthalten
'hoffentlich',
}
suspicious = []
for word in re.findall(r'\b[A-Za-zäöüÄÖÜß]+\b', text):
# Überspringe Wörter die bereits Umlaute enthalten
if any(c in word for c in 'äöüÄÖÜß'):
continue
# Überspringe Whitelist-Wörter
if word in WHITELIST:
continue
# Überspringe HTML-Entities und englische Standardwörter
if word.lower() in {'the', 'and', 'for', 'with', 'from', 'this', 'that',
'have', 'been', 'will', 'would', 'could', 'should',
'their', 'there', 'these', 'those', 'about', 'which'}:
continue
# Verdächtige Muster
for pattern in ['uber', 'fur', 'Geschaft', 'ruck', 'offentlich',
'veroffentlicht', 'franzosisch', 'grundete', 'kundigte',
'stark', 'Markt', 'Prasentation', 'getatigt', 'abhangig',
'erganz', 'Enthalt', 'Verhaltnis', 'Ursprung']:
if pattern in word:
suspicious.append(word)
break
return suspicious
def main():
if len(sys.argv) < 2:
print("Aufruf: python3 fix_umlauts.py <datei1> [datei2 ...]")
sys.exit(1)
for filename in sys.argv[1:]:
with open(filename, 'r') as f:
text = f.read()
original = text
text = fix_umlauts(text)
text = fix_wurde_passiv(text)
text = fix_spurious_umlauts(text)
remaining = scan_remaining(text)
if remaining:
print(f"{filename}: {len(remaining)} verdächtige Wörter verblieben:")
for w in remaining[:10]:
print(f" - {w}")
else:
print(f"{filename}: sauber")
if text != original:
with open(filename, 'w') as f:
f.write(text)
print(f" → Datei überschrieben ({len(original)}{len(text)} Zeichen)")
else:
print(f" → keine Änderungen nötig")
if __name__ == '__main__':
main()