3 # :Copyright: © 2014 Günter Milde.
4 # Released without warranty under the terms of the
5 # GNU General Public License (v. 2 or later)
8 # Versuche Trennstellen neuer Wörter aus vorhandenen zu ermitteln
9 # ===============================================================
11 u
"""Trenne neue Wörter durch Ableitung von Einträgen der Wortliste.
13 Eingabe: 1 ungetrenntes Wort oder Eintrag im Wortliste-Format pro Zeile.
15 Ausgabe: Wortliste-Einträge (Vorschläge), sortiert nach:
16 identisch (falls Eingabe bereits Wortliste-Eintrag ist und eindeutig ist),
18 eindeutig abgeleitet (andere Großschreibung),
19 mehrdeutig abgeleitet,
22 Bsp: python abgleich_neueintraege.py < dict-fail.txt > neu.todo
24 (``neu.todo`` kann (nach Durchsicht!!) mit `prepare_patch.py neu`
25 in die Wortliste eingepflegt werden.)
30 import sys
, os
, codecs
, optparse
31 from collections
import defaultdict
# Wörterbuch mit Default
33 # path for local Python modules (parent dir of this file's dir)
35 os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
))))
36 from wortliste
import WordFile
, WordEntry
, join_word
, toggle_case
, sortkey_duden
37 from expand_teilwoerter
import expand_words
42 # Übertrag von Praefixen auf Wörter ohne Präfix::
44 def praefixabgleich(key
, praefix
, grossklein
=False):
47 praefix
= praefix
.title()
49 if not key
.startswith(join_word(praefix
)):
52 altkey
= key
[len(join_word(praefix
)):]
55 altkey
= toggle_case(altkey
)
58 altentry
= words
[altkey
]
62 entry
= WordEntry(key
)
63 # print "fundum", key, unicode(entry)
64 for wort
in altentry
[1:]:
65 if not wort
.startswith(u
'-'):
67 wort
= u
'<'.join([praefix
, wort
])
387 # Nach Länge sortieren, damit spezifischere zuerst Probiert werden:
388 praefixe
.sort(key
= len)
392 # Übertrag von Einträgen auf Wörter mit anderer Endung::
394 def endungsabgleich(key
, alt
, neu
, grossklein
=False):
396 if not key
.endswith(join_word(neu
)):
399 altkey
= key
[:-len(join_word(neu
))] + join_word(alt
)
401 altkey
= toggle_case(altkey
)
404 altentry
= words
[altkey
]
408 entry
= WordEntry(key
)
409 # print "fundum", key, unicode(entry)
410 for wort
in altentry
[1:]:
411 if wort
.startswith(u
'-'):
413 if not wort
.endswith(neu
):
416 wort
= wort
[:-len(alt
)]
419 wort
= toggle_case(wort
)
420 if join_word(wort
) != key
:
425 print u
"# Übertragungsproblem: %s -> %s (%s,%s) %s" % (
426 altkey
, key
, alt
, neu
, unicode(entry
))
428 if len(entry
) == 1: # keine Übertragung möglich
431 entry
.regelaenderungen() # Sprachabgleich
437 # ``(<alt>, <neu>)`` Paare von Endungen::
482 (u
'-en', u
'>bar>keit'),
498 (u
'-sten', u
's-mus'),
520 (u
'e-ren', u
'-ti-on'),
551 (u
'isch', u
'i-sche'),
575 (u
'on', u
'o-nis-mus'),
592 (u
'ph', u
'-phis-mus'),
656 # Zerlege einen String mit von vorn bis hinten wandernder Bruchstelle::
658 # >>> from abgleich_neueintraege import zerlege
659 # >>> list(zerlege(u'wolle'))
660 # [(u'w', u'olle'), (u'wo', u'lle'), (u'wol', u'le'), (u'woll', u'e')]
665 for i
in range(1, len(s
)):
668 # Zerlege Kompositum in gleichberechtigte Teile::
670 # >>> from abgleich_neueintraege import split_composits
671 # >>> from edit_tools.wortliste import WordEntry
672 # >>> split_composits(WordEntry(u'Blockheizkraftwerk;Block===heiz==kraft=werk'))
673 # [u'Block', u'heiz', u'kraft', u'werk']
677 def split_composits(entry
):
678 return [w
for w
in entry
[1].split(u
'=') if w
]
680 # Zerlege String, wenn die Teile in der Wortliste vorhanden sind, setze
681 # sie neu zusammen und übernimm die Trennmarkierer:
684 def trenne_key(key
, grossklein
= False):
687 for k1
, k2
in zerlege(key
):
695 e2
= words
.get(toggle_case(k2
))
697 # Falls ein Teil in Sprachvarianten existiert, verdopple den 2.
698 if len(e1
) != len(e2
):
700 e1
= [e1
[1]] * len(e2
)
702 e2
= [e2
[1]] * len(e1
)
705 entry
= WordEntry(key
)
706 for w1
, w2
in zip(e1
,e2
)[1:]:
707 if w1
.startswith(u
'-'): # empty column -2-, -3-, ...
709 elif w2
.startswith(u
'-'):
716 while (level
*sep
in w1
) or (level
*sep
in w2
):
718 wort
= (level
*sep
).join([w1
, w2
])
721 entries
.append(entry
)
722 # Teste auf 3-teilige Composita und entferne die Wichtung:
723 # ['Kau==zahn=weh', 'Kau=zahn=weh'] -> ['Kau=zahn=weh']
724 if len(entries
) == 2:
725 teile
= [split_composits(entry
) for entry
in entries
]
726 if teile
[0] == teile
[1]:
728 while level
*sep
in teile
[0]:
730 entries
= [entries
[0]]
731 entries
[0][1] = entries
[0][1].replace((level
+1)*sep
, level
*sep
)
734 def filter_neuliste(liste
, words
):
736 line
= line
.decode('utf8').strip()
737 if line
.startswith('#'):
740 neukey
= line
.split(u
';')[0]
742 # print 'vorhanden:', line
744 if neukey
.title() in words
:
745 # print 'Vorhanden:', line
747 if neukey
.lower() in words
:
748 # print 'vorhanden (kleingeschrieben):', line
752 class SortableDict(dict):
753 """Dictionary with additional sorting methods
755 Tip: use key starting with with '_' for sorting before small letters
756 and with '~' for sorting after small letters.
758 def sortedkeys(self
):
759 """Return sorted list of keys"""
764 def sortedvalues(self
):
765 """Return list of values sorted by keys"""
766 return [self
[key
] for key
in self
.sortedkeys()]
768 def filter_ableitungen(liste
):
769 words
= SortableDict()
770 words
['#'] = '# Ableitungen entfernt'
772 line
= line
.decode('utf8').strip()
773 if line
.startswith('#'):
774 words
['#'] += '\n' + line
776 key
= line
.split(u
';')[0]
778 for alt
, neu
in endungen
:
779 altkey
= key
[:-len(join_word(neu
))] + join_word(alt
)
785 return words
.sortedvalues()
787 def print_proposal(entry
):
788 proposal
= getattr(entry
, "proposal", u
'')
789 if proposal
and len(proposal
) > 1:
790 print u
' ' + unicode(entry
)
791 print u
'#' + unicode(proposal
)
796 if __name__
== '__main__':
798 # Pfad zu "../../../wortliste" unabhängig vom Arbeitsverzeichnis::
800 default_wortliste
= os
.path
.relpath(os
.path
.join(
801 os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(
802 os
.path
.abspath(__file__
))))),
807 usage
= '%prog [Optionen]\n' + __doc__
809 parser
= optparse
.OptionParser(usage
=usage
)
810 parser
.add_option('-i', '--file', dest
='wortliste',
811 help='Vergleichsdatei, Vorgabe "%s"'%default
_wortliste
,
812 default
=default_wortliste
)
813 parser
.add_option('-f', '--filter', action
="store_true",
814 help=u
'in WORTLISTE vorhandene Wörter aussortieren',
816 parser
.add_option('-a', '--filter-ableitungen', action
="store_true",
817 help=u
'Ableitungen von Wörtern der Eingabe aussortieren',
819 (options
, args
) = parser
.parse_args()
821 # sys.stdout mit UTF8 encoding.
822 sys
.stdout
= codecs
.getwriter('UTF-8')(sys
.stdout
)
824 wordfile
= WordFile(options
.wortliste
)
829 words
= wordfile
.asdict()
830 for line
in filter_neuliste(sys
.stdin
, words
):
834 if options
.filter_ableitungen
:
835 for line
in filter_ableitungen(sys
.stdin
):
839 # `Wortliste` einlesen
841 # Wörter, Teilwörter und Kombinationen (siehe expand_teilwoerter.py)
842 # entweder vom "cache", oder "live" expandiert::
844 cache
= "wortliste-expandiert"
846 cache_mtime
= os
.path
.getmtime(cache
)
850 # Umschreiben in Dictionary, Aussortieren von Abkürzungen
852 for entry
in wordfile
:
853 if u
'Abk.' in entry
.comment
:
855 words
[entry
[0]] = entry
857 if os
.path
.getmtime(options
.wortliste
) <= cache_mtime
:
858 words
.update(WordFile(cache
).asdict())
860 words
.update(expand_words(words
))
863 # Aussortieren von Wörtern, die zu "false positives" führen::
865 # Wörter, die oft als Endungen auftauchen:
866 for alt
, neu
in endungen
:
867 words
.pop(join_word(neu
), None)
869 for unwort
in [u
'ei', u
'Ei', u
'em', u
'est', u
'et', u
'Mc',
870 u
'in', u
'is', u
'so', u
'Tu', u
'Um', u
'Wa']:
871 words
.pop(unwort
, None)
875 # Erstellen der neuen Einträge::
881 proposals
= [WordEntry(line
.decode('utf8').strip())
882 for line
in sys
.stdin
883 if line
.strip() and not line
.startswith('#')]
885 for newentry
in proposals
:
889 # print key, unicode(newentry)
892 # Test auf vorhandene (Teil-) Wörter:
894 entry
= words
.get(key
)
898 # kleingeschrieben oder Großgeschrieben:
899 entry
= words
.get(key
.lower()) or words
.get(key
.title())
901 neue_grossklein
.append(entry
)
906 for alt
, neu
in endungen
:
907 entry
= endungsabgleich(key
, alt
, neu
, grossklein
=False)
909 entry
.comment
= newentry
.comment
916 for alt
, neu
in endungen
:
917 entry
= endungsabgleich(key
, alt
, neu
, grossklein
=True)
919 entry
.comment
= newentry
.comment
920 neue_grossklein
.append(entry
)
928 for praefix
in praefixe
:
929 entry
= praefixabgleich(key
, praefix
, grossklein
=False)
931 entry
.comment
= newentry
.comment
935 entry
= praefixabgleich(key
, praefix
, grossklein
=True)
937 entry
.comment
= newentry
.comment
938 neue_grossklein
.append(entry
)
944 # Zerlegen und test auf Fugen::
946 entries
= trenne_key(key
, grossklein
=False)
950 entries
= trenne_key(key
, grossklein
=True)
952 neue_grossklein
.extend(entries
)
955 # Nicht gefundene Wörter::
957 rest
.append(newentry
)
959 # Mehrdeutige aussortieren::
963 doppelkeys_gleich
= defaultdict(int)
965 # doppelte keys finden:
966 for entry
in neue
+ neue_grossklein
:
967 key
= entry
[0].lower()
968 if key
in alle_neuen
:
969 if entry
== alle_neuen
[key
]:
970 doppelkeys_gleich
[key
] += 1
973 alle_neuen
[key
] = entry
975 # doppelte Einträge "verlegen":
977 eindeutige_grossklein
= []
981 key
= entry
[0].lower()
982 if key
in doppelkeys
:
983 doppelte
.append(entry
)
984 elif doppelkeys_gleich
[key
] > 0:
985 doppelkeys_gleich
[key
] -= 1
987 eindeutige
.append(entry
)
989 for entry
in neue_grossklein
:
990 key
= entry
[0].lower()
991 if key
in doppelkeys
:
992 doppelte
.append(entry
)
993 elif doppelkeys_gleich
[key
] > 0:
994 doppelkeys_gleich
[key
] -= 1
996 eindeutige_grossklein
.append(entry
)
999 # Vergleich mit Original::
1002 for proposal
in proposals
:
1003 key
= proposal
[0].lower()
1004 newentry
= alle_neuen
.get(key
)
1005 if proposal
== newentry
:
1006 identische
[key
] = proposal
1009 newentry
.proposal
= proposal
1013 print u
'\n## identisch rekonstruiert:'
1014 for entry
in sorted(identische
.values(), key
=sortkey_duden
):
1015 print unicode(entry
)
1017 print u
'\n## eindeutig abgeleitet'
1018 for entry
in eindeutige
:
1019 if entry
[0].lower() not in identische
:
1020 print_proposal(entry
)
1021 print u
'\n## eindeutig abgeleitet (andere Großschreibung)'
1022 for entry
in eindeutige_grossklein
:
1023 if entry
[0].lower() not in identische
:
1024 print_proposal(entry
)
1026 print u
'\n## mehrdeutig abgeleitet'
1027 for entry
in doppelte
:
1028 print_proposal(entry
)
1034 print_proposal(entry
)