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)
15 """Hilfsmittel für die Arbeit mit der `Wortliste`"""
19 # Die hier versammelten Funktionen und Klassen dienen der Arbeit an und
20 # mit der freien `Wortliste der deutschsprachigen Trennmustermannschaft`_
21 # ("Lembergsche Liste")
37 # Klasse zum Lesen und Schreiben der `Wortliste`::
51 # Die spezielle Funktion `__iter__` wird aufgerufen wenn über eine
52 # Klasseninstanz iteriert wird.
54 # Liefer einen Iterator über die "geparsten" Zeilen (Datenfelder)::
57 line
= self
.readline().rstrip().decode(self
.encoding
)
60 line
= self
.readline().rstrip().decode(self
.encoding
)
65 # Lies Datei und trage die Zeilen mit ungetrenntem Wort
66 # als `key` und den Datenfeldern als `value` in ein `dictionary`
67 # (assoziatives Array) ein::
72 words
[entry
[0]] = entry
78 # Schreibe eine Liste von `unicode` Strings (Zeilen ohne Zeilenendezeichen)
79 # in die Datei `destination`::
81 def writelines(self
, lines
, destination
, encoding
=None):
82 outfile
= codecs
.open(destination
, 'w',
83 encoding
=(encoding
or self
.encoding
))
84 outfile
.write(u
'\n'.join(lines
))
90 # Schreibe eine Liste von Einträgen (WordEntry/ShortEntry Objekten) in
91 # die Datei `destination`::
93 def write_entries(self
, wortliste
, destination
, encoding
=None):
94 lines
= (unicode(entry
) for entry
in wortliste
)
95 self
.writelines(lines
, destination
, encoding
)
101 # Klasse für Einträge (Zeilen) der Wortliste
105 # >>> from wortliste import WordEntry, ShortEntry
107 # >>> aalbestand = WordEntry(u'Aalbestand;Aal=be<stand # Test')
108 # >>> print aalbestand
109 # Aalbestand;Aal=be<stand # Test
113 class WordEntry(list):
118 # Kommentare (aktualisiert, wenn Kommentar vorhanden)::
125 # 2. Wort mit Trennungen, falls für alle Varianten identisch,
127 # 3. falls Feld 2 leer, Trennung nach traditioneller Rechtschreibung
128 # 4. falls Feld 2 leer, Trennung nach reformierter Rechtschreibung (2006)
129 # 5. falls Feld 2 leer, Trennung für Wortform, die entweder in
130 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird
131 # und für traditionelle und reformierte Rechtschreibung identisch ist
132 # 6. falls Feld 5 leer, Trennung für Wortform, die entweder in
133 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
134 # traditionelle Rechtschreibung
135 # 7. falls Feld 5 leer, Trennung für Wortform, die entweder in
136 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
137 # reformierte Rechtschreibung (2006)
138 # 8. falls Feld 5 leer, Trennung nach (deutsch)schweizerischer
139 # Rechtschreibung; insbesondere Wörter mit "sss" gefolgt von
140 # einem Vokal, die wie andere Dreifachkonsonanten gehandhabt wurden
141 # (also anders, als der Duden früher vorgeschrieben hat), z.B.
144 # Sprachvarianten (Tags nach [BCP47]_) (Die Zählung der Indizes beginn in
147 feldnamen
= ('de', 'de-1901', 'de-1996', 'de-x-versal',
148 'de-1901-x-versal', 'de-1996-x-versal', 'de-CH-1901')
151 'de': 1, # Deutsch, allgemeingültig
152 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
153 'de-1996': 3, # reformierte Reformschreibung (1996)
154 'de-x-GROSS': 4, # ohne ß (Schweiz oder versal) allgemein
155 'de-x-versal': 4, # ohne ß (Schweiz oder versal) allgemein
156 # 'de-CH': 4, # Alias
157 'de-1901-x-GROSS': 5, # ohne ß (Schweiz oder versal) "traditionell"
158 'de-1901-x-versal': 5, # ohne ß (Schweiz oder versal) "traditionell"
159 'de-1996-x-GROSS': 6, # ohne ß (Schweiz oder versal) "reformiert"
160 'de-1996-x-versal': 6, # ohne ß (Schweiz oder versal) "reformiert"
161 # 'de-CH-1996': 6, # Alias
162 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
171 def __init__(self
, line
, delimiter
=';'):
173 self
.delimiter
= delimiter
175 # eventuell vorhandenen Kommentar abtrennen und speichern::
178 line
, comment
= line
.split(u
'#', 1)
179 self
.comment
= comment
.lstrip()
181 # print (line, self.comment)
183 # Zerlegen in Datenfelder, in Liste eintragen::
185 list.__init
__(self
, line
.split(delimiter
))
188 # Rückverwandlung in String
189 # -----------------------------------
191 # Erzeugen eines Eintrag-Strings (Zeile) aus der Liste der Datenfelder und
194 # >>> unicode(aalbestand)
195 # u'Aalbestand;Aal=be<stand # Test'
196 # >>> unicode(WordEntry(u'# Testkommentar'))
201 def __unicode__(self
):
202 line
= u
';'.join(self
)
204 line
= u
' # '.join((line
, self
.comment
)).lstrip()
208 return unicode(self
).encode('utf8')
213 # Index des zur Sprachvariante gehörenden Datenfeldes:
215 # >>> aalbestand.lang_index('de')
217 # >>> aalbestand.lang_index('de-1901')
219 # >>> aalbestand.lang_index('de-1996')
221 # >>> aalbestand.lang_index('de-x-GROSS')
223 # >>> aalbestand.lang_index('de-1901-x-GROSS')
225 # >>> aalbestand.lang_index('de-1996-x-GROSS')
227 # >>> abbeissen = WordEntry(
228 # ... u'abbeissen;-2-;-3-;-4-;-5-;ab<bei-ssen;ab<beis-sen;ab<beis-sen')
229 # >>> print abbeissen.lang_index('de')
231 # >>> print abbeissen.lang_index('de-x-GROSS')
233 # >>> abbeissen.lang_index('de-CH-1901')
235 # >>> urlaubstipp = WordEntry(u'Urlaubstipp;-2-;-3-;Ur<laubs=tipp')
236 # >>> print urlaubstipp.lang_index('de')
238 # >>> print urlaubstipp.lang_index('de-1901')
240 # >>> print urlaubstipp.lang_index('de-1996')
242 # >>> print urlaubstipp.lang_index('de-x-GROSS')
244 # >>> print urlaubstipp.lang_index('de-1901-x-GROSS')
249 def lang_index(self
, lang
):
251 assert lang
in self
.sprachvarianten
, \
252 'Sprachvariante "%s" nicht in %s' % (lang
,
253 self
.sprachvarianten
.keys())
255 # Einfacher Fall: eine allgemeine Schreibweise::
260 # Spezielle Schreibung::
263 i
= self
.sprachvarianten
[lang
]
266 if i
> 4 and len(self
) == 5:
267 return 4 # Allgemeine Schweiz/GROSS Schreibung:
268 return None # Feld nicht vorhanden
270 if feld
.startswith('-'): # '-1-', '-2-', ...
271 return None # leeres Feld
275 # Trennmuster für Sprachvariante ausgeben
277 # >>> aalbestand.get('de')
279 # >>> aalbestand.get('de-1901')
281 # >>> aalbestand.get('de-1996')
283 # >>> aalbestand.get('de-x-GROSS')
285 # >>> aalbestand.get('de-1901-x-GROSS')
287 # >>> aalbestand.get('de-1996-x-GROSS')
289 # >>> aalbestand.get('de-CH-1901')
292 # >>> weste = WordEntry(u'Weste;-2-;We-ste;Wes-te')
293 # >>> print weste.get('de')
295 # >>> weste.get('de-1901')
297 # >>> weste.get('de-1996')
300 # >>> print abbeissen.get('de')
302 # >>> print abbeissen.get('de-x-GROSS')
304 # >>> print abbeissen.get('de,de-x-GROSS')
306 # >>> abbeissen.get('de-1901-x-GROSS')
308 # >>> abbeissen.get('de,de-1901,de-1901-x-GROSS')
310 # >>> abbeissen.get('de-CH-1901')
315 def get(self
, sprachvarianten
):
316 for lang
in sprachvarianten
.split(','):
317 i
= self
.lang_index(lang
) # integer>0 or None
322 # Trennmuster für Sprachvariante setzen
324 # >>> abbeissen.set('test', 'de-1901-x-GROSS')
325 # >>> print abbeissen
326 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
328 # >>> abbeissen.set('test', 'de-1901')
329 # Traceback (most recent call last):
331 # IndexError: kann kein leeres Feld setzen
333 # >>> abbeissen.set('test', 'de-1901,de-1901-x-GROSS')
334 # >>> print abbeissen
335 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
339 def set(self
, wort
, sprachvarianten
):
340 for lang
in sprachvarianten
.split(','):
341 i
= self
.lang_index(lang
)
348 raise IndexError, "kann kein leeres Feld setzen"
351 # Felder für alle Sprachvarianten ausfüllen
353 # >>> print str(aalbestand), len(aalbestand)
354 # Aalbestand;Aal=be<stand # Test 2
355 # >>> aalbestand.expand_fields()
356 # >>> print len(aalbestand)
358 # >>> auffrass = WordEntry(u'auffrass;-2-;-3-;-4-;auf-frass')
359 # >>> auffrass.expand_fields()
361 # auffrass;-2-;-3-;-4-;auf-frass;auf-frass;auf-frass;auf-frass
362 # >>> fresssack= WordEntry(u'Fresssack;-2-;-3-;Fress=sack;Fress=sack')
363 # >>> fresssack.expand_fields()
364 # >>> print fresssack
365 # Fresssack;-2-;-3-;Fress=sack;Fress=sack;Fress=sack;Fress=sack;Fress=sack
369 def expand_fields(self
):
370 fields
= [self
.get(sv
) or '-%d-' % (self
.sprachvarianten
[sv
] + 1)
371 for sv
in self
.feldnamen
]
373 for i
, field
in enumerate(fields
):
375 self
[i
+1] = field
# Feld 1 ist "key" (ungetrennt)
380 # Felder für Sprachvarianten zusammenfassen
382 # >>> aalbestand.conflate_fields()
383 # >>> print aalbestand
384 # Aalbestand;Aal=be<stand # Test
385 # >>> auffrass.conflate_fields()
387 # auffrass;-2-;-3-;-4-;auf-frass
388 # >>> entry = WordEntry(u'distanziert;-2-;di-stan-ziert;di-stan-ziert')
389 # >>> entry.conflate_fields()
391 # distanziert;di-stan-ziert
392 # >>> entry = WordEntry(u'Gauss;-2-;Gauss;Gauss;Gauss')
393 # >>> entry.conflate_fields()
396 # >>> fresssack.conflate_fields()
397 # >>> print fresssack
398 # Fresssack;-2-;-3-;Fress=sack;Fress=sack
401 # >>> masse = WordEntry(u'Masse;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se;Mas-se')
402 # >>> masse.conflate_fields()
406 # Aber nicht, wenn die Trennstellen sich unterscheiden:
408 # >>> abenddienste = WordEntry(
409 # ... u'Abenddienste;-2-;Abend=dien-ste;Abend=diens-te')
410 # >>> abenddienste.conflate_fields()
411 # >>> print abenddienste
412 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
414 # >>> ackerstrasse = WordEntry(u'Ackerstraße;-2-;A{ck/k-k}er=stra-ße;Acker=stra-ße')
415 # >>> ackerstrasse.expand_fields()
416 # >>> ackerstrasse.conflate_fields()
417 # >>> print unicode(ackerstrasse)
418 # Ackerstraße;-2-;A{ck/k-k}er=stra-ße;Acker=stra-ße
422 def conflate_fields(self
):
424 if self
[7] == self
[6] == self
[5]:
425 self
[4] = self
[5] # umschreiben auf GROSS-allgemein
429 elif (self
[4].startswith(u
'-')
430 and self
[5].startswith(u
'-')
431 and self
[6].startswith(u
'-')
432 and self
[7].startswith(u
'-')):
440 if self
[1] == self
[4]: # de-x-GROSS == de
444 elif self
[2] == self
[3] == self
[4]:
445 self
[1] = self
[2] # Umschreiben auf de (allgemein)
450 if self
[3] == self
[2]: # de-1996 == de-1901
451 self
[1] = self
[2] # Umschreiben auf de (allgemein)
458 # Einträge zusammenfassen
460 # >>> entry = WordEntry(u'Abenddress;Abend=dress')
461 # >>> entry.merge(WordEntry(u'Abenddress;-2-;-3-;-4-;Abend=dress'))
462 # >>> print unicode(entry)
463 # Abenddress;Abend=dress
464 # >>> entry = WordEntry(u'Gauss;Gauss')
465 # >>> entry.merge(WordEntry(u'Gauss;-2-;-3-;-4-;Gauss'))
466 # >>> print unicode(entry)
468 # >>> masse.merge(WordEntry(u'Masse;-2-;-3-;-4-;-5-;Ma-sse;Mas-se;Mas-se'), allow_alternatives=True)
470 # Masse;-2-;Mas-se;Mas-se;-5-;[Mas-se/Ma-sse];Mas-se;Mas-se
474 def merge(self
, other
, allow_alternatives
=False):
476 other
.expand_fields()
477 for i
, (s_field
, o_field
) in enumerate(zip(self
,other
)):
478 if s_field
== o_field
or o_field
.startswith(u
'-'):
480 if s_field
.startswith(u
'-'):
482 elif s_field
!= o_field
:
483 if not allow_alternatives
:
484 self
.conflate_fields()
485 other
.conflate_fields()
486 raise AssertionError('%s != %s' %(s_field
, o_field
))
487 self
[i
] = u
'[%s/%s]' % (s_field
, o_field
)
488 other
.conflate_fields()
489 self
.conflate_fields()
492 # Prüfe auf Vorkommen von Regeländerungen der Orthographiereform 1996.
494 # >>> entry = WordEntry(u'Würste;Wür-ste')
495 # >>> entry.regelaenderungen()
496 # >>> print unicode(entry)
497 # Würste;-2-;Wür-ste;Würs-te
498 # >>> entry = WordEntry(u'Würste;Würs-te')
499 # >>> entry.regelaenderungen()
500 # >>> print unicode(entry)
501 # Würste;-2-;Wür-ste;Würs-te
502 # >>> entry = WordEntry(u'Hecke;He-cke')
503 # >>> entry.regelaenderungen()
504 # >>> print unicode(entry)
505 # Hecke;-2-;He{ck/k-k}e;He-cke
506 # >>> entry = WordEntry(u'Ligusterhecke;Ligu-ster=he{ck/k-k}e')
507 # >>> entry.regelaenderungen()
508 # >>> print unicode(entry)
509 # Ligusterhecke;-2-;Ligu-ster=he{ck/k-k}e;Ligus-ter=he-cke
510 # >>> entry = WordEntry(u'Hass;Hass')
511 # >>> entry.regelaenderungen()
512 # >>> print unicode(entry)
513 # Hass;-2-;-3-;Hass;Hass
514 # >>> entry = WordEntry(u'fasst;fasst')
515 # >>> entry.regelaenderungen()
516 # >>> print unicode(entry)
517 # fasst;-2-;-3-;fasst;fasst
518 # >>> entry = WordEntry(u'Missbrauch;Miss<brauch')
519 # >>> entry.regelaenderungen()
520 # >>> print unicode(entry)
521 # Missbrauch;-2-;-3-;Miss<brauch;Miss<brauch
522 # >>> entry = WordEntry(u'schlifffest;schliff=fest')
523 # >>> entry.regelaenderungen()
524 # >>> print unicode(entry)
525 # schlifffest;-2-;-3-;schliff=fest
529 def regelaenderungen(self
):
530 # Trennregeländerungen:
531 r1901
= (u
'-st', u
'{ck/k-k}')
532 r1996
= (u
's-t', u
'-ck')
534 w1901
= self
.get('de-1901')
535 w1996
= self
.get('de-1996')
538 if w1901
is None or w1996
is None:
541 for r1
, r2
in zip(r1901
, r1996
):
542 w1901
= w1901
.replace(r2
,r1
)
543 w1996
= w1996
.replace(r1
,r2
)
545 # kein Schluss-ss und sst in de-1901 (ungetrenntes "ss" nur in Ausnahmen)
546 # aber: 'ßt' und Schluß-ß auch in de-1996 möglich (langer Vokal)
551 # Dreikonsonantenregel:
552 if w1901
and re
.search(ur
'(.)\1=\1', w1901
):
556 if w1901
== w1996
: # keine Regeländerung im Wort
558 self
.conflate_fields()
562 self
.extend( ['']*(4-len(self
)) )
567 self
.extend( ['']*(4-len(self
)) )
572 self
.append(w_x_GROSS
)
578 # Klasse für Einträge (Zeilen) der Wortlisten (neues, kurzes Format)
580 # >>> from wortliste import ShortEntry
582 # >>> aalbst = ShortEntry(u'Aal=be<stand # Test')
586 class ShortEntry(WordEntry
):
588 # Feldbelegung im Kurzformat (Vorschlag)
590 # Sprachtags nach [BCP47]_.
593 # Wort mit Trennungen nach aktueller Rechtschreibung (de-1996).
594 # Einziges Feld, falls andere Varianten über Regeln gewonnen werden können.
596 # "-1-" falls die Schreibung in de-1996 unzulässig ist (-1-;Pro<zeß).
599 # Wort mit Trennung nach de-1901.
600 # Belegt, falls abweichend von der regelbasierten Ableitung aus "de".
602 # "-2-" falls die Schreibung in de-1901 unzulässig ist (Ur<laubs=tipp;-2-).
604 # de-CH oder de-x-versal:
605 # Wort mit ß-Ersatzschreibung, die entweder in
606 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird.
607 # Trennungen nach aktueller Rechtschreibung (de-CH-1996, de-1996-x-versal).
609 # "-3-" falls die Schreibung in de-CH-1996 unzulässig ist und weitere Felder
613 # Wort mit ß-Ersatzschreibung für de-1901 mit Großbuchstaben oder
615 # Belegt, falls abweichend von der Ableitung aus "de-x-versal".
617 # "-4-" falls die abgeleitete Schreibung in de-1901 unzulässig ist.
620 # Wort mit ß-Ersatzschreibung, die der Schweiz benutzt wird.
621 # Insbesondere Wörter mit „sss“ gefolgt von einem Vokal, die wie
622 # andere Dreifachkonsonanten gehandhabt wurden (also anders, als
623 # bei Ersatzschreibung in Deutschland und Österreich), z.B. „süssauer“
624 # Belegt, falls abweichend von der regelbasierten Ableitung aus "de-CH".
626 # "-5-" falls die abgeleitete Schreibung in de-CH-1901 unzulässig ist.
628 # >>> print ShortEntry.feldnamen
629 # ('de', 'de-1901', 'de-CH', 'de-1901-x-versal', 'de-CH-1901')
630 # >>> print aalbst.sprachvarianten['de'], aalbst.sprachvarianten['de-CH-1901']
632 # >>> print aalbst.sprachvarianten['de-x-versal'], aalbst.sprachvarianten['de-CH']
638 'de', # Deutsch, aktuell (Reform 1996)
639 'de-1901', # "traditionell" (Reform 1901)
640 'de-CH', # ohne ß (Schweiz oder versal) "aktuell"
641 'de-1901-x-versal', # ohne ß (Schweiz oder versal) "traditionell"
642 'de-CH-1901', # ohne ß (Schweiz) "traditionell" ("süssauer")
645 sprachvarianten
= dict((tag
, index
)
646 for (index
,tag
) in enumerate(feldnamen
))
647 sprachvarianten
['de-x-versal'] = 2 # Alias für "ohne ß"
653 # Aal=be<stand # Test
654 # >>> print ShortEntry(aalbestand)
655 # Aal=be<stand # Test
656 # >>> dst = ShortEntry(u'Diens-te;Dien-ste')
659 # >>> fluss = ShortEntry(u'Fluss;Fluß')
660 # >>> print unicode(fluss)
662 # >>> tpp = ShortEntry(u'Ur<laubs=tipp;-2-')
665 # >>> drs = unicode(ShortEntry(u'-1-;Dreß'))
669 # # >>> print ShortEntry(u'# Testkommentar')
673 # Wird dem Konstruktor eine WordEntry-Instanz übergeben, so wird
674 # diese in das Kurzformat überführt:
676 # >>> print abenddienste
677 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
678 # >>> print ShortEntry(abenddienste)
680 # >>> print urlaubstipp
681 # Urlaubstipp;-2-;-3-;Ur<laubs=tipp
682 # >>> print ShortEntry(urlaubstipp)
684 # >>> dress = WordEntry(u'Dress;Dress')
685 # >>> print ShortEntry(dress)
687 # >>> dresz = WordEntry(u'Dreß;-2-;Dreß;-4-')
688 # >>> print unicode(ShortEntry(dresz))
690 # >>> boss = ShortEntry(WordEntry(u'Boss;Boss # en.'))
693 # >>> biss = ShortEntry(WordEntry(u'Biss;-2-;-3-;Biss'))
696 # >>> g_ebene = WordEntry(u'Gaußebene;Gauß=ebe-ne')
697 # >>> print unicode(ShortEntry(g_ebene))
699 # >>> print unicode(ShortEntry(WordEntry(u'Abfalllager;-2-;-3-;Ab<fall=la-ger')))
702 # auffrass;-2-;-3-;-4-;auf-frass
703 # >>> print ShortEntry(auffrass)
705 # >>> fraesse = WordEntry(u'frässe;-2-;-3-;-4-;-5-;frä-sse;fräs-se;fräs-se')
706 # >>> frs = ShortEntry(fraesse)
707 # >>> print unicode(frs)
708 # -1-;-2-;fräs-se;frä-sse
709 # >>> messignal = WordEntry(u'Messignal;-2-;-3-;-4-;-5-;-6-;-7-;Me{ss/ss=s}i-.gnal')
710 # >>> print unicode(ShortEntry(messignal))
711 # -1-;-2-;-3-;-4-;Me{ss/ss=s}i-.gnal
712 # >>> loesz = WordEntry(u'Lößboden;Löß=bo-den')
713 # >>> print unicode(ShortEntry(loesz))
715 # >>> loess = WordEntry(u'Lössboden;-2-;-3-;Löss=bo-den;Löss=bo-den')
716 # >>> print unicode(ShortEntry(loess))
718 # >>> print ShortEntry(WordEntry(u'Amnesty;Am-nes-ty # en.'))
719 # Am-nes-ty;Am-nes-ty # en.
723 def __init__(self
, line
, delimiter
=';', conflate
=True):
725 if type(line
) == WordEntry
:
726 list.__init
__(self
, line
) # copy
727 self
.comment
= line
.comment
728 self
.pop(0) # ungetrenntes Wort entfernen
729 # de-1996 in Spalte 1
731 if self
.get('de-1901') != self
[0]:
734 self
[0] = self
.pop(2)
737 # print "jetzt", self
738 self
[2] = self
.pop(4)
739 # Felder zusammenfassen
741 self
.conflate_fields()
742 # Umnummerieren leerer Felder:
743 for (i
, f
) in enumerate(self
):
744 if f
.startswith(u
'-'):
745 self
[i
] = u
'-%d-' % (i
+1)
747 WordEntry
.__init
__(self
, line
, delimiter
)
753 # Als Schlüssel wird das erste nichtleere Feld ohne Trennzeichen, bei Angabe
754 # des optionalen Arguments ``lang`` die Schreibweise in dieser Rechtschreibung
757 # >>> print fluss.key()
759 # >>> print ShortEntry(dresz).key()
762 # Die Key-Auswahl kann über das `lang` Argument gesteuert werden:
764 # >>> print fluss.key('de-1901')
769 def key(self
, lang
=None):
770 """Erstelle einen Schlüssel (ungetrenntes Wort)."""
772 return join_word(self
.get(lang
)).lower()
774 if f
.startswith(u
'-'):
779 # Trennmuster für Sprachvariante ausgeben
780 # ---------------------------------------
782 # Suche die passende Sprachvariante (oder generiere sie).
784 # Namen (Sprachtags) der Spalten und ihre Normalisierung:
791 # 'de-x-versal', 'de'
793 # 4. 'de-1901-x-versal', 'de-1901', 'de-x-versal', 'de'
795 # 5. 'de-CH-1901', 'de-CH', 'de-1901', 'de'
797 # Ohne Transformation
799 # >>> aalbst.get('de')
801 # >>> aalbst.get('de-1996')
803 # >>> aalbst.get('de-1901')
805 # >>> aalbst.get('de-x-versal')
807 # >>> aalbst.get('de-1901-x-versal')
809 # >>> aalbst.get('de-1996-x-versal')
811 # >>> aalbst.get('de-CH-1901')
814 # >>> print dst.get('de')
816 # >>> print dst.get('de-1901')
818 # >>> print dst.get('de-1996')
823 # >>> rs = ShortEntry(u'-1-;-2-;Russ') # versal f. Ruß
824 # >>> print rs.get('de-x-versal')
826 # >>> print rs.get('de-1901-x-versal')
829 # >>> print tpp.get('de')
831 # >>> print tpp.get('de-x-versal')
833 # >>> print tpp.get('de-1901')
836 # >>> print unicode(frs)
837 # -1-;-2-;fräs-se;frä-sse
838 # >>> print frs.get('de')
840 # >>> print frs.get('de-1901')
842 # >>> print frs.get('de-x-versal')
844 # >>> print frs.get('de-1901-x-versal')
846 # >>> print frs.get('de-CH-1901')
849 # Mit Transformation:
851 # >>> print ShortEntry(u'Es-te').get('de-1901')
853 # >>> print ShortEntry(u'passt').get('de-1901')
855 # >>> print ShortEntry(u'passt').get('de-1901-x-versal')
857 # >>> abus = ShortEntry(u'ab<bü-ßen')
858 # >>> print abus.get('de-CH')
860 # >>> print abus.get('de-1901-x-versal')
862 # >>> print abus.get('de-CH-1901')
865 # >>> print ShortEntry(u'-1-;-2-;fräs-se;frä-sse').get('de-CH')
867 # >>> print ShortEntry(u'-1-;-2-;fräs-se;frä-sse').get('de-CH-1901')
869 # >>> print ShortEntry(u'-1-;-2-;fräs-se;frä-sse').get('de-1901-x-versal')
871 # >>> print ShortEntry(u'Bus-se').get('de-1901-x-versal')
873 # >>> print ShortEntry(u'Pass=sys-tem').get('de-CH-1901')
876 # >>> print ShortEntry(u'Pro<zess=en-.de;Pro<zeß=en-de;Pro<zess=en-.de').get('de-CH-1901')
879 # >>> pstr = ShortEntry(u'Pass=stra-ße;-2-')
880 # >>> pstr.expand_fields()
881 # >>> print unicode(pstr)
882 # Pass=stra-ße;-2-;Pass=stras-se;-4-;Pass=stras-se
884 # >>> print pstr.get('de') # Deutsch, aktuell (Reform 1996)
886 # >>> print pstr.get('de-1901') # "traditionell" (Reform 1901)
888 # >>> print pstr.get('de-CH') # ohne ß (Schweiz oder versal) "aktuell"
890 # >>> print pstr.get('de-x-versal') # Alias de-CH
892 # >>> print pstr.get('de-1901-x-versal')
894 # >>> print pstr.get('de-CH-1901')
900 for tag
in normalize_language_tag(lang
):
902 word
= self
[self
.sprachvarianten
[tag
]]
903 except LookupError: # gewählte Spalte nicht vorhanden
905 if word
.startswith(u
'-'): # "ausgekreuzt"
906 if lang
== 'de-1901-x-versal' and self
[0].startswith(u
'-'):
913 word
= ableitung1901(word
)
914 if ('-CH' in lang
) or ('x-versal' in lang
) and (u
'ß' in word
):
915 word
= versalschreibung(word
, lang
)
919 # Felder für alle Sprachvarianten ausfüllen
921 # >>> dst.expand_fields()
923 # Diens-te;Dien-ste;Diens-te;Dien-ste;Dien-ste
924 # >>> aalbst.expand_fields()
926 # Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand;Aal=be<stand # Test
927 # >>> abus.expand_fields()
928 # >>> print unicode(abus)
929 # ab<bü-ßen;ab<bü-ßen;ab<büs-sen;ab<bü-ssen;ab<büs-sen
930 # >>> boss.expand_fields()
931 # >>> print unicode(boss)
932 # Boss;Boss;Boss;Boss;Boss # en.
933 # >>> frs.expand_fields()
934 # >>> print unicode(frs)
935 # -1-;-2-;fräs-se;frä-sse;fräs-se
939 def expand_fields(self
):
940 for i
, sv
in enumerate(self
.feldnamen
):
941 field
= self
.get(sv
) or '-%d-' % (i
+1)
947 # Felder für Sprachvarianten zusammenfassen
948 # -----------------------------------------
950 # Weglassen von Feldern, wenn sich der Inhalt durch Ableitung gewinnen läßt::
952 # >>> aalbst.conflate_fields()
954 # Aal=be<stand # Test
955 # >>> dst.conflate_fields()
958 # >>> frs.conflate_fields()
959 # >>> print unicode(frs)
960 # -1-;-2-;fräs-se;frä-sse
961 # >>> abus.conflate_fields()
962 # >>> print unicode(abus)
964 # >>> boss.conflate_fields()
965 # >>> print unicode(boss)
970 def conflate_fields(self
):
971 if len(self
) == 1: # bereits kompakt
974 for i
, wort
in enumerate(self
):
975 if wort
.startswith(u
'-'):
977 for tag
in reversed(self
.feldnamen
[l_min
:len(self
)]):
979 rekonstruktion
= self
.get(tag
)
980 # print tag, repr(self), wort, rekonstruktion
981 if wort
!= rekonstruktion
:
985 # Einträge zusammenfassen
986 # -----------------------
988 # >>> entry = ShortEntry(u'qua-li-täts=be<wusst;-2-')
989 # >>> entry.merge(ShortEntry(u'-1-;qua-li-täts=be<wußt'))
990 # >>> print unicode(entry)
991 # qua-li-täts=be<wusst
992 # >>> entry = ShortEntry(u'Qua-li-fi-zie-rungs==maß=nah-me')
993 # >>> entry.merge(ShortEntry(u'-1-;-2-;Qua-li-fi-zie-rungs==mass=nah-me'))
994 # >>> print unicode(entry)
995 # Qua-li-fi-zie-rungs==maß=nah-me
996 # >>> entry = ShortEntry(u'-1-;-2-;Qua-li-fi-zie-rungs==mass=nah-me')
997 # >>> entry.merge(ShortEntry(u'Qua-li-fi-zie-rungs==maß=nah-me'))
998 # >>> print unicode(entry)
999 # Qua-li-fi-zie-rungs==maß=nah-me
1003 def merge(self
, other
, allow_alternatives
=False):
1004 self
.expand_fields()
1005 other
.expand_fields()
1006 for i
, (s_field
, o_field
) in enumerate(zip(self
,other
)):
1007 if s_field
== o_field
or o_field
.startswith(u
'-'):
1009 if s_field
.startswith(u
'-'):
1011 elif s_field
!= o_field
:
1012 if not allow_alternatives
:
1013 self
.conflate_fields()
1014 other
.conflate_fields()
1015 raise AssertionError('%s != %s' %(s_field
, o_field
))
1016 self
[i
] = u
'[%s/%s]' % (s_field
, o_field
)
1017 self
.conflate_fields()
1018 other
.conflate_fields()
1021 # Umwandeln in Tupel von WordEntry Instanzen (Langformat)
1022 # -------------------------------------------------------
1024 # >>> def print_langform(line, conflate=True):
1025 # ... for e in ShortEntry(line).wordentries(conflate):
1026 # ... print unicode(e)
1028 # >>> print_langform(u'ba-den')
1030 # >>> print_langform(u'Wes-te')
1031 # Weste;-2-;We-ste;Wes-te
1032 # >>> print_langform(u'Toll-patsch;-2-')
1033 # Tollpatsch;-2-;-3-;Toll-patsch
1034 # >>> print_langform(u'-1-;rau-he')
1035 # rauhe;-2-;rau-he;-4-
1037 # Bei Rechtschreibänderungen entsteht mehr als ein Eintrag:
1039 # >>> print_langform(u'Ab<fall=la-ger # Rechtschreibänderung')
1040 # Abfallager;-2-;Ab<fa{ll/ll=l}a-.ger;-4- # Rechtschreibänderung
1041 # Abfalllager;-2-;-3-;Ab<fall=la-ger # Rechtschreibänderung
1043 # >>> print_langform(u'Ab<guss')
1044 # Abguß;-2-;Ab<guß;-4-
1045 # Abguss;-2-;-3-;Ab<guss;Ab<guss
1047 # >>> print_langform(u'groß')
1049 # gross;-2-;-3-;-4-;gross
1051 # >>> print_langform(u'gro-ßen')
1053 # grossen;-2-;-3-;-4-;-5-;gro-ssen;gros-sen;gros-sen
1055 # >>> print_langform(u'Spaß')
1057 # Spass;-2-;-3-;-4-;Spass
1058 # >>> print_langform(u'Spass')
1060 # Spass;-2-;-3-;Spass;Spass
1061 # >>> print_langform(u'spa-ßen')
1063 # spassen;-2-;-3-;-4-;-5-;spa-ssen;spas-sen;spas-sen
1065 # >>> print_langform(u'ab<bü-ßen')
1067 # abbüssen;-2-;-3-;-4-;-5-;ab<bü-ssen;ab<büs-sen;ab<büs-sen
1069 # >>> print_langform(u'Boss;Boss # en.')
1071 # >>> print_langform(u'Biss;-2-')
1074 # >>> print_langform(u'Pass=sys-tem')
1075 # Paßsystem;-2-;Paß=sy-stem;-4-
1076 # Passsystem;-2-;-3-;Pass=sys-tem;-5-;Pass=sy-stem;Pass=sys-tem;-8-
1077 # Passystem;-2-;-3-;-4-;-5-;-6-;-7-;Pa{ss/ss=s}y-.stem
1079 # >>> print_langform(u'Press=saft')
1080 # Preßsaft;-2-;Preß=saft;-4-
1081 # Presssaft;-2-;-3-;Press=saft;-5-;Press=saft;Press=saft;-8-
1082 # Pressaft;-2-;-3-;-4-;-5-;-6-;-7-;Pre{ss/ss=s}aft
1084 # >>> print_langform(u'Pro<gramm==maß=nah-me')
1085 # Programmaßnahme;-2-;Pro<gra{mm/mm==m}aß=nah-me;-4-
1086 # Programmmaßnahme;-2-;-3-;Pro<gramm==maß=nah-me
1087 # Programmassnahme;-2-;-3-;-4-;-5-;Pro<gra{mm/mm==m}ass=nah-me;-7-;Pro<gra{mm/mm==m}ass=nah-me
1088 # Programmmassnahme;-2-;-3-;-4-;-5-;-6-;Pro<gramm==mass=nah-me;-8-
1090 # >>> print_langform(u'Pass=stra-ße')
1091 # Paßstraße;-2-;Paß=stra-ße;-4-
1092 # Passstraße;-2-;-3-;Pass=stra-ße
1093 # Passstrasse;-2-;-3-;-4-;-5-;Pass=stra-sse;Pass=stras-se;Pass=stras-se
1095 # Explizit nur eine Schreibung:
1096 # >>> print_langform(u'Abend=dress;Abend=dress')
1097 # Abenddress;Abend=dress
1098 # >>> print_langform(u'-1-;Abend=dreß')
1099 # Abenddreß;-2-;Abend=dreß;-4-
1100 # Abenddress;-2-;-3-;-4-;-5-;Abend=dress;-7-;Abend=dress
1103 # >>> print_langform(u'# holla')
1106 # >>> print_langform(u'Pro<zess=en-.de;Pro<zeß=en-de;Pro<zess=en-.de')
1107 # Prozeßende;-2-;Pro<zeß=en-de;-4-
1108 # Prozessende;-2-;-3-;Pro<zess=en-.de;Pro<zess=en-.de
1112 def wordentries(self
, conflate
=True):
1113 tags
= ['x', 'de-1901', 'de-1996']
1114 if (u
'ß' in (self
.get('de-1901') or self
.get('de') or '')
1116 tags
+= ['x', 'de-1901-x-versal',
1117 'de-1996-x-versal', 'de-CH-1901']
1121 for (n
, tag
) in enumerate(tags
):
1122 word
= self
.get(tag
)
1125 key
= join_word(word
)
1129 i
= keys
[key
] = len(entries
)
1130 entries
.append(WordEntry(key
))
1131 entries
[-1].comment
= self
.comment
1134 while len(entry
) < n
+1:
1135 # print tag, key, "len e", len(entry), "Spalte", n+1
1136 entry
.append(u
'-%s-' % (len(entry
)+1))
1139 # Auffüllen, Komprimieren
1140 for entry
in entries
:
1142 entry
.append(u
'-4-')
1144 entry
.append(u
'-8-')
1146 entry
.conflate_fields()
1147 # # allgemein-versal, falls gleich de-1996 und de-1901 fehlt
1148 # if (len(entry) == 4 and entry[2].startswith(u'-')
1149 # and entry[3] == self.get('de-CH-1901')):
1150 # entry.append(entry[3])
1152 if not entries
and self
.comment
:
1153 return [WordEntry(u
'# '+self
.comment
)]
1160 # Ableitung von de-1901 aus de-1996
1161 # ---------------------------------
1163 # Ableitung von de-1901 aus de-1996 (Reversion der Reform 1996).
1165 # Mit keep_key == True werden nur Änderungen vorgenommen, die die
1166 # Schreibweise des (ungetrennten) Wortes nicht verändern.
1168 # >>> from wortliste import ableitung1901
1172 def ableitung1901(wort
, keep_key
=False):
1174 # Trennregeländerungen
1175 # ~~~~~~~~~~~~~~~~~~~~
1177 # Diese Regeln ändern nicht das Wort, nur die Trennmöglichkeiten.
1179 # 1. Trenne nie st: Ersetze 's-t' mit u'-st'.
1181 # >>> ableitung1901(u'Diens-te')
1183 # >>> print ableitung1901(u'wuss-te', keep_key=True)
1186 # Keine Trennung nach nur einem Buchstaben:
1188 # >>> ableitung1901(u'Es-te')
1190 # >>> print ableitung1901(u'Nord=os-ten')
1192 # >>> print ableitung1901(u'Po-ly<es-ter')
1194 # >>> print ableitung1901(u'the-is-tisch')
1197 # Keine Trennung von s-theta:
1199 # >>> print ableitung1901(u'Äs-thet')
1202 # >>> print ableitung1901(u'pssst') # Sonderfall: falsche Umwandlung
1205 # 2. Trenne ck als k-k: Ersetze '-ck' mit '{ck/k-k}'.
1207 # >>> ableitung1901(u'Ha-cke')
1212 wort
= re
.sub(u
'(?<!s)s-t(?!h)', u
'-st', wort
)
1213 wort
= wort
.replace(u
'-ck', u
'{ck/k-k}')
1214 # keine Trennung nach nur 1 Buchstaben:
1215 wort
= re
.sub(u
'((?<=[=<-].)|(?<=^.))-', ur
'', wort
)
1220 # Rechtschreibänderungen
1221 # ~~~~~~~~~~~~~~~~~~~~~~
1223 # Diese Regeln ändern die Schreibung des ungetrennten Worts (und somit den
1224 # Schlüssel im Langformat der Wortliste).
1226 # 3. kein ss und sst am Wortende (ungetrenntes "ss" nur in Ausnahmen):
1228 # Ersetze 'ss' (evt. gefolgt von t), wenn es am Silbenende auftaucht
1229 # (vor [-=<>]) mit ß (und evt. t).
1231 # Aber: 'ßt' und Schluss-ß auch in de-1996 möglich (langer Vokal)
1233 # >>> print ableitung1901(u'passt')
1235 # >>> print ableitung1901(u'Hass')
1237 # >>> print ableitung1901(u'Was-ser')
1239 # >>> print ableitung1901(u'Fass=brau-se')
1241 # >>> print ableitung1901(u'wuss-te')
1246 wort
= re
.sub(u
'ss(t?)(?=[-=<>]|$)', ur
'ß\1', wort
)
1248 # 4. Dreikonsonantenregel:
1250 # Ersetze 'mm=m' mit '{mm/mm=m}' (für alle Konsonanten vor Selbstlaut)
1252 # >>> print ableitung1901(u'Kipp=pflug')
1254 # >>> print ableitung1901(u'Kipp=punkt')
1256 # >>> print ableitung1901(u'Ab<fall=la-ger')
1257 # Ab<fa{ll/ll=l}a-.ger
1258 # >>> print ableitung1901(u'hell>licht')
1260 # >>> print ableitung1901(u'Pro<gramm==maß=nah-me')
1261 # Pro<gra{mm/mm==m}aß=nah-me
1265 wort
= re
.sub(ur
'([bfglmnprt])\1([=>]+)\1(?=[aeiouyäöü])',
1266 ur
'{\1\1/\1\1\2\1}', wort
)
1268 # unterdrücken der Trennung nach nur einem Buchstaben
1269 wort
= re
.sub(ur
'(?<=[=>].}[aeiouyäöü])([-<])\.?', ur
'\1.', wort
)
1274 # Ableitung von de-CH/de-x-versal (SZ-Ersatzschreibung):
1275 # ------------------------------------------------------------------
1277 # >>> from wortliste import versalschreibung
1278 # >>> print versalschreibung(u'paßt')
1280 # >>> print versalschreibung(u'bü-ßen', 'de-CH')
1282 # >>> print versalschreibung(u'bü-ßen', 'de-1996-x-versal')
1284 # >>> print versalschreibung(u'bü-ßen', 'de-CH-1901')
1286 # >>> print versalschreibung(u'Groß=se-gel', 'de-1901-x-versal')
1288 # >>> print versalschreibung(u'Groß=se-gel', 'de-CH-1901')
1289 # Gro{ss/ss=s}e-.gel
1290 # >>> print versalschreibung(u'Pass=sy-ste-me', 'de-CH-1901')
1291 # Pa{ss/ss=s}y-.ste-me
1292 # >>> print versalschreibung(u'Pro<zeß=en-de', 'de-CH-1901')
1294 # >>> print versalschreibung(u'Pro<zess=en-.de', 'de-CH-1901')
1296 # >>> print versalschreibung(u'Ma-te-ri-al=fluß==sy-stem', 'de-CH-1901')
1297 # Ma-te-ri-al=flu{ss/ss==s}y-.stem
1301 def versalschreibung(wort
, lang
='de'):
1303 # Ersetze 'ß' mit 'ss' ::
1305 wort
= wort
.replace(u
'ß', u
'ss')
1307 # Trennung von Ersatz-ss in de-CH und de-1996 nach Sprechsilbenregel::
1309 if '1901-x-versal' not in lang
:
1310 wort
= re
.sub(u
'(?<=[aeiouyäöü])-\.?ss', u
's-s', wort
)
1312 # Unterdrückung irreführender Trennung::
1314 wort
= re
.sub(u
'ss(=+)(en|er)([<-])\.?', ur
'ss\1\2\3.', wort
)
1316 # Dreikonsonantenregel für Ersatz-ss in de-CH-1901::
1318 if 'CH-1901' in lang
:
1319 wort
= re
.sub(u
'ss(=+)s(?=[aeiouyäöü])', ur
'{ss/ss\1s}', wort
)
1320 # unterdrücken der Trennung nach nur einem Buchstaben
1321 wort
= re
.sub(ur
'(?<=[=>]s}[aeiouyäöü])([-<])\.?', ur
'\1.', wort
)
1329 # Trennzeichen entfernen::
1331 def join_word(wort
, assert_complete
=False):
1333 # Einfache Trennzeichen:
1335 # == ================================================================
1336 # \· ungewichtete Trennstelle (solche, wo sich noch niemand um die
1337 # Gewichtung gekümmert hat)
1338 # \. unerwünschte Trennstelle (sinnentstellend), z.B. Ur·in.stinkt
1339 # oder ungünstige Trennstelle (verwirrend), z.B. Atom·en.er·gie
1340 # in ungewichteten Wörtern
1341 # \= Trennstelle an Wortfugen (Wort=fu-ge)
1342 # \< Trennstelle nach Präfix (Vor<sil-be)
1343 # \> Trennstelle vor Suffix (Freund>schaf-ten)
1344 # \- Nebentrennstelle (ge-hen)
1345 # == ================================================================
1350 for char
in u
'·.=|-_<>':
1351 table
[ord(char
)] = None
1352 key
= wort
.translate(table
)
1354 # Spezielle Trennungen für die traditionelle Rechtschreibung
1355 # (siehe ../../dokumente/README.wortliste)::
1357 if '{' in key
or '}' in key
:
1358 key
= key
.replace(u
'{ck/kk}', u
'ck')
1359 key
= key
.replace(u
'{ck/k', u
'k')
1360 key
= key
.replace(u
'k}', u
'k')
1361 # Konsonanthäufungen an Wortfuge: '{xx/xxx}' -> 'xx':
1362 key
= re
.sub(ur
'\{(.)\1/\1\1\1\}', ur
'\1\1', key
)
1363 # schon getrennt: ('{xx/xx' -> 'xx' und 'x}' -> 'x'):
1364 key
= re
.sub(ur
'\{(.)\1/\1\1$', ur
'\1\1', key
)
1365 key
= re
.sub(ur
'^(.)\}', ur
'\1', key
)
1367 # Trennstellen in doppeldeutigen Wörtern::
1369 if '[' in key
or ']' in key
:
1370 key
= re
.sub(ur
'\[(.*)/\1\]', ur
'\1', key
)
1372 key
= re
.sub(ur
'\[([^/\[]+)$', ur
'\1', key
)
1373 key
= re
.sub(ur
'^([^/\]]+)\]', ur
'\1', key
)
1375 # Test auf verbliebene komplexe Trennstellen::
1378 for spez
in u
'[{/}]':
1380 raise AssertionError('Spezialtrennung %s, %s' %
1381 (wort
.encode('utf8'), key
.encode('utf8')))
1388 # Zerlege ein Wort mit Trennzeichen in eine Liste von Silben und eine Liste
1391 # >>> from wortliste import zerlege
1393 # >>> zerlege(u'Haupt=stel-le')
1394 # ([u'Haupt', u'stel', u'le'], [u'=', u'-'])
1395 # >>> zerlege(u'Ge<samt=be<triebs=rats==chef')
1396 # ([u'Ge', u'samt', u'be', u'triebs', u'rats', u'chef'], [u'<', u'=', u'<', u'=', u'=='])
1397 # >>> zerlege(u'an<stands>los')
1398 # ([u'an', u'stands', u'los'], [u'<', u'>'])
1399 # >>> zerlege(u'An<al.pha-bet')
1400 # ([u'An', u'al', u'pha', u'bet'], [u'<', u'.', u'-'])
1405 silben
= re
.split(u
'[-·._<>=]+', wort
)
1406 trennzeichen
= re
.split(u
'[^-·._|<>=]+', wort
)
1407 return silben
, [tz
for tz
in trennzeichen
if tz
]
1412 # Fehler beim Übertragen von Trennstellen mit uebertrage_::
1414 class TransferError(ValueError):
1415 def __init__(self
, wort1
, wort2
):
1416 msg
= u
'Inkompatibel: %s %s' % (wort1
, wort2
)
1417 ValueError.__init
__(self
, msg
.encode('utf8'))
1419 def __unicode__(self
):
1420 return str(self
).decode('utf8')
1426 # Übertrage die Trennzeichen von `wort1` auf `wort2`:
1428 # >>> from wortliste import uebertrage, TransferError
1430 # >>> uebertrage(u'Haupt=stel-le', u'Haupt·stel·le')
1433 # Auch teilweise Übertragung, von "kategorisiert" nach "unkategorisiert":
1435 # >>> print uebertrage(u'Haupt=stel-le', u'Haupt=stel·le')
1438 # >>> print uebertrage(u'Haupt·stel-le', u'Haupt=stel·le')
1441 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler')
1444 # >>> print uebertrage(u'Freund>schaf·ten', u'Freund-schaf-ten')
1447 # Übertragung doppelter Marker:
1449 # >>> print uebertrage(u'ver<<aus<ga-be', u'ver<aus<ga-be')
1452 # >>> print uebertrage(u'freund>lich>>keit', u'freund>lich>keit')
1455 # >>> print uebertrage(u'Amts==haupt=stel-le', u'Amts=haupt=stel-le')
1456 # Amts==haupt=stel-le
1458 # Kein Überschreiben doppelter Marker:
1459 # >>> print uebertrage(u'ver<aus<ga-be', u'ver<<aus<ga-be')
1462 # >>> print uebertrage(u'Amts=haupt=stel-le', u'Amts==haupt=stel·le')
1463 # Amts==haupt=stel-le
1465 # Erhalt des Markers für ungünstige Stellen:
1466 # >>> print uebertrage(u'An·al.pha·bet', u'An<al.pha-bet')
1469 # Keine Übertragung, wenn die Zahl oder Position der Trennstellen
1470 # unterschiedlich ist oder bei unterschiedlichen Wörtern:
1473 # ... uebertrage(u'Ha-upt=stel-le', u'Haupt=stel·le')
1474 # ... uebertrage(u'Haupt=ste-lle', u'Haupt=stel·le')
1475 # ... uebertrage(u'Waupt=stel-le', u'Haupt=stel·le')
1476 # ... except TransferError:
1479 # Übertragung auch bei unterschiedlicher Schreibung oder Position der
1480 # Trennstellen mit `strict=False` (für Abgleich zwischen Sprachvarianten):
1482 # >>> uebertrage(u'er-ster', u'ers·ter', strict=False)
1484 # >>> uebertrage(u'Fluß=bett', u'Fluss·bett', strict=False)
1486 # >>> uebertrage(u'ab>bei-ßen', u'ab>beis·sen', strict=False)
1488 # >>> print uebertrage(u'Aus<tausch=dien-stes', u'Aus-tausch=diens-tes', False)
1489 # Aus<tausch=diens-tes
1491 # Auch mit `strict=False` muß die Zahl der Trennstellen übereinstimmen
1492 # (Ausnahmen siehe unten):
1495 # ... uebertrage(u'Ha-upt=ste-lle', u'Haupt=stel·le', strict=False)
1496 # ... except TransferError:
1499 # Akzeptiere unterschiedliche Anzahl von Trennungen bei st und ck nach
1502 # >>> uebertrage(u'acht=ecki-ge', u'acht·e{ck/k·k}i·ge', strict=False)
1503 # u'acht=e{ck/k-k}i-ge'
1504 # >>> uebertrage(u'As-to-ria', u'Asto·ria', strict=False)
1506 # >>> uebertrage(u'Asto-ria', u'As·to·ria', strict=False)
1508 # >>> uebertrage(u'So-fa=ecke', u'So·fa=e{ck/k-k}e', strict=False)
1509 # u'So-fa=e{ck/k-k}e'
1510 # >>> uebertrage(u'Drei=ecks=ecke', u'Drei=ecks==e{ck/k-k}e', strict=False)
1511 # u'Drei=ecks==e{ck/k-k}e'
1513 # Mit ``upgrade=False`` werden nur unspezifische Trennstellen überschrieben:
1515 # >>> print uebertrage(u'an=stel-le', u'an<stel·le', upgrade=False)
1518 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler', upgrade=False)
1521 # >>> print uebertrage(u'Aus-stel-ler', u'Aus<stel-ler', upgrade=False)
1524 # >>> print uebertrage(u'vor<an<<stel-le', u'vor-an<stel·le', upgrade=False)
1529 selbstlaute
= u
'aeiouäöüAEIOUÄÖÜ'
1531 def uebertrage(wort1
, wort2
, strict
=True, upgrade
=True):
1533 silben1
, trennzeichen1
= zerlege(wort1
)
1534 silben2
, trennzeichen2
= zerlege(wort2
)
1535 # Prüfe strikte Übereinstimmung:
1536 if silben1
!= silben2
and strict
:
1537 if u
'<' in trennzeichen1
or u
'·' in trennzeichen2
:
1538 raise TransferError(wort1
, wort2
)
1541 # Prüfe ungefähre Übereinstimmung:
1542 if len(trennzeichen1
) != len(trennzeichen2
):
1543 # Selbstlaut + st oder ck?
1544 for s
in selbstlaute
:
1545 if (wort2
.find(s
+u
'{ck/k·k}') != -1 or
1546 wort2
.find(s
+u
'{ck/k-k}') != -1):
1547 wort1
= re
.sub(u
'%sck([%s])'%(s
,selbstlaute
),
1548 ur
'%s-ck\1'%s, wort1
)
1549 silben1
, trennzeichen1
= zerlege(wort1
)
1550 if wort2
.find(s
+u
's·t') != -1:
1551 wort1
= wort1
.replace(s
+u
'st', s
+u
's-t')
1552 silben1
, trennzeichen1
= zerlege(wort1
)
1553 elif wort1
.find(s
+u
's-t') != -1:
1554 wort1
= wort1
.replace(s
+u
's-t', s
+u
'st')
1555 silben1
, trennzeichen1
= zerlege(wort1
)
1556 # print u'retry:', silben1, trennzeichen1
1557 # immer noch ungleiche Zahl an Trennstellen?
1558 if len(trennzeichen1
) != len(trennzeichen2
):
1559 raise TransferError(wort1
, wort2
)
1561 # Baue wort3 aus silben2 und spezifischeren Trennzeichen:
1562 wort3
= silben2
.pop(0)
1563 for t1
,t2
in zip(trennzeichen1
, trennzeichen2
):
1564 if ((t2
== u
'·' and t1
!= u
'.') # unspezifisch
1566 ((t2
in (u
'-', u
'<') and t1
in (u
'<', u
'<<', u
'<=')) # Praefixe
1567 or (t2
in (u
'-', u
'>') and t1
in (u
'>', u
'>>', u
'=>')) # Suffixe
1568 or (t2
in (u
'-', u
'=') and t1
in (u
'=', u
'==', u
'===')) # W-fugen
1572 elif t2
== u
'.' and t1
not in u
'·.':
1576 wort3
+= silben2
.pop(0)
1580 # Übertrag kategorisierter Trennstellen zwischen den Feldern aller Einträge
1583 def sprachabgleich(entry
, vorbildentry
=None):
1586 return # allgemeine Schreibung
1588 mit_affix
= None # < oder >
1589 kategorisiert
= None # kein ·
1590 unkategorisiert
= None # mindestens ein ·
1591 gewichtet
= None # == oder <= oder =>
1592 for field
in entry
[1:]:
1593 if field
.startswith('-'): # -2-, -3-, ...
1595 if u
'{' in field
and u
'[' in field
: # Bi-ber==be[t=t/{tt/tt=t}]uch
1596 continue # zu komplex
1598 unkategorisiert
= field
1599 elif u
'<' in field
or u
'>' in field
:
1602 kategorisiert
= field
1603 if u
'==' in field
or u
'<=' in field
or u
'=>' in field
:
1606 for field
in vorbildentry
[1:]:
1607 if field
.startswith('-'): # -2-, -3-, ...
1609 if u
'{' in field
and u
'[' in field
: # Bi-ber==be[t=t/{tt/tt=t}]uch
1610 continue # zu komplex
1611 if not mit_affix
and u
'<' in field
or u
'>' in field
:
1613 elif not kategorisiert
and unkategorisiert
and u
'·' not in field
:
1614 kategorisiert
= field
1615 if not gewichtet
and u
'==' in field
or u
'<=' in field
or u
'=>' in field
:
1617 # print 've:', mit_affix, kategorisiert, unkategorisiert
1618 if mit_affix
and (kategorisiert
or unkategorisiert
or gewichtet
):
1619 for i
in range(1,len(entry
)):
1620 if entry
[i
].startswith('-'): # -2-, -3-, ...
1622 if u
'<' not in entry
[i
] or u
'·' in entry
[i
]:
1624 entry
[i
] = uebertrage(mit_affix
, entry
[i
], strict
=False)
1625 except TransferError
, e
:
1626 if not '/' in entry
[i
]:
1627 print u
'Sprachabgleich:', unicode(e
)
1628 # print mit_affix+u':', unicode(entry)
1629 elif kategorisiert
and unkategorisiert
:
1630 for i
in range(1,len(entry
)):
1631 if u
'·' in entry
[i
]:
1633 entry
[i
] = uebertrage(kategorisiert
, entry
[i
], strict
=False)
1634 except TransferError
, e
:
1635 print u
'Sprachabgleich:', unicode(e
)
1636 # print kategorisiert, unicode(entry)
1638 for i
in range(1,len(entry
)):
1639 if u
'=' in entry
[i
] and not (
1640 u
'{' in entry
[i
] and u
'[' in entry
[i
]):
1642 entry
[i
] = uebertrage(gewichtet
, entry
[i
], strict
=False)
1643 except TransferError
, e
:
1644 print u
'Sprachabgleich:', unicode(e
)
1648 # Großschreibung in Kleinschreibung wandeln und umgekehrt
1650 # Diese Version funktioniert auch für Wörter mit Trennzeichen (während
1651 # str.title() nach jedem Trennzeichen wieder groß anfängt)
1653 # >>> from wortliste import toggle_case
1654 # >>> toggle_case(u'Ha-se')
1656 # >>> toggle_case(u'arm')
1658 # >>> toggle_case(u'frei=bier')
1660 # >>> toggle_case(u'L}a-ger')
1663 # Keine Änderung bei Wörtern mit Großbuchstaben im Inneren:
1665 # >>> toggle_case(u'USA')
1667 # >>> toggle_case(u'iRFD')
1670 # >>> toggle_case(u'gri[f-f/{ff/ff')
1672 # >>> toggle_case(u'Gri[f-f/{ff/ff')
1677 def toggle_case(wort
):
1679 key
= join_word(wort
, assert_complete
=True)
1680 except AssertionError:
1685 return wort
[0].upper() + wort
[1:]
1692 # Duden-Sortierung für die Wortliste
1694 # >>> from wortliste import sortkey_duden
1695 # >>> sortkey_duden([u"Abflußröhren"])
1696 # u'abflussrohren a*bflu*szroehren'
1697 # >>> sortkey_duden([u"Abflußrohren"])
1698 # u'abflussrohren a*bflu*szro*hren'
1699 # >>> sortkey_duden([u"Abflussrohren"])
1702 # >>> s = sorted([[u"Abflußröhren"], [u"Abflußrohren"], [u"Abflussrohren"]],
1703 # ... key=sortkey_duden)
1704 # >>> print ', '.join(e[0] for e in s)
1705 # Abflussrohren, Abflußrohren, Abflußröhren
1709 # Ligaturen auflösen und andere "normalisierunde" Ersetzungen für den
1710 # (Haupt-)Sortierschlüssel (Akzente werden über ``unicodedata.normalize``
1719 # "Zweitschlüssel" zur Unterscheidung von Umlauten/SZ und Basisbuchstaben::
1721 umschrift_subkey
= {
1737 # Schlüssel für die alphabetische Sortierung gemäß Duden-Regeln
1738 # `entry` ist ein Eintrag im WordEntry oder ShortEntry Format oder
1739 # ein (Unicode) String.
1741 # >>> print sortkey_duden(weste) # WordEntry
1743 # >>> print sortkey_duden(dst) # ShortEntry
1745 # >>> print sortkey_duden(u'Stra-ße')
1747 # >>> print sortkey_duden([u'Büh-ne'])
1752 def sortkey_duden(entry
):
1754 # ggf. ungetrenntes Wort extrahieren oder generieren::
1756 if isinstance(entry
, list):
1759 except AttributeError:
1761 if len(entry
) == 1: # ein Muster pro Zeile, siehe z.B. pre-1901
1762 key
= join_word(key
)
1764 key
= join_word(entry
)
1766 # Großschreibung ignorieren:
1768 # Der Duden sortiert Wörter, die sich nur in der Großschreibung unterscheiden
1769 # "klein vor groß" (ASCII sortiert "groß vor klein"). In der
1770 # `Trennmuster-Wortliste` kommen Wörter nur mit der häufiger anzutreffenden
1771 # Großschreibung vor, denn der TeX-Trennalgorithmus ignoriert Großschreibung.
1780 skey
= key
.replace(u
'ß', u
'ss')
1782 # Restliche Akzente weglassen: Wandeln in Darstellung von Buchstaben mit
1783 # Akzent als "Grundzeichen + kombinierender Akzent". Anschließend alle
1784 # nicht-ASCII-Zeichen ignorieren::
1786 skey
= skey
.translate(umschrift_skey
)
1787 skey
= unicodedata
.normalize('NFKD', skey
)
1788 skey
= unicode(skey
.encode('ascii', 'ignore'))
1790 # "Zweitschlüssel" für das eindeutige Einsortieren von Wörtern mit
1791 # gleichem Schlüssel (Masse/Maße, waren/wären, ...):
1793 # * "*" nach aou für die Unterscheidung Grund-/Umlaut
1799 subkey
= key
.translate(umschrift_subkey
)
1800 skey
= u
'%s %s' % (skey
,subkey
)
1802 # Gib den Sortierschlüssel zurück::
1811 # Vergleiche zwei Sequenzen von `WordEntries`, gib einen "unified diff" als
1812 # Byte-String zurück (weil difflib nicht mit Unicode-Strings arbeiten kann).
1816 # >>> from wortliste import udiff
1817 # >>> print udiff([abbeissen, aalbestand], [abbeissen], 'alt', 'neu')
1821 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
1822 # -Aalbestand;Aal=be<stand # Test
1826 def udiff(a
, b
, fromfile
='', tofile
='',
1827 fromfiledate
='', tofiledate
='', n
=1, encoding
='utf8'):
1829 a
= [unicode(entry
).encode(encoding
) for entry
in a
]
1830 b
= [unicode(entry
).encode(encoding
) for entry
in b
]
1832 diff
= difflib
.unified_diff(a
, b
, fromfile
, tofile
,
1833 fromfiledate
, tofiledate
, n
, lineterm
='')
1836 return '\n'.join(diff
)
1841 def test_keys(wortliste
):
1842 """Teste Übereinstimmung des ungetrennten Wortes in Feld 1
1843 mit den Trennmustern nach Entfernen der Trennmarker.
1844 Schreibe Inkonsistenzen auf die Standardausgabe.
1846 `wortliste` ist ein Iterator über die Einträge (Klasse `WordEntry`)
1849 for entry
in wortliste
:
1850 # Test der Übereinstimmung ungetrenntes/getrenntes Wort
1853 for wort
in entry
[1:]:
1854 if wort
.startswith(u
'-'): # leere Felder
1856 if key
!= join_word(wort
):
1858 print u
"\nkey '%s' != join_word('%s')" % (key
, wort
),
1859 if key
.lower() == join_word(wort
).lower():
1860 print(u
" Abgleich der Großschreibung mit"
1861 u
"`prepare-patch.py grossabgleich`."),
1868 # Normalisierung und Expansion von Sprachtags nach [BCP47]_
1870 # >>> from wortliste import normalize_language_tag
1871 # >>> normalize_language_tag('de_AT-1901')
1872 # ['de-AT-1901', 'de-AT', 'de-1901', 'de']
1874 # >>> normalize_language_tag('de') # Deutsch, allgemeingültig
1876 # >>> normalize_language_tag('de_1901') # traditionell (Reform 1901)
1878 # >>> normalize_language_tag('de_1996') # reformiert (Reform 1996)
1880 # >>> normalize_language_tag('de_CH') # ohne ß (Schweiz oder versal)
1882 # >>> normalize_language_tag('de-x-versal') # versal
1883 # ['de-x-versal', 'de']
1884 # >>> normalize_language_tag('de-1901-x-versal') # versal
1885 # ['de-1901-x-versal', 'de-1901', 'de-x-versal', 'de']
1886 # >>> normalize_language_tag('de_CH-1996') # Schweiz traditionell (süssauer)
1887 # ['de-CH-1996', 'de-CH', 'de-1996', 'de']
1889 # 'de': 1, # Deutsch, allgemeingültig
1890 # 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
1891 # 'de-1996': 3, # reformierte Reformschreibung (1996)
1892 # 'de-x-versal': 4, # ohne ß (Schweiz oder versal) allgemein
1893 # # 'de-CH': 4, # Alias
1894 # 'de-1901-x-versal': 5, # ohne ß (Schweiz oder versal) "traditionell"
1895 # 'de-1996-x-versal': 6, # ohne ß (Schweiz oder versal) "reformiert"
1896 # # 'de-CH-1996': 6, # Alias
1897 # 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
1902 def normalize_language_tag(tag
):
1903 """Return a list of normalized combinations for a `BCP 47` language tag.
1906 tag
= tag
.replace('_','-')
1907 # split (except singletons, which mark the following tag as non-standard):
1908 tag
= re
.sub(r
'-([a-zA-Z0-9])-', r
'-\1_', tag
)
1910 subtags
= [subtag
.replace('_', '-') for subtag
in tag
.split('-')]
1911 base_tag
= [subtags
.pop(0)]
1912 # find all combinations of subtags
1913 for n
in range(len(subtags
), 0, -1):
1914 # for tags in unique_combinations(subtags, n):
1915 for tags
in itertools
.combinations(subtags
, n
):
1916 taglist
.append('-'.join(base_tag
+list(tags
)))
1927 if __name__
== '__main__':
1932 # sys.stdout mit UTF8 encoding (wie in Python 3)
1933 sys
.stdout
= codecs
.getwriter('UTF-8')(sys
.stdout
)
1935 print u
"Test der Werkzeuge und inneren Konsistenz der Wortliste\n"
1937 wordfile
= WordFile('../../../wortliste')
1938 # print 'Dateiobjekt:', wordfile
1940 # Liste der Datenfelder (die Klasseninstanz als Argument für `list` liefert
1941 # den Iterator über die Felder, `list` macht daraus eine Liste)::
1943 wortliste
= list(wordfile
)
1944 print len(wortliste
), u
"Einträge\n"
1950 # sprache = 'de-1901' # traditionell
1951 # sprache = 'de-1996' # Reformschreibung
1952 # sprache = 'de-x-GROSS' # ohne ß (Schweiz oder GROSS) allgemein
1953 # sprache = 'de-1901-x-GROSS' # ohne ß (Schweiz oder GROSS) "traditionell"
1954 # sprache = 'de-1996-x-GROSS' # ohne ß (Schweiz oder GROSS) "reformiert"
1955 # sprache = 'de-CH-1901' # ohne ß (Schweiz) "traditionell" ("süssauer")
1957 # worte = [entry.get(sprache) for entry in wortliste if wort is not None]
1958 # print len(worte), u"Einträge für Sprachvariante", sprache
1963 print u
"Teste Schlüssel-Trennmuster-Übereinstimmung:",
1964 if test_keys(wortliste
):
1972 for entry
in wortliste
:
1973 key
= entry
[0].lower()
1976 print unicode(entry
)
1979 print u
"Doppeleinträge (ohne Berücksichtigung der Großschreibung)."
1981 print u
" Entfernen mit `prepare-patch.py doppelte`."
1982 print u
" Patch vor Anwendung durchsehen!"
1985 # Ein Wörterbuch (dict Instanz)::
1987 # wordfile.seek(0) # Pointer zurücksetzen
1988 # words = wordfile.asdict()
1990 # print len(words), u"Wörterbucheinträge"
1992 # Zeilenrekonstruktion::
1994 # am Beispiel der Scheiterbeige:
1995 # original = u'beige;beige # vgl. Scheiter-bei-ge'
1996 # entry = words[u"beige"]
1997 # line = unicode(entry)
1998 # assert original == line, "Rejoined %s != %s" % (line, original)
2001 wordfile
.seek(0) # Pointer zurücksetzen
2003 line
= wordfile
.readline().rstrip().decode(wordfile
.encoding
)
2005 entry
= WordEntry(line
)
2006 if line
== unicode(entry
):
2010 print u
'+', unicode(entry
)
2011 line
= wordfile
.readline().rstrip().decode(wordfile
.encoding
)
2013 print OK
, u
"Einträge rekonstruiert"
2020 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
2021 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
2023 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
2024 # http://mirrors.ctan.org/language/hyphenation/dehyph-exptl/projektbeschreibung.pdf