Neuer Trennstil "dehyphen".
[wortliste.git] / skripte / python / edit_tools / wortliste.py
blob4d0d4fb3da151389656cc43560b12bb903cbf182
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 auswählen::
1830 wort = fremdwortsilben(wort)
1831 wort = verblasst(wort)
1833 # st-Trennung
1834 # """""""""""
1836 # K75: Trenne nie st.
1838 # Ersetze 's-t' mit '-st':
1840 # >>> ableitung1901(u'Diens-te')
1841 # u'Dien-ste'
1842 # >>> print ableitung1901(u'wuss-te', keep_key=True)
1843 # wuss-te
1845 # Aber Trennung von s-theta erlaubt (nach K74):
1847 # >>> print ableitung1901(u'Äs-thet')
1848 # Äs-thet
1850 # ::
1852 wort = re.sub(u'(?<!s)s-t(?!h)', u'-st', wort)
1855 # ck-Trennung
1856 # """""""""""
1858 # K76: Trenne 'ck' als 'k-k'.
1860 # Ersetze '-ck' mit '{ck/k-k}':
1862 # >>> ableitung1901(u'Ha-cke')
1863 # u'Ha{ck/k-k}e'
1864 # >>> ableitung1901(u'Acker')
1865 # u'A{ck/k-k}er'
1866 # >>> ableitung1901(u'Got-tes=acker')
1867 # u'Got-tes=a{ck/k-k}er'
1868 # >>> ableitung1901(u'Aal=beck')
1869 # u'Aal=beck'
1870 # >>> ableitung1901(u'be<spick-te')
1871 # u'be<spick-te'
1873 # ::
1875 wort = wort.replace(u'-ck', u'{ck/k-k}')
1876 wort = re.sub(u'(?<=[AEIOUYÄÖÜaeiouyäöü])ck(?=[aeiouyäöü])',
1877 u'{ck/k-k}', wort)
1879 # Keine Trennung nach nur einem Buchstaben am Wortanfang:
1881 # >>> ableitung1901(u'Es-te')
1882 # u'Este'
1883 # >>> print ableitung1901(u'Nord=os-ten')
1884 # Nord=osten
1885 # >>> print ableitung1901(u'Po-ly<es-ter')
1886 # Po-ly<ester
1888 # Im Wort sind Einvokalsilben erlaubt (aber nicht immer günstig):
1890 # >>> print ableitung1901(u'the-is-tisch')
1891 # the-i-stisch
1893 # ::
1895 wort = re.sub(u'((?<=[=<].)|(?<=^.))-', ur'', wort)
1897 if keep_key:
1898 return wort
1900 # Rechtschreibänderungen
1901 # ~~~~~~~~~~~~~~~~~~~~~~
1903 # Diese Regeln ändern die Schreibung des ungetrennten Worts (und somit den
1904 # Schlüssel im Langformat der Wortliste).
1906 # Schluss-ss
1907 # """"""""""
1909 # K38: Kein "ss" und "sst" am Silbenende (ungetrenntes "ss" nur in Ausnahmen)
1910 # (Andererseit ist 'ßt' und Schluss-ß auch in de-1996 möglich (langer Vokal).)
1912 # Ersetze ungetrenntes 'ss' mit 'ß':
1914 # >>> print ableitung1901(u'passt')
1915 # paßt
1916 # >>> print ableitung1901(u'Hass')
1917 # Haß
1918 # >>> print ableitung1901(u'Fass=brau-se')
1919 # Faß=brau-se
1920 # >>> print ableitung1901(u'wuss-te')
1921 # wuß-te
1923 # ß steht für inlautendes ss, wenn ein 'e' ausfällt (und der Ausfall nicht
1924 # durch Apostroph angedeutet wird)
1926 # >>> print ableitung1901(u'wäss-rig')
1927 # wäß-rig
1928 # >>> print ableitung1901(u'an<ge<mess-ner')
1929 # an<ge<meß-ner
1930 # >>> print ableitung1901(u'duss-lig')
1931 # duß-lig
1932 # >>> print ableitung1901(u'bissl')
1933 # bißl
1935 # Keine Wandlung zu "ß":
1936 # getrenntes Doppel-s (s-s)
1938 # >>> print ableitung1901(u'Was-ser')
1939 # Was-ser
1941 # Vokal folgt (Fremdwörter):
1942 # >>> print ableitung1901(u'Com-tesse')
1943 # Com-tesse
1945 # Großbuchstabe folgt
1947 # >>> print ableitung1901(u'WissZeitVG') # Abkürzung
1948 # WissZeitVG
1950 # Drei oder mehr 's' = Lautmalerei → erhalten:
1952 # >>> print ableitung1901(u'pssst')
1953 # pssst
1955 # ::
1957 wort = re.sub(u'(?<=[^s])ss(?=[^aeiouyäöüA-Zs]|$)', ur'ß', wort)
1959 # Unterdrückung der Trennstelle nach "…ß=er" und "…ß=en" nicht nötig:
1961 # >>> print ableitung1901(u'hass=er<.füllt')
1962 # haß=er<füllt
1964 # ::
1966 wort = re.sub(ur'ß(=+)e([rn][<-]+)\.', ur'ß\1e\2', wort)
1968 # Dreikonsonantenregel
1969 # """"""""""""""""""""
1971 # K78: Zusammensetzungen, bei denen von drei zusammenstoßenden gleichen
1972 # Konsonanten einer entfällt (K15), schreibt man bei Silbentrennung wieder
1973 # mit allen drei Konsonanten.
1975 # Ersetze 'xx=x' xit '{xx/xx=x}' (für alle Konsonanten vor Selbstlaut)
1977 # >>> print ableitung1901(u'Kipp=pflug')
1978 # Kipp=pflug
1979 # >>> print ableitung1901(u'Kipp=punkt')
1980 # Ki{pp/pp=p}unkt
1981 # >>> print ableitung1901(u'Ab<fall=la-ger')
1982 # Ab<fa{ll/ll=l}a-.ger
1983 # >>> print ableitung1901(u'All<lie-be')
1984 # A{ll/ll<l}ie-be
1985 # >>> print ableitung1901(u'hell>licht')
1986 # he{ll/ll>l}icht
1987 # >>> print ableitung1901(u'Pro<gramm==maß=nah-me')
1988 # Pro<gra{mm/mm==m}aß=nah-me
1990 # ::
1992 wort = re.sub(ur'([bfglmnprt])\1([<=>]+)\1(?=[aeiouyäöü])',
1993 ur'{\1\1/\1\1\2\1}', wort)
1995 # Unterdrücken der Trennung nach nur einem Buchstaben::
1997 wort = re.sub(ur'(?<=[=>].}[aeiouyäöü])([-<])\.?', ur'\1.', wort)
2000 return wort
2002 # Tests:
2004 # Ein-Vokal-Silben auch schon 1901 erlaubt:
2006 # >>> print ableitung1901(u'ver<knäu-e-le')
2007 # ver<knäu-e-le
2010 # versalschreibung()
2011 # ------------------
2012 # Ersetze 'ß' mit 'ss', trenne je nach Sprachvarietät `lang`:
2014 # >>> from wortliste import versalschreibung
2015 # >>> print versalschreibung(u'paßt')
2016 # passt
2017 # >>> print versalschreibung(u'Dar-ßer')
2018 # Dars-ser
2019 # >>> print versalschreibung(u'bü-ßen', 'de-CH')
2020 # büs-sen
2021 # >>> print versalschreibung(u'bü-ßen', 'de-1996-x-versal')
2022 # büs-sen
2023 # >>> print versalschreibung(u'bü-ßen', 'de-CH-1901')
2024 # büs-sen
2025 # >>> print versalschreibung(u'äßen', 'de-CH-1901')
2026 # äs-sen
2027 # >>> print versalschreibung(u'auf<äßen', 'de-CH-1901')
2028 # auf<äs-sen
2029 # >>> print versalschreibung(u'auf<eßt', 'de-CH-1901')
2030 # auf<esst
2031 # >>> print versalschreibung(u'Groß=se-gel', 'de-1901-x-versal')
2032 # Gross=se-gel
2033 # >>> print versalschreibung(u'Groß=se-gel', 'de-CH-1901')
2034 # Gross=se-gel
2035 # >>> print versalschreibung(u'Paß=sy-ste-me', 'de-CH-1901')
2036 # Pass=sy-ste-me
2037 # >>> print versalschreibung(u'Pro<zeß=en-de', 'de-CH-1901')
2038 # Pro<zess=en-.de
2039 # >>> print versalschreibung(u'Pro<zess=en-.de', 'de-CH-1901')
2040 # Pro<zess=en-.de
2041 # >>> print versalschreibung(u'Fluß==sy-stem', 'de-CH-1901')
2042 # Fluss==sy-stem
2043 # >>> print versalschreibung(u'Meß==sen-der', 'de-CH-1901')
2044 # Mess==sen-der
2046 # ::
2048 def versalschreibung(wort, lang='de'):
2050 if not u'ß' in wort:
2051 return wort
2053 wort = wort.replace(u'ß', u'ss')
2055 # Trennung von Ersatz-ss in de-CH und de-1996 nach Sprechsilbenregel::
2057 if '1901-x-versal' not in lang:
2058 # wort = re.sub(u'(?<=[aeiouyäöü])-\.?ss', u's-s', wort)
2059 wort = re.sub(u'-\.?ss(?=[aeiouyäöü])', u's-s', wort)
2060 wort = re.sub(u'(?<=^[aeiouyäöü])ss(?=[aeiouyäöü])', u's-s', wort)
2061 wort = re.sub(u'(?<=[=<][aeiouyäöü])ss(?=[aeiouyäöü])', u's-s', wort)
2063 # Unterdrückung irreführender Trennung::
2065 wort = re.sub(u'ss(=+)(en|er)([<-])\.?', ur'ss\1\2\3.', wort)
2067 # Dreikonsonantenregel für Ersatz-ss in de-CH-1901::
2069 if 'CH-1901-x-dreikonsonanten' in lang:
2070 wort = re.sub(u'ss(=+)s(?=[aeiouyäöü])', ur'{ss/ss\1s}', wort)
2071 # Unterdrücken der Trennung nach nur einem Buchstaben und irreführender Trennungen
2072 wort = re.sub(ur'(?<=[=>]s}[aeiouyäöü])([-<])\.?', ur'\1.', wort)
2073 # wort = re.sub(ur'(?<===s}[aeiouyäöü])([-<])\.?', ur'\1.', wort) # Reißverschus=sy-.stem
2074 wort = re.sub(u'(?<=[=>]s})(en|er)([<-])\.?', ur'\1\2.', wort)
2076 return wort
2079 # Kurzformat in Langformat
2080 # ------------------------
2082 # Zusätzlich benötigte Felder werden automatisch erzeugt. Ein Kurzeintrag kann
2083 # mehrere Langeinträge ergeben:
2085 # >>> from wortliste import short2long
2086 # >>> for line in short2long([u"Diens-te", u"Ge<biss"]):
2087 # ... print unicode(line)
2088 # Dienste;-2-;Dien-ste;Diens-te
2089 # Gebiss;-2-;-3-;Ge<biss;Ge<biss
2090 # Gebiß;-2-;Ge<biß;-4-
2092 # >>> for line in short2long([u"Ge<schoss", u"Ge<schoß # österr."]):
2093 # ... print unicode(line)
2094 # Geschoss;-2-;-3-;Ge<schoss;Ge<schoss
2095 # Geschoß;Ge<schoß # österr.
2097 # short2long()
2098 # ~~~~~~~~~~~~
2099 # ::
2101 def short2long(lines, sort=True, prune=True):
2102 """Convert sequence of lines in ShortEntry format to WordEntry instances.
2105 # Sammeln in Liste und Dictionary:
2106 words = {} # zum Zusammenfassen
2107 entries = []
2109 for line in lines:
2110 shortentry = ShortEntry(line)
2111 shortentry.complete()
2112 for entry in shortentry.wordentries(prune=False):
2113 key = entry.key().lower()
2114 try: # Eintrag mit gleichem Schlüssel vorhanden?
2115 altentry = words[key]
2116 except KeyError: # nein -> neuer Eintrag
2117 words[key] = entry
2118 entries.append(entry)
2119 continue
2120 try:
2121 if entry[3]: # de-1996 non-empty
2122 entry.merge(altentry, prune=False, allow_alternatives=True)
2123 # Alternativen Eintrag "in-place" ersetzen:
2124 for i, word in enumerate(entry):
2125 altentry[i] = word
2126 altentry.comment = entry.comment
2127 else:
2128 altentry.merge(entry, prune=False, allow_alternatives=True)
2129 except AssertionError as e:
2130 sys.stderr.write(unicode(e).encode('utf8')+'\n')
2131 entries.append(entry)
2132 except IndexError: # Leerer Eintrag (Kommentar)
2133 entries.append(entry)
2136 if prune:
2137 for entry in entries:
2138 entry.prune()
2140 if sort:
2141 entries.sort(key=sortkey_duden)
2143 return entries
2145 # Tests:
2147 # Kommentare bleiben erhalten:
2149 # >>> for line in short2long([u'Aal=an-geln', u'# toller Kommentar'],
2150 # ... sort=False):
2151 # ... print unicode(line)
2152 # Aalangeln;Aal=an-geln
2153 # # toller Kommentar
2155 # Beim Sortieren werden Kommenare an den Beginn geschrieben.
2156 # >>> for line in short2long([u'# erster Kommentar',
2157 # ... u'Aal=an-geln',
2158 # ... u'# zweiter Kommentar']):
2159 # ... print unicode(line)
2160 # # erster Kommentar
2161 # # zweiter Kommentar
2162 # Aalangeln;Aal=an-geln
2165 # Langformat in Kurzformat
2166 # ------------------------
2168 # Optionale Felder werden weggelassen, wenn sie mit dem automatisch erzeugten
2169 # Inhalt übereinstimmen:
2171 # >>> from wortliste import long2short
2172 # >>> for entry in long2short([u"Dienste;-2-;Dien-ste;Diens-te"]):
2173 # ... print unicode(entry)
2174 # Diens-te
2176 # Zusammengehörige Einträge werden zusammengefasst:
2178 # >>> for line in long2short([u"Großanlass;-2-;-3-;Groß=an<lass",
2179 # ... u"Großanlaß;-2-;Groß=an<laß;-4-",
2180 # ... u"Grossanlass;-2-;-3-;-4-;Gross=an<lass"]):
2181 # ... print unicode(line)
2182 # Groß=an<lass
2184 # Sonderfälle
2185 # ~~~~~~~~~~~
2187 # Bei einigen Wörtern ist die ß-ss-Beziehung nicht eindeutig:
2189 # ======= ======== =========
2190 # de1901 de-1996 de-CH
2191 # ======= ======== =========
2192 # Maße Maße Masse
2193 # Masse Masse Masse
2194 # Geschoß Geschoß Geschoss
2195 # Geschoß Geschoss Geschoss
2196 # ======= ======== =========
2198 # Daher kann es vorkommen, dass ein Langform-Eintrag Beiträge von
2199 # verschiedenen Kurzformen erhält. So erzeugen, z.B. sowohl
2200 # „Masse“ als auch „Maße“ einen Langeintrag mit Schlüssel „Masse“:
2202 # >>> for entry in short2long([u'Mas-se']): print unicode(entry)
2203 # Masse;Mas-se
2204 # >>> for entry in short2long([u'Ma-ße']): print unicode(entry)
2205 # Masse;-2-;-3-;-4-;-5-;Ma-sse;Mas-se;Mas-se
2206 # Maße;Ma-ße
2208 # In der Langform werden Alternativen in ein Feld geschrieben:
2210 # >>> for entry in short2long([u'Mas-se', u'Ma-ße']):
2211 # ... print unicode(entry)
2212 # Masse;-2-;Mas-se;Mas-se;-5-;Ma[s-/-s]se;Mas-se;Mas-se
2213 # Maße;Ma-ße
2215 # Sind die Alternativen bereits in der Quelle, bleiben sie in der Kurzform
2216 # erhalten:
2218 # >>> ml = [u'Masse;-2-;Mas-se;Mas-se;-5-;Ma[-s/s-]se;Mas-se;Mas-se',
2219 # ... u'Maße;Ma-ße']
2220 # >>> for entry in long2short(ml):
2221 # ... print unicode(entry)
2222 # Mas-se;Mas-se;Mas-se;Ma[-s/s-]se
2223 # Ma-ße;Ma-ße;Mas-se;Ma[-s/s-]se
2225 # Zurück in die Langform:
2227 # >>> for entry in short2long([u'Mas-se;Mas-se;Mas-se;Ma[-s/s-]se',
2228 # ... u'Ma-ße;Ma-ße;-3-;Ma[-s/s-]se']):
2229 # ... print unicode(entry)
2230 # Masse;-2-;Mas-se;Mas-se;-5-;Ma[-s/s-]se;Mas-se;Mas-se
2231 # Maße;Ma-ße
2233 # long2short()
2234 # ~~~~~~~~~~~~
2235 # ::
2237 def long2short(lines, prune=True, drop_sz=False):
2238 """Convert sequence of 8-column lines to ShortEntry instances."""
2239 words = {} # Einträge mit `de`
2240 words_x = {} # Einträge ohne `de`
2241 words_merged = set() # Einträge die vollständig in andere einsortiert wurden
2242 entries = [] # Rückgabewert: Liste der Kurzeinträge
2244 # Zeilen Einlesen, Wandeln und Sammeln::
2246 for line in lines:
2247 longentry = WordEntry(line)
2248 entry = ShortEntry(longentry, prune=False)
2249 key = entry.key().lower() # Schlüssel ohne Großschreibung
2251 if not entry: # reiner Kommentar oder leerer Eintrag
2252 if entry.comment:
2253 entries.append(entry)
2254 continue
2256 # Einträge mit leerem ersten Feld werden später einsortiert:
2257 if prune and not entry[0]:
2258 words_x[key] = entry
2259 continue
2261 # Eintrag in `dictionary` und Liste:
2262 words[key] = entry
2263 entries.append(entry)
2265 # Straffen (Weglassen von Feldern/Einträgen wenn möglich),
2266 # es sei denn, der Aufruf erfolgte mit `prune=False`::
2268 if not prune:
2269 return entries
2271 for entry in entries:
2273 # Auffüllen:
2274 for i in range(1, len(entry)):
2275 if not entry[i]: # Feld ausgekreuzt
2276 key_i = join_word(entry.getitem(i, substitute=True)).lower()
2277 # print i, key_i
2278 try:
2279 co_entry = words_x[key_i]
2280 entry.merge(co_entry, prune=False, start=1)
2281 words_merged.add(key_i)
2282 # del words_x[key_i]
2283 except (KeyError, AssertionError):
2284 try:
2285 co_entry = words[key_i]
2286 entry.merge(co_entry, prune=False, start=1)
2287 words_merged.add(key_i)
2288 except (KeyError, AssertionError):
2289 pass
2291 # Anhängen aller Einträge mit leerem `de`-Feld, die nicht in
2292 # einen zugehörigen Eintrag einsortiert wurden an die Liste::
2294 for key, entry in words_x.iteritems():
2295 # print key, unicode(entry)
2296 if key in words_merged:
2297 continue
2298 # ggf. ergänzen:
2299 if len(entry) > 2 and not entry[3]: # de-1901-x-versal
2300 key = join_word(entry.getitem(3, substitute=True)).lower()
2301 try:
2302 co_entry = words_x[key]
2303 entry.merge(co_entry, prune=False, start=3)
2304 words_merged.add(key)
2305 except (KeyError, AssertionError):
2306 try:
2307 co_entry = words[key]
2308 entry.merge(co_entry, prune=False, start=3)
2309 # print key, unicode(co_entry)
2310 except (KeyError, AssertionError):
2311 pass
2313 entries.append(entry)
2315 for entry in entries:
2316 entry.prune(drop_sz)
2318 return entries
2320 # Tests:
2322 # Separate Einträge ß-Schreibungen zusammenfassen:
2324 # Ein von 2 ß wird zu ss in de-1996:
2326 # >>> for entry in long2short([
2327 # ... u'Passstrasse;-2-;-3-;-4-;-5-;Pass=stra-sse;Pass=stras-se;Pass=stras-se',
2328 # ... u'Passstraße;-2-;-3-;Pass=stra-ße',
2329 # ... u'Paßstraße;-2-;Paß=stra-ße;-4-']):
2330 # ... print unicode(entry)
2331 # Pass=stra-ße
2333 # ß in de-1996:
2335 # >>> for line in long2short([
2336 # ... u"Grossserie;-2-;-3-;-4-;-5-;Gross=se-rie;Gross=se-rie",
2337 # ... u"Großserie;Groß=se-rie"]):
2338 # ... print unicode(line)
2339 # Groß=se-rie
2341 # kein ß in de-1996:
2343 # >>> for line in long2short([
2344 # ... u"Basssaite;-2-;-3-;Bass=sai-te;-5-;Bass=sai-te;Bass=sai-te",
2345 # ... u"Baßsaite;-2-;Baß=sai-te;-4-"]):
2346 # ... print unicode(line)
2347 # Bass=sai-te
2349 # Zusätzliche Variante (Fremdwort vs. Lehnwort) in de-1901:
2351 # >>> for entry in short2long([u'Boss;Boss # en.']): print unicode(entry)
2352 # Boss;Boss # en.
2353 # >>> for entry in long2short([u'Boss;Boss # en.']): print unicode(entry)
2354 # Boss;Boss # en.
2355 # >>> for entry in long2short([u'Boss;Boss # en.', u'Boß;-2-;Boß;-3- # < en.']):
2356 # ... print unicode(entry)
2357 # Boss;Boss # en.
2358 # -1-;Boß # < en.
2360 # Alternativschreibung in de-1996 (Geschoß, Löß):
2362 # >>> for entry in long2short([u'Geschoss;-2-;-3-;Ge<schoss;Ge<schoss',
2363 # ... u'Geschoß;Ge<schoß # österr. auch de-1996']):
2364 # ... print unicode(entry)
2365 # Ge<schoss
2366 # Ge<schoß # österr. auch de-1996
2368 # Eigennamen auf -ss:
2370 # >>> for entry in long2short([u'Vossstrasse;-2-;-3-;-4-;-5-;Voss=stra-sse;Voss=stras-se;Voss=stras-se',
2371 # ... u'Vossstraße;Voss=stra-ße',
2372 # ... u'Voßstraße;Voß=stra-ße']):
2373 # ... print unicode(entry)
2374 # Voss=stra-ße;Voss=stra-ße
2375 # Voß=stra-ße
2377 # Kommentare:
2379 # >>> long2short(['# toller Kommentar'], prune=False)
2380 # [ShortEntry(u'# toller Kommentar')]
2381 # >>> long2short(['# toller Kommentar'], prune=True)
2382 # [ShortEntry(u'# toller Kommentar')]
2385 # Trennfilter
2386 # ===========
2388 # Funktionen, die einen Trennstil (oder einen Aspekt eines Trennstils)
2389 # implementieren (siehe auch dokumentation/Trennstile.txt).
2391 # fremdwortsilben()
2392 # -----------------
2394 # Entferne Alternativtrennungen bei einfachen und suffigierten Fremdwörtern:
2396 # Regelwerk (1996) § 112:
2397 # In Fremdwörtern können die Verbindungen aus Buchstaben für einen
2398 # Konsonanten + l, n oder r entweder entsprechend § 110 getrennt werden,
2399 # oder sie kommen ungetrennt auf die neue Zeile.
2401 # >>> from wortliste import fremdwortsilben
2403 # >>> fremdwoerter = (u'no-b-le Zy-k-lus Ma-g-net Fe-b-ru-ar '
2404 # ... u'Hy-d-rant Ar-th-ri-tis')
2405 # >>> for wort in fremdwoerter.split():
2406 # ... print wort, '->', fremdwortsilben(wort)
2407 # no-b-le -> no-ble
2408 # Zy-k-lus -> Zy-klus
2409 # Ma-g-net -> Ma-gnet
2410 # Fe-b-ru-ar -> Fe-bru-ar
2411 # Hy-d-rant -> Hy-drant
2412 # Ar-th-ri-tis -> Ar-thri-tis
2414 # >>> for wort in fremdwoerter.split():
2415 # ... print wort, '->', fremdwortsilben(wort, 'modern')
2416 # no-b-le -> nob-le
2417 # Zy-k-lus -> Zyk-lus
2418 # Ma-g-net -> Mag-net
2419 # Fe-b-ru-ar -> Feb-ru-ar
2420 # Hy-d-rant -> Hyd-rant
2421 # Ar-th-ri-tis -> Arth-ri-tis
2423 # ::
2425 def fremdwortsilben(wort, style='etymologisch'):
2426 """Select in-word hyphenation of foreign words."""
2427 if style == "modern": # Sprechsilbenregel
2428 return re.sub(u'-([bcdfgkptv]|th|st)-(?=[lrn])', u'\\1-', wort)
2429 else: # etymologisch
2430 return re.sub(u'-([bcdfgkptv]|th|st)-(?=[lrn])', u'-\\1', wort)
2431 # Versuch: auch Alternativtrennung nach führendem Vokal:
2432 # Ap-ri-kose -> Apri-kose aber auch Ad-ler -> Adler (!)
2433 # return re.sub(u'(-|^[AEIOUÄÖÜaeiouäöü])([bcdfgkptv]|th|st)-(?=[lrn])',
2434 # u'\\1\\2', wort)
2436 # Tests:
2438 # K86 Untrennbar sind in Fremdwörtern die Verbindungen von Verschluß- und
2439 # Reibelauten mit l und r, ...
2441 # >>> fremdwoerter = (u'Pu-b-li-kum flexi-b-ler Zy-k-lone Qua-d-rat '
2442 # ... u'Spek-t-rum manö-v-rieren')
2443 # >>> for wort in fremdwoerter.split():
2444 # ... print wort, '->', fremdwortsilben(wort)
2445 # Pu-b-li-kum -> Pu-bli-kum
2446 # flexi-b-ler -> flexi-bler
2447 # Zy-k-lone -> Zy-klone
2448 # Qua-d-rat -> Qua-drat
2449 # Spek-t-rum -> Spek-trum
2450 # manö-v-rieren -> manö-vrieren
2452 # die Lautfolge st+r bleibt ungetrennt, wenn keine Wortfuge vorliegt.
2454 # >>> fremdwoerter = u'Di-st-rikt Magi-st-rat laku-st-risch ab-st-rakt'
2455 # >>> for wort in fremdwoerter.split():
2456 # ... print wort, '->', fremdwortsilben(wort)
2457 # Di-st-rikt -> Di-strikt
2458 # Magi-st-rat -> Magi-strat
2459 # laku-st-risch -> laku-strisch
2460 # ab-st-rakt -> ab-strakt
2462 # K 87 Untrennbar ist die Konsonantenverbindung "gn".
2464 # >>> fremdwoerter = u'Ma-g-net Pro-g-nose Si-g-net'
2465 # >>> for wort in fremdwoerter.split():
2466 # ... print wort, '->', fremdwortsilben(wort)
2467 # Ma-g-net -> Ma-gnet
2468 # Pro-g-nose -> Pro-gnose
2469 # Si-g-net -> Si-gnet
2471 # Keine Übergeneralisierung:
2473 # >>> woerter = u'Seg-ler bast-le Ad-ler'
2474 # >>> for wort in woerter.split():
2475 # ... print wort, '->', fremdwortsilben(wort)
2476 # Seg-ler -> Seg-ler
2477 # bast-le -> bast-le
2478 # Ad-ler -> Ad-ler
2480 # wegen Übergeneralisierung nicht möglich:
2481 # Ap-ri-kose -> Apri-kose
2482 # ig-no-rie-ren -> igno-rie-ren
2485 # verblasst()
2486 # --------------
2488 # Entferne Alternativtrennungen nach §113 (verblasste Etymologie).
2490 # Regelwerk (1996) §113:
2491 # Wörter, die sprachhistorisch oder von der Herkunftssprache her gesehen
2492 # Zusammensetzungen oder Präfigierungen sind, aber nicht mehr als solche
2493 # empfunden oder erkannt werden, kann man entweder nach § 108 oder nach §
2494 # 109 bis § 112 trennen.
2496 # >>> from wortliste import verblasst
2497 # >>> blasse = (u'hi-n<auf he-r<an da-r<um Chry-s<an-the-me Hek-t<ar '
2498 # ... u'He-li-ko<p-ter in-te-r>es-sant Li-n<oleum Pä-d<a-go-gik')
2499 # >>> for wort in blasse.split():
2500 # ... print wort, '->', verblasst(wort)
2501 # hi-n<auf -> hin<auf
2502 # he-r<an -> her<an
2503 # da-r<um -> dar<um
2504 # Chry-s<an-the-me -> Chrys<an-the-me
2505 # Hek-t<ar -> Hekt<ar
2506 # He-li-ko<p-ter -> He-li-ko<pter
2507 # in-te-r>es-sant -> in-ter>es-sant
2508 # Li-n<oleum -> Lin<oleum
2509 # Pä-d<a-go-gik -> Päd<ago-gik
2511 # >>> for wort in blasse.split():
2512 # ... print wort, '->', verblasst(wort, 'modern')
2513 # hi-n<auf -> hi-nauf
2514 # he-r<an -> he-ran
2515 # da-r<um -> da-rum
2516 # Chry-s<an-the-me -> Chry-san-the-me
2517 # Hek-t<ar -> Hek-tar
2518 # He-li-ko<p-ter -> He-li-kop-ter
2519 # in-te-r>es-sant -> in-te-res-sant
2520 # Li-n<oleum -> Li-noleum
2521 # Pä-d<a-go-gik -> Pä-da-go-gik
2523 # Ersetze, wenn zwischen Haupttrennstelle und Nebentrennstelle nur ein
2524 # Buchstabe liegt.
2525 # (Die Haupttrennstelle kann vor oder nach der Nebentrennstelle liegen.)
2526 # ::
2528 def verblasst(wort, style='etymologisch'):
2529 """Select hyphenation of foreign words with obscure etymology."""
2530 if style == "modern": # Sprechsilbenregel
2531 wort = re.sub(u'[<>=]+[.]*(.[-.]+)', u'\\1', wort)
2532 wort = re.sub(u'([-.]+.)[<>=]+[.]*', u'\\1', wort)
2533 else: # etymologisch
2534 wort = re.sub(u'([<>=]+[.]*.)[-.]+', u'\\1', wort)
2535 wort = re.sub(u'[-.]+(.[<>=]+)', u'\\1', wort)
2536 return wort
2540 # Tests:
2542 # K44 „ſ“ (langes s) steht in Fremdwörtern...
2544 # >>> blasse = (u'tran<s-pirieren tran<s-zendent ab<s-tinent '
2545 # ... u'Ab<s-zess Pro-s<odie')
2546 # >>> for wort in blasse.split():
2547 # ... print wort, '->', verblasst(wort)
2548 # tran<s-pirieren -> tran<spirieren
2549 # tran<s-zendent -> tran<szendent
2550 # ab<s-tinent -> ab<stinent
2551 # Ab<s-zess -> Ab<szess
2552 # Pro-s<odie -> Pros<odie
2554 # Trennstellen können als ungünstig markiert sein:
2556 # >>> blasse = (u'Bür-ger=in<.i-ti-a-ti-ve Pä-..d<e-..rast')
2557 # >>> for wort in blasse.split():
2558 # ... print wort, '->', verblasst(wort)
2559 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=in<.iti-a-ti-ve
2560 # Pä-..d<e-..rast -> Päd<erast
2561 # >>> for wort in blasse.split():
2562 # ... print wort, '->', verblasst(wort, 'modern')
2563 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=ini-ti-a-ti-ve
2564 # Pä-..d<e-..rast -> Pä-..de-..rast
2567 # scoretext()
2568 # ------------
2570 # Füge Trennmöglichkeiten am Wortanfang und -ende zu, die nach K79 (bzw. §107
2571 # E2 des Regelwerkes) verboten sind aber in Notentexten gebraucht werden.
2573 # >>> from wortliste import scoretext
2574 # >>> scoretext(u'Abend')
2575 # u'A.bend'
2576 # >>> scoretext(u'Ra-dio')
2577 # u'Ra-di.o'
2579 # Das gleiche gilt für Trennmöglichkeiten am Anfang/Ende von Teilwörtern:
2581 # >>> scoretext(u'Eis=ano-ma-lie')
2582 # u'Eis=a.no-ma-lie'
2583 # >>> scoretext(u'Ra-dio<phon')
2584 # u'Ra-di.o<phon'
2586 # Ausnahmen:
2588 # >>> scoretext(u'Ai-chin-ger'), scoretext(u'Ai-da')
2589 # (u'Ai-chin-ger', u'A.i-da')
2590 # >>> scoretext(u'Ma-rie'), scoretext(u'Li-nie')
2591 # (u'Ma-rie', u'Li-ni.e')
2592 # >>> scoretext(u'Ta-too'), scoretext(u'Zoo<lo-gie')
2593 # (u'Ta-too', u'Zo.o<lo-gie')
2594 # >>> scoretext(u'A-pnoe'), scoretext(u'O-boe')
2595 # (u'A-pnoe', u'O-bo.e')
2596 # >>> scoretext(u'Plaque'), scoretext(u'treue')
2597 # (u'Plaque', u'treu.e')
2598 # >>> scoretext(u'Fon-due=pfan-ne'), scoretext(u'Aue')
2599 # (u'Fon-due=pfan-ne', u'Au.e')
2600 # >>> scoretext(u'Ge-nie'), scoretext(u'Iphi-ge-nie')
2601 # (u'Ge-nie', u'I.phi-ge-nie')
2602 # >>> scoretext(u'Ago-nie'), scoretext(u'Be-go-nie')
2603 # (u'A.go-nie', u'Be-go-ni.e')
2604 # >>> scoretext(u'Kom-pa-nie'), scoretext(u'Kas-ta-nie'), scoretext(u'Ge-ra-nie')
2605 # (u'Kom-pa-nie', u'Kas-ta-ni.e', u'Ge-ra-ni.e')
2607 # ungelöst: Knie / Kni.e # pl.
2608 # ::
2610 def scoretext(word):
2611 """Mark up leading one-letter syllables."""
2612 # Führender Vokal, gefolgt von Silbenanfang
2613 # (optionaler Konsonant (auch ch/ck/ph/rh/sch/sh/th) + Vokal)
2614 for match in re.finditer(u'(^|[<=])([aeiouäöü])((.|ch|ck|ph|sch|th)?[aeiouäöü])',
2615 word, flags=re.IGNORECASE):
2616 # print match.groups()
2617 # Ausnahmen: Doppellaute, Diphtonge (außer A-i-da), Umlaute
2618 if (re.search(u'(aa|ae|ai|au|äu|ei|eu|oe|oo|ou|ue)',
2619 match.group(0), flags=re.IGNORECASE)
2620 and word != u'Ai-da' or word == u'io-ta' ):
2621 continue
2622 word = ''.join((word[:match.start()], match.expand(u'\\1\\2.\\3'),
2623 scoretext(word[match.end():])))
2624 break
2625 # zwei Vokale am Wortende
2626 for match in re.finditer(u'([aeiouäöü])([aeiouäöü])([<>=]|$)', word):
2627 # if re.search(u'(oo)', match.group(0)):
2628 # if 'ie' in match.group(0) and re.search(u'([a]-nie)', word, flags=re.IGNORECASE):
2629 # sys.stderr.write(word+' '+match.group(0)+'\n')
2630 # Ausnahmen: Doppellaute, Diphtonge, Umlaute (außer "Oboe")
2631 if (re.search(u'(aa|ae|ai|au|äu|ee|ei|eu|oe|oi|ou|ui)', match.group(0))
2632 and not word[:match.end()].endswith(u'waii') # ! Hawaii
2633 and not word[:match.end()].endswith(u'boe')): # ! Oboe
2634 continue
2635 # Ausnahmen mit Ausnahmen:
2636 # …oo außer zoo<, ...
2637 if 'oo' in match.group(0) and match.group(0) != 'oo<':
2638 continue
2639 # …ie außer "-(l)inie", Iphigenie, Kastanie, Geranie, Begonie
2640 if ('ie' in match.group(0)
2641 and not word[:match.end()].endswith(u'i-nie') # Linie,
2642 and not word[:match.end()].endswith(u'ta-nie') # Kastanie,
2643 and not word[:match.end()].endswith(u'ra-nie') # Geranie,
2644 and not word[:match.end()].endswith(u'e-go-nie') # Begonie != Agonie
2645 and not word == u'Iphi-ge-nie'):
2646 continue
2647 # …ue (Plaque, Re-vue, vogue) außer "-tue, -aue, ... -äue"
2648 if 'ue' in match.group(0) and re.search(u'[^aeät]ue([<>=]|$)',
2649 word[:match.end()], flags=re.IGNORECASE):
2650 continue
2652 word = ''.join((word[:match.start()], match.expand(u'\\1.\\2\\3'),
2653 scoretext(word[match.end():])))
2654 break
2655 return word
2658 # keine_einzelvokale()
2659 # --------------------
2661 # Traditionell enthalten die TeX-Trennmuster keine Trennstellen deren Abstand
2662 # zur Nachbartrennstelle einen Buchstaben beträgt. Dies ist eine *ästhetische*
2663 # Entscheidung um „Flatterbuchstaben“ zu vermeiden
2665 # Bei einvokalischen Silben im Wortinneren, nimm die zweite:
2667 # >>> from wortliste import keine_einzelvokale
2668 # >>> einzelne = (u'The-a-ter me-ri-di-.o-nal')
2669 # >>> for wort in einzelne.split():
2670 # ... print wort, '->', keine_einzelvokale(wort)
2671 # The-a-ter -> Thea-ter
2672 # me-ri-di-.o-nal -> me-ri-dio-nal
2674 # Allerdings nicht, wenn die erste Trennstelle unterdrückt ist:
2676 # >>> einzelne = (u'La-sal-le-a-.ner Athe-i-.sten')
2677 # >>> for wort in einzelne.split():
2678 # ... print wort, '->', keine_einzelvokale(wort)
2679 # La-sal-le-a-.ner -> La-sal-le-a-.ner
2680 # Athe-i-.sten -> Athe-i-.sten
2682 # ::
2684 def keine_einzelvokale(wort):
2685 """Drop marker after single vowels."""
2686 return re.sub(u'-[.]*([aeiouyäöü]-[^.])', u'\\1', wort)
2689 # Hilfsfunktionen
2690 # ===============
2692 # join_word()
2693 # -----------
2695 # Trennzeichen entfernen::
2697 def join_word(wort, assert_complete=False):
2699 # Einfache Trennzeichen:
2701 # == ================================================================
2702 # \= Trennstelle an Wortfugen (Wort=fu-ge)
2703 # \< Trennstelle nach Präfix (Vor<sil-be)
2704 # \> Trennstelle vor Suffix (Freund>schaf-ten)
2705 # \- Nebentrennstelle (ge-hen)
2706 # \. unerwünschte/ungünstige Trennstelle (Ge<schoss=en<.er-gie)
2707 # == ================================================================
2709 # ::
2711 for marker in u'=<>-.':
2712 wort = wort.replace(marker, u'')
2714 # Spezielle Trennungen für die traditionelle Rechtschreibung
2715 # (siehe ../../dokumente/README.wortliste)::
2717 if '{' in wort or '}' in wort:
2718 wort = wort.replace(u'{ck/kk}', u'ck')
2719 wort = wort.replace(u'{ck/k', u'k')
2720 wort = wort.replace(u'k}', u'k')
2721 # Konsonanthäufungen an Wortfuge: '{xx/xxx}' -> 'xx':
2722 wort = re.sub(ur'\{(.)\1/\1\1\1\}', ur'\1\1', wort)
2723 # schon getrennt: ('{xx/xx' -> 'xx' und 'x}' -> 'x'):
2724 wort = re.sub(ur'\{(.)\1/\1\1$', ur'\1\1', wort)
2725 wort = re.sub(ur'^(.)\}', ur'\1', wort)
2727 # Trennstellen in doppeldeutigen Wörtern::
2729 if '[' in wort or ']' in wort:
2730 wort = re.sub(ur'\[(.*)/\1\]', ur'\1', wort)
2731 # schon getrennt:
2732 wort = re.sub(ur'\[([^/\[]+)$', ur'\1', wort)
2733 wort = re.sub(ur'^([^/\]]+)\]', ur'\1', wort)
2735 # Test auf verbliebene komplexe Trennstellen::
2737 if assert_complete:
2738 for spez in u'[{/}]':
2739 if spez in wort:
2740 raise AssertionError('Spezialtrennung %s, %s' %
2741 (wort.encode('utf8'), wort.encode('utf8')))
2743 return wort
2745 # zerlege()
2746 # ---------
2748 # Zerlege ein Wort mit Trennzeichen in eine Liste von Silben und eine Liste
2749 # von Trennzeichen)
2751 # >>> from wortliste import zerlege
2753 # >>> zerlege(u'Haupt=stel-le')
2754 # ([u'Haupt', u'stel', u'le'], [u'=', u'-'])
2755 # >>> zerlege(u'Ge<samt=be<triebs=rats==chef')
2756 # ([u'Ge', u'samt', u'be', u'triebs', u'rats', u'chef'], [u'<', u'=', u'<', u'=', u'=='])
2757 # >>> zerlege(u'an<stands>los')
2758 # ([u'an', u'stands', u'los'], [u'<', u'>'])
2759 # >>> zerlege(u'An<al.pha-bet')
2760 # ([u'An', u'al', u'pha', u'bet'], [u'<', u'.', u'-'])
2762 # ::
2764 def zerlege(wort):
2765 silben = re.split(u'[-·._<>=]+', wort)
2766 trennzeichen = re.split(u'[^-·._|<>=]+', wort)
2767 return silben, [tz for tz in trennzeichen if tz]
2769 # alternatives()
2770 # --------------
2772 # Gib einen String mit Trennmarkierung für abweichende Trennungen in
2773 # mehrdeutigen Wörtern zurück:
2775 # >>> from wortliste import alternatives
2776 # >>> alternatives(u'Mas-se', u'Ma-sse')
2777 # u'Ma[s-/-s]se'
2778 # >>> alternatives(u'Ma-sse', u'Mas-se')
2779 # u'Ma[-s/s-]se'
2781 # ::
2783 def alternatives(wort1, wort2):
2784 # convert to lists
2785 wort1 = [c for c in wort1]
2786 wort2 = [c for c in wort2]
2787 pre = []
2788 post = []
2789 for c1, c2 in zip(wort1, wort2):
2790 if c1 != c2:
2791 break
2792 pre += c1
2793 for c1, c2 in zip(wort1.__reversed__(), wort2.__reversed__()):
2794 if c1 != c2:
2795 break
2796 post += c1
2797 post.reverse()
2798 return u''.join(pre + [u'['] + wort1[len(pre):-len(post)] + [u'/']
2799 + wort2[len(pre):-len(post)] + [u']'] + post)
2801 # TransferError
2802 # -------------
2804 # Fehler beim Übertragen von Trennstellen mit uebertrage()_::
2806 class TransferError(ValueError):
2807 def __init__(self, wort1, wort2):
2808 msg = u'Inkompatibel: %s %s' % (wort1, wort2)
2809 ValueError.__init__(self, msg.encode('utf8'))
2811 def __unicode__(self):
2812 return str(self).decode('utf8')
2815 # uebertrage()
2816 # ------------
2818 # Übertrage die Trennzeichen von `wort1` auf `wort2`:
2820 # >>> from wortliste import uebertrage, TransferError
2822 # >>> uebertrage(u'Haupt=stel-le', u'Haupt·stel·le')
2823 # u'Haupt=stel-le'
2825 # Auch teilweise Übertragung, von "kategorisiert" nach "unkategorisiert":
2827 # >>> print uebertrage(u'Haupt=stel-le', u'Haupt=stel·le')
2828 # Haupt=stel-le
2830 # >>> print uebertrage(u'Haupt·stel-le', u'Haupt=stel·le')
2831 # Haupt=stel-le
2833 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler')
2834 # Aus<stel-ler
2836 # >>> print uebertrage(u'Freund>schaf·ten', u'Freund-schaf-ten')
2837 # Freund>schaf-ten
2839 # Übertragung doppelter Marker:
2841 # >>> print uebertrage(u'ver<<aus<ga-be', u'ver<aus<ga-be')
2842 # ver<<aus<ga-be
2844 # >>> print uebertrage(u'freund>lich>>keit', u'freund>lich>keit')
2845 # freund>lich>>keit
2847 # >>> print uebertrage(u'Amts==haupt=stel-le', u'Amts=haupt=stel-le')
2848 # Amts==haupt=stel-le
2850 # Kein Überschreiben doppelter Marker:
2851 # >>> print uebertrage(u'ver<aus<ga-be', u'ver<<aus<ga-be')
2852 # ver<<aus<ga-be
2854 # >>> print uebertrage(u'Amts=haupt=stel-le', u'Amts==haupt=stel·le')
2855 # Amts==haupt=stel-le
2857 # Erhalt des Markers für ungünstige Stellen:
2858 # >>> print uebertrage(u'An·al.pha·bet', u'An<al.pha-bet')
2859 # An<al.pha-bet
2861 # Keine Übertragung, wenn die Zahl oder Position der Trennstellen
2862 # unterschiedlich ist oder bei unterschiedlichen Wörtern:
2864 # >>> try:
2865 # ... uebertrage(u'Ha-upt=stel-le', u'Haupt=stel·le')
2866 # ... uebertrage(u'Haupt=ste-lle', u'Haupt=stel·le')
2867 # ... uebertrage(u'Waupt=stel-le', u'Haupt=stel·le')
2868 # ... except TransferError:
2869 # ... pass
2871 # Übertragung auch bei unterschiedlicher Schreibung oder Position der
2872 # Trennstellen mit `strict=False` (für Abgleich zwischen Sprachvarianten):
2874 # >>> uebertrage(u'er-ster', u'ers·ter', strict=False)
2875 # u'ers-ter'
2876 # >>> uebertrage(u'Fluß=bett', u'Fluss·bett', strict=False)
2877 # u'Fluss=bett'
2878 # >>> uebertrage(u'ab>bei-ßen', u'ab>beis·sen', strict=False)
2879 # u'ab>beis-sen'
2880 # >>> print uebertrage(u'Aus<tausch=dien-stes', u'Aus-tausch=diens-tes', False)
2881 # Aus<tausch=diens-tes
2883 # Auch mit `strict=False` muß die Zahl der Trennstellen übereinstimmen
2884 # (Ausnahmen siehe unten):
2886 # >>> try:
2887 # ... uebertrage(u'Ha-upt=ste-lle', u'Haupt=stel·le', strict=False)
2888 # ... except TransferError:
2889 # ... pass
2891 # Akzeptiere unterschiedliche Anzahl von Trennungen bei st und ck nach
2892 # Selbstlaut:
2894 # >>> uebertrage(u'acht=ecki-ge', u'acht·e{ck/k·k}i·ge', strict=False)
2895 # u'acht=e{ck/k-k}i-ge'
2896 # >>> uebertrage(u'As-to-ria', u'Asto·ria', strict=False)
2897 # u'Asto-ria'
2898 # >>> uebertrage(u'Asto-ria', u'As·to·ria', strict=False)
2899 # u'As-to-ria'
2900 # >>> uebertrage(u'So-fa=ecke', u'So·fa=e{ck/k-k}e', strict=False)
2901 # u'So-fa=e{ck/k-k}e'
2902 # >>> uebertrage(u'Drei=ecks=ecke', u'Drei=ecks==e{ck/k-k}e', strict=False)
2903 # u'Drei=ecks==e{ck/k-k}e'
2905 # Mit ``upgrade=False`` werden nur unspezifische Trennstellen überschrieben:
2907 # >>> print uebertrage(u'an=stel-le', u'an<stel·le', upgrade=False)
2908 # an<stel-le
2910 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler', upgrade=False)
2911 # Aus-stel-ler
2913 # >>> print uebertrage(u'Aus-stel-ler', u'Aus<stel-ler', upgrade=False)
2914 # Aus<stel-ler
2916 # >>> print uebertrage(u'vor<an<<stel-le', u'vor-an<stel·le', upgrade=False)
2917 # vor-an<stel-le
2919 # ::
2921 selbstlaute = u'aeiouäöüAEIOUÄÖÜ'
2923 def uebertrage(wort1, wort2, strict=True, upgrade=True):
2925 silben1, trennzeichen1 = zerlege(wort1)
2926 silben2, trennzeichen2 = zerlege(wort2)
2927 # Prüfe strikte Übereinstimmung:
2928 if silben1 != silben2 and strict:
2929 if u'<' in trennzeichen1 or u'·' in trennzeichen2:
2930 raise TransferError(wort1, wort2)
2931 else:
2932 return wort2
2933 # Prüfe ungefähre Übereinstimmung:
2934 if len(trennzeichen1) != len(trennzeichen2):
2935 # Selbstlaut + st oder ck?
2936 for s in selbstlaute:
2937 if (wort2.find(s+u'{ck/k·k}') != -1 or
2938 wort2.find(s+u'{ck/k-k}') != -1):
2939 wort1 = re.sub(u'%sck([%s])'%(s,selbstlaute),
2940 ur'%s-ck\1'%s, wort1)
2941 silben1, trennzeichen1 = zerlege(wort1)
2942 if wort2.find(s+u's·t') != -1:
2943 wort1 = wort1.replace(s+u'st', s+u's-t')
2944 silben1, trennzeichen1 = zerlege(wort1)
2945 elif wort1.find(s+u's-t') != -1:
2946 wort1 = wort1.replace(s+u's-t', s+u'st')
2947 silben1, trennzeichen1 = zerlege(wort1)
2948 # print u'retry:', silben1, trennzeichen1
2949 # immer noch ungleiche Zahl an Trennstellen?
2950 if len(trennzeichen1) != len(trennzeichen2):
2951 raise TransferError(wort1, wort2)
2953 # Baue wort3 aus silben2 und spezifischeren Trennzeichen:
2954 wort3 = silben2.pop(0)
2955 for t1,t2 in zip(trennzeichen1, trennzeichen2):
2956 if ((t2 == u'·' and t1 != u'.') # unspezifisch
2957 or upgrade and
2958 ((t2 in (u'-', u'<') and t1 in (u'<', u'<<', u'<=')) # Praefixe
2959 or (t2 in (u'-', u'>') and t1 in (u'>', u'>>', u'=>')) # Suffixe
2960 or (t2 in (u'-', u'=') and t1 in (u'=', u'==', u'===')) # W-fugen
2963 wort3 += t1
2964 elif t2 == u'.' and t1 not in u'·.':
2965 wort3 += t1 + t2
2966 else:
2967 wort3 += t2
2968 wort3 += silben2.pop(0)
2969 return wort3
2972 # Übertrag kategorisierter Trennstellen zwischen den Feldern aller Einträge
2973 # in `wortliste`::
2975 def sprachabgleich(entry, vorbildentry=None):
2977 if len(entry) <= 2:
2978 return # allgemeine Schreibung
2980 mit_affix = None # < oder >
2981 kategorisiert = None # kein ·
2982 unkategorisiert = None # mindestens ein ·
2983 gewichtet = None # == oder <= oder =>
2984 for field in entry[1:]:
2985 if not field: # -2-, -3-, ...
2986 continue
2987 if u'{' in field and u'[' in field: # Bi-ber==be[t=t/{tt/tt=t}]uch
2988 continue # zu komplex
2989 if u'·' in field:
2990 unkategorisiert = field
2991 elif u'<' in field or u'>' in field:
2992 mit_affix = field
2993 else:
2994 kategorisiert = field
2995 if u'==' in field or u'<=' in field or u'=>' in field:
2996 gewichtet = field
2997 if vorbildentry:
2998 for field in vorbildentry[1:]:
2999 if not field: # -2-, -3-, ...
3000 continue
3001 if u'{' in field and u'[' in field: # Bi-ber==be[t=t/{tt/tt=t}]uch
3002 continue # zu komplex
3003 if not mit_affix and u'<' in field or u'>' in field :
3004 mit_affix = field
3005 elif not kategorisiert and unkategorisiert and u'·' not in field:
3006 kategorisiert = field
3007 if not gewichtet and u'==' in field or u'<=' in field or u'=>' in field:
3008 gewichtet = field
3009 # print 've:', mit_affix, kategorisiert, unkategorisiert
3010 if mit_affix and (kategorisiert or unkategorisiert or gewichtet):
3011 for i in range(1,len(entry)):
3012 if not entry[i]: # -2-, -3-, ...
3013 continue
3014 if u'<' not in entry[i] or u'·' in entry[i]:
3015 try:
3016 entry[i] = uebertrage(mit_affix, entry[i], strict=False)
3017 except TransferError, e:
3018 if not '/' in entry[i]:
3019 print u'Sprachabgleich:', unicode(e)
3020 # print mit_affix+u':', unicode(entry)
3021 elif kategorisiert and unkategorisiert:
3022 for i in range(1,len(entry)):
3023 if u'·' in entry[i]:
3024 try:
3025 entry[i] = uebertrage(kategorisiert, entry[i], strict=False)
3026 except TransferError, e:
3027 print u'Sprachabgleich:', unicode(e)
3028 # print kategorisiert, unicode(entry)
3029 elif gewichtet:
3030 for i in range(1,len(entry)):
3031 if u'=' in entry[i] and not (
3032 u'{' in entry[i] and u'[' in entry[i]):
3033 try:
3034 entry[i] = uebertrage(gewichtet, entry[i], strict=False)
3035 except TransferError, e:
3036 print u'Sprachabgleich:', unicode(e)
3040 # Großschreibung in Kleinschreibung wandeln und umgekehrt
3042 # Diese Version funktioniert auch für Wörter mit Trennzeichen (während
3043 # str.title() nach jedem Trennzeichen wieder groß anfängt)
3045 # >>> from wortliste import toggle_case
3046 # >>> toggle_case(u'Ha-se')
3047 # u'ha-se'
3048 # >>> toggle_case(u'arm')
3049 # u'Arm'
3050 # >>> toggle_case(u'frei=bier')
3051 # u'Frei=bier'
3052 # >>> toggle_case(u'L}a-ger')
3053 # u'l}a-ger'
3055 # Keine Änderung bei Wörtern mit Großbuchstaben im Inneren:
3057 # >>> toggle_case(u'USA')
3058 # u'USA'
3059 # >>> toggle_case(u'iRFD')
3060 # u'iRFD'
3062 # >>> toggle_case(u'gri[f-f/{ff/ff')
3063 # u'Gri[f-f/{ff/ff'
3064 # >>> toggle_case(u'Gri[f-f/{ff/ff')
3065 # u'gri[f-f/{ff/ff'
3067 # ::
3069 def toggle_case(wort):
3070 try:
3071 key = join_word(wort, assert_complete=True)
3072 except AssertionError:
3073 key = wort[0]
3074 if key.istitle():
3075 return wort.lower()
3076 elif key.islower():
3077 return wort[0].upper() + wort[1:]
3078 else:
3079 return wort
3081 # Sortierschlüssel
3082 # ================
3084 # Duden-Sortierung für die Wortliste
3086 # >>> from wortliste import sortkey_duden
3087 # >>> sortkey_duden([u"Abflußröhren"])
3088 # u'abflussrohren a*bflu*szroehren'
3089 # >>> sortkey_duden([u"Abflußrohren"])
3090 # u'abflussrohren a*bflu*szro*hren'
3091 # >>> sortkey_duden([u"Abflussrohren"])
3092 # u'abflussrohren'
3094 # >>> s = sorted([[u"Abflußröhren"], [u"Abflußrohren"], [u"Abflussrohren"]],
3095 # ... key=sortkey_duden)
3096 # >>> print ', '.join(e[0] for e in s)
3097 # Abflussrohren, Abflußrohren, Abflußröhren
3099 # Umschreibung
3101 # Ligaturen auflösen und andere "normalisierunde" Ersetzungen für den
3102 # (Haupt-)Sortierschlüssel (Akzente werden über ``unicodedata.normalize``
3103 # entfernt)::
3105 umschrift_skey = {
3106 ord(u'æ'): u'ae',
3107 ord(u'œ'): u'oe',
3108 ord(u'ſ'): u's',
3111 # "Zweitschlüssel" zur Unterscheidung von Umlauten/SZ und Basisbuchstaben::
3113 umschrift_subkey = {
3114 ord(u'a'): u'a*',
3115 ord(u'å'): u'aa',
3116 ord(u'ä'): u'ae',
3117 ord(u'o'): u'o*',
3118 ord(u'ö'): u'oe',
3119 ord(u'ø'): u'oe',
3120 ord(u'u'): u'u*',
3121 ord(u'ü'): u'ue',
3122 ord(u'ß'): u'sz',
3126 # sortkey_duden()
3127 # ---------------
3129 # Schlüssel für die alphabetische Sortierung gemäß Duden-Regeln.
3131 # Argument ist ein Eintrag im WordEntry oder ShortEntry Format oder
3132 # ein (Unicode) String:
3134 # >>> print sortkey_duden(weste) # WordEntry
3135 # weste
3136 # >>> print sortkey_duden(dst) # ShortEntry
3137 # dienste
3139 # >>> print sortkey_duden(u'Stra-ße')
3140 # strasse stra*sze
3141 # >>> print sortkey_duden([u'Büh-ne'])
3142 # buhne buehne
3143 # >>> print sortkey_duden(u'Weiß=flog;Weiß=flog;-3-;-4-;-5-')
3144 # weissflog weiszflo*g
3145 # >>> print sortkey_duden(u'-1-;Meß=sen-der;-3-;-4-;-5-')
3146 # messsender meszsender
3147 # >>> print sortkey_duden(u'As-sen # (geogr. und Eigen-) Name\n')
3148 # assen
3149 # >>> print sortkey_duden(u'aßen;aßen;-3-;-4-;-5-\n')
3150 # assen a*szen
3151 # >>> print sortkey_duden(u'äßen\n')
3152 # assen aeszen
3154 # ::
3156 def sortkey_duden(entry):
3158 # ggf. ungetrenntes Wort extrahieren oder generieren::
3160 if isinstance(entry, list):
3161 try:
3162 key = entry.key()
3163 except AttributeError:
3164 key = entry[0]
3165 if len(entry) == 1: # ein Muster pro Zeile, siehe z.B. pre-1901
3166 key = join_word(key)
3167 else:
3168 match = re.search(u'^[-0-9;]*([^;\s]+)', entry) # erstes volles Feld
3169 if match:
3170 key = match.group(1)
3171 else:
3172 key = u''
3173 key = join_word(key)
3174 key = re.sub(u'[0-9;]', ur'', key)
3176 # Großschreibung ignorieren:
3178 # Der Duden sortiert Wörter, die sich nur in der Großschreibung unterscheiden
3179 # "klein vor groß" (ASCII sortiert "groß vor klein"). In der
3180 # `Trennmuster-Wortliste` kommen Wörter nur mit der häufiger anzutreffenden
3181 # Großschreibung vor, denn der TeX-Trennalgorithmus ignoriert Großschreibung.
3182 # ::
3184 key = key.lower()
3186 # Ersetzungen:
3188 # ß -> ss ::
3190 skey = key.replace(u'ß', u'ss')
3192 # Restliche Akzente weglassen: Wandeln in Darstellung von Buchstaben mit
3193 # Akzent als "Grundzeichen + kombinierender Akzent". Anschließend alle
3194 # nicht-ASCII-Zeichen ignorieren::
3196 skey = skey.translate(umschrift_skey)
3197 skey = unicodedata.normalize('NFKD', skey)
3198 skey = unicode(skey.encode('ascii', 'ignore'))
3200 # "Zweitschlüssel" für das eindeutige Einsortieren von Wörtern mit
3201 # gleichem Schlüssel (Masse/Maße, waren/wären, ...):
3203 # * "*" nach aou für die Unterscheidung Grund-/Umlaut
3204 # * ß->sz
3206 # ::
3208 if key != skey:
3209 subkey = key.translate(umschrift_subkey)
3210 skey = u'%s %s' % (skey,subkey)
3212 # Gib den Sortierschlüssel zurück::
3214 return skey
3218 # udiff()
3219 # -------
3221 # Vergleiche zwei Sequenzen von `WordEntries` (genauer: alle Objekte, die
3222 # sich sinnvoll zu Unicode wandeln lassen).
3224 # Beispiel:
3226 # >>> from wortliste import udiff
3227 # >>> print udiff([abbeissen, aalbestand], [abbeissen,dresz], 'alt', 'neu')
3228 # --- alt
3229 # +++ neu
3230 # @@ -1,2 +1,2 @@
3231 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
3232 # -Aalbestand;Aal=be<stand # Test
3233 # +Dreß;-2-;Dreß;-4-
3234 # <BLANKLINE>
3235 # >>> udiff([abbeissen, aalbestand], [abbeissen, aalbestand], 'alt', 'neu')
3236 # u''
3238 # ::
3240 def udiff(a, b, fromfile='', tofile='',
3241 fromfiledate='', tofiledate='', n=1, encoding='utf8'):
3243 a = [unicode(entry).rstrip().encode(encoding) for entry in a]
3244 b = [unicode(entry).rstrip().encode(encoding) for entry in b]
3246 diff = '\n'.join(difflib.unified_diff(a, b, fromfile, tofile,
3247 fromfiledate, tofiledate, n, lineterm=''))
3248 if diff:
3249 diff += '\n'
3250 return diff.decode(encoding)
3253 # Sprachtags
3254 # ==========
3256 # Normalisierung und Expansion von Sprachtags nach [BCP47]_
3258 # >>> from wortliste import normalize_language_tag
3259 # >>> normalize_language_tag('de_AT-1901')
3260 # ['de-AT-1901', 'de-AT', 'de-1901', 'de']
3262 # >>> normalize_language_tag('de') # Deutsch, allgemeingültig
3263 # ['de']
3264 # >>> normalize_language_tag('de_1901') # traditionell (Reform 1901)
3265 # ['de-1901', 'de']
3266 # >>> normalize_language_tag('de_1996') # reformiert (Reform 1996)
3267 # ['de-1996', 'de']
3268 # >>> normalize_language_tag('de_CH') # ohne ß (Schweiz oder versal)
3269 # ['de-CH', 'de']
3270 # >>> normalize_language_tag('de-x-versal') # versal
3271 # ['de-x-versal', 'de']
3272 # >>> normalize_language_tag('de-1901-x-versal') # versal
3273 # ['de-1901-x-versal', 'de-1901', 'de-x-versal', 'de']
3274 # >>> normalize_language_tag('de_CH-1996') # Schweiz traditionell (süssauer)
3275 # ['de-CH-1996', 'de-CH', 'de-1996', 'de']
3277 # 'de': 1, # Deutsch, allgemeingültig
3278 # 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
3279 # 'de-1996': 3, # reformierte Reformschreibung (1996)
3280 # 'de-x-versal': 4, # ohne ß (Schweiz oder versal) allgemein
3281 # # 'de-CH': 4, # Alias
3282 # 'de-1901-x-versal': 5, # ohne ß (Schweiz oder versal) "traditionell"
3283 # 'de-1996-x-versal': 6, # ohne ß (Schweiz oder versal) "reformiert"
3284 # # 'de-CH-1996': 6, # Alias
3285 # 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
3288 # ::
3290 def normalize_language_tag(tag):
3291 """Return a list of normalized combinations for a `BCP 47` language tag.
3293 # normalize:
3294 tag = tag.replace('_','-')
3295 # split (except singletons, which mark the following tag as non-standard):
3296 tag = re.sub(r'-([a-zA-Z0-9])-', r'-\1_', tag)
3297 taglist = []
3298 subtags = [subtag.replace('_', '-') for subtag in tag.split('-')]
3299 base_tag = [subtags.pop(0)]
3300 # find all combinations of subtags
3301 for n in range(len(subtags), 0, -1):
3302 # for tags in unique_combinations(subtags, n):
3303 for tags in itertools.combinations(subtags, n):
3304 taglist.append('-'.join(base_tag+list(tags)))
3305 taglist += base_tag
3306 return taglist
3310 # Tests
3311 # =====
3313 # Teste Übereinstimmung des ungetrennten Wortes in Feld 1 mit den
3314 # Trennmustern nach Entfernen der Trennmarker. Schreibe Inkonsistenzen auf die
3315 # Standardausgabe.
3317 # Das Argument ist ein Iterator über die Einträge (Klasse `WordEntry`). ::
3319 def test_keys(wortliste):
3320 print u"Teste Schlüssel-Trennmuster-Übereinstimmung:"
3321 is_OK = True
3322 for entry in wortliste:
3323 if isinstance(entry, ShortEntry):
3324 print u"Wortliste im Kurzformat: überspringe Schlüssel-Test."
3325 return
3326 # Test der Übereinstimmung ungetrenntes/getrenntes Wort
3327 # für alle Felder:
3328 key = entry.key()
3329 for wort in entry[1:]:
3330 if not wort: # leere Felder
3331 continue
3332 if key != join_word(wort):
3333 is_OK = False
3334 print u"\nkey '%s' != join_word('%s')" % (key, wort),
3335 if key.lower() == join_word(wort).lower():
3336 print(u" Abgleich der Großschreibung mit"
3337 u"`prepare-patch.py grossabgleich`."),
3338 if is_OK:
3339 print u"OK"
3342 # Finde Doppeleinträge (teste, ob jeder Schlüssel nur einmal vorkommt).
3343 # Schreibe Inkonsistenzen auf die Standardausgabe.
3345 # Das Argument ist ein Iterator über die Einträge (Klasse `WordEntry`). ::
3347 def test_uniqueness(wortliste):
3349 doppelte = 0
3350 words = {}
3351 for entry in wortliste:
3352 key = entry.key()
3353 if key in words:
3354 doppelte += 1
3355 print "da ", unicode(words[key])
3356 print "neu", unicode(entry)
3357 words[key] = entry
3358 print u"%d Doppeleinträge." % doppelte
3360 # Teste die Wandlung einer Zeile im "wortliste"-Format in eine
3361 # ``WordEntry``-Instanz und zurück::
3363 def test_str_entry_str_conversion(wordfile):
3364 OK = 0
3365 for line in file(wordfile.name):
3366 line = line.rstrip().decode(wordfile.encoding)
3367 entry = WordEntry(line)
3368 if line == unicode(entry):
3369 OK +=1
3370 else:
3371 print u'-', line,
3372 print u'+', unicode(entry)
3374 print OK, u"Einträge rekonstruiert"
3377 # Teste Vervollständigung und Zusammenfassung von Einträgen::
3379 def test_completion_pruning(entries):
3380 reko = []
3381 for entry in entries:
3382 new = copy.copy(entry)
3383 new.complete()
3384 new.prune()
3385 reko.append(new)
3386 patch = udiff(entries, reko, 'wortliste', 'neu')
3387 if patch:
3388 print patch
3389 else:
3390 print u"alle Einträge rekonstruiert"
3394 # Aufruf von der Kommandozeile
3395 # ============================
3397 # ::
3399 if __name__ == '__main__':
3400 import sys
3402 # sys.stdout mit UTF8 encoding (wie in Python 3)
3403 sys.stdout = codecs.getwriter('UTF-8')(sys.stdout)
3404 # sys.stderr = codecs.getwriter('UTF-8')(sys.stderr)
3406 print u"Test der Werkzeuge und inneren Konsistenz der Wortliste"
3408 # Ein WordFile Dateiobjekt::
3410 try:
3411 wordfile = WordFile(sys.argv[1], format='auto')
3412 except IndexError:
3413 wordfile = WordFile('../../../wortliste')
3414 # wordfile = WordFile('../../../wlst', format='auto') # wortliste im Kurzformat
3415 # wordfile = WordFile('neu.todo')
3416 # wordfile = WordFile('neu-kurz.todo', format='f5')
3417 # wordfile = WordFile('korrektur.todo', format='auto')
3419 # Format bestimmen::
3421 print "Datei: '%s'" % wordfile.name, u"Format:", wordfile.format
3423 # Liste der Datenfelder (die Klasseninstanz als Argument für `list` liefert
3424 # den Iterator über die Felder, `list` macht daraus eine Liste)::
3426 wordlist = list(wordfile)
3427 print len(wordlist), u"Zeilen"
3429 # Ein Wörterbuch (dict Instanz)::
3431 wordfile.seek(0) # Pointer zurücksetzen
3432 words = wordfile.asdict()
3433 print len(words), u"Wörterbucheinträge"
3435 # Test auf Doppeleinträge::
3437 if len(words) != len(wordlist):
3438 test_uniqueness(wordlist)
3441 # Teste Schlüssel-Trennmuster-Übereinstimmung::
3443 if isinstance(wordlist[0], WordEntry):
3444 test_keys(wordlist)
3446 # Teste Eintrags-Konsistenz im Kurzformat::
3448 if isinstance(wordlist[0], ShortEntry):
3449 doppelte = 0
3450 langs = ("de", "de-1901", "de-CH", "de-1901-x-versal", "de-CH-1901")
3451 for lang in langs:
3452 words[lang] = {}
3454 for entry in wordlist:
3455 entry.complete()
3456 for lang in langs:
3457 word = entry.get(lang)
3458 key = join_word(word)
3459 if word and (key in words[lang]):
3460 oldword = words[lang][key].get(lang)
3461 if oldword and (word != oldword):
3462 doppelte += 1
3463 print "%-16s" % lang, oldword, '!=', word
3464 print " "*18, unicode(words[lang][key].pruned())
3465 print " "*18, unicode(entry.pruned())
3466 words[lang][key] = entry
3467 print u"%d Doppeleinträge." % doppelte
3470 # Teste Komplettieren/Zusammenfassen der Einträge::
3472 # test_completion_pruning(wordlist)
3474 # Sprachauswahl::
3476 # Sprachtags:
3478 # sprache = 'de-1901' # traditionell
3479 sprache = 'de-1996' # Reformschreibung
3480 # sprache = 'de-x-versal' # ohne ß (Schweiz oder versal) allgemein
3481 # sprache = 'de-1901-x-versal' # ohne ß (Schweiz oder versal) "traditionell"
3482 # sprache = 'de-1996-x-versal' # ohne ß (Schweiz oder versal) "reformiert"
3483 # sprache = 'de-CH-1901' # ohne ß (Schweiz) "traditionell" ("süssauer")
3485 # worte = [entry.get(sprache) for entry in wordlist]
3486 # worte = [wort for wort in worte if wort]
3487 # print len(worte), u"Einträge für Sprachvariante", sprache
3489 # Zeilenrekonstruktion::
3491 # test_str_entry_str_conversion(wordfile)
3494 # Quellen
3495 # =======
3497 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
3498 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
3500 # .. _aktuelle Rechtschreibung:
3502 # .. [Rechtschreibregeln] Rat für deutsche Rechtschreibung,
3503 # `Deutsche Rechtschreibung – Regeln und Wörterverzeichnis`,
3504 # http://www.rechtschreibrat.com/regeln-und-woerterverzeichnis/
3506 # .. _traditionelle Rechtschreibung:
3508 # .. [Duden1991] Wissenschaftlicher Rat der Dudenredaktion (Editoren),
3509 # `Duden: Rechtschreibung der deutschen Sprache`,
3510 # Dudenverlag Mannheim, 1991.
3512 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
3513 # ../../../dokumente/README.wortliste
3515 # .. _wortliste: ../../../wortliste