Python-Skript update:
[wortliste.git] / skripte / python / prepare_patch.py
blobf37aa7ba3d156d69a9caf9c80be77b4e02b1b1d8
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
33 from copy import copy, deepcopy
36 from werkzeug import WordFile, WordEntry, join_word, udiff
37 # sort.py im Überverzeichnis:
38 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
39 from sort import sortkey_wl, sortkey_duden
42 def teste_datei(datei):
43 """Teste, ob Datei geöffnet werden kann."""
45 try:
46 file = open(datei, 'r')
47 file.close()
48 except:
49 sys.stderr.write("Kann '" + datei + "' nicht öffnen\n" )
50 sys.exit()
53 # Sprachvarianten
54 # ---------------
55 # Sprach-Tag nach [BCP47]_::
57 sprachvariante = 'de-1901' # "traditionell"
58 # sprachvariante = 'de-1996' # Reformschreibung
59 # sprachvariante = 'de-x-GROSS' # ohne ß (Großbuchstaben und Kapitälchen)
60 # sprachvariante = 'de-1901-x-GROSS' # ohne ß (Schweiz oder GROSS)
61 # sprachvariante = 'de-1996-x-GROSS' # ohne ß (Schweiz oder GROSS)
62 # sprachvariante = 'de-CH-1901' # ohne ß (Schweiz) ("süssauer")
65 # Allgemeine Korrektur (z.B. Fehltrennung)
67 # Format:
68 # * ein Wort mit Trennstellen (für "Sprachvariante"):
69 # * vollständiger Eintrag (für Wörter mit Sprachvarianten).
71 # ::
73 def korrektur(wordfile, datei):
74 """Patch aus korrigierten Einträgen"""
76 if not datei:
77 datei = 'korrektur.todo'
78 teste_datei(datei)
80 korrekturen = {}
81 for line in open(datei, 'r'):
82 if line.startswith('#'):
83 continue
84 # Dekodieren, Zeilenende entfernen
85 line = line.decode('utf8').strip()
86 if not line:
87 continue
88 # Eintrag ggf. komplettieren
89 if u';' in line:
90 key = line.split(';')[0]
91 else:
92 key = join_word(line)
93 korrekturen[key] = line
95 wortliste = list(wordfile)
96 wortliste_neu = [] # korrigierte Liste
98 for entry in wortliste:
99 key = entry[0]
100 if key in korrekturen:
101 korrektur = korrekturen.pop(key)
102 if u';' in korrektur:
103 entry = WordEntry(korrektur)
104 else:
105 entry = copy(entry)
106 entry.set(korrektur, sprachvariante)
107 # print entry
108 wortliste_neu.append(entry)
110 if korrekturen:
111 print korrekturen # übrige Einträge
113 return (wortliste, wortliste_neu)
116 # Fehleinträge
117 # ------------
120 def fehleintraege(wordfile, datei):
121 """Entfernen der Einträge aus einer Liste von Fehleinträgen """
123 # Fehleinträge aus Datei.
125 # Format:
126 # Ein Eintrag/Zeile, mit oder ohne Trennzeichen
128 # ::
130 if not datei:
131 datei = 'fehleintraege.todo'
132 teste_datei(datei)
134 # Dekodieren, Zeilenende entfernen, Trennzeichen entfernen
135 korrekturen = set(join_word(
136 line.decode('utf8').strip().replace(u';', u' ').split()[0])
137 for line in open(datei, 'r')
138 if line.strip() and not line.startswith('#'))
139 wortliste = list(wordfile)
140 wortliste_neu = [] # korrigierte Liste
141 for entry in wortliste:
142 if entry[0] in korrekturen: # nicht kopieren
143 korrekturen.discard(entry[0]) # erledigt
144 else:
145 wortliste_neu.append(entry)
147 if korrekturen:
148 print 'nicht gefunden:'
149 for w in korrekturen:
150 print w.encode('utf8')
152 return (wortliste, wortliste_neu)
155 # Groß-/Kleinschreibung ändern
156 # ----------------------------
158 # Umstellen der Groß- oder Kleinschreibung auf die Variante in der Datei
159 # ``grossklein.todo``
161 # Format:
162 # ein Eintrag oder Wort pro Zeile, mit vorhandener Groß-/Kleinschreibung.
164 # ::
166 def grossklein(wordfile, datei):
167 """Groß-/Kleinschreibung umstellen"""
169 if not datei:
170 datei = 'grossklein.todo'
171 teste_datei(datei)
173 wortliste = list(wordfile)
175 # Dekodieren, Feldtrenner zu Leerzeichen
176 korrekturen = [line.decode('utf8').replace(';',' ')
177 for line in open(datei, 'r')
178 if not line.startswith('#')]
180 # erstes Feld, Trennzeichen entfernen
181 korrekturen = [join_word(line.split()[0]) for line in korrekturen
182 if line.strip() and not line.startswith('#')]
183 korrekturen = set(korrekturen)
184 wortliste_neu = deepcopy(wortliste) # korrigierte Liste
186 for entry in wortliste_neu:
187 if entry[0] in korrekturen:
188 korrekturen.discard(entry[0]) # gefunden
189 # Anfangsbuchstabe mit geänderter Großschreibung:
190 if entry[0][0].islower():
191 anfangsbuchstabe = entry[0][0].title()
192 else:
193 anfangsbuchstabe = entry[0][0].lower()
194 # Einträge korrigieren:
195 for i in range(len(entry)):
196 if entry[i].startswith('-'): # -2-, -3-, ...
197 continue
198 entry[i] = anfangsbuchstabe + entry[i][1:]
200 if korrekturen:
201 print korrekturen # übrige Einträge
203 return (wortliste, wortliste_neu)
205 # Anpassung der Großschreibung der Trennmuster an das erste Feld
206 # (ungetrenntes Wort). Siehe "werkzeug.py" für einen Test auf Differenzen.
207 # (Differenzen sind größtenteils auf unvorsichtiges Ersetzen mit Texteditor
208 # zurückzuführen.)
209 # ::
211 def grossabgleich(wordfile):
212 wortliste = list(wordfile)
213 wortliste_neu = deepcopy(wortliste) # korrigierte Liste
214 for entry in wortliste_neu:
215 # Übertrag des Anfangsbuchstabens
216 for i in range(1,len(entry)):
217 if entry[i].startswith('-'):
218 continue
219 entry[i] = entry[0][0] + entry[i][1:]
220 return (wortliste, wortliste_neu)
223 # Sprachvariante ändern
224 # ---------------------
226 # Einträge mit allgemeingültiger (oder sonstwie mehrfacher) Sprachvariante
227 # in "nur in Reformschreibung" (allgemein) ändern.
229 # Format:
230 # ein Wort/(Alt-)Eintrag pro Zeile.
232 # ::
234 def reformschreibung(wordfile, datei):
235 """Wörter die nur in (allgemeiner) Reformschreibung existieren"""
237 if not datei:
238 datei='reformschreibung.todo'
239 teste_datei(datei)
240 # Dekodieren, Zeilenende entfernen
241 korrekturen = [line.decode('utf8').strip()
242 for line in open(datei, 'r')
243 if not line.startswith('#')
245 # erstes Feld
246 korrekturen = [line.split(';')[0] for line in korrekturen]
247 korrekturen = set(korrekturen)
249 wortliste = list(wordfile)
250 wortliste_neu = [] # korrigierte Liste
252 for entry in wortliste:
253 if entry[0] in korrekturen:
254 key = entry[0]
255 wort = entry.get('de-1996')
256 entry = WordEntry('%s;-2-;-3-;%s;%s' % (key, wort, wort))
257 korrekturen.discard(key) # erledigt
258 wortliste_neu.append(entry)
260 if korrekturen:
261 print korrekturen # übrige Einträge
262 return wortliste, wortliste_neu
265 # Getrennte Einträge für Sprachvarianten
266 # --------------------------------------
268 # Korrigiere fehlende Spezifizierung nach Sprachvarianten, z.B.
270 # - System;Sy-stem
271 # + System;-2-;Sy-stem;Sys-tem
273 # ::
275 def sprachvariante_split(wordfile, alt, neu,
276 altsprache='de-1901', neusprache='de-1996'):
278 wortliste = list(wordfile)
279 wortliste_neu = [] # korrigierte Liste
281 for entry in wortliste:
282 if len(entry) == 2: # Allgemeine Schreibung
283 altwort = entry.get(altsprache)
284 neuwort = altwort.replace(alt, neu)
285 if altwort != neuwort:
286 entry = WordEntry('%s;-2-;3;4' % (join_word(altwort)))
287 entry.set(altwort, altsprache)
288 entry.set(neuwort, neusprache)
289 wortliste_neu.append(entry)
290 return (wortliste, wortliste_neu)
294 # Neueinträge prüfen und vorbereiten
295 # ----------------------------------
297 # Die in einer Datei (ein Neueintrag pro Zeile) gesammelten Vorschläge auf
298 # auf Neuwert testen (vorhandene Wörter werden ignoriert, unabhängig von der
299 # Groß-/Kleinschreibung) und einsortieren.
301 # Akzeptierte Formate:
303 # * vollständiger Eintrag (für Wörter mit Sprachvarianten oder Kommentaren).
305 # * ein Wort mit Trennstellen:
307 # - Schlüssel wird generiert und vorangestellt (durch Semikolon getrennt).
308 # - Test auf einfache/häufige Reformänderungen 1996:
309 # s-t/-st, {ck/k-k}/c-k
311 # ::
313 def neu(wordfile, datei):
314 """Neueinträge prüfen und vorbereiten."""
316 if not datei:
317 datei = 'neu.todo'
318 teste_datei(datei)
319 korrekturen = open(datei, 'r')
321 wortliste = list(wordfile)
322 wortliste_neu = deepcopy(wortliste)
323 words = set(entry[0] for entry in wortliste) # vorhandene Wörter
325 # Regeländerungen:
326 r1901 = (u'-st', u'{ck/k-k}')
327 r1996 = (u's-t', u'-ck')
329 for line in korrekturen:
330 if line.startswith('#'):
331 continue
332 # Dekodieren, Zeilenende entfernen
333 line = line.decode('utf8').strip()
334 if not line:
335 continue
336 # Eintrag ggf. komplettieren:
337 if u';' in line:
338 key = u';'.split(line)[0]
339 else:
340 key = join_word(line)
341 for r1, r2 in zip(r1901, r1996):
342 if r1 in line:
343 line = u'%s;-2-;%s;%s' % (key, line, line.replace(r1,r2))
344 if r2 in line:
345 line = u'%s;-2-;%s;%s' % (key, line.replace(r2,r1), line)
346 # 'ßt' und Schluß-ß auch in de-1996 möglich (langer Vokal)
347 if u'sst' in line or line.endswith(u'ss'):
348 line = u'%s;-2-;%s;' % (key, line)
349 if u';' not in line: # keine Regeländerung im Wort
350 line = u'%s;%s' % (key, line)
351 # Test auf "Neuwert":
352 if key in words:
353 print key.encode('utf8'), 'schon vorhanden'
354 continue
355 if key.lower() in words or key.title() in words:
356 print (key.encode('utf8'),
357 'mit anderer Groß-/Kleinschreibung vorhanden')
358 continue
359 wortliste_neu.append(WordEntry(line))
361 # Sortieren
362 wortliste_neu.sort(key=sortkey_duden)
364 return (wortliste, wortliste_neu)
367 def doppelte(wordfile, use_first=False):
368 """Doppeleinträge entfernen (ohne Berücksichtigung der Großschreibung).
370 Boolscher Wert `use_first` bestimmt, ob der erste oder der letzte von
371 Einträgen mit gleichem Schlüssel in der Liste verbleibt.
373 Die neue Liste ist sortiert.
375 wortliste = list(wordfile)
376 worddict = {}
377 for entry in wortliste:
378 key = entry[0].lower()
379 if use_first and key in worddict:
380 continue
381 worddict[key] = entry
383 wortliste_neu = worddict.values() # korrigierte Liste
384 # wortliste_neu.sort(key=sortkey_wl)
385 wortliste_neu.sort(key=sortkey_duden)
387 print len(wortliste) - len(wortliste_neu), u"Einträge entfernt"
388 return (wortliste, wortliste_neu)
390 # Default-Aktion::
392 if __name__ == '__main__':
394 # Optionen::
396 usage = '%prog [Optionen] AKTION\n' + __doc__
398 parser = optparse.OptionParser(usage=usage)
399 parser.add_option('-i', '--file', dest='wortliste',
400 help='Eingangsdatei, Vorgabe "../../wortliste"',
401 default='../../wortliste')
402 parser.add_option('-k', '--todofile', dest='todo',
403 help='Korrekturdatei, Vorgabe "<AKTION>.todo"')
404 parser.add_option('-o', '--outfile', dest='patchfile',
405 help='Ausgangsdatei (Patch), Vorgabe "wortliste.patch"',
406 default='wortliste.patch')
408 (options, args) = parser.parse_args()
410 if args:
411 aktion = args[0]
412 else:
413 print 'Nichts zu tun: AKTION Argument fehlt.', '\n'
414 parser.print_help()
415 sys.exit()
417 # Die `Wortliste`::
418 wordfile = WordFile(options.wortliste)
420 # Da der Aufruf von `wortliste = list(wordfile)` lange dauert, wird er
421 # in den Aktionsroutinen nach dem Test auf die Eingabedatei ausgeführt.
423 # Behandeln::
425 if aktion == 'neu':
426 (wortliste, wortliste_neu) = neu(wordfile, options.todo)
427 elif aktion == 'doppelte':
428 (wortliste, wortliste_neu) = doppelte(wordfile)
429 elif aktion == 'fehleintraege':
430 (wortliste, wortliste_neu) = fehleintraege(wordfile, options.todo)
431 elif aktion == 'grossklein':
432 (wortliste, wortliste_neu) = grossklein(wordfile, options.todo)
433 elif aktion == 'grossabgleich':
434 (wortliste, wortliste_neu) = grossabgleich(wordfile)
435 elif aktion == 'korrektur':
436 (wortliste, wortliste_neu) = korrektur(wordfile, options.todo)
437 elif aktion == 'reformschreibung':
438 (wortliste, wortliste_neu) = reformschreibung(wordfile, options.todo)
439 else:
440 print 'Unbekannte AKTION', '\n'
441 parser.print_help()
442 sys.exit()
444 # (wortliste, wortliste_neu) = sprachvariante_split(wordfile,
445 # u'knien', u'kni-en')
447 # Patch erstellen::
449 patch = udiff(wortliste, wortliste_neu, 'wortliste', 'wortliste-neu',
450 encoding=wordfile.encoding)
451 if patch:
452 # print patch
453 patchfile = open(options.patchfile, 'w')
454 patchfile.write(patch + '\n')
455 print u'Änderungen nach %s geschrieben' % options.patchfile
456 else:
457 print u'keine Änderungen'