Update Trennstile und -filter.
[wortliste.git] / skripte / python / edit_tools / wortliste.py
blobda89b47a12d659f05b9ec0ee98f5e53eaa4a64e2
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 # ************
12 # ::
14 """Hilfsmittel für die Arbeit mit der `Wortliste`"""
16 # .. contents::
18 # Die hier versammelten Funktionen und Klassen dienen der Arbeit an und
19 # mit der freien `Wortliste der deutschsprachigen Trennmustermannschaft`_
20 # (wortliste_).
22 # Abhängigkeiten
23 # ==============
25 # Python 2.7 mit den Standardbibliotheken::
27 import difflib
28 import re
29 import codecs
30 import unicodedata
31 import copy
32 import itertools
33 import sys
34 from collections import defaultdict # Wörterbuch mit Default
36 # WordFile
37 # ========
39 # Klasse zum Lesen und Schreiben der `Wortliste`::
41 class WordFile(file):
43 encoding = 'utf8'
45 # Initialisierung
46 # ----------------
48 # Das Argument ``format`` ist "f8", "f5", oder "auto":
50 # :f8: das originale, maximal 8-spaltige Wortlisten-Format (Langform_),
51 # :f5: das neue, maximal 5-spaltige Wortlisten-Format (Kurzform_),
52 # :auto: bestimme das Format automatisch.
54 # ::
56 def __init__(self, name, mode='r', encoding='utf8', format='f8'):
58 self.encoding = encoding
59 file.__init__(self, name, mode)
60 # Dateiformat bestimmen und die Eintrags-Klasse setzen:
61 self.set_entry_class(format)
63 # Klasse zum Verarbeiten der Zeilen setzen.
64 # Mit self.format == "auto", werden die ersten Zeilen der Datei untersucht
65 # und der Pointer zurückgesetzt.
67 # ::
69 def set_entry_class(self, format, search_limit=1000):
71 assert(format in ('f8', 'f5', 'auto'))
72 self.format = format # default
73 self.entry_class = WordEntry # default
74 n = 0 # Zählindex
76 # Format anhand des Dateiinhalts bestimmen:
77 if format == 'auto':
78 for e in self:
79 # nur ein Feld oder erstes Feld leer oder mit Trennzeichen:
80 if len(e) == 1 or e and re.search(u'[-=<>]', e[0]):
81 self.format = 'f5'
82 break
83 # mehr als 5 Felder: 8-Felder-Format (Langform)
84 elif len(e) > 5:
85 self.format = 'f8'
86 break
87 if n > search_limit:
88 break
89 n += 1
90 if self.format == 'f5':
91 self.entry_class = ShortEntry
93 self.seek(0) # Dateizeiger zurücksetzen
96 # Iteration
97 # ---------
99 # Die spezielle Funktion `__iter__` wird aufgerufen wenn über eine
100 # Klasseninstanz iteriert wird.
101 # Sie liefert einen Iterator über die "geparsten" Zeilen (Datenfelder)
102 # ::
104 def __iter__(self):
105 entry_class = self.entry_class
106 while True:
107 line = self.next().rstrip().decode(self.encoding)
108 yield entry_class(line)
111 # asdict()
112 # --------
114 # Gib ein `dictionary` (assoziatives Array) mit ungetrenntem Wort als
115 # Schlüssel (`key`) und den Datenfeldern als `value` zurück::
117 def asdict(self):
118 words = {}
119 for entry in self:
120 key = entry.key()
121 if key == u'#': # reiner Kommentar
122 try:
123 words[u'#'].append(self.comment)
124 except KeyError:
125 words[u'#'] = self.comment
126 else:
127 words[key] = entry
128 return words
130 # writelines()
131 # ------------
133 # Schreibe eine Liste von `unicode` Strings (Zeilen ohne Zeilenendezeichen)
134 # in die Datei `destination`::
136 def writelines(self, lines, destination, encoding=None):
137 outfile = codecs.open(destination, 'w',
138 encoding=(encoding or self.encoding))
139 outfile.writelines(lines)
141 # write_entries()
142 # ---------------
144 # Schreibe eine Liste von Einträgen (WordEntry_ oder ShortEntry_ Objekten) in
145 # die Datei `destination`::
147 def write_entries(self, wortliste, destination, encoding=None):
148 lines = (unicode(entry) for entry in wortliste)
149 self.writelines(lines, destination, encoding)
152 # WordEntry
153 # =========
155 # Klasse für Einträge (Zeilen) der Wortliste_ in Langform_.
156 # Jede Zeile enthält einen Eintrag mit durch Semikolon „;“
157 # getrennten Feldern.
159 # Beispiel:
161 # >>> from wortliste import WordEntry
163 # >>> aalbestand = WordEntry(u'Aalbestand;Aal=be<stand # Test')
164 # >>> print aalbestand
165 # Aalbestand;Aal=be<stand # Test
167 # .. _Langform:
168 # .. _8-Spalten-Format:
170 # Bedeutung der Felder im 8-Spalten-Format (Langform):
172 # 1. Wort ungetrennt
173 # 2. Wort mit Trennungen, falls für alle Varianten identisch,
174 # anderenfalls leer
175 # 3. falls Feld 2 leer, Trennung nach traditioneller Rechtschreibung
176 # 4. falls Feld 2 leer, Trennung nach reformierter Rechtschreibung (2006)
177 # 5. falls Feld 2 leer, Trennung für Wortform, die entweder in
178 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird
179 # und für traditionelle und reformierte Rechtschreibung identisch ist
180 # 6. falls Feld 5 leer, Trennung für Wortform, die entweder in
181 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
182 # traditionelle Rechtschreibung
183 # 7. falls Feld 5 leer, Trennung für Wortform, die entweder in
184 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
185 # reformierte Rechtschreibung (2006)
186 # 8. falls Feld 5 leer, Trennung nach (deutsch)schweizerischer
187 # Rechtschreibung; insbesondere Wörter mit "sss" gefolgt von
188 # einem Vokal, die wie andere Dreifachkonsonanten gehandhabt wurden
189 # (also anders, als der Duden früher vorgeschrieben hat), z.B.
190 # "süssauer"
192 # ::
194 class WordEntry(list):
196 # Argumente
197 # ---------
199 # feldnamen, feldindizes
200 # ~~~~~~~~~~~~~~~~~~~~~~
202 # Benennung der Felder mit Sprachkürzeln (Tags nach [BCP47]_).
203 # (Die Zählung der Indizes beginnt in Python bei 0.)::
205 feldnamen = ('key', 'de', 'de-1901', 'de-1996', 'de-x-versal',
206 'de-1901-x-versal', 'de-1996-x-versal', 'de-CH-1901')
208 feldindizes = {
209 'key': 0, # Schlüssel
210 'de': 1, # Deutsch, allgemeingültig
211 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
212 'de-1996': 3, # reformierte Reformschreibung (1996)
213 'de-x-GROSS': 4, # ohne ß (Schweiz oder versal) allgemein
214 'de-x-versal': 4, # Alias
215 # 'de-CH': 4, # Alias
216 'de-1901-x-GROSS': 5, # ohne ß (Schweiz oder versal) "traditionell"
217 'de-1901-x-versal': 5, # Alias
218 'de-1996-x-GROSS': 6, # ohne ß (Schweiz oder versal) "reformiert"
219 'de-1996-x-versal': 6, # Alias
220 # 'de-CH-1996': 6, # Alias
221 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
225 # ersatzfelder
226 # ~~~~~~~~~~~~
228 # Bei der Auswahl eines Wortes in einer Rechtschreibvariante werden
229 # "generische" Felder gewählt, falls das "Originalfeld" fehlt. Die folgenden
230 # Tupel listen die Ersatzfelder für das ensprechende Originalfeld.
232 # Bsp: Ersatz-Felder für 'de-CH-1901':
234 # >>> for i in WordEntry.ersatzfelder[WordEntry.feldindizes['de-CH-1901']]:
235 # ... print WordEntry.feldnamen[i]
236 # de-1901-x-versal
237 # de-x-versal
238 # de-1901
239 # de
241 # ::
243 # Ersatzindizes Index Nr Name
244 ersatzfelder = (tuple(), # 0 1 key
245 tuple(), # 1 2 'de'
246 (1,), # 2 3 'de-1901'
247 (1,), # 3 4 'de-1996'
248 (1,), # 4 5 'de-x-versal'
249 (4, 2, 1), # 5 6 'de-1901-x-versal'
250 (4, 3, 1), # 6 7 'de-1996-x-versal'
251 (5, 4, 2, 1), # 7 8 'de-CH-1901'
255 # comment
256 # ~~~~~~~
258 # Kommentar, Vorgabe ist ein leerer String::
260 comment = u''
263 # Initialisierung
264 # ---------------
265 # ::
268 def __init__(self, line, delimiter=u';'):
270 self.delimiter = delimiter
272 # eventuell vorhandenen Kommentar abtrennen und speichern::
274 if u'#' in line:
275 line, comment = line.split(u'#', 1)
276 self.comment = comment.strip()
277 line = line.rstrip()
278 # print (line, self.comment)
280 if not line: # kein Inhalt
281 return
283 # Zerlegen in Datenfelder, in Liste eintragen::
285 for field in line.split(delimiter):
286 if field.startswith(u'-'):
287 self.append(u'')
288 else:
289 self.append(field)
292 # Rückverwandlung in String
293 # -------------------------
295 # Erzeugen eines Eintrag-Strings (ohne Zeilenendezeichen) aus der Liste der
296 # Datenfelder und dem Kommentar
298 # unicode()
299 # ~~~~~~~~~
301 # >>> unicode(aalbestand)
302 # u'Aalbestand;Aal=be<stand # Test'
304 # >>> grse = WordEntry(u'Grüße;Grü-ße')
305 # >>> print unicode(grse)
306 # Grüße;Grü-ße
308 # >>> leerkommentar = WordEntry(u'# Testkommentar ')
309 # >>> leerkommentar, unicode(leerkommentar)
310 # (WordEntry(u'# Testkommentar'), u'# Testkommentar')
312 # ::
314 def __unicode__(self):
315 # Nummerieren leerer Felder:
316 fields = [field or u'-%d-' % (i+1)
317 for (i, field) in enumerate(self)]
318 line = u';'.join(fields)
319 if self.comment:
320 line += u' # ' + self.comment
321 if not self:
322 line = line.lstrip()
323 return line
325 # __str__()
326 # ~~~~~~~~~
327 # >>> print type(str(grse)), str(grse).decode('utf8')
328 # <type 'str'> Grüße;Grü-ße
330 # ::
332 def __str__(self):
333 return unicode(self).encode('utf8')
335 # __repr__()
336 # ~~~~~~~~~~
337 # >>> print type(repr(grse)), repr(grse).decode('utf8')
338 # <type 'str'> WordEntry(u'Grüße;Grü-ße')
340 # ::
342 def __repr__(self):
343 s = u'%s(u\'%s\')' % (self.__class__.__name__, self)
344 return s.encode('utf8')
346 # lang_index()
347 # ------------
349 # Bestimme den Index des zur Sprachvariante gehörenden Datenfeldes unter
350 # Verwendung der Ersatzfelder:
352 # >>> aalbestand.lang_index('de')
354 # >>> aalbestand.lang_index('de-1901')
356 # >>> aalbestand.lang_index('de-1996')
358 # >>> aalbestand.lang_index('de-x-versal')
360 # >>> aalbestand.lang_index('de-1901-x-versal')
362 # >>> aalbestand.lang_index('de-1996-x-versal')
364 # >>> abbeissen = WordEntry(
365 # ... u'abbeissen;-2-;-3-;-4-;-5-;ab<bei-ssen;ab<beis-sen;ab<beis-sen')
366 # >>> print abbeissen.lang_index('de')
367 # None
368 # >>> print abbeissen.lang_index('de-x-versal')
369 # None
370 # >>> abbeissen.lang_index('de-1901-x-versal')
372 # >>> abbeissen.lang_index('de-1996-x-versal')
374 # >>> abbeissen.lang_index('de-CH-1901')
376 # >>> urlaubstipp = WordEntry(u'Urlaubstipp;-2-;-3-;Ur<laubs=tipp')
377 # >>> print urlaubstipp.lang_index('de')
378 # None
379 # >>> print urlaubstipp.lang_index('de-1901')
380 # None
381 # >>> print urlaubstipp.lang_index('de-1996')
383 # >>> print urlaubstipp.lang_index('de-x-versal')
384 # None
385 # >>> print urlaubstipp.lang_index('de-1901-x-versal')
386 # None
388 # ::
390 def lang_index(self, lang):
392 if lang not in self.feldindizes:
393 raise ValueError('Sprachvariante "%s" nicht in %s'
394 % (lang, self.feldindizes.keys()))
396 # Einfacher Fall: eine allgemeine Schreibweise::
398 if len(self) == 2:
399 if u'ß' in self[0] and ('CH' in lang or 'versal' in lang):
400 return None
401 else:
402 return 1
404 # Spezielle Schreibung::
406 try:
407 i = self.feldindizes[lang]
408 feld = self[i]
409 except IndexError:
410 if lang == 'de-CH-1901':
411 return self.lang_index('de-1901-x-versal')
412 # Allgemeine Schweiz/versal Schreibung:
413 if i > 4 and len(self) == 5:
414 return 4
415 # versal == normal (kein sz):
416 if len(self) == 4 and u'ß' not in self[0]:
417 if lang in ('de-CH-1901', 'de-1901-x-versal'):
418 return self.lang_index('de-1901')
419 elif lang in ('de-CH-1996', 'de-1996-x-versal'):
420 return self.lang_index('de-1996')
421 return None # Feld nicht vorhanden
423 if not feld: # leeres Feld
424 return None
426 return i
428 # key()
429 # -----
431 # Gib einen Schlüssel (ungetrenntes Wort) zurück.
433 # Bei Einträgen im Langform_ ist der Schlüssel gleich dem Inhalt des ersten
434 # Feldes. Die Funktion dient zur Kompatibilität mit ShortEntry_.
436 # >>> print WordEntry(u'Fluss;Quatsch').key()
437 # Fluss
438 # >>> print WordEntry(u'Dreß;-1-;Dreß;-3-').key()
439 # Dreß
441 # Das `lang` Argument wird ignoriert, da der Schlüssel (im Gegensatz zur
442 # Kurzform_) stets eindeutig ist:
444 # >>> print WordEntry(u'Dreß;-1-;Dreß;-3-').key('de-1996')
445 # Dreß
447 # Der Schlüssel eines leeren Eintrags ist ein leerer String, der eines leeren
448 # Kommentars das Kommentarzeichen:
450 # >>> WordEntry(u'').key(), WordEntry(u'# toller Kommentar').key()
451 # (u'', u'#')
453 # ::
455 def key(self, lang=None):
456 """Gib den Schlüssel (ungetrenntes Wort) zurück."""
457 try:
458 return self[0]
459 except IndexError: # reiner Kommentar oder leerer Eintrag
460 if self.comment:
461 return u'#'
462 return u''
465 # getitem()
466 # ---------
468 # Gib Feld ``i`` oder Ersatzfeld zurück.
469 # ::
471 def getitem(self, i):
472 """Return item ``i`` or a subsititute"""
473 try:
474 return self[i]
475 except IndexError: # Feld i nicht vorhanden
476 pass
477 # Ersatzregeln anwenden
478 for _i in self.ersatzfelder[i]:
479 try:
480 word = self[_i]
481 except IndexError: # Feld i nicht vorhanden
482 continue # nächsten `tag` versuchen
483 # Spezialfall: in Versalschreibung ungültige Ersatz-Wörter
484 if i >= 4 and _i <4 and u'ß' in word:
485 return u''
486 return word
487 return u''
489 # .. _WordEntry.get():
491 # get()
492 # -----
494 # Trennmuster für eine Sprachvariante ausgeben:
496 # >>> aalbestand.get('de')
497 # u'Aal=be<stand'
498 # >>> aalbestand.get('de-1901')
499 # u'Aal=be<stand'
500 # >>> aalbestand.get('de-1996')
501 # u'Aal=be<stand'
502 # >>> aalbestand.get('de-x-versal')
503 # u'Aal=be<stand'
504 # >>> aalbestand.get('de-1901-x-versal')
505 # u'Aal=be<stand'
506 # >>> aalbestand.get('de-1996-x-versal')
507 # u'Aal=be<stand'
508 # >>> aalbestand.get('de-CH-1901')
509 # u'Aal=be<stand'
511 # >>> weste = WordEntry(u'Weste;-2-;We-ste;Wes-te')
512 # >>> weste.get('de')
513 # u''
514 # >>> weste.get('de-1901')
515 # u'We-ste'
516 # >>> weste.get('de-1996')
517 # u'Wes-te'
519 # >>> abbeissen.get('de')
520 # u''
521 # >>> abbeissen.get('de-x-versal')
522 # u''
523 # >>> abbeissen.get('de,de-x-versal')
524 # u''
525 # >>> abbeissen.get('de-1901-x-versal')
526 # u'ab<bei-ssen'
527 # >>> abbeissen.get('de,de-1901,de-1901-x-versal')
528 # u'ab<bei-ssen'
529 # >>> abbeissen.get('de-CH-1901')
530 # u'ab<beis-sen'
532 # ::
534 def get(self, tags):
535 for lang in tags.split(','):
536 word = self.getitem(self.feldindizes[lang])
537 if word:
538 break
539 else:
540 return u''
541 return word
544 # set()
545 # -----
547 # Trennmuster für Sprachvariante setzen:
549 # >>> abbeissen.set('test', 'de-1901-x-versal')
550 # >>> print abbeissen
551 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
553 # >>> abbeissen.set('test', 'de-1901')
554 # Traceback (most recent call last):
555 # ...
556 # IndexError: kann kein leeres Feld setzen
558 # >>> abbeissen.set('test', 'de-1901,de-1901-x-versal')
559 # >>> print abbeissen
560 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
562 # ::
564 def set(self, word, tags):
565 for lang in tags.split(','):
566 i = self.lang_index(lang)
567 if i is None:
568 continue
569 if word is None:
570 word = u''
571 self[i] = word
572 return
573 raise IndexError, "kann kein leeres Feld setzen"
575 # setitem()
576 # ---------
578 # Feld ``i`` setzen:
580 # >>> entry = WordEntry(u'test;test')
581 # >>> entry.setitem(4, u's-x')
582 # >>> print entry
583 # test;test;-3-;-4-;s-x
584 # >>> entry.setitem(3, u'sz')
585 # >>> print entry
586 # test;test;-3-;sz;s-x
588 # ::
590 def setitem(self, i, word):
591 while len(self) < i+1:
592 self.append(u'')
593 self[i] = word
596 # complete()
597 # ----------
599 # Alle Felder setzen:
601 # >>> print WordEntry(u'Ruhe;Ru-he').completed()
602 # Ruhe;Ru-he;Ru-he;Ru-he;Ru-he;Ru-he;Ru-he;Ru-he
604 # >>> print str(aalbestand), len(aalbestand)
605 # Aalbestand;Aal=be<stand # Test 2
606 # >>> print aalbestand.completed()
607 # Aalbestand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand # Test
609 # >>> auffrass = WordEntry(u'auffrass;-2-;-3-;-4-;auf-frass')
610 # >>> print auffrass.completed()
611 # auffrass;-2-;-3-;-4-;auf-frass;auf-frass;auf-frass;auf-frass
613 # >>> fresssack= WordEntry(u'Fresssack;-2-;-3-;Fress=sack;Fress=sack')
614 # >>> print fresssack.completed()
615 # Fresssack;-2-;-3-;Fress=sack;Fress=sack;Fress=sack;Fress=sack;Fress=sack
617 # >>> line = u'Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-'
618 # >>> print WordEntry(line).completed()
619 # Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-;Fluss=schi{ff/ff=f}ahrt
621 # >>> line = u'Ackerstraße;-2-;A{ck/k-k}er=stra-ße;Acker=stra-ße'
622 # >>> ackerstrasse = WordEntry(line).completed()
623 # >>> print unicode(ackerstrasse)
624 # Ackerstraße;-2-;A{ck/k-k}er=stra-ße;Acker=stra-ße;-5-;-6-;-7-;-8-
626 # ::
628 def complete(self):
629 for i in range(len(self),8):
630 self.append(self.getitem(i))
632 # .. _WordEntry.completed():
634 # completed()
635 # -----------
637 # Gib eine vervollständigte Kopie des Eintrags zurück.
638 # ::
640 def completed(self):
641 """Return expanded copy of self."""
642 neu = copy.copy(self)
643 neu.complete()
644 return neu
646 # .. _ShortEntry.prune():
648 # prune()
649 # -------
651 # Eintrag kürzen.
653 # Felder für Sprachvarianten zusammenfassen, wenn gleich:
655 # >>> aalbestand.prune()
656 # >>> print aalbestand
657 # Aalbestand;Aal=be<stand # Test
658 # >>> auffrass.prune()
659 # >>> print auffrass
660 # auffrass;-2-;-3-;-4-;auf-frass
661 # >>> entry = WordEntry(u'distanziert;-2-;di-stan-ziert;di-stan-ziert')
662 # >>> entry.prune()
663 # >>> print entry
664 # distanziert;di-stan-ziert
665 # >>> entry = WordEntry(u'Gauss;-2-;Gauss;Gauss;Gauss')
666 # >>> entry.prune()
667 # >>> print entry
668 # Gauss;Gauss
669 # >>> fresssack.prune()
670 # >>> print fresssack
671 # Fresssack;-2-;-3-;Fress=sack;Fress=sack
673 # >>> masse = WordEntry(u'Masse;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se')
674 # >>> masse.prune()
675 # >>> print masse
676 # Masse;Mas-se
678 # Aber nicht, wenn die Trennstellen sich unterscheiden:
680 # >>> abenddienste = WordEntry(
681 # ... u'Abenddienste;-2-;Abend=dien-ste;Abend=diens-te')
682 # >>> abenddienste.prune()
683 # >>> print abenddienste
684 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
685 # >>> ackerstrasse.prune()
686 # >>> print unicode(ackerstrasse)
687 # Ackerstraße;-2-;A{ck/k-k}er=stra-ße;Acker=stra-ße
688 # >>> entry = WordEntry(
689 # ... u'Schlammmasse;-2-;-3-;Schlamm=mas-se;-5-;-6-;Schlamm=mas-se;-8-')
690 # >>> entry.prune()
691 # >>> print entry
692 # Schlammmasse;-2-;-3-;Schlamm=mas-se
693 # >>> entry = WordEntry(
694 # ... u'Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-')
695 # >>> entry.prune()
696 # >>> print entry
697 # Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-
699 # ::
701 def prune(self):
702 if len(self) == 8:
703 if self[7] == self[5]: # de-CH-1901 gleich versal-1901
704 self.pop()
705 if len(self) == 7:
706 if self[6] == self[5]:
707 self[4] = self[5] # umschreiben auf versal-allgemein
708 self.pop()
709 self.pop()
710 elif (not(self[4] or self[5] or self[6])
711 or self[5] == self[2] and self[6] == self[3]):
712 # alle leer oder ohne ß-Ersetzung
713 self.pop()
714 self.pop()
715 self.pop()
716 if len(self) == 5:
717 if not self[4]:
718 self.pop()
719 elif self[1] == self[4]: # de-x-versal == de
720 self.pop()
721 self.pop()
722 self.pop()
723 elif self[2] == self[3] == self[4]:
724 self[1] = self[2] # Umschreiben auf de (allgemein)
725 self.pop()
726 self.pop()
727 self.pop()
728 if len(self) == 4:
729 if self[3] == self[2]: # de-1996 == de-1901
730 self[1] = self[2] # Umschreiben auf de (allgemein)
731 self.pop()
732 self.pop()
734 if len(self) > 6:
735 self[4] = u''
736 if len(self) > 2:
737 self[1] = u''
740 # pruned()
741 # -----------
743 # Gib eine gekürzte Kopie des Eintrags zurück.
744 # ::
746 def pruned(self):
747 """Return pruned copy of self."""
748 neu = copy.copy(self)
749 neu.prune()
750 return neu
752 # .. _WordEntry.merge():
754 # merge()
755 # -------
757 # Einträge zusammenfassen:
759 # >>> entry = WordEntry(u'Dienste;-2-;Dien-ste')
760 # >>> entry.merge(WordEntry(u'Dienste;-2-;-3-;diens-te'))
761 # >>> print unicode(entry)
762 # Dienste;-2-;Dien-ste;Diens-te
763 # >>> entry = WordEntry(u'Abenddress;Abend=dress')
764 # >>> entry.merge(WordEntry(u'Abenddress;-2-;-3-;-4-;Abend=dress'))
765 # >>> print unicode(entry)
766 # Abenddress;Abend=dress
767 # >>> entry = WordEntry(u'Gauss;Gauss')
768 # >>> entry.merge(WordEntry(u'Gauss;-2-;-3-;-4-;Gauss'))
769 # >>> print unicode(entry)
770 # Gauss;Gauss
771 # >>> masse.merge(WordEntry(u'masse;-2-;-3-;-4-;-5-;Ma-sse;Mas-se;Mas-se'),
772 # ... allow_alternatives=True)
773 # >>> print masse
774 # Masse;-2-;Mas-se;Mas-se;-5-;Ma[s-/-s]se;Mas-se;Mas-se
776 # ::
778 def merge(self, other, allow_alternatives=False, prune=True,
779 merge_comments=False, start=0, stop=None):
780 self.complete()
781 other.complete()
782 islower = self.key().islower()
783 conflict = False
784 # `stop=None` oder `stop=0` bedeutet "keine Obergrenze".
785 stop = stop or len(self)
786 for i in range(start, stop):
787 o_i = other[i]
788 if not o_i:
789 continue
790 if islower != o_i.islower():
791 o_i = toggle_case(o_i)
792 s_i = self[i]
793 if not s_i:
794 self[i] = o_i
795 # other[i] = u''
796 # elif s_i == o_i:
797 # other[i] = u''
798 elif s_i != o_i:
799 if s_i.lower() == o_i.lower():
800 self[i] = toggle_case(o_i)
801 elif allow_alternatives:
802 self[i] = alternatives(s_i, o_i) #u'[%s/%s]' % (s_i, o_i)
803 # o_i = u''
804 else:
805 conflict = True
806 if merge_comments and not conflict and self.comment != other.comment:
807 if allow_alternatives and self.comment and other.comment:
808 self.comment += u' / ' + other.comment
809 else:
810 self.comment = self.comment or other.comment
812 if conflict:
813 raise AssertionError(u'Merge Error:\n %s\n %s'
814 % (unicode(self), unicode(other)))
816 if prune:
817 other.prune()
818 self.prune()
821 # regelaenderungen()
822 # ------------------
824 # Teste Felder auf Konsistenz mit den Regeländerungen der Orthographiereform
825 # 1996, ändere Unstimmigkeiten `in place`.
827 # Die neuere Funktion `ableitung1901()`_, deckt mehr Ausnahmen ab aber
828 # funktioniert nur in eine Richtung.
830 # Falls ein generisches Feld von Änderung betroffen ist, schreibe die
831 # korrekten Trennungen in die spezifischen Felder.
833 # >>> entry = WordEntry(u'Würste;Wür-ste')
834 # >>> entry.regelaenderungen()
835 # >>> print unicode(entry)
836 # Würste;-2-;Wür-ste;Würs-te
837 # >>> entry = WordEntry(u'Würste;Würs-te')
838 # >>> entry.regelaenderungen()
839 # >>> print unicode(entry)
840 # Würste;-2-;Wür-ste;Würs-te
841 # >>> entry = WordEntry(u'Hecke;He-cke')
842 # >>> entry.regelaenderungen()
843 # >>> print unicode(entry)
844 # Hecke;-2-;He{ck/k-k}e;He-cke
845 # >>> entry = WordEntry(u'Ligusterhecke;Ligu-ster=he{ck/k-k}e')
846 # >>> entry.regelaenderungen()
847 # >>> print unicode(entry)
848 # Ligusterhecke;-2-;Ligu-ster=he{ck/k-k}e;Ligus-ter=he-cke
850 # Bei Änderungen der Schreibung wird das entsprechende Feld ausgekreuzt:
852 # >>> entry = WordEntry(u'Hass;Hass')
853 # >>> entry.regelaenderungen()
854 # >>> print unicode(entry)
855 # Hass;-2-;-3-;Hass;Hass
856 # >>> entry = WordEntry(u'fasst;fasst')
857 # >>> entry.regelaenderungen()
858 # >>> print unicode(entry)
859 # fasst;-2-;-3-;fasst;fasst
860 # >>> entry = WordEntry(u'Missbrauch;Miss<brauch')
861 # >>> entry.regelaenderungen()
862 # >>> print unicode(entry)
863 # Missbrauch;-2-;-3-;Miss<brauch;Miss<brauch
864 # >>> entry = WordEntry(u'schlifffest;schliff=fest')
865 # >>> entry.regelaenderungen()
866 # >>> print unicode(entry)
867 # schlifffest;-2-;-3-;schliff=fest
869 # Bei gleicher, nicht betroffener Trennung werden Felder zusammengefasst:
871 # >>> entry = WordEntry(u'austoben;-2-;aus<to-ben;aus<to-ben')
872 # >>> entry.regelaenderungen()
873 # >>> print unicode(entry)
874 # austoben;aus<to-ben
876 # Achtung: nicht narrensicher -- Ausnahmen und Mehrdeutigkeiten werden nicht
877 # alle erkannt:
879 # >>> entry = WordEntry(u'Boss;Boss # engl.') # korrekt
880 # >>> entry.regelaenderungen()
881 # >>> print unicode(entry)
882 # Boss;Boss # engl.
884 # >>> entry = WordEntry(u'Ästhesie;Äs-the-sie') # Trennung von "sth" erlaubt
885 # >>> entry.regelaenderungen()
886 # >>> print unicode(entry)
887 # Ästhesie;Äs-the-sie
889 # >>> entry = WordEntry(u'Fluß;Fluß') # in de-1996 nur Fluss
890 # >>> entry.regelaenderungen()
891 # >>> print unicode(entry)
892 # Fluß;Fluß
894 # ::
896 def regelaenderungen(self):
897 wort01 = self.get('de-1901')
898 wort96 = self.get('de-1996')
899 w_versal = None
901 if wort01 is None or wort96 is None or 'engl.' in self.comment:
902 return
904 # Trennregeländerungen:
906 # Trennung von ck:
907 wort01 = wort01.replace(u'-ck', u'{ck/k-k}')
908 wort96 = wort96.replace(u'{ck/k-k}', u'-ck')
910 # Trenne nie st:
911 wort01 = re.sub(u'(?<!s)s-t(?!h)', u'-st', wort01)
912 wort96 = wort96.replace(u'-st', u's-t')
914 # kein Schluss-ss und sst in de-1901 (ungetrenntes "ss" nur in Ausnahmen)
915 # aber: 'ßt' und Schluß-ß auch in de-1996 möglich (langer Vokal)
916 if u'ss' in wort01:
917 w_versal = wort01
918 wort01 = None
920 # Dreikonsonantenregel:
921 if wort01 and re.search(ur'(.)\1=\1', wort01):
922 wort01 = None
924 # Speichern:
925 if wort01 == wort96: # keine Regeländerung im Wort
926 if len(self) > 2:
927 self.prune()
928 return
930 if wort01 is None:
931 self.extend( ['']*(4-len(self)) )
932 self[1] = u''
933 self[2] = u''
934 self[3] = wort96
935 else:
936 self.extend( ['']*(4-len(self)) )
937 self[1] = u''
938 self[2] = wort01
939 self[3] = wort96
940 if w_versal:
941 self.append(w_versal)
944 # ShortEntry
945 # ==========
947 # Klasse für Einträge (Zeilen) der Wortlisten im `5-Felder-Format`_ (Kurzform).
949 # ::
951 class ShortEntry(WordEntry):
952 """Entry of the German hyphenation database file (5 fields)."""
954 # .. _Kurzform:
956 # 5-Felder-Format
957 # ---------------
959 # Ein vollständiger Eintrag enthält fünf, durch Semikolon getrennte, Felder
960 # für die Sprachvarianten `de`, `de-1901`, `de-CH`, `de-1901-x-versal` und
961 # `de-CH-1901` (Tags nach [BCP47]_).
963 # Jedes Feld enthält ein Wort mit Kennzeichnung der Trennstellen im Format
964 # der `Wortliste der deutschsprachigen Trennmustermannschaft`_, z.B.
965 # ``Pro<zent=zah-len``.
967 # Felder können weggelassen werden, wenn sich der Inhalt aus einem
968 # allgemeineren Feld gewinnen lässt (siehe `Ersatzregeln`_).
971 # Feldbelegung
972 # ~~~~~~~~~~~~
974 # Die Felder beschreiben die Schreibung und Trennung eines Wortes gemäß
975 # der Sprachvarianten:
977 # 1. `de`: `aktuelle Rechtschreibung`_ wie sie in Deutschland und Österreich
978 # angewendet wird.
980 # Beispiele: ``Diens-te``, ``ba-cken``, ``Grü-ße``, ``Schluss=satz``
982 # 2. `de-1901`: `traditionelle Rechtschreibung`_ wie sie in Deutschland und
983 # Österreich von 1901 bis 1996 gültig war.
985 # Beispiele: ``Dien-ste``, ``ba{ck/k-k}en``, ``Grü-ße``, ``Schluß=satz``
987 # 3. `de-CH` oder `de-x-versal`: aktuelle Rechtschreibung wie sie in der
988 # Schweiz und bei ß-Ersatzschreibung angewendet wird.
990 # Beispiele: ``ba-cken``, ``Grüs-se``, ``Schluss=satz``
992 # 4. `de-1901-x-versal`: traditionelle Rechtschreibung mit
993 # ß-Ersatzschreibung wie sie in Deutschland und Österreich
994 # angewendet wurde (keine Trennung von „ss“ als Ersatz für „ß“).
996 # Beispiele: ``ba{ck/k-k}en``, ``Grü-sse``, ``Schluss=satz``
998 # 5. `de-CH-1901`: traditionelle Rechtschreibung wie sie in der Schweiz
999 # angewendet wurde (Trennung von „ss“ auch wenn es für „ß“ steht aber
1000 # keine Dreikonsonantenregel für „ss=s“).
1002 # Beispiele: ``ba{ck/k-k}en``, ``Grüs-se``, ``Schluss=satz``
1005 # Ersatzregeln
1006 # ~~~~~~~~~~~~
1008 # Feld 1 (`de`) ist ein Pflichtfeld, die anderen Felder können weggelassen
1009 # werden, wenn sich der Inhalt über regelmäßige Transformationen_ aus einem
1010 # anderen Feld gewinnen lässt:
1012 # ==================== ============= =======================
1013 # Feld Quelle Transformation
1014 # ==================== ============= =======================
1015 # 1 de
1016 # 2 de-1901 1 de „Rechtschreibreversion_“
1017 # 3 de-CH 1 de SZ-Ersatz_
1018 # 4 de-1901-x-versal 2 de-1901 SZ-Ersatz_
1019 # 5 de-CH-1901 2 de-1901 SZ-Ersatz_
1020 # ==================== ============= =======================
1022 # Die Ersetzung erfolgt rekursiv (d.h. wenn Feld 2 nicht gegeben ist, können
1023 # die Felder 4 und 5 aus Feld 1 mit „Rechtschreibreversion_“ und SZ-Ersatz_
1024 # bestimmt werden).
1026 # Wenn ein Eintrag in einer Sprachvariante leer bleiben soll, muss das
1027 # zugehörige Feld entsprechend markiert („ausgekreuzt“) werden.
1028 # (Da die ß-Ersatzschreibung für alle Wörter definiert ist, ist in einer
1029 # vollständigen Liste das Auskreuzen der Spalten 3…5 nicht erforderlich.)
1031 # Beispiele: ``-1-;de<pla-ziert``, ``auf<wän-dig;-2-;``.
1034 # Implementiert in `ShortEntry.getitem()`_.
1037 # Rechtschreibreversion
1038 # """""""""""""""""""""
1040 # Überführung eines Wortes in die Sprachvariante de-1901 durch
1041 # Anwendung der mit der Rechtschreibreform 1996 entfallenen Regeln:
1043 # st-Trennung_:
1044 # Trenne nie "st". (Aber "s-th" ist trennbar.)
1045 # ck-Trennung_:
1046 # Trenne "ck" als "k-k".
1047 # Schluss-ss_:
1048 # "ß", "ßt" und "ßl" statt "ss", "sst" und "ssl" am Silbenende.
1049 # Dreikonsonantenregel_:
1050 # Von drei gleichen Konsonanten vor einem Selbstlaut entfällt einer,
1051 # taucht aber bei Worttrennung wieder auf.
1053 # Abweichende Änderungen müssen als Ausnahmen explizit erfasst werden.
1055 # Implementiert in `ableitung1901()`_.
1057 # SZ-Ersatz
1058 # """""""""
1059 # Ersetze 'ß' mit 'ss', trenne je nach Sprachvarietät (siehe Feldbelegung_).
1061 # Implementiert in `versalschreibung()`_.
1065 # Beispiel
1066 # --------
1068 # Durch die Ersatzregeln reicht für die meisten Einträge die Angabe des ersten
1069 # Feldes, z.B.
1071 # >>> from wortliste import ShortEntry
1072 # >>> entry = ShortEntry(u'Pass=stra-ße')
1074 # Eintrag vervollständigen mit `ShortEntry.complete()`_:
1076 # >>> entry.complete(); print unicode(entry)
1077 # Pass=stra-ße;Paß=stra-ße;Pass=stras-se;Pass=stra-sse;Pass=stras-se
1079 # Eintrag kürzen mit `ShortEntry.prune()`_:
1081 # >>> entry.prune(); print unicode(entry)
1082 # Pass=stra-ße
1084 # Wort mit Unregelmäßigkeiten:
1086 # >>> entry = ShortEntry(u'Boss;Boss;Boss;Boss;Boss # en.')
1087 # >>> entry.prune(); print unicode(entry)
1088 # Boss;Boss # en.
1091 # Argumente
1092 # ---------
1094 # feldnamen
1095 # ~~~~~~~~~
1097 # Tupel der Sprachbezeichner für die Felder im 5-Felder-Format_::
1099 feldnamen = ("de", "de-1901", "de-CH", "de-1901-x-versal", "de-CH-1901")
1101 # feldindizes
1102 # ~~~~~~~~~~~
1104 # Zuordnung von Sprachbezeichnern nach [BCP47]_ zu den Feldern.
1106 # In Python beginnt die Indexzählung mit Null::
1108 feldindizes = {
1109 "de": 0, # Deutsch, aktuell
1110 "de-DE": 0, # Alias
1111 "de-AT": 0, # Alias
1112 "de-1996": 0, # Alias
1113 "de-1996": 0, # Alias
1114 "de-DE-1996": 0, # Alias
1115 "de-AT-1996": 0, # Alias
1116 "de-1901": 1, # traditionell (Reform 1901)
1117 "de-CH": 2, # ohne ß (Schweiz/versal) aktuell
1118 "de-x-versal": 2, # Alias
1119 "de-1996-x-versal": 2, # Alias
1120 "de-1901-x-versal": 3, # ohne ß (versal) traditionell
1121 "de-CH-1901": 4, # ohne ß (Schweiz) traditionell
1124 # Achtung: die Sprachtags `de` und `de-CH` sind in der Kurzform_ ein Alias für
1125 # die aktuelle Rechtschreibung aber in der Langform_ Bezeichner für separate
1126 # Felder mit allgemeingültigen Trennungen:
1128 # >>> print ShortEntry.feldindizes['de-1901'], ShortEntry.feldindizes['de-CH']
1129 # 1 2
1130 # >>> print ShortEntry.feldindizes['de'], ShortEntry.feldindizes['de-1996']
1131 # 0 0
1133 # >>> print WordEntry.feldindizes['de'], WordEntry.feldindizes['de-1996']
1134 # 1 3
1136 # Schlüssel-Cache
1137 # ~~~~~~~~~~~~~~~
1138 # ::
1140 _key = None
1143 # Initialisierung
1144 # ---------------
1146 # Das Argument `line` ist eine Zeichenkette (`string`, `unicode`) im
1147 # `5-Felder-Format`_ oder eine Instanz der Klasse WordEntry_.
1149 # >>> print ShortEntry(u'Diens-te')
1150 # Diens-te
1151 # >>> tpp = ShortEntry(u'Ur<laubs=tipp;-2-')
1152 # >>> print tpp
1153 # Ur<laubs=tipp;-2-
1155 # >>> print abenddienste
1156 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
1157 # >>> print ShortEntry(abenddienste)
1158 # Abend=diens-te
1160 # Achtung: Ein Lang-Eintrag mit nur zwei Feldern kann unregelmäßig sein und
1161 # daher im Kurzformat mehrere Felder benötigen (siehe Tests)
1162 # ::
1164 def __init__(self, line, delimiter=';', prune=True):
1166 if isinstance(line, WordEntry):
1167 self.comment = line.comment # Kommentar
1168 self._key = line.getitem(0) # Schlüssel cachen
1169 if not line:
1170 return
1171 self.append(line.getitem(3)) # Deutsch, aktuell (Reform 1996)
1172 self.append(line.getitem(2)) # "traditionell" (Reform 1901)
1173 if len(line) > 4:
1174 self.append(line.getitem(6)) # ohne ß (Schweiz oder versal) "aktuell"
1175 self.append(line.getitem(5)) # ohne ß (Schweiz oder versal) "traditionell"
1176 self.append(line.getitem(7)) # ohne ß (Schweiz) "traditionell" ("süssauer")
1177 elif u'ß' in self._key: # auskreuzen
1178 self.append(u'')
1179 self.append(u'')
1180 self.append(u'')
1181 else:
1182 WordEntry.__init__(self, line, delimiter)
1184 if prune: # Felder zusammenfassen
1185 self.prune()
1187 # Tests
1188 # ~~~~~
1189 # >>> drs = unicode(ShortEntry(u'-1-;Dreß'))
1190 # >>> print drs
1191 # -1-;Dreß
1192 # >>> print ShortEntry(u'# Testkommentar')
1193 # # Testkommentar
1195 # In der Voreinstellung werden optionale Felder mit `prune()` gekürzt.
1196 # Mit der Option `prune=False` bleiben alle übergebenen Felder erhalten:
1198 # >>> print unicode(ShortEntry(u'Fluss;Fluß'))
1199 # Fluss
1200 # >>> print unicode(ShortEntry(u'Fluss;Fluß', prune=False))
1201 # Fluss;Fluß
1203 # Wird dem Konstruktor eine WordEntry_-Instanz übergeben, so wird diese in das
1204 # Kurzformat überführt:
1206 # >>> print ShortEntry(WordEntry(u'heute;heu-te'))
1207 # heu-te
1209 # Bei Ausnahmen von der regelmäßigen Worttrennung werden bei Bedarf
1210 # zusätzliche Spalten belegt:
1212 # >>> print ShortEntry(WordEntry(u'Amnesty;Am-nes-ty # en.'))
1213 # Am-nes-ty;Am-nes-ty # en.
1214 # >>> bss = ShortEntry(WordEntry(u'Boss;Boss # engl.'))
1215 # >>> print bss
1216 # Boss;Boss # engl.
1218 # Bei Wörtern mit unterschiedlicher Schreibung in den verschiedenen
1219 # Sprachvarianten werden Felder, die im übergebenen `WordEntry` nicht belegt
1220 # sind als leer markiert:
1222 # >>> print urlaubstipp
1223 # Urlaubstipp;-2-;-3-;Ur<laubs=tipp
1224 # >>> print ShortEntry(urlaubstipp)
1225 # Ur<laubs=tipp;-2-
1226 # >>> print unicode(ShortEntry(WordEntry(u'Abfalllager;-2-;-3-;Ab<fall=la-ger')))
1227 # Ab<fall=la-ger;-2-
1229 # >>> dresz = WordEntry(u'Dreß;-2-;Dreß;-4-')
1230 # >>> print unicode(ShortEntry(dresz))
1231 # -1-;Dreß;-3-;-4-;-5-
1232 # >>> g_ebene = WordEntry(u'Gaußebene;Gauß=ebe-ne')
1233 # >>> print unicode(ShortEntry(g_ebene))
1234 # Gauß=ebe-ne;Gauß=ebe-ne;-3-;-4-;-5-
1235 # >>> print auffrass
1236 # auffrass;-2-;-3-;-4-;auf-frass
1237 # >>> print ShortEntry(auffrass)
1238 # -1-;-2-;auf-frass;auf-frass;auf-frass
1239 # >>> fraesse = WordEntry(u'frässe;-2-;-3-;-4-;-5-;frä-sse;fräs-se;fräs-se')
1240 # >>> frs = ShortEntry(fraesse)
1241 # >>> print unicode(frs)
1242 # -1-;-2-;fräs-se;frä-sse;fräs-se
1243 # >>> loesz = WordEntry(u'Lößboden;Löß=bo-den')
1244 # >>> print unicode(ShortEntry(loesz))
1245 # Löß=bo-den;Löß=bo-den;-3-;-4-;-5-
1246 # >>> loess = WordEntry(u'Lössboden;-2-;-3-;Löss=bo-den;Löss=bo-den')
1247 # >>> print unicode(ShortEntry(loess))
1248 # Löss=bo-den;-2-;Löss=bo-den;Löss=bo-den;Löss=bo-den
1250 # >>> print ShortEntry(WordEntry(u'Fussballliga;-2-;-3-;-4-;-5-;-6-;Fuss=ball==li-ga'))
1251 # -1-;-2-;Fuss=ball==li-ga
1252 # >>> print ShortEntry(WordEntry(u'Fussballiga;-2-;-3-;-4-;-5-;Fuss=ba{ll/ll=l}i-.ga;-7-'))
1253 # -1-;-2-;-3-;Fuss=ba{ll/ll=l}i-.ga;Fuss=ba{ll/ll=l}i-.ga
1254 # >>> messignal = WordEntry(u'Messignal;-2-;-3-;-4-;-5-;-6-;-7-;Me{ss/ss=s}i-.gnal')
1255 # >>> print unicode(ShortEntry(messignal))
1256 # -1-;-2-;-3-;-4-;Me{ss/ss=s}i-.gnal
1258 # >>> ShortEntry(WordEntry(u'# nur Kommentar'))
1259 # ShortEntry(u'# nur Kommentar')
1262 # key()
1263 # -----
1265 # Gib einen Schlüssel (ungetrenntes Wort) zurück.
1267 # Bei Einträgen im Kurzformat ist der Schlüssel nicht eindeutig: durch
1268 # Transformationen_ kann ein Wort in verschiedenen Schreibungen vorkommen.
1270 # Standardmäßig wird das erste nichtleere Feld ohne Trennzeichen verwendet:
1272 # >>> print ShortEntry(u'Fluss;Fluß').key()
1273 # Fluss
1274 # >>> print ShortEntry(u'-1-;Dreß').key()
1275 # Dreß
1277 # Die Auswahl kann über das `lang` Argument gesteuert werden:
1279 # >>> print ShortEntry(u'-1-;Dreß').key('de-CH-1901')
1280 # Dress
1282 # Der Schlüssel eines leeren Eintrags ist ein leerer String, der eines leeren
1283 # Kommenars das Kommentarzeichen:
1285 # >>> ShortEntry(u'').key(), ShortEntry(u'# toller Kommentar').key()
1286 # (u'', u'#')
1288 # ::
1290 def key(self, lang=None):
1291 """Erstelle einen Schlüssel (ungetrenntes Wort)."""
1292 if lang:
1293 return join_word(self.get(lang))
1294 if self._key:
1295 return self._key
1296 for f in self:
1297 if f:
1298 self._key = f
1299 return join_word(f)
1300 if self.comment:
1301 return u'#' # reiner Kommentar
1302 return u'' # leerer Eintrag
1305 # .. _`ShortEntry.getitem()`:
1307 # getitem()
1308 # ---------
1310 # Gib Feld `i` zurück. Wende bei Bedarf die Ersatzregeln_ an.
1312 # >>> entry = ShortEntry(u'Bu-ße')
1313 # >>> print entry.getitem(1), entry.getitem(2), entry.getitem(4)
1314 # Bu-ße Bus-se Bus-se
1316 # >>> entry.getitem(5)
1317 # Traceback (most recent call last):
1318 # ...
1319 # IndexError: list index out of range
1321 # Mit `subsititute=True` wird der Feldinhalt ignoriert und immer nach
1322 # Ersatzregel bestimmt:
1324 # >>> entry = ShortEntry(u'Bu-ße;Reue;Bu-sse')
1325 # >>> print entry.getitem(1), entry.getitem(1, substitute=True)
1326 # Reue Bu-ße
1328 # ::
1331 def getitem(self, i, substitute=False):
1332 """Return item ``i`` or a subsititute"""
1333 if not substitute:
1334 try:
1335 return self[i]
1336 except IndexError: # Feld i nicht vorhanden
1337 if not self:
1338 raise ValueError('leerer Eintrag')
1339 if i > 4:
1340 raise # maximal 5 Felder im Kurzformat
1342 # Rekursion: wähle das generischere Feld
1343 ersatzfelder = (None, 0, 0, 1, 1)
1344 word = self.getitem(ersatzfelder[i])
1346 # Bei leerem Feld ("ausgekreuzt") ist keine Transformation nötig
1347 if not word:
1348 return u''
1350 # Rechschreibreform (s-t, ck, Dreikonsonantenregel)
1351 if i == 1: # de-1901
1352 return ableitung1901(word)
1354 # Versalschreibung: ß -> ss
1355 if u'ß' in word:
1356 word = versalschreibung(word, self.feldnamen[i])
1357 return word
1360 # get()
1361 # -----
1363 # Gib Trennmuster für Sprachvariante zurück (ggf. über Transformationen_).
1365 # Beispiele:
1367 # ohne Transformation
1369 # >>> aalbst = ShortEntry(u'Aal=be<stand # Test')
1370 # >>> aalbst.get('de')
1371 # u'Aal=be<stand'
1372 # >>> aalbst.get('de-1996')
1373 # u'Aal=be<stand'
1374 # >>> aalbst.get('de-1901')
1375 # u'Aal=be<stand'
1376 # >>> aalbst.get('de-x-versal')
1377 # u'Aal=be<stand'
1378 # >>> aalbst.get('de-1901-x-versal')
1379 # u'Aal=be<stand'
1380 # >>> aalbst.get('de-1996-x-versal')
1381 # u'Aal=be<stand'
1382 # >>> aalbst.get('de-CH-1901')
1383 # u'Aal=be<stand'
1385 # st und ck:
1387 # >>> dst = ShortEntry(u'Diens-te')
1388 # >>> print dst.get('de'), dst.get('de-1901'), dst.get('de-1996')
1389 # Diens-te Dien-ste Diens-te
1390 # >>> print ShortEntry(u'Es-te').get('de-1901')
1391 # Este
1392 # >>> sck = ShortEntry(u'Stre-cke')
1393 # >>> print sck.get('de'), sck.get('de-1901')
1394 # Stre-cke Stre{ck/k-k}e
1396 # Versalschreibung:
1398 # >>> abus = ShortEntry(u'ab<bü-ßen')
1399 # >>> print abus.get('de'), abus.get('de-CH')
1400 # ab<bü-ßen ab<büs-sen
1401 # >>> print abus.get('de-1901-x-versal'), abus.get('de-CH-1901')
1402 # ab<bü-ssen ab<büs-sen
1404 # >>> print ShortEntry(u'passt').get('de-1901')
1405 # paßt
1406 # >>> print ShortEntry(u'passt').get('de-1901-x-versal')
1407 # passt
1408 # >>> rs = ShortEntry(u'-1-;-2-;Russ') # versal für Ruß
1409 # >>> print rs.get('de-x-versal')
1410 # Russ
1411 # >>> rs.get('de-1901-x-versal')
1412 # u''
1414 # >>> entry = ShortEntry(u'süß=sau-er')
1415 # >>> print entry.get('de-CH'), entry.get('de-1901-x-versal'), entry.get('de-CH-1901')
1416 # süss=sau-er süss=sau-er süss=sau-er
1418 # >>> ShortEntry(u'Pro<zess=en-.de').get('de-CH-1901')
1419 # u'Pro<zess=en-.de'
1420 # >>> pstr = ShortEntry(u'Pass=stra-ße')
1421 # >>> print pstr.get('de-1996'), pstr.get('de-1901')
1422 # Pass=stra-ße Paß=stra-ße
1423 # >>> print pstr.get('de-x-versal'), pstr.get('de-1901-x-versal')
1424 # Pass=stras-se Pass=stra-sse
1426 # Test: Wenn keine Ersatzschreibung vorliegt, wird auch in traditioneller
1427 # Versalschreibung s-s getrennt:
1429 # >>> print ShortEntry(u'Bu-ße').get('de-1901-x-versal')
1430 # Bu-sse
1431 # >>> print ShortEntry(u'Bus-se').get('de-1901-x-versal')
1432 # Bus-se
1434 # Geerbt von `WordEntry.get()`_.
1436 # .. _ShortEntry.complete():
1438 # complete()
1439 # ----------
1441 # Eintrag vervollständigen (alle 5 Felder ausfüllen).
1443 # >>> dst.complete()
1444 # >>> print dst
1445 # Diens-te;Dien-ste;Diens-te;Dien-ste;Dien-ste
1446 # >>> aalbst.complete()
1447 # >>> print aalbst
1448 # Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand # Test
1449 # >>> abus.complete()
1450 # >>> print unicode(abus)
1451 # ab<bü-ßen;ab<bü-ßen;ab<büs-sen;ab<bü-ssen;ab<büs-sen
1452 # >>> bss.complete()
1453 # >>> print unicode(bss)
1454 # Boss;Boss;Boss;Boss;Boss # engl.
1455 # >>> frs.complete()
1456 # >>> print unicode(frs)
1457 # -1-;-2-;fräs-se;frä-sse;fräs-se
1458 # >>> tpp.complete()
1459 # >>> print tpp
1460 # Ur<laubs=tipp;-2-;Ur<laubs=tipp;-4-;-5-
1462 # >>> entry = ShortEntry(u'# toller Hecht')
1463 # >>> entry.complete()
1464 # >>> print unicode(entry)
1465 # # toller Hecht
1467 # ::
1469 def complete(self):
1470 for i in range(len(self), 5):
1471 try:
1472 field = self.getitem(i)
1473 except ValueError: # leerer Eintrag
1474 return
1475 self.append(field)
1478 # completed()
1479 # -----------
1481 # Gib eine vervollständigte Kopie des Eintrags zurück.
1483 # >>> entry = ShortEntry(u'-1-;-2-;sties-se').completed()
1484 # >>> entry
1485 # ShortEntry(u'-1-;-2-;sties-se;-4-;-5-')
1486 # >>> [field for field in entry]
1487 # [u'', u'', u'sties-se', u'', u'']
1488 # >>> print unicode(pstr.completed())
1489 # Pass=stra-ße;Paß=stra-ße;Pass=stras-se;Pass=stra-sse;Pass=stras-se
1491 # Geerbt von `WordEntry.completed()`_.
1494 # prune()
1495 # -------
1497 # Eintrag kürzen.
1499 # Felder weglassen, wenn sich der Inhalt durch Ersatzregeln_ gewinnen läßt:
1501 # >>> aalbst.prune()
1502 # >>> print aalbst
1503 # Aal=be<stand # Test
1504 # >>> dst.prune()
1505 # >>> print dst
1506 # Diens-te
1507 # >>> abus.prune()
1508 # >>> print unicode(abus)
1509 # ab<bü-ßen
1510 # >>> bss.prune()
1511 # >>> print unicode(bss)
1512 # Boss;Boss # engl.
1513 # >>> tpp.prune()
1514 # >>> print tpp
1515 # Ur<laubs=tipp;-2-
1517 # Auch das „Auskreuzen“ wird weitergereicht:
1519 # Wenn ein Wort in "traditioneller" Rechtschreibung nicht existiert, reicht
1520 # das "Auskreuzen" von Feld 2:
1522 # >>> entry = ShortEntry(u'Tipp;-2-;Tipp;-4-;-5-')
1523 # >>> entry.prune()
1524 # >>> print entry
1525 # Tipp;-2-
1527 # Wenn ein Wort in aktueller Rechtschreibung nicht existiert, reicht das
1528 # "Auskreuzen" von Feld 1:
1530 # >>> entry = ShortEntry(u'-1-;Rauh=nacht;-3-;Rauh=nacht')
1531 # >>> entry.prune()
1532 # >>> print entry
1533 # -1-;Rauh=nacht
1535 # Felder werden nicht gekürzt, wenn die Rekonstruktion einen anderen Wert
1536 # ergäbe:
1538 # >>> frs.prune()
1539 # >>> print unicode(frs)
1540 # -1-;-2-;fräs-se;frä-sse;fräs-se
1542 # >>> entry = ShortEntry(u'-1-;Dreß;-3-;-4-;-5-')
1543 # >>> entry.prune()
1544 # >>> print unicode(entry)
1545 # -1-;Dreß;-3-;-4-;-5-
1547 # Mit ``drop_sz=True`` werden die drei letzten Felder (ß-Ersatzschreibung)
1548 # stets gekürzt:
1550 # * ß-Ersatzschreibung ist immer definiert, daher kann eigentlich nichts falsch
1551 # gemacht werden.
1553 # * Die "nachlässige" Wandlung garantiert nicht die exakte Reproduktion der
1554 # Ausgangsliste nach einem "Rundtrip":
1556 # Mit `ShortEntry.complete()`_ werden die letzten Spalten ausgefüllt, auch
1557 # wenn sie im Original "ausgekreuzt" waren.
1559 # Bei Wandlung ins Langformat können zusätzliche Einträge mit
1560 # ß-Ersatzschreibung entstehen.
1562 # Die Auszeichnung von Mehrdeutigkeiten (z.B. „Mas-se/Ma-sse“ in
1563 # de-1901-x-versal) geht verloren.
1565 # >>> entry.prune(drop_sz=True)
1566 # >>> print unicode(entry)
1567 # -1-;Dreß
1568 # >>> entry.complete()
1569 # >>> print unicode(entry)
1570 # -1-;Dreß;-3-;Dress;Dress
1572 # ::
1574 def prune(self, drop_sz=False):
1575 if len(self) == 1: # bereits kompakt
1576 return
1577 if drop_sz:
1578 while len(self) > 2:
1579 self.pop()
1580 for i in range(len(self)-1, 0, -1):
1581 wort = self.pop()
1582 rekonstruktion = self.getitem(i)
1583 if wort != rekonstruktion:
1584 # print tag, repr(self), wort, rekonstruktion
1585 self.append(wort)
1586 return
1587 if len(self) == 1 and not self[0]:
1588 self.pop()
1591 # merge()
1592 # -------
1594 # Einträge zusammenlegen.
1596 # Das als Argument übergebene WordEntry Objekt wird in dem eigenen Eintrag
1597 # eingegliedert.
1599 # Leere („ausgekreuzte“) Felder werden überschrieben:
1601 # >>> entry = ShortEntry(u'be<wusst;-2-')
1602 # >>> entry.merge(ShortEntry(u'-1-;be<wußt'))
1603 # >>> print unicode(entry)
1604 # be<wusst
1606 # Identische Felder bleiben erhalten. Alle Felder die über Transformationen_
1607 # rekonstruiert werden können werden entfernt:
1609 # >>> entry = ShortEntry(u'Maß=nah-me # < nehmen')
1610 # >>> entry.merge(ShortEntry(u'-1-;-2-;Mass=nah-me'))
1611 # >>> print unicode(entry)
1612 # Maß=nah-me # < nehmen
1613 # >>> entry = ShortEntry(u'-1-;-2-;Mass=nah-me')
1614 # >>> entry.merge(ShortEntry(u'Maß=nah-me # < nehmen'), merge_comments=True)
1615 # >>> print unicode(entry)
1616 # Maß=nah-me # < nehmen
1618 # Es sei den die Option ``prune`` ist ``False``:
1620 # >>> entry.merge(ShortEntry(u'Maß=nah-me'), prune=False)
1621 # >>> print unicode(entry)
1622 # Maß=nah-me;Maß=nah-me;Mass=nah-me;Mass=nah-me;Mass=nah-me # < nehmen
1624 # Bei Konflikten wird ein AssertionError erzeugt:
1626 # >>> entry = ShortEntry(u'be<wusst;-2-')
1627 # >>> entry.merge(ShortEntry(u'-1-;-2-;ver<bor-gen'))
1628 # Traceback (most recent call last):
1629 # ...
1630 # AssertionError: Merge Error:
1631 # be<wusst;-2-;be<wusst;-4-;-5-
1632 # -1-;-2-;ver<bor-gen;-4-;-5-
1634 # Geerbt von `WordEntry.merge()`_
1637 # wordentries()
1638 # -------------
1640 # Gib eine Liste von WordEntry_ Instanzen (8-Spalten-Format_) zurück.
1642 # >>> mse = ShortEntry(u'Mas-se')
1643 # >>> mse.wordentries()
1644 # [WordEntry(u'Masse;Mas-se')]
1646 # Im Langformat gibt es für jede Schreibung einen separaten Eintrag:
1648 # >>> mas = ShortEntry(u'Ma-ße')
1649 # >>> for e in mas.wordentries():
1650 # ... print unicode(e)
1651 # Maße;Ma-ße
1652 # Masse;-2-;-3-;-4-;-5-;Ma-sse;Mas-se;Mas-se
1654 # >>> entry = ShortEntry(u'Löss')
1655 # >>> for e in entry.wordentries():
1656 # ... print unicode(e)
1657 # Löss;-2-;-3-;Löss;Löss
1658 # Löß;-2-;Löß;-4-
1660 # ::
1662 def wordentries(self, prune=True):
1664 if not self:
1665 if self.comment: # leerer Kommentar
1666 return [WordEntry(u'# '+self.comment)]
1667 return [WordEntry(u'')]
1669 entries = [] # liste für WordEntry Einträge (Rückgabeobjekt)
1670 words = {} # dictionary für WordEntry Einträge
1671 # Zuordnung der Indizes: ShortEntry[s] == WordEntry[l]
1672 indices = (3, 2, 6, 5, 7)
1674 for s,l in enumerate(indices):
1675 word = self.getitem(s)
1676 if not word:
1677 continue # Leerfelder überspringen
1678 # Schlüssel:
1679 key = join_word(word)
1680 # WordEntry Instanz heraussuchen oder erzeugen:
1681 try:
1682 entry = words[key]
1683 except KeyError:
1684 entry = WordEntry(key)
1685 entry.comment = self.comment
1686 words[key] = entry
1687 entries.append(entry)
1688 # Eintrag in entry[j]:
1689 entry.setitem(l, word)
1691 # Auffüllen und Komprimieren
1692 for entry in entries:
1693 while len(entry) < 8:
1694 entry.append(u'')
1695 if prune:
1696 entry.prune()
1697 return entries
1700 # Hilfsfunktion für Tests:
1702 # >>> def print_langform(line, prune=True):
1703 # ... for e in ShortEntry(line).wordentries(prune):
1704 # ... print unicode(e)
1706 # Eine Schreibung:
1708 # >>> print_langform(u'ba-den')
1709 # baden;ba-den
1710 # >>> print_langform(u'Wes-te')
1711 # Weste;-2-;We-ste;Wes-te
1712 # >>> print_langform(u'Toll-patsch;-2-')
1713 # Tollpatsch;-2-;-3-;Toll-patsch
1714 # >>> print_langform(u'-1-;rau-he')
1715 # rauhe;-2-;rau-he;-4-
1717 # Unterschiedliche Schreibungen:
1719 # >>> print_langform(u'Ab<fall=la-ger # Rechtschreibänderung')
1720 # Abfalllager;-2-;-3-;Ab<fall=la-ger # Rechtschreibänderung
1721 # Abfallager;-2-;Ab<fa{ll/ll=l}a-.ger;-4- # Rechtschreibänderung
1723 # >>> print_langform(u'Ab<guss')
1724 # Abguss;-2-;-3-;Ab<guss;Ab<guss
1725 # Abguß;-2-;Ab<guß;-4-
1727 # >>> print_langform(u'groß')
1728 # groß;groß
1729 # gross;-2-;-3-;-4-;gross
1731 # >>> print_langform(u'gro-ßen')
1732 # großen;gro-ßen
1733 # grossen;-2-;-3-;-4-;-5-;gro-ssen;gros-sen;gros-sen
1735 # >>> print_langform(u'Spaß')
1736 # Spaß;Spaß
1737 # Spass;-2-;-3-;-4-;Spass
1738 # >>> print_langform(u'Spass')
1739 # Spass;-2-;-3-;Spass;Spass
1740 # Spaß;-2-;Spaß;-4-
1741 # >>> print_langform(u'spa-ßen')
1742 # spaßen;spa-ßen
1743 # spassen;-2-;-3-;-4-;-5-;spa-ssen;spas-sen;spas-sen
1745 # >>> print_langform(u'ab<bü-ßen')
1746 # abbüßen;ab<bü-ßen
1747 # abbüssen;-2-;-3-;-4-;-5-;ab<bü-ssen;ab<büs-sen;ab<büs-sen
1749 # >>> print_langform(u'Biss')
1750 # Biss;-2-;-3-;Biss;Biss
1751 # Biß;-2-;Biß;-4-
1752 # >>> print_langform(u'Boss;Boss # engl.')
1753 # Boss;Boss # engl.
1755 # >>> print_langform(u'Pass=sys-tem')
1756 # Passsystem;-2-;-3-;Pass=sys-tem;-5-;Pass=sy-stem;Pass=sys-tem
1757 # Paßsystem;-2-;Paß=sy-stem;-4-
1759 # >>> print_langform(u'Press=saft')
1760 # Presssaft;-2-;-3-;Press=saft;Press=saft
1761 # Preßsaft;-2-;Preß=saft;-4-
1763 # >>> print_langform(u'Pro<gramm==maß=nah-me')
1764 # Programmmaßnahme;-2-;-3-;Pro<gramm==maß=nah-me
1765 # Programmaßnahme;-2-;Pro<gra{mm/mm==m}aß=nah-me;-4-
1766 # Programmmassnahme;-2-;-3-;-4-;-5-;-6-;Pro<gramm==mass=nah-me
1767 # Programmassnahme;-2-;-3-;-4-;-5-;Pro<gra{mm/mm==m}ass=nah-me;-7-
1769 # >>> print_langform(u'Pass=stra-ße')
1770 # Passstraße;-2-;-3-;Pass=stra-ße
1771 # Paßstraße;-2-;Paß=stra-ße;-4-
1772 # Passstrasse;-2-;-3-;-4-;-5-;Pass=stra-sse;Pass=stras-se;Pass=stras-se
1774 # >>> print_langform(u'Pro<zess=en-.de;Pro<zeß=en-de;Pro<zess=en-.de')
1775 # Prozessende;-2-;-3-;Pro<zess=en-.de;Pro<zess=en-.de
1776 # Prozeßende;-2-;Pro<zeß=en-de;-4-
1778 # Explizit nur eine Schreibung:
1780 # >>> print_langform(u'Abend=dress;Abend=dress')
1781 # Abenddress;Abend=dress
1782 # >>> print_langform(u'-1-;Abend=dreß')
1783 # Abenddreß;-2-;Abend=dreß;-4-
1784 # Abenddress;-2-;-3-;-4-;-5-;Abend=dress;-7-
1786 # Nur Versalschreibung:
1788 # >>> print_langform(u'-1-;-2-;Fuss;Fuss;Fuss')
1789 # Fuss;-2-;-3-;-4-;Fuss
1790 # >>> line = u'Fussballiga;-2-;-3-;-4-;-5-;Fuss=ba{ll/ll=l}i-.ga'
1791 # >>> print ShortEntry(WordEntry(line), prune=False)
1792 # -1-;-2-;-3-;Fuss=ba{ll/ll=l}i-.ga;Fuss=ba{ll/ll=l}i-.ga
1793 # >>> print_langform(u'-1-;-2-;-3-;Fuss=ba{ll/ll=l}i-.ga;Fuss=ba{ll/ll=l}i-.ga')
1794 # Fussballiga;-2-;-3-;-4-;-5-;Fuss=ba{ll/ll=l}i-.ga;-7-
1795 # >>> print_langform(u'-1-;-2-;Fuss=ball==li-ga')
1796 # Fussballliga;-2-;-3-;-4-;-5-;-6-;Fuss=ball==li-ga
1798 # Nur Kommentar:
1800 # >>> print_langform(u'# holla')
1801 # # holla
1804 # Transformationen
1805 # ================
1807 # ableitung1901()
1808 # ---------------
1810 # Ableitung von de-1901 aus de-1996 (Reversion der Reform 1996).
1812 # Mit keep_key == True werden nur Änderungen vorgenommen, die die
1813 # Schreibung des (ungetrennten) Wortes nicht verändern.
1815 # >>> from wortliste import ableitung1901
1817 # ::
1819 def ableitung1901(wort, keep_key=False):
1820 """Reverse regular changes of the 1996 orthography reform."""
1823 # Trennregeländerungen
1824 # ~~~~~~~~~~~~~~~~~~~~
1826 # Diese Regeln ändern nicht das Wort, nur die Trennmöglichkeiten.
1828 # Alternativtrennungen
1829 # """"""""""""""""""""
1831 # (siehe `fremdwortsilben()`_, `verblasst()`_)::
1833 wort = fremdwortsilben(wort)
1834 wort = verblasst(wort)
1836 # st-Trennung
1837 # """""""""""
1839 # K75: Trenne nie st.
1841 # Ersetze 's-t' mit '-st':
1843 # >>> ableitung1901(u'Diens-te')
1844 # u'Dien-ste'
1845 # >>> print ableitung1901(u'wuss-te', keep_key=True)
1846 # wuss-te
1848 # Aber Trennung von s-theta erlaubt (nach K74):
1850 # >>> print ableitung1901(u'Äs-thet')
1851 # Äs-thet
1853 # ::
1855 wort = re.sub(u'(?<!s)s-t(?!h)', u'-st', wort)
1858 # ck-Trennung
1859 # """""""""""
1861 # K76: Trenne 'ck' als 'k-k'.
1863 # Ersetze '-ck' mit '{ck/k-k}':
1865 # >>> ableitung1901(u'Ha-cke')
1866 # u'Ha{ck/k-k}e'
1867 # >>> ableitung1901(u'Acker')
1868 # u'A{ck/k-k}er'
1869 # >>> ableitung1901(u'Got-tes=acker')
1870 # u'Got-tes=a{ck/k-k}er'
1871 # >>> ableitung1901(u'Aal=beck')
1872 # u'Aal=beck'
1873 # >>> ableitung1901(u'be<spick-te')
1874 # u'be<spick-te'
1876 # ::
1878 wort = wort.replace(u'-ck', u'{ck/k-k}')
1879 wort = re.sub(u'(?<=[AEIOUYÄÖÜaeiouyäöü])ck(?=[aeiouyäöü])',
1880 u'{ck/k-k}', wort)
1883 # Keine Trennung nach nur einem Buchstaben am Wortanfang:
1885 # >>> ableitung1901(u'Es-te')
1886 # u'Este'
1887 # >>> print ableitung1901(u'Nord=os-ten')
1888 # Nord=osten
1889 # >>> print ableitung1901(u'Po-ly<es-ter')
1890 # Po-ly<ester
1892 # Im Wort sind Einvokalsilben erlaubt (aber nicht immer günstig):
1894 # >>> print ableitung1901(u'the-is-tisch')
1895 # the-i-stisch
1897 # ::
1899 wort = re.sub(u'((?<=[=<].)|(?<=^.))-', ur'', wort)
1901 if keep_key:
1902 return wort
1904 # Rechtschreibänderungen
1905 # ~~~~~~~~~~~~~~~~~~~~~~
1907 # Diese Regeln ändern die Schreibung des ungetrennten Worts (und somit den
1908 # Schlüssel im Langformat der Wortliste).
1910 # Schluss-ss
1911 # """"""""""
1913 # K38: Kein "ss" und "sst" am Silbenende (ungetrenntes "ss" nur in Ausnahmen)
1914 # (Andererseit ist 'ßt' und Schluss-ß auch in de-1996 möglich (langer Vokal).)
1916 # Ersetze ungetrenntes 'ss' mit 'ß':
1918 # >>> print ableitung1901(u'passt')
1919 # paßt
1920 # >>> print ableitung1901(u'Hass')
1921 # Haß
1922 # >>> print ableitung1901(u'Fass=brau-se')
1923 # Faß=brau-se
1924 # >>> print ableitung1901(u'wuss-te')
1925 # wuß-te
1927 # ß steht für inlautendes ss, wenn ein 'e' ausfällt (und der Ausfall nicht
1928 # durch Apostroph angedeutet wird)
1930 # >>> print ableitung1901(u'wäss-rig')
1931 # wäß-rig
1932 # >>> print ableitung1901(u'an<ge<mess-ner')
1933 # an<ge<meß-ner
1934 # >>> print ableitung1901(u'duss-lig')
1935 # duß-lig
1936 # >>> print ableitung1901(u'bissl')
1937 # bißl
1939 # Keine Wandlung zu "ß":
1940 # getrenntes Doppel-s (s-s)
1942 # >>> print ableitung1901(u'Was-ser')
1943 # Was-ser
1945 # Vokal folgt (Fremdwörter):
1946 # >>> print ableitung1901(u'Com-tesse')
1947 # Com-tesse
1949 # Großbuchstabe folgt
1951 # >>> print ableitung1901(u'WissZeitVG') # Abkürzung
1952 # WissZeitVG
1954 # Drei oder mehr 's' = Lautmalerei → erhalten:
1956 # >>> print ableitung1901(u'pssst')
1957 # pssst
1959 # ::
1961 wort = re.sub(u'(?<=[^s])ss(?=[^aeiouyäöüA-Zs]|$)', ur'ß', wort)
1963 # Unterdrückung der Trennstelle nach "…ß=er" und "…ß=en" nicht nötig:
1965 # >>> print ableitung1901(u'hass=er<.füllt')
1966 # haß=er<füllt
1968 # ::
1970 wort = re.sub(ur'ß(=+)e([rn][<-]+)\.', ur'ß\1e\2', wort)
1972 # Dreikonsonantenregel
1973 # """"""""""""""""""""
1975 # K78: Zusammensetzungen, bei denen von drei zusammenstoßenden gleichen
1976 # Konsonanten einer entfällt (K15), schreibt man bei Silbentrennung wieder
1977 # mit allen drei Konsonanten.
1979 # Ersetze 'xx=x' xit '{xx/xx=x}' (für alle Konsonanten vor Selbstlaut)
1981 # >>> print ableitung1901(u'Kipp=pflug')
1982 # Kipp=pflug
1983 # >>> print ableitung1901(u'Kipp=punkt')
1984 # Ki{pp/pp=p}unkt
1985 # >>> print ableitung1901(u'Ab<fall=la-ger')
1986 # Ab<fa{ll/ll=l}a-.ger
1987 # >>> print ableitung1901(u'All<lie-be')
1988 # A{ll/ll<l}ie-be
1989 # >>> print ableitung1901(u'hell>licht')
1990 # he{ll/ll>l}icht
1991 # >>> print ableitung1901(u'Pro<gramm==maß=nah-me')
1992 # Pro<gra{mm/mm==m}aß=nah-me
1994 # ::
1996 wort = re.sub(ur'([bfglmnprt])\1([<=>]+)\1(?=[aeiouyäöü])',
1997 ur'{\1\1/\1\1\2\1}', wort)
1999 # Unterdrücken der Trennung nach nur einem Buchstaben::
2001 wort = re.sub(ur'(?<=[=>].}[aeiouyäöü])([-<])\.?', ur'\1.', wort)
2004 return wort
2006 # Tests:
2008 # Ein-Vokal-Silben auch schon 1901 erlaubt:
2010 # >>> print ableitung1901(u'ver<knäu-e-le')
2011 # ver<knäu-e-le
2014 # versalschreibung()
2015 # ------------------
2016 # Ersetze 'ß' mit 'ss', trenne je nach Sprachvarietät `lang`:
2018 # >>> from wortliste import versalschreibung
2019 # >>> print versalschreibung(u'paßt')
2020 # passt
2021 # >>> print versalschreibung(u'Dar-ßer')
2022 # Dars-ser
2023 # >>> print versalschreibung(u'bü-ßen', 'de-CH')
2024 # büs-sen
2025 # >>> print versalschreibung(u'bü-ßen', 'de-1996-x-versal')
2026 # büs-sen
2027 # >>> print versalschreibung(u'bü-ßen', 'de-CH-1901')
2028 # büs-sen
2029 # >>> print versalschreibung(u'äßen', 'de-CH-1901')
2030 # äs-sen
2031 # >>> print versalschreibung(u'auf<äßen', 'de-CH-1901')
2032 # auf<äs-sen
2033 # >>> print versalschreibung(u'auf<eßt', 'de-CH-1901')
2034 # auf<esst
2035 # >>> print versalschreibung(u'Groß=se-gel', 'de-1901-x-versal')
2036 # Gross=se-gel
2037 # >>> print versalschreibung(u'Groß=se-gel', 'de-CH-1901')
2038 # Gross=se-gel
2039 # >>> print versalschreibung(u'Paß=sy-ste-me', 'de-CH-1901')
2040 # Pass=sy-ste-me
2041 # >>> print versalschreibung(u'Pro<zeß=en-de', 'de-CH-1901')
2042 # Pro<zess=en-.de
2043 # >>> print versalschreibung(u'Pro<zess=en-.de', 'de-CH-1901')
2044 # Pro<zess=en-.de
2045 # >>> print versalschreibung(u'Fluß==sy-stem', 'de-CH-1901')
2046 # Fluss==sy-stem
2047 # >>> print versalschreibung(u'Meß==sen-der', 'de-CH-1901')
2048 # Mess==sen-der
2050 # ::
2052 def versalschreibung(wort, lang='de'):
2054 if not u'ß' in wort:
2055 return wort
2057 wort = wort.replace(u'ß', u'ss')
2059 # Trennung von Ersatz-ss in de-CH und de-1996 nach Sprechsilbenregel::
2061 if '1901-x-versal' not in lang:
2062 # wort = re.sub(u'(?<=[aeiouyäöü])-\.?ss', u's-s', wort)
2063 wort = re.sub(u'-\.?ss(?=[aeiouyäöü])', u's-s', wort)
2064 wort = re.sub(u'(?<=^[aeiouyäöü])ss(?=[aeiouyäöü])', u's-s', wort)
2065 wort = re.sub(u'(?<=[=<][aeiouyäöü])ss(?=[aeiouyäöü])', u's-s', wort)
2067 # Unterdrückung irreführender Trennung::
2069 wort = re.sub(u'ss(=+)(en|er)([<-])\.?', ur'ss\1\2\3.', wort)
2071 # Dreikonsonantenregel für Ersatz-ss in de-CH-1901::
2073 if 'CH-1901-x-dreikonsonanten' in lang:
2074 wort = re.sub(u'ss(=+)s(?=[aeiouyäöü])', ur'{ss/ss\1s}', wort)
2075 # Unterdrücken der Trennung nach nur einem Buchstaben und irreführender Trennungen
2076 wort = re.sub(ur'(?<=[=>]s}[aeiouyäöü])([-<])\.?', ur'\1.', wort)
2077 # wort = re.sub(ur'(?<===s}[aeiouyäöü])([-<])\.?', ur'\1.', wort) # Reißverschus=sy-.stem
2078 wort = re.sub(u'(?<=[=>]s})(en|er)([<-])\.?', ur'\1\2.', wort)
2080 return wort
2083 # Kurzformat in Langformat
2084 # ------------------------
2086 # Zusätzlich benötigte Felder werden automatisch erzeugt. Ein Kurzeintrag kann
2087 # mehrere Langeinträge ergeben:
2089 # >>> from wortliste import short2long
2090 # >>> for line in short2long([u"Diens-te", u"Ge<biss"]):
2091 # ... print unicode(line)
2092 # Dienste;-2-;Dien-ste;Diens-te
2093 # Gebiss;-2-;-3-;Ge<biss;Ge<biss
2094 # Gebiß;-2-;Ge<biß;-4-
2096 # >>> for line in short2long([u"Ge<schoss", u"Ge<schoß # österr."]):
2097 # ... print unicode(line)
2098 # Geschoss;-2-;-3-;Ge<schoss;Ge<schoss
2099 # Geschoß;Ge<schoß # österr.
2101 # short2long()
2102 # ~~~~~~~~~~~~
2103 # ::
2105 def short2long(lines, sort=True, prune=True):
2106 """Convert sequence of lines in ShortEntry format to WordEntry instances.
2109 # Sammeln in Liste und Dictionary:
2110 words = {} # zum Zusammenfassen
2111 entries = []
2113 for line in lines:
2114 shortentry = ShortEntry(line)
2115 shortentry.complete()
2116 for entry in shortentry.wordentries(prune=False):
2117 key = entry.key().lower()
2118 try: # Eintrag mit gleichem Schlüssel vorhanden?
2119 altentry = words[key]
2120 except KeyError: # nein -> neuer Eintrag
2121 words[key] = entry
2122 entries.append(entry)
2123 continue
2124 try:
2125 if entry[3]: # de-1996 non-empty
2126 entry.merge(altentry, prune=False, allow_alternatives=True)
2127 # Alternativen Eintrag "in-place" ersetzen:
2128 for i, word in enumerate(entry):
2129 altentry[i] = word
2130 altentry.comment = entry.comment
2131 else:
2132 altentry.merge(entry, prune=False, allow_alternatives=True)
2133 except AssertionError as e:
2134 sys.stderr.write(unicode(e).encode('utf8')+'\n')
2135 entries.append(entry)
2136 except IndexError: # Leerer Eintrag (Kommentar)
2137 entries.append(entry)
2140 if prune:
2141 for entry in entries:
2142 entry.prune()
2144 if sort:
2145 entries.sort(key=sortkey_duden)
2147 return entries
2149 # Tests:
2151 # Kommentare bleiben erhalten:
2153 # >>> for line in short2long([u'Aal=an-geln', u'# toller Kommentar'],
2154 # ... sort=False):
2155 # ... print unicode(line)
2156 # Aalangeln;Aal=an-geln
2157 # # toller Kommentar
2159 # Beim Sortieren werden Kommenare an den Beginn geschrieben.
2160 # >>> for line in short2long([u'# erster Kommentar',
2161 # ... u'Aal=an-geln',
2162 # ... u'# zweiter Kommentar']):
2163 # ... print unicode(line)
2164 # # erster Kommentar
2165 # # zweiter Kommentar
2166 # Aalangeln;Aal=an-geln
2169 # Langformat in Kurzformat
2170 # ------------------------
2172 # Optionale Felder werden weggelassen, wenn sie mit dem automatisch erzeugten
2173 # Inhalt übereinstimmen:
2175 # >>> from wortliste import long2short
2176 # >>> for entry in long2short([u"Dienste;-2-;Dien-ste;Diens-te"]):
2177 # ... print unicode(entry)
2178 # Diens-te
2180 # Zusammengehörige Einträge werden zusammengefasst:
2182 # >>> for line in long2short([u"Großanlass;-2-;-3-;Groß=an<lass",
2183 # ... u"Großanlaß;-2-;Groß=an<laß;-4-",
2184 # ... u"Grossanlass;-2-;-3-;-4-;Gross=an<lass"]):
2185 # ... print unicode(line)
2186 # Groß=an<lass
2188 # Sonderfälle
2189 # ~~~~~~~~~~~
2191 # Bei einigen Wörtern ist die ß-ss-Beziehung nicht eindeutig:
2193 # ======= ======== =========
2194 # de1901 de-1996 de-CH
2195 # ======= ======== =========
2196 # Maße Maße Masse
2197 # Masse Masse Masse
2198 # Geschoß Geschoß Geschoss
2199 # Geschoß Geschoss Geschoss
2200 # ======= ======== =========
2202 # Daher kann es vorkommen, dass ein Langform-Eintrag Beiträge von
2203 # verschiedenen Kurzformen erhält. So erzeugen, z.B. sowohl
2204 # „Masse“ als auch „Maße“ einen Langeintrag mit Schlüssel „Masse“:
2206 # >>> for entry in short2long([u'Mas-se']): print unicode(entry)
2207 # Masse;Mas-se
2208 # >>> for entry in short2long([u'Ma-ße']): print unicode(entry)
2209 # Masse;-2-;-3-;-4-;-5-;Ma-sse;Mas-se;Mas-se
2210 # Maße;Ma-ße
2212 # In der Langform werden Alternativen in ein Feld geschrieben:
2214 # >>> for entry in short2long([u'Mas-se', u'Ma-ße']):
2215 # ... print unicode(entry)
2216 # Masse;-2-;Mas-se;Mas-se;-5-;Ma[s-/-s]se;Mas-se;Mas-se
2217 # Maße;Ma-ße
2219 # Sind die Alternativen bereits in der Quelle, bleiben sie in der Kurzform
2220 # erhalten:
2222 # >>> ml = [u'Masse;-2-;Mas-se;Mas-se;-5-;Ma[-s/s-]se;Mas-se;Mas-se',
2223 # ... u'Maße;Ma-ße']
2224 # >>> for entry in long2short(ml):
2225 # ... print unicode(entry)
2226 # Mas-se;Mas-se;Mas-se;Ma[-s/s-]se
2227 # Ma-ße;Ma-ße;Mas-se;Ma[-s/s-]se
2229 # Zurück in die Langform:
2231 # >>> for entry in short2long([u'Mas-se;Mas-se;Mas-se;Ma[-s/s-]se',
2232 # ... u'Ma-ße;Ma-ße;-3-;Ma[-s/s-]se']):
2233 # ... print unicode(entry)
2234 # Masse;-2-;Mas-se;Mas-se;-5-;Ma[-s/s-]se;Mas-se;Mas-se
2235 # Maße;Ma-ße
2237 # long2short()
2238 # ~~~~~~~~~~~~
2239 # ::
2241 def long2short(lines, prune=True, drop_sz=False):
2242 """Convert sequence of 8-column lines to ShortEntry instances."""
2243 words = {} # Einträge mit `de`
2244 words_x = {} # Einträge ohne `de`
2245 words_merged = set() # Einträge die vollständig in andere einsortiert wurden
2246 entries = [] # Rückgabewert: Liste der Kurzeinträge
2248 # Zeilen Einlesen, Wandeln und Sammeln::
2250 for line in lines:
2251 longentry = WordEntry(line)
2252 entry = ShortEntry(longentry, prune=False)
2253 key = entry.key().lower() # Schlüssel ohne Großschreibung
2255 if not entry: # reiner Kommentar oder leerer Eintrag
2256 if entry.comment:
2257 entries.append(entry)
2258 continue
2260 # Einträge mit leerem ersten Feld werden später einsortiert:
2261 if prune and not entry[0]:
2262 words_x[key] = entry
2263 continue
2265 # Eintrag in `dictionary` und Liste:
2266 words[key] = entry
2267 entries.append(entry)
2269 # Straffen (Weglassen von Feldern/Einträgen wenn möglich),
2270 # es sei denn, der Aufruf erfolgte mit `prune=False`::
2272 if not prune:
2273 return entries
2275 for entry in entries:
2277 # Auffüllen:
2278 for i in range(1, len(entry)):
2279 if not entry[i]: # Feld ausgekreuzt
2280 key_i = join_word(entry.getitem(i, substitute=True)).lower()
2281 # print i, key_i
2282 try:
2283 co_entry = words_x[key_i]
2284 entry.merge(co_entry, prune=False, start=1)
2285 words_merged.add(key_i)
2286 # del words_x[key_i]
2287 except (KeyError, AssertionError):
2288 try:
2289 co_entry = words[key_i]
2290 entry.merge(co_entry, prune=False, start=1)
2291 words_merged.add(key_i)
2292 except (KeyError, AssertionError):
2293 pass
2295 # Anhängen aller Einträge mit leerem `de`-Feld, die nicht in
2296 # einen zugehörigen Eintrag einsortiert wurden an die Liste::
2298 for key, entry in words_x.iteritems():
2299 # print key, unicode(entry)
2300 if key in words_merged:
2301 continue
2302 # ggf. ergänzen:
2303 if len(entry) > 2 and not entry[3]: # de-1901-x-versal
2304 key = join_word(entry.getitem(3, substitute=True)).lower()
2305 try:
2306 co_entry = words_x[key]
2307 entry.merge(co_entry, prune=False, start=3)
2308 words_merged.add(key)
2309 except (KeyError, AssertionError):
2310 try:
2311 co_entry = words[key]
2312 entry.merge(co_entry, prune=False, start=3)
2313 # print key, unicode(co_entry)
2314 except (KeyError, AssertionError):
2315 pass
2317 entries.append(entry)
2319 for entry in entries:
2320 entry.prune(drop_sz)
2322 return entries
2324 # Tests:
2326 # Separate Einträge ß-Schreibungen zusammenfassen:
2328 # Ein von 2 ß wird zu ss in de-1996:
2330 # >>> for entry in long2short([
2331 # ... u'Passstrasse;-2-;-3-;-4-;-5-;Pass=stra-sse;Pass=stras-se;Pass=stras-se',
2332 # ... u'Passstraße;-2-;-3-;Pass=stra-ße',
2333 # ... u'Paßstraße;-2-;Paß=stra-ße;-4-']):
2334 # ... print unicode(entry)
2335 # Pass=stra-ße
2337 # ß in de-1996:
2339 # >>> for line in long2short([
2340 # ... u"Grossserie;-2-;-3-;-4-;-5-;Gross=se-rie;Gross=se-rie",
2341 # ... u"Großserie;Groß=se-rie"]):
2342 # ... print unicode(line)
2343 # Groß=se-rie
2345 # kein ß in de-1996:
2347 # >>> for line in long2short([
2348 # ... u"Basssaite;-2-;-3-;Bass=sai-te;-5-;Bass=sai-te;Bass=sai-te",
2349 # ... u"Baßsaite;-2-;Baß=sai-te;-4-"]):
2350 # ... print unicode(line)
2351 # Bass=sai-te
2353 # Zusätzliche Variante (Fremdwort vs. Lehnwort) in de-1901:
2355 # >>> for entry in short2long([u'Boss;Boss # en.']): print unicode(entry)
2356 # Boss;Boss # en.
2357 # >>> for entry in long2short([u'Boss;Boss # en.']): print unicode(entry)
2358 # Boss;Boss # en.
2359 # >>> for entry in long2short([u'Boss;Boss # en.', u'Boß;-2-;Boß;-3- # < en.']):
2360 # ... print unicode(entry)
2361 # Boss;Boss # en.
2362 # -1-;Boß # < en.
2364 # Alternativschreibung in de-1996 (Geschoß, Löß):
2366 # >>> for entry in long2short([u'Geschoss;-2-;-3-;Ge<schoss;Ge<schoss',
2367 # ... u'Geschoß;Ge<schoß # österr. auch de-1996']):
2368 # ... print unicode(entry)
2369 # Ge<schoss
2370 # Ge<schoß # österr. auch de-1996
2372 # Eigennamen auf -ss:
2374 # >>> for entry in long2short([u'Vossstrasse;-2-;-3-;-4-;-5-;Voss=stra-sse;Voss=stras-se;Voss=stras-se',
2375 # ... u'Vossstraße;Voss=stra-ße',
2376 # ... u'Voßstraße;Voß=stra-ße']):
2377 # ... print unicode(entry)
2378 # Voss=stra-ße;Voss=stra-ße
2379 # Voß=stra-ße
2381 # Kommentare:
2383 # >>> long2short(['# toller Kommentar'], prune=False)
2384 # [ShortEntry(u'# toller Kommentar')]
2385 # >>> long2short(['# toller Kommentar'], prune=True)
2386 # [ShortEntry(u'# toller Kommentar')]
2389 # Trennfilter
2390 # ===========
2392 # Funktionen, die einen Trennstil (oder einen Aspekt eines Trennstils)
2393 # implementieren (siehe auch dokumentation/Trennstile.txt).
2395 # fremdwortsilben()
2396 # -----------------
2398 # Entferne Alternativtrennungen bei einfachen und suffigierten Fremdwörtern:
2400 # Regelwerk (1996) § 112:
2401 # In Fremdwörtern können die Verbindungen aus Buchstaben für einen
2402 # Konsonanten + l, n oder r entweder entsprechend § 110 getrennt werden,
2403 # oder sie kommen ungetrennt auf die neue Zeile.
2405 # >>> from wortliste import fremdwortsilben
2407 # >>> fremdwoerter = (u'no-b-le Zy-k-lus Ma-g-net Fe-b-ru-ar '
2408 # ... u'Hy-d-rant Ar-th-ri-tis ger<i-a-t-ri-sche')
2409 # >>> for wort in fremdwoerter.split():
2410 # ... print wort, '->', fremdwortsilben(wort)
2411 # no-b-le -> no-ble
2412 # Zy-k-lus -> Zy-klus
2413 # Ma-g-net -> Ma-gnet
2414 # Fe-b-ru-ar -> Fe-bru-ar
2415 # Hy-d-rant -> Hy-drant
2416 # Ar-th-ri-tis -> Ar-thri-tis
2417 # ger<i-a-t-ri-sche -> ger<i-a-tri-sche
2419 # >>> for wort in fremdwoerter.split():
2420 # ... print wort, '->', fremdwortsilben(wort, 'modern')
2421 # no-b-le -> nob-le
2422 # Zy-k-lus -> Zyk-lus
2423 # Ma-g-net -> Mag-net
2424 # Fe-b-ru-ar -> Feb-ru-ar
2425 # Hy-d-rant -> Hyd-rant
2426 # Ar-th-ri-tis -> Arth-ri-tis
2427 # ger<i-a-t-ri-sche -> ger<i-at-ri-sche
2429 # ::
2431 def fremdwortsilben(wort, style='etymologisch'):
2432 """Select in-word hyphenation of foreign words."""
2433 if style == "modern": # Sprechsilbenregel
2434 return re.sub(u'-([bcdfgkptv]|th)-(?=[lrn])', u'\\1-', wort) # §112
2435 else: # morphematisch
2436 return re.sub(u'-([bcdfgkptv]|th)-(?=[lrn])', u'-\\1', wort) # K86, K87
2437 # Versuch: auch Alternativtrennung nach führendem Vokal:
2438 # Ap-ri-kose -> Apri-kose aber auch Ad-ler -> Adler (!)
2439 # return re.sub(u'(-|^[AEIOUÄÖÜaeiouäöü])([bcdfgkptv]|th|st)-(?=[lrn])',
2440 # u'\\1\\2', wort)
2442 # Tests:
2444 # K86 Untrennbar sind in Fremdwörtern die Verbindungen von Verschluß- und
2445 # Reibelauten mit l und r, ...
2447 # >>> fremdwoerter = (u'Pu-b-li-kum flexi-b-ler Zy-k-lone Qua-d-rat '
2448 # ... u'Spek-t-rum manö-v-rieren')
2449 # >>> for wort in fremdwoerter.split():
2450 # ... print wort, '->', fremdwortsilben(wort)
2451 # Pu-b-li-kum -> Pu-bli-kum
2452 # flexi-b-ler -> flexi-bler
2453 # Zy-k-lone -> Zy-klone
2454 # Qua-d-rat -> Qua-drat
2455 # Spek-t-rum -> Spek-trum
2456 # manö-v-rieren -> manö-vrieren
2458 # die Lautfolge st+r bleibt ungetrennt, wenn keine Wortfuge vorliegt.
2460 # >>> fremdwoerter = u'Di-st-rikt Magi-st-rat laku-st-risch ab-st-rakt'
2461 # >>> for wort in fremdwoerter.split():
2462 # ... print wort, '->', fremdwortsilben(wort)
2463 # Di-st-rikt -> Di-strikt
2464 # Magi-st-rat -> Magi-strat
2465 # laku-st-risch -> laku-strisch
2466 # ab-st-rakt -> ab-strakt
2468 # K 87 Untrennbar ist die Konsonantenverbindung "gn".
2470 # >>> fremdwoerter = u'Ma-g-net Pro-g-nose Si-g-net'
2471 # >>> for wort in fremdwoerter.split():
2472 # ... print wort, '->', fremdwortsilben(wort)
2473 # Ma-g-net -> Ma-gnet
2474 # Pro-g-nose -> Pro-gnose
2475 # Si-g-net -> Si-gnet
2477 # Keine Übergeneralisierung:
2479 # >>> woerter = u'Seg-ler bast-le Ad-ler'
2480 # >>> for wort in woerter.split():
2481 # ... print wort, '->', fremdwortsilben(wort)
2482 # Seg-ler -> Seg-ler
2483 # bast-le -> bast-le
2484 # Ad-ler -> Ad-ler
2486 # wegen Übergeneralisierung nicht möglich:
2487 # Ap-ri-kose -> Apri-kose
2488 # ig-no-rie-ren -> igno-rie-ren
2491 # verblasst()
2492 # --------------
2494 # Entferne Alternativtrennungen nach §113 (verblasste Etymologie).
2496 # Regelwerk (1996) §113:
2497 # Wörter, die sprachhistorisch oder von der Herkunftssprache her gesehen
2498 # Zusammensetzungen oder Präfigierungen sind, aber nicht mehr als solche
2499 # empfunden oder erkannt werden, kann man entweder nach § 108 oder nach §
2500 # 109 bis § 112 trennen.
2502 # >>> from wortliste import verblasst
2503 # >>> blasse = (u'hi-n<auf he-r<an da-r<um Chry-s<an-the-me Hek-t<ar '
2504 # ... u'He-li-ko<p-ter in-te-r>es-sant Li-n<oleum Pä-d<a-go-gik')
2505 # >>> for wort in blasse.split():
2506 # ... print wort, '->', verblasst(wort)
2507 # hi-n<auf -> hin<auf
2508 # he-r<an -> her<an
2509 # da-r<um -> dar<um
2510 # Chry-s<an-the-me -> Chrys<an-the-me
2511 # Hek-t<ar -> Hekt<ar
2512 # He-li-ko<p-ter -> He-li-ko<pter
2513 # in-te-r>es-sant -> in-ter>es-sant
2514 # Li-n<oleum -> Lin<oleum
2515 # Pä-d<a-go-gik -> Päd<ago-gik
2517 # >>> for wort in blasse.split():
2518 # ... print wort, '->', verblasst(wort, 'modern')
2519 # hi-n<auf -> hi-nauf
2520 # he-r<an -> he-ran
2521 # da-r<um -> da-rum
2522 # Chry-s<an-the-me -> Chry-san-the-me
2523 # Hek-t<ar -> Hek-tar
2524 # He-li-ko<p-ter -> He-li-kop-ter
2525 # in-te-r>es-sant -> in-te-res-sant
2526 # Li-n<oleum -> Li-noleum
2527 # Pä-d<a-go-gik -> Pä-da-go-gik
2529 # Ersetze, wenn zwischen Haupttrennstelle und Nebentrennstelle nur ein
2530 # Buchstabe liegt.
2531 # (Die Haupttrennstelle kann vor oder nach der Nebentrennstelle liegen.)
2532 # ::
2534 def verblasst(wort, style='morphematisch'):
2535 """Select hyphenation of foreign words with obscure etymology."""
2536 if style == "modern": # Sprechsilbenregel
2537 wort = re.sub(u'[<>=]+[.]*(.[-.]+)', u'\\1', wort)
2538 wort = re.sub(u'([-.]+.)[<>=]+[.]*', u'\\1', wort)
2539 else: # morphematisch
2540 wort = re.sub(u'([<>=]+[.]*.)[-.]+', u'\\1', wort)
2541 wort = re.sub(u'[-.]+(.[<>=]+)', u'\\1', wort)
2542 return wort
2546 # Tests:
2548 # K44 „ſ“ (langes s) steht in Fremdwörtern...
2550 # >>> blasse = (u'tran<s-pirieren tran<s-zendent ab<s-tinent '
2551 # ... u'Ab<s-zess Pro-s<odie')
2552 # >>> for wort in blasse.split():
2553 # ... print wort, '->', verblasst(wort)
2554 # tran<s-pirieren -> tran<spirieren
2555 # tran<s-zendent -> tran<szendent
2556 # ab<s-tinent -> ab<stinent
2557 # Ab<s-zess -> Ab<szess
2558 # Pro-s<odie -> Pros<odie
2560 # Trennstellen können als ungünstig markiert sein:
2562 # >>> blasse = (u'Bür-ger=in<.i-ti-a-ti-ve Pä-..d<e-..rast')
2563 # >>> for wort in blasse.split():
2564 # ... print wort, '->', verblasst(wort)
2565 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=in<.iti-a-ti-ve
2566 # Pä-..d<e-..rast -> Päd<erast
2567 # >>> for wort in blasse.split():
2568 # ... print wort, '->', verblasst(wort, 'modern')
2569 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=ini-ti-a-ti-ve
2570 # Pä-..d<e-..rast -> Pä-..de-..rast
2573 # scoretext()
2574 # ------------
2576 # Füge Trennmöglichkeiten am Wortanfang und -ende zu, die nach K79 (bzw. §107
2577 # E2 des Regelwerkes) verboten sind aber in Notentexten gebraucht werden.
2579 # >>> from wortliste import scoretext
2580 # >>> scoretext(u'Abend')
2581 # u'A.bend'
2582 # >>> scoretext(u'Ra-dio')
2583 # u'Ra-di.o'
2585 # Das gleiche gilt für Trennmöglichkeiten am Anfang/Ende von Teilwörtern:
2587 # >>> scoretext(u'Eis=ano-ma-lie')
2588 # u'Eis=a.no-ma-lie'
2589 # >>> scoretext(u'Ra-dio<phon')
2590 # u'Ra-di.o<phon'
2592 # Ausnahmen:
2594 # >>> scoretext(u'Ai-chin-ger'), scoretext(u'Ai-da')
2595 # (u'Ai-chin-ger', u'A.i-da')
2596 # >>> scoretext(u'Ma-rie'), scoretext(u'Li-nie')
2597 # (u'Ma-rie', u'Li-ni.e')
2598 # >>> scoretext(u'Ta-too'), scoretext(u'Zoo<lo-gie')
2599 # (u'Ta-too', u'Zo.o<lo-gie')
2600 # >>> scoretext(u'A-pnoe'), scoretext(u'O-boe')
2601 # (u'A-pnoe', u'O-bo.e')
2602 # >>> scoretext(u'Plaque'), scoretext(u'treue')
2603 # (u'Plaque', u'treu.e')
2604 # >>> scoretext(u'Fon-due=pfan-ne'), scoretext(u'Aue')
2605 # (u'Fon-due=pfan-ne', u'Au.e')
2606 # >>> scoretext(u'Ge-nie'), scoretext(u'Iphi-ge-nie')
2607 # (u'Ge-nie', u'I.phi-ge-nie')
2608 # >>> scoretext(u'Ago-nie'), scoretext(u'Be-go-nie')
2609 # (u'A.go-nie', u'Be-go-ni.e')
2610 # >>> scoretext(u'Kom-pa-nie'), scoretext(u'Kas-ta-nie'), scoretext(u'Ge-ra-nie')
2611 # (u'Kom-pa-nie', u'Kas-ta-ni.e', u'Ge-ra-ni.e')
2613 # ungelöst: Knie / Kni.e # pl.
2614 # ::
2616 def scoretext(word):
2617 """Mark up leading one-letter syllables."""
2618 # Führender Vokal, gefolgt von Silbenanfang
2619 # (optionaler Konsonant (auch ch/ck/ph/rh/sch/sh/th) + Vokal)
2620 for match in re.finditer(u'(^|[<=])([aeiouäöü])((.|ch|ck|ph|sch|th)?[aeiouäöü])',
2621 word, flags=re.IGNORECASE):
2622 # print match.groups()
2623 # Ausnahmen: Doppellaute, Diphtonge (außer A-i-da), Umlaute
2624 if (re.search(u'(aa|ae|ai|au|äu|ei|eu|oe|oo|ou|ue)',
2625 match.group(0), flags=re.IGNORECASE)
2626 and word != u'Ai-da' or word == u'io-ta' ):
2627 continue
2628 word = ''.join((word[:match.start()], match.expand(u'\\1\\2.\\3'),
2629 scoretext(word[match.end():])))
2630 break
2631 # zwei Vokale am Wortende
2632 for match in re.finditer(u'([aeiouäöü])([aeiouäöü])([<>=]|$)', word):
2633 # if re.search(u'(oo)', match.group(0)):
2634 # if 'ie' in match.group(0) and re.search(u'([a]-nie)', word, flags=re.IGNORECASE):
2635 # sys.stderr.write(word+' '+match.group(0)+'\n')
2636 # Ausnahmen: Doppellaute, Diphtonge, Umlaute (außer "Oboe")
2637 if (re.search(u'(aa|ae|ai|au|äu|ee|ei|eu|oe|oi|ou|ui)', match.group(0))
2638 and not word[:match.end()].endswith(u'waii') # ! Hawaii
2639 and not word[:match.end()].endswith(u'boe')): # ! Oboe
2640 continue
2641 # Ausnahmen mit Ausnahmen:
2642 # …oo außer zoo<, ...
2643 if 'oo' in match.group(0) and match.group(0) != 'oo<':
2644 continue
2645 # …ie außer "-(l)inie", Iphigenie, Kastanie, Geranie, Begonie
2646 if ('ie' in match.group(0)
2647 and not word[:match.end()].endswith(u'i-nie') # Linie,
2648 and not word[:match.end()].endswith(u'ta-nie') # Kastanie,
2649 and not word[:match.end()].endswith(u'ra-nie') # Geranie,
2650 and not word[:match.end()].endswith(u'e-go-nie') # Begonie != Agonie
2651 and not word == u'Iphi-ge-nie'):
2652 continue
2653 # …ue (Plaque, Re-vue, vogue) außer "-tue, -aue, ... -äue"
2654 if 'ue' in match.group(0) and re.search(u'[^aeät]ue([<>=]|$)',
2655 word[:match.end()], flags=re.IGNORECASE):
2656 continue
2658 word = ''.join((word[:match.start()], match.expand(u'\\1.\\2\\3'),
2659 scoretext(word[match.end():])))
2660 break
2661 return word
2664 # keine_einzelvokale()
2665 # --------------------
2667 # Traditionell enthalten die TeX-Trennmuster keine Trennstellen deren Abstand
2668 # zur Nachbartrennstelle einen Buchstaben beträgt. Dies ist eine *ästhetische*
2669 # Entscheidung um „Flatterbuchstaben“ zu vermeiden
2671 # Bei einvokalischen Silben im Wortinneren, nimm die zweite:
2673 # >>> from wortliste import keine_einzelvokale
2674 # >>> einzelne = (u'The-a-ter me-ri-di-.o-nal')
2675 # >>> for wort in einzelne.split():
2676 # ... print wort, '->', keine_einzelvokale(wort)
2677 # The-a-ter -> Thea-ter
2678 # me-ri-di-.o-nal -> me-ri-dio-nal
2680 # Allerdings nicht, wenn die erste Trennstelle unterdrückt ist:
2682 # >>> einzelne = (u'La-sal-le-a-.ner Athe-i-.sten')
2683 # >>> for wort in einzelne.split():
2684 # ... print wort, '->', keine_einzelvokale(wort)
2685 # La-sal-le-a-.ner -> La-sal-le-a-.ner
2686 # Athe-i-.sten -> Athe-i-.sten
2688 # ::
2690 def keine_einzelvokale(wort):
2691 """Drop hyphenation marker after single vowels."""
2692 return re.sub(u'-[.]*([aeiouyäöü])(?=-[^.])', u'\\1', wort)
2695 # unguenstig()
2696 # ------------
2698 # Entferne die explizit als ungünstig gekennzeichneten Trennstellen:
2700 # >>> from wortliste import unguenstig
2701 # >>> unguenstig(u'Text=il<..lu-stra-ti-.on')
2702 # u'Text=illu-stra-tion'
2703 # >>> unguenstig(u'Text=il<..lu-stra-ti-.on', level=2)
2704 # u'Text=illu-stra-ti-on'
2706 # ::
2708 def unguenstig(wort, level=1):
2709 """Drop bad hyphenations."""
2710 marker = r'\.' * level
2711 wort = re.sub(u'[-=<>]+%s+'%marker, u'', wort)
2712 return wort.replace(u'.', u'')
2714 # Hilfsfunktionen
2715 # ===============
2717 # join_word()
2718 # -----------
2720 # Trennzeichen entfernen::
2722 def join_word(wort, assert_complete=False):
2724 # Einfache Trennzeichen:
2726 # == ================================================================
2727 # \= Trennstelle an Wortfugen (Wort=fu-ge)
2728 # \< Trennstelle nach Präfix (Vor<sil-be)
2729 # \> Trennstelle vor Suffix (Freund>schaf-ten)
2730 # \- Nebentrennstelle (ge-hen)
2731 # \. unerwünschte/ungünstige Trennstelle (Ge<schoss=en<.er-gie)
2732 # == ================================================================
2734 # ::
2736 for marker in u'=<>-.':
2737 wort = wort.replace(marker, u'')
2739 # Spezielle Trennungen für die traditionelle Rechtschreibung
2740 # (siehe ../../dokumente/README.wortliste)::
2742 if '{' in wort or '}' in wort:
2743 wort = wort.replace(u'{ck/kk}', u'ck')
2744 wort = wort.replace(u'{ck/k', u'k')
2745 wort = wort.replace(u'k}', u'k')
2746 # Konsonanthäufungen an Wortfuge: '{xx/xxx}' -> 'xx':
2747 wort = re.sub(ur'\{(.)\1/\1\1\1\}', ur'\1\1', wort)
2748 # schon getrennt: ('{xx/xx' -> 'xx' und 'x}' -> 'x'):
2749 wort = re.sub(ur'\{(.)\1/\1\1$', ur'\1\1', wort)
2750 wort = re.sub(ur'^(.)\}', ur'\1', wort)
2752 # Trennstellen in doppeldeutigen Wörtern::
2754 if '[' in wort or ']' in wort:
2755 wort = re.sub(ur'\[(.*)/\1\]', ur'\1', wort)
2756 # schon getrennt:
2757 wort = re.sub(ur'\[([^/\[]+)$', ur'\1', wort)
2758 wort = re.sub(ur'^([^/\]]+)\]', ur'\1', wort)
2760 # Test auf verbliebene komplexe Trennstellen::
2762 if assert_complete:
2763 for spez in u'[{/}]':
2764 if spez in wort:
2765 raise AssertionError('Spezialtrennung %s, %s' %
2766 (wort.encode('utf8'), wort.encode('utf8')))
2768 return wort
2770 # zerlege()
2771 # ---------
2773 # Zerlege ein Wort mit Trennzeichen in eine Liste von Silben und eine Liste
2774 # von Trennzeichen)
2776 # >>> from wortliste import zerlege
2778 # >>> zerlege(u'Haupt=stel-le')
2779 # ([u'Haupt', u'stel', u'le'], [u'=', u'-'])
2780 # >>> zerlege(u'Ge<samt=be<triebs=rats==chef')
2781 # ([u'Ge', u'samt', u'be', u'triebs', u'rats', u'chef'], [u'<', u'=', u'<', u'=', u'=='])
2782 # >>> zerlege(u'an<stands>los')
2783 # ([u'an', u'stands', u'los'], [u'<', u'>'])
2784 # >>> zerlege(u'An<al.pha-bet')
2785 # ([u'An', u'al', u'pha', u'bet'], [u'<', u'.', u'-'])
2787 # ::
2789 def zerlege(wort):
2790 silben = re.split(u'[-·._<>=]+', wort)
2791 trennzeichen = re.split(u'[^-·._|<>=]+', wort)
2792 return silben, [tz for tz in trennzeichen if tz]
2794 # alternatives()
2795 # --------------
2797 # Gib einen String mit Trennmarkierung für abweichende Trennungen in
2798 # mehrdeutigen Wörtern zurück:
2800 # >>> from wortliste import alternatives
2801 # >>> alternatives(u'Mas-se', u'Ma-sse')
2802 # u'Ma[s-/-s]se'
2803 # >>> alternatives(u'Ma-sse', u'Mas-se')
2804 # u'Ma[-s/s-]se'
2806 # ::
2808 def alternatives(wort1, wort2):
2809 # convert to lists
2810 wort1 = [c for c in wort1]
2811 wort2 = [c for c in wort2]
2812 pre = []
2813 post = []
2814 for c1, c2 in zip(wort1, wort2):
2815 if c1 != c2:
2816 break
2817 pre += c1
2818 for c1, c2 in zip(wort1.__reversed__(), wort2.__reversed__()):
2819 if c1 != c2:
2820 break
2821 post += c1
2822 post.reverse()
2823 return u''.join(pre + [u'['] + wort1[len(pre):-len(post)] + [u'/']
2824 + wort2[len(pre):-len(post)] + [u']'] + post)
2826 # TransferError
2827 # -------------
2829 # Fehler beim Übertragen von Trennstellen mit uebertrage()_::
2831 class TransferError(ValueError):
2832 def __init__(self, wort1, wort2):
2833 msg = u'Inkompatibel: %s %s' % (wort1, wort2)
2834 ValueError.__init__(self, msg.encode('utf8'))
2836 def __unicode__(self):
2837 return str(self).decode('utf8')
2840 # uebertrage()
2841 # ------------
2843 # Übertrage die Trennzeichen von `wort1` auf `wort2`:
2845 # >>> from wortliste import uebertrage, TransferError
2847 # >>> uebertrage(u'Haupt=stel-le', u'Haupt·stel·le')
2848 # u'Haupt=stel-le'
2850 # Auch teilweise Übertragung, von "kategorisiert" nach "unkategorisiert":
2852 # >>> print uebertrage(u'Haupt=stel-le', u'Haupt=stel·le')
2853 # Haupt=stel-le
2855 # >>> print uebertrage(u'Haupt·stel-le', u'Haupt=stel·le')
2856 # Haupt=stel-le
2858 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler')
2859 # Aus<stel-ler
2861 # >>> print uebertrage(u'Freund>schaf·ten', u'Freund-schaf-ten')
2862 # Freund>schaf-ten
2864 # Übertragung doppelter Marker:
2866 # >>> print uebertrage(u'ver<<aus<ga-be', u'ver<aus<ga-be')
2867 # ver<<aus<ga-be
2869 # >>> print uebertrage(u'freund>lich>>keit', u'freund>lich>keit')
2870 # freund>lich>>keit
2872 # >>> print uebertrage(u'Amts==haupt=stel-le', u'Amts=haupt=stel-le')
2873 # Amts==haupt=stel-le
2875 # Kein Überschreiben doppelter Marker:
2876 # >>> print uebertrage(u'ver<aus<ga-be', u'ver<<aus<ga-be')
2877 # ver<<aus<ga-be
2879 # >>> print uebertrage(u'Amts=haupt=stel-le', u'Amts==haupt=stel·le')
2880 # Amts==haupt=stel-le
2882 # Erhalt des Markers für ungünstige Stellen:
2883 # >>> print uebertrage(u'An·al.pha·bet', u'An<al.pha-bet')
2884 # An<al.pha-bet
2886 # Keine Übertragung, wenn die Zahl oder Position der Trennstellen
2887 # unterschiedlich ist oder bei unterschiedlichen Wörtern:
2889 # >>> try:
2890 # ... uebertrage(u'Ha-upt=stel-le', u'Haupt=stel·le')
2891 # ... uebertrage(u'Haupt=ste-lle', u'Haupt=stel·le')
2892 # ... uebertrage(u'Waupt=stel-le', u'Haupt=stel·le')
2893 # ... except TransferError:
2894 # ... pass
2896 # Übertragung auch bei unterschiedlicher Schreibung oder Position der
2897 # Trennstellen mit `strict=False` (für Abgleich zwischen Sprachvarianten):
2899 # >>> uebertrage(u'er-ster', u'ers·ter', strict=False)
2900 # u'ers-ter'
2901 # >>> uebertrage(u'Fluß=bett', u'Fluss·bett', strict=False)
2902 # u'Fluss=bett'
2903 # >>> uebertrage(u'ab>bei-ßen', u'ab>beis·sen', strict=False)
2904 # u'ab>beis-sen'
2905 # >>> print uebertrage(u'Aus<tausch=dien-stes', u'Aus-tausch=diens-tes', False)
2906 # Aus<tausch=diens-tes
2908 # Auch mit `strict=False` muß die Zahl der Trennstellen übereinstimmen
2909 # (Ausnahmen siehe unten):
2911 # >>> try:
2912 # ... uebertrage(u'Ha-upt=ste-lle', u'Haupt=stel·le', strict=False)
2913 # ... except TransferError:
2914 # ... pass
2916 # Akzeptiere unterschiedliche Anzahl von Trennungen bei st und ck nach
2917 # Selbstlaut:
2919 # >>> uebertrage(u'acht=ecki-ge', u'acht·e{ck/k·k}i·ge', strict=False)
2920 # u'acht=e{ck/k-k}i-ge'
2921 # >>> uebertrage(u'As-to-ria', u'Asto·ria', strict=False)
2922 # u'Asto-ria'
2923 # >>> uebertrage(u'Asto-ria', u'As·to·ria', strict=False)
2924 # u'As-to-ria'
2925 # >>> uebertrage(u'So-fa=ecke', u'So·fa=e{ck/k-k}e', strict=False)
2926 # u'So-fa=e{ck/k-k}e'
2927 # >>> uebertrage(u'Drei=ecks=ecke', u'Drei=ecks==e{ck/k-k}e', strict=False)
2928 # u'Drei=ecks==e{ck/k-k}e'
2930 # Mit ``upgrade=False`` werden nur unspezifische Trennstellen überschrieben:
2932 # >>> print uebertrage(u'an=stel-le', u'an<stel·le', upgrade=False)
2933 # an<stel-le
2935 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler', upgrade=False)
2936 # Aus-stel-ler
2938 # >>> print uebertrage(u'Aus-stel-ler', u'Aus<stel-ler', upgrade=False)
2939 # Aus<stel-ler
2941 # >>> print uebertrage(u'vor<an<<stel-le', u'vor-an<stel·le', upgrade=False)
2942 # vor-an<stel-le
2944 # ::
2946 selbstlaute = u'aeiouäöüAEIOUÄÖÜ'
2948 def uebertrage(wort1, wort2, strict=True, upgrade=True):
2950 silben1, trennzeichen1 = zerlege(wort1)
2951 silben2, trennzeichen2 = zerlege(wort2)
2952 # Prüfe strikte Übereinstimmung:
2953 if silben1 != silben2 and strict:
2954 if u'<' in trennzeichen1 or u'·' in trennzeichen2:
2955 raise TransferError(wort1, wort2)
2956 else:
2957 return wort2
2958 # Prüfe ungefähre Übereinstimmung:
2959 if len(trennzeichen1) != len(trennzeichen2):
2960 # Selbstlaut + st oder ck?
2961 for s in selbstlaute:
2962 if (wort2.find(s+u'{ck/k·k}') != -1 or
2963 wort2.find(s+u'{ck/k-k}') != -1):
2964 wort1 = re.sub(u'%sck([%s])'%(s,selbstlaute),
2965 ur'%s-ck\1'%s, wort1)
2966 silben1, trennzeichen1 = zerlege(wort1)
2967 if wort2.find(s+u's·t') != -1:
2968 wort1 = wort1.replace(s+u'st', s+u's-t')
2969 silben1, trennzeichen1 = zerlege(wort1)
2970 elif wort1.find(s+u's-t') != -1:
2971 wort1 = wort1.replace(s+u's-t', s+u'st')
2972 silben1, trennzeichen1 = zerlege(wort1)
2973 # print u'retry:', silben1, trennzeichen1
2974 # immer noch ungleiche Zahl an Trennstellen?
2975 if len(trennzeichen1) != len(trennzeichen2):
2976 raise TransferError(wort1, wort2)
2978 # Baue wort3 aus silben2 und spezifischeren Trennzeichen:
2979 wort3 = silben2.pop(0)
2980 for t1,t2 in zip(trennzeichen1, trennzeichen2):
2981 if ((t2 == u'·' and t1 != u'.') # unspezifisch
2982 or upgrade and
2983 ((t2 in (u'-', u'<') and t1 in (u'<', u'<<', u'<=')) # Praefixe
2984 or (t2 in (u'-', u'>') and t1 in (u'>', u'>>', u'=>')) # Suffixe
2985 or (t2 in (u'-', u'=') and t1 in (u'=', u'==', u'===')) # W-fugen
2988 wort3 += t1
2989 elif t2 == u'.' and t1 not in u'·.':
2990 wort3 += t1 + t2
2991 else:
2992 wort3 += t2
2993 wort3 += silben2.pop(0)
2994 return wort3
2997 # Übertrag kategorisierter Trennstellen zwischen den Feldern aller Einträge
2998 # in `wortliste`::
3000 def sprachabgleich(entry, vorbildentry=None):
3002 if len(entry) <= 2:
3003 return # allgemeine Schreibung
3005 mit_affix = None # < oder >
3006 kategorisiert = None # kein ·
3007 unkategorisiert = None # mindestens ein ·
3008 gewichtet = None # == oder <= oder =>
3009 for field in entry[1:]:
3010 if not field: # -2-, -3-, ...
3011 continue
3012 if u'{' in field and u'[' in field: # Bi-ber==be[t=t/{tt/tt=t}]uch
3013 continue # zu komplex
3014 if u'·' in field:
3015 unkategorisiert = field
3016 elif u'<' in field or u'>' in field:
3017 mit_affix = field
3018 else:
3019 kategorisiert = field
3020 if u'==' in field or u'<=' in field or u'=>' in field:
3021 gewichtet = field
3022 if vorbildentry:
3023 for field in vorbildentry[1:]:
3024 if not field: # -2-, -3-, ...
3025 continue
3026 if u'{' in field and u'[' in field: # Bi-ber==be[t=t/{tt/tt=t}]uch
3027 continue # zu komplex
3028 if not mit_affix and u'<' in field or u'>' in field :
3029 mit_affix = field
3030 elif not kategorisiert and unkategorisiert and u'·' not in field:
3031 kategorisiert = field
3032 if not gewichtet and u'==' in field or u'<=' in field or u'=>' in field:
3033 gewichtet = field
3034 # print 've:', mit_affix, kategorisiert, unkategorisiert
3035 if mit_affix and (kategorisiert or unkategorisiert or gewichtet):
3036 for i in range(1,len(entry)):
3037 if not entry[i]: # -2-, -3-, ...
3038 continue
3039 if u'<' not in entry[i] or u'·' in entry[i]:
3040 try:
3041 entry[i] = uebertrage(mit_affix, entry[i], strict=False)
3042 except TransferError, e:
3043 if not '/' in entry[i]:
3044 print u'Sprachabgleich:', unicode(e)
3045 # print mit_affix+u':', unicode(entry)
3046 elif kategorisiert and unkategorisiert:
3047 for i in range(1,len(entry)):
3048 if u'·' in entry[i]:
3049 try:
3050 entry[i] = uebertrage(kategorisiert, entry[i], strict=False)
3051 except TransferError, e:
3052 print u'Sprachabgleich:', unicode(e)
3053 # print kategorisiert, unicode(entry)
3054 elif gewichtet:
3055 for i in range(1,len(entry)):
3056 if u'=' in entry[i] and not (
3057 u'{' in entry[i] and u'[' in entry[i]):
3058 try:
3059 entry[i] = uebertrage(gewichtet, entry[i], strict=False)
3060 except TransferError, e:
3061 print u'Sprachabgleich:', unicode(e)
3065 # Großschreibung in Kleinschreibung wandeln und umgekehrt
3067 # Diese Version funktioniert auch für Wörter mit Trennzeichen (während
3068 # str.title() nach jedem Trennzeichen wieder groß anfängt)
3070 # >>> from wortliste import toggle_case
3071 # >>> toggle_case(u'Ha-se')
3072 # u'ha-se'
3073 # >>> toggle_case(u'arm')
3074 # u'Arm'
3075 # >>> toggle_case(u'frei=bier')
3076 # u'Frei=bier'
3077 # >>> toggle_case(u'L}a-ger')
3078 # u'l}a-ger'
3080 # Keine Änderung bei Wörtern mit Großbuchstaben im Inneren:
3082 # >>> toggle_case(u'USA')
3083 # u'USA'
3084 # >>> toggle_case(u'iRFD')
3085 # u'iRFD'
3087 # >>> toggle_case(u'gri[f-f/{ff/ff')
3088 # u'Gri[f-f/{ff/ff'
3089 # >>> toggle_case(u'Gri[f-f/{ff/ff')
3090 # u'gri[f-f/{ff/ff'
3092 # ::
3094 def toggle_case(wort):
3095 try:
3096 key = join_word(wort, assert_complete=True)
3097 except AssertionError:
3098 key = wort[0]
3099 if key.istitle():
3100 return wort.lower()
3101 elif key.islower():
3102 return wort[0].upper() + wort[1:]
3103 else:
3104 return wort
3106 # Sortierschlüssel
3107 # ================
3109 # Duden-Sortierung für die Wortliste
3111 # >>> from wortliste import sortkey_duden
3112 # >>> sortkey_duden([u"Abflußröhren"])
3113 # u'abflussrohren a*bflu*szroehren'
3114 # >>> sortkey_duden([u"Abflußrohren"])
3115 # u'abflussrohren a*bflu*szro*hren'
3116 # >>> sortkey_duden([u"Abflussrohren"])
3117 # u'abflussrohren'
3119 # >>> s = sorted([[u"Abflußröhren"], [u"Abflußrohren"], [u"Abflussrohren"]],
3120 # ... key=sortkey_duden)
3121 # >>> print ', '.join(e[0] for e in s)
3122 # Abflussrohren, Abflußrohren, Abflußröhren
3124 # Umschreibung
3126 # Ligaturen auflösen und andere "normalisierunde" Ersetzungen für den
3127 # (Haupt-)Sortierschlüssel (Akzente werden über ``unicodedata.normalize``
3128 # entfernt)::
3130 umschrift_skey = {
3131 ord(u'æ'): u'ae',
3132 ord(u'œ'): u'oe',
3133 ord(u'ſ'): u's',
3136 # "Zweitschlüssel" zur Unterscheidung von Umlauten/SZ und Basisbuchstaben::
3138 umschrift_subkey = {
3139 ord(u'a'): u'a*',
3140 ord(u'å'): u'aa',
3141 ord(u'ä'): u'ae',
3142 ord(u'o'): u'o*',
3143 ord(u'ö'): u'oe',
3144 ord(u'ø'): u'oe',
3145 ord(u'u'): u'u*',
3146 ord(u'ü'): u'ue',
3147 ord(u'ß'): u'sz',
3151 # sortkey_duden()
3152 # ---------------
3154 # Schlüssel für die alphabetische Sortierung gemäß Duden-Regeln.
3156 # Argument ist ein Eintrag im WordEntry oder ShortEntry Format oder
3157 # ein (Unicode) String:
3159 # >>> print sortkey_duden(weste) # WordEntry
3160 # weste
3161 # >>> print sortkey_duden(dst) # ShortEntry
3162 # dienste
3164 # >>> print sortkey_duden(u'Stra-ße')
3165 # strasse stra*sze
3166 # >>> print sortkey_duden([u'Büh-ne'])
3167 # buhne buehne
3168 # >>> print sortkey_duden(u'Weiß=flog;Weiß=flog;-3-;-4-;-5-')
3169 # weissflog weiszflo*g
3170 # >>> print sortkey_duden(u'-1-;Meß=sen-der;-3-;-4-;-5-')
3171 # messsender meszsender
3172 # >>> print sortkey_duden(u'As-sen # (geogr. und Eigen-) Name\n')
3173 # assen
3174 # >>> print sortkey_duden(u'aßen;aßen;-3-;-4-;-5-\n')
3175 # assen a*szen
3176 # >>> print sortkey_duden(u'äßen\n')
3177 # assen aeszen
3179 # ::
3181 def sortkey_duden(entry):
3183 # ggf. ungetrenntes Wort extrahieren oder generieren::
3185 if isinstance(entry, list):
3186 try:
3187 key = entry.key()
3188 except AttributeError:
3189 key = entry[0]
3190 if len(entry) == 1: # ein Muster pro Zeile, siehe z.B. pre-1901
3191 key = join_word(key)
3192 else:
3193 match = re.search(u'^[-0-9;]*([^;\s]+)', entry) # erstes volles Feld
3194 if match:
3195 key = match.group(1)
3196 else:
3197 key = u''
3198 key = join_word(key)
3199 key = re.sub(u'[0-9;]', ur'', key)
3201 # Großschreibung ignorieren:
3203 # Der Duden sortiert Wörter, die sich nur in der Großschreibung unterscheiden
3204 # "klein vor groß" (ASCII sortiert "groß vor klein"). In der
3205 # `Trennmuster-Wortliste` kommen Wörter nur mit der häufiger anzutreffenden
3206 # Großschreibung vor, denn der TeX-Trennalgorithmus ignoriert Großschreibung.
3207 # ::
3209 key = key.lower()
3211 # Ersetzungen:
3213 # ß -> ss ::
3215 skey = key.replace(u'ß', u'ss')
3217 # Restliche Akzente weglassen: Wandeln in Darstellung von Buchstaben mit
3218 # Akzent als "Grundzeichen + kombinierender Akzent". Anschließend alle
3219 # nicht-ASCII-Zeichen ignorieren::
3221 skey = skey.translate(umschrift_skey)
3222 skey = unicodedata.normalize('NFKD', skey)
3223 skey = unicode(skey.encode('ascii', 'ignore'))
3225 # "Zweitschlüssel" für das eindeutige Einsortieren von Wörtern mit
3226 # gleichem Schlüssel (Masse/Maße, waren/wären, ...):
3228 # * "*" nach aou für die Unterscheidung Grund-/Umlaut
3229 # * ß->sz
3231 # ::
3233 if key != skey:
3234 subkey = key.translate(umschrift_subkey)
3235 skey = u'%s %s' % (skey,subkey)
3237 # Gib den Sortierschlüssel zurück::
3239 return skey
3243 # udiff()
3244 # -------
3246 # Vergleiche zwei Sequenzen von `WordEntries` (genauer: alle Objekte, die
3247 # sich sinnvoll zu Unicode wandeln lassen).
3249 # Beispiel:
3251 # >>> from wortliste import udiff
3252 # >>> print udiff([abbeissen, aalbestand], [abbeissen,dresz], 'alt', 'neu')
3253 # --- alt
3254 # +++ neu
3255 # @@ -1,2 +1,2 @@
3256 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
3257 # -Aalbestand;Aal=be<stand # Test
3258 # +Dreß;-2-;Dreß;-4-
3259 # <BLANKLINE>
3260 # >>> udiff([abbeissen, aalbestand], [abbeissen, aalbestand], 'alt', 'neu')
3261 # u''
3263 # ::
3265 def udiff(a, b, fromfile='', tofile='',
3266 fromfiledate='', tofiledate='', n=1, encoding='utf8'):
3268 a = [unicode(entry).rstrip().encode(encoding) for entry in a]
3269 b = [unicode(entry).rstrip().encode(encoding) for entry in b]
3271 diff = '\n'.join(difflib.unified_diff(a, b, fromfile, tofile,
3272 fromfiledate, tofiledate, n, lineterm=''))
3273 if diff:
3274 diff += '\n'
3275 return diff.decode(encoding)
3278 # Sprachtags
3279 # ==========
3281 # Normalisierung und Expansion von Sprachtags nach [BCP47]_
3283 # >>> from wortliste import normalize_language_tag
3284 # >>> normalize_language_tag('de_AT-1901')
3285 # ['de-AT-1901', 'de-AT', 'de-1901', 'de']
3287 # >>> normalize_language_tag('de') # Deutsch, allgemeingültig
3288 # ['de']
3289 # >>> normalize_language_tag('de_1901') # traditionell (Reform 1901)
3290 # ['de-1901', 'de']
3291 # >>> normalize_language_tag('de_1996') # reformiert (Reform 1996)
3292 # ['de-1996', 'de']
3293 # >>> normalize_language_tag('de_CH') # ohne ß (Schweiz oder versal)
3294 # ['de-CH', 'de']
3295 # >>> normalize_language_tag('de-x-versal') # versal
3296 # ['de-x-versal', 'de']
3297 # >>> normalize_language_tag('de-1901-x-versal') # versal
3298 # ['de-1901-x-versal', 'de-1901', 'de-x-versal', 'de']
3299 # >>> normalize_language_tag('de_CH-1996') # Schweiz traditionell (süssauer)
3300 # ['de-CH-1996', 'de-CH', 'de-1996', 'de']
3302 # 'de': 1, # Deutsch, allgemeingültig
3303 # 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
3304 # 'de-1996': 3, # reformierte Reformschreibung (1996)
3305 # 'de-x-versal': 4, # ohne ß (Schweiz oder versal) allgemein
3306 # # 'de-CH': 4, # Alias
3307 # 'de-1901-x-versal': 5, # ohne ß (Schweiz oder versal) "traditionell"
3308 # 'de-1996-x-versal': 6, # ohne ß (Schweiz oder versal) "reformiert"
3309 # # 'de-CH-1996': 6, # Alias
3310 # 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
3313 # ::
3315 def normalize_language_tag(tag):
3316 """Return a list of normalized combinations for a `BCP 47` language tag.
3318 # normalize:
3319 tag = tag.replace('_','-')
3320 # split (except singletons, which mark the following tag as non-standard):
3321 tag = re.sub(r'-([a-zA-Z0-9])-', r'-\1_', tag)
3322 taglist = []
3323 subtags = [subtag.replace('_', '-') for subtag in tag.split('-')]
3324 base_tag = [subtags.pop(0)]
3325 # find all combinations of subtags
3326 for n in range(len(subtags), 0, -1):
3327 # for tags in unique_combinations(subtags, n):
3328 for tags in itertools.combinations(subtags, n):
3329 taglist.append('-'.join(base_tag+list(tags)))
3330 taglist += base_tag
3331 return taglist
3335 # Tests
3336 # =====
3338 # Teste Übereinstimmung des ungetrennten Wortes in Feld 1 mit den
3339 # Trennmustern nach Entfernen der Trennmarker. Schreibe Inkonsistenzen auf die
3340 # Standardausgabe.
3342 # Das Argument ist ein Iterator über die Einträge (Klasse `WordEntry`). ::
3344 def test_keys(wortliste):
3345 print u"Teste Schlüssel-Trennmuster-Übereinstimmung:"
3346 is_OK = True
3347 for entry in wortliste:
3348 if isinstance(entry, ShortEntry):
3349 print u"Wortliste im Kurzformat: überspringe Schlüssel-Test."
3350 return
3351 # Test der Übereinstimmung ungetrenntes/getrenntes Wort
3352 # für alle Felder:
3353 key = entry.key()
3354 for wort in entry[1:]:
3355 if not wort: # leere Felder
3356 continue
3357 if key != join_word(wort):
3358 is_OK = False
3359 print u"\nkey '%s' != join_word('%s')" % (key, wort),
3360 if key.lower() == join_word(wort).lower():
3361 print(u" Abgleich der Großschreibung mit"
3362 u"`prepare-patch.py grossabgleich`."),
3363 if is_OK:
3364 print u"OK"
3367 # Finde Doppeleinträge (teste, ob jeder Schlüssel nur einmal vorkommt).
3368 # Schreibe Inkonsistenzen auf die Standardausgabe.
3370 # Das Argument ist ein Iterator über die Einträge (Klasse `WordEntry`). ::
3372 def test_uniqueness(wortliste):
3374 doppelte = 0
3375 words = {}
3376 for entry in wortliste:
3377 key = entry.key()
3378 if key in words:
3379 doppelte += 1
3380 print "da ", unicode(words[key])
3381 print "neu", unicode(entry)
3382 words[key] = entry
3383 print u"%d Doppeleinträge." % doppelte
3385 # Teste die Wandlung einer Zeile im "wortliste"-Format in eine
3386 # ``WordEntry``-Instanz und zurück::
3388 def test_str_entry_str_conversion(wordfile):
3389 OK = 0
3390 for line in file(wordfile.name):
3391 line = line.rstrip().decode(wordfile.encoding)
3392 entry = WordEntry(line)
3393 if line == unicode(entry):
3394 OK +=1
3395 else:
3396 print u'-', line,
3397 print u'+', unicode(entry)
3399 print OK, u"Einträge rekonstruiert"
3402 # Teste Vervollständigung und Zusammenfassung von Einträgen::
3404 def test_completion_pruning(entries):
3405 reko = []
3406 for entry in entries:
3407 new = copy.copy(entry)
3408 new.complete()
3409 new.prune()
3410 reko.append(new)
3411 patch = udiff(entries, reko, 'wortliste', 'neu')
3412 if patch:
3413 print patch
3414 else:
3415 print u"alle Einträge rekonstruiert"
3419 # Aufruf von der Kommandozeile
3420 # ============================
3422 # ::
3424 if __name__ == '__main__':
3425 import sys
3427 # sys.stdout mit UTF8 encoding (wie in Python 3)
3428 sys.stdout = codecs.getwriter('UTF-8')(sys.stdout)
3429 # sys.stderr = codecs.getwriter('UTF-8')(sys.stderr)
3431 print u"Test der Werkzeuge und inneren Konsistenz der Wortliste"
3433 # Ein WordFile Dateiobjekt::
3435 try:
3436 wordfile = WordFile(sys.argv[1], format='auto')
3437 except IndexError:
3438 wordfile = WordFile('../../../wortliste')
3439 # wordfile = WordFile('../../../wlst', format='auto') # wortliste im Kurzformat
3440 # wordfile = WordFile('neu.todo')
3441 # wordfile = WordFile('neu-kurz.todo', format='f5')
3442 # wordfile = WordFile('korrektur.todo', format='auto')
3444 # Format bestimmen::
3446 print "Datei: '%s'" % wordfile.name, u"Format:", wordfile.format
3448 # Liste der Datenfelder (die Klasseninstanz als Argument für `list` liefert
3449 # den Iterator über die Felder, `list` macht daraus eine Liste)::
3451 wordlist = list(wordfile)
3452 print len(wordlist), u"Zeilen"
3454 # Ein Wörterbuch (dict Instanz)::
3456 wordfile.seek(0) # Pointer zurücksetzen
3457 words = wordfile.asdict()
3458 print len(words), u"Wörterbucheinträge"
3460 # Test auf Doppeleinträge::
3462 if len(words) != len(wordlist):
3463 test_uniqueness(wordlist)
3466 # Teste Schlüssel-Trennmuster-Übereinstimmung::
3468 if isinstance(wordlist[0], WordEntry):
3469 test_keys(wordlist)
3471 # Teste Eintrags-Konsistenz im Kurzformat::
3473 if isinstance(wordlist[0], ShortEntry):
3474 doppelte = 0
3475 langs = ("de", "de-1901", "de-CH", "de-1901-x-versal", "de-CH-1901")
3476 for lang in langs:
3477 words[lang] = {}
3479 for entry in wordlist:
3480 entry.complete()
3481 for lang in langs:
3482 word = entry.get(lang)
3483 key = join_word(word)
3484 if word and (key in words[lang]):
3485 oldword = words[lang][key].get(lang)
3486 if oldword and (word != oldword):
3487 doppelte += 1
3488 print "%-16s" % lang, oldword, '!=', word
3489 print " "*18, unicode(words[lang][key].pruned())
3490 print " "*18, unicode(entry.pruned())
3491 words[lang][key] = entry
3492 print u"%d Doppeleinträge." % doppelte
3495 # Teste Komplettieren/Zusammenfassen der Einträge::
3497 # test_completion_pruning(wordlist)
3499 # Sprachauswahl::
3501 # Sprachtags:
3503 # sprache = 'de-1901' # traditionell
3504 sprache = 'de-1996' # Reformschreibung
3505 # sprache = 'de-x-versal' # ohne ß (Schweiz oder versal) allgemein
3506 # sprache = 'de-1901-x-versal' # ohne ß (Schweiz oder versal) "traditionell"
3507 # sprache = 'de-1996-x-versal' # ohne ß (Schweiz oder versal) "reformiert"
3508 # sprache = 'de-CH-1901' # ohne ß (Schweiz) "traditionell" ("süssauer")
3510 # worte = [entry.get(sprache) for entry in wordlist]
3511 # worte = [wort for wort in worte if wort]
3512 # print len(worte), u"Einträge für Sprachvariante", sprache
3514 # Zeilenrekonstruktion::
3516 # test_str_entry_str_conversion(wordfile)
3519 # Quellen
3520 # =======
3522 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
3523 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
3525 # .. _aktuelle Rechtschreibung:
3527 # .. [Rechtschreibregeln] Rat für deutsche Rechtschreibung,
3528 # `Deutsche Rechtschreibung – Regeln und Wörterverzeichnis`,
3529 # http://www.rechtschreibrat.com/regeln-und-woerterverzeichnis/
3531 # .. _traditionelle Rechtschreibung:
3533 # .. [Duden1991] Wissenschaftlicher Rat der Dudenredaktion (Editoren),
3534 # `Duden: Rechtschreibung der deutschen Sprache`,
3535 # Dudenverlag Mannheim, 1991.
3537 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
3538 # ../../../dokumente/README.wortliste
3540 # .. _wortliste: ../../../wortliste