Nacharbeiten "Normalisierung".
[wortliste.git] / skripte / python / edit_tools / wortliste.py
blob839cb8a5c3d569683e3a463d6dfe36f133d82976
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
3 # :Copyright: © 2012 Günter Milde.
4 # :Licence: This work may be distributed and/or modified under
5 # the conditions of the `LaTeX Project Public License`,
6 # either version 1.3 of this license or (at your option)
7 # any later version.
8 # :Version: 0.1 (2012-02-07)
10 # wortliste.py
11 # ***********
13 # ::
15 """Hilfsmittel für die Arbeit mit der `Wortliste`"""
17 # .. contents::
19 # Die hier versammelten Funktionen und Klassen dienen der Arbeit an und
20 # mit der freien `Wortliste der deutschsprachigen Trennmustermannschaft`_
21 # ("Lembergsche Liste")
23 # Vorspann
25 # ::
27 import difflib
28 import re
29 import codecs
30 import unicodedata
31 import copy
32 import itertools
34 # WordFile
35 # ========
37 # Klasse zum Lesen und Schreiben der `Wortliste`::
39 class WordFile(file):
41 # encoding
42 # --------
44 # ::
46 encoding = 'utf8'
48 # Iteration
49 # ---------
51 # Die spezielle Funktion `__iter__` wird aufgerufen wenn über eine
52 # Klasseninstanz iteriert wird.
54 # Liefer einen Iterator über die "geparsten" Zeilen (Datenfelder)::
56 def __iter__(self):
57 line = self.readline().rstrip().decode(self.encoding)
58 while line:
59 yield WordEntry(line)
60 line = self.readline().rstrip().decode(self.encoding)
62 # asdict
63 # ------
65 # Lies Datei und trage die Zeilen mit ungetrenntem Wort
66 # als `key` und den Datenfeldern als `value` in ein `dictionary`
67 # (assoziatives Array) ein::
69 def asdict(self):
70 words = dict()
71 for entry in self:
72 words[entry[0]] = entry
73 return words
75 # writelines
76 # -----------
78 # Schreibe eine Liste von `unicode` Strings (Zeilen ohne Zeilenendezeichen)
79 # in die Datei `destination`::
81 def writelines(self, lines, destination, encoding=None):
82 outfile = codecs.open(destination, 'w',
83 encoding=(encoding or self.encoding))
84 outfile.write(u'\n'.join(lines))
85 outfile.write(u'\n')
87 # write_entries
88 # -------------
90 # Schreibe eine Liste von Einträgen (WordEntry/ShortEntry Objekten) in
91 # die Datei `destination`::
93 def write_entries(self, wortliste, destination, encoding=None):
94 lines = (unicode(entry) for entry in wortliste)
95 self.writelines(lines, destination, encoding)
98 # WordEntry
99 # =========
101 # Klasse für Einträge (Zeilen) der Wortliste
103 # Beispiel:
105 # >>> from wortliste import WordEntry, ShortEntry
107 # >>> aalbestand = WordEntry(u'Aalbestand;Aal=be<stand # Test')
108 # >>> print aalbestand
109 # Aalbestand;Aal=be<stand # Test
111 # ::
113 class WordEntry(list):
115 # Argumente
116 # ---------
118 # Kommentare (aktualisiert, wenn Kommentar vorhanden)::
120 comment = u''
122 # Feldbelegung:
124 # 1. Wort ungetrennt
125 # 2. Wort mit Trennungen, falls für alle Varianten identisch,
126 # anderenfalls leer
127 # 3. falls Feld 2 leer, Trennung nach traditioneller Rechtschreibung
128 # 4. falls Feld 2 leer, Trennung nach reformierter Rechtschreibung (2006)
129 # 5. falls Feld 2 leer, Trennung für Wortform, die entweder in
130 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird
131 # und für traditionelle und reformierte Rechtschreibung identisch ist
132 # 6. falls Feld 5 leer, Trennung für Wortform, die entweder in
133 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
134 # traditionelle Rechtschreibung
135 # 7. falls Feld 5 leer, Trennung für Wortform, die entweder in
136 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
137 # reformierte Rechtschreibung (2006)
138 # 8. falls Feld 5 leer, Trennung nach (deutsch)schweizerischer
139 # Rechtschreibung; insbesondere Wörter mit "sss" gefolgt von
140 # einem Vokal, die wie andere Dreifachkonsonanten gehandhabt wurden
141 # (also anders, als der Duden früher vorgeschrieben hat), z.B.
142 # "süssauer"
144 # Sprachvarianten (Tags nach [BCP47]_) (Die Zählung der Indizes beginn in
145 # Python bei 0)::
147 feldnamen = ('de', 'de-1901', 'de-1996', 'de-x-versal',
148 'de-1901-x-versal', 'de-1996-x-versal', 'de-CH-1901')
150 sprachvarianten = {
151 'de': 1, # Deutsch, allgemeingültig
152 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
153 'de-1996': 3, # reformierte Reformschreibung (1996)
154 'de-x-GROSS': 4, # ohne ß (Schweiz oder versal) allgemein
155 'de-x-versal': 4, # ohne ß (Schweiz oder versal) allgemein
156 # 'de-CH': 4, # Alias
157 'de-1901-x-GROSS': 5, # ohne ß (Schweiz oder versal) "traditionell"
158 'de-1901-x-versal': 5, # ohne ß (Schweiz oder versal) "traditionell"
159 'de-1996-x-GROSS': 6, # ohne ß (Schweiz oder versal) "reformiert"
160 'de-1996-x-versal': 6, # ohne ß (Schweiz oder versal) "reformiert"
161 # 'de-CH-1996': 6, # Alias
162 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
166 # Initialisierung
168 # ::
171 def __init__(self, line, delimiter=';'):
173 self.delimiter = delimiter
175 # eventuell vorhandenen Kommentar abtrennen und speichern::
177 if u'#' in line:
178 line, comment = line.split(u'#', 1)
179 self.comment = comment.lstrip()
180 line = line.rstrip()
181 # print (line, self.comment)
183 # Zerlegen in Datenfelder, in Liste eintragen::
185 list.__init__(self, line.split(delimiter))
188 # Rückverwandlung in String
189 # -----------------------------------
191 # Erzeugen eines Eintrag-Strings (Zeile) aus der Liste der Datenfelder und
192 # dem Kommentar
194 # >>> unicode(aalbestand)
195 # u'Aalbestand;Aal=be<stand # Test'
196 # >>> unicode(WordEntry(u'# Testkommentar'))
197 # u'# Testkommentar'
199 # ::
201 def __unicode__(self):
202 line = u';'.join(self)
203 if self.comment:
204 line = u' # '.join((line, self.comment)).lstrip()
205 return line
207 def __str__(self):
208 return unicode(self).encode('utf8')
210 # lang_index
211 # ---------------
213 # Index des zur Sprachvariante gehörenden Datenfeldes:
215 # >>> aalbestand.lang_index('de')
217 # >>> aalbestand.lang_index('de-1901')
219 # >>> aalbestand.lang_index('de-1996')
221 # >>> aalbestand.lang_index('de-x-GROSS')
223 # >>> aalbestand.lang_index('de-1901-x-GROSS')
225 # >>> aalbestand.lang_index('de-1996-x-GROSS')
227 # >>> abbeissen = WordEntry(
228 # ... u'abbeissen;-2-;-3-;-4-;-5-;ab<bei-ssen;ab<beis-sen;ab<beis-sen')
229 # >>> print abbeissen.lang_index('de')
230 # None
231 # >>> print abbeissen.lang_index('de-x-GROSS')
232 # None
233 # >>> abbeissen.lang_index('de-CH-1901')
235 # >>> urlaubstipp = WordEntry(u'Urlaubstipp;-2-;-3-;Ur<laubs=tipp')
236 # >>> print urlaubstipp.lang_index('de')
237 # None
238 # >>> print urlaubstipp.lang_index('de-1901')
239 # None
240 # >>> print urlaubstipp.lang_index('de-1996')
242 # >>> print urlaubstipp.lang_index('de-x-GROSS')
243 # None
244 # >>> print urlaubstipp.lang_index('de-1901-x-GROSS')
245 # None
247 # ::
249 def lang_index(self, lang):
251 assert lang in self.sprachvarianten, \
252 'Sprachvariante "%s" nicht in %s' % (lang,
253 self.sprachvarianten.keys())
255 # Einfacher Fall: eine allgemeine Schreibweise::
257 if len(self) == 2:
258 return 1
260 # Spezielle Schreibung::
262 try:
263 i = self.sprachvarianten[lang]
264 feld = self[i]
265 except IndexError:
266 if i > 4 and len(self) == 5:
267 return 4 # Allgemeine Schweiz/GROSS Schreibung:
268 return None # Feld nicht vorhanden
270 if feld.startswith('-'): # '-1-', '-2-', ...
271 return None # leeres Feld
273 return i
275 # Trennmuster für Sprachvariante ausgeben
277 # >>> aalbestand.get('de')
278 # u'Aal=be<stand'
279 # >>> aalbestand.get('de-1901')
280 # u'Aal=be<stand'
281 # >>> aalbestand.get('de-1996')
282 # u'Aal=be<stand'
283 # >>> aalbestand.get('de-x-GROSS')
284 # u'Aal=be<stand'
285 # >>> aalbestand.get('de-1901-x-GROSS')
286 # u'Aal=be<stand'
287 # >>> aalbestand.get('de-1996-x-GROSS')
288 # u'Aal=be<stand'
289 # >>> aalbestand.get('de-CH-1901')
290 # u'Aal=be<stand'
292 # >>> weste = WordEntry(u'Weste;-2-;We-ste;Wes-te')
293 # >>> print weste.get('de')
294 # None
295 # >>> weste.get('de-1901')
296 # u'We-ste'
297 # >>> weste.get('de-1996')
298 # u'Wes-te'
300 # >>> print abbeissen.get('de')
301 # None
302 # >>> print abbeissen.get('de-x-GROSS')
303 # None
304 # >>> print abbeissen.get('de,de-x-GROSS')
305 # None
306 # >>> abbeissen.get('de-1901-x-GROSS')
307 # u'ab<bei-ssen'
308 # >>> abbeissen.get('de,de-1901,de-1901-x-GROSS')
309 # u'ab<bei-ssen'
310 # >>> abbeissen.get('de-CH-1901')
311 # u'ab<beis-sen'
313 # ::
315 def get(self, sprachvarianten):
316 for lang in sprachvarianten.split(','):
317 i = self.lang_index(lang) # integer>0 or None
318 if i:
319 return self[i]
320 return None
322 # Trennmuster für Sprachvariante setzen
324 # >>> abbeissen.set('test', 'de-1901-x-GROSS')
325 # >>> print abbeissen
326 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
328 # >>> abbeissen.set('test', 'de-1901')
329 # Traceback (most recent call last):
330 # ...
331 # IndexError: kann kein leeres Feld setzen
333 # >>> abbeissen.set('test', 'de-1901,de-1901-x-GROSS')
334 # >>> print abbeissen
335 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
337 # ::
339 def set(self, wort, sprachvarianten):
340 for lang in sprachvarianten.split(','):
341 i = self.lang_index(lang)
342 if i is None:
343 continue
344 if wort is None:
345 wort = u'-%d-' % i+1
346 self[i] = wort
347 return
348 raise IndexError, "kann kein leeres Feld setzen"
351 # Felder für alle Sprachvarianten ausfüllen
353 # >>> print str(aalbestand), len(aalbestand)
354 # Aalbestand;Aal=be<stand # Test 2
355 # >>> aalbestand.expand_fields()
356 # >>> print len(aalbestand)
358 # >>> auffrass = WordEntry(u'auffrass;-2-;-3-;-4-;auf-frass')
359 # >>> auffrass.expand_fields()
360 # >>> print auffrass
361 # auffrass;-2-;-3-;-4-;auf-frass;auf-frass;auf-frass;auf-frass
362 # >>> fresssack= WordEntry(u'Fresssack;-2-;-3-;Fress=sack;Fress=sack')
363 # >>> fresssack.expand_fields()
364 # >>> print fresssack
365 # Fresssack;-2-;-3-;Fress=sack;Fress=sack;Fress=sack;Fress=sack;Fress=sack
367 # ::
369 def expand_fields(self):
370 fields = [self.get(sv) or '-%d-' % (self.sprachvarianten[sv] + 1)
371 for sv in self.feldnamen]
372 # return fields
373 for i, field in enumerate(fields):
374 try:
375 self[i+1] = field # Feld 1 ist "key" (ungetrennt)
376 except IndexError:
377 self.append(field)
380 # Felder für Sprachvarianten zusammenfassen
382 # >>> aalbestand.conflate_fields()
383 # >>> print aalbestand
384 # Aalbestand;Aal=be<stand # Test
385 # >>> auffrass.conflate_fields()
386 # >>> print auffrass
387 # auffrass;-2-;-3-;-4-;auf-frass
388 # >>> entry = WordEntry(u'distanziert;-2-;di-stan-ziert;di-stan-ziert')
389 # >>> entry.conflate_fields()
390 # >>> print entry
391 # distanziert;di-stan-ziert
392 # >>> entry = WordEntry(u'Gauss;-2-;Gauss;Gauss;Gauss')
393 # >>> entry.conflate_fields()
394 # >>> print entry
395 # Gauss;Gauss
396 # >>> fresssack.conflate_fields()
397 # >>> print fresssack
398 # Fresssack;-2-;-3-;Fress=sack;Fress=sack
401 # >>> masse = WordEntry(u'Masse;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se')
402 # >>> masse.conflate_fields()
403 # >>> print masse
404 # Masse;Mas-se
406 # Aber nicht, wenn die Trennstellen sich unterscheiden:
408 # >>> abenddienste = WordEntry(
409 # ... u'Abenddienste;-2-;Abend=dien-ste;Abend=diens-te')
410 # >>> abenddienste.conflate_fields()
411 # >>> print abenddienste
412 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
414 # >>> ackerstrasse = WordEntry(u'Ackerstraße;-2-;A{ck/k-k}er=stra-ße;Acker=stra-ße')
415 # >>> ackerstrasse.expand_fields()
416 # >>> ackerstrasse.conflate_fields()
417 # >>> print unicode(ackerstrasse)
418 # Ackerstraße;-2-;A{ck/k-k}er=stra-ße;Acker=stra-ße
420 # ::
422 def conflate_fields(self):
423 if len(self) == 8:
424 if self[7] == self[6] == self[5]:
425 self[4] = self[5] # umschreiben auf GROSS-allgemein
426 self.pop()
427 self.pop()
428 self.pop()
429 elif (self[4].startswith(u'-')
430 and self[5].startswith(u'-')
431 and self[6].startswith(u'-')
432 and self[7].startswith(u'-')):
433 self.pop()
434 self.pop()
435 self.pop()
436 self.pop()
437 else:
438 self[4] = u'-5-'
439 if len(self) == 5:
440 if self[1] == self[4]: # de-x-GROSS == de
441 self.pop()
442 self.pop()
443 self.pop()
444 elif self[2] == self[3] == self[4]:
445 self[1] = self[2] # Umschreiben auf de (allgemein)
446 self.pop()
447 self.pop()
448 self.pop()
449 elif len(self) == 4:
450 if self[3] == self[2]: # de-1996 == de-1901
451 self[1] = self[2] # Umschreiben auf de (allgemein)
452 self.pop()
453 self.pop()
455 if len(self) > 2:
456 self[1] = u'-2-'
458 # Einträge zusammenfassen
460 # >>> entry = WordEntry(u'Abenddress;Abend=dress')
461 # >>> entry.merge(WordEntry(u'Abenddress;-2-;-3-;-4-;Abend=dress'))
462 # >>> print unicode(entry)
463 # Abenddress;Abend=dress
464 # >>> entry = WordEntry(u'Gauss;Gauss')
465 # >>> entry.merge(WordEntry(u'Gauss;-2-;-3-;-4-;Gauss'))
466 # >>> print unicode(entry)
467 # Gauss;Gauss
468 # >>> masse.merge(WordEntry(u'Masse;-2-;-3-;-4-;-5-;Ma-sse;Mas-se;Mas-se'), allow_alternatives=True)
469 # >>> print masse
470 # Masse;-2-;Mas-se;Mas-se;-5-;[Mas-se/Ma-sse];Mas-se;Mas-se
472 # ::
474 def merge(self, other, allow_alternatives=False):
475 self.expand_fields()
476 other.expand_fields()
477 for i, (s_field, o_field) in enumerate(zip(self,other)):
478 if s_field == o_field or o_field.startswith(u'-'):
479 continue
480 if s_field.startswith(u'-'):
481 self[i] = o_field
482 elif s_field != o_field:
483 if not allow_alternatives:
484 self.conflate_fields()
485 other.conflate_fields()
486 raise AssertionError('%s != %s' %(s_field, o_field))
487 self[i] = u'[%s/%s]' % (s_field, o_field)
488 other.conflate_fields()
489 self.conflate_fields()
492 # Prüfe auf Vorkommen von Regeländerungen der Orthographiereform 1996.
494 # >>> entry = WordEntry(u'Würste;Wür-ste')
495 # >>> entry.regelaenderungen()
496 # >>> print unicode(entry)
497 # Würste;-2-;Wür-ste;Würs-te
498 # >>> entry = WordEntry(u'Würste;Würs-te')
499 # >>> entry.regelaenderungen()
500 # >>> print unicode(entry)
501 # Würste;-2-;Wür-ste;Würs-te
502 # >>> entry = WordEntry(u'Hecke;He-cke')
503 # >>> entry.regelaenderungen()
504 # >>> print unicode(entry)
505 # Hecke;-2-;He{ck/k-k}e;He-cke
506 # >>> entry = WordEntry(u'Ligusterhecke;Ligu-ster=he{ck/k-k}e')
507 # >>> entry.regelaenderungen()
508 # >>> print unicode(entry)
509 # Ligusterhecke;-2-;Ligu-ster=he{ck/k-k}e;Ligus-ter=he-cke
510 # >>> entry = WordEntry(u'Hass;Hass')
511 # >>> entry.regelaenderungen()
512 # >>> print unicode(entry)
513 # Hass;-2-;-3-;Hass;Hass
514 # >>> entry = WordEntry(u'fasst;fasst')
515 # >>> entry.regelaenderungen()
516 # >>> print unicode(entry)
517 # fasst;-2-;-3-;fasst;fasst
518 # >>> entry = WordEntry(u'Missbrauch;Miss<brauch')
519 # >>> entry.regelaenderungen()
520 # >>> print unicode(entry)
521 # Missbrauch;-2-;-3-;Miss<brauch;Miss<brauch
522 # >>> entry = WordEntry(u'schlifffest;schliff=fest')
523 # >>> entry.regelaenderungen()
524 # >>> print unicode(entry)
525 # schlifffest;-2-;-3-;schliff=fest
527 # ::
529 def regelaenderungen(self):
530 # Trennregeländerungen:
531 r1901 = (u'-st', u'{ck/k-k}')
532 r1996 = (u's-t', u'-ck')
534 w1901 = self.get('de-1901')
535 w1996 = self.get('de-1996')
536 w_x_GROSS = None
538 if w1901 is None or w1996 is None:
539 return
541 for r1, r2 in zip(r1901, r1996):
542 w1901 = w1901.replace(r2,r1)
543 w1996 = w1996.replace(r1,r2)
545 # kein Schluss-ss und sst in de-1901 (ungetrenntes "ss" nur in Ausnahmen)
546 # aber: 'ßt' und Schluß-ß auch in de-1996 möglich (langer Vokal)
547 if u'ss' in w1901:
548 w_x_GROSS = w1901
549 w1901 = None
551 # Dreikonsonantenregel:
552 if w1901 and re.search(ur'(.)\1=\1', w1901):
553 w1901 = None
555 # Speichern:
556 if w1901 == w1996: # keine Regeländerung im Wort
557 if len(self) > 2:
558 self.conflate_fields()
559 return
561 if w1901 is None:
562 self.extend( ['']*(4-len(self)) )
563 self[1] = u'-2-'
564 self[2] = u'-3-'
565 self[3] = w1996
566 else:
567 self.extend( ['']*(4-len(self)) )
568 self[1] = u'-2-'
569 self[2] = w1901
570 self[3] = w1996
571 if w_x_GROSS:
572 self.append(w_x_GROSS)
575 # ShortEntry
576 # ==========
578 # Klasse für Einträge (Zeilen) der Wortlisten (neues, kurzes Format)
580 # >>> from wortliste import ShortEntry
582 # >>> aalbst = ShortEntry(u'Aal=be<stand # Test')
584 # ::
586 class ShortEntry(WordEntry):
588 # Feldbelegung im Kurzformat (Vorschlag)
590 # Sprachtags nach [BCP47]_.
592 # de:
593 # Wort mit Trennungen nach aktueller Rechtschreibung (de-1996).
594 # Einziges Feld, falls andere Varianten über Regeln gewonnen werden können.
596 # "-1-" falls die Schreibung in de-1996 unzulässig ist (-1-;Pro<zeß).
598 # de-1901:
599 # Wort mit Trennung nach de-1901.
600 # Belegt, falls abweichend von der regelbasierten Ableitung aus "de".
602 # "-2-" falls die Schreibung in de-1901 unzulässig ist (Ur<laubs=tipp;-2-).
604 # de-CH oder de-x-versal:
605 # Wort mit ß-Ersatzschreibung, die entweder in
606 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird.
607 # Trennungen nach aktueller Rechtschreibung (de-CH-1996, de-1996-x-versal).
609 # "-3-" falls die Schreibung in de-CH-1996 unzulässig ist und weitere Felder
610 # folgen.
612 # de-1901-x-versal:
613 # Wort mit ß-Ersatzschreibung für de-1901 mit Großbuchstaben oder
614 # Kapitälchen.
615 # Belegt, falls abweichend von der Ableitung aus "de-x-versal".
617 # "-4-" falls die abgeleitete Schreibung in de-1901 unzulässig ist.
619 # de-CH-1901:
620 # Wort mit ß-Ersatzschreibung, die der Schweiz benutzt wird.
621 # Insbesondere Wörter mit „sss“ gefolgt von einem Vokal, die wie
622 # andere Dreifachkonsonanten gehandhabt wurden (also anders, als
623 # bei Ersatzschreibung in Deutschland und Österreich), z.B. „süssauer“
624 # Belegt, falls abweichend von der regelbasierten Ableitung aus "de-CH".
626 # "-5-" falls die abgeleitete Schreibung in de-CH-1901 unzulässig ist.
628 # >>> print ShortEntry.feldnamen
629 # ('de', 'de-1901', 'de-CH', 'de-1901-x-versal', 'de-CH-1901')
630 # >>> print aalbst.sprachvarianten['de'], aalbst.sprachvarianten['de-CH-1901']
631 # 0 4
632 # >>> print aalbst.sprachvarianten['de-x-versal'], aalbst.sprachvarianten['de-CH']
633 # 2 2
635 # ::
637 feldnamen = (
638 'de', # Deutsch, aktuell (Reform 1996)
639 'de-1901', # "traditionell" (Reform 1901)
640 'de-CH', # ohne ß (Schweiz oder versal) "aktuell"
641 'de-1901-x-versal', # ohne ß (Schweiz oder versal) "traditionell"
642 'de-CH-1901', # ohne ß (Schweiz) "traditionell" ("süssauer")
645 sprachvarianten = dict((tag, index)
646 for (index,tag) in enumerate(feldnamen))
647 sprachvarianten['de-x-versal'] = 2 # Alias für "ohne ß"
650 # Beispiele:
652 # >>> print aalbst
653 # Aal=be<stand # Test
654 # >>> print ShortEntry(aalbestand)
655 # Aal=be<stand # Test
656 # >>> dst = ShortEntry(u'Diens-te;Dien-ste')
657 # >>> print dst
658 # Diens-te;Dien-ste
659 # >>> fluss = ShortEntry(u'Fluss;Fluß')
660 # >>> print unicode(fluss)
661 # Fluss;Fluß
662 # >>> tpp = ShortEntry(u'Ur<laubs=tipp;-2-')
663 # >>> print tpp
664 # Ur<laubs=tipp;-2-
665 # >>> drs = unicode(ShortEntry(u'-1-;Dreß'))
666 # >>> print drs
667 # -1-;Dreß
669 # # >>> print ShortEntry(u'# Testkommentar')
670 # u'# Testkommentar'
673 # Wird dem Konstruktor eine WordEntry-Instanz übergeben, so wird
674 # diese in das Kurzformat überführt:
676 # >>> print abenddienste
677 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
678 # >>> print ShortEntry(abenddienste)
679 # Abend=diens-te
680 # >>> print urlaubstipp
681 # Urlaubstipp;-2-;-3-;Ur<laubs=tipp
682 # >>> print ShortEntry(urlaubstipp)
683 # Ur<laubs=tipp;-2-
684 # >>> dress = WordEntry(u'Dress;Dress')
685 # >>> print ShortEntry(dress)
686 # Dress;Dress
687 # >>> dresz = WordEntry(u'Dreß;-2-;Dreß;-4-')
688 # >>> print unicode(ShortEntry(dresz))
689 # -1-;Dreß
690 # >>> boss = ShortEntry(WordEntry(u'Boss;Boss # en.'))
691 # >>> print boss
692 # Boss;Boss # en.
693 # >>> biss = ShortEntry(WordEntry(u'Biss;-2-;-3-;Biss'))
694 # >>> print biss
695 # Biss;-2-
696 # >>> g_ebene = WordEntry(u'Gaußebene;Gauß=ebe-ne')
697 # >>> print unicode(ShortEntry(g_ebene))
698 # Gauß=ebe-ne
699 # >>> print unicode(ShortEntry(WordEntry(u'Abfalllager;-2-;-3-;Ab<fall=la-ger')))
700 # Ab<fall=la-ger;-2-
701 # >>> print auffrass
702 # auffrass;-2-;-3-;-4-;auf-frass
703 # >>> print ShortEntry(auffrass)
704 # -1-;-2-;auf-frass
705 # >>> fraesse = WordEntry(u'frässe;-2-;-3-;-4-;-5-;frä-sse;fräs-se;fräs-se')
706 # >>> frs = ShortEntry(fraesse)
707 # >>> print unicode(frs)
708 # -1-;-2-;fräs-se;frä-sse
709 # >>> messignal = WordEntry(u'Messignal;-2-;-3-;-4-;-5-;-6-;-7-;Me{ss/ss=s}i-.gnal')
710 # >>> print unicode(ShortEntry(messignal))
711 # -1-;-2-;-3-;-4-;Me{ss/ss=s}i-.gnal
712 # >>> loesz = WordEntry(u'Lößboden;Löß=bo-den')
713 # >>> print unicode(ShortEntry(loesz))
714 # Löß=bo-den
715 # >>> loess = WordEntry(u'Lössboden;-2-;-3-;Löss=bo-den;Löss=bo-den')
716 # >>> print unicode(ShortEntry(loess))
717 # Löss=bo-den;-2-
718 # >>> print ShortEntry(WordEntry(u'Amnesty;Am-nes-ty # en.'))
719 # Am-nes-ty;Am-nes-ty # en.
721 # ::
723 def __init__(self, line, delimiter=';', conflate=True):
725 if type(line) == WordEntry:
726 list.__init__(self, line) # copy
727 self.comment = line.comment
728 self.pop(0) # ungetrenntes Wort entfernen
729 # de-1996 in Spalte 1
730 if len(self) <= 2:
731 if self.get('de-1901') != self[0]:
732 self.append(self[0])
733 return
734 self[0] = self.pop(2)
735 # de-CH in Spalte 3
736 if len(self) > 3:
737 # print "jetzt", self
738 self[2] = self.pop(4)
739 # Felder zusammenfassen
740 if conflate:
741 self.conflate_fields()
742 # Umnummerieren leerer Felder:
743 for (i, f) in enumerate(self):
744 if f.startswith(u'-'):
745 self[i] = u'-%d-' % (i+1)
746 else:
747 WordEntry.__init__(self, line, delimiter)
750 # Schlüssel erzeugen
751 # ------------------
753 # Als Schlüssel wird das erste nichtleere Feld ohne Trennzeichen, bei Angabe
754 # des optionalen Arguments ``lang`` die Schreibweise in dieser Rechtschreibung
755 # verwendet.
757 # >>> print fluss.key()
758 # Fluss
759 # >>> print ShortEntry(dresz).key()
760 # Dreß
762 # Die Key-Auswahl kann über das `lang` Argument gesteuert werden:
764 # >>> print fluss.key('de-1901')
765 # fluß
767 # ::
769 def key(self, lang=None):
770 """Erstelle einen Schlüssel (ungetrenntes Wort)."""
771 if lang:
772 return join_word(self.get(lang)).lower()
773 for f in self:
774 if f.startswith(u'-'):
775 continue
776 return join_word(f)
779 # Trennmuster für Sprachvariante ausgeben
780 # ---------------------------------------
782 # Suche die passende Sprachvariante (oder generiere sie).
784 # Namen (Sprachtags) der Spalten und ihre Normalisierung:
786 # 1. 'de'
788 # 2. 'de-1901', 'de'
790 # 3. 'de-CH', 'de'
791 # 'de-x-versal', 'de'
793 # 4. 'de-1901-x-versal', 'de-1901', 'de-x-versal', 'de'
795 # 5. 'de-CH-1901', 'de-CH', 'de-1901', 'de'
797 # Ohne Transformation
799 # >>> aalbst.get('de')
800 # u'Aal=be<stand'
801 # >>> aalbst.get('de-1996')
802 # u'Aal=be<stand'
803 # >>> aalbst.get('de-1901')
804 # u'Aal=be<stand'
805 # >>> aalbst.get('de-x-versal')
806 # u'Aal=be<stand'
807 # >>> aalbst.get('de-1901-x-versal')
808 # u'Aal=be<stand'
809 # >>> aalbst.get('de-1996-x-versal')
810 # u'Aal=be<stand'
811 # >>> aalbst.get('de-CH-1901')
812 # u'Aal=be<stand'
814 # >>> print dst.get('de')
815 # Diens-te
816 # >>> print dst.get('de-1901')
817 # Dien-ste
818 # >>> print dst.get('de-1996')
819 # Diens-te
821 # Versalschreibung:
823 # >>> rs = ShortEntry(u'-1-;-2-;Russ') # versal f. Ruß
824 # >>> print rs.get('de-x-versal')
825 # Russ
826 # >>> print rs.get('de-1901-x-versal')
827 # Russ
829 # >>> print tpp.get('de')
830 # Ur<laubs=tipp
831 # >>> print tpp.get('de-x-versal')
832 # Ur<laubs=tipp
833 # >>> print tpp.get('de-1901')
834 # None
836 # >>> print unicode(frs)
837 # -1-;-2-;fräs-se;frä-sse
838 # >>> print frs.get('de')
839 # None
840 # >>> print frs.get('de-1901')
841 # None
842 # >>> print frs.get('de-x-versal')
843 # fräs-se
844 # >>> print frs.get('de-1901-x-versal')
845 # frä-sse
846 # >>> print frs.get('de-CH-1901')
847 # fräs-se
849 # Mit Transformation:
851 # >>> print ShortEntry(u'Es-te').get('de-1901')
852 # Este
853 # >>> print ShortEntry(u'passt').get('de-1901')
854 # paßt
855 # >>> print ShortEntry(u'passt').get('de-1901-x-versal')
856 # passt
857 # >>> abus = ShortEntry(u'ab<bü-ßen')
858 # >>> print abus.get('de-CH')
859 # ab<büs-sen
860 # >>> print abus.get('de-1901-x-versal')
861 # ab<bü-ssen
862 # >>> print abus.get('de-CH-1901')
863 # ab<büs-sen
865 # >>> print ShortEntry(u'-1-;-2-;fräs-se;frä-sse').get('de-CH')
866 # fräs-se
867 # >>> print ShortEntry(u'-1-;-2-;fräs-se;frä-sse').get('de-CH-1901')
868 # fräs-se
869 # >>> print ShortEntry(u'-1-;-2-;fräs-se;frä-sse').get('de-1901-x-versal')
870 # frä-sse
871 # >>> print ShortEntry(u'Bus-se').get('de-1901-x-versal')
872 # Bus-se
873 # >>> print ShortEntry(u'Pass=sys-tem').get('de-CH-1901')
874 # Pa{ss/ss=s}y-.stem
876 # >>> print ShortEntry(u'Pro<zess=en-.de;Pro<zeß=en-de;Pro<zess=en-.de').get('de-CH-1901')
877 # Pro<zess=en-.de
879 # >>> pstr = ShortEntry(u'Pass=stra-ße;-2-')
880 # >>> pstr.expand_fields()
881 # >>> print unicode(pstr)
882 # Pass=stra-ße;-2-;Pass=stras-se;-4-;Pass=stras-se
884 # >>> print pstr.get('de') # Deutsch, aktuell (Reform 1996)
885 # Pass=stra-ße
886 # >>> print pstr.get('de-1901') # "traditionell" (Reform 1901)
887 # None
888 # >>> print pstr.get('de-CH') # ohne ß (Schweiz oder versal) "aktuell"
889 # Pass=stras-se
890 # >>> print pstr.get('de-x-versal') # Alias de-CH
891 # Pass=stras-se
892 # >>> print pstr.get('de-1901-x-versal')
893 # None
894 # >>> print pstr.get('de-CH-1901')
895 # Pass=stras-se
897 # ::
899 def get(self, lang):
900 for tag in normalize_language_tag(lang):
901 try:
902 word = self[self.sprachvarianten[tag]]
903 except LookupError: # gewählte Spalte nicht vorhanden
904 continue
905 if word.startswith(u'-'): # "ausgekreuzt"
906 if lang == 'de-1901-x-versal' and self[0].startswith(u'-'):
907 continue
908 return None
909 if tag != lang:
910 if '1901' in lang:
911 # if '-CH' in lang:
912 # continue
913 word = ableitung1901(word)
914 if ('-CH' in lang) or ('x-versal' in lang) and (u'ß' in word):
915 word = versalschreibung(word, lang)
916 return word
917 return None
919 # Felder für alle Sprachvarianten ausfüllen
921 # >>> dst.expand_fields()
922 # >>> print dst
923 # Diens-te;Dien-ste;Diens-te;Dien-ste;Dien-ste
924 # >>> aalbst.expand_fields()
925 # >>> print aalbst
926 # Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand # Test
927 # >>> abus.expand_fields()
928 # >>> print unicode(abus)
929 # ab<bü-ßen;ab<bü-ßen;ab<büs-sen;ab<bü-ssen;ab<büs-sen
930 # >>> boss.expand_fields()
931 # >>> print unicode(boss)
932 # Boss;Boss;Boss;Boss;Boss # en.
933 # >>> frs.expand_fields()
934 # >>> print unicode(frs)
935 # -1-;-2-;fräs-se;frä-sse;fräs-se
937 # ::
939 def expand_fields(self):
940 for i, sv in enumerate(self.feldnamen):
941 field = self.get(sv) or '-%d-' % (i+1)
942 try:
943 self[i] = field
944 except IndexError:
945 self.append(field)
947 # Felder für Sprachvarianten zusammenfassen
948 # -----------------------------------------
950 # Weglassen von Feldern, wenn sich der Inhalt durch Ableitung gewinnen läßt::
952 # >>> aalbst.conflate_fields()
953 # >>> print aalbst
954 # Aal=be<stand # Test
955 # >>> dst.conflate_fields()
956 # >>> print dst
957 # Diens-te
958 # >>> frs.conflate_fields()
959 # >>> print unicode(frs)
960 # -1-;-2-;fräs-se;frä-sse
961 # >>> abus.conflate_fields()
962 # >>> print unicode(abus)
963 # ab<bü-ßen
964 # >>> boss.conflate_fields()
965 # >>> print unicode(boss)
966 # Boss;Boss # en.
968 # ::
970 def conflate_fields(self):
971 if len(self) == 1: # bereits kompakt
972 return
973 l_min = 1
974 for i, wort in enumerate(self):
975 if wort.startswith(u'-'):
976 l_min = i+1
977 for tag in reversed(self.feldnamen[l_min:len(self)]):
978 wort = self.pop()
979 rekonstruktion = self.get(tag)
980 # print tag, repr(self), wort, rekonstruktion
981 if wort != rekonstruktion:
982 self.append(wort)
983 return
985 # Einträge zusammenfassen
986 # -----------------------
988 # >>> entry = ShortEntry(u'qua-li-täts=be<wusst;-2-')
989 # >>> entry.merge(ShortEntry(u'-1-;qua-li-täts=be<wußt'))
990 # >>> print unicode(entry)
991 # qua-li-täts=be<wusst
992 # >>> entry = ShortEntry(u'Qua-li-fi-zie-rungs==maß=nah-me')
993 # >>> entry.merge(ShortEntry(u'-1-;-2-;Qua-li-fi-zie-rungs==mass=nah-me'))
994 # >>> print unicode(entry)
995 # Qua-li-fi-zie-rungs==maß=nah-me
996 # >>> entry = ShortEntry(u'-1-;-2-;Qua-li-fi-zie-rungs==mass=nah-me')
997 # >>> entry.merge(ShortEntry(u'Qua-li-fi-zie-rungs==maß=nah-me'))
998 # >>> print unicode(entry)
999 # Qua-li-fi-zie-rungs==maß=nah-me
1001 # ::
1003 def merge(self, other, allow_alternatives=False):
1004 self.expand_fields()
1005 other.expand_fields()
1006 for i, (s_field, o_field) in enumerate(zip(self,other)):
1007 if s_field == o_field or o_field.startswith(u'-'):
1008 continue
1009 if s_field.startswith(u'-'):
1010 self[i] = o_field
1011 elif s_field != o_field:
1012 if not allow_alternatives:
1013 self.conflate_fields()
1014 other.conflate_fields()
1015 raise AssertionError('%s != %s' %(s_field, o_field))
1016 self[i] = u'[%s/%s]' % (s_field, o_field)
1017 self.conflate_fields()
1018 other.conflate_fields()
1021 # Umwandeln in Tupel von WordEntry Instanzen (Langformat)
1022 # -------------------------------------------------------
1024 # >>> def print_langform(line, conflate=True):
1025 # ... for e in ShortEntry(line).wordentries(conflate):
1026 # ... print unicode(e)
1028 # >>> print_langform(u'ba-den')
1029 # baden;ba-den
1030 # >>> print_langform(u'Wes-te')
1031 # Weste;-2-;We-ste;Wes-te
1032 # >>> print_langform(u'Toll-patsch;-2-')
1033 # Tollpatsch;-2-;-3-;Toll-patsch
1034 # >>> print_langform(u'-1-;rau-he')
1035 # rauhe;-2-;rau-he;-4-
1037 # Bei Rechtschreibänderungen entsteht mehr als ein Eintrag:
1039 # >>> print_langform(u'Ab<fall=la-ger # Rechtschreibänderung')
1040 # Abfallager;-2-;Ab<fa{ll/ll=l}a-.ger;-4- # Rechtschreibänderung
1041 # Abfalllager;-2-;-3-;Ab<fall=la-ger # Rechtschreibänderung
1043 # >>> print_langform(u'Ab<guss')
1044 # Abguß;-2-;Ab<guß;-4-
1045 # Abguss;-2-;-3-;Ab<guss;Ab<guss
1047 # >>> print_langform(u'groß')
1048 # groß;groß
1049 # gross;-2-;-3-;-4-;gross
1051 # >>> print_langform(u'gro-ßen')
1052 # großen;gro-ßen
1053 # grossen;-2-;-3-;-4-;-5-;gro-ssen;gros-sen;gros-sen
1055 # >>> print_langform(u'Spaß')
1056 # Spaß;Spaß
1057 # Spass;-2-;-3-;-4-;Spass
1058 # >>> print_langform(u'Spass')
1059 # Spaß;-2-;Spaß;-4-
1060 # Spass;-2-;-3-;Spass;Spass
1061 # >>> print_langform(u'spa-ßen')
1062 # spaßen;spa-ßen
1063 # spassen;-2-;-3-;-4-;-5-;spa-ssen;spas-sen;spas-sen
1065 # >>> print_langform(u'ab<bü-ßen')
1066 # abbüßen;ab<bü-ßen
1067 # abbüssen;-2-;-3-;-4-;-5-;ab<bü-ssen;ab<büs-sen;ab<büs-sen
1069 # >>> print_langform(u'Boss;Boss # en.')
1070 # Boss;Boss # en.
1071 # >>> print_langform(u'Biss;-2-')
1072 # Biss;-2-;-3-;Biss
1074 # >>> print_langform(u'Pass=sys-tem')
1075 # Paßsystem;-2-;Paß=sy-stem;-4-
1076 # Passsystem;-2-;-3-;Pass=sys-tem;-5-;Pass=sy-stem;Pass=sys-tem;-8-
1077 # Passystem;-2-;-3-;-4-;-5-;-6-;-7-;Pa{ss/ss=s}y-.stem
1079 # >>> print_langform(u'Press=saft')
1080 # Preßsaft;-2-;Preß=saft;-4-
1081 # Presssaft;-2-;-3-;Press=saft;-5-;Press=saft;Press=saft;-8-
1082 # Pressaft;-2-;-3-;-4-;-5-;-6-;-7-;Pre{ss/ss=s}aft
1084 # >>> print_langform(u'Pro<gramm==maß=nah-me')
1085 # Programmaßnahme;-2-;Pro<gra{mm/mm==m}aß=nah-me;-4-
1086 # Programmmaßnahme;-2-;-3-;Pro<gramm==maß=nah-me
1087 # Programmassnahme;-2-;-3-;-4-;-5-;Pro<gra{mm/mm==m}ass=nah-me;-7-;Pro<gra{mm/mm==m}ass=nah-me
1088 # Programmmassnahme;-2-;-3-;-4-;-5-;-6-;Pro<gramm==mass=nah-me;-8-
1090 # >>> print_langform(u'Pass=stra-ße')
1091 # Paßstraße;-2-;Paß=stra-ße;-4-
1092 # Passstraße;-2-;-3-;Pass=stra-ße
1093 # Passstrasse;-2-;-3-;-4-;-5-;Pass=stra-sse;Pass=stras-se;Pass=stras-se
1095 # Explizit nur eine Schreibung:
1096 # >>> print_langform(u'Abend=dress;Abend=dress')
1097 # Abenddress;Abend=dress
1098 # >>> print_langform(u'-1-;Abend=dreß')
1099 # Abenddreß;-2-;Abend=dreß;-4-
1100 # Abenddress;-2-;-3-;-4-;-5-;Abend=dress;-7-;Abend=dress
1102 # Nur Kommentar:
1103 # >>> print_langform(u'# holla')
1104 # # holla
1106 # >>> print_langform(u'Pro<zess=en-.de;Pro<zeß=en-de;Pro<zess=en-.de')
1107 # Prozeßende;-2-;Pro<zeß=en-de;-4-
1108 # Prozessende;-2-;-3-;Pro<zess=en-.de;Pro<zess=en-.de
1110 # ::
1112 def wordentries(self, conflate=True):
1113 tags = ['x', 'de-1901', 'de-1996']
1114 if (u'ß' in (self.get('de-1901') or self.get('de') or '')
1115 or len(self) > 2):
1116 tags += ['x', 'de-1901-x-versal',
1117 'de-1996-x-versal', 'de-CH-1901']
1118 keys = {}
1119 entries = []
1121 for (n, tag) in enumerate(tags):
1122 word = self.get(tag)
1123 if not word:
1124 continue
1125 key = join_word(word)
1126 try:
1127 i = keys[key]
1128 except KeyError:
1129 i = keys[key] = len(entries)
1130 entries.append(WordEntry(key))
1131 entries[-1].comment = self.comment
1132 entry = entries[i]
1134 while len(entry) < n+1:
1135 # print tag, key, "len e", len(entry), "Spalte", n+1
1136 entry.append(u'-%s-' % (len(entry)+1))
1137 entry.append(word)
1139 # Auffüllen, Komprimieren
1140 for entry in entries:
1141 if len(entry) == 3:
1142 entry.append(u'-4-')
1143 if len(entry) == 7:
1144 entry.append(u'-8-')
1145 if conflate:
1146 entry.conflate_fields()
1147 # # allgemein-versal, falls gleich de-1996 und de-1901 fehlt
1148 # if (len(entry) == 4 and entry[2].startswith(u'-')
1149 # and entry[3] == self.get('de-CH-1901')):
1150 # entry.append(entry[3])
1152 if not entries and self.comment:
1153 return [WordEntry(u'# '+self.comment)]
1154 return entries
1157 # Funktionen
1158 # ==========
1160 # Ableitung von de-1901 aus de-1996
1161 # ---------------------------------
1163 # Ableitung von de-1901 aus de-1996 (Reversion der Reform 1996).
1165 # Mit keep_key == True werden nur Änderungen vorgenommen, die die
1166 # Schreibweise des (ungetrennten) Wortes nicht verändern.
1168 # >>> from wortliste import ableitung1901
1170 # ::
1172 def ableitung1901(wort, keep_key=False):
1174 # Trennregeländerungen
1175 # ~~~~~~~~~~~~~~~~~~~~
1177 # Diese Regeln ändern nicht das Wort, nur die Trennmöglichkeiten.
1179 # 1. Trenne nie st: Ersetze 's-t' mit u'-st'.
1181 # >>> ableitung1901(u'Diens-te')
1182 # u'Dien-ste'
1183 # >>> print ableitung1901(u'wuss-te', keep_key=True)
1184 # wuss-te
1186 # Keine Trennung nach nur einem Buchstaben:
1188 # >>> ableitung1901(u'Es-te')
1189 # u'Este'
1190 # >>> print ableitung1901(u'Nord=os-ten')
1191 # Nord=osten
1192 # >>> print ableitung1901(u'Po-ly<es-ter')
1193 # Po-ly<ester
1194 # >>> print ableitung1901(u'the-is-tisch')
1195 # the-istisch
1197 # Keine Trennung von s-theta:
1199 # >>> print ableitung1901(u'Äs-thet')
1200 # Äs-thet
1202 # >>> print ableitung1901(u'pssst') # Sonderfall: falsche Umwandlung
1203 # psßt
1205 # 2. Trenne ck als k-k: Ersetze '-ck' mit '{ck/k-k}'.
1207 # >>> ableitung1901(u'Ha-cke')
1208 # u'Ha{ck/k-k}e'
1210 # ::
1212 wort = re.sub(u'(?<!s)s-t(?!h)', u'-st', wort)
1213 wort = wort.replace(u'-ck', u'{ck/k-k}')
1214 # keine Trennung nach nur 1 Buchstaben:
1215 wort = re.sub(u'((?<=[=<-].)|(?<=^.))-', ur'', wort)
1217 if keep_key:
1218 return wort
1220 # Rechtschreibänderungen
1221 # ~~~~~~~~~~~~~~~~~~~~~~
1223 # Diese Regeln ändern die Schreibung des ungetrennten Worts (und somit den
1224 # Schlüssel im Langformat der Wortliste).
1226 # 3. kein ss und sst am Wortende (ungetrenntes "ss" nur in Ausnahmen):
1228 # Ersetze 'ss' (evt. gefolgt von t), wenn es am Silbenende auftaucht
1229 # (vor [-=<>]) mit ß (und evt. t).
1231 # Aber: 'ßt' und Schluss-ß auch in de-1996 möglich (langer Vokal)
1233 # >>> print ableitung1901(u'passt')
1234 # paßt
1235 # >>> print ableitung1901(u'Hass')
1236 # Haß
1237 # >>> print ableitung1901(u'Was-ser')
1238 # Was-ser
1239 # >>> print ableitung1901(u'Fass=brau-se')
1240 # Faß=brau-se
1241 # >>> print ableitung1901(u'wuss-te')
1242 # wuß-te
1244 # ::
1246 wort = re.sub(u'ss(t?)(?=[-=<>]|$)', ur'ß\1', wort)
1248 # 4. Dreikonsonantenregel:
1250 # Ersetze 'mm=m' mit '{mm/mm=m}' (für alle Konsonanten vor Selbstlaut)
1252 # >>> print ableitung1901(u'Kipp=pflug')
1253 # Kipp=pflug
1254 # >>> print ableitung1901(u'Kipp=punkt')
1255 # Ki{pp/pp=p}unkt
1256 # >>> print ableitung1901(u'Ab<fall=la-ger')
1257 # Ab<fa{ll/ll=l}a-.ger
1258 # >>> print ableitung1901(u'hell>licht')
1259 # he{ll/ll>l}icht
1260 # >>> print ableitung1901(u'Pro<gramm==maß=nah-me')
1261 # Pro<gra{mm/mm==m}aß=nah-me
1263 # ::
1265 wort = re.sub(ur'([bfglmnprt])\1([=>]+)\1(?=[aeiouyäöü])',
1266 ur'{\1\1/\1\1\2\1}', wort)
1268 # unterdrücken der Trennung nach nur einem Buchstaben
1269 wort = re.sub(ur'(?<=[=>].}[aeiouyäöü])([-<])\.?', ur'\1.', wort)
1271 return wort
1274 # Ableitung von de-CH/de-x-versal (SZ-Ersatzschreibung):
1275 # ------------------------------------------------------------------
1277 # >>> from wortliste import versalschreibung
1278 # >>> print versalschreibung(u'paßt')
1279 # passt
1280 # >>> print versalschreibung(u'bü-ßen', 'de-CH')
1281 # büs-sen
1282 # >>> print versalschreibung(u'bü-ßen', 'de-1996-x-versal')
1283 # büs-sen
1284 # >>> print versalschreibung(u'bü-ßen', 'de-CH-1901')
1285 # büs-sen
1286 # >>> print versalschreibung(u'Groß=se-gel', 'de-1901-x-versal')
1287 # Gross=se-gel
1288 # >>> print versalschreibung(u'Groß=se-gel', 'de-CH-1901')
1289 # Gro{ss/ss=s}e-.gel
1290 # >>> print versalschreibung(u'Pass=sy-ste-me', 'de-CH-1901')
1291 # Pa{ss/ss=s}y-.ste-me
1292 # >>> print versalschreibung(u'Pro<zeß=en-de', 'de-CH-1901')
1293 # Pro<zess=en-.de
1294 # >>> print versalschreibung(u'Pro<zess=en-.de', 'de-CH-1901')
1295 # Pro<zess=en-.de
1296 # >>> print versalschreibung(u'Ma-te-ri-al=fluß==sy-stem', 'de-CH-1901')
1297 # Ma-te-ri-al=flu{ss/ss==s}y-.stem
1299 # ::
1301 def versalschreibung(wort, lang='de'):
1303 # Ersetze 'ß' mit 'ss' ::
1305 wort = wort.replace(u'ß', u'ss')
1307 # Trennung von Ersatz-ss in de-CH und de-1996 nach Sprechsilbenregel::
1309 if '1901-x-versal' not in lang:
1310 wort = re.sub(u'(?<=[aeiouyäöü])-\.?ss', u's-s', wort)
1312 # Unterdrückung irreführender Trennung::
1314 wort = re.sub(u'ss(=+)(en|er)([<-])\.?', ur'ss\1\2\3.', wort)
1316 # Dreikonsonantenregel für Ersatz-ss in de-CH-1901::
1318 if 'CH-1901' in lang:
1319 wort = re.sub(u'ss(=+)s(?=[aeiouyäöü])', ur'{ss/ss\1s}', wort)
1320 # unterdrücken der Trennung nach nur einem Buchstaben
1321 wort = re.sub(ur'(?<=[=>]s}[aeiouyäöü])([-<])\.?', ur'\1.', wort)
1323 return wort
1326 # join_word
1327 # ---------
1329 # Trennzeichen entfernen::
1331 def join_word(wort, assert_complete=False):
1333 # Einfache Trennzeichen:
1335 # == ================================================================
1336 # \· ungewichtete Trennstelle (solche, wo sich noch niemand um die
1337 # Gewichtung gekümmert hat)
1338 # \. unerwünschte Trennstelle (sinnentstellend), z.B. Ur·in.stinkt
1339 # oder ungünstige Trennstelle (verwirrend), z.B. Atom·en.er·gie
1340 # in ungewichteten Wörtern
1341 # \= Trennstelle an Wortfugen (Wort=fu-ge)
1342 # \< Trennstelle nach Präfix (Vor<sil-be)
1343 # \> Trennstelle vor Suffix (Freund>schaf-ten)
1344 # \- Nebentrennstelle (ge-hen)
1345 # == ================================================================
1347 # ::
1349 table = {}
1350 for char in u'·.=|-_<>':
1351 table[ord(char)] = None
1352 key = wort.translate(table)
1354 # Spezielle Trennungen für die traditionelle Rechtschreibung
1355 # (siehe ../../dokumente/README.wortliste)::
1357 if '{' in key or '}' in key:
1358 key = key.replace(u'{ck/kk}', u'ck')
1359 key = key.replace(u'{ck/k', u'k')
1360 key = key.replace(u'k}', u'k')
1361 # Konsonanthäufungen an Wortfuge: '{xx/xxx}' -> 'xx':
1362 key = re.sub(ur'\{(.)\1/\1\1\1\}', ur'\1\1', key)
1363 # schon getrennt: ('{xx/xx' -> 'xx' und 'x}' -> 'x'):
1364 key = re.sub(ur'\{(.)\1/\1\1$', ur'\1\1', key)
1365 key = re.sub(ur'^(.)\}', ur'\1', key)
1367 # Trennstellen in doppeldeutigen Wörtern::
1369 if '[' in key or ']' in key:
1370 key = re.sub(ur'\[(.*)/\1\]', ur'\1', key)
1371 # schon getrennt:
1372 key = re.sub(ur'\[([^/\[]+)$', ur'\1', key)
1373 key = re.sub(ur'^([^/\]]+)\]', ur'\1', key)
1375 # Test auf verbliebene komplexe Trennstellen::
1377 if assert_complete:
1378 for spez in u'[{/}]':
1379 if spez in key:
1380 raise AssertionError('Spezialtrennung %s, %s' %
1381 (wort.encode('utf8'), key.encode('utf8')))
1383 return key
1385 # zerlege
1386 # -------
1388 # Zerlege ein Wort mit Trennzeichen in eine Liste von Silben und eine Liste
1389 # von Trennzeichen)
1391 # >>> from wortliste import zerlege
1393 # >>> zerlege(u'Haupt=stel-le')
1394 # ([u'Haupt', u'stel', u'le'], [u'=', u'-'])
1395 # >>> zerlege(u'Ge<samt=be<triebs=rats==chef')
1396 # ([u'Ge', u'samt', u'be', u'triebs', u'rats', u'chef'], [u'<', u'=', u'<', u'=', u'=='])
1397 # >>> zerlege(u'an<stands>los')
1398 # ([u'an', u'stands', u'los'], [u'<', u'>'])
1399 # >>> zerlege(u'An<al.pha-bet')
1400 # ([u'An', u'al', u'pha', u'bet'], [u'<', u'.', u'-'])
1402 # ::
1404 def zerlege(wort):
1405 silben = re.split(u'[-·._<>=]+', wort)
1406 trennzeichen = re.split(u'[^-·._|<>=]+', wort)
1407 return silben, [tz for tz in trennzeichen if tz]
1409 # TransferError
1410 # -------------
1412 # Fehler beim Übertragen von Trennstellen mit uebertrage_::
1414 class TransferError(ValueError):
1415 def __init__(self, wort1, wort2):
1416 msg = u'Inkompatibel: %s %s' % (wort1, wort2)
1417 ValueError.__init__(self, msg.encode('utf8'))
1419 def __unicode__(self):
1420 return str(self).decode('utf8')
1423 # uebertrage
1424 # ----------
1426 # Übertrage die Trennzeichen von `wort1` auf `wort2`:
1428 # >>> from wortliste import uebertrage, TransferError
1430 # >>> uebertrage(u'Haupt=stel-le', u'Haupt·stel·le')
1431 # u'Haupt=stel-le'
1433 # Auch teilweise Übertragung, von "kategorisiert" nach "unkategorisiert":
1435 # >>> print uebertrage(u'Haupt=stel-le', u'Haupt=stel·le')
1436 # Haupt=stel-le
1438 # >>> print uebertrage(u'Haupt·stel-le', u'Haupt=stel·le')
1439 # Haupt=stel-le
1441 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler')
1442 # Aus<stel-ler
1444 # >>> print uebertrage(u'Freund>schaf·ten', u'Freund-schaf-ten')
1445 # Freund>schaf-ten
1447 # Übertragung doppelter Marker:
1449 # >>> print uebertrage(u'ver<<aus<ga-be', u'ver<aus<ga-be')
1450 # ver<<aus<ga-be
1452 # >>> print uebertrage(u'freund>lich>>keit', u'freund>lich>keit')
1453 # freund>lich>>keit
1455 # >>> print uebertrage(u'Amts==haupt=stel-le', u'Amts=haupt=stel-le')
1456 # Amts==haupt=stel-le
1458 # Kein Überschreiben doppelter Marker:
1459 # >>> print uebertrage(u'ver<aus<ga-be', u'ver<<aus<ga-be')
1460 # ver<<aus<ga-be
1462 # >>> print uebertrage(u'Amts=haupt=stel-le', u'Amts==haupt=stel·le')
1463 # Amts==haupt=stel-le
1465 # Erhalt des Markers für ungünstige Stellen:
1466 # >>> print uebertrage(u'An·al.pha·bet', u'An<al.pha-bet')
1467 # An<al.pha-bet
1469 # Keine Übertragung, wenn die Zahl oder Position der Trennstellen
1470 # unterschiedlich ist oder bei unterschiedlichen Wörtern:
1472 # >>> try:
1473 # ... uebertrage(u'Ha-upt=stel-le', u'Haupt=stel·le')
1474 # ... uebertrage(u'Haupt=ste-lle', u'Haupt=stel·le')
1475 # ... uebertrage(u'Waupt=stel-le', u'Haupt=stel·le')
1476 # ... except TransferError:
1477 # ... pass
1479 # Übertragung auch bei unterschiedlicher Schreibung oder Position der
1480 # Trennstellen mit `strict=False` (für Abgleich zwischen Sprachvarianten):
1482 # >>> uebertrage(u'er-ster', u'ers·ter', strict=False)
1483 # u'ers-ter'
1484 # >>> uebertrage(u'Fluß=bett', u'Fluss·bett', strict=False)
1485 # u'Fluss=bett'
1486 # >>> uebertrage(u'ab>bei-ßen', u'ab>beis·sen', strict=False)
1487 # u'ab>beis-sen'
1488 # >>> print uebertrage(u'Aus<tausch=dien-stes', u'Aus-tausch=diens-tes', False)
1489 # Aus<tausch=diens-tes
1491 # Auch mit `strict=False` muß die Zahl der Trennstellen übereinstimmen
1492 # (Ausnahmen siehe unten):
1494 # >>> try:
1495 # ... uebertrage(u'Ha-upt=ste-lle', u'Haupt=stel·le', strict=False)
1496 # ... except TransferError:
1497 # ... pass
1499 # Akzeptiere unterschiedliche Anzahl von Trennungen bei st und ck nach
1500 # Selbstlaut:
1502 # >>> uebertrage(u'acht=ecki-ge', u'acht·e{ck/k·k}i·ge', strict=False)
1503 # u'acht=e{ck/k-k}i-ge'
1504 # >>> uebertrage(u'As-to-ria', u'Asto·ria', strict=False)
1505 # u'Asto-ria'
1506 # >>> uebertrage(u'Asto-ria', u'As·to·ria', strict=False)
1507 # u'As-to-ria'
1508 # >>> uebertrage(u'So-fa=ecke', u'So·fa=e{ck/k-k}e', strict=False)
1509 # u'So-fa=e{ck/k-k}e'
1510 # >>> uebertrage(u'Drei=ecks=ecke', u'Drei=ecks==e{ck/k-k}e', strict=False)
1511 # u'Drei=ecks==e{ck/k-k}e'
1513 # Mit ``upgrade=False`` werden nur unspezifische Trennstellen überschrieben:
1515 # >>> print uebertrage(u'an=stel-le', u'an<stel·le', upgrade=False)
1516 # an<stel-le
1518 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler', upgrade=False)
1519 # Aus-stel-ler
1521 # >>> print uebertrage(u'Aus-stel-ler', u'Aus<stel-ler', upgrade=False)
1522 # Aus<stel-ler
1524 # >>> print uebertrage(u'vor<an<<stel-le', u'vor-an<stel·le', upgrade=False)
1525 # vor-an<stel-le
1527 # ::
1529 selbstlaute = u'aeiouäöüAEIOUÄÖÜ'
1531 def uebertrage(wort1, wort2, strict=True, upgrade=True):
1533 silben1, trennzeichen1 = zerlege(wort1)
1534 silben2, trennzeichen2 = zerlege(wort2)
1535 # Prüfe strikte Übereinstimmung:
1536 if silben1 != silben2 and strict:
1537 if u'<' in trennzeichen1 or u'·' in trennzeichen2:
1538 raise TransferError(wort1, wort2)
1539 else:
1540 return wort2
1541 # Prüfe ungefähre Übereinstimmung:
1542 if len(trennzeichen1) != len(trennzeichen2):
1543 # Selbstlaut + st oder ck?
1544 for s in selbstlaute:
1545 if (wort2.find(s+u'{ck/k·k}') != -1 or
1546 wort2.find(s+u'{ck/k-k}') != -1):
1547 wort1 = re.sub(u'%sck([%s])'%(s,selbstlaute),
1548 ur'%s-ck\1'%s, wort1)
1549 silben1, trennzeichen1 = zerlege(wort1)
1550 if wort2.find(s+u's·t') != -1:
1551 wort1 = wort1.replace(s+u'st', s+u's-t')
1552 silben1, trennzeichen1 = zerlege(wort1)
1553 elif wort1.find(s+u's-t') != -1:
1554 wort1 = wort1.replace(s+u's-t', s+u'st')
1555 silben1, trennzeichen1 = zerlege(wort1)
1556 # print u'retry:', silben1, trennzeichen1
1557 # immer noch ungleiche Zahl an Trennstellen?
1558 if len(trennzeichen1) != len(trennzeichen2):
1559 raise TransferError(wort1, wort2)
1561 # Baue wort3 aus silben2 und spezifischeren Trennzeichen:
1562 wort3 = silben2.pop(0)
1563 for t1,t2 in zip(trennzeichen1, trennzeichen2):
1564 if ((t2 == u'·' and t1 != u'.') # unspezifisch
1565 or upgrade and
1566 ((t2 in (u'-', u'<') and t1 in (u'<', u'<<', u'<=')) # Praefixe
1567 or (t2 in (u'-', u'>') and t1 in (u'>', u'>>', u'=>')) # Suffixe
1568 or (t2 in (u'-', u'=') and t1 in (u'=', u'==', u'===')) # W-fugen
1571 wort3 += t1
1572 elif t2 == u'.' and t1 not in u'·.':
1573 wort3 += t1 + t2
1574 else:
1575 wort3 += t2
1576 wort3 += silben2.pop(0)
1577 return wort3
1580 # Übertrag kategorisierter Trennstellen zwischen den Feldern aller Einträge
1581 # in `wortliste`::
1583 def sprachabgleich(entry, vorbildentry=None):
1585 if len(entry) <= 2:
1586 return # allgemeine Schreibung
1588 mit_affix = None # < oder >
1589 kategorisiert = None # kein ·
1590 unkategorisiert = None # mindestens ein ·
1591 gewichtet = None # == oder <= oder =>
1592 for field in entry[1:]:
1593 if field.startswith('-'): # -2-, -3-, ...
1594 continue
1595 if u'{' in field and u'[' in field: # Bi-ber==be[t=t/{tt/tt=t}]uch
1596 continue # zu komplex
1597 if u'·' in field:
1598 unkategorisiert = field
1599 elif u'<' in field or u'>' in field:
1600 mit_affix = field
1601 else:
1602 kategorisiert = field
1603 if u'==' in field or u'<=' in field or u'=>' in field:
1604 gewichtet = field
1605 if vorbildentry:
1606 for field in vorbildentry[1:]:
1607 if field.startswith('-'): # -2-, -3-, ...
1608 continue
1609 if u'{' in field and u'[' in field: # Bi-ber==be[t=t/{tt/tt=t}]uch
1610 continue # zu komplex
1611 if not mit_affix and u'<' in field or u'>' in field :
1612 mit_affix = field
1613 elif not kategorisiert and unkategorisiert and u'·' not in field:
1614 kategorisiert = field
1615 if not gewichtet and u'==' in field or u'<=' in field or u'=>' in field:
1616 gewichtet = field
1617 # print 've:', mit_affix, kategorisiert, unkategorisiert
1618 if mit_affix and (kategorisiert or unkategorisiert or gewichtet):
1619 for i in range(1,len(entry)):
1620 if entry[i].startswith('-'): # -2-, -3-, ...
1621 continue
1622 if u'<' not in entry[i] or u'·' in entry[i]:
1623 try:
1624 entry[i] = uebertrage(mit_affix, entry[i], strict=False)
1625 except TransferError, e:
1626 if not '/' in entry[i]:
1627 print u'Sprachabgleich:', unicode(e)
1628 # print mit_affix+u':', unicode(entry)
1629 elif kategorisiert and unkategorisiert:
1630 for i in range(1,len(entry)):
1631 if u'·' in entry[i]:
1632 try:
1633 entry[i] = uebertrage(kategorisiert, entry[i], strict=False)
1634 except TransferError, e:
1635 print u'Sprachabgleich:', unicode(e)
1636 # print kategorisiert, unicode(entry)
1637 elif gewichtet:
1638 for i in range(1,len(entry)):
1639 if u'=' in entry[i] and not (
1640 u'{' in entry[i] and u'[' in entry[i]):
1641 try:
1642 entry[i] = uebertrage(gewichtet, entry[i], strict=False)
1643 except TransferError, e:
1644 print u'Sprachabgleich:', unicode(e)
1648 # Großschreibung in Kleinschreibung wandeln und umgekehrt
1650 # Diese Version funktioniert auch für Wörter mit Trennzeichen (während
1651 # str.title() nach jedem Trennzeichen wieder groß anfängt)
1653 # >>> from wortliste import toggle_case
1654 # >>> toggle_case(u'Ha-se')
1655 # u'ha-se'
1656 # >>> toggle_case(u'arm')
1657 # u'Arm'
1658 # >>> toggle_case(u'frei=bier')
1659 # u'Frei=bier'
1660 # >>> toggle_case(u'L}a-ger')
1661 # u'l}a-ger'
1663 # Keine Änderung bei Wörtern mit Großbuchstaben im Inneren:
1665 # >>> toggle_case(u'USA')
1666 # u'USA'
1667 # >>> toggle_case(u'iRFD')
1668 # u'iRFD'
1670 # >>> toggle_case(u'gri[f-f/{ff/ff')
1671 # u'Gri[f-f/{ff/ff'
1672 # >>> toggle_case(u'Gri[f-f/{ff/ff')
1673 # u'gri[f-f/{ff/ff'
1675 # ::
1677 def toggle_case(wort):
1678 try:
1679 key = join_word(wort, assert_complete=True)
1680 except AssertionError:
1681 key = wort[0]
1682 if key.istitle():
1683 return wort.lower()
1684 elif key.islower():
1685 return wort[0].upper() + wort[1:]
1686 else:
1687 return wort
1689 # Sortierschlüssel
1690 # ================
1692 # Duden-Sortierung für die Wortliste
1694 # >>> from wortliste import sortkey_duden
1695 # >>> sortkey_duden([u"Abflußröhren"])
1696 # u'abflussrohren a*bflu*szroehren'
1697 # >>> sortkey_duden([u"Abflußrohren"])
1698 # u'abflussrohren a*bflu*szro*hren'
1699 # >>> sortkey_duden([u"Abflussrohren"])
1700 # u'abflussrohren'
1702 # >>> s = sorted([[u"Abflußröhren"], [u"Abflußrohren"], [u"Abflussrohren"]],
1703 # ... key=sortkey_duden)
1704 # >>> print ', '.join(e[0] for e in s)
1705 # Abflussrohren, Abflußrohren, Abflußröhren
1707 # Umschreibung
1709 # Ligaturen auflösen und andere "normalisierunde" Ersetzungen für den
1710 # (Haupt-)Sortierschlüssel (Akzente werden über ``unicodedata.normalize``
1711 # entfernt)::
1713 umschrift_skey = {
1714 ord(u'æ'): u'ae',
1715 ord(u'œ'): u'oe',
1716 ord(u'ſ'): u's',
1719 # "Zweitschlüssel" zur Unterscheidung von Umlauten/SZ und Basisbuchstaben::
1721 umschrift_subkey = {
1722 ord(u'a'): u'a*',
1723 ord(u'å'): u'aa',
1724 ord(u'ä'): u'ae',
1725 ord(u'o'): u'o*',
1726 ord(u'ö'): u'oe',
1727 ord(u'ø'): u'oe',
1728 ord(u'u'): u'u*',
1729 ord(u'ü'): u'ue',
1730 ord(u'ß'): u'sz',
1734 # sortkey_duden
1735 # -------------
1737 # Schlüssel für die alphabetische Sortierung gemäß Duden-Regeln
1738 # `entry` ist ein Eintrag im WordEntry oder ShortEntry Format oder
1739 # ein (Unicode) String.
1741 # >>> print sortkey_duden(weste) # WordEntry
1742 # weste
1743 # >>> print sortkey_duden(dst) # ShortEntry
1744 # dienste
1745 # >>> print sortkey_duden(u'Stra-ße')
1746 # strasse stra*sze
1747 # >>> print sortkey_duden([u'Büh-ne'])
1748 # buhne buehne
1750 # ::
1752 def sortkey_duden(entry):
1754 # ggf. ungetrenntes Wort extrahieren oder generieren::
1756 if isinstance(entry, list):
1757 try:
1758 key = entry.key()
1759 except AttributeError:
1760 key = entry[0]
1761 if len(entry) == 1: # ein Muster pro Zeile, siehe z.B. pre-1901
1762 key = join_word(key)
1763 else:
1764 key = join_word(entry)
1766 # Großschreibung ignorieren:
1768 # Der Duden sortiert Wörter, die sich nur in der Großschreibung unterscheiden
1769 # "klein vor groß" (ASCII sortiert "groß vor klein"). In der
1770 # `Trennmuster-Wortliste` kommen Wörter nur mit der häufiger anzutreffenden
1771 # Großschreibung vor, denn der TeX-Trennalgorithmus ignoriert Großschreibung.
1772 # ::
1774 key = key.lower()
1776 # Ersetzungen:
1778 # ß -> ss ::
1780 skey = key.replace(u'ß', u'ss')
1782 # Restliche Akzente weglassen: Wandeln in Darstellung von Buchstaben mit
1783 # Akzent als "Grundzeichen + kombinierender Akzent". Anschließend alle
1784 # nicht-ASCII-Zeichen ignorieren::
1786 skey = skey.translate(umschrift_skey)
1787 skey = unicodedata.normalize('NFKD', skey)
1788 skey = unicode(skey.encode('ascii', 'ignore'))
1790 # "Zweitschlüssel" für das eindeutige Einsortieren von Wörtern mit
1791 # gleichem Schlüssel (Masse/Maße, waren/wären, ...):
1793 # * "*" nach aou für die Unterscheidung Grund-/Umlaut
1794 # * ß->sz
1796 # ::
1798 if key != skey:
1799 subkey = key.translate(umschrift_subkey)
1800 skey = u'%s %s' % (skey,subkey)
1802 # Gib den Sortierschlüssel zurück::
1804 return skey
1808 # udiff
1809 # -----
1811 # Vergleiche zwei Sequenzen von `WordEntries`, gib einen "unified diff" als
1812 # Byte-String zurück (weil difflib nicht mit Unicode-Strings arbeiten kann).
1814 # Beispiel:
1816 # >>> from wortliste import udiff
1817 # >>> print udiff([abbeissen, aalbestand], [abbeissen], 'alt', 'neu')
1818 # --- alt
1819 # +++ neu
1820 # @@ -1,2 +1 @@
1821 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
1822 # -Aalbestand;Aal=be<stand # Test
1824 # ::
1826 def udiff(a, b, fromfile='', tofile='',
1827 fromfiledate='', tofiledate='', n=1, encoding='utf8'):
1829 a = [unicode(entry).encode(encoding) for entry in a]
1830 b = [unicode(entry).encode(encoding) for entry in b]
1832 diff = difflib.unified_diff(a, b, fromfile, tofile,
1833 fromfiledate, tofiledate, n, lineterm='')
1835 if diff:
1836 return '\n'.join(diff)
1837 else:
1838 return None
1841 def test_keys(wortliste):
1842 """Teste Übereinstimmung des ungetrennten Wortes in Feld 1
1843 mit den Trennmustern nach Entfernen der Trennmarker.
1844 Schreibe Inkonsistenzen auf die Standardausgabe.
1846 `wortliste` ist ein Iterator über die Einträge (Klasse `WordEntry`)
1848 is_OK = True
1849 for entry in wortliste:
1850 # Test der Übereinstimmung ungetrenntes/getrenntes Wort
1851 # für alle Felder:
1852 key = entry[0]
1853 for wort in entry[1:]:
1854 if wort.startswith(u'-'): # leere Felder
1855 continue
1856 if key != join_word(wort):
1857 is_OK = False
1858 print u"\nkey '%s' != join_word('%s')" % (key, wort),
1859 if key.lower() == join_word(wort).lower():
1860 print(u" Abgleich der Großschreibung mit"
1861 u"`prepare-patch.py grossabgleich`."),
1862 return is_OK
1865 # Sprachtags
1866 # ==========
1868 # Normalisierung und Expansion von Sprachtags nach [BCP47]_
1870 # >>> from wortliste import normalize_language_tag
1871 # >>> normalize_language_tag('de_AT-1901')
1872 # ['de-AT-1901', 'de-AT', 'de-1901', 'de']
1874 # >>> normalize_language_tag('de') # Deutsch, allgemeingültig
1875 # ['de']
1876 # >>> normalize_language_tag('de_1901') # traditionell (Reform 1901)
1877 # ['de-1901', 'de']
1878 # >>> normalize_language_tag('de_1996') # reformiert (Reform 1996)
1879 # ['de-1996', 'de']
1880 # >>> normalize_language_tag('de_CH') # ohne ß (Schweiz oder versal)
1881 # ['de-CH', 'de']
1882 # >>> normalize_language_tag('de-x-versal') # versal
1883 # ['de-x-versal', 'de']
1884 # >>> normalize_language_tag('de-1901-x-versal') # versal
1885 # ['de-1901-x-versal', 'de-1901', 'de-x-versal', 'de']
1886 # >>> normalize_language_tag('de_CH-1996') # Schweiz traditionell (süssauer)
1887 # ['de-CH-1996', 'de-CH', 'de-1996', 'de']
1889 # 'de': 1, # Deutsch, allgemeingültig
1890 # 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
1891 # 'de-1996': 3, # reformierte Reformschreibung (1996)
1892 # 'de-x-versal': 4, # ohne ß (Schweiz oder versal) allgemein
1893 # # 'de-CH': 4, # Alias
1894 # 'de-1901-x-versal': 5, # ohne ß (Schweiz oder versal) "traditionell"
1895 # 'de-1996-x-versal': 6, # ohne ß (Schweiz oder versal) "reformiert"
1896 # # 'de-CH-1996': 6, # Alias
1897 # 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
1900 # ::
1902 def normalize_language_tag(tag):
1903 """Return a list of normalized combinations for a `BCP 47` language tag.
1905 # normalize:
1906 tag = tag.replace('_','-')
1907 # split (except singletons, which mark the following tag as non-standard):
1908 tag = re.sub(r'-([a-zA-Z0-9])-', r'-\1_', tag)
1909 taglist = []
1910 subtags = [subtag.replace('_', '-') for subtag in tag.split('-')]
1911 base_tag = [subtags.pop(0)]
1912 # find all combinations of subtags
1913 for n in range(len(subtags), 0, -1):
1914 # for tags in unique_combinations(subtags, n):
1915 for tags in itertools.combinations(subtags, n):
1916 taglist.append('-'.join(base_tag+list(tags)))
1917 taglist += base_tag
1918 return taglist
1922 # Test
1923 # ====
1925 # ::
1927 if __name__ == '__main__':
1928 import sys
1930 # sys.exit()
1932 # sys.stdout mit UTF8 encoding (wie in Python 3)
1933 sys.stdout = codecs.getwriter('UTF-8')(sys.stdout)
1935 print u"Test der Werkzeuge und inneren Konsistenz der Wortliste\n"
1937 wordfile = WordFile('../../../wortliste')
1938 # print 'Dateiobjekt:', wordfile
1940 # Liste der Datenfelder (die Klasseninstanz als Argument für `list` liefert
1941 # den Iterator über die Felder, `list` macht daraus eine Liste)::
1943 wortliste = list(wordfile)
1944 print len(wortliste), u"Einträge\n"
1946 # Sprachauswahl::
1948 # Sprachtags:
1950 # sprache = 'de-1901' # traditionell
1951 # sprache = 'de-1996' # Reformschreibung
1952 # sprache = 'de-x-GROSS' # ohne ß (Schweiz oder GROSS) allgemein
1953 # sprache = 'de-1901-x-GROSS' # ohne ß (Schweiz oder GROSS) "traditionell"
1954 # sprache = 'de-1996-x-GROSS' # ohne ß (Schweiz oder GROSS) "reformiert"
1955 # sprache = 'de-CH-1901' # ohne ß (Schweiz) "traditionell" ("süssauer")
1957 # worte = [entry.get(sprache) for entry in wortliste if wort is not None]
1958 # print len(worte), u"Einträge für Sprachvariante", sprache
1961 # Test keys::
1963 print u"Teste Schlüssel-Trennmuster-Übereinstimmung:",
1964 if test_keys(wortliste):
1965 print u"OK",
1966 print
1968 # Doppeleinträge::
1970 doppelte = 0
1971 words = set()
1972 for entry in wortliste:
1973 key = entry[0].lower()
1974 if key in words:
1975 doppelte += 1
1976 print unicode(entry)
1977 words.add(key)
1978 print doppelte,
1979 print u"Doppeleinträge (ohne Berücksichtigung der Großschreibung)."
1980 if doppelte:
1981 print u" Entfernen mit `prepare-patch.py doppelte`."
1982 print u" Patch vor Anwendung durchsehen!"
1985 # Ein Wörterbuch (dict Instanz)::
1987 # wordfile.seek(0) # Pointer zurücksetzen
1988 # words = wordfile.asdict()
1990 # print len(words), u"Wörterbucheinträge"
1992 # Zeilenrekonstruktion::
1994 # am Beispiel der Scheiterbeige:
1995 # original = u'beige;beige # vgl. Scheiter-bei-ge'
1996 # entry = words[u"beige"]
1997 # line = unicode(entry)
1998 # assert original == line, "Rejoined %s != %s" % (line, original)
2000 # komplett:
2001 wordfile.seek(0) # Pointer zurücksetzen
2002 OK = 0
2003 line = wordfile.readline().rstrip().decode(wordfile.encoding)
2004 while line:
2005 entry = WordEntry(line)
2006 if line == unicode(entry):
2007 OK +=1
2008 else:
2009 print u'-', line,
2010 print u'+', unicode(entry)
2011 line = wordfile.readline().rstrip().decode(wordfile.encoding)
2013 print OK, u"Einträge rekonstruiert"
2017 # Quellen
2018 # =======
2020 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
2021 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
2023 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
2024 # http://mirrors.ctan.org/language/hyphenation/dehyph-exptl/projektbeschreibung.pdf