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)
8 # :Version: 0.1 (2012-02-07)
14 """Hilfsmittel für die Arbeit mit der `Wortliste`"""
18 # Die hier versammelten Funktionen und Klassen dienen der Arbeit an und
19 # mit der freien `Wortliste der deutschsprachigen Trennmustermannschaft`_
25 # Python 2.7 mit den Standardbibliotheken::
34 from collections
import defaultdict
# Wörterbuch mit Default
39 # Klasse zum Lesen und Schreiben der `Wortliste`::
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.
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.
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
76 # Format anhand des Dateiinhalts bestimmen:
79 # nur ein Feld oder erstes Feld leer oder mit Trennzeichen:
80 if len(e
) == 1 or e
and re
.search(u
'[-=<>]', e
[0]):
83 # mehr als 5 Felder: 8-Felder-Format (Langform)
90 if self
.format
== 'f5':
91 self
.entry_class
= ShortEntry
93 self
.seek(0) # Dateizeiger zurücksetzen
99 # Die spezielle Funktion `__iter__` wird aufgerufen wenn über eine
100 # Klasseninstanz iteriert wird.
101 # Sie liefert einen Iterator über die "geparsten" Zeilen (Datenfelder)
105 entry_class
= self
.entry_class
107 line
= self
.next().rstrip().decode(self
.encoding
)
108 yield entry_class(line
)
114 # Gib ein `dictionary` (assoziatives Array) mit ungetrenntem Wort als
115 # Schlüssel (`key`) und den Datenfeldern als `value` zurück::
121 if key
== u
'#': # reiner Kommentar
123 words
[u
'#'].append(self
.comment
)
125 words
[u
'#'] = self
.comment
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
)
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
)
155 # Klasse für Einträge (Zeilen) der Wortliste_ in Langform_.
156 # Jede Zeile enthält einen Eintrag mit durch Semikolon „;“
157 # getrennten Feldern.
161 # >>> from wortliste import WordEntry
163 # >>> aalbestand = WordEntry(u'Aalbestand;Aal=be<stand # Test')
164 # >>> print aalbestand
165 # Aalbestand;Aal=be<stand # Test
168 # .. _8-Spalten-Format:
170 # Bedeutung der Felder im 8-Spalten-Format (Langform):
173 # 2. Wort mit Trennungen, falls für alle Varianten identisch,
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.
194 class WordEntry(list):
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')
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")
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]
243 # Ersatzindizes Index Nr Name
244 ersatzfelder
= (tuple(), # 0 1 key
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'
258 # Kommentar, Vorgabe ist ein leerer String::
268 def __init__(self
, line
, delimiter
=u
';'):
270 self
.delimiter
= delimiter
272 # eventuell vorhandenen Kommentar abtrennen und speichern::
275 line
, comment
= line
.split(u
'#', 1)
276 self
.comment
= comment
.strip()
278 # print (line, self.comment)
280 if not line
: # kein Inhalt
283 # Zerlegen in Datenfelder, in Liste eintragen::
285 for field
in line
.split(delimiter
):
286 if field
.startswith(u
'-'):
292 # Rückverwandlung in String
293 # -------------------------
295 # Erzeugen eines Eintrag-Strings (ohne Zeilenendezeichen) aus der Liste der
296 # Datenfelder und dem Kommentar
301 # >>> unicode(aalbestand)
302 # u'Aalbestand;Aal=be<stand # Test'
304 # >>> grse = WordEntry(u'Grüße;Grü-ße')
305 # >>> print unicode(grse)
308 # >>> leerkommentar = WordEntry(u'# Testkommentar ')
309 # >>> leerkommentar, unicode(leerkommentar)
310 # (WordEntry(u'# Testkommentar'), u'# Testkommentar')
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
)
320 line
+= u
' # ' + self
.comment
327 # >>> print type(str(grse)), str(grse).decode('utf8')
328 # <type 'str'> Grüße;Grü-ße
333 return unicode(self
).encode('utf8')
337 # >>> print type(repr(grse)), repr(grse).decode('utf8')
338 # <type 'str'> WordEntry(u'Grüße;Grü-ße')
343 s
= u
'%s(u\'%s\')' % (self
.__class
__.__name
__, self
)
344 return s
.encode('utf8')
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')
368 # >>> print abbeissen.lang_index('de-x-versal')
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')
379 # >>> print urlaubstipp.lang_index('de-1901')
381 # >>> print urlaubstipp.lang_index('de-1996')
383 # >>> print urlaubstipp.lang_index('de-x-versal')
385 # >>> print urlaubstipp.lang_index('de-1901-x-versal')
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::
399 if u
'ß' in self
[0] and ('CH' in lang
or 'versal' in lang
):
404 # Spezielle Schreibung::
407 i
= self
.feldindizes
[lang
]
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:
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
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()
438 # >>> print WordEntry(u'Dreß;-1-;Dreß;-3-').key()
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')
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()
455 def key(self
, lang
=None):
456 """Gib den Schlüssel (ungetrenntes Wort) zurück."""
459 except IndexError: # reiner Kommentar oder leerer Eintrag
468 # Gib Feld ``i`` oder Ersatzfeld zurück.
471 def getitem(self
, i
):
472 """Return item ``i`` or a subsititute"""
475 except IndexError: # Feld i nicht vorhanden
477 # Ersatzregeln anwenden
478 for _i
in self
.ersatzfelder
[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
:
489 # .. _WordEntry.get():
494 # Trennmuster für eine Sprachvariante ausgeben:
496 # >>> aalbestand.get('de')
498 # >>> aalbestand.get('de-1901')
500 # >>> aalbestand.get('de-1996')
502 # >>> aalbestand.get('de-x-versal')
504 # >>> aalbestand.get('de-1901-x-versal')
506 # >>> aalbestand.get('de-1996-x-versal')
508 # >>> aalbestand.get('de-CH-1901')
511 # >>> weste = WordEntry(u'Weste;-2-;We-ste;Wes-te')
512 # >>> weste.get('de')
514 # >>> weste.get('de-1901')
516 # >>> weste.get('de-1996')
519 # >>> abbeissen.get('de')
521 # >>> abbeissen.get('de-x-versal')
523 # >>> abbeissen.get('de,de-x-versal')
525 # >>> abbeissen.get('de-1901-x-versal')
527 # >>> abbeissen.get('de,de-1901,de-1901-x-versal')
529 # >>> abbeissen.get('de-CH-1901')
535 for lang
in tags
.split(','):
536 word
= self
.getitem(self
.feldindizes
[lang
])
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):
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
564 def set(self
, word
, tags
):
565 for lang
in tags
.split(','):
566 i
= self
.lang_index(lang
)
573 raise IndexError, "kann kein leeres Feld setzen"
580 # >>> entry = WordEntry(u'test;test')
581 # >>> entry.setitem(4, u's-x')
583 # test;test;-3-;-4-;s-x
584 # >>> entry.setitem(3, u'sz')
586 # test;test;-3-;sz;s-x
590 def setitem(self
, i
, word
):
591 while len(self
) < i
+1:
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-
629 for i
in range(len(self
),8):
630 self
.append(self
.getitem(i
))
632 # .. _WordEntry.completed():
637 # Gib eine vervollständigte Kopie des Eintrags zurück.
641 """Return expanded copy of self."""
642 neu
= copy
.copy(self
)
646 # .. _ShortEntry.prune():
653 # Felder für Sprachvarianten zusammenfassen, wenn gleich:
655 # >>> aalbestand.prune()
656 # >>> print aalbestand
657 # Aalbestand;Aal=be<stand # Test
658 # >>> auffrass.prune()
660 # auffrass;-2-;-3-;-4-;auf-frass
661 # >>> entry = WordEntry(u'distanziert;-2-;di-stan-ziert;di-stan-ziert')
664 # distanziert;di-stan-ziert
665 # >>> entry = WordEntry(u'Gauss;-2-;Gauss;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')
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-')
692 # Schlammmasse;-2-;-3-;Schlamm=mas-se
693 # >>> entry = WordEntry(
694 # ... u'Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-')
697 # Flussschiffahrt;-2-;-3-;-4-;-5-;Fluss=schi{ff/ff=f}ahrt;-7-
703 if self
[7] == self
[5]: # de-CH-1901 gleich versal-1901
706 if self
[6] == self
[5]:
707 self
[4] = self
[5] # umschreiben auf versal-allgemein
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
719 elif self
[1] == self
[4]: # de-x-versal == de
723 elif self
[2] == self
[3] == self
[4]:
724 self
[1] = self
[2] # Umschreiben auf de (allgemein)
729 if self
[3] == self
[2]: # de-1996 == de-1901
730 self
[1] = self
[2] # Umschreiben auf de (allgemein)
743 # Gib eine gekürzte Kopie des Eintrags zurück.
747 """Return pruned copy of self."""
748 neu
= copy
.copy(self
)
752 # .. _WordEntry.merge():
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)
771 # >>> masse.merge(WordEntry(u'masse;-2-;-3-;-4-;-5-;Ma-sse;Mas-se;Mas-se'),
772 # ... allow_alternatives=True)
774 # Masse;-2-;Mas-se;Mas-se;-5-;Ma[s-/-s]se;Mas-se;Mas-se
778 def merge(self
, other
, allow_alternatives
=False, prune
=True,
779 merge_comments
=False, start
=0, stop
=None):
782 islower
= self
.key().islower()
784 # `stop=None` oder `stop=0` bedeutet "keine Obergrenze".
785 stop
= stop
or len(self
)
786 for i
in range(start
, stop
):
790 if islower
!= o_i
.islower():
791 o_i
= toggle_case(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)
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
810 self
.comment
= self
.comment
or other
.comment
813 raise AssertionError(u
'Merge Error:\n %s\n %s'
814 % (unicode(self
), unicode(other
)))
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
879 # >>> entry = WordEntry(u'Boss;Boss # engl.') # korrekt
880 # >>> entry.regelaenderungen()
881 # >>> print unicode(entry)
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)
896 def regelaenderungen(self
):
897 wort01
= self
.get('de-1901')
898 wort96
= self
.get('de-1996')
901 if wort01
is None or wort96
is None or 'engl.' in self
.comment
:
904 # Trennregeländerungen:
907 wort01
= wort01
.replace(u
'-ck', u
'{ck/k-k}')
908 wort96
= wort96
.replace(u
'{ck/k-k}', u
'-ck')
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)
920 # Dreikonsonantenregel:
921 if wort01
and re
.search(ur
'(.)\1=\1', wort01
):
925 if wort01
== wort96
: # keine Regeländerung im Wort
931 self
.extend( ['']*(4-len(self
)) )
936 self
.extend( ['']*(4-len(self
)) )
941 self
.append(w_versal
)
947 # Klasse für Einträge (Zeilen) der Wortlisten im `5-Felder-Format`_ (Kurzform).
951 class ShortEntry(WordEntry
):
952 """Entry of the German hyphenation database file (5 fields)."""
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`_).
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
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``
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 # ==================== ============= =======================
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_
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:
1044 # Trenne nie "st". (Aber "s-th" ist trennbar.)
1046 # Trenne "ck" als "k-k".
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()`_.
1059 # Ersetze 'ß' mit 'ss', trenne je nach Sprachvarietät (siehe Feldbelegung_).
1061 # Implementiert in `versalschreibung()`_.
1068 # Durch die Ersatzregeln reicht für die meisten Einträge die Angabe des ersten
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)
1084 # Wort mit Unregelmäßigkeiten:
1086 # >>> entry = ShortEntry(u'Boss;Boss;Boss;Boss;Boss # en.')
1087 # >>> entry.prune(); print unicode(entry)
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")
1104 # Zuordnung von Sprachbezeichnern nach [BCP47]_ zu den Feldern.
1106 # In Python beginnt die Indexzählung mit Null::
1109 "de": 0, # Deutsch, aktuell
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']
1130 # >>> print ShortEntry.feldindizes['de'], ShortEntry.feldindizes['de-1996']
1133 # >>> print WordEntry.feldindizes['de'], WordEntry.feldindizes['de-1996']
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')
1151 # >>> tpp = ShortEntry(u'Ur<laubs=tipp;-2-')
1155 # >>> print abenddienste
1156 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
1157 # >>> print ShortEntry(abenddienste)
1160 # Achtung: Ein Lang-Eintrag mit nur zwei Feldern kann unregelmäßig sein und
1161 # daher im Kurzformat mehrere Felder benötigen (siehe Tests)
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
1171 self
.append(line
.getitem(3)) # Deutsch, aktuell (Reform 1996)
1172 self
.append(line
.getitem(2)) # "traditionell" (Reform 1901)
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
1182 WordEntry
.__init
__(self
, line
, delimiter
)
1184 if prune
: # Felder zusammenfassen
1189 # >>> drs = unicode(ShortEntry(u'-1-;Dreß'))
1192 # >>> print ShortEntry(u'# 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ß'))
1200 # >>> print unicode(ShortEntry(u'Fluss;Fluß', prune=False))
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'))
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.'))
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)
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')
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()
1274 # >>> print ShortEntry(u'-1-;Dreß').key()
1277 # Die Auswahl kann über das `lang` Argument gesteuert werden:
1279 # >>> print ShortEntry(u'-1-;Dreß').key('de-CH-1901')
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()
1290 def key(self
, lang
=None):
1291 """Erstelle einen Schlüssel (ungetrenntes Wort)."""
1293 return join_word(self
.get(lang
))
1301 return u
'#' # reiner Kommentar
1302 return u
'' # leerer Eintrag
1305 # .. _`ShortEntry.getitem()`:
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):
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)
1331 def getitem(self
, i
, substitute
=False):
1332 """Return item ``i`` or a subsititute"""
1336 except IndexError: # Feld i nicht vorhanden
1338 raise ValueError('leerer Eintrag')
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
1350 # Rechschreibreform (s-t, ck, Dreikonsonantenregel)
1351 if i
== 1: # de-1901
1352 return ableitung1901(word
)
1354 # Versalschreibung: ß -> ss
1356 word
= versalschreibung(word
, self
.feldnamen
[i
])
1363 # Gib Trennmuster für Sprachvariante zurück (ggf. über Transformationen_).
1367 # ohne Transformation
1369 # >>> aalbst = ShortEntry(u'Aal=be<stand # Test')
1370 # >>> aalbst.get('de')
1372 # >>> aalbst.get('de-1996')
1374 # >>> aalbst.get('de-1901')
1376 # >>> aalbst.get('de-x-versal')
1378 # >>> aalbst.get('de-1901-x-versal')
1380 # >>> aalbst.get('de-1996-x-versal')
1382 # >>> aalbst.get('de-CH-1901')
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')
1392 # >>> sck = ShortEntry(u'Stre-cke')
1393 # >>> print sck.get('de'), sck.get('de-1901')
1394 # Stre-cke Stre{ck/k-k}e
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')
1406 # >>> print ShortEntry(u'passt').get('de-1901-x-versal')
1408 # >>> rs = ShortEntry(u'-1-;-2-;Russ') # versal für Ruß
1409 # >>> print rs.get('de-x-versal')
1411 # >>> rs.get('de-1901-x-versal')
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')
1431 # >>> print ShortEntry(u'Bus-se').get('de-1901-x-versal')
1434 # Geerbt von `WordEntry.get()`_.
1436 # .. _ShortEntry.complete():
1441 # Eintrag vervollständigen (alle 5 Felder ausfüllen).
1443 # >>> dst.complete()
1445 # Diens-te;Dien-ste;Diens-te;Dien-ste;Dien-ste
1446 # >>> aalbst.complete()
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()
1460 # Ur<laubs=tipp;-2-;Ur<laubs=tipp;-4-;-5-
1462 # >>> entry = ShortEntry(u'# toller Hecht')
1463 # >>> entry.complete()
1464 # >>> print unicode(entry)
1470 for i
in range(len(self
), 5):
1472 field
= self
.getitem(i
)
1473 except ValueError: # leerer Eintrag
1481 # Gib eine vervollständigte Kopie des Eintrags zurück.
1483 # >>> entry = ShortEntry(u'-1-;-2-;sties-se').completed()
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()`_.
1499 # Felder weglassen, wenn sich der Inhalt durch Ersatzregeln_ gewinnen läßt:
1501 # >>> aalbst.prune()
1503 # Aal=be<stand # Test
1508 # >>> print unicode(abus)
1511 # >>> print unicode(bss)
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-')
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')
1535 # Felder werden nicht gekürzt, wenn die Rekonstruktion einen anderen Wert
1539 # >>> print unicode(frs)
1540 # -1-;-2-;fräs-se;frä-sse;fräs-se
1542 # >>> entry = ShortEntry(u'-1-;Dreß;-3-;-4-;-5-')
1544 # >>> print unicode(entry)
1545 # -1-;Dreß;-3-;-4-;-5-
1547 # Mit ``drop_sz=True`` werden die drei letzten Felder (ß-Ersatzschreibung)
1550 # * ß-Ersatzschreibung ist immer definiert, daher kann eigentlich nichts falsch
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)
1568 # >>> entry.complete()
1569 # >>> print unicode(entry)
1570 # -1-;Dreß;-3-;Dress;Dress
1574 def prune(self
, drop_sz
=False):
1575 if len(self
) == 1: # bereits kompakt
1578 while len(self
) > 2:
1580 for i
in range(len(self
)-1, 0, -1):
1582 rekonstruktion
= self
.getitem(i
)
1583 if wort
!= rekonstruktion
:
1584 # print tag, repr(self), wort, rekonstruktion
1587 if len(self
) == 1 and not self
[0]:
1594 # Einträge zusammenlegen.
1596 # Das als Argument übergebene WordEntry Objekt wird in dem eigenen Eintrag
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)
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):
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()`_
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)
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
1662 def wordentries(self
, prune
=True):
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
)
1677 continue # Leerfelder überspringen
1679 key
= join_word(word
)
1680 # WordEntry Instanz heraussuchen oder erzeugen:
1684 entry
= WordEntry(key
)
1685 entry
.comment
= self
.comment
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:
1700 # Hilfsfunktion für Tests:
1702 # >>> def print_langform(line, prune=True):
1703 # ... for e in ShortEntry(line).wordentries(prune):
1704 # ... print unicode(e)
1708 # >>> print_langform(u'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ß')
1729 # gross;-2-;-3-;-4-;gross
1731 # >>> print_langform(u'gro-ßen')
1733 # grossen;-2-;-3-;-4-;-5-;gro-ssen;gros-sen;gros-sen
1735 # >>> print_langform(u'Spaß')
1737 # Spass;-2-;-3-;-4-;Spass
1738 # >>> print_langform(u'Spass')
1739 # Spass;-2-;-3-;Spass;Spass
1741 # >>> print_langform(u'spa-ßen')
1743 # spassen;-2-;-3-;-4-;-5-;spa-ssen;spas-sen;spas-sen
1745 # >>> print_langform(u'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
1752 # >>> print_langform(u'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
1800 # >>> print_langform(u'# holla')
1810 # Entferne Alternativtrennungen bei einfachen und suffigierten Fremdwörtern:
1812 # Regelwerk (1996) § 112:
1813 # In Fremdwörtern können die Verbindungen aus Buchstaben für einen
1814 # Konsonanten + l, n oder r entweder entsprechend § 110 getrennt werden,
1815 # oder sie kommen ungetrennt auf die neue Zeile.
1817 # >>> from wortliste import fremdwortsilben
1819 # >>> fremdwoerter = (u'no-b-le Zy-k-lus Ma-g-net Fe-b-ru-ar '
1820 # ... u'Hy-d-rant Ar-th-ri-tis')
1821 # >>> for wort in fremdwoerter.split():
1822 # ... print wort, '->', fremdwortsilben(wort)
1824 # Zy-k-lus -> Zy-klus
1825 # Ma-g-net -> Ma-gnet
1826 # Fe-b-ru-ar -> Fe-bru-ar
1827 # Hy-d-rant -> Hy-drant
1828 # Ar-th-ri-tis -> Ar-thri-tis
1830 # >>> for wort in fremdwoerter.split():
1831 # ... print wort, '->', fremdwortsilben(wort, 'modern')
1833 # Zy-k-lus -> Zyk-lus
1834 # Ma-g-net -> Mag-net
1835 # Fe-b-ru-ar -> Feb-ru-ar
1836 # Hy-d-rant -> Hyd-rant
1837 # Ar-th-ri-tis -> Arth-ri-tis
1841 def fremdwortsilben(wort
, style
='traditional'):
1842 """Select in-word hyphenation of foreign words."""
1843 if style
== "modern": # Sprechsilbenregel
1844 return re
.sub(u
'-([bcdfgkptv]|th|st)-(?=[lrn])', u
'\\1-', wort
)
1845 else: # traditionell
1846 return re
.sub(u
'-([bcdfgkptv]|th|st)-(?=[lrn])', u
'-\\1', wort
)
1847 # Versuch: auch Alternativtrennung nach führendem Vokal:
1848 # Ap-ri-kose -> Apri-kose aber auch Ad-ler -> Adler (!)
1849 # return re.sub(u'(-|^[AEIOUÄÖÜaeiouäöü])([bcdfgkptv]|th|st)-(?=[lrn])',
1854 # K86 Untrennbar sind in Fremdwörtern die Verbindungen von Verschluß- und
1855 # Reibelauten mit l und r, ...
1857 # >>> fremdwoerter = (u'Pu-b-li-kum flexi-b-ler Zy-k-lone Qua-d-rat '
1858 # ... u'Spek-t-rum manö-v-rieren')
1859 # >>> for wort in fremdwoerter.split():
1860 # ... print wort, '->', fremdwortsilben(wort)
1861 # Pu-b-li-kum -> Pu-bli-kum
1862 # flexi-b-ler -> flexi-bler
1863 # Zy-k-lone -> Zy-klone
1864 # Qua-d-rat -> Qua-drat
1865 # Spek-t-rum -> Spek-trum
1866 # manö-v-rieren -> manö-vrieren
1868 # die Lautfolge st+r bleibt ungetrennt, wenn keine Wortfuge vorliegt.
1870 # >>> fremdwoerter = u'Di-st-rikt Magi-st-rat laku-st-risch ab-st-rakt'
1871 # >>> for wort in fremdwoerter.split():
1872 # ... print wort, '->', fremdwortsilben(wort)
1873 # Di-st-rikt -> Di-strikt
1874 # Magi-st-rat -> Magi-strat
1875 # laku-st-risch -> laku-strisch
1876 # ab-st-rakt -> ab-strakt
1878 # K 87 Untrennbar ist die Konsonantenverbindung "gn".
1880 # >>> fremdwoerter = u'Ma-g-net Pro-g-nose Si-g-net'
1881 # >>> for wort in fremdwoerter.split():
1882 # ... print wort, '->', fremdwortsilben(wort)
1883 # Ma-g-net -> Ma-gnet
1884 # Pro-g-nose -> Pro-gnose
1885 # Si-g-net -> Si-gnet
1887 # Keine Übergeneralisierung:
1889 # >>> woerter = u'Seg-ler bast-le Ad-ler'
1890 # >>> for wort in woerter.split():
1891 # ... print wort, '->', fremdwortsilben(wort)
1892 # Seg-ler -> Seg-ler
1893 # bast-le -> bast-le
1896 # wegen Übergeneralisierung nicht möglich:
1897 # Ap-ri-kose -> Apri-kose
1898 # ig-no-rie-ren -> igno-rie-ren
1904 # Entferne Alternativtrennungen nach §113 (verblasste Etymologie).
1906 # Regelwerk (1996) §113:
1907 # Wörter, die sprachhistorisch oder von der Herkunftssprache her gesehen
1908 # Zusammensetzungen oder Präfigierungen sind, aber nicht mehr als solche
1909 # empfunden oder erkannt werden, kann man entweder nach § 108 oder nach §
1910 # 109 bis § 112 trennen.
1912 # >>> from wortliste import verblasst
1913 # >>> blasse = (u'hi-n<auf he-r<an da-r<um Chry-s<an-the-me Hek-t<ar '
1914 # ... u'He-li-ko<p-ter in-te-r>es-sant Li-n<oleum Pä-d<a-go-gik')
1915 # >>> for wort in blasse.split():
1916 # ... print wort, '->', verblasst(wort)
1917 # hi-n<auf -> hin<auf
1920 # Chry-s<an-the-me -> Chrys<an-the-me
1921 # Hek-t<ar -> Hekt<ar
1922 # He-li-ko<p-ter -> He-li-ko<pter
1923 # in-te-r>es-sant -> in-ter>es-sant
1924 # Li-n<oleum -> Lin<oleum
1925 # Pä-d<a-go-gik -> Päd<ago-gik
1927 # >>> for wort in blasse.split():
1928 # ... print wort, '->', verblasst(wort, 'modern')
1929 # hi-n<auf -> hi-nauf
1932 # Chry-s<an-the-me -> Chry-san-the-me
1933 # Hek-t<ar -> Hek-tar
1934 # He-li-ko<p-ter -> He-li-kop-ter
1935 # in-te-r>es-sant -> in-te-res-sant
1936 # Li-n<oleum -> Li-noleum
1937 # Pä-d<a-go-gik -> Pä-da-go-gik
1939 # Ersetze, wenn zwischen Haupttrennstelle und Nebentrennstelle nur ein
1941 # (Die Haupttrennstelle kann vor oder nach der Nebentrennstelle liegen.)
1944 def verblasst(wort
, style
='traditional'):
1945 """Select hyphenation of foreign words with obscure etymology."""
1946 if style
== "modern": # Sprechsilbenregel
1947 wort
= re
.sub(u
'[<>=]+[.]*(.[-.]+)', u
'\\1', wort
)
1948 wort
= re
.sub(u
'([-.]+.)[<>=]+[.]*', u
'\\1', wort
)
1949 else: # etymologisch
1950 wort
= re
.sub(u
'([<>=]+[.]*.)[-.]+', u
'\\1', wort
)
1951 wort
= re
.sub(u
'[-.]+(.[<>=]+)', u
'\\1', wort
)
1958 # K44 „ſ“ (langes s) steht in Fremdwörtern...
1960 # >>> blasse = (u'tran<s-pirieren tran<s-zendent ab<s-tinent '
1961 # ... u'Ab<s-zess Pro-s<odie')
1962 # >>> for wort in blasse.split():
1963 # ... print wort, '->', verblasst(wort)
1964 # tran<s-pirieren -> tran<spirieren
1965 # tran<s-zendent -> tran<szendent
1966 # ab<s-tinent -> ab<stinent
1967 # Ab<s-zess -> Ab<szess
1968 # Pro-s<odie -> Pros<odie
1970 # Trennstellen können als ungünstig markiert sein:
1972 # >>> blasse = (u'Bür-ger=in<.i-ti-a-ti-ve Pä-..d<e-..rast')
1973 # >>> for wort in blasse.split():
1974 # ... print wort, '->', verblasst(wort)
1975 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=in<.iti-a-ti-ve
1976 # Pä-..d<e-..rast -> Päd<erast
1977 # >>> for wort in blasse.split():
1978 # ... print wort, '->', verblasst(wort, 'modern')
1979 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=ini-ti-a-ti-ve
1980 # Pä-..d<e-..rast -> Pä-..de-..rast
1986 # Füge Trennmöglichkeiten am Wortanfang und -ende zu, die nach K79 (bzw. §107
1987 # E2 des Regelwerkes) verboten sind aber in Notentexten gebraucht werden.
1989 # >>> from wortliste import scoretext
1991 # >>> scoretext(u'Abend')
1993 # >>> scoretext(u'Ra-dio')
1996 # Das gleiche gilt für Trennmöglichkeiten am Anfang/Ende von Teilwörtern:
1998 # >>> scoretext(u'Eis=ano-ma-lie')
1999 # u'Eis=a.no-ma-lie'
2000 # >>> scoretext(u'Ra-dio<phon')
2005 # >>> scoretext(u'Ai-chin-ger'), scoretext(u'Ai-da')
2006 # (u'Ai-chin-ger', u'A.i-da')
2007 # >>> scoretext(u'Ma-rie'), scoretext(u'Li-nie')
2008 # (u'Ma-rie', u'Li-ni.e')
2009 # >>> scoretext(u'Ta-too'), scoretext(u'Zoo<lo-gie')
2010 # (u'Ta-too', u'Zo.o<lo-gie')
2011 # >>> scoretext(u'A-pnoe'), scoretext(u'O-boe')
2012 # (u'A-pnoe', u'O-bo.e')
2013 # >>> scoretext(u'Plaque'), scoretext(u'treue')
2014 # (u'Plaque', u'treu.e')
2015 # >>> scoretext(u'Fon-due=pfan-ne'), scoretext(u'Aue')
2016 # (u'Fon-due=pfan-ne', u'Au.e')
2017 # >>> scoretext(u'Ge-nie'), scoretext(u'Iphi-ge-nie')
2018 # (u'Ge-nie', u'I.phi-ge-nie')
2019 # >>> scoretext(u'Ago-nie'), scoretext(u'Be-go-nie')
2020 # (u'A.go-nie', u'Be-go-ni.e')
2021 # >>> scoretext(u'Kom-pa-nie'), scoretext(u'Kas-ta-nie'), scoretext(u'Ge-ra-nie')
2022 # (u'Kom-pa-nie', u'Kas-ta-ni.e', u'Ge-ra-ni.e')
2024 # ungelöst: Knie / Kni.e # pl.
2027 def scoretext(word
):
2028 """Mark up leading one-letter syllables."""
2029 # Führender Vokal, gefolgt von Silbenanfang
2030 # (optionaler Konsonant (auch ch/ck/ph/rh/sch/sh/th) + Vokal)
2031 for match
in re
.finditer(u
'(^|[<=])([aeiouäöü])((.|ch|ck|ph|sch|th)?[aeiouäöü])',
2032 word
, flags
=re
.IGNORECASE
):
2033 # print match.groups()
2034 # Ausnahmen: Doppellaute, Diphtonge (außer A-i-da), Umlaute
2035 if (re
.search(u
'(aa|ae|ai|au|äu|ei|eu|oe|oo|ou|ue)',
2036 match
.group(0), flags
=re
.IGNORECASE
)
2037 and word
!= u
'Ai-da' or word
== u
'io-ta' ):
2039 word
= ''.join((word
[:match
.start()], match
.expand(u
'\\1\\2.\\3'),
2040 scoretext(word
[match
.end():])))
2042 # zwei Vokale am Wortende
2043 for match
in re
.finditer(u
'([aeiouäöü])([aeiouäöü])([<>=]|$)', word
):
2044 # if re.search(u'(oo)', match.group(0)):
2045 # if 'ie' in match.group(0) and re.search(u'([a]-nie)', word, flags=re.IGNORECASE):
2046 # sys.stderr.write(word+' '+match.group(0)+'\n')
2047 # Ausnahmen: Doppellaute, Diphtonge, Umlaute (außer "Oboe")
2048 if (re
.search(u
'(aa|ae|ai|au|äu|ee|ei|eu|oe|oi|ou|ui)', match
.group(0))
2049 and not word
[:match
.end()].endswith(u
'waii') # ! Hawaii
2050 and not word
[:match
.end()].endswith(u
'boe')): # ! Oboe
2052 # Ausnahmen mit Ausnahmen:
2053 # …oo außer zoo<, ...
2054 if 'oo' in match
.group(0) and match
.group(0) != 'oo<':
2056 # …ie außer "-(l)inie", Iphigenie, Kastanie, Geranie, Begonie
2057 if ('ie' in match
.group(0)
2058 and not word
[:match
.end()].endswith(u
'i-nie') # Linie,
2059 and not word
[:match
.end()].endswith(u
'ta-nie') # Kastanie,
2060 and not word
[:match
.end()].endswith(u
'ra-nie') # Geranie,
2061 and not word
[:match
.end()].endswith(u
'e-go-nie') # Begonie != Agonie
2062 and not word
== u
'Iphi-ge-nie'):
2064 # …ue (Plaque, Re-vue, vogue) außer "-tue, -aue, ... -äue"
2065 if 'ue' in match
.group(0) and re
.search(u
'[^aeät]ue([<>=]|$)',
2066 word
[:match
.end()], flags
=re
.IGNORECASE
):
2069 word
= ''.join((word
[:match
.start()], match
.expand(u
'\\1.\\2\\3'),
2070 scoretext(word
[match
.end():])))
2078 # Ableitung von de-1901 aus de-1996 (Reversion der Reform 1996).
2080 # Mit keep_key == True werden nur Änderungen vorgenommen, die die
2081 # Schreibung des (ungetrennten) Wortes nicht verändern.
2083 # >>> from wortliste import ableitung1901
2087 def ableitung1901(wort
, keep_key
=False):
2088 """Reverse regular changes of the 1996 orthography reform."""
2091 # Trennregeländerungen
2092 # ~~~~~~~~~~~~~~~~~~~~
2094 # Diese Regeln ändern nicht das Wort, nur die Trennmöglichkeiten.
2096 # Alternativtrennungen auswählen::
2098 wort
= fremdwortsilben(wort
)
2099 wort
= verblasst(wort
)
2104 # K75: Trenne nie st.
2106 # Ersetze 's-t' mit '-st':
2108 # >>> ableitung1901(u'Diens-te')
2110 # >>> print ableitung1901(u'wuss-te', keep_key=True)
2113 # Aber Trennung von s-theta erlaubt (nach K74):
2115 # >>> print ableitung1901(u'Äs-thet')
2120 wort
= re
.sub(u
'(?<!s)s-t(?!h)', u
'-st', wort
)
2126 # K76: Trenne 'ck' als 'k-k'.
2128 # Ersetze '-ck' mit '{ck/k-k}':
2130 # >>> ableitung1901(u'Ha-cke')
2132 # >>> ableitung1901(u'Acker')
2134 # >>> ableitung1901(u'Got-tes=acker')
2135 # u'Got-tes=a{ck/k-k}er'
2136 # >>> ableitung1901(u'Aal=beck')
2138 # >>> ableitung1901(u'be<spick-te')
2143 wort
= wort
.replace(u
'-ck', u
'{ck/k-k}')
2144 wort
= re
.sub(u
'(?<=[AEIOUYÄÖÜaeiouyäöü])ck(?=[aeiouyäöü])',
2147 # Keine Trennung nach nur einem Buchstaben am Wortanfang:
2149 # >>> ableitung1901(u'Es-te')
2151 # >>> print ableitung1901(u'Nord=os-ten')
2153 # >>> print ableitung1901(u'Po-ly<es-ter')
2156 # Im Wort sind Einvokalsilben erlaubt (aber nicht immer günstig):
2158 # >>> print ableitung1901(u'the-is-tisch')
2163 wort
= re
.sub(u
'((?<=[=<].)|(?<=^.))-', ur
'', wort
)
2168 # Rechtschreibänderungen
2169 # ~~~~~~~~~~~~~~~~~~~~~~
2171 # Diese Regeln ändern die Schreibung des ungetrennten Worts (und somit den
2172 # Schlüssel im Langformat der Wortliste).
2177 # K38: Kein "ss" und "sst" am Silbenende (ungetrenntes "ss" nur in Ausnahmen)
2178 # (Andererseit ist 'ßt' und Schluss-ß auch in de-1996 möglich (langer Vokal).)
2180 # Ersetze ungetrenntes 'ss' mit 'ß':
2182 # >>> print ableitung1901(u'passt')
2184 # >>> print ableitung1901(u'Hass')
2186 # >>> print ableitung1901(u'Fass=brau-se')
2188 # >>> print ableitung1901(u'wuss-te')
2191 # ß steht für inlautendes ss, wenn ein 'e' ausfällt (und der Ausfall nicht
2192 # durch Apostroph angedeutet wird)
2194 # >>> print ableitung1901(u'wäss-rig')
2196 # >>> print ableitung1901(u'an<ge<mess-ner')
2198 # >>> print ableitung1901(u'duss-lig')
2200 # >>> print ableitung1901(u'bissl')
2203 # Keine Wandlung zu "ß":
2204 # getrenntes Doppel-s (s-s)
2206 # >>> print ableitung1901(u'Was-ser')
2209 # Vokal folgt (Fremdwörter):
2210 # >>> print ableitung1901(u'Com-tesse')
2213 # Großbuchstabe folgt
2215 # >>> print ableitung1901(u'WissZeitVG') # Abkürzung
2218 # Drei oder mehr 's' = Lautmalerei → erhalten:
2220 # >>> print ableitung1901(u'pssst')
2225 wort
= re
.sub(u
'(?<=[^s])ss(?=[^aeiouyäöüA-Zs]|$)', ur
'ß', wort
)
2227 # Unterdrückung der Trennstelle nach "…ß=er" und "…ß=en" nicht nötig:
2229 # >>> print ableitung1901(u'hass=er<.füllt')
2234 wort
= re
.sub(ur
'ß(=+)e([rn][<-]+)\.', ur
'ß\1e\2', wort
)
2236 # Dreikonsonantenregel
2237 # """"""""""""""""""""
2239 # K78: Zusammensetzungen, bei denen von drei zusammenstoßenden gleichen
2240 # Konsonanten einer entfällt (K15), schreibt man bei Silbentrennung wieder
2241 # mit allen drei Konsonanten.
2243 # Ersetze 'xx=x' xit '{xx/xx=x}' (für alle Konsonanten vor Selbstlaut)
2245 # >>> print ableitung1901(u'Kipp=pflug')
2247 # >>> print ableitung1901(u'Kipp=punkt')
2249 # >>> print ableitung1901(u'Ab<fall=la-ger')
2250 # Ab<fa{ll/ll=l}a-.ger
2251 # >>> print ableitung1901(u'All<lie-be')
2253 # >>> print ableitung1901(u'hell>licht')
2255 # >>> print ableitung1901(u'Pro<gramm==maß=nah-me')
2256 # Pro<gra{mm/mm==m}aß=nah-me
2260 wort
= re
.sub(ur
'([bfglmnprt])\1([<=>]+)\1(?=[aeiouyäöü])',
2261 ur
'{\1\1/\1\1\2\1}', wort
)
2263 # Unterdrücken der Trennung nach nur einem Buchstaben::
2265 wort
= re
.sub(ur
'(?<=[=>].}[aeiouyäöü])([-<])\.?', ur
'\1.', wort
)
2272 # Ein-Vokal-Silben auch schon 1901 erlaubt:
2274 # >>> print ableitung1901(u'ver<knäu-e-le')
2278 # versalschreibung()
2279 # ------------------
2280 # Ersetze 'ß' mit 'ss', trenne je nach Sprachvarietät `lang`:
2282 # >>> from wortliste import versalschreibung
2283 # >>> print versalschreibung(u'paßt')
2285 # >>> print versalschreibung(u'Dar-ßer')
2287 # >>> print versalschreibung(u'bü-ßen', 'de-CH')
2289 # >>> print versalschreibung(u'bü-ßen', 'de-1996-x-versal')
2291 # >>> print versalschreibung(u'bü-ßen', 'de-CH-1901')
2293 # >>> print versalschreibung(u'äßen', 'de-CH-1901')
2295 # >>> print versalschreibung(u'auf<äßen', 'de-CH-1901')
2297 # >>> print versalschreibung(u'auf<eßt', 'de-CH-1901')
2299 # >>> print versalschreibung(u'Groß=se-gel', 'de-1901-x-versal')
2301 # >>> print versalschreibung(u'Groß=se-gel', 'de-CH-1901')
2303 # >>> print versalschreibung(u'Paß=sy-ste-me', 'de-CH-1901')
2305 # >>> print versalschreibung(u'Pro<zeß=en-de', 'de-CH-1901')
2307 # >>> print versalschreibung(u'Pro<zess=en-.de', 'de-CH-1901')
2309 # >>> print versalschreibung(u'Fluß==sy-stem', 'de-CH-1901')
2311 # >>> print versalschreibung(u'Meß==sen-der', 'de-CH-1901')
2316 def versalschreibung(wort
, lang
='de'):
2318 if not u
'ß' in wort
:
2321 wort
= wort
.replace(u
'ß', u
'ss')
2323 # Trennung von Ersatz-ss in de-CH und de-1996 nach Sprechsilbenregel::
2325 if '1901-x-versal' not in lang
:
2326 # wort = re.sub(u'(?<=[aeiouyäöü])-\.?ss', u's-s', wort)
2327 wort
= re
.sub(u
'-\.?ss(?=[aeiouyäöü])', u
's-s', wort
)
2328 wort
= re
.sub(u
'(?<=^[aeiouyäöü])ss(?=[aeiouyäöü])', u
's-s', wort
)
2329 wort
= re
.sub(u
'(?<=[=<][aeiouyäöü])ss(?=[aeiouyäöü])', u
's-s', wort
)
2331 # Unterdrückung irreführender Trennung::
2333 wort
= re
.sub(u
'ss(=+)(en|er)([<-])\.?', ur
'ss\1\2\3.', wort
)
2335 # Dreikonsonantenregel für Ersatz-ss in de-CH-1901::
2337 if 'CH-1901-x-dreikonsonanten' in lang
:
2338 wort
= re
.sub(u
'ss(=+)s(?=[aeiouyäöü])', ur
'{ss/ss\1s}', wort
)
2339 # Unterdrücken der Trennung nach nur einem Buchstaben und irreführender Trennungen
2340 wort
= re
.sub(ur
'(?<=[=>]s}[aeiouyäöü])([-<])\.?', ur
'\1.', wort
)
2341 # wort = re.sub(ur'(?<===s}[aeiouyäöü])([-<])\.?', ur'\1.', wort) # Reißverschus=sy-.stem
2342 wort
= re
.sub(u
'(?<=[=>]s})(en|er)([<-])\.?', ur
'\1\2.', wort
)
2347 # Kurzformat in Langformat
2348 # ------------------------
2350 # Zusätzlich benötigte Felder werden automatisch erzeugt. Ein Kurzeintrag kann
2351 # mehrere Langeinträge ergeben:
2353 # >>> from wortliste import short2long
2354 # >>> for line in short2long([u"Diens-te", u"Ge<biss"]):
2355 # ... print unicode(line)
2356 # Dienste;-2-;Dien-ste;Diens-te
2357 # Gebiss;-2-;-3-;Ge<biss;Ge<biss
2358 # Gebiß;-2-;Ge<biß;-4-
2360 # >>> for line in short2long([u"Ge<schoss", u"Ge<schoß # österr."]):
2361 # ... print unicode(line)
2362 # Geschoss;-2-;-3-;Ge<schoss;Ge<schoss
2363 # Geschoß;Ge<schoß # österr.
2369 def short2long(lines
, sort
=True, prune
=True):
2370 """Convert sequence of lines in ShortEntry format to WordEntry instances.
2373 # Sammeln in Liste und Dictionary:
2374 words
= {} # zum Zusammenfassen
2378 shortentry
= ShortEntry(line
)
2379 shortentry
.complete()
2380 for entry
in shortentry
.wordentries(prune
=False):
2381 key
= entry
.key().lower()
2382 try: # Eintrag mit gleichem Schlüssel vorhanden?
2383 altentry
= words
[key
]
2384 except KeyError: # nein -> neuer Eintrag
2386 entries
.append(entry
)
2389 if entry
[3]: # de-1996 non-empty
2390 entry
.merge(altentry
, prune
=False, allow_alternatives
=True)
2391 # Alternativen Eintrag "in-place" ersetzen:
2392 for i
, word
in enumerate(entry
):
2394 altentry
.comment
= entry
.comment
2396 altentry
.merge(entry
, prune
=False, allow_alternatives
=True)
2397 except AssertionError as e
:
2398 sys
.stderr
.write(unicode(e
).encode('utf8')+'\n')
2399 entries
.append(entry
)
2400 except IndexError: # Leerer Eintrag (Kommentar)
2401 entries
.append(entry
)
2405 for entry
in entries
:
2409 entries
.sort(key
=sortkey_duden
)
2415 # Kommentare bleiben erhalten:
2417 # >>> for line in short2long([u'Aal=an-geln', u'# toller Kommentar'],
2419 # ... print unicode(line)
2420 # Aalangeln;Aal=an-geln
2421 # # toller Kommentar
2423 # Beim Sortieren werden Kommenare an den Beginn geschrieben.
2424 # >>> for line in short2long([u'# erster Kommentar',
2425 # ... u'Aal=an-geln',
2426 # ... u'# zweiter Kommentar']):
2427 # ... print unicode(line)
2428 # # erster Kommentar
2429 # # zweiter Kommentar
2430 # Aalangeln;Aal=an-geln
2433 # Langformat in Kurzformat
2434 # ------------------------
2436 # Optionale Felder werden weggelassen, wenn sie mit dem automatisch erzeugten
2437 # Inhalt übereinstimmen:
2439 # >>> from wortliste import long2short
2440 # >>> for entry in long2short([u"Dienste;-2-;Dien-ste;Diens-te"]):
2441 # ... print unicode(entry)
2444 # Zusammengehörige Einträge werden zusammengefasst:
2446 # >>> for line in long2short([u"Großanlass;-2-;-3-;Groß=an<lass",
2447 # ... u"Großanlaß;-2-;Groß=an<laß;-4-",
2448 # ... u"Grossanlass;-2-;-3-;-4-;Gross=an<lass"]):
2449 # ... print unicode(line)
2455 # Bei einigen Wörtern ist die ß-ss-Beziehung nicht eindeutig:
2457 # ======= ======== =========
2458 # de1901 de-1996 de-CH
2459 # ======= ======== =========
2462 # Geschoß Geschoß Geschoss
2463 # Geschoß Geschoss Geschoss
2464 # ======= ======== =========
2466 # Daher kann es vorkommen, dass ein Langform-Eintrag Beiträge von
2467 # verschiedenen Kurzformen erhält. So erzeugen, z.B. sowohl
2468 # „Masse“ als auch „Maße“ einen Langeintrag mit Schlüssel „Masse“:
2470 # >>> for entry in short2long([u'Mas-se']): print unicode(entry)
2472 # >>> for entry in short2long([u'Ma-ße']): print unicode(entry)
2473 # Masse;-2-;-3-;-4-;-5-;Ma-sse;Mas-se;Mas-se
2476 # In der Langform werden Alternativen in ein Feld geschrieben:
2478 # >>> for entry in short2long([u'Mas-se', u'Ma-ße']):
2479 # ... print unicode(entry)
2480 # Masse;-2-;Mas-se;Mas-se;-5-;Ma[s-/-s]se;Mas-se;Mas-se
2483 # Sind die Alternativen bereits in der Quelle, bleiben sie in der Kurzform
2486 # >>> ml = [u'Masse;-2-;Mas-se;Mas-se;-5-;Ma[-s/s-]se;Mas-se;Mas-se',
2487 # ... u'Maße;Ma-ße']
2488 # >>> for entry in long2short(ml):
2489 # ... print unicode(entry)
2490 # Mas-se;Mas-se;Mas-se;Ma[-s/s-]se
2491 # Ma-ße;Ma-ße;Mas-se;Ma[-s/s-]se
2493 # Zurück in die Langform:
2495 # >>> for entry in short2long([u'Mas-se;Mas-se;Mas-se;Ma[-s/s-]se',
2496 # ... u'Ma-ße;Ma-ße;-3-;Ma[-s/s-]se']):
2497 # ... print unicode(entry)
2498 # Masse;-2-;Mas-se;Mas-se;-5-;Ma[-s/s-]se;Mas-se;Mas-se
2505 def long2short(lines
, prune
=True, drop_sz
=False):
2506 """Convert sequence of 8-column lines to ShortEntry instances."""
2507 words
= {} # Einträge mit `de`
2508 words_x
= {} # Einträge ohne `de`
2509 words_merged
= set() # Einträge die vollständig in andere einsortiert wurden
2510 entries
= [] # Rückgabewert: Liste der Kurzeinträge
2512 # Zeilen Einlesen, Wandeln und Sammeln::
2515 longentry
= WordEntry(line
)
2516 entry
= ShortEntry(longentry
, prune
=False)
2517 key
= entry
.key().lower() # Schlüssel ohne Großschreibung
2519 if not entry
: # reiner Kommentar oder leerer Eintrag
2521 entries
.append(entry
)
2524 # Einträge mit leerem ersten Feld werden später einsortiert:
2525 if prune
and not entry
[0]:
2526 words_x
[key
] = entry
2529 # Eintrag in `dictionary` und Liste:
2531 entries
.append(entry
)
2533 # Straffen (Weglassen von Feldern/Einträgen wenn möglich),
2534 # es sei denn, der Aufruf erfolgte mit `prune=False`::
2539 for entry
in entries
:
2542 for i
in range(1, len(entry
)):
2543 if not entry
[i
]: # Feld ausgekreuzt
2544 key_i
= join_word(entry
.getitem(i
, substitute
=True)).lower()
2547 co_entry
= words_x
[key_i
]
2548 entry
.merge(co_entry
, prune
=False, start
=1)
2549 words_merged
.add(key_i
)
2550 # del words_x[key_i]
2551 except (KeyError, AssertionError):
2553 co_entry
= words
[key_i
]
2554 entry
.merge(co_entry
, prune
=False, start
=1)
2555 words_merged
.add(key_i
)
2556 except (KeyError, AssertionError):
2559 # Anhängen aller Einträge mit leerem `de`-Feld, die nicht in
2560 # einen zugehörigen Eintrag einsortiert wurden an die Liste::
2562 for key
, entry
in words_x
.iteritems():
2563 # print key, unicode(entry)
2564 if key
in words_merged
:
2567 if len(entry
) > 2 and not entry
[3]: # de-1901-x-versal
2568 key
= join_word(entry
.getitem(3, substitute
=True)).lower()
2570 co_entry
= words_x
[key
]
2571 entry
.merge(co_entry
, prune
=False, start
=3)
2572 words_merged
.add(key
)
2573 except (KeyError, AssertionError):
2575 co_entry
= words
[key
]
2576 entry
.merge(co_entry
, prune
=False, start
=3)
2577 # print key, unicode(co_entry)
2578 except (KeyError, AssertionError):
2581 entries
.append(entry
)
2583 for entry
in entries
:
2584 entry
.prune(drop_sz
)
2590 # Separate Einträge ß-Schreibungen zusammenfassen:
2592 # Ein von 2 ß wird zu ss in de-1996:
2594 # >>> for entry in long2short([
2595 # ... u'Passstrasse;-2-;-3-;-4-;-5-;Pass=stra-sse;Pass=stras-se;Pass=stras-se',
2596 # ... u'Passstraße;-2-;-3-;Pass=stra-ße',
2597 # ... u'Paßstraße;-2-;Paß=stra-ße;-4-']):
2598 # ... print unicode(entry)
2603 # >>> for line in long2short([
2604 # ... u"Grossserie;-2-;-3-;-4-;-5-;Gross=se-rie;Gross=se-rie",
2605 # ... u"Großserie;Groß=se-rie"]):
2606 # ... print unicode(line)
2609 # kein ß in de-1996:
2611 # >>> for line in long2short([
2612 # ... u"Basssaite;-2-;-3-;Bass=sai-te;-5-;Bass=sai-te;Bass=sai-te",
2613 # ... u"Baßsaite;-2-;Baß=sai-te;-4-"]):
2614 # ... print unicode(line)
2617 # Zusätzliche Variante (Fremdwort vs. Lehnwort) in de-1901:
2619 # >>> for entry in short2long([u'Boss;Boss # en.']): print unicode(entry)
2621 # >>> for entry in long2short([u'Boss;Boss # en.']): print unicode(entry)
2623 # >>> for entry in long2short([u'Boss;Boss # en.', u'Boß;-2-;Boß;-3- # < en.']):
2624 # ... print unicode(entry)
2628 # Alternativschreibung in de-1996 (Geschoß, Löß):
2630 # >>> for entry in long2short([u'Geschoss;-2-;-3-;Ge<schoss;Ge<schoss',
2631 # ... u'Geschoß;Ge<schoß # österr. auch de-1996']):
2632 # ... print unicode(entry)
2634 # Ge<schoß # österr. auch de-1996
2636 # Eigennamen auf -ss:
2638 # >>> for entry in long2short([u'Vossstrasse;-2-;-3-;-4-;-5-;Voss=stra-sse;Voss=stras-se;Voss=stras-se',
2639 # ... u'Vossstraße;Voss=stra-ße',
2640 # ... u'Voßstraße;Voß=stra-ße']):
2641 # ... print unicode(entry)
2642 # Voss=stra-ße;Voss=stra-ße
2647 # >>> long2short(['# toller Kommentar'], prune=False)
2648 # [ShortEntry(u'# toller Kommentar')]
2649 # >>> long2short(['# toller Kommentar'], prune=True)
2650 # [ShortEntry(u'# toller Kommentar')]
2659 # Trennzeichen entfernen::
2661 def join_word(wort
, assert_complete
=False):
2663 # Einfache Trennzeichen:
2665 # == ================================================================
2666 # \= Trennstelle an Wortfugen (Wort=fu-ge)
2667 # \< Trennstelle nach Präfix (Vor<sil-be)
2668 # \> Trennstelle vor Suffix (Freund>schaf-ten)
2669 # \- Nebentrennstelle (ge-hen)
2670 # \. unerwünschte/ungünstige Trennstelle (Ge<schoss=en<.er-gie)
2671 # == ================================================================
2675 for marker
in u
'=<>-.':
2676 wort
= wort
.replace(marker
, u
'')
2678 # Spezielle Trennungen für die traditionelle Rechtschreibung
2679 # (siehe ../../dokumente/README.wortliste)::
2681 if '{' in wort
or '}' in wort
:
2682 wort
= wort
.replace(u
'{ck/kk}', u
'ck')
2683 wort
= wort
.replace(u
'{ck/k', u
'k')
2684 wort
= wort
.replace(u
'k}', u
'k')
2685 # Konsonanthäufungen an Wortfuge: '{xx/xxx}' -> 'xx':
2686 wort
= re
.sub(ur
'\{(.)\1/\1\1\1\}', ur
'\1\1', wort
)
2687 # schon getrennt: ('{xx/xx' -> 'xx' und 'x}' -> 'x'):
2688 wort
= re
.sub(ur
'\{(.)\1/\1\1$', ur
'\1\1', wort
)
2689 wort
= re
.sub(ur
'^(.)\}', ur
'\1', wort
)
2691 # Trennstellen in doppeldeutigen Wörtern::
2693 if '[' in wort
or ']' in wort
:
2694 wort
= re
.sub(ur
'\[(.*)/\1\]', ur
'\1', wort
)
2696 wort
= re
.sub(ur
'\[([^/\[]+)$', ur
'\1', wort
)
2697 wort
= re
.sub(ur
'^([^/\]]+)\]', ur
'\1', wort
)
2699 # Test auf verbliebene komplexe Trennstellen::
2702 for spez
in u
'[{/}]':
2704 raise AssertionError('Spezialtrennung %s, %s' %
2705 (wort
.encode('utf8'), wort
.encode('utf8')))
2712 # Zerlege ein Wort mit Trennzeichen in eine Liste von Silben und eine Liste
2715 # >>> from wortliste import zerlege
2717 # >>> zerlege(u'Haupt=stel-le')
2718 # ([u'Haupt', u'stel', u'le'], [u'=', u'-'])
2719 # >>> zerlege(u'Ge<samt=be<triebs=rats==chef')
2720 # ([u'Ge', u'samt', u'be', u'triebs', u'rats', u'chef'], [u'<', u'=', u'<', u'=', u'=='])
2721 # >>> zerlege(u'an<stands>los')
2722 # ([u'an', u'stands', u'los'], [u'<', u'>'])
2723 # >>> zerlege(u'An<al.pha-bet')
2724 # ([u'An', u'al', u'pha', u'bet'], [u'<', u'.', u'-'])
2729 silben
= re
.split(u
'[-·._<>=]+', wort
)
2730 trennzeichen
= re
.split(u
'[^-·._|<>=]+', wort
)
2731 return silben
, [tz
for tz
in trennzeichen
if tz
]
2736 # Gib einen String mit Trennmarkierung für abweichende Trennungen in
2737 # mehrdeutigen Wörtern zurück:
2739 # >>> from wortliste import alternatives
2740 # >>> alternatives(u'Mas-se', u'Ma-sse')
2742 # >>> alternatives(u'Ma-sse', u'Mas-se')
2747 def alternatives(wort1
, wort2
):
2749 wort1
= [c
for c
in wort1
]
2750 wort2
= [c
for c
in wort2
]
2753 for c1
, c2
in zip(wort1
, wort2
):
2757 for c1
, c2
in zip(wort1
.__reversed__(), wort2
.__reversed__()):
2762 return u
''.join(pre
+ [u
'['] + wort1
[len(pre
):-len(post
)] + [u
'/']
2763 + wort2
[len(pre
):-len(post
)] + [u
']'] + post
)
2768 # Fehler beim Übertragen von Trennstellen mit uebertrage()_::
2770 class TransferError(ValueError):
2771 def __init__(self
, wort1
, wort2
):
2772 msg
= u
'Inkompatibel: %s %s' % (wort1
, wort2
)
2773 ValueError.__init
__(self
, msg
.encode('utf8'))
2775 def __unicode__(self
):
2776 return str(self
).decode('utf8')
2782 # Übertrage die Trennzeichen von `wort1` auf `wort2`:
2784 # >>> from wortliste import uebertrage, TransferError
2786 # >>> uebertrage(u'Haupt=stel-le', u'Haupt·stel·le')
2789 # Auch teilweise Übertragung, von "kategorisiert" nach "unkategorisiert":
2791 # >>> print uebertrage(u'Haupt=stel-le', u'Haupt=stel·le')
2794 # >>> print uebertrage(u'Haupt·stel-le', u'Haupt=stel·le')
2797 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler')
2800 # >>> print uebertrage(u'Freund>schaf·ten', u'Freund-schaf-ten')
2803 # Übertragung doppelter Marker:
2805 # >>> print uebertrage(u'ver<<aus<ga-be', u'ver<aus<ga-be')
2808 # >>> print uebertrage(u'freund>lich>>keit', u'freund>lich>keit')
2811 # >>> print uebertrage(u'Amts==haupt=stel-le', u'Amts=haupt=stel-le')
2812 # Amts==haupt=stel-le
2814 # Kein Überschreiben doppelter Marker:
2815 # >>> print uebertrage(u'ver<aus<ga-be', u'ver<<aus<ga-be')
2818 # >>> print uebertrage(u'Amts=haupt=stel-le', u'Amts==haupt=stel·le')
2819 # Amts==haupt=stel-le
2821 # Erhalt des Markers für ungünstige Stellen:
2822 # >>> print uebertrage(u'An·al.pha·bet', u'An<al.pha-bet')
2825 # Keine Übertragung, wenn die Zahl oder Position der Trennstellen
2826 # unterschiedlich ist oder bei unterschiedlichen Wörtern:
2829 # ... uebertrage(u'Ha-upt=stel-le', u'Haupt=stel·le')
2830 # ... uebertrage(u'Haupt=ste-lle', u'Haupt=stel·le')
2831 # ... uebertrage(u'Waupt=stel-le', u'Haupt=stel·le')
2832 # ... except TransferError:
2835 # Übertragung auch bei unterschiedlicher Schreibung oder Position der
2836 # Trennstellen mit `strict=False` (für Abgleich zwischen Sprachvarianten):
2838 # >>> uebertrage(u'er-ster', u'ers·ter', strict=False)
2840 # >>> uebertrage(u'Fluß=bett', u'Fluss·bett', strict=False)
2842 # >>> uebertrage(u'ab>bei-ßen', u'ab>beis·sen', strict=False)
2844 # >>> print uebertrage(u'Aus<tausch=dien-stes', u'Aus-tausch=diens-tes', False)
2845 # Aus<tausch=diens-tes
2847 # Auch mit `strict=False` muß die Zahl der Trennstellen übereinstimmen
2848 # (Ausnahmen siehe unten):
2851 # ... uebertrage(u'Ha-upt=ste-lle', u'Haupt=stel·le', strict=False)
2852 # ... except TransferError:
2855 # Akzeptiere unterschiedliche Anzahl von Trennungen bei st und ck nach
2858 # >>> uebertrage(u'acht=ecki-ge', u'acht·e{ck/k·k}i·ge', strict=False)
2859 # u'acht=e{ck/k-k}i-ge'
2860 # >>> uebertrage(u'As-to-ria', u'Asto·ria', strict=False)
2862 # >>> uebertrage(u'Asto-ria', u'As·to·ria', strict=False)
2864 # >>> uebertrage(u'So-fa=ecke', u'So·fa=e{ck/k-k}e', strict=False)
2865 # u'So-fa=e{ck/k-k}e'
2866 # >>> uebertrage(u'Drei=ecks=ecke', u'Drei=ecks==e{ck/k-k}e', strict=False)
2867 # u'Drei=ecks==e{ck/k-k}e'
2869 # Mit ``upgrade=False`` werden nur unspezifische Trennstellen überschrieben:
2871 # >>> print uebertrage(u'an=stel-le', u'an<stel·le', upgrade=False)
2874 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler', upgrade=False)
2877 # >>> print uebertrage(u'Aus-stel-ler', u'Aus<stel-ler', upgrade=False)
2880 # >>> print uebertrage(u'vor<an<<stel-le', u'vor-an<stel·le', upgrade=False)
2885 selbstlaute
= u
'aeiouäöüAEIOUÄÖÜ'
2887 def uebertrage(wort1
, wort2
, strict
=True, upgrade
=True):
2889 silben1
, trennzeichen1
= zerlege(wort1
)
2890 silben2
, trennzeichen2
= zerlege(wort2
)
2891 # Prüfe strikte Übereinstimmung:
2892 if silben1
!= silben2
and strict
:
2893 if u
'<' in trennzeichen1
or u
'·' in trennzeichen2
:
2894 raise TransferError(wort1
, wort2
)
2897 # Prüfe ungefähre Übereinstimmung:
2898 if len(trennzeichen1
) != len(trennzeichen2
):
2899 # Selbstlaut + st oder ck?
2900 for s
in selbstlaute
:
2901 if (wort2
.find(s
+u
'{ck/k·k}') != -1 or
2902 wort2
.find(s
+u
'{ck/k-k}') != -1):
2903 wort1
= re
.sub(u
'%sck([%s])'%(s
,selbstlaute
),
2904 ur
'%s-ck\1'%s, wort1
)
2905 silben1
, trennzeichen1
= zerlege(wort1
)
2906 if wort2
.find(s
+u
's·t') != -1:
2907 wort1
= wort1
.replace(s
+u
'st', s
+u
's-t')
2908 silben1
, trennzeichen1
= zerlege(wort1
)
2909 elif wort1
.find(s
+u
's-t') != -1:
2910 wort1
= wort1
.replace(s
+u
's-t', s
+u
'st')
2911 silben1
, trennzeichen1
= zerlege(wort1
)
2912 # print u'retry:', silben1, trennzeichen1
2913 # immer noch ungleiche Zahl an Trennstellen?
2914 if len(trennzeichen1
) != len(trennzeichen2
):
2915 raise TransferError(wort1
, wort2
)
2917 # Baue wort3 aus silben2 und spezifischeren Trennzeichen:
2918 wort3
= silben2
.pop(0)
2919 for t1
,t2
in zip(trennzeichen1
, trennzeichen2
):
2920 if ((t2
== u
'·' and t1
!= u
'.') # unspezifisch
2922 ((t2
in (u
'-', u
'<') and t1
in (u
'<', u
'<<', u
'<=')) # Praefixe
2923 or (t2
in (u
'-', u
'>') and t1
in (u
'>', u
'>>', u
'=>')) # Suffixe
2924 or (t2
in (u
'-', u
'=') and t1
in (u
'=', u
'==', u
'===')) # W-fugen
2928 elif t2
== u
'.' and t1
not in u
'·.':
2932 wort3
+= silben2
.pop(0)
2936 # Übertrag kategorisierter Trennstellen zwischen den Feldern aller Einträge
2939 def sprachabgleich(entry
, vorbildentry
=None):
2942 return # allgemeine Schreibung
2944 mit_affix
= None # < oder >
2945 kategorisiert
= None # kein ·
2946 unkategorisiert
= None # mindestens ein ·
2947 gewichtet
= None # == oder <= oder =>
2948 for field
in entry
[1:]:
2949 if not field
: # -2-, -3-, ...
2951 if u
'{' in field
and u
'[' in field
: # Bi-ber==be[t=t/{tt/tt=t}]uch
2952 continue # zu komplex
2954 unkategorisiert
= field
2955 elif u
'<' in field
or u
'>' in field
:
2958 kategorisiert
= field
2959 if u
'==' in field
or u
'<=' in field
or u
'=>' in field
:
2962 for field
in vorbildentry
[1:]:
2963 if not field
: # -2-, -3-, ...
2965 if u
'{' in field
and u
'[' in field
: # Bi-ber==be[t=t/{tt/tt=t}]uch
2966 continue # zu komplex
2967 if not mit_affix
and u
'<' in field
or u
'>' in field
:
2969 elif not kategorisiert
and unkategorisiert
and u
'·' not in field
:
2970 kategorisiert
= field
2971 if not gewichtet
and u
'==' in field
or u
'<=' in field
or u
'=>' in field
:
2973 # print 've:', mit_affix, kategorisiert, unkategorisiert
2974 if mit_affix
and (kategorisiert
or unkategorisiert
or gewichtet
):
2975 for i
in range(1,len(entry
)):
2976 if not entry
[i
]: # -2-, -3-, ...
2978 if u
'<' not in entry
[i
] or u
'·' in entry
[i
]:
2980 entry
[i
] = uebertrage(mit_affix
, entry
[i
], strict
=False)
2981 except TransferError
, e
:
2982 if not '/' in entry
[i
]:
2983 print u
'Sprachabgleich:', unicode(e
)
2984 # print mit_affix+u':', unicode(entry)
2985 elif kategorisiert
and unkategorisiert
:
2986 for i
in range(1,len(entry
)):
2987 if u
'·' in entry
[i
]:
2989 entry
[i
] = uebertrage(kategorisiert
, entry
[i
], strict
=False)
2990 except TransferError
, e
:
2991 print u
'Sprachabgleich:', unicode(e
)
2992 # print kategorisiert, unicode(entry)
2994 for i
in range(1,len(entry
)):
2995 if u
'=' in entry
[i
] and not (
2996 u
'{' in entry
[i
] and u
'[' in entry
[i
]):
2998 entry
[i
] = uebertrage(gewichtet
, entry
[i
], strict
=False)
2999 except TransferError
, e
:
3000 print u
'Sprachabgleich:', unicode(e
)
3004 # Großschreibung in Kleinschreibung wandeln und umgekehrt
3006 # Diese Version funktioniert auch für Wörter mit Trennzeichen (während
3007 # str.title() nach jedem Trennzeichen wieder groß anfängt)
3009 # >>> from wortliste import toggle_case
3010 # >>> toggle_case(u'Ha-se')
3012 # >>> toggle_case(u'arm')
3014 # >>> toggle_case(u'frei=bier')
3016 # >>> toggle_case(u'L}a-ger')
3019 # Keine Änderung bei Wörtern mit Großbuchstaben im Inneren:
3021 # >>> toggle_case(u'USA')
3023 # >>> toggle_case(u'iRFD')
3026 # >>> toggle_case(u'gri[f-f/{ff/ff')
3028 # >>> toggle_case(u'Gri[f-f/{ff/ff')
3033 def toggle_case(wort
):
3035 key
= join_word(wort
, assert_complete
=True)
3036 except AssertionError:
3041 return wort
[0].upper() + wort
[1:]
3048 # Duden-Sortierung für die Wortliste
3050 # >>> from wortliste import sortkey_duden
3051 # >>> sortkey_duden([u"Abflußröhren"])
3052 # u'abflussrohren a*bflu*szroehren'
3053 # >>> sortkey_duden([u"Abflußrohren"])
3054 # u'abflussrohren a*bflu*szro*hren'
3055 # >>> sortkey_duden([u"Abflussrohren"])
3058 # >>> s = sorted([[u"Abflußröhren"], [u"Abflußrohren"], [u"Abflussrohren"]],
3059 # ... key=sortkey_duden)
3060 # >>> print ', '.join(e[0] for e in s)
3061 # Abflussrohren, Abflußrohren, Abflußröhren
3065 # Ligaturen auflösen und andere "normalisierunde" Ersetzungen für den
3066 # (Haupt-)Sortierschlüssel (Akzente werden über ``unicodedata.normalize``
3075 # "Zweitschlüssel" zur Unterscheidung von Umlauten/SZ und Basisbuchstaben::
3077 umschrift_subkey
= {
3093 # Schlüssel für die alphabetische Sortierung gemäß Duden-Regeln.
3095 # Argument ist ein Eintrag im WordEntry oder ShortEntry Format oder
3096 # ein (Unicode) String:
3098 # >>> print sortkey_duden(weste) # WordEntry
3100 # >>> print sortkey_duden(dst) # ShortEntry
3103 # >>> print sortkey_duden(u'Stra-ße')
3105 # >>> print sortkey_duden([u'Büh-ne'])
3107 # >>> print sortkey_duden(u'Weiß=flog;Weiß=flog;-3-;-4-;-5-')
3108 # weissflog weiszflo*g
3109 # >>> print sortkey_duden(u'-1-;Meß=sen-der;-3-;-4-;-5-')
3110 # messsender meszsender
3111 # >>> print sortkey_duden(u'As-sen # (geogr. und Eigen-) Name\n')
3113 # >>> print sortkey_duden(u'aßen;aßen;-3-;-4-;-5-\n')
3115 # >>> print sortkey_duden(u'äßen\n')
3120 def sortkey_duden(entry
):
3122 # ggf. ungetrenntes Wort extrahieren oder generieren::
3124 if isinstance(entry
, list):
3127 except AttributeError:
3129 if len(entry
) == 1: # ein Muster pro Zeile, siehe z.B. pre-1901
3130 key
= join_word(key
)
3132 match
= re
.search(u
'^[-0-9;]*([^;\s]+)', entry
) # erstes volles Feld
3134 key
= match
.group(1)
3137 key
= join_word(key
)
3138 key
= re
.sub(u
'[0-9;]', ur
'', key
)
3140 # Großschreibung ignorieren:
3142 # Der Duden sortiert Wörter, die sich nur in der Großschreibung unterscheiden
3143 # "klein vor groß" (ASCII sortiert "groß vor klein"). In der
3144 # `Trennmuster-Wortliste` kommen Wörter nur mit der häufiger anzutreffenden
3145 # Großschreibung vor, denn der TeX-Trennalgorithmus ignoriert Großschreibung.
3154 skey
= key
.replace(u
'ß', u
'ss')
3156 # Restliche Akzente weglassen: Wandeln in Darstellung von Buchstaben mit
3157 # Akzent als "Grundzeichen + kombinierender Akzent". Anschließend alle
3158 # nicht-ASCII-Zeichen ignorieren::
3160 skey
= skey
.translate(umschrift_skey
)
3161 skey
= unicodedata
.normalize('NFKD', skey
)
3162 skey
= unicode(skey
.encode('ascii', 'ignore'))
3164 # "Zweitschlüssel" für das eindeutige Einsortieren von Wörtern mit
3165 # gleichem Schlüssel (Masse/Maße, waren/wären, ...):
3167 # * "*" nach aou für die Unterscheidung Grund-/Umlaut
3173 subkey
= key
.translate(umschrift_subkey
)
3174 skey
= u
'%s %s' % (skey
,subkey
)
3176 # Gib den Sortierschlüssel zurück::
3185 # Vergleiche zwei Sequenzen von `WordEntries` (genauer: alle Objekte, die
3186 # sich sinnvoll zu Unicode wandeln lassen).
3190 # >>> from wortliste import udiff
3191 # >>> print udiff([abbeissen, aalbestand], [abbeissen,dresz], 'alt', 'neu')
3195 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
3196 # -Aalbestand;Aal=be<stand # Test
3197 # +Dreß;-2-;Dreß;-4-
3199 # >>> udiff([abbeissen, aalbestand], [abbeissen, aalbestand], 'alt', 'neu')
3204 def udiff(a
, b
, fromfile
='', tofile
='',
3205 fromfiledate
='', tofiledate
='', n
=1, encoding
='utf8'):
3207 a
= [unicode(entry
).rstrip().encode(encoding
) for entry
in a
]
3208 b
= [unicode(entry
).rstrip().encode(encoding
) for entry
in b
]
3210 diff
= '\n'.join(difflib
.unified_diff(a
, b
, fromfile
, tofile
,
3211 fromfiledate
, tofiledate
, n
, lineterm
=''))
3214 return diff
.decode(encoding
)
3220 # Normalisierung und Expansion von Sprachtags nach [BCP47]_
3222 # >>> from wortliste import normalize_language_tag
3223 # >>> normalize_language_tag('de_AT-1901')
3224 # ['de-AT-1901', 'de-AT', 'de-1901', 'de']
3226 # >>> normalize_language_tag('de') # Deutsch, allgemeingültig
3228 # >>> normalize_language_tag('de_1901') # traditionell (Reform 1901)
3230 # >>> normalize_language_tag('de_1996') # reformiert (Reform 1996)
3232 # >>> normalize_language_tag('de_CH') # ohne ß (Schweiz oder versal)
3234 # >>> normalize_language_tag('de-x-versal') # versal
3235 # ['de-x-versal', 'de']
3236 # >>> normalize_language_tag('de-1901-x-versal') # versal
3237 # ['de-1901-x-versal', 'de-1901', 'de-x-versal', 'de']
3238 # >>> normalize_language_tag('de_CH-1996') # Schweiz traditionell (süssauer)
3239 # ['de-CH-1996', 'de-CH', 'de-1996', 'de']
3241 # 'de': 1, # Deutsch, allgemeingültig
3242 # 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
3243 # 'de-1996': 3, # reformierte Reformschreibung (1996)
3244 # 'de-x-versal': 4, # ohne ß (Schweiz oder versal) allgemein
3245 # # 'de-CH': 4, # Alias
3246 # 'de-1901-x-versal': 5, # ohne ß (Schweiz oder versal) "traditionell"
3247 # 'de-1996-x-versal': 6, # ohne ß (Schweiz oder versal) "reformiert"
3248 # # 'de-CH-1996': 6, # Alias
3249 # 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
3254 def normalize_language_tag(tag
):
3255 """Return a list of normalized combinations for a `BCP 47` language tag.
3258 tag
= tag
.replace('_','-')
3259 # split (except singletons, which mark the following tag as non-standard):
3260 tag
= re
.sub(r
'-([a-zA-Z0-9])-', r
'-\1_', tag
)
3262 subtags
= [subtag
.replace('_', '-') for subtag
in tag
.split('-')]
3263 base_tag
= [subtags
.pop(0)]
3264 # find all combinations of subtags
3265 for n
in range(len(subtags
), 0, -1):
3266 # for tags in unique_combinations(subtags, n):
3267 for tags
in itertools
.combinations(subtags
, n
):
3268 taglist
.append('-'.join(base_tag
+list(tags
)))
3277 # Teste Übereinstimmung des ungetrennten Wortes in Feld 1 mit den
3278 # Trennmustern nach Entfernen der Trennmarker. Schreibe Inkonsistenzen auf die
3281 # Das Argument ist ein Iterator über die Einträge (Klasse `WordEntry`). ::
3283 def test_keys(wortliste
):
3284 print u
"Teste Schlüssel-Trennmuster-Übereinstimmung:"
3286 for entry
in wortliste
:
3287 if isinstance(entry
, ShortEntry
):
3288 print u
"Wortliste im Kurzformat: überspringe Schlüssel-Test."
3290 # Test der Übereinstimmung ungetrenntes/getrenntes Wort
3293 for wort
in entry
[1:]:
3294 if not wort
: # leere Felder
3296 if key
!= join_word(wort
):
3298 print u
"\nkey '%s' != join_word('%s')" % (key
, wort
),
3299 if key
.lower() == join_word(wort
).lower():
3300 print(u
" Abgleich der Großschreibung mit"
3301 u
"`prepare-patch.py grossabgleich`."),
3306 # Finde Doppeleinträge (teste, ob jeder Schlüssel nur einmal vorkommt).
3307 # Schreibe Inkonsistenzen auf die Standardausgabe.
3309 # Das Argument ist ein Iterator über die Einträge (Klasse `WordEntry`). ::
3311 def test_uniqueness(wortliste
):
3315 for entry
in wortliste
:
3319 print "da ", unicode(words
[key
])
3320 print "neu", unicode(entry
)
3322 print u
"%d Doppeleinträge." % doppelte
3324 # Teste die Wandlung einer Zeile im "wortliste"-Format in eine
3325 # ``WordEntry``-Instanz und zurück::
3327 def test_str_entry_str_conversion(wordfile
):
3329 for line
in file(wordfile
.name
):
3330 line
= line
.rstrip().decode(wordfile
.encoding
)
3331 entry
= WordEntry(line
)
3332 if line
== unicode(entry
):
3336 print u
'+', unicode(entry
)
3338 print OK
, u
"Einträge rekonstruiert"
3341 # Teste Vervollständigung und Zusammenfassung von Einträgen::
3343 def test_completion_pruning(entries
):
3345 for entry
in entries
:
3346 new
= copy
.copy(entry
)
3350 patch
= udiff(entries
, reko
, 'wortliste', 'neu')
3354 print u
"alle Einträge rekonstruiert"
3358 # Aufruf von der Kommandozeile
3359 # ============================
3363 if __name__
== '__main__':
3366 # sys.stdout mit UTF8 encoding (wie in Python 3)
3367 sys
.stdout
= codecs
.getwriter('UTF-8')(sys
.stdout
)
3368 # sys.stderr = codecs.getwriter('UTF-8')(sys.stderr)
3370 print u
"Test der Werkzeuge und inneren Konsistenz der Wortliste"
3372 # Ein WordFile Dateiobjekt::
3375 wordfile
= WordFile(sys
.argv
[1], format
='auto')
3377 wordfile
= WordFile('../../../wortliste')
3378 # wordfile = WordFile('../../../wlst', format='auto') # wortliste im Kurzformat
3379 # wordfile = WordFile('neu.todo')
3380 # wordfile = WordFile('neu-kurz.todo', format='f5')
3381 # wordfile = WordFile('korrektur.todo', format='auto')
3383 # Format bestimmen::
3385 print "Datei: '%s'" % wordfile
.name
, u
"Format:", wordfile
.format
3387 # Liste der Datenfelder (die Klasseninstanz als Argument für `list` liefert
3388 # den Iterator über die Felder, `list` macht daraus eine Liste)::
3390 wordlist
= list(wordfile
)
3391 print len(wordlist
), u
"Zeilen"
3393 # Ein Wörterbuch (dict Instanz)::
3395 wordfile
.seek(0) # Pointer zurücksetzen
3396 words
= wordfile
.asdict()
3397 print len(words
), u
"Wörterbucheinträge"
3399 # Test auf Doppeleinträge::
3401 if len(words
) != len(wordlist
):
3402 test_uniqueness(wordlist
)
3405 # Teste Schlüssel-Trennmuster-Übereinstimmung::
3407 if isinstance(wordlist
[0], WordEntry
):
3410 # Teste Eintrags-Konsistenz im Kurzformat::
3412 if isinstance(wordlist
[0], ShortEntry
):
3414 langs
= ("de", "de-1901", "de-CH", "de-1901-x-versal", "de-CH-1901")
3418 for entry
in wordlist
:
3421 word
= entry
.get(lang
)
3422 key
= join_word(word
)
3423 if word
and (key
in words
[lang
]):
3424 oldword
= words
[lang
][key
].get(lang
)
3425 if oldword
and (word
!= oldword
):
3427 print "%-16s" % lang
, oldword
, '!=', word
3428 print " "*18, unicode(words
[lang
][key
].pruned())
3429 print " "*18, unicode(entry
.pruned())
3430 words
[lang
][key
] = entry
3431 print u
"%d Doppeleinträge." % doppelte
3434 # Teste Komplettieren/Zusammenfassen der Einträge::
3436 # test_completion_pruning(wordlist)
3442 # sprache = 'de-1901' # traditionell
3443 sprache
= 'de-1996' # Reformschreibung
3444 # sprache = 'de-x-versal' # ohne ß (Schweiz oder versal) allgemein
3445 # sprache = 'de-1901-x-versal' # ohne ß (Schweiz oder versal) "traditionell"
3446 # sprache = 'de-1996-x-versal' # ohne ß (Schweiz oder versal) "reformiert"
3447 # sprache = 'de-CH-1901' # ohne ß (Schweiz) "traditionell" ("süssauer")
3449 # worte = [entry.get(sprache) for entry in wordlist]
3450 # worte = [wort for wort in worte if wort]
3451 # print len(worte), u"Einträge für Sprachvariante", sprache
3453 # Zeilenrekonstruktion::
3455 # test_str_entry_str_conversion(wordfile)
3461 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
3462 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
3464 # .. _aktuelle Rechtschreibung:
3466 # .. [Rechtschreibregeln] Rat für deutsche Rechtschreibung,
3467 # `Deutsche Rechtschreibung – Regeln und Wörterverzeichnis`,
3468 # http://www.rechtschreibrat.com/regeln-und-woerterverzeichnis/
3470 # .. _traditionelle Rechtschreibung:
3472 # .. [Duden1991] Wissenschaftlicher Rat der Dudenredaktion (Editoren),
3473 # `Duden: Rechtschreibung der deutschen Sprache`,
3474 # Dudenverlag Mannheim, 1991.
3476 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
3477 # ../../../dokumente/README.wortliste
3479 # .. _wortliste: ../../../wortliste