Update hypenate-neueintraege.py
[wortliste.git] / skripte / python / edit_tools / hyphenate_neueintraege.py
blob5a907ba70eb8122328bd5be38b7db6b085ff7895
1 #!/usr/bin/env python3
2 # -*- coding: utf8 -*-
3 # :Copyright: © 2014 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 # hyphenate_neueintraege.py: kategorisierte Trennung mit patgen-patterns.
9 # =======================================================================
11 # ::
13 """Trenne Wörter mittels "hyphenation"-Algorithmus und patgen-patterns¹.
15 Eingabe: Ein ungetrenntes Wort oder Eintrag im Wortliste-Format pro Zeile.²
17 Ausgabe: Wortliste-Einträge (Neueintrag;Neu=ein-trag)
18 ohne Unterscheidung von Sprachvarianten (!)³
19 getrennt nach:
21 identisch rekonstruiert
22 wenn die vorhandene Trennmarkierung der ermittelten
23 entspricht.
24 mit Pattern getrennt
25 wenn die Eingabe ungetrennt ist oder eine abweichende
26 Trennmarkierung aufweist
28 Bsp: python hyphenate_neueintraege.py < missing-words.txt > neu.todo
30 ``neu.todo`` kann (nach Durchsicht!!) mit `prepare_patch.py neu`
31 in die Wortliste eingepflegt werden.³
33 ¹ Verwendet als Voreinstellung "Reformschreibungs" Pattern-Dateien, welche
34 über die "make" Ziele `make pattern-refo`, `make major pattern-refo`,
35 `make fugen pattern-refo` und `make suffix pattern-refo` im
36 Wurzelverzeichnis der Wortliste generiert werden können (Fehler bei
37 `make fugen pattern-refo` und `make suffix pattern-refo` können ignoriert
38 werden).
40 ² Tip: mit `abgleich_neueintraege.py --filter < neue.txt > wirklich-neue.txt`
41 können in der WORTLISTE vorhandene Wörter aussortiert werden.
43 ³ `prepare_patch.py neu` nimmt auch eine Unterscheidung nach de-1901/de-1996
44 anhand der wesentlichen Regeländerungen (-st/s-t, ck/c-k, ss/ß)
45 vor. (Schweizer Spezialitäten und andere Grenzfälle müssen per Hand
46 eingepflegt werden.)
48 Für Trennungen nach traditioneller Orthographie (de-1091) müssen die
49 Musterdateinen über die Optionen angegeben werden.
50 """
52 # Doctest: Beispiel für Anwendung als Python-Module
54 # >>> from hyphenate_neueintraege import *
56 # ::
58 import sys, os, codecs, glob, copy, argparse, re, random
60 # path for local Python modules (parent dir of this file's dir)
61 sys.path.insert(0,
62 os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
64 # import patuse, trennstellenkategorisierung
65 from wortliste import (WordFile, WordEntry, ShortEntry,
66 join_word, toggle_case, sortkey_duden)
67 from patuse.hyphenation import Hyphenator
70 # Trenne mit Hyphenator
72 # Eingabe: ungetrenntes Wort
73 # Ausgabe: Wort mit kategorisierten Trennungen
75 # ::
77 def trenne(word, verbose=False):
78 if not word:
79 return ''
80 parts_fugen = h_fugen.split_word(word)
81 parts_major = h_major.split_word(word)
82 parts_suffix = h_suffix.split_word(word)
83 parts_all = h_all.split_word(word)
84 if verbose:
85 print('#' + parts_fugen)
86 print('#' + parts_major)
87 print('#' + parts_suffix)
88 print('#' + parts_all)
90 parts = [] # Liste von Silben und Trennzeichen, wird am Ende zusammengefügt.
91 p_major = '' # zum Vergleich mit parts_major
92 p_fugen = ''
93 p_suffix = ''
94 # Kategorisierung der Trennstellen
95 for part_all in parts_all[:-1]:
96 parts.append(part_all)
97 p_major += part_all
98 p_fugen += part_all
99 p_suffix += part_all
100 if parts_fugen and p_fugen == parts_fugen[0]:
101 parts_fugen.pop(0)
102 p_fugen = ''
103 try:
104 parts_major.pop(0)
105 except IndexError:
106 pass
107 p_major = ''
108 parts.append('=')
109 elif parts_suffix and p_suffix == parts_suffix[0]:
110 parts_suffix.pop(0)
111 p_suffix = ''
112 parts.append('>')
113 elif parts_major and p_major == parts_major[0]:
114 parts_major.pop(0)
115 p_major = ''
116 parts.append('<')
117 else:
118 parts.append('-')
119 parts.append(parts_all[-1])
120 word = ''.join(parts)
122 # Alternative Kategorisierung über Zerlegung der Teilwörter/Wortteile:
123 # word = '='.join(['<'.join([h_all.hyphenate_word(part, '-')
124 # for part in h_major.split_word(teilwort)])
125 # for teilwort in h_fugen.split_word(word)])
126 return word
129 def print_proposal(entry):
130 proposal = getattr(entry, "proposal", '')
131 if proposal and isinstance(proposal, ShortEntry) or len(proposal) > 1:
132 print(' ' + str(proposal))
133 print('# ' + str(entry))
136 # Hauptfunktion::
138 if __name__ == '__main__':
140 # Optionen::
142 # Die neuesten Pattern-Dateien, welche über die "make"-Ziele
144 # make pattern-refo
145 # make major pattern-refo
146 # make fugen pattern-refo
147 # make suffix pattern-refo
149 # im Wurzelverzeichnis der wortliste generiert werden
150 # (--help zeigt das Erstelldatum) ::
152 # Pfad zum Wurzelverzeichnis ("../../../") unabhängig vom Arbeitsverzeichnis::
154 root_dir = os.path.relpath(
155 os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
156 os.path.abspath(__file__))))) )
158 patterns = {}
159 for cat in ('', '-major', '-fugen', '-suffix'):
160 ppath = os.path.join(root_dir, 'dehyphn-x%s/dehyphn-x%s-*.pat'%(cat,cat))
161 patterns[cat] = sorted(glob.glob(ppath))[-1]
163 parser = argparse.ArgumentParser(description = __doc__,
164 formatter_class=argparse.RawDescriptionHelpFormatter)
165 parser.add_argument('-p', '--patterns',
166 help='Pattern-Datei (alle Trennstellen), '
167 'Vorgabe "%s"'%patterns[''], default=patterns[''])
168 parser.add_argument('--patterns_major',
169 help='Pattern-Datei (Trennstellen an Morphemgrenzen), '
170 'Vorgabe "%s"'%patterns['-major'],
171 default=patterns['-major'])
172 parser.add_argument('--patterns_fugen',
173 help='Pattern-Datei (Trennstellen an Wortfugen), '
174 'Vorgabe "%s"'%patterns['-fugen'],
175 default=patterns['-fugen'])
176 parser.add_argument('--patterns_suffix',
177 help='Pattern-Datei (Trennstellen vor Suffixen), '
178 'Vorgabe "%s"'%patterns['-suffix'],
179 default=patterns['-suffix'])
180 parser.add_argument('-k', '--kurzformat', action='store_true',
181 help='Eingabe ist im Kurzformat')
182 parser.add_argument('-l', '--language', metavar='SPRACHE',
183 help='Sprachvariante (Vorgabe: "de-1996").',
184 default="de-1996")
185 parser.add_argument('-w', '--read_probability',
186 help='Wahrscheinlichkeit mit der eine Zeile '
187 'der Eingabe gelesen wird. Vorgabe 1',
188 type=float, default=1)
189 args = parser.parse_args()
192 if args.kurzformat:
193 eintragsklasse = ShortEntry
194 else:
195 eintragsklasse = WordEntry
197 # Trenner-Instanzen::
199 h_all = Hyphenator(args.patterns)
200 h_major = Hyphenator(args.patterns_major)
201 h_fugen = Hyphenator(args.patterns_fugen)
202 h_suffix = Hyphenator(args.patterns_suffix)
205 # Erstellen der neuen Einträge::
207 #proposals = [WordEntry(line.strip().replace('-', ''))
208 proposals = [eintragsklasse(line.strip())
209 for line in sys.stdin
210 if line.strip() and not line.startswith('#')
211 and (random.random() < args.read_probability)]
213 if args.read_probability != 1:
214 print("## Eintragsverarbeitungswahrscheinlichkeit",
215 args.read_probability)
217 # Trennen::
219 entries = []
221 for proposal in proposals:
222 word = trenne(proposal.key(lang=args.language))
223 if not word:
224 continue
225 entry = ShortEntry(word)
226 entry.proposal = proposal
227 entries.append(entry)
229 # Vergleich mit Original::
231 gleiche = [] # Übereinstimmung für Sprachvariante
232 ungewichtete = [] # Übereinstimmung bis auf Wichtung ("==" vs. "=")
233 unkategorisierte = [] # Übereinstimmung bis Kategorisierung (=/</>/-/·)
234 andere = [] # Unterschiede bei der Trennung
235 ungetrennt = [] # Eingabe war ungetrennt
237 # >>> import re
238 # >>> re.sub('([=<>])+', '\\1', 'Park==ei-sen=bahn')
239 # 'Park=ei-sen=bahn'
240 # >>> re.sub('([=<>])+', '\\1', 'All<=heil=mit-tel')
241 # 'All=heil=mit-tel'
242 # >>> re.sub('([=<>.])+', '-', 'All<=heil=mit-tel')
243 # 'All-heil-mit-tel'
244 # >>> re.sub('[-=<>]*\\.', '', 'An<.al-.ge-ti-ka')
245 # 'Analge-ti-ka'
247 # ::
249 for entry in entries:
250 # print(entry.proposal),
251 # print(entry.get('de-1996'),)
252 # print(re.sub('=+', '=', entry.proposal.get('de-1996')))
253 if (not entry.proposal
254 or (args.kurzformat == False and len(entry.proposal) == 1)):
255 ungetrennt.append(entry)
256 continue
258 getrennt = entry.get(args.language) or ''
259 proposal = entry.proposal.get(args.language) or ''
260 if getrennt == proposal:
261 gleiche.append(entry)
262 continue
264 ungewichtet = re.sub('([=<>])[-=<>.]+', '\\1', proposal) # Verdoppelungen und Unterdrückungspunkt
265 aehnlich = re.sub('[=<>]?·', '', ungewichtet) # Gesangs-Trennungen
266 aehnlich = re.sub('[=<>.]', '-', aehnlich) # Vereinheitlichen
267 # print(proposal, '->', aehnlich)
269 if getrennt == ungewichtet:
270 ungewichtete.append(entry)
271 elif re.sub('[=<>]+', '-', getrennt) == aehnlich:
272 unkategorisierte.append(entry)
273 else:
274 andere.append(entry)
276 # Ausgabe::
278 if ungetrennt:
279 print('\n## Trennung mit Pattern (Eingabe ohne Trennungen)')
280 for entry in sorted(ungetrennt, key=sortkey_duden):
281 print_proposal(entry)
282 if andere:
283 print('\n## Vergleich: Eingabe vs. Trennung mit Pattern')
284 print('\n## Unterschiedliche Trennung: Eingabe/# Trennung mit Pattern')
285 for entry in sorted(andere, key=sortkey_duden):
286 print_proposal(entry)
287 if unkategorisierte:
288 print('\n## Gleiche Trennung außer Kategorisierung:')
289 for entry in sorted(unkategorisierte, key=sortkey_duden):
290 print_proposal(entry)
291 if ungewichtete:
292 print('\n## Gleiche Trennung außer Wichtung/Unterdrückung:')
293 for entry in sorted(ungewichtete, key=sortkey_duden):
294 print_proposal(entry)
296 print('\n## Gleiche Trennung für', args.language)
297 for entry in sorted(gleiche, key=sortkey_duden):
298 print_proposal(entry)
300 print('\n## Statistik')
301 print('#', len(gleiche), 'gleich')
302 print('#', len(ungewichtete), 'gleich (bis auf Wichtung/Unterdrückung)')
303 print('#', len(unkategorisierte), 'gleich (bis auf Kategorisierung)')
304 print('#', len(andere), 'anders getrennt')
305 print('#', len(ungetrennt), 'Vorgabe ohne Trennung')