d02c9906a9
- 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
384 lines
16 KiB
Python
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()
|