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
32 from wortliste
import WordFile
, WordEntry
, join_word
, toggle_case
, sortkey_duden
33 from expand_teilwoerter
import expand_wordfile
38 # Übertrag von Praefixen auf Wörter ohne Präfix::
40 def praefixabgleich(key
, praefix
, grossklein
=False):
43 praefix
= praefix
.title()
45 if not key
.startswith(join_word(praefix
)):
48 altkey
= key
[len(join_word(praefix
)):]
51 altkey
= toggle_case(altkey
)
54 altentry
= words
[altkey
]
58 entry
= WordEntry(key
)
59 # print "fundum", key, unicode(entry)
60 for wort
in altentry
[1:]:
61 if not wort
.startswith(u
'-'):
63 wort
= u
'<'.join([praefix
, wort
])
383 # Nach Länge sortieren, damit spezifischere zuerst Probiert werden:
384 praefixe
.sort(key
= len)
388 # Übertrag von Einträgen auf Wörter mit anderer Endung::
390 def endungsabgleich(key
, alt
, neu
, grossklein
=False):
392 if not key
.endswith(join_word(neu
)):
395 altkey
= key
[:-len(join_word(neu
))] + join_word(alt
)
397 altkey
= toggle_case(altkey
)
400 altentry
= words
[altkey
]
404 entry
= WordEntry(key
)
405 # print "fundum", key, unicode(entry)
406 for wort
in altentry
[1:]:
407 if wort
.startswith(u
'-'):
409 if not wort
.endswith(neu
):
412 wort
= wort
[:-len(alt
)]
415 wort
= toggle_case(wort
)
416 if join_word(wort
) != key
:
421 print u
"# Übertragungsproblem: %s -> %s (%s,%s) %s" % (
422 altkey
, key
, alt
, neu
, unicode(entry
))
424 if len(entry
) == 1: # keine Übertragung möglich
427 entry
.regelaenderungen() # Sprachabgleich
433 # ``(<alt>, <neu>)`` Paare von Endungen::
478 (u
'-en', u
'>bar>keit'),
494 (u
'-sten', u
's-mus'),
516 (u
'e-ren', u
'-ti-on'),
547 (u
'isch', u
'i-sche'),
571 (u
'on', u
'o-nis-mus'),
588 (u
'ph', u
'-phis-mus'),
652 # Zerlege einen String mit von vorn bis hinten wandernder Bruchstelle::
654 # >>> from abgleich_neueintraege import zerlege
655 # >>> list(zerlege(u'wolle'))
656 # [(u'w', u'olle'), (u'wo', u'lle'), (u'wol', u'le'), (u'woll', u'e')]
661 for i
in range(1, len(s
)):
664 # Zerlege Kompositum in gleichberechtigte Teile::
666 # >>> from abgleich_neueintraege import split_composits
667 # >>> from wortliste import WordEntry
668 # >>> split_composits(WordEntry(u'Blockheizkraftwerk;Block===heiz==kraft=werk'))
669 # [u'Block', u'heiz', u'kraft', u'werk']
673 def split_composits(entry
):
674 return [w
for w
in entry
[1].split(u
'=') if w
]
676 # Zerlege String, wenn die Teile in der Wortliste vorhanden sind, setze
677 # sie neu zusammen und übernimm die Trennmarkierer:
680 def trenne_key(key
, grossklein
= False):
683 for k1
, k2
in zerlege(key
):
691 e2
= words
.get(toggle_case(k2
))
693 if len(e1
) != len(e2
):
695 e1
= [e1
[1]] * len(e2
)
697 e2
= [e2
[1]] * len(e1
)
700 entry
= WordEntry(key
)
701 for w1
, w2
in zip(e1
,e2
)[1:]:
702 if w1
.startswith(u
'-'): # empty column -2-, -3-, ...
704 elif w2
.startswith(u
'-'):
711 while (level
*sep
in w1
) or (level
*sep
in w2
):
713 wort
= (level
*sep
).join([w1
, w2
])
715 entry
.conflate_fields()
716 entries
.append(entry
)
717 # Teste auf 3-teilige Composita und entferne die Wichtung:
718 # ['Kau==zahn=weh', 'Kau=zahn=weh'] -> ['Kau=zahn=weh']
719 if len(entries
) == 2:
720 teile
= [split_composits(entry
) for entry
in entries
]
721 if teile
[0] == teile
[1]:
723 while level
*sep
in teile
[0]:
725 entries
= [entries
[0]]
726 entries
[0][1] = entries
[0][1].replace((level
+1)*sep
, level
*sep
)
729 def filter_neuliste(liste
, words
):
731 line
= line
.decode('utf8').strip()
732 if line
.startswith('#'):
735 neukey
= line
.split(u
';')[0]
737 # print 'vorhanden:', line
739 if neukey
.title() in words
:
740 # print 'Vorhanden:', line
742 if neukey
.lower() in words
:
743 # print 'vorhanden (kleingeschrieben):', line
747 class SortableDict(dict):
748 """Dictionary with additional sorting methods
750 Tip: use key starting with with '_' for sorting before small letters
751 and with '~' for sorting after small letters.
753 def sortedkeys(self
):
754 """Return sorted list of keys"""
759 def sortedvalues(self
):
760 """Return list of values sorted by keys"""
761 return [self
[key
] for key
in self
.sortedkeys()]
763 def filter_ableitungen(liste
):
764 words
= SortableDict()
765 words
['#'] = '# Ableitungen entfernt'
767 line
= line
.decode('utf8').strip()
768 if line
.startswith('#'):
769 words
['#'] += '\n' + line
771 key
= line
.split(u
';')[0]
773 for alt
, neu
in endungen
:
774 altkey
= key
[:-len(join_word(neu
))] + join_word(alt
)
780 return words
.sortedvalues()
782 def print_proposal(entry
):
783 proposal
= getattr(entry
, "proposal", u
'')
784 if proposal
and len(proposal
) > 1:
785 print u
' ' + unicode(entry
)
786 print u
'#' + unicode(proposal
)
790 if __name__
== '__main__':
792 # Pfad zu "../../../wortliste" unabhängig vom Arbeitsverzeichnis::
794 default_wortliste
= os
.path
.relpath(os
.path
.join(
795 os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(
796 os
.path
.abspath(__file__
))))),
799 # os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
803 usage
= '%prog [Optionen]\n' + __doc__
805 parser
= optparse
.OptionParser(usage
=usage
)
806 parser
.add_option('-i', '--file', dest
='wortliste',
807 help='Vergleichsdatei, Vorgabe "%s"'%default
_wortliste
,
808 default
=default_wortliste
)
809 parser
.add_option('-f', '--filter', action
="store_true",
810 help=u
'in WORTLISTE vorhandene Wörter aussortieren',
812 parser
.add_option('-a', '--filter-ableitungen', action
="store_true",
813 help=u
'Ableitungen von Wörtern der Eingabe aussortieren',
815 (options
, args
) = parser
.parse_args()
817 # sys.stdout mit UTF8 encoding.
818 sys
.stdout
= codecs
.getwriter('UTF-8')(sys
.stdout
)
820 wordfile
= WordFile(options
.wortliste
)
825 words
= wordfile
.asdict()
826 for line
in filter_neuliste(sys
.stdin
, words
):
830 if options
.filter_ableitungen
:
831 for line
in filter_ableitungen(sys
.stdin
):
835 # `Wortliste` einlesen
837 # Wörter, Teilwörter und Kombinationen (siehe expand_teilwoerter.py)
838 # entweder vom "cache", oder "live" expandiert::
840 cache
= "wortliste-expandiert"
842 cache_mtime
= os
.path
.getmtime(cache
)
846 if os
.path
.getmtime(options
.wortliste
) <= cache_mtime
:
847 words
= WordFile(cache
).asdict()
849 words
= expand_wordfile(wordfile
)
851 # Aussortieren von Wörtern, die zu "false positives" führen::
853 # Wörter, die oft als Endungen auftauchen:
854 for alt
, neu
in endungen
:
855 words
.pop(join_word(neu
), None)
857 for unwort
in [u
'Em', u
'Gen']:
858 words
.pop(unwort
, None)
860 # Erstellen der neuen Einträge::
866 proposals
= [WordEntry(line
.decode('utf8').strip())
867 for line
in sys
.stdin
868 if not line
.startswith('#')]
870 for newentry
in proposals
:
874 # print key, unicode(newentry)
877 # Test auf vorhandene (Teil-) Wörter:
879 entry
= words
.get(key
)
884 entry
= words
.get(key
.lower())
886 neue_grossklein
.append(entry
)
889 entry
= words
.get(key
.title())
891 neue_grossklein
.append(entry
)
896 for alt
, neu
in endungen
:
897 entry
= endungsabgleich(key
, alt
, neu
, grossklein
=False)
899 entry
.comment
= newentry
.comment
906 for alt
, neu
in endungen
:
907 entry
= endungsabgleich(key
, alt
, neu
, grossklein
=True)
909 entry
.comment
= newentry
.comment
910 neue_grossklein
.append(entry
)
918 for praefix
in praefixe
:
919 entry
= praefixabgleich(key
, praefix
, grossklein
=False)
921 entry
.comment
= newentry
.comment
925 entry
= praefixabgleich(key
, praefix
, grossklein
=True)
927 entry
.comment
= newentry
.comment
928 neue_grossklein
.append(entry
)
934 # Zerlegen und test auf Fugen::
936 entries
= trenne_key(key
, grossklein
=False)
940 entries
= trenne_key(key
, grossklein
=True)
942 neue_grossklein
.extend(entries
)
945 # Nicht gefundene Wörter::
947 rest
.append(newentry
)
949 # Mehrdeutige aussortieren::
953 doppelkeys_gleich
= defaultdict(int)
955 # doppelte keys finden:
956 for entry
in neue
+ neue_grossklein
:
957 key
= entry
[0].lower()
958 if key
in alle_neuen
:
959 if entry
== alle_neuen
[key
]:
960 doppelkeys_gleich
[key
] += 1
963 alle_neuen
[key
] = entry
965 # doppelte Einträge "verlegen":
967 eindeutige_grossklein
= []
971 key
= entry
[0].lower()
972 if key
in doppelkeys
:
973 doppelte
.append(entry
)
974 elif doppelkeys_gleich
[key
] > 0:
975 doppelkeys_gleich
[key
] -= 1
977 eindeutige
.append(entry
)
979 for entry
in neue_grossklein
:
980 key
= entry
[0].lower()
981 if key
in doppelkeys
:
982 doppelte
.append(entry
)
983 elif doppelkeys_gleich
[key
] > 0:
984 doppelkeys_gleich
[key
] -= 1
986 eindeutige_grossklein
.append(entry
)
989 # Vergleich mit Original::
992 for proposal
in proposals
:
993 key
= proposal
[0].lower()
994 newentry
= alle_neuen
.get(key
)
995 if proposal
== newentry
:
996 identische
[key
] = proposal
999 newentry
.proposal
= proposal
1003 print u
'\n# identisch rekonstruiert:'
1004 for entry
in sorted(identische
.values(), key
=sortkey_duden
):
1005 print unicode(entry
)
1007 print u
'\n# eindeutig abgeleitet'
1008 for entry
in eindeutige
:
1009 if entry
[0].lower() not in identische
:
1010 print_proposal(entry
)
1011 print u
'\n# eindeutig abgeleitet (andere Großschreibung)'
1012 for entry
in eindeutige_grossklein
:
1013 if entry
[0].lower() not in identische
:
1014 print_proposal(entry
)
1016 print u
'\n# mehrdeutig abgeleitet'
1017 for entry
in doppelte
:
1018 print_proposal(entry
)
1024 print_proposal(entry
)