Kleine Fixes der Python-Skripte.
[wortliste.git] / skripte / python / edit_tools / wortliste.py
blob82e78e62ba509d2f7290812381dc06aa653269e3
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
3 # :Copyright: © 2012 Günter Milde.
4 # :Licence: This work may be distributed and/or modified under
5 # the conditions of the `LaTeX Project Public License`,
6 # either version 1.3 of this license or (at your option)
7 # any later version.
8 # :Version: 0.1 (2012-02-07)
10 # wortliste.py
11 # ***********
13 # ::
15 """Hilfsmittel für die Arbeit mit der `Wortliste`"""
17 # .. contents::
19 # Die hier versammelten Funktionen und Klassen dienen der Arbeit an und
20 # mit der freien `Wortliste der deutschsprachigen Trennmustermannschaft`_
21 # ("Lembergsche Liste")
23 # Vorspann
25 # ::
27 import difflib
28 import re
29 import codecs
30 import unicodedata
31 import copy
32 import itertools
34 # WordFile
35 # ========
37 # Klasse zum Lesen und Schreiben der `Wortliste`::
39 class WordFile(file):
41 encoding = 'utf8'
43 # Initialisierung
44 # ----------------
46 # Das Argument ``format`` ist "f8", "f5", oder "auto":
48 # :f8: das originale, maximal 8-spaltige Wortlisten-Format,
49 # :f5: das neue, maximal 5-spaltige Wortlisten-Format,
50 # :auto: bestimme das Format automatisch.
52 # ::
54 def __init__(self, name, mode='r', encoding='utf8', format='f8'):
56 self.encoding = encoding
57 file.__init__(self, name, mode)
58 # Dateiformat bestimmen und die Eintrags-Klasse setzen:
59 self.set_entry_class(format)
61 # Klasse zum Verarbeiten der Zeilen setzen.
62 # Mit self.format == "auto", werden die ersten Zeilen der Datei untersucht
63 # und der Pointer zurückgesetzt.
65 # ::
67 def set_entry_class(self, format, search_limit=1000):
69 assert(format in ('f8', 'f5', 'auto'))
70 self.format = format # default
71 self.entry_class = WordEntry # default
72 n = 0 # Zählindex
74 # Format anhand des Dateiinhalts bestimmen:
75 if format == 'auto':
76 for e in self:
77 # nur ein Feld oder erstes Feld leer oder mit Trennzeichen:
78 if len(e) == 1 or e and re.search(u'[-=<>]', e[0]):
79 self.format = 'f5'
80 break
81 # mehr als 5 Felder: 8-Felder-Format (Langform)
82 elif len(e) > 5:
83 self.format = 'f8'
84 break
85 if n > search_limit:
86 break
87 n += 1
88 if self.format == 'f5':
89 self.entry_class = ShortEntry
91 self.seek(0) # Dateizeiger zurücksetzen
94 # Iteration
95 # ---------
97 # Die spezielle Funktion `__iter__` wird aufgerufen wenn über eine
98 # Klasseninstanz iteriert wird.
99 # Sie liefert einen Iterator über die "geparsten" Zeilen (Datenfelder)
100 # ::
102 def __iter__(self):
103 entry_class = self.entry_class
104 while True:
105 line = self.next().rstrip().decode(self.encoding)
106 yield entry_class(line)
109 # asdict
110 # ------
112 # Lies Datei und trage die Zeilen mit ungetrenntem Wort
113 # als `key` und den Datenfeldern als `value` in ein `dictionary`
114 # (assoziatives Array) ein::
116 def asdict(self):
117 words = dict()
118 for entry in self:
119 words[entry[0]] = entry
120 return words
122 # writelines
123 # -----------
125 # Schreibe eine Liste von `unicode` Strings (Zeilen ohne Zeilenendezeichen)
126 # in die Datei `destination`::
128 def writelines(self, lines, destination, encoding=None):
129 outfile = codecs.open(destination, 'w',
130 encoding=(encoding or self.encoding))
131 # outfile.write(u'\n'.join(lines))
132 # outfile.write(u'\n')
133 outfile.writelines(lines)
135 # write_entries
136 # -------------
138 # Schreibe eine Liste von Einträgen (WordEntry/ShortEntry Objekten) in
139 # die Datei `destination`::
141 def write_entries(self, wortliste, destination, encoding=None):
142 lines = (unicode(entry) for entry in wortliste)
143 self.writelines(lines, destination, encoding)
146 # WordEntry
147 # =========
149 # Klasse für Einträge (Zeilen) der Wortliste
151 # Beispiel:
153 # >>> from wortliste import WordEntry, ShortEntry
155 # >>> aalbestand = WordEntry(u'Aalbestand;Aal=be<stand # Test')
156 # >>> print aalbestand
157 # Aalbestand;Aal=be<stand # Test
159 # ::
161 class WordEntry(list):
163 # Argumente
164 # ---------
166 # Kommentare (aktualisiert, wenn Kommentar vorhanden)::
168 comment = u''
170 # Feldbelegung
171 # ~~~~~~~~~~~~
173 # 1. Wort ungetrennt
174 # 2. Wort mit Trennungen, falls für alle Varianten identisch,
175 # anderenfalls leer
176 # 3. falls Feld 2 leer, Trennung nach traditioneller Rechtschreibung
177 # 4. falls Feld 2 leer, Trennung nach reformierter Rechtschreibung (2006)
178 # 5. falls Feld 2 leer, Trennung für Wortform, die entweder in
179 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird
180 # und für traditionelle und reformierte Rechtschreibung identisch ist
181 # 6. falls Feld 5 leer, Trennung für Wortform, die entweder in
182 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
183 # traditionelle Rechtschreibung
184 # 7. falls Feld 5 leer, Trennung für Wortform, die entweder in
185 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
186 # reformierte Rechtschreibung (2006)
187 # 8. falls Feld 5 leer, Trennung nach (deutsch)schweizerischer
188 # Rechtschreibung; insbesondere Wörter mit "sss" gefolgt von
189 # einem Vokal, die wie andere Dreifachkonsonanten gehandhabt wurden
190 # (also anders, als der Duden früher vorgeschrieben hat), z.B.
191 # "süssauer"
193 # Rechtschreibvarianten (Tags nach [BCP47]_) (Die Zählung der Indizes beginn
194 # in Python bei 0)::
196 feldindizes = {
197 'key': 0, # Schlüssel
198 'de': 1, # Deutsch, allgemeingültig
199 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
200 'de-1996': 3, # reformierte Reformschreibung (1996)
201 'de-x-GROSS': 4, # ohne ß (Schweiz oder versal) allgemein
202 'de-x-versal': 4, # Alias
203 # 'de-CH': 4, # Alias
204 'de-1901-x-GROSS': 5, # ohne ß (Schweiz oder versal) "traditionell"
205 'de-1901-x-versal': 5, # ohne ß (Schweiz oder versal) "traditionell"
206 'de-1996-x-GROSS': 6, # ohne ß (Schweiz oder versal) "reformiert"
207 'de-1996-x-versal': 6, # ohne ß (Schweiz oder versal) "reformiert"
208 # 'de-CH-1996': 6, # Alias
209 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
212 feldnamen = ('key', 'de', 'de-1901', 'de-1996', 'de-x-versal',
213 'de-1901-x-versal', 'de-1996-x-versal', 'de-CH-1901')
216 # Ersatzregeln:
218 # Bei gleicher Trennung werden Felder weggelassen. Bei der Auswahl eines
219 # Wortes in einer Rechtschreibvariante werden Ersatzfelder gewählt, falls das
220 # "Originalfeld" fehlt. Die folgenden Tupel listen die Ersatzfelder für das
221 # ensprechende Originalfeld.
223 # Bsp: Ersatz-Felder für 'de-CH-1901':
225 # >>> for i in WordEntry.ersatzfelder[WordEntry.feldindizes['de-CH-1901']]:
226 # ... print WordEntry.feldnamen[i]
227 # de-1901-x-versal
228 # de-x-versal
229 # de-1901
230 # de
232 # ::
234 # Ersatzindizes Index Nr Name
235 ersatzfelder = (tuple(), # 0 1 key
236 tuple(), # 1 2 'de'
237 (1,), # 2 3 'de-1901'
238 (1,), # 3 4 'de-1996'
239 (1,), # 4 5 'de-x-versal'
240 (4, 2, 1), # 5 6 'de-1901-x-versal'
241 (4, 3, 1), # 6 7 'de-1996-x-versal'
242 (5, 4, 2, 1), # 7 8 'de-CH-1901'
246 # Initialisierung
247 # ---------------
249 # ::
252 def __init__(self, line, delimiter=u';'):
254 self.delimiter = delimiter
256 # eventuell vorhandenen Kommentar abtrennen und speichern::
258 if u'#' in line:
259 line, comment = line.split(u'#', 1)
260 self.comment = comment.lstrip()
261 line = line.rstrip()
262 # print (line, self.comment)
264 if not line: # kein Inhalt
265 return
267 # Zerlegen in Datenfelder, in Liste eintragen::
269 for field in line.split(delimiter):
270 if field.startswith(u'-'):
271 self.append(u'')
272 else:
273 self.append(field)
276 # Rückverwandlung in String
277 # -----------------------------------
279 # Erzeugen eines Eintrag-Strings (Zeile) aus der Liste der Datenfelder und
280 # dem Kommentar
282 # >>> unicode(aalbestand)
283 # u'Aalbestand;Aal=be<stand # Test'
284 # >>> leerkommentar = WordEntry(u'# Testkommentar')
285 # >>> leerkommentar
286 # []
287 # >>> unicode(leerkommentar)
288 # u'# Testkommentar'
290 # ::
292 def __unicode__(self):
293 # Umnummerieren leerer Felder:
294 fields = [field or u'-%d-' % (i+1)
295 for (i, field) in enumerate(self)]
296 line = u';'.join(fields)
297 if self.comment:
298 line = u' # '.join((line, self.comment)).lstrip()
299 return line
301 def __str__(self):
302 return unicode(self).encode('utf8')
304 # lang_index
305 # ---------------
307 # Index des zur Sprachvariante gehörenden Datenfeldes:
309 # >>> aalbestand.lang_index('de')
311 # >>> aalbestand.lang_index('de-1901')
313 # >>> aalbestand.lang_index('de-1996')
315 # >>> aalbestand.lang_index('de-x-versal')
317 # >>> aalbestand.lang_index('de-1901-x-versal')
319 # >>> aalbestand.lang_index('de-1996-x-versal')
321 # >>> abbeissen = WordEntry(
322 # ... u'abbeissen;-2-;-3-;-4-;-5-;ab<bei-ssen;ab<beis-sen;ab<beis-sen')
323 # >>> print abbeissen.lang_index('de')
324 # None
325 # >>> print abbeissen.lang_index('de-x-versal')
326 # None
327 # >>> abbeissen.lang_index('de-1901-x-versal')
329 # >>> abbeissen.lang_index('de-1996-x-versal')
331 # >>> abbeissen.lang_index('de-CH-1901')
333 # >>> urlaubstipp = WordEntry(u'Urlaubstipp;-2-;-3-;Ur<laubs=tipp')
334 # >>> print urlaubstipp.lang_index('de')
335 # None
336 # >>> print urlaubstipp.lang_index('de-1901')
337 # None
338 # >>> print urlaubstipp.lang_index('de-1996')
340 # >>> print urlaubstipp.lang_index('de-x-versal')
341 # None
342 # >>> print urlaubstipp.lang_index('de-1901-x-versal')
343 # None
345 # ::
347 def lang_index(self, lang):
349 assert lang in self.feldindizes, \
350 'Sprachvariante "%s" nicht in %s' % (lang,
351 self.feldindizes.keys())
353 # Einfacher Fall: eine allgemeine Schreibweise::
355 if len(self) == 2:
356 if u'ß' in self[0] and lang in ('de-CH-1901', 'de-1901-x-versal',
357 'de-CH-1996', 'de-1996-x-versal'):
358 return None
359 else:
360 return 1
362 # Spezielle Schreibung::
364 try:
365 i = self.feldindizes[lang]
366 feld = self[i]
367 except IndexError:
368 if lang == 'de-CH-1901':
369 return self.lang_index('de-1901-x-versal')
370 # Allgemeine Schweiz/versal Schreibung:
371 if i > 4 and len(self) == 5:
372 return 4
373 # versal == normal (kein sz):
374 if len(self) == 4 and u'ß' not in self[0]:
375 if lang in ('de-CH-1901', 'de-1901-x-versal'):
376 return self.lang_index('de-1901')
377 elif lang in ('de-CH-1996', 'de-1996-x-versal'):
378 return self.lang_index('de-1996')
379 return None # Feld nicht vorhanden
381 if not feld: # leeres Feld
382 return None
384 return i
386 # Schlüssel
387 # ---------
389 # Bei Einträgen im 8-Feld-Format ist der Schlüssel gleich dem Inhalt des
390 # ersten Feldes. Das `lang` Argument wird ignoriert, da der Schlüssel (im
391 # Gegensatz zum 5-Feld-Format) stets eindeutig ist::
393 def key(self, lang=None):
394 """Gib den Schlüssel (ungetrenntes Wort) zurück."""
395 try:
396 return self[0]
397 except IndexError: # reiner Kommentar oder leerer Eintrag
398 if self.comment:
399 return u'#'
400 return u''
403 def getitem(self, i):
404 """Return item ``i`` or a subsititute"""
405 try:
406 return self[i]
407 except IndexError: # Feld i nicht vorhanden
408 pass
409 # Ersatzregeln anwenden
410 for _i in self.ersatzfelder[i]:
411 try:
412 word = self[_i]
413 except IndexError: # Feld i nicht vorhanden
414 continue # nächsten `tag` versuchen
415 # Spezialfall: in Versalschreibung ungültige Ersatz-Wörter
416 if i >= 4 and _i <4 and u'ß' in word:
417 return u''
418 return word
419 return u''
422 # getitem(): Gib Feld ``i`` oder Ersatzfeld zurück.
424 def getitem(self, i):
425 """Return item ``i`` or a subsititute"""
426 try:
427 return self[i]
428 except IndexError: # Feld i nicht vorhanden
429 pass
430 # Ersatzregeln anwenden
431 for _i in self.ersatzfelder[i]:
432 try:
433 word = self[_i]
434 except IndexError: # Feld i nicht vorhanden
435 continue # nächsten `tag` versuchen
436 # Spezialfall: in Versalschreibung ungültige Ersatz-Wörter
437 if i >= 4 and _i <4 and u'ß' in word:
438 return u''
439 return word
440 return u''
443 # Trennmuster für eine Sprachvariante ausgeben
445 # >>> aalbestand.get('de')
446 # u'Aal=be<stand'
447 # >>> aalbestand.get('de-1901')
448 # u'Aal=be<stand'
449 # >>> aalbestand.get('de-1996')
450 # u'Aal=be<stand'
451 # >>> aalbestand.get('de-x-versal')
452 # u'Aal=be<stand'
453 # >>> aalbestand.get('de-1901-x-versal')
454 # u'Aal=be<stand'
455 # >>> aalbestand.get('de-1996-x-versal')
456 # u'Aal=be<stand'
457 # >>> aalbestand.get('de-CH-1901')
458 # u'Aal=be<stand'
460 # >>> weste = WordEntry(u'Weste;-2-;We-ste;Wes-te')
461 # >>> weste.get('de')
462 # u''
463 # >>> weste.get('de-1901')
464 # u'We-ste'
465 # >>> weste.get('de-1996')
466 # u'Wes-te'
468 # >>> abbeissen.get('de')
469 # u''
470 # >>> abbeissen.get('de-x-versal')
471 # u''
472 # >>> abbeissen.get('de,de-x-versal')
473 # u''
474 # >>> abbeissen.get('de-1901-x-versal')
475 # u'ab<bei-ssen'
476 # >>> abbeissen.get('de,de-1901,de-1901-x-versal')
477 # u'ab<bei-ssen'
478 # >>> abbeissen.get('de-CH-1901')
479 # u'ab<beis-sen'
481 # ::
483 def get(self, tags):
484 for lang in tags.split(','):
485 word = self.getitem(self.feldindizes[lang])
486 if word:
487 return word
488 return u''
491 # Trennmuster für Sprachvariante setzen
493 # >>> abbeissen.set('test', 'de-1901-x-versal')
494 # >>> print abbeissen
495 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
497 # >>> abbeissen.set('test', 'de-1901')
498 # Traceback (most recent call last):
499 # ...
500 # IndexError: kann kein leeres Feld setzen
502 # >>> abbeissen.set('test', 'de-1901,de-1901-x-versal')
503 # >>> print abbeissen
504 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
506 # ::
508 def set(self, word, tags):
509 for lang in tags.split(','):
510 i = self.lang_index(lang)
511 if i is None:
512 continue
513 if word is None:
514 word = u''
515 self[i] = word
516 return
517 raise IndexError, "kann kein leeres Feld setzen"
519 # setitem(): Feld ``i`` setzen.
521 # >>> entry = WordEntry(u'test;test')
522 # >>> entry.setitem(4, u's-x')
523 # >>> print entry
524 # test;test;-3-;-4-;s-x
525 # >>> entry.setitem(3, u'sz')
526 # >>> print entry
527 # test;test;-3-;sz;s-x
529 # ::
531 def setitem(self, i, word):
532 while len(self) < i+1:
533 self.append(u'')
534 self[i] = word
536 # complete(): Alle Felder setzen.
538 # >>> print str(aalbestand), len(aalbestand)
539 # Aalbestand;Aal=be<stand # Test 2
540 # >>> print aalbestand.completed()
541 # Aalbestand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand # Test
543 # >>> auffrass = WordEntry(u'auffrass;-2-;-3-;-4-;auf-frass')
544 # >>> print auffrass.completed()
545 # auffrass;-2-;-3-;-4-;auf-frass;auf-frass;auf-frass;auf-frass
547 # >>> fresssack= WordEntry(u'Fresssack;-2-;-3-;Fress=sack;Fress=sack')
548 # >>> print fresssack.completed()
549 # Fresssack;-2-;-3-;Fress=sack;Fress=sack;Fress=sack;Fress=sack;Fress=sack
551 # >>> line = u'Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-'
552 # >>> print WordEntry(line).completed()
553 # Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-;Fluss=schi{ff/ff=f}ahrt
555 # >>> line = u'Ackerstraße;-2-;A{ck/k-k}er=stra-ße;Acker=stra-ße'
556 # >>> ackerstrasse = WordEntry(line).completed()
557 # >>> ackerstrasse
558 # [u'Ackerstra\xdfe', u'', u'A{ck/k-k}er=stra-\xdfe', u'Acker=stra-\xdfe', u'', u'', u'', u'']
560 # ::
562 def complete(self):
563 for i in range(len(self),8):
564 self.append(self.getitem(i))
566 def completed(self):
567 neu = copy.copy(self)
568 neu.complete()
569 return neu
572 # Felder für Sprachvarianten zusammenfassen, wenn gleich
574 # >>> aalbestand.prune()
575 # >>> print aalbestand
576 # Aalbestand;Aal=be<stand # Test
577 # >>> auffrass.prune()
578 # >>> print auffrass
579 # auffrass;-2-;-3-;-4-;auf-frass
580 # >>> entry = WordEntry(u'distanziert;-2-;di-stan-ziert;di-stan-ziert')
581 # >>> entry.prune()
582 # >>> print entry
583 # distanziert;di-stan-ziert
584 # >>> entry = WordEntry(u'Gauss;-2-;Gauss;Gauss;Gauss')
585 # >>> entry.prune()
586 # >>> print entry
587 # Gauss;Gauss
588 # >>> fresssack.prune()
589 # >>> print fresssack
590 # Fresssack;-2-;-3-;Fress=sack;Fress=sack
592 # >>> masse = WordEntry(u'Masse;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se')
593 # >>> masse.prune()
594 # >>> print masse
595 # Masse;Mas-se
597 # Aber nicht, wenn die Trennstellen sich unterscheiden:
599 # >>> abenddienste = WordEntry(
600 # ... u'Abenddienste;-2-;Abend=dien-ste;Abend=diens-te')
601 # >>> abenddienste.prune()
602 # >>> print abenddienste
603 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
604 # >>> ackerstrasse.prune()
605 # >>> print unicode(ackerstrasse)
606 # Ackerstraße;-2-;A{ck/k-k}er=stra-ße;Acker=stra-ße
607 # >>> entry = WordEntry(
608 # ... u'Schlammmasse;-2-;-3-;Schlamm=mas-se;-5-;-6-;Schlamm=mas-se;-8-')
609 # >>> entry.prune()
610 # >>> print entry
611 # Schlammmasse;-2-;-3-;Schlamm=mas-se
612 # >>> entry = WordEntry(
613 # ... u'Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-')
614 # >>> entry.prune()
615 # >>> print entry
616 # Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-
618 # ::
620 def prune(self):
621 if len(self) == 8:
622 if self[7] == self[5]: # de-CH-1901 gleich versal-1901
623 self.pop()
624 if len(self) == 7:
625 if self[6] == self[5]:
626 self[4] = self[5] # umschreiben auf versal-allgemein
627 self.pop()
628 self.pop()
629 elif (not(self[4] or self[5] or self[6])
630 or self[5] == self[2] and self[6] == self[3]):
631 # alle leer oder ohne ß-Ersetzung
632 self.pop()
633 self.pop()
634 self.pop()
635 if len(self) == 5:
636 if not self[4]:
637 self.pop()
638 elif self[1] == self[4]: # de-x-versal == de
639 self.pop()
640 self.pop()
641 self.pop()
642 elif self[2] == self[3] == self[4]:
643 self[1] = self[2] # Umschreiben auf de (allgemein)
644 self.pop()
645 self.pop()
646 self.pop()
647 if len(self) == 4:
648 if self[3] == self[2]: # de-1996 == de-1901
649 self[1] = self[2] # Umschreiben auf de (allgemein)
650 self.pop()
651 self.pop()
653 if len(self) > 6:
654 self[4] = u''
655 if len(self) > 2:
656 self[1] = u''
659 # Einträge zusammenfassen
661 # >>> entry = WordEntry(u'Abenddress;Abend=dress')
662 # >>> entry.merge(WordEntry(u'Abenddress;-2-;-3-;-4-;Abend=dress'))
663 # >>> print unicode(entry)
664 # Abenddress;Abend=dress
665 # >>> entry = WordEntry(u'Gauss;Gauss')
666 # >>> entry.merge(WordEntry(u'Gauss;-2-;-3-;-4-;Gauss'))
667 # >>> print unicode(entry)
668 # Gauss;Gauss
669 # >>> masse.merge(WordEntry(u'Masse;-2-;-3-;-4-;-5-;Ma-sse;Mas-se;Mas-se'), allow_alternatives=True)
670 # >>> print masse
671 # Masse;-2-;Mas-se;Mas-se;-5-;[Mas-se/Ma-sse];Mas-se;Mas-se
673 # ::
675 def merge(self, other, allow_alternatives=False, prune=True):
676 self.complete()
677 other.complete()
678 conflict = False
679 for i, (s_i, o_i) in enumerate(zip(self,other)):
680 if not o_i:
681 continue
682 if not s_i:
683 self[i] = o_i
684 other[i] = u''
685 elif s_i == o_i:
686 other[i] = u''
687 elif s_i != o_i:
688 if allow_alternatives:
689 self[i] = u'[%s/%s]' % (s_i, o_i)
690 o_i = u''
691 else:
692 conflict = True
693 # self.comment = other.comment or self.comment
694 if self.comment != other.comment:
695 conflict = True
697 if prune:
698 other.prune()
699 self.prune()
701 if conflict:
702 raise AssertionError(u'Merge Error:\n %s\n %s '
703 % (unicode(self), unicode(other)))
706 # Prüfe auf Vorkommen von Regeländerungen der Orthographiereform 1996.
708 # >>> entry = WordEntry(u'Würste;Wür-ste')
709 # >>> entry.regelaenderungen()
710 # >>> print unicode(entry)
711 # Würste;-2-;Wür-ste;Würs-te
712 # >>> entry = WordEntry(u'Würste;Würs-te')
713 # >>> entry.regelaenderungen()
714 # >>> print unicode(entry)
715 # Würste;-2-;Wür-ste;Würs-te
716 # >>> entry = WordEntry(u'Hecke;He-cke')
717 # >>> entry.regelaenderungen()
718 # >>> print unicode(entry)
719 # Hecke;-2-;He{ck/k-k}e;He-cke
720 # >>> entry = WordEntry(u'Ligusterhecke;Ligu-ster=he{ck/k-k}e')
721 # >>> entry.regelaenderungen()
722 # >>> print unicode(entry)
723 # Ligusterhecke;-2-;Ligu-ster=he{ck/k-k}e;Ligus-ter=he-cke
724 # >>> entry = WordEntry(u'Hass;Hass')
725 # >>> entry.regelaenderungen()
726 # >>> print unicode(entry)
727 # Hass;-2-;-3-;Hass;Hass
728 # >>> entry = WordEntry(u'fasst;fasst')
729 # >>> entry.regelaenderungen()
730 # >>> print unicode(entry)
731 # fasst;-2-;-3-;fasst;fasst
732 # >>> entry = WordEntry(u'Missbrauch;Miss<brauch')
733 # >>> entry.regelaenderungen()
734 # >>> print unicode(entry)
735 # Missbrauch;-2-;-3-;Miss<brauch;Miss<brauch
736 # >>> entry = WordEntry(u'schlifffest;schliff=fest')
737 # >>> entry.regelaenderungen()
738 # >>> print unicode(entry)
739 # schlifffest;-2-;-3-;schliff=fest
741 # ::
743 def regelaenderungen(self):
744 # Trennregeländerungen:
745 r1901 = (u'-st', u'{ck/k-k}')
746 r1996 = (u's-t', u'-ck')
748 w1901 = self.get('de-1901')
749 w1996 = self.get('de-1996')
750 w_versal = None
752 if w1901 is None or w1996 is None:
753 return
755 for r1, r2 in zip(r1901, r1996):
756 w1901 = w1901.replace(r2,r1)
757 w1996 = w1996.replace(r1,r2)
759 # kein Schluss-ss und sst in de-1901 (ungetrenntes "ss" nur in Ausnahmen)
760 # aber: 'ßt' und Schluß-ß auch in de-1996 möglich (langer Vokal)
761 if u'ss' in w1901:
762 w_versal = w1901
763 w1901 = None
765 # Dreikonsonantenregel:
766 if w1901 and re.search(ur'(.)\1=\1', w1901):
767 w1901 = None
769 # Speichern:
770 if w1901 == w1996: # keine Regeländerung im Wort
771 if len(self) > 2:
772 self.prune()
773 return
775 if w1901 is None:
776 self.extend( ['']*(4-len(self)) )
777 self[1] = u''
778 self[2] = u''
779 self[3] = w1996
780 else:
781 self.extend( ['']*(4-len(self)) )
782 self[1] = u''
783 self[2] = w1901
784 self[3] = w1996
785 if w_versal:
786 self.append(w_versal)
789 # ShortEntry
790 # ==========
792 # Klasse für Einträge (Zeilen) der Wortlisten (neues, kurzes Format)
794 # >>> from wortliste import ShortEntry
796 # >>> aalbst = ShortEntry(u'Aal=be<stand # Test')
798 # ::
800 class ShortEntry(WordEntry):
802 # Feldbelegung im Kurzformat (Vorschlag)
804 # Sprachtags nach [BCP47]_.
806 # de:
807 # Wort mit Trennungen nach aktueller Rechtschreibung (de-1996).
808 # Einziges Feld, falls andere Varianten über Regeln gewonnen werden können.
810 # "-1-" falls die Schreibung in de-1996 unzulässig ist (-1-;Pro<zeß).
812 # de-1901:
813 # Wort mit Trennung nach de-1901.
814 # Belegt, falls abweichend von der regelbasierten Ableitung aus "de".
816 # "-2-" falls die Schreibung in de-1901 unzulässig ist (Ur<laubs=tipp;-2-).
818 # de-CH oder de-x-versal:
819 # Wort mit ß-Ersatzschreibung, die entweder in
820 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird.
821 # Trennungen nach aktueller Rechtschreibung (de-CH-1996, de-1996-x-versal).
823 # "-3-" falls die Schreibung in de-CH-1996 unzulässig ist.
825 # de-1901-x-versal:
826 # Wort mit ß-Ersatzschreibung für de-1901 mit Großbuchstaben oder
827 # Kapitälchen.
828 # Belegt, falls abweichend von der Ableitung aus "de-x-versal".
830 # "-4-" falls die abgeleitete Schreibung in de-1901 unzulässig ist.
832 # de-CH-1901:
833 # Wort mit ß-Ersatzschreibung, die der Schweiz benutzt wird.
834 # Insbesondere Wörter mit „sss“ gefolgt von einem Vokal, die wie
835 # andere Dreifachkonsonanten gehandhabt wurden (also anders, als
836 # bei Ersatzschreibung in Deutschland und Österreich), z.B. „süssauer“
837 # Belegt, falls abweichend von der regelbasierten Ableitung aus "de-CH".
839 # "-5-" falls die abgeleitete Schreibung in de-CH-1901 unzulässig ist.
841 # >>> print ShortEntry.feldnamen
842 # ('de', 'de-1901', 'de-CH', 'de-1901-x-versal', 'de-CH-1901')
843 # >>> print aalbst.feldindizes['de'], aalbst.feldindizes['de-CH-1901']
844 # 0 4
845 # >>> print aalbst.feldindizes['de-x-versal'], aalbst.feldindizes['de-CH']
846 # 2 2
848 # ::
850 feldindizes = {
851 'de': 0, # Deutsch, aktuell (Reform 1996)
852 'de-1996': 0, # Alias
853 'de-1901': 1, # "traditionell" (Reform 1901)
854 'de-CH': 2, # ohne ß (Schweiz oder versal) "aktuell"
855 'de-x-versal': 2, # Alias
856 'de-1996-x-versal': 2, # Alias
857 'de-1901-x-versal': 3, # ohne ß (Schweiz oder versal) "traditionell"
858 'de-CH-1901': 4, # ohne ß (Schweiz) "traditionell" ("süssauer")
861 feldnamen = ('de', 'de-1901', 'de-CH', 'de-1901-x-versal', 'de-CH-1901')
863 # Ersatzregeln:
865 # Wenn sich die getrennten Wörter über Ableitungsregeln gewinnen
866 # lassen, werden Felder weggelassen.
868 # Die folgenden Tupel listen die Ersatzfelder für das ensprechende
869 # Originalfeld.
871 # Bsp: Ersatz-Felder für 'de-CH-1901':
873 # >>> for i in ShortEntry.ersatzfelder[ShortEntry.feldindizes['de-CH-1901']]:
874 # ... print ShortEntry.feldnamen[i]
875 # de-1901
876 # de
878 # ::
880 # Ersatzindizes Index Name
881 ersatzfelder = (tuple(), # 0 'de'
882 (0,), # 1 'de-1901'
883 (0,), # 2 'de-CH'
884 (1, 0), # 3 'de-1901-x-versal'
885 (1, 0), # 4 'de-CH-1901'
889 # Beispiele:
891 # >>> print aalbst
892 # Aal=be<stand # Test
893 # >>> print ShortEntry(aalbestand)
894 # Aal=be<stand # Test
895 # >>> dst = ShortEntry(u'Diens-te;Dien-ste')
896 # >>> print dst
897 # Diens-te;Dien-ste
898 # >>> fluss = ShortEntry(u'Fluss;Fluß')
899 # >>> print unicode(fluss)
900 # Fluss;Fluß
901 # >>> tpp = ShortEntry(u'Ur<laubs=tipp;-2-')
902 # >>> print tpp
903 # Ur<laubs=tipp;-2-
904 # >>> drs = unicode(ShortEntry(u'-1-;Dreß'))
905 # >>> print drs
906 # -1-;Dreß
908 # # >>> print ShortEntry(u'# Testkommentar')
909 # u'# Testkommentar'
912 # Wird dem Konstruktor eine WordEntry-Instanz übergeben, so wird
913 # diese in das Kurzformat überführt:
915 # >>> print abenddienste
916 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
917 # >>> print ShortEntry(abenddienste)
918 # Abend=diens-te
919 # >>> print urlaubstipp
920 # Urlaubstipp;-2-;-3-;Ur<laubs=tipp
921 # >>> print ShortEntry(urlaubstipp)
922 # Ur<laubs=tipp;-2-
923 # >>> dress = WordEntry(u'Dress;Dress')
924 # >>> print ShortEntry(dress)
925 # Dress;Dress
926 # >>> dresz = WordEntry(u'Dreß;-2-;Dreß;-4-')
927 # >>> print unicode(ShortEntry(dresz))
928 # -1-;Dreß;-3-;-4-;-5-
929 # >>> boss = ShortEntry(WordEntry(u'Boss;Boss # en.'))
930 # >>> print boss
931 # Boss;Boss # en.
932 # >>> biss = ShortEntry(WordEntry(u'Biss;-2-;-3-;Biss'))
933 # >>> print biss
934 # Biss;-2-
935 # >>> g_ebene = WordEntry(u'Gaußebene;Gauß=ebe-ne')
936 # >>> print unicode(ShortEntry(g_ebene))
937 # Gauß=ebe-ne;Gauß=ebe-ne;-3-;-4-;-5-
938 # >>> print unicode(ShortEntry(WordEntry(u'Abfalllager;-2-;-3-;Ab<fall=la-ger')))
939 # Ab<fall=la-ger;-2-
940 # >>> print auffrass
941 # auffrass;-2-;-3-;-4-;auf-frass
942 # >>> print ShortEntry(auffrass)
943 # -1-;-2-;auf-frass;auf-frass;auf-frass
944 # >>> fraesse = WordEntry(u'frässe;-2-;-3-;-4-;-5-;frä-sse;fräs-se;fräs-se')
945 # >>> frs = ShortEntry(fraesse)
946 # >>> print unicode(frs)
947 # -1-;-2-;fräs-se;frä-sse;fräs-se
948 # >>> print ShortEntry(WordEntry(u'Fussballliga;-2-;-3-;-4-;-5-;-6-;Fuss=ball==li-ga'))
949 # -1-;-2-;Fuss=ball==li-ga
950 # >>> print ShortEntry(WordEntry(u'Fussballiga;-2-;-3-;-4-;-5-;Fuss=ba{ll/ll=l}i-.ga;-7-'))
951 # -1-;-2-;-3-;Fuss=ba{ll/ll=l}i-.ga;Fuss=ba{ll/ll=l}i-.ga
952 # >>> messignal = WordEntry(u'Messignal;-2-;-3-;-4-;-5-;-6-;-7-;Me{ss/ss=s}i-.gnal')
953 # >>> print unicode(ShortEntry(messignal))
954 # -1-;-2-;-3-;-4-;Me{ss/ss=s}i-.gnal
955 # >>> loesz = WordEntry(u'Lößboden;Löß=bo-den')
956 # >>> print unicode(ShortEntry(loesz))
957 # Löß=bo-den;Löß=bo-den;-3-;-4-;-5-
958 # >>> loess = WordEntry(u'Lössboden;-2-;-3-;Löss=bo-den;Löss=bo-den')
959 # >>> print unicode(ShortEntry(loess))
960 # Löss=bo-den;-2-;Löss=bo-den;Löss=bo-den;Löss=bo-den
961 # >>> print ShortEntry(WordEntry(u'Amnesty;Am-nes-ty # en.'))
962 # Am-nes-ty;Am-nes-ty # en.
963 # >>> print ShortEntry(WordEntry(u'# nur Kommentar'))
964 # # nur Kommentar
966 # ::
968 def __init__(self, line, delimiter=';', prune=True):
970 if type(line) == WordEntry:
971 self.comment = line.comment
972 self.append(line.getitem(3)) # Deutsch, aktuell (Reform 1996)
973 self.append(line.getitem(2)) # "traditionell" (Reform 1901)
974 self.append(line.getitem(6)) # ohne ß (Schweiz oder versal) "aktuell"
975 self.append(line.getitem(5)) # ohne ß (Schweiz oder versal) "traditionell"
976 self.append(line.getitem(7)) # ohne ß (Schweiz) "traditionell" ("süssauer")
977 # Felder zusammenfassen
978 if prune:
979 self.prune()
980 else:
981 WordEntry.__init__(self, line, delimiter)
984 # Schlüssel erzeugen
985 # ------------------
987 # Bei Einträgen im Kurzformat ist der Schlüssel nicht eindeutig:
988 # durch regelbasierte Ableitung kann ein Wort in verschiedenen
989 # Schreibungen vorkommen.
990 # Standardmäig wird das erste nichtleere Feld ohne Trennzeichen, bei Angabe
991 # des optionalen Arguments ``lang`` die Schreibweise in dieser Rechtschreibung
992 # verwendet.
994 # >>> print fluss.key()
995 # Fluss
996 # >>> print ShortEntry(u'-1-;Dreß').key()
997 # Dreß
999 # Die Key-Auswahl kann über das `lang` Argument gesteuert werden:
1001 # >>> print ShortEntry(u'-1-;Dreß').key('de-CH-1901')
1002 # Dress
1004 # ::
1006 def key(self, lang=None):
1007 """Erstelle einen Schlüssel (ungetrenntes Wort)."""
1008 if lang:
1009 return join_word(self.get(lang))
1010 for f in self:
1011 if f:
1012 return join_word(f)
1013 return u''
1016 # Trennmuster für Sprachvariante ausgeben
1017 # ---------------------------------------
1019 # Suche die passende Sprachvariante (oder generiere sie).
1021 # Ohne Transformation
1023 # >>> aalbst.get('de')
1024 # u'Aal=be<stand'
1025 # >>> aalbst.get('de-1996')
1026 # u'Aal=be<stand'
1027 # >>> aalbst.get('de-1901')
1028 # u'Aal=be<stand'
1029 # >>> aalbst.get('de-x-versal')
1030 # u'Aal=be<stand'
1031 # >>> aalbst.get('de-1901-x-versal')
1032 # u'Aal=be<stand'
1033 # >>> aalbst.get('de-1996-x-versal')
1034 # u'Aal=be<stand'
1035 # >>> aalbst.get('de-CH-1901')
1036 # u'Aal=be<stand'
1038 # >>> print dst.get('de')
1039 # Diens-te
1040 # >>> print dst.get('de-1901')
1041 # Dien-ste
1042 # >>> print dst.get('de-1996')
1043 # Diens-te
1045 # Versalschreibung:
1047 # >>> rs = ShortEntry(u'-1-;-2-;Russ') # versal f. Ruß
1048 # >>> print rs.get('de-x-versal')
1049 # Russ
1050 # >>> rs.get('de-1901-x-versal')
1051 # u''
1053 # >>> print tpp.get('de')
1054 # Ur<laubs=tipp
1055 # >>> print tpp.get('de-x-versal')
1056 # Ur<laubs=tipp
1057 # >>> tpp.get('de-1901')
1058 # u''
1060 # alle Felder mit "get":
1061 # >>> print u';'.join(ShortEntry(u'-1-;-2-;fräs-se').completed())
1062 # ;;fräs-se;;
1064 # Mit Transformation:
1066 # >>> print ShortEntry(u'Es-te').get('de-1901')
1067 # Este
1068 # >>> print ShortEntry(u'passt').get('de-1901')
1069 # paßt
1070 # >>> print ShortEntry(u'passt').get('de-1901-x-versal')
1071 # passt
1073 # >>> abus = ShortEntry(u'ab<bü-ßen')
1074 # >>> print abus.get('de-CH')
1075 # ab<büs-sen
1076 # >>> print abus.get('de-1901-x-versal')
1077 # ab<bü-ssen
1078 # >>> print abus.get('de-CH-1901')
1079 # ab<büs-sen
1081 # >>> print ShortEntry(u'Bus-se').get('de-1901-x-versal')
1082 # Bus-se
1083 # >>> print ShortEntry(u'Pass=sys-tem').get('de-CH-1901')
1084 # Pa{ss/ss=s}y-.stem
1086 # >>> ShortEntry(u'Pro<zess=en-.de;Pro<zeß=en-de;Pro<zess=en-.de').get('de-CH-1901')
1087 # u'Pro<zess=en-.de'
1088 # >>> pstr = ShortEntry(u'Pass=stra-ße;-2-')
1089 # >>> print u';'.join(pstr.completed())
1090 # Pass=stra-ße;;Pass=stras-se;;
1091 # >>> print pstr.get('de-1996') # Deutsch, aktuell (Reform 1996)
1092 # Pass=stra-ße
1093 # >>> print pstr.get('de-x-versal') # Alias de-CH
1094 # Pass=stras-se
1096 # ::
1098 def getitem(self, i):
1099 """Return item ``i`` or a subsititute"""
1100 try:
1101 return self[i]
1102 except IndexError: # Feld i nicht vorhanden
1103 pass
1104 # Ersatzregeln anwenden
1105 for _i in self.ersatzfelder[i]:
1106 try:
1107 word = self[_i]
1108 except IndexError: # Feld i nicht vorhanden
1109 continue # nächsten `tag` versuchen
1111 if not word: # "ausgekreuzt"
1112 return u'' # nicht ableiten
1114 # if '1901' in lang and '1901' not in tag:
1115 if i in (1, 3, 4) and _i == 0:
1116 word = ableitung1901(word)
1117 # if ('-CH' in lang) or ('x-versal' in lang) and (u'ß' in word):
1118 if i > 1 and (u'ß' in word):
1119 word = versalschreibung(word, self.feldnamen[i])
1120 return word
1121 return u''
1123 # Felder für alle Sprachvarianten ausfüllen
1125 # >>> dst.complete()
1126 # >>> print dst
1127 # Diens-te;Dien-ste;Diens-te;Dien-ste;Dien-ste
1128 # >>> aalbst.complete()
1129 # >>> print aalbst
1130 # Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand # Test
1131 # >>> abus.complete()
1132 # >>> print unicode(abus)
1133 # ab<bü-ßen;ab<bü-ßen;ab<büs-sen;ab<bü-ssen;ab<büs-sen
1134 # >>> boss.complete()
1135 # >>> print unicode(boss)
1136 # Boss;Boss;Boss;Boss;Boss # en.
1137 # >>> frs.complete()
1138 # >>> frs
1139 # [u'', u'', u'fr\xe4s-se', u'fr\xe4-sse', u'fr\xe4s-se']
1140 # >>> print unicode(frs)
1141 # -1-;-2-;fräs-se;frä-sse;fräs-se
1142 # >>> tpp.complete()
1143 # >>> tpp
1144 # [u'Ur<laubs=tipp', u'', u'Ur<laubs=tipp', u'', u'']
1145 # >>> print tpp
1146 # Ur<laubs=tipp;-2-;Ur<laubs=tipp;-4-;-5-
1148 # ::
1150 def completed(self):
1151 return [self.get(sv) for sv in self.feldnamen]
1153 def complete(self):
1154 if len(self) == 5: # schon ausführlich
1155 return
1156 # TODO: schon vorhandene Felder müssen nicht neuberechnet werden...
1157 for i, field in enumerate(self.completed()):
1158 try:
1159 self[i] = field
1160 except IndexError:
1161 self.append(field)
1163 # Felder für Sprachvarianten zusammenfassen
1164 # -----------------------------------------
1166 # Weglassen von Feldern, wenn sich der Inhalt durch Ableitung gewinnen läßt::
1168 # >>> aalbst.prune()
1169 # >>> print aalbst
1170 # Aal=be<stand # Test
1171 # >>> dst.prune()
1172 # >>> print dst
1173 # Diens-te
1174 # >>> frs.prune()
1175 # >>> print unicode(frs)
1176 # -1-;-2-;fräs-se;frä-sse;fräs-se
1177 # >>> abus.prune()
1178 # >>> print unicode(abus)
1179 # ab<bü-ßen
1180 # >>> boss.prune()
1181 # >>> print unicode(boss)
1182 # Boss;Boss # en.
1183 # >>> tpp.prune()
1184 # >>> print tpp
1185 # Ur<laubs=tipp;-2-
1187 # >>> entry = ShortEntry(u'-1-;Dreß;-3-;-4-;-5-')
1188 # >>> entry.prune()
1189 # >>> print unicode(entry)
1190 # -1-;Dreß;-3-;-4-;-5-
1192 # >> foo = ShortEntry(u'ade;de01;deCH;de01v;deCH01')
1193 # >> foo = ShortEntry(u'ade;de01;deCH;de01v')
1194 # >> foo.prune()
1195 # >> print foo
1197 # ::
1199 def prune(self):
1200 if len(self) == 1: # bereits kompakt
1201 return
1202 for tag in reversed(self.feldnamen[1:len(self)]):
1203 wort = self.pop()
1204 rekonstruktion = self.get(tag)
1205 if wort != rekonstruktion:
1206 # print tag, repr(self), wort, rekonstruktion
1207 self.append(wort)
1208 return
1209 if len(self) == 1 and not self[0]:
1210 self.pop()
1213 # Einträge zusammenfassen
1214 # -----------------------
1216 # >>> entry = ShortEntry(u'qua-li-täts=be<wusst;-2-')
1217 # >>> entry.merge(ShortEntry(u'-1-;qua-li-täts=be<wußt'))
1218 # >>> print unicode(entry)
1219 # qua-li-täts=be<wusst
1220 # >>> entry = ShortEntry(u'Qua-li-fi-zie-rungs==maß=nah-me')
1221 # >>> entry.merge(ShortEntry(u'-1-;-2-;Qua-li-fi-zie-rungs==mass=nah-me'))
1222 # >>> print unicode(entry)
1223 # Qua-li-fi-zie-rungs==maß=nah-me
1224 # >>> entry = ShortEntry(u'-1-;-2-;Qua-li-fi-zie-rungs==mass=nah-me')
1225 # >>> entry.merge(ShortEntry(u'Qua-li-fi-zie-rungs==maß=nah-me'))
1226 # >>> print unicode(entry)
1227 # Qua-li-fi-zie-rungs==maß=nah-me
1229 # ::
1231 # def merge(self, other, allow_alternatives=False):
1232 # self.complete()
1233 # other.complete()
1234 # for i, (s_i, o_i) in enumerate(zip(self,other)):
1235 # if s_i == o_i or o_i.startswith(u'-'):
1236 # continue
1237 # if s_i.startswith(u'-'):
1238 # self[i] = o_i
1239 # elif s_i != o_i:
1240 # if not allow_alternatives:
1241 # self.prune()
1242 # other.prune()
1243 # raise AssertionError('%s != %s' %(s_i, o_i))
1244 # self[i] = u'[%s/%s]' % (s_i, o_i)
1245 # self.prune()
1246 # other.prune()
1249 # Umwandeln in Listen von WordEntry Instanzen (Langformat)
1250 # -------------------_------------------------------------
1252 # >> entry = ShortEntry(u'fl')
1253 # >> entry.wordentries(prune=False)
1255 # >>> mse = ShortEntry(u'Mas-se')
1256 # >>> mse.wordentries()
1257 # [[u'Masse', u'Mas-se']]
1258 # >>> mas = ShortEntry(u'Ma-ße')
1259 # >>> for e in mas.wordentries(): e
1260 # [u'Ma\xdfe', u'Ma-\xdfe']
1261 # [u'Masse', u'', u'', u'', u'', u'Ma-sse', u'Mas-se', u'Mas-se']
1263 # >>> def print_langform(line, prune=True):
1264 # ... for e in ShortEntry(line).wordentries(prune):
1265 # ... print unicode(e)
1267 # >>> print_langform(u'ba-den')
1268 # baden;ba-den
1269 # >>> print_langform(u'Wes-te')
1270 # Weste;-2-;We-ste;Wes-te
1271 # >>> print_langform(u'Toll-patsch;-2-')
1272 # Tollpatsch;-2-;-3-;Toll-patsch
1273 # >>> print_langform(u'-1-;rau-he')
1274 # rauhe;-2-;rau-he;-4-
1276 # Bei Rechtschreibänderungen entsteht mehr als ein Eintrag:
1278 # >>> print_langform(u'Ab<fall=la-ger # Rechtschreibänderung')
1279 # Abfalllager;-2-;-3-;Ab<fall=la-ger # Rechtschreibänderung
1280 # Abfallager;-2-;Ab<fa{ll/ll=l}a-.ger;-4- # Rechtschreibänderung
1282 # >>> print_langform(u'Ab<guss')
1283 # Abguss;-2-;-3-;Ab<guss;Ab<guss
1284 # Abguß;-2-;Ab<guß;-4-
1286 # >>> print_langform(u'groß')
1287 # groß;groß
1288 # gross;-2-;-3-;-4-;gross
1290 # >>> print_langform(u'gro-ßen')
1291 # großen;gro-ßen
1292 # grossen;-2-;-3-;-4-;-5-;gro-ssen;gros-sen;gros-sen
1294 # >>> print_langform(u'Spaß')
1295 # Spaß;Spaß
1296 # Spass;-2-;-3-;-4-;Spass
1297 # >>> print_langform(u'Spass')
1298 # Spass;-2-;-3-;Spass;Spass
1299 # Spaß;-2-;Spaß;-4-
1300 # >>> print_langform(u'spa-ßen')
1301 # spaßen;spa-ßen
1302 # spassen;-2-;-3-;-4-;-5-;spa-ssen;spas-sen;spas-sen
1304 # >>> print_langform(u'ab<bü-ßen')
1305 # abbüßen;ab<bü-ßen
1306 # abbüssen;-2-;-3-;-4-;-5-;ab<bü-ssen;ab<büs-sen;ab<büs-sen
1308 # >>> print_langform(u'Boss;Boss # en.')
1309 # Boss;Boss # en.
1310 # >>> print_langform(u'Biss;-2-')
1311 # Biss;-2-;-3-;Biss
1313 # >>> print_langform(u'Pass=sys-tem')
1314 # Passsystem;-2-;-3-;Pass=sys-tem;-5-;Pass=sy-stem;Pass=sys-tem;-8-
1315 # Paßsystem;-2-;Paß=sy-stem;-4-
1316 # Passystem;-2-;-3-;-4-;-5-;-6-;-7-;Pa{ss/ss=s}y-.stem
1318 # >>> print_langform(u'Press=saft')
1319 # Presssaft;-2-;-3-;Press=saft;-5-;Press=saft;Press=saft;-8-
1320 # Preßsaft;-2-;Preß=saft;-4-
1321 # Pressaft;-2-;-3-;-4-;-5-;-6-;-7-;Pre{ss/ss=s}aft
1323 # >>> print_langform(u'Pro<gramm==maß=nah-me')
1324 # Programmmaßnahme;-2-;-3-;Pro<gramm==maß=nah-me
1325 # Programmaßnahme;-2-;Pro<gra{mm/mm==m}aß=nah-me;-4-
1326 # Programmmassnahme;-2-;-3-;-4-;-5-;-6-;Pro<gramm==mass=nah-me
1327 # Programmassnahme;-2-;-3-;-4-;-5-;Pro<gra{mm/mm==m}ass=nah-me;-7-
1329 # >>> print_langform(u'Pass=stra-ße')
1330 # Passstraße;-2-;-3-;Pass=stra-ße
1331 # Paßstraße;-2-;Paß=stra-ße;-4-
1332 # Passstrasse;-2-;-3-;-4-;-5-;Pass=stra-sse;Pass=stras-se;Pass=stras-se
1334 # Explizit nur eine Schreibung:
1336 # >>> print_langform(u'Abend=dress;Abend=dress')
1337 # Abenddress;Abend=dress
1338 # >>> print_langform(u'-1-;Abend=dreß')
1339 # Abenddreß;-2-;Abend=dreß;-4-
1340 # Abenddress;-2-;-3-;-4-;-5-;Abend=dress;-7-
1342 # Nur Versalschreibung:
1343 # >>> print_langform(u'-1-;-2-;Fuss;Fuss;Fuss')
1344 # Fuss;-2-;-3-;-4-;Fuss
1345 # >>> line = u'Fussballiga;-2-;-3-;-4-;-5-;Fuss=ba{ll/ll=l}i-.ga'
1346 # >>> print ShortEntry(WordEntry(line), prune=False)
1347 # -1-;-2-;-3-;Fuss=ba{ll/ll=l}i-.ga;Fuss=ba{ll/ll=l}i-.ga
1348 # >>> print_langform(u'-1-;-2-;-3-;Fuss=ba{ll/ll=l}i-.ga;Fuss=ba{ll/ll=l}i-.ga')
1349 # Fussballiga;-2-;-3-;-4-;-5-;Fuss=ba{ll/ll=l}i-.ga;-7-
1350 # >>> print_langform(u'-1-;-2-;Fuss=ball==li-ga')
1351 # Fussballliga;-2-;-3-;-4-;-5-;-6-;Fuss=ball==li-ga
1353 # Nur Kommentar:
1354 # >>> print_langform(u'# holla')
1355 # # holla
1357 # >>> print_langform(u'Pro<zess=en-.de;Pro<zeß=en-de;Pro<zess=en-.de')
1358 # Prozessende;-2-;-3-;Pro<zess=en-.de;Pro<zess=en-.de
1359 # Prozeßende;-2-;Pro<zeß=en-de;-4-
1361 # ::
1363 def wordentries(self, prune=True):
1365 if not self and self.comment: # leerer Kommentar
1366 return [WordEntry(u'# '+self.comment)]
1368 entries = [] # liste von WordEntry Einträgen
1369 keys = {} # list-index des Eintrags mit Schlüssel key
1370 # Zuordnung der Indizes: ShortEntry[s] == WordEntry[l]
1371 indices = (3, 2, 6, 5, 7)
1373 for s,l in enumerate(indices):
1374 # print s,l
1375 word = self.getitem(s)
1376 if not word: # unklar wo einzutragen da kein passender Schlüssel
1377 continue # -> überspringen.
1378 key = join_word(word)
1379 # Felder mit gleichem Schlüssel in eine WordEntry Instanz:
1380 try:
1381 entry = entries[keys[key]]
1382 except KeyError:
1383 # neuen Eintrag mit Schlüssel und Kommentar erzeugen:
1384 entry = WordEntry(key)
1385 entry.comment = self.comment
1386 keys[key] = len(entries)
1387 entries.append(entry)
1389 # Eintrag in entry[j]:
1390 entry.setitem(l, word)
1392 # Auffüllen und Komprimieren
1393 for entry in entries:
1394 while len(entry) < 8:
1395 entry.append(u'')
1396 if prune:
1397 entry.prune()
1398 return entries
1401 # Funktionen
1402 # ==========
1404 # Ableitung von de-1901 aus de-1996
1405 # ---------------------------------
1407 # Ableitung von de-1901 aus de-1996 (Reversion der Reform 1996).
1409 # Mit keep_key == True werden nur Änderungen vorgenommen, die die
1410 # Schreibweise des (ungetrennten) Wortes nicht verändern.
1412 # >>> from wortliste import ableitung1901
1414 # ::
1416 def ableitung1901(wort, keep_key=False):
1418 # Trennregeländerungen
1419 # ~~~~~~~~~~~~~~~~~~~~
1421 # Diese Regeln ändern nicht das Wort, nur die Trennmöglichkeiten.
1423 # 1. Trenne nie st: Ersetze 's-t' mit u'-st'.
1425 # >>> ableitung1901(u'Diens-te')
1426 # u'Dien-ste'
1427 # >>> print ableitung1901(u'wuss-te', keep_key=True)
1428 # wuss-te
1430 # Keine Trennung nach nur einem Buchstaben:
1432 # >>> ableitung1901(u'Es-te')
1433 # u'Este'
1434 # >>> print ableitung1901(u'Nord=os-ten')
1435 # Nord=osten
1436 # >>> print ableitung1901(u'Po-ly<es-ter')
1437 # Po-ly<ester
1438 # >>> print ableitung1901(u'the-is-tisch')
1439 # the-istisch
1441 # Keine Trennung von s-theta:
1443 # >>> print ableitung1901(u'Äs-thet')
1444 # Äs-thet
1446 # >>> print ableitung1901(u'pssst') # Sonderfall: falsche Umwandlung
1447 # psßt
1449 # 2. Trenne ck als k-k: Ersetze '-ck' mit '{ck/k-k}'.
1451 # >>> ableitung1901(u'Ha-cke')
1452 # u'Ha{ck/k-k}e'
1453 # >>> ableitung1901(u'Acker')
1454 # u'A{ck/k-k}er'
1455 # >>> ableitung1901(u'Got-tes=acker')
1456 # u'Got-tes=a{ck/k-k}er'
1457 # >>> ableitung1901(u'Aal=beck')
1458 # u'Aal=beck'
1459 # >>> ableitung1901(u'be<spick-te')
1460 # u'be<spick-te'
1462 # ::
1464 wort = re.sub(u'(?<!s)s-t(?!h)', u'-st', wort)
1465 wort = wort.replace(u'-ck', u'{ck/k-k}')
1466 wort = re.sub(u'(?<=[AEIOUYÄÖÜaeiouyäöü])ck(?=[aeiouyäöü])',
1467 u'{ck/k-k}', wort)
1468 # keine Trennung nach nur 1 Buchstaben:
1469 wort = re.sub(u'((?<=[=<-].)|(?<=^.))-', ur'', wort)
1471 if keep_key:
1472 return wort
1474 # Rechtschreibänderungen
1475 # ~~~~~~~~~~~~~~~~~~~~~~
1477 # Diese Regeln ändern die Schreibung des ungetrennten Worts (und somit den
1478 # Schlüssel im Langformat der Wortliste).
1480 # 3. kein ss und sst am Wortende (ungetrenntes "ss" nur in Ausnahmen):
1482 # Ersetze 'ss' (evt. gefolgt von t), wenn es am Silbenende auftaucht
1483 # (vor [-=<>]) mit ß (und evt. t).
1485 # Aber: 'ßt' und Schluss-ß auch in de-1996 möglich (langer Vokal)
1487 # >>> print ableitung1901(u'passt')
1488 # paßt
1489 # >>> print ableitung1901(u'Hass')
1490 # Haß
1491 # >>> print ableitung1901(u'Was-ser')
1492 # Was-ser
1493 # >>> print ableitung1901(u'Fass=brau-se')
1494 # Faß=brau-se
1495 # >>> print ableitung1901(u'wuss-te')
1496 # wuß-te
1498 # ::
1500 wort = re.sub(u'ss(t?)(?=[-=<>]|$)', ur'ß\1', wort)
1502 # 4. Dreikonsonantenregel:
1504 # Ersetze 'mm=m' mit '{mm/mm=m}' (für alle Konsonanten vor Selbstlaut)
1506 # >>> print ableitung1901(u'Kipp=pflug')
1507 # Kipp=pflug
1508 # >>> print ableitung1901(u'Kipp=punkt')
1509 # Ki{pp/pp=p}unkt
1510 # >>> print ableitung1901(u'Ab<fall=la-ger')
1511 # Ab<fa{ll/ll=l}a-.ger
1512 # >>> print ableitung1901(u'hell>licht')
1513 # he{ll/ll>l}icht
1514 # >>> print ableitung1901(u'Pro<gramm==maß=nah-me')
1515 # Pro<gra{mm/mm==m}aß=nah-me
1517 # ::
1519 wort = re.sub(ur'([bfglmnprt])\1([=>]+)\1(?=[aeiouyäöü])',
1520 ur'{\1\1/\1\1\2\1}', wort)
1522 # unterdrücken der Trennung nach nur einem Buchstaben
1523 wort = re.sub(ur'(?<=[=>].}[aeiouyäöü])([-<])\.?', ur'\1.', wort)
1525 return wort
1528 # Ableitung von de-CH/de-x-versal (SZ-Ersatzschreibung):
1529 # ------------------------------------------------------------------
1531 # >>> from wortliste import versalschreibung
1532 # >>> print versalschreibung(u'paßt')
1533 # passt
1534 # >>> print versalschreibung(u'bü-ßen', 'de-CH')
1535 # büs-sen
1536 # >>> print versalschreibung(u'bü-ßen', 'de-1996-x-versal')
1537 # büs-sen
1538 # >>> print versalschreibung(u'bü-ßen', 'de-CH-1901')
1539 # büs-sen
1540 # >>> print versalschreibung(u'äßen', 'de-CH-1901')
1541 # äs-sen
1542 # >>> print versalschreibung(u'auf<äßen', 'de-CH-1901')
1543 # auf<äs-sen
1544 # >>> print versalschreibung(u'auf<eßt', 'de-CH-1901')
1545 # auf<esst
1546 # >>> print versalschreibung(u'Groß=se-gel', 'de-1901-x-versal')
1547 # Gross=se-gel
1548 # >>> print versalschreibung(u'Groß=se-gel', 'de-CH-1901')
1549 # Gro{ss/ss=s}e-.gel
1550 # >>> print versalschreibung(u'Pass=sy-ste-me', 'de-CH-1901')
1551 # Pa{ss/ss=s}y-.ste-me
1552 # >>> print versalschreibung(u'Pro<zeß=en-de', 'de-CH-1901')
1553 # Pro<zess=en-.de
1554 # >>> print versalschreibung(u'Pro<zess=en-.de', 'de-CH-1901')
1555 # Pro<zess=en-.de
1556 # >>> print versalschreibung(u'Fluß==sy-stem', 'de-CH-1901')
1557 # Flu{ss/ss==s}y-.stem
1558 # >>> print versalschreibung(u'Meß==sen-der', 'de-CH-1901')
1559 # Me{ss/ss==s}en-.der
1561 # ::
1563 def versalschreibung(wort, lang='de'):
1565 # Ersetze 'ß' mit 'ss' ::
1567 wort = wort.replace(u'ß', u'ss')
1569 # Trennung von Ersatz-ss in de-CH und de-1996 nach Sprechsilbenregel::
1571 if '1901-x-versal' not in lang:
1572 wort = re.sub(u'(?<=[aeiouyäöü])-\.?ss', u's-s', wort)
1573 wort = re.sub(u'(?<=^[aeiouyäöü])ss(?=[aeiouyäöü])', u's-s', wort)
1574 wort = re.sub(u'(?<=[=<][aeiouyäöü])ss(?=[aeiouyäöü])', u's-s', wort)
1576 # Unterdrückung irreführender Trennung::
1578 wort = re.sub(u'ss(=+)(en|er)([<-])\.?', ur'ss\1\2\3.', wort)
1580 # Dreikonsonantenregel für Ersatz-ss in de-CH-1901::
1582 if 'CH-1901' in lang:
1583 wort = re.sub(u'ss(=+)s(?=[aeiouyäöü])', ur'{ss/ss\1s}', wort)
1584 # Unterdrücken der Trennung nach nur einem Buchstaben und irreführender Trennungen
1585 wort = re.sub(ur'(?<=[=>]s}[aeiouyäöü])([-<])\.?', ur'\1.', wort)
1586 # wort = re.sub(ur'(?<===s}[aeiouyäöü])([-<])\.?', ur'\1.', wort) # Reißverschus=sy-.stem
1587 wort = re.sub(u'(?<=[=>]s})(en|er)([<-])\.?', ur'\1\2.', wort)
1589 return wort
1592 # join_word
1593 # ---------
1595 # Trennzeichen entfernen::
1597 def join_word(wort, assert_complete=False):
1599 # Einfache Trennzeichen:
1601 # == ================================================================
1602 # \· ungewichtete Trennstelle (solche, wo sich noch niemand um die
1603 # Gewichtung gekümmert hat)
1604 # \. unerwünschte Trennstelle (sinnentstellend), z.B. Ur·in.stinkt
1605 # oder ungünstige Trennstelle (verwirrend), z.B. Atom·en.er·gie
1606 # in ungewichteten Wörtern
1607 # \= Trennstelle an Wortfugen (Wort=fu-ge)
1608 # \< Trennstelle nach Präfix (Vor<sil-be)
1609 # \> Trennstelle vor Suffix (Freund>schaf-ten)
1610 # \- Nebentrennstelle (ge-hen)
1611 # == ================================================================
1613 # ::
1615 table = {}
1616 for char in u'·.=|-_<>':
1617 table[ord(char)] = None
1618 key = wort.translate(table)
1620 # Spezielle Trennungen für die traditionelle Rechtschreibung
1621 # (siehe ../../dokumente/README.wortliste)::
1623 if '{' in key or '}' in key:
1624 key = key.replace(u'{ck/kk}', u'ck')
1625 key = key.replace(u'{ck/k', u'k')
1626 key = key.replace(u'k}', u'k')
1627 # Konsonanthäufungen an Wortfuge: '{xx/xxx}' -> 'xx':
1628 key = re.sub(ur'\{(.)\1/\1\1\1\}', ur'\1\1', key)
1629 # schon getrennt: ('{xx/xx' -> 'xx' und 'x}' -> 'x'):
1630 key = re.sub(ur'\{(.)\1/\1\1$', ur'\1\1', key)
1631 key = re.sub(ur'^(.)\}', ur'\1', key)
1633 # Trennstellen in doppeldeutigen Wörtern::
1635 if '[' in key or ']' in key:
1636 key = re.sub(ur'\[(.*)/\1\]', ur'\1', key)
1637 # schon getrennt:
1638 key = re.sub(ur'\[([^/\[]+)$', ur'\1', key)
1639 key = re.sub(ur'^([^/\]]+)\]', ur'\1', key)
1641 # Test auf verbliebene komplexe Trennstellen::
1643 if assert_complete:
1644 for spez in u'[{/}]':
1645 if spez in key:
1646 raise AssertionError('Spezialtrennung %s, %s' %
1647 (wort.encode('utf8'), key.encode('utf8')))
1649 return key
1651 # zerlege
1652 # -------
1654 # Zerlege ein Wort mit Trennzeichen in eine Liste von Silben und eine Liste
1655 # von Trennzeichen)
1657 # >>> from wortliste import zerlege
1659 # >>> zerlege(u'Haupt=stel-le')
1660 # ([u'Haupt', u'stel', u'le'], [u'=', u'-'])
1661 # >>> zerlege(u'Ge<samt=be<triebs=rats==chef')
1662 # ([u'Ge', u'samt', u'be', u'triebs', u'rats', u'chef'], [u'<', u'=', u'<', u'=', u'=='])
1663 # >>> zerlege(u'an<stands>los')
1664 # ([u'an', u'stands', u'los'], [u'<', u'>'])
1665 # >>> zerlege(u'An<al.pha-bet')
1666 # ([u'An', u'al', u'pha', u'bet'], [u'<', u'.', u'-'])
1668 # ::
1670 def zerlege(wort):
1671 silben = re.split(u'[-·._<>=]+', wort)
1672 trennzeichen = re.split(u'[^-·._|<>=]+', wort)
1673 return silben, [tz for tz in trennzeichen if tz]
1675 # TransferError
1676 # -------------
1678 # Fehler beim Übertragen von Trennstellen mit uebertrage_::
1680 class TransferError(ValueError):
1681 def __init__(self, wort1, wort2):
1682 msg = u'Inkompatibel: %s %s' % (wort1, wort2)
1683 ValueError.__init__(self, msg.encode('utf8'))
1685 def __unicode__(self):
1686 return str(self).decode('utf8')
1689 # uebertrage
1690 # ----------
1692 # Übertrage die Trennzeichen von `wort1` auf `wort2`:
1694 # >>> from wortliste import uebertrage, TransferError
1696 # >>> uebertrage(u'Haupt=stel-le', u'Haupt·stel·le')
1697 # u'Haupt=stel-le'
1699 # Auch teilweise Übertragung, von "kategorisiert" nach "unkategorisiert":
1701 # >>> print uebertrage(u'Haupt=stel-le', u'Haupt=stel·le')
1702 # Haupt=stel-le
1704 # >>> print uebertrage(u'Haupt·stel-le', u'Haupt=stel·le')
1705 # Haupt=stel-le
1707 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler')
1708 # Aus<stel-ler
1710 # >>> print uebertrage(u'Freund>schaf·ten', u'Freund-schaf-ten')
1711 # Freund>schaf-ten
1713 # Übertragung doppelter Marker:
1715 # >>> print uebertrage(u'ver<<aus<ga-be', u'ver<aus<ga-be')
1716 # ver<<aus<ga-be
1718 # >>> print uebertrage(u'freund>lich>>keit', u'freund>lich>keit')
1719 # freund>lich>>keit
1721 # >>> print uebertrage(u'Amts==haupt=stel-le', u'Amts=haupt=stel-le')
1722 # Amts==haupt=stel-le
1724 # Kein Überschreiben doppelter Marker:
1725 # >>> print uebertrage(u'ver<aus<ga-be', u'ver<<aus<ga-be')
1726 # ver<<aus<ga-be
1728 # >>> print uebertrage(u'Amts=haupt=stel-le', u'Amts==haupt=stel·le')
1729 # Amts==haupt=stel-le
1731 # Erhalt des Markers für ungünstige Stellen:
1732 # >>> print uebertrage(u'An·al.pha·bet', u'An<al.pha-bet')
1733 # An<al.pha-bet
1735 # Keine Übertragung, wenn die Zahl oder Position der Trennstellen
1736 # unterschiedlich ist oder bei unterschiedlichen Wörtern:
1738 # >>> try:
1739 # ... uebertrage(u'Ha-upt=stel-le', u'Haupt=stel·le')
1740 # ... uebertrage(u'Haupt=ste-lle', u'Haupt=stel·le')
1741 # ... uebertrage(u'Waupt=stel-le', u'Haupt=stel·le')
1742 # ... except TransferError:
1743 # ... pass
1745 # Übertragung auch bei unterschiedlicher Schreibung oder Position der
1746 # Trennstellen mit `strict=False` (für Abgleich zwischen Sprachvarianten):
1748 # >>> uebertrage(u'er-ster', u'ers·ter', strict=False)
1749 # u'ers-ter'
1750 # >>> uebertrage(u'Fluß=bett', u'Fluss·bett', strict=False)
1751 # u'Fluss=bett'
1752 # >>> uebertrage(u'ab>bei-ßen', u'ab>beis·sen', strict=False)
1753 # u'ab>beis-sen'
1754 # >>> print uebertrage(u'Aus<tausch=dien-stes', u'Aus-tausch=diens-tes', False)
1755 # Aus<tausch=diens-tes
1757 # Auch mit `strict=False` muß die Zahl der Trennstellen übereinstimmen
1758 # (Ausnahmen siehe unten):
1760 # >>> try:
1761 # ... uebertrage(u'Ha-upt=ste-lle', u'Haupt=stel·le', strict=False)
1762 # ... except TransferError:
1763 # ... pass
1765 # Akzeptiere unterschiedliche Anzahl von Trennungen bei st und ck nach
1766 # Selbstlaut:
1768 # >>> uebertrage(u'acht=ecki-ge', u'acht·e{ck/k·k}i·ge', strict=False)
1769 # u'acht=e{ck/k-k}i-ge'
1770 # >>> uebertrage(u'As-to-ria', u'Asto·ria', strict=False)
1771 # u'Asto-ria'
1772 # >>> uebertrage(u'Asto-ria', u'As·to·ria', strict=False)
1773 # u'As-to-ria'
1774 # >>> uebertrage(u'So-fa=ecke', u'So·fa=e{ck/k-k}e', strict=False)
1775 # u'So-fa=e{ck/k-k}e'
1776 # >>> uebertrage(u'Drei=ecks=ecke', u'Drei=ecks==e{ck/k-k}e', strict=False)
1777 # u'Drei=ecks==e{ck/k-k}e'
1779 # Mit ``upgrade=False`` werden nur unspezifische Trennstellen überschrieben:
1781 # >>> print uebertrage(u'an=stel-le', u'an<stel·le', upgrade=False)
1782 # an<stel-le
1784 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler', upgrade=False)
1785 # Aus-stel-ler
1787 # >>> print uebertrage(u'Aus-stel-ler', u'Aus<stel-ler', upgrade=False)
1788 # Aus<stel-ler
1790 # >>> print uebertrage(u'vor<an<<stel-le', u'vor-an<stel·le', upgrade=False)
1791 # vor-an<stel-le
1793 # ::
1795 selbstlaute = u'aeiouäöüAEIOUÄÖÜ'
1797 def uebertrage(wort1, wort2, strict=True, upgrade=True):
1799 silben1, trennzeichen1 = zerlege(wort1)
1800 silben2, trennzeichen2 = zerlege(wort2)
1801 # Prüfe strikte Übereinstimmung:
1802 if silben1 != silben2 and strict:
1803 if u'<' in trennzeichen1 or u'·' in trennzeichen2:
1804 raise TransferError(wort1, wort2)
1805 else:
1806 return wort2
1807 # Prüfe ungefähre Übereinstimmung:
1808 if len(trennzeichen1) != len(trennzeichen2):
1809 # Selbstlaut + st oder ck?
1810 for s in selbstlaute:
1811 if (wort2.find(s+u'{ck/k·k}') != -1 or
1812 wort2.find(s+u'{ck/k-k}') != -1):
1813 wort1 = re.sub(u'%sck([%s])'%(s,selbstlaute),
1814 ur'%s-ck\1'%s, wort1)
1815 silben1, trennzeichen1 = zerlege(wort1)
1816 if wort2.find(s+u's·t') != -1:
1817 wort1 = wort1.replace(s+u'st', s+u's-t')
1818 silben1, trennzeichen1 = zerlege(wort1)
1819 elif wort1.find(s+u's-t') != -1:
1820 wort1 = wort1.replace(s+u's-t', s+u'st')
1821 silben1, trennzeichen1 = zerlege(wort1)
1822 # print u'retry:', silben1, trennzeichen1
1823 # immer noch ungleiche Zahl an Trennstellen?
1824 if len(trennzeichen1) != len(trennzeichen2):
1825 raise TransferError(wort1, wort2)
1827 # Baue wort3 aus silben2 und spezifischeren Trennzeichen:
1828 wort3 = silben2.pop(0)
1829 for t1,t2 in zip(trennzeichen1, trennzeichen2):
1830 if ((t2 == u'·' and t1 != u'.') # unspezifisch
1831 or upgrade and
1832 ((t2 in (u'-', u'<') and t1 in (u'<', u'<<', u'<=')) # Praefixe
1833 or (t2 in (u'-', u'>') and t1 in (u'>', u'>>', u'=>')) # Suffixe
1834 or (t2 in (u'-', u'=') and t1 in (u'=', u'==', u'===')) # W-fugen
1837 wort3 += t1
1838 elif t2 == u'.' and t1 not in u'·.':
1839 wort3 += t1 + t2
1840 else:
1841 wort3 += t2
1842 wort3 += silben2.pop(0)
1843 return wort3
1846 # Übertrag kategorisierter Trennstellen zwischen den Feldern aller Einträge
1847 # in `wortliste`::
1849 def sprachabgleich(entry, vorbildentry=None):
1851 if len(entry) <= 2:
1852 return # allgemeine Schreibung
1854 mit_affix = None # < oder >
1855 kategorisiert = None # kein ·
1856 unkategorisiert = None # mindestens ein ·
1857 gewichtet = None # == oder <= oder =>
1858 for field in entry[1:]:
1859 if not field: # -2-, -3-, ...
1860 continue
1861 if u'{' in field and u'[' in field: # Bi-ber==be[t=t/{tt/tt=t}]uch
1862 continue # zu komplex
1863 if u'·' in field:
1864 unkategorisiert = field
1865 elif u'<' in field or u'>' in field:
1866 mit_affix = field
1867 else:
1868 kategorisiert = field
1869 if u'==' in field or u'<=' in field or u'=>' in field:
1870 gewichtet = field
1871 if vorbildentry:
1872 for field in vorbildentry[1:]:
1873 if not field: # -2-, -3-, ...
1874 continue
1875 if u'{' in field and u'[' in field: # Bi-ber==be[t=t/{tt/tt=t}]uch
1876 continue # zu komplex
1877 if not mit_affix and u'<' in field or u'>' in field :
1878 mit_affix = field
1879 elif not kategorisiert and unkategorisiert and u'·' not in field:
1880 kategorisiert = field
1881 if not gewichtet and u'==' in field or u'<=' in field or u'=>' in field:
1882 gewichtet = field
1883 # print 've:', mit_affix, kategorisiert, unkategorisiert
1884 if mit_affix and (kategorisiert or unkategorisiert or gewichtet):
1885 for i in range(1,len(entry)):
1886 if not entry[i]: # -2-, -3-, ...
1887 continue
1888 if u'<' not in entry[i] or u'·' in entry[i]:
1889 try:
1890 entry[i] = uebertrage(mit_affix, entry[i], strict=False)
1891 except TransferError, e:
1892 if not '/' in entry[i]:
1893 print u'Sprachabgleich:', unicode(e)
1894 # print mit_affix+u':', unicode(entry)
1895 elif kategorisiert and unkategorisiert:
1896 for i in range(1,len(entry)):
1897 if u'·' in entry[i]:
1898 try:
1899 entry[i] = uebertrage(kategorisiert, entry[i], strict=False)
1900 except TransferError, e:
1901 print u'Sprachabgleich:', unicode(e)
1902 # print kategorisiert, unicode(entry)
1903 elif gewichtet:
1904 for i in range(1,len(entry)):
1905 if u'=' in entry[i] and not (
1906 u'{' in entry[i] and u'[' in entry[i]):
1907 try:
1908 entry[i] = uebertrage(gewichtet, entry[i], strict=False)
1909 except TransferError, e:
1910 print u'Sprachabgleich:', unicode(e)
1914 # Großschreibung in Kleinschreibung wandeln und umgekehrt
1916 # Diese Version funktioniert auch für Wörter mit Trennzeichen (während
1917 # str.title() nach jedem Trennzeichen wieder groß anfängt)
1919 # >>> from wortliste import toggle_case
1920 # >>> toggle_case(u'Ha-se')
1921 # u'ha-se'
1922 # >>> toggle_case(u'arm')
1923 # u'Arm'
1924 # >>> toggle_case(u'frei=bier')
1925 # u'Frei=bier'
1926 # >>> toggle_case(u'L}a-ger')
1927 # u'l}a-ger'
1929 # Keine Änderung bei Wörtern mit Großbuchstaben im Inneren:
1931 # >>> toggle_case(u'USA')
1932 # u'USA'
1933 # >>> toggle_case(u'iRFD')
1934 # u'iRFD'
1936 # >>> toggle_case(u'gri[f-f/{ff/ff')
1937 # u'Gri[f-f/{ff/ff'
1938 # >>> toggle_case(u'Gri[f-f/{ff/ff')
1939 # u'gri[f-f/{ff/ff'
1941 # ::
1943 def toggle_case(wort):
1944 try:
1945 key = join_word(wort, assert_complete=True)
1946 except AssertionError:
1947 key = wort[0]
1948 if key.istitle():
1949 return wort.lower()
1950 elif key.islower():
1951 return wort[0].upper() + wort[1:]
1952 else:
1953 return wort
1955 # Sortierschlüssel
1956 # ================
1958 # Duden-Sortierung für die Wortliste
1960 # >>> from wortliste import sortkey_duden
1961 # >>> sortkey_duden([u"Abflußröhren"])
1962 # u'abflussrohren a*bflu*szroehren'
1963 # >>> sortkey_duden([u"Abflußrohren"])
1964 # u'abflussrohren a*bflu*szro*hren'
1965 # >>> sortkey_duden([u"Abflussrohren"])
1966 # u'abflussrohren'
1968 # >>> s = sorted([[u"Abflußröhren"], [u"Abflußrohren"], [u"Abflussrohren"]],
1969 # ... key=sortkey_duden)
1970 # >>> print ', '.join(e[0] for e in s)
1971 # Abflussrohren, Abflußrohren, Abflußröhren
1973 # Umschreibung
1975 # Ligaturen auflösen und andere "normalisierunde" Ersetzungen für den
1976 # (Haupt-)Sortierschlüssel (Akzente werden über ``unicodedata.normalize``
1977 # entfernt)::
1979 umschrift_skey = {
1980 ord(u'æ'): u'ae',
1981 ord(u'œ'): u'oe',
1982 ord(u'ſ'): u's',
1985 # "Zweitschlüssel" zur Unterscheidung von Umlauten/SZ und Basisbuchstaben::
1987 umschrift_subkey = {
1988 ord(u'a'): u'a*',
1989 ord(u'å'): u'aa',
1990 ord(u'ä'): u'ae',
1991 ord(u'o'): u'o*',
1992 ord(u'ö'): u'oe',
1993 ord(u'ø'): u'oe',
1994 ord(u'u'): u'u*',
1995 ord(u'ü'): u'ue',
1996 ord(u'ß'): u'sz',
2000 # sortkey_duden
2001 # -------------
2003 # Schlüssel für die alphabetische Sortierung gemäß Duden-Regeln.
2005 # Argument ist ein Eintrag im WordEntry oder ShortEntry Format oder
2006 # ein (Unicode) String:
2008 # >>> print sortkey_duden(weste) # WordEntry
2009 # weste
2010 # >>> print sortkey_duden(dst) # ShortEntry
2011 # dienste
2013 # >>> print sortkey_duden(u'Stra-ße')
2014 # strasse stra*sze
2015 # >>> print sortkey_duden([u'Büh-ne'])
2016 # buhne buehne
2017 # >>> print sortkey_duden(u'Weiß=flog;Weiß=flog;-3-;-4-;-5-')
2018 # weissflog weiszflo*g
2019 # >>> print sortkey_duden(u'Weiß=flog;Weiß=flog;-3-;-4-;-5-')
2020 # weissflog weiszflo*g
2021 # >>> print sortkey_duden(u'-1-;Meß=sen-der;-3-;-4-;-5-')
2022 # messsender meszsender
2024 # ::
2026 def sortkey_duden(entry):
2028 # ggf. ungetrenntes Wort extrahieren oder generieren::
2030 if isinstance(entry, list):
2031 try:
2032 key = entry.key()
2033 except AttributeError:
2034 key = entry[0]
2035 if len(entry) == 1: # ein Muster pro Zeile, siehe z.B. pre-1901
2036 key = join_word(key)
2037 else:
2038 match = re.search(u'^[-0-9;]*([^;]+)', entry) # erstes volles Feld
2039 if match:
2040 key = match.group(1)
2041 else:
2042 key = u''
2043 key = join_word(key)
2044 key = re.sub(u'[0-9;]', ur'', key)
2046 # Großschreibung ignorieren:
2048 # Der Duden sortiert Wörter, die sich nur in der Großschreibung unterscheiden
2049 # "klein vor groß" (ASCII sortiert "groß vor klein"). In der
2050 # `Trennmuster-Wortliste` kommen Wörter nur mit der häufiger anzutreffenden
2051 # Großschreibung vor, denn der TeX-Trennalgorithmus ignoriert Großschreibung.
2052 # ::
2054 key = key.lower()
2056 # Ersetzungen:
2058 # ß -> ss ::
2060 skey = key.replace(u'ß', u'ss')
2062 # Restliche Akzente weglassen: Wandeln in Darstellung von Buchstaben mit
2063 # Akzent als "Grundzeichen + kombinierender Akzent". Anschließend alle
2064 # nicht-ASCII-Zeichen ignorieren::
2066 skey = skey.translate(umschrift_skey)
2067 skey = unicodedata.normalize('NFKD', skey)
2068 skey = unicode(skey.encode('ascii', 'ignore'))
2070 # "Zweitschlüssel" für das eindeutige Einsortieren von Wörtern mit
2071 # gleichem Schlüssel (Masse/Maße, waren/wären, ...):
2073 # * "*" nach aou für die Unterscheidung Grund-/Umlaut
2074 # * ß->sz
2076 # ::
2078 if key != skey:
2079 subkey = key.translate(umschrift_subkey)
2080 skey = u'%s %s' % (skey,subkey)
2082 # Gib den Sortierschlüssel zurück::
2084 return skey
2088 # udiff
2089 # -----
2091 # Vergleiche zwei Sequenzen von `WordEntries` (genauer: alle Objekte, die
2092 # sich sinnvoll zu Unicode wandlen lassen).
2094 # Beispiel:
2096 # >>> from wortliste import udiff
2097 # >>> print udiff([abbeissen, aalbestand], [abbeissen,dresz], 'alt', 'neu')
2098 # --- alt
2099 # +++ neu
2100 # @@ -1,2 +1,2 @@
2101 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
2102 # -Aalbestand;Aal=be<stand # Test
2103 # +Dreß;-2-;Dreß;-4-
2105 # ::
2107 def udiff(a, b, fromfile='', tofile='',
2108 fromfiledate='', tofiledate='', n=1, encoding='utf8'):
2110 a = [unicode(entry).rstrip().encode(encoding) for entry in a]
2111 b = [unicode(entry).rstrip().encode(encoding) for entry in b]
2113 diff = difflib.unified_diff(a, b, fromfile, tofile,
2114 fromfiledate, tofiledate, n, lineterm='')
2116 if diff:
2117 return '\n'.join(diff).decode(encoding)
2118 else:
2119 return None
2122 # Sprachtags
2123 # ==========
2125 # Normalisierung und Expansion von Sprachtags nach [BCP47]_
2127 # >>> from wortliste import normalize_language_tag
2128 # >>> normalize_language_tag('de_AT-1901')
2129 # ['de-AT-1901', 'de-AT', 'de-1901', 'de']
2131 # >>> normalize_language_tag('de') # Deutsch, allgemeingültig
2132 # ['de']
2133 # >>> normalize_language_tag('de_1901') # traditionell (Reform 1901)
2134 # ['de-1901', 'de']
2135 # >>> normalize_language_tag('de_1996') # reformiert (Reform 1996)
2136 # ['de-1996', 'de']
2137 # >>> normalize_language_tag('de_CH') # ohne ß (Schweiz oder versal)
2138 # ['de-CH', 'de']
2139 # >>> normalize_language_tag('de-x-versal') # versal
2140 # ['de-x-versal', 'de']
2141 # >>> normalize_language_tag('de-1901-x-versal') # versal
2142 # ['de-1901-x-versal', 'de-1901', 'de-x-versal', 'de']
2143 # >>> normalize_language_tag('de_CH-1996') # Schweiz traditionell (süssauer)
2144 # ['de-CH-1996', 'de-CH', 'de-1996', 'de']
2146 # 'de': 1, # Deutsch, allgemeingültig
2147 # 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
2148 # 'de-1996': 3, # reformierte Reformschreibung (1996)
2149 # 'de-x-versal': 4, # ohne ß (Schweiz oder versal) allgemein
2150 # # 'de-CH': 4, # Alias
2151 # 'de-1901-x-versal': 5, # ohne ß (Schweiz oder versal) "traditionell"
2152 # 'de-1996-x-versal': 6, # ohne ß (Schweiz oder versal) "reformiert"
2153 # # 'de-CH-1996': 6, # Alias
2154 # 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
2157 # ::
2159 def normalize_language_tag(tag):
2160 """Return a list of normalized combinations for a `BCP 47` language tag.
2162 # normalize:
2163 tag = tag.replace('_','-')
2164 # split (except singletons, which mark the following tag as non-standard):
2165 tag = re.sub(r'-([a-zA-Z0-9])-', r'-\1_', tag)
2166 taglist = []
2167 subtags = [subtag.replace('_', '-') for subtag in tag.split('-')]
2168 base_tag = [subtags.pop(0)]
2169 # find all combinations of subtags
2170 for n in range(len(subtags), 0, -1):
2171 # for tags in unique_combinations(subtags, n):
2172 for tags in itertools.combinations(subtags, n):
2173 taglist.append('-'.join(base_tag+list(tags)))
2174 taglist += base_tag
2175 return taglist
2179 # Tests
2180 # =====
2182 # Teste Übereinstimmung des ungetrennten Wortes in Feld 1 mit den
2183 # Trennmustern nach Entfernen der Trennmarker. Schreibe Inkonsistenzen auf die
2184 # Standardausgabe.
2186 # Das Argument ist ein Iterator über die Einträge (Klasse `WordEntry`). ::
2188 def test_keys(wortliste):
2189 print u"Teste Schlüssel-Trennmuster-Übereinstimmung:"
2190 is_OK = True
2191 for entry in wortliste:
2192 # Test der Übereinstimmung ungetrenntes/getrenntes Wort
2193 # für alle Felder:
2194 key = entry.key()
2195 for wort in entry[1:]:
2196 if not wort: # leere Felder
2197 continue
2198 if key != join_word(wort):
2199 is_OK = False
2200 print u"\nkey '%s' != join_word('%s')" % (key, wort),
2201 if key.lower() == join_word(wort).lower():
2202 print(u" Abgleich der Großschreibung mit"
2203 u"`prepare-patch.py grossabgleich`."),
2204 if is_OK:
2205 print u"OK"
2208 # Finde Doppeleinträge (teste, ob jeder Schlüssel nur einmal vorkommt).
2209 # Schreibe Inkonsistenzen auf die Standardausgabe.
2211 # Das Argument ist ein Iterator über die Einträge (Klasse `WordEntry`). ::
2213 def test_uniqueness(wortliste):
2215 doppelte = 0
2216 words = set()
2217 for entry in wortliste:
2218 key = self.key()
2219 if key in words:
2220 doppelte += 1
2221 print unicode(entry)
2222 words.add(key)
2223 print doppelte,
2224 print u"Doppeleinträge (ohne Berücksichtigung der Großschreibung)."
2225 if doppelte:
2226 print u" Entfernen mit `prepare-patch.py doppelte`."
2227 print u" Patch vor Anwendung durchsehen!"
2229 # Teste die Wandlung einer Zeile im "wortliste"-Format in eine
2230 # ``WordEntry``-Instanz und zurück::
2232 def test_str_entry_str_conversion(wordfile):
2233 OK = 0
2234 for line in file(wordfile.name):
2235 line = line.rstrip().decode(wordfile.encoding)
2236 entry = WordEntry(line)
2237 if line == unicode(entry):
2238 OK +=1
2239 else:
2240 print u'-', line,
2241 print u'+', unicode(entry)
2243 print OK, u"Einträge rekonstruiert"
2246 # Teste Vervollständigung und Zusammenfassung von Einträgen::
2248 def test_completion_pruning(entries):
2249 reko = []
2250 for entry in entries:
2251 new = copy.copy(entry)
2252 new.complete()
2253 new.prune()
2254 reko.append(new)
2255 patch = udiff(entries, reko, 'wortliste', 'neu')
2256 if patch:
2257 print patch
2258 else:
2259 print u"alle Einträge rekonstruiert"
2263 # Aufruf von der Kommandozeile
2264 # ============================
2266 # ::
2268 if __name__ == '__main__':
2269 import sys
2271 # sys.stdout mit UTF8 encoding (wie in Python 3)
2272 sys.stdout = codecs.getwriter('UTF-8')(sys.stdout)
2273 # sys.stderr = codecs.getwriter('UTF-8')(sys.stderr)
2275 print u"Test der Werkzeuge und inneren Konsistenz der Wortliste\n"
2277 # Ein WordFile Dateiobjekt::
2279 wordfile = WordFile('../../../wortliste')
2280 print wordfile.name, u"Format", wordfile.format
2281 # wordfile = WordFile('../../../wlst', format='auto') # wortliste im Kurzformat
2282 # print wordfile.name, u"Format", wordfile.format
2283 # wordfile = WordFile('neu.todo')
2284 # print wordfile.name, u"Format", wordfile.format
2285 # wordfile = WordFile('neu-kurz.todo', format='f5')
2286 # print wordfile.name, u"Format", wordfile.format
2287 # wordfile = WordFile('korrektur.todo', format='auto')
2288 # print wordfile.name, u"Format", wordfile.format
2290 # Liste der Datenfelder (die Klasseninstanz als Argument für `list` liefert
2291 # den Iterator über die Felder, `list` macht daraus eine Liste)::
2293 wordlist = list(wordfile)
2294 print len(wordlist), u"Einträge"
2296 # Sprachauswahl::
2298 # Sprachtags:
2300 # sprache = 'de-1901' # traditionell
2301 sprache = 'de-1996' # Reformschreibung
2302 # sprache = 'de-x-versal' # ohne ß (Schweiz oder versal) allgemein
2303 # sprache = 'de-1901-x-versal' # ohne ß (Schweiz oder versal) "traditionell"
2304 # sprache = 'de-1996-x-versal' # ohne ß (Schweiz oder versal) "reformiert"
2305 # sprache = 'de-CH-1901' # ohne ß (Schweiz) "traditionell" ("süssauer")
2307 # worte = [entry.get(sprache) for entry in wordlist]
2308 # worte = [wort for wort in worte if wort]
2309 # print len(worte), u"Einträge für Sprachvariante", sprache
2312 # Teste Schlüssel-Trennmuster-Übereinstimmung::
2314 # test_keys(wordlist)
2316 # Test auf Doppeleinträge::
2318 # test_uniqueness(wordlist)
2320 # Teste Komplettieren/Zusammenfassen der Einträge::
2322 # test_completion_pruning(wordlist)
2324 # Ein Wörterbuch (dict Instanz)::
2326 # wordfile.seek(0) # Pointer zurücksetzen
2327 # words = wordfile.asdict()
2328 # print len(words), u"Wörterbucheinträge"
2330 # Zeilenrekonstruktion::
2332 #test_str_entry_str_conversion(wordfile)
2335 # Quellen
2336 # =======
2338 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
2339 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
2341 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
2342 # http://mirrors.ctan.org/language/hyphenation/dehyph-exptl/projektbeschreibung.pdf