Kategorisierung (unguenstige Trennstellen).
[wortliste.git] / skripte / python / werkzeug.py
blobe6c8ef4ce8b90db18ea411aed725582d2b184c90
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
3 # :Copyright: © 2012 Günter Milde.
4 # :Licence: This work may be distributed and/or modified under
5 # the conditions of the `LaTeX Project Public License`,
6 # either version 1.3 of this license or (at your option)
7 # any later version.
8 # :Version: 0.1 (2012-02-07)
10 # werkzeug.py
11 # ***********
13 # ::
15 """Hilfsmittel für die Arbeit mit der `Wortliste`"""
17 # .. contents::
19 # Die hier versammelten Funktionen und Klassen dienen der Arbeit an und
20 # mit der freien `Wortliste der deutschsprachigen Trennmustermannschaft`_
21 # ("Lembergsche Liste")
23 # Vorspann
25 # ::
27 import difflib
28 import re
29 import codecs
31 # WordFile
32 # ========
34 # Klasse zum Lesen und Schreiben der `Wortliste`::
36 class WordFile(file):
38 # encoding
39 # --------
41 # ::
43 encoding = 'utf8'
45 # Iteration
46 # ---------
48 # Die spezielle Funktion `__iter__` wird aufgerufen wenn über eine
49 # Klasseninstanz iteriert wird.
51 # Liefer einen Iterator über die "geparsten" Zeilen (Datenfelder)::
53 def __iter__(self):
54 line = self.readline().rstrip().decode(self.encoding)
55 while line:
56 yield WordEntry(line)
57 line = self.readline().rstrip().decode(self.encoding)
59 # asdict
60 # ------
62 # Lies Datei und trage die Zeilen mit ungetrenntem Wort
63 # als `key` und den Datenfeldern als `value` in ein `dictionary`
64 # (assoziatives Array) ein::
66 def asdict(self):
67 words = dict()
68 for entry in self:
69 words[entry[0]] = entry
70 return words
72 # writelines
73 # -----------
75 # Schreibe eine Liste von `unicode` Strings (Zeilen ohne Zeilenendezeichen)
76 # in die Datei `destination`::
78 def writelines(self, lines, destination, encoding=None):
79 outfile = codecs.open(destination, 'w',
80 encoding=(encoding or self.encoding))
81 outfile.write(u'\n'.join(lines))
82 outfile.write(u'\n')
84 # write_entry
85 # ------------
87 # Schreibe eine Liste von Datenfeldern (geparste Zeilen) in die Datei
88 # `destination`::
90 def write_entry(self, wortliste, destination, encoding=None):
91 lines = [unicode(entry) for entry in wortliste]
92 self.writelines(lines, destination, encoding)
95 # WordEntry
96 # =========
98 # Klasse für Einträge (Zeilen) der Wortliste
100 # Beispiel:
102 # >>> from werkzeug import WordEntry
104 # >>> aalbestand = WordEntry(u'Aalbestand;Aal=be<stand # Test')
105 # >>> print aalbestand
106 # Aalbestand;Aal=be<stand # Test
108 # ::
110 class WordEntry(list):
112 # Argumente
113 # ---------
115 # Kommentare (aktualisiert, wenn Kommentar vorhanden)::
117 comment = None
119 # Feldbelegung:
121 # 1. Wort ungetrennt
122 # 2. Wort mit Trennungen, falls für alle Varianten identisch,
123 # anderenfalls leer
124 # 3. falls Feld 2 leer, Trennung nach traditioneller Rechtschreibung
125 # 4. falls Feld 2 leer, Trennung nach reformierter Rechtschreibung (2006)
126 # 5. falls Feld 2 leer, Trennung für Wortform, die entweder in
127 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird
128 # und für traditionelle und reformierte Rechtschreibung identisch ist
129 # 6. falls Feld 5 leer, Trennung für Wortform, die entweder in
130 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
131 # traditionelle Rechtschreibung
132 # 7. falls Feld 5 leer, Trennung für Wortform, die entweder in
133 # der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
134 # reformierte Rechtschreibung (2006)
135 # 8. falls Feld 5 leer, Trennung nach (deutsch)schweizerischer
136 # Rechtschreibung; insbesondere Wörter mit "sss" gefolgt von
137 # einem Vokal, die wie andere Dreifachkonsonanten gehandhabt wurden
138 # (also anders, als der Duden früher vorgeschrieben hat), z.B.
139 # "süssauer"
141 # Sprachvarianten (Tags nach [BCP47]_) (Die Zählung der Indizes beginn in
142 # Python bei 0)::
144 sprachvarianten = {
145 'de': 1, # Deutsch, allgemeingültig
146 'de-1901': 2, # "traditionell" (nach Rechtschreibreform 1901)
147 'de-1996': 3, # reformierte Reformschreibung (1996)
148 'de-x-GROSS': 4, # ohne ß (Schweiz oder GROSS) allgemein
149 'de-1901-x-GROSS': 5, # ohne ß (Schweiz oder GROSS) "traditionell"
150 'de-1996-x-GROSS': 6, # ohne ß (Schweiz oder GROSS) "reformiert"
151 # 'de-CH-1996': 6, # Alias für 'de-1996-x-GROSS'
152 'de-CH-1901': 7, # ohne ß (Schweiz) "traditionell" ("süssauer")
156 # Initialisierung::
158 def __init__(self, line, delimiter=';'):
159 self.delimiter = delimiter
161 # eventuell vorhandenen Kommentar abtrennen und speichern::
163 if '#' in line:
164 line, self.comment = line.split('#')
165 line = line.rstrip()
167 # Zerlegen in Datenfelder, in Liste eintragen::
169 list.__init__(self, line.split(delimiter))
172 # Rückverwandlung in String
173 # -----------------------------------
175 # Erzeugen eines Eintrag-Strings (Zeile) aus der Liste der Datenfelder und
176 # dem Kommentar
178 # >>> unicode(aalbestand)
179 # u'Aalbestand;Aal=be<stand # Test'
181 # ::
183 def __unicode__(self):
184 line = ';'.join(self)
185 if self.comment is not None:
186 line += ' #' + self.comment
187 return line
190 def __str__(self):
191 return unicode(self).encode('utf8')
193 # lang_index
194 # ---------------
196 # Index des zur Sprachvariante gehörenden Datenfeldes:
198 # >>> aalbestand.lang_index('de')
200 # >>> aalbestand.lang_index('de-1901')
202 # >>> aalbestand.lang_index('de-1996')
204 # >>> abbeissen = WordEntry(
205 # ... u'abbeissen;-2-;-3-;-4-;-5-;ab<bei-ssen;ab<beis-sen;ab<beis-sen')
206 # >>> print abbeissen.lang_index('de')
207 # None
208 # >>> print abbeissen.lang_index('de-x-GROSS')
209 # None
210 # >>> abbeissen.lang_index('de-CH-1901')
213 # ::
215 def lang_index(self, sprachvariante):
217 assert sprachvariante in self.sprachvarianten, \
218 'Sprachvariante nicht in ' + str(self.sprachvarianten.keys())
220 # Einfacher Fall: eine allgemeine Schreibweise::
222 if len(self) == 2:
223 return 1
225 # Spezielle Schreibung::
227 try:
228 i = self.sprachvarianten[sprachvariante]
229 feld = self[i]
230 except IndexError:
231 if i > 4 and len(self) == 5:
232 return 4 # Allgemeine Schweiz/GROSS Schreibung:
233 return None # Feld nicht vorhanden
235 if feld.startswith('-'): # '-1-', '-2-', ...
236 return None # leeres Feld
238 return i
240 # Trennmuster für Sprachvariante ausgeben
242 # >>> aalbestand.get('de')
243 # u'Aal=be<stand'
244 # >>> aalbestand.get('de-1901')
245 # u'Aal=be<stand'
246 # >>> aalbestand.get('de-1996')
247 # u'Aal=be<stand'
248 # >>> aalbestand.get('de-x-GROSS')
249 # u'Aal=be<stand'
250 # >>> aalbestand.get('de-1901-x-GROSS')
251 # u'Aal=be<stand'
252 # >>> aalbestand.get('de-1996-x-GROSS')
253 # u'Aal=be<stand'
254 # >>> aalbestand.get('de-CH-1901')
255 # u'Aal=be<stand'
257 # >>> print abbeissen.get('de')
258 # None
259 # >>> print abbeissen.get('de-x-GROSS')
260 # None
261 # >>> abbeissen.get('de-1901-x-GROSS')
262 # u'ab<bei-ssen'
263 # >>> abbeissen.get('de-CH-1901')
264 # u'ab<beis-sen'
266 # ::
268 def get(self, sprachvariante):
269 try:
270 return self[self.lang_index(sprachvariante)]
271 except TypeError: # Muster in `sprachvariante` nicht vorhanden
272 return None
274 # Trennmuster für Sprachvariante setzen
276 # >>> abbeissen.set('test', 'de-1901-x-GROSS')
277 # >>> print abbeissen
278 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
280 # >>> abbeissen.set('test', 'de-1901')
281 # Traceback (most recent call last):
282 # ...
283 # IndexError: kann kein leeres Feld setzen
285 # ::
287 def set(self, wort, sprachvariante):
288 i = self.lang_index(sprachvariante)
289 if i is None:
290 raise IndexError, "kann kein leeres Feld setzen"
291 self[i] = wort
293 # Felder für alle Sprachvarianten ausfüllen
295 # >>> print str(aalbestand), len(aalbestand)
296 # Aalbestand;Aal=be<stand # Test 2
297 # >>> aalbestand.expand_fields()
298 # >>> print len(aalbestand)
300 # >>> auffrass = WordEntry('auffrass;-2-;-3-;-4-;auf-frass')
301 # >>> auffrass.expand_fields()
302 # >>> print auffrass
303 # auffrass;-2-;-3-;-4-;auf-frass;auf-frass;auf-frass;auf-frass
305 # ::
307 def expand_fields(self):
308 fields = [self.get(sv) or '-%d-' % (self.sprachvarianten[sv] + 1)
309 for sv in sorted(self.sprachvarianten.keys(),
310 key=self.sprachvarianten.get)]
311 # return fields
312 for i, field in enumerate(fields):
313 try:
314 self[i+1] = field # Feld 1 ist "key" (ungetrennt)
315 except IndexError:
316 self.append(field)
319 # Felder für Sprachvarianten zusammenfassen
321 # >>> aalbestand.conflate_fields()
322 # >>> print aalbestand
323 # Aalbestand;Aal=be<stand # Test
324 # >>> auffrass.conflate_fields()
325 # >>> print auffrass
326 # auffrass;-2-;-3-;-4-;auf-frass
328 # Aber nicht, wenn die Trennstellen sich unterscheiden:
330 # >>> abenddienste = WordEntry(
331 # ... u'Abenddienste;-2-;Abend=dien-ste;Abend=diens-te')
332 # >>> abenddienste.conflate_fields()
333 # >>> print abenddienste
334 # Abenddienste;-2-;Abend=dien-ste;Abend=diens-te
336 # ::
338 def conflate_fields(self):
339 if len(self) == 8:
340 if self[7] == self[6] == self[5]:
341 self[4] = self[5] # umschreiben auf GROSS-allgemein
342 self.pop()
343 self.pop()
344 self.pop()
345 if len(self) == 5:
346 if self[4] == self[2]: # de-x-GROSS == de-1901
347 self.pop()
348 else:
349 return
350 if len(self) >= 4:
351 if self[3] == self[2]: # de-1996 == de-1901
352 self[1] = self[2] # Umschreiben auf de (allgemein)
353 self.pop()
354 self.pop()
358 # Funktionen
359 # ==========
361 # join_word
362 # ---------
364 # Trennzeichen entfernen::
366 def join_word(word, assert_complete=False):
368 # Einfache Trennzeichen:
370 # == ================================================================
371 # \· ungewichtete Trennstelle (solche, wo sich noch niemand um die
372 # Gewichtung gekümmert hat)
373 # \. unerwünschte Trennstelle (sinnentstellend), z.B. Ur·in.stinkt
374 # oder ungünstige Trennstelle (verwirrend), z.B. Atom·en.er·gie
375 # in ungewichteten Wörtern
376 # \= Trennstelle an Wortfugen (Wort=fu-ge)
377 # \< Trennstelle nach Präfix (Vor<sil-be)
378 # \> Trennstelle vor Suffix (Freund>schaf-ten)
379 # \- Nebentrennstelle (ge-hen)
380 # == ================================================================
382 # ::
384 table = {}
385 for char in u'·.=|-_<>':
386 table[ord(char)] = None
387 key = word.translate(table)
389 # Spezielle Trennungen für die traditionelle Rechtschreibung
390 # (siehe ../../dokumente/README.wortliste)::
392 if '{' in key or '}' in key:
393 key = key.replace(u'{ck/kk}', u'ck')
394 key = key.replace(u'{ck/k', u'k')
395 key = key.replace(u'k}', u'k')
396 # Konsonanthäufungen an Wortfuge: '{xx/xxx}' -> 'xx':
397 key = re.sub(ur'\{(.)\1/\1\1\1\}', ur'\1\1', key)
398 # schon getrennt: ('{xx/xx' -> 'xx' und 'x}' -> 'x'):
399 key = re.sub(ur'\{(.)\1/\1\1$', ur'\1\1', key)
400 key = re.sub(ur'^(.)\}', ur'\1', key)
402 # Trennstellen in doppeldeutigen Wörtern::
404 if '[' in key or ']' in key:
405 key = re.sub(ur'\[(.*)/\1\]', ur'\1', key)
406 # schon getrennt:
407 key = re.sub(ur'\[([^/\[]+)$', ur'\1', key)
408 key = re.sub(ur'^([^/\]]+)\]', ur'\1', key)
410 # Test auf verbliebene komplexe Trennstellen::
412 if assert_complete:
413 for spez in u'[{/}]':
414 if spez in key:
415 raise AssertionError('Spezialtrennung %s, %s' %
416 (word.encode('utf8'), key.encode('utf8')))
418 return key
420 # zerlege
421 # -------
423 # Zerlege ein Wort mit Trennzeichen in eine Liste von Silben und eine Liste
424 # von Trennzeichen)
426 # >>> from werkzeug import zerlege
428 # >>> zerlege(u'Haupt=stel-le')
429 # ([u'Haupt', u'stel', u'le'], [u'=', u'-'])
430 # >>> zerlege(u'Ge<samt=be<triebs=rats==chef')
431 # ([u'Ge', u'samt', u'be', u'triebs', u'rats', u'chef'], [u'<', u'=', u'<', u'=', u'=='])
432 # >>> zerlege(u'an<stands>los')
433 # ([u'an', u'stands', u'los'], [u'<', u'>'])
434 # >>> zerlege(u'An<al.pha-bet')
435 # ([u'An', u'al', u'pha', u'bet'], [u'<', u'.', u'-'])
437 # ::
439 def zerlege(wort):
440 silben = re.split(u'[-·._<>=]+', wort)
441 trennzeichen = re.split(u'[^-·._|<>=]+', wort)
442 return silben, [tz for tz in trennzeichen if tz]
444 # TransferError
445 # -------------
447 # Fehler beim Übertragen von Trennstellen mit uebertrage_::
449 class TransferError(ValueError):
450 def __init__(self, wort1, wort2):
451 msg = u'Inkompatibel: %s %s' % (wort1, wort2)
452 ValueError.__init__(self, msg.encode('utf8'))
454 def __unicode__(self):
455 return str(self).decode('utf8')
458 # uebertrage
459 # ----------
461 # Übertrage die Trennzeichen von `wort1` auf `wort2`:
463 # >>> from werkzeug import uebertrage, TransferError
465 # >>> uebertrage(u'Haupt=stel-le', u'Haupt·stel·le')
466 # u'Haupt=stel-le'
468 # Auch teilweise Übertragung, von "kategorisiert" nach "unkategorisiert":
470 # >>> print uebertrage(u'Haupt=stel-le', u'Haupt=stel·le')
471 # Haupt=stel-le
473 # >>> print uebertrage(u'Haupt·stel-le', u'Haupt=stel·le')
474 # Haupt=stel-le
476 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler')
477 # Aus<stel-ler
479 # >>> print uebertrage(u'Freund>schaf·ten', u'Freund-schaf-ten')
480 # Freund>schaf-ten
482 # Übertragung doppelter Marker:
484 # >>> print uebertrage(u'ver<<aus<ga-be', u'ver<aus<ga-be')
485 # ver<<aus<ga-be
487 # >>> print uebertrage(u'freund>lich>>keit', u'freund>lich>keit')
488 # freund>lich>>keit
490 # Kein Überschreiben doppelter Marker:
491 # >>> print uebertrage(u'ver<aus<ga-be', u'ver<<aus<ga-be')
492 # ver<<aus<ga-be
494 # Erhalt des Markers für ungünstige Stellen:
495 # >>> print uebertrage(u'An·al.pha·bet', u'An<al.pha-bet')
496 # An<al.pha-bet
498 # Keine Übertragung, wenn die Zahl oder Position der Trennstellen
499 # unterschiedlich ist oder bei unterschiedlichen Wörtern:
501 # >>> try:
502 # ... uebertrage(u'Ha-upt=stel-le', u'Haupt=stel·le')
503 # ... uebertrage(u'Haupt=ste-lle', u'Haupt=stel·le')
504 # ... uebertrage(u'Waupt=stel-le', u'Haupt=stel·le')
505 # ... except TransferError:
506 # ... pass
508 # Übertragung auch bei unterschiedlicher Schreibung oder Position der
509 # Trennstellen mit `strict=False` (für Abgleich zwischen Sprachvarianten):
511 # >>> uebertrage(u'er-ster', u'ers·ter', strict=False)
512 # u'ers-ter'
513 # >>> uebertrage(u'Fluß=bett', u'Fluss·bett', strict=False)
514 # u'Fluss=bett'
515 # >>> uebertrage(u'ab>bei-ßen', u'ab>beis·sen', strict=False)
516 # u'ab>beis-sen'
517 # >>> print uebertrage(u'Aus<tausch=dien-stes', u'Aus-tausch=diens-tes', False)
518 # Aus<tausch=diens-tes
520 # Auch mit `strict=False` muß die Zahl der Trennstellen übereinstimmen
521 # (Ausnahmen siehe unten):
523 # >>> try:
524 # ... uebertrage(u'Ha-upt=ste-lle', u'Haupt=stel·le', strict=False)
525 # ... except TransferError:
526 # ... pass
528 # Akzeptiere unterschiedliche Anzahl von Trennungen bei st und ck nach
529 # Selbstlaut:
531 # >>> uebertrage(u'acht=ecki-ge', u'acht·e{ck/k·k}i·ge', strict=False)
532 # u'acht=e{ck/k-k}i-ge'
533 # >>> uebertrage(u'As-to-ria', u'Asto·ria', strict=False)
534 # u'Asto-ria'
535 # >>> uebertrage(u'Asto-ria', u'As·to·ria', strict=False)
536 # u'As-to-ria'
537 # >>> uebertrage(u'So-fa=ecke', u'So·fa=e{ck/k-k}e', strict=False)
538 # u'So-fa=e{ck/k-k}e'
540 # Mit ``upgrade=False`` werden nur unspezifische Trennstellen überschrieben:
542 # >>> print uebertrage(u'an=stel-le', u'an<stel·le', upgrade=False)
543 # an<stel-le
545 # >>> print uebertrage(u'Aus<stel-ler', u'Aus-stel-ler', upgrade=False)
546 # Aus-stel-ler
548 # >>> print uebertrage(u'Aus-stel-ler', u'Aus<stel-ler', upgrade=False)
549 # Aus<stel-ler
551 # >>> print uebertrage(u'vor<an<<stel-le', u'vor-an<stel·le', upgrade=False)
552 # vor-an<stel-le
554 # ::
556 selbstlaute = u'aeiouäöüAEIOUÄÖÜ'
558 def uebertrage(wort1, wort2, strict=True, upgrade=True):
560 silben1, trennzeichen1 = zerlege(wort1)
561 silben2, trennzeichen2 = zerlege(wort2)
562 # Prüfe strikte Übereinstimmung:
563 if silben1 != silben2 and strict:
564 if u'<' in trennzeichen1 or u'·' in trennzeichen2:
565 raise TransferError(wort1, wort2)
566 else:
567 return wort2
568 # Prüfe ungefähre Übereinstimmung:
569 if len(trennzeichen1) != len(trennzeichen2):
570 # Selbstlaut + st oder ck?
571 for s in selbstlaute:
572 if (wort2.find(s+u'{ck/k·k}') != -1 or
573 wort2.find(s+u'{ck/k-k}') != -1):
574 wort1 = wort1.replace(s+u'ck', s+u'-ck')
575 silben1, trennzeichen1 = zerlege(wort1)
576 if wort2.find(s+u's·t') != -1:
577 wort1 = wort1.replace(s+u'st', s+u's-t')
578 silben1, trennzeichen1 = zerlege(wort1)
579 elif wort1.find(s+u's-t') != -1:
580 wort1 = wort1.replace(s+u's-t', s+u'st')
581 silben1, trennzeichen1 = zerlege(wort1)
582 # print u'retry:', silben1, trennzeichen1
583 # immer noch ungleiche Zahl an Trennstellen?
584 if len(trennzeichen1) != len(trennzeichen2):
585 raise TransferError(wort1, wort2)
587 # Baue wort3 aus silben2 und spezifischeren Trennzeichen:
588 wort3 = silben2.pop(0)
589 for t1,t2 in zip(trennzeichen1, trennzeichen2):
590 if ((t2 == u'·' and t1 != u'.') # unspezifisch
591 or upgrade and
592 ((t2 in (u'-', u'<') and t1 in (u'<', u'<<', u'<=')) # Praefixe
593 or (t2 in (u'-', u'>') and t1 in (u'>', u'>>', u'=>')) # Suffixe
594 or (t2 in (u'-', u'=') and t1 in (u'=', u'==', u'===')) # W-fugen
597 wort3 += t1
598 elif t2 == u'.' and t1 != u'·':
599 wort3 += t1 + t2
600 else:
601 wort3 += t2
602 wort3 += silben2.pop(0)
603 return wort3
606 # Übertrag kategorisierter Trennstellen zwischen den Feldern aller Einträge
607 # in `wortliste`::
609 def sprachabgleich(entry, vorbildentry=None):
611 if len(entry) <= 2:
612 return # allgemeine Schreibung
614 mit_vorsilbe = None
615 gewichtet = None
616 ungewichtet = None
617 for field in entry[1:]:
618 if field.startswith('-'): # -2-, -3-, ...
619 continue
620 if u'·' in field:
621 ungewichtet = field
622 elif u'<' in field:
623 mit_vorsilbe = field
624 else:
625 gewichtet = field
626 if vorbildentry:
627 for field in vorbildentry[1:]:
628 if field.startswith('-'): # -2-, -3-, ...
629 continue
630 if u'<' in field and not mit_vorsilbe:
631 mit_vorsilbe = field
632 elif u'·' not in field and (not gewichtet) and ungewichtet:
633 gewichtet = field
634 # print 've:', mit_vorsilbe, gewichtet, ungewichtet
635 if mit_vorsilbe and (gewichtet or ungewichtet):
636 for i in range(1,len(entry)):
637 if entry[i].startswith('-'): # -2-, -3-, ...
638 continue
639 if u'<' not in entry[i] or u'·' in entry[i]:
640 try:
641 entry[i] = uebertrage(mit_vorsilbe, entry[i], strict=False)
642 except TransferError, e:
643 print u'Sprachabgleich:', unicode(e)
644 print mit_vorsilbe+u':', unicode(entry)
645 elif gewichtet and ungewichtet:
646 for i in range(1,len(entry)):
647 if u'·' in entry[i]:
648 try:
649 entry[i] = uebertrage(gewichtet, entry[i], strict=False)
650 except TransferError, e:
651 print u'Sprachabgleich:', unicode(e)
652 print gewichtet, unicode(entry)
655 # Großschreibung in Kleinschreibung wandeln und umgekehrt
657 # Diese Version funktioniert auch für Wörter mit Trennzeichen (während
658 # str.title() nach jedem Trennzeichen wieder groß anfängt)
660 # >>> from werkzeug import toggle_case
661 # >>> toggle_case(u'Ha-se')
662 # u'ha-se'
663 # >>> toggle_case(u'arm')
664 # u'Arm'
665 # >>> toggle_case(u'frei=bier')
666 # u'Frei=bier'
667 # >>> toggle_case(u'L}a-ger')
668 # u'l}a-ger'
670 # Keine Änderung bei Wörtern mit Großbuchstaben im Inneren:
672 # >>> toggle_case(u'USA')
673 # u'USA'
675 # >>> toggle_case(u'gri[f-f/{ff/ff')
676 # u'Gri[f-f/{ff/ff'
677 # >>> toggle_case(u'Gri[f-f/{ff/ff')
678 # u'gri[f-f/{ff/ff'
680 # ::
682 def toggle_case(wort):
683 try:
684 key = join_word(wort, assert_complete=True)
685 except AssertionError:
686 key = wort[0]
687 if key.istitle():
688 return wort.lower()
689 elif key.islower():
690 return wort[0].upper() + wort[1:]
691 else:
692 return wort
695 # udiff
696 # ------------
698 # Vergleiche zwei Sequenzen von `WordEntries`, gib einen "unified diff" als
699 # Byte-String zurück (weil difflib nicht mit Unicode-Strings arbeiten kann).
701 # Beispiel:
703 # >>> from werkzeug import udiff
704 # >>> print udiff([abbeissen, aalbestand], [abbeissen], 'alt', 'neu')
705 # --- alt
706 # +++ neu
707 # @@ -1,2 +1 @@
708 # abbeissen;-2-;-3-;-4-;-5-;test;ab<beis-sen;ab<beis-sen
709 # -Aalbestand;Aal=be<stand # Test
711 # ::
713 def udiff(a, b, fromfile='', tofile='',
714 fromfiledate='', tofiledate='', n=1, encoding='utf8'):
716 a = [unicode(entry).encode(encoding) for entry in a]
717 b = [unicode(entry).encode(encoding) for entry in b]
719 diff = difflib.unified_diff(a, b, fromfile, tofile,
720 fromfiledate, tofiledate, n, lineterm='')
722 if diff:
723 return '\n'.join(diff)
724 else:
725 return None
728 def test_keys(wortliste):
729 """Teste Übereinstimmung des ungetrennten Wortes in Feld 1
730 mit den Trennmustern nach Entfernen der Trennmarker.
731 Schreibe Inkonsistenzen auf die Standardausgabe.
733 `wortliste` ist ein Iterator über die Einträge (Klasse `WordEntry`)
735 is_OK = True
736 for entry in wortliste:
737 # Test der Übereinstimmung ungetrenntes/getrenntes Wort
738 # für alle Felder:
739 key = entry[0]
740 for wort in entry[1:]:
741 if wort.startswith(u'-'): # leere Felder
742 continue
743 if key != join_word(wort):
744 is_OK = False
745 print u"\nkey '%s' != '%s'" % (key, wort),
746 if key.lower() == join_word(wort).lower():
747 print(u" Abgleich der Großschreibung mit"
748 u"`prepare-patch.py grossabgleich`."),
749 return is_OK
752 # Test
753 # ====
755 # ::
757 if __name__ == '__main__':
758 import sys
760 # sys.stdout mit UTF8 encoding (wie in Python 3)
761 sys.stdout = codecs.getwriter('UTF-8')(sys.stdout)
763 print u"Test der Werkzeuge und inneren Konsistenz der Wortliste\n"
765 # wordfile = WordFile('../../wortliste-binnen-s')
766 wordfile = WordFile('../../wortliste')
767 # print 'Dateiobjekt:', wordfile
769 # Liste der Datenfelder (die Klasseninstanz als Argument für `list` liefert
770 # den Iterator über die Felder, `list` macht daraus eine Liste)::
772 wortliste = list(wordfile)
773 print len(wortliste), u"Einträge\n"
775 # Sprachauswahl::
777 # Sprachtags:
779 # sprache = 'de-1901' # traditionell
780 # sprache = 'de-1996' # Reformschreibung
781 # sprache = 'de-x-GROSS' # ohne ß (Schweiz oder GROSS) allgemein
782 # sprache = 'de-1901-x-GROSS' # ohne ß (Schweiz oder GROSS) "traditionell"
783 # sprache = 'de-1996-x-GROSS' # ohne ß (Schweiz oder GROSS) "reformiert"
784 # sprache = 'de-CH-1901' # ohne ß (Schweiz) "traditionell" ("süssauer")
786 # worte = [entry.get(sprache) for entry in wortliste if wort is not None]
787 # print len(worte), u"Einträge für Sprachvariante", sprache
790 # Test keys::
792 print u"Teste Schlüssel-Trennmuster-Übereinstimmung:",
793 if test_keys(wortliste):
794 print u"OK",
795 print
797 # Doppeleinträge::
799 doppelte = 0
800 words = set()
801 for entry in wortliste:
802 key = entry[0].lower()
803 if key in words:
804 doppelte += 1
805 print unicode(entry)
806 words.add(key)
807 print doppelte,
808 print u"Doppeleinträge (ohne Berücksichtigung der Großschreibung)."
809 if doppelte:
810 print u" Entfernen mit `prepare-patch.py doppelte`."
811 print u" Patch vor Anwendung durchsehen!"
814 # Ein Wörterbuch (dict Instanz)::
816 # wordfile.seek(0) # Pointer zurücksetzen
817 # words = wordfile.asdict()
819 # print len(words), u"Wörterbucheinträge"
821 # Zeilenrekonstruktion::
823 # am Beispiel der Scheiterbeige:
824 # original = u'beige;beige # vgl. Scheiter-bei-ge'
825 # entry = words[u"beige"]
826 # line = unicode(entry)
827 # assert original == line, "Rejoined %s != %s" % (line, original)
829 # komplett:
830 wordfile.seek(0) # Pointer zurücksetzen
831 OK = 0
832 line = wordfile.readline().rstrip().decode(wordfile.encoding)
833 while line:
834 entry = WordEntry(line)
835 if line == unicode(entry):
836 OK +=1
837 else:
838 print u'-', line,
839 print u'+', unicode(entry)
840 line = wordfile.readline().rstrip().decode(wordfile.encoding)
842 print OK, u"Einträge rekonstruiert"
846 # Quellen
847 # =======
849 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
850 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
852 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
853 # http://mirrors.ctan.org/language/hyphenation/dehyph-exptl/projektbeschreibung.pdf