Merge branch 'spell'
[wortliste.git] / skripte / python / prepare_patch.py
bloba90a2265791fcf04bc5b93574d74aff7b2a22d0e
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
3 # :Copyright: © 2011 Günter Milde.
4 # Released without warranty under the terms of the
5 # GNU General Public License (v. 2 or later)
6 # :Id: $Id: $
8 # prepare_patch.py: Helfer für kleine Editieraufgaben
9 # ===================================================
10 # ::
12 u"""
13 Erstelle einen Patch für kleinere Korrekturen der Wortliste.
14 Ausgangspunkt sind Dateien mit einer Korrektur pro Zeile.
15 (Zeilen, die mit ``#`` starten, werden ignoriert.)
17 AKTION ist eine von:
18 doppelte: Einträge mit gleichem Schlüssel entfernen,
19 fehleintraege: Einträge entfernen,
20 grossklein: Großschreibung ändern,
21 grossabgleich: Großschreibung der Trennmuster wie erstes Feld,
22 korrektur: Einträge durch alternative Version ersetzen.
23 neu: Einträge hinzufügen,
24 reformschreibung: Eintrag in "nur Reformschreibung" ändern.
25 """
27 # Die ``<AKTION>.todo`` Dateien in diesem Verzeichnis beschreiben das
28 # jeweils erforderliche Datenformat im Dateikopf.
30 # ::
32 import optparse, sys, os, codecs
33 from copy import copy, deepcopy
36 from werkzeug import WordFile, WordEntry, join_word, udiff, sortkey_duden
39 def teste_datei(datei):
40 """Teste, ob Datei geöffnet werden kann."""
42 try:
43 file = open(datei, 'r')
44 file.close()
45 except:
46 sys.stderr.write("Kann '" + datei + "' nicht öffnen\n" )
47 sys.exit()
50 # Sprachvarianten
51 # ---------------
52 # Sprach-Tag nach [BCP47]_::
54 sprachvariante = 'de-1901' # "traditionell"
55 # sprachvariante = 'de-1996' # Reformschreibung
56 # sprachvariante = 'de-x-GROSS' # ohne ß (Großbuchstaben und Kapitälchen)
57 # sprachvariante = 'de-1901-x-GROSS' # ohne ß (Schweiz oder GROSS)
58 # sprachvariante = 'de-1996-x-GROSS' # ohne ß (Schweiz oder GROSS)
59 # sprachvariante = 'de-CH-1901' # ohne ß (Schweiz) ("süssauer")
62 # Allgemeine Korrektur (z.B. Fehltrennung)
64 # Format:
65 # * ein Wort mit Trennstellen (für "Sprachvariante"):
66 # * vollständiger Eintrag (für Wörter mit Sprachvarianten).
68 # ::
70 def korrektur(wordfile, datei):
71 """Patch aus korrigierten Einträgen"""
73 if not datei:
74 datei = 'korrektur.todo'
75 teste_datei(datei)
77 korrekturen = {}
78 for line in open(datei, 'r'):
79 if line.startswith('#'):
80 continue
81 # Dekodieren, Zeilenende entfernen
82 line = line.decode('utf8').strip()
83 if not line:
84 continue
85 # Eintrag ggf. komplettieren
86 if u';' in line:
87 key = line.split(';')[0]
88 else:
89 key = join_word(line)
90 korrekturen[key] = line
92 wortliste = list(wordfile)
93 wortliste_neu = [] # korrigierte Liste
95 for entry in wortliste:
96 key = entry[0]
97 if key in korrekturen:
98 korrektur = korrekturen.pop(key)
99 if u';' in korrektur:
100 entry = WordEntry(korrektur)
101 else:
102 entry = copy(entry)
103 entry.set(korrektur, sprachvariante)
104 # print entry
105 wortliste_neu.append(entry)
107 if korrekturen:
108 print korrekturen # übrige Einträge
110 return (wortliste, wortliste_neu)
113 # Fehleinträge
114 # ------------
117 def fehleintraege(wordfile, datei):
118 """Entfernen der Einträge aus einer Liste von Fehleinträgen """
120 # Fehleinträge aus Datei.
122 # Format:
123 # Ein Eintrag/Zeile, mit oder ohne Trennzeichen
125 # ::
127 if not datei:
128 datei = 'fehleintraege.todo'
129 teste_datei(datei)
131 # Dekodieren, Zeilenende entfernen, Trennzeichen entfernen
132 korrekturen = set(join_word(
133 line.decode('utf8').strip().replace(u';', u' ').split()[0])
134 for line in open(datei, 'r')
135 if line.strip() and not line.startswith('#'))
136 wortliste = list(wordfile)
137 wortliste_neu = [] # korrigierte Liste
138 for entry in wortliste:
139 if entry[0] in korrekturen: # nicht kopieren
140 korrekturen.discard(entry[0]) # erledigt
141 else:
142 wortliste_neu.append(entry)
144 if korrekturen:
145 print 'nicht gefunden:'
146 for w in korrekturen:
147 print w.encode('utf8')
149 return (wortliste, wortliste_neu)
152 # Groß-/Kleinschreibung ändern
153 # ----------------------------
155 # Umstellen der Groß- oder Kleinschreibung auf die Variante in der Datei
156 # ``grossklein.todo``
158 # Format:
159 # ein Eintrag oder Wort pro Zeile, mit vorhandener Groß-/Kleinschreibung.
161 # ::
163 def grossklein(wordfile, datei):
164 """Groß-/Kleinschreibung umstellen"""
166 if not datei:
167 datei = 'grossklein.todo'
168 teste_datei(datei)
170 wortliste = list(wordfile)
172 # Dekodieren, Feldtrenner zu Leerzeichen
173 korrekturen = [line.decode('utf8').replace(';',' ')
174 for line in open(datei, 'r')
175 if not line.startswith('#')]
177 # erstes Feld, Trennzeichen entfernen
178 korrekturen = [join_word(line.split()[0]) for line in korrekturen
179 if line.strip() and not line.startswith('#')]
180 korrekturen = set(korrekturen)
181 wortliste_neu = deepcopy(wortliste) # korrigierte Liste
183 for entry in wortliste_neu:
184 if entry[0] in korrekturen:
185 korrekturen.discard(entry[0]) # gefunden
186 # Anfangsbuchstabe mit geänderter Großschreibung:
187 if entry[0][0].islower():
188 anfangsbuchstabe = entry[0][0].title()
189 else:
190 anfangsbuchstabe = entry[0][0].lower()
191 # Einträge korrigieren:
192 for i in range(len(entry)):
193 if entry[i].startswith('-'): # -2-, -3-, ...
194 continue
195 entry[i] = anfangsbuchstabe + entry[i][1:]
197 if korrekturen:
198 print korrekturen # übrige Einträge
200 return (wortliste, wortliste_neu)
202 # Anpassung der Großschreibung der Trennmuster an das erste Feld
203 # (ungetrenntes Wort). Siehe "werkzeug.py" für einen Test auf Differenzen.
204 # (Differenzen sind größtenteils auf unvorsichtiges Ersetzen mit Texteditor
205 # zurückzuführen.)
206 # ::
208 def grossabgleich(wordfile):
209 wortliste = list(wordfile)
210 wortliste_neu = deepcopy(wortliste) # korrigierte Liste
211 for entry in wortliste_neu:
212 # Übertrag des Anfangsbuchstabens
213 for i in range(1,len(entry)):
214 if entry[i].startswith('-'):
215 continue
216 entry[i] = entry[0][0] + entry[i][1:]
217 return (wortliste, wortliste_neu)
220 # Sprachvariante ändern
221 # ---------------------
223 # Einträge mit allgemeingültiger (oder sonstwie mehrfacher) Sprachvariante
224 # in "nur in Reformschreibung" (allgemein) ändern.
226 # Format:
227 # ein Wort/(Alt-)Eintrag pro Zeile.
229 # ::
231 def reformschreibung(wordfile, datei):
232 """Wörter die nur in (allgemeiner) Reformschreibung existieren"""
234 if not datei:
235 datei='reformschreibung.todo'
236 teste_datei(datei)
237 # Dekodieren, Zeilenende entfernen
238 korrekturen = [line.decode('utf8').strip()
239 for line in open(datei, 'r')
240 if not line.startswith('#')
242 # erstes Feld
243 korrekturen = [line.split(';')[0] for line in korrekturen]
244 korrekturen = set(korrekturen)
246 wortliste = list(wordfile)
247 wortliste_neu = [] # korrigierte Liste
249 for entry in wortliste:
250 if entry[0] in korrekturen:
251 key = entry[0]
252 wort = entry.get('de-1996')
253 entry = WordEntry('%s;-2-;-3-;%s;%s' % (key, wort, wort))
254 korrekturen.discard(key) # erledigt
255 wortliste_neu.append(entry)
257 if korrekturen:
258 print korrekturen # übrige Einträge
259 return wortliste, wortliste_neu
262 # Getrennte Einträge für Sprachvarianten
263 # --------------------------------------
265 # Korrigiere fehlende Spezifizierung nach Sprachvarianten, z.B.
267 # - System;Sy-stem
268 # + System;-2-;Sy-stem;Sys-tem
270 # ::
272 def sprachvariante_split(wordfile, alt, neu,
273 altsprache='de-1901', neusprache='de-1996'):
275 wortliste = list(wordfile)
276 wortliste_neu = [] # korrigierte Liste
278 for entry in wortliste:
279 if len(entry) == 2: # Allgemeine Schreibung
280 altwort = entry.get(altsprache)
281 neuwort = altwort.replace(alt, neu)
282 if altwort != neuwort:
283 entry = WordEntry('%s;-2-;3;4' % (join_word(altwort)))
284 entry.set(altwort, altsprache)
285 entry.set(neuwort, neusprache)
286 wortliste_neu.append(entry)
287 return (wortliste, wortliste_neu)
291 # Neueinträge prüfen und vorbereiten
292 # ----------------------------------
294 # Die in einer Datei (ein Neueintrag pro Zeile) gesammelten Vorschläge auf
295 # auf Neuwert testen (vorhandene Wörter werden ignoriert, unabhängig von der
296 # Groß-/Kleinschreibung) und einsortieren.
298 # Akzeptierte Formate:
300 # * vollständiger Eintrag (für Wörter mit Sprachvarianten oder Kommentaren).
302 # * ein Wort mit Trennstellen:
304 # - Schlüssel wird generiert und vorangestellt (durch Semikolon getrennt).
305 # - Test auf einfache/häufige Reformänderungen 1996:
306 # s-t/-st, {ck/k-k}/c-k
308 # ::
310 def neu(wordfile, datei):
311 """Neueinträge prüfen und vorbereiten."""
313 if not datei:
314 datei = 'neu.todo'
315 teste_datei(datei)
316 korrekturen = open(datei, 'r')
318 wortliste = list(wordfile)
319 wortliste_neu = copy(wortliste)
320 words = set(entry[0] for entry in wortliste) # vorhandene Wörter
322 # Regeländerungen:
323 r1901 = (u'-st', u'{ck/k-k}')
324 r1996 = (u's-t', u'-ck')
326 for line in korrekturen:
327 if line.startswith('#'):
328 continue
329 # Dekodieren, Zeilenende entfernen
330 line = line.decode('utf8').strip()
331 if not line:
332 continue
333 # Eintrag ggf. komplettieren:
334 if u';' in line:
335 key = line.split(u';')[0]
336 else:
337 key = join_word(line)
338 for r1, r2 in zip(r1901, r1996):
339 if r1 in line:
340 line = u'%s;-2-;%s;%s' % (key, line, line.replace(r1,r2))
341 if r2 in line:
342 line = u'%s;-2-;%s;%s' % (key, line.replace(r2,r1), line)
343 # 'ßt' und Schluß-ß auch in de-1996 möglich (langer Vokal)
344 if u'sst' in line or line.endswith(u'ss'):
345 line = u'%s;-2-;%s;' % (key, line)
346 if u';' not in line: # keine Regeländerung im Wort
347 line = u'%s;%s' % (key, line)
348 # Test auf "Neuwert":
349 if key in words:
350 print key, 'schon vorhanden'
351 continue
352 if key.lower() in words or key.title() in words:
353 print key, 'mit anderer Groß-/Kleinschreibung vorhanden'
354 continue
355 wortliste_neu.append(WordEntry(line))
356 words.add(key)
358 # Sortieren
359 wortliste_neu.sort(key=sortkey_duden)
361 return (wortliste, wortliste_neu)
364 def doppelte(wordfile, use_first=False):
365 """Doppeleinträge entfernen (ohne Berücksichtigung der Großschreibung).
367 Boolscher Wert `use_first` bestimmt, ob der erste oder der letzte von
368 Einträgen mit gleichem Schlüssel in der Liste verbleibt.
370 Die neue Liste ist sortiert.
372 wortliste = list(wordfile)
373 worddict = {}
374 for entry in wortliste:
375 key = entry[0].lower()
376 if use_first and key in worddict:
377 continue
378 worddict[key] = entry
380 wortliste_neu = worddict.values() # korrigierte Liste
381 wortliste_neu.sort(key=sortkey_duden)
383 print len(wortliste) - len(wortliste_neu), u"Einträge entfernt"
384 return (wortliste, wortliste_neu)
386 # Default-Aktion::
388 if __name__ == '__main__':
390 # sys.stdout mit UTF8 encoding.
391 sys.stdout = codecs.getwriter('UTF-8')(sys.stdout)
393 # Optionen::
395 usage = '%prog [Optionen] AKTION\n' + __doc__
397 parser = optparse.OptionParser(usage=usage)
398 parser.add_option('-i', '--file', dest='wortliste',
399 help='Eingangsdatei, Vorgabe "../../wortliste"',
400 default='../../wortliste')
401 parser.add_option('-k', '--todofile', dest='todo',
402 help='Korrekturdatei, Vorgabe "<AKTION>.todo"')
403 parser.add_option('-o', '--outfile', dest='patchfile',
404 help='Ausgangsdatei (Patch), Vorgabe "wortliste.patch"',
405 default='wortliste.patch')
407 (options, args) = parser.parse_args()
409 if args:
410 aktion = args[0]
411 else:
412 print 'Nichts zu tun: AKTION Argument fehlt.', '\n'
413 parser.print_help()
414 sys.exit()
416 # Die `Wortliste`::
417 wordfile = WordFile(options.wortliste)
419 # Da der Aufruf von `wortliste = list(wordfile)` lange dauert, wird er
420 # in den Aktionsroutinen nach dem Test auf die Eingabedatei ausgeführt.
422 # Behandeln::
424 if aktion == 'neu':
425 (wortliste, wortliste_neu) = neu(wordfile, options.todo)
426 elif aktion == 'doppelte':
427 (wortliste, wortliste_neu) = doppelte(wordfile)
428 elif aktion == 'fehleintraege':
429 (wortliste, wortliste_neu) = fehleintraege(wordfile, options.todo)
430 elif aktion == 'grossklein':
431 (wortliste, wortliste_neu) = grossklein(wordfile, options.todo)
432 elif aktion == 'grossabgleich':
433 (wortliste, wortliste_neu) = grossabgleich(wordfile)
434 elif aktion == 'korrektur':
435 (wortliste, wortliste_neu) = korrektur(wordfile, options.todo)
436 elif aktion == 'reformschreibung':
437 (wortliste, wortliste_neu) = reformschreibung(wordfile, options.todo)
438 else:
439 print 'Unbekannte AKTION', '\n'
440 parser.print_help()
441 sys.exit()
443 # (wortliste, wortliste_neu) = sprachvariante_split(wordfile,
444 # u'knien', u'kni-en')
446 # Patch erstellen::
448 patch = udiff(wortliste, wortliste_neu, 'wortliste', 'wortliste-neu',
449 encoding=wordfile.encoding)
450 if patch:
451 # print patch
452 patchfile = open(options.patchfile, 'w')
453 patchfile.write(patch + '\n')
454 print u'Änderungen nach %s geschrieben' % options.patchfile
455 else:
456 print u'keine Änderungen'