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
)
795 if __name__
== '__main__':
797 # Pfad zu "../../../wortliste" unabhängig vom Arbeitsverzeichnis::
799 default_wortliste
= os
.path
.relpath(os
.path
.join(
800 os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(
801 os
.path
.abspath(__file__
))))),
806 usage
= '%prog [Optionen]\n' + __doc__
808 parser
= optparse
.OptionParser(usage
=usage
)
809 parser
.add_option('-i', '--file', dest
='wortliste',
810 help='Vergleichsdatei, Vorgabe "%s"'%default
_wortliste
,
811 default
=default_wortliste
)
812 parser
.add_option('-f', '--filter', action
="store_true",
813 help=u
'in WORTLISTE vorhandene Wörter aussortieren',
815 parser
.add_option('-a', '--filter-ableitungen', action
="store_true",
816 help=u
'Ableitungen von Wörtern der Eingabe aussortieren',
818 (options
, args
) = parser
.parse_args()
820 # sys.stdout mit UTF8 encoding.
821 sys
.stdout
= codecs
.getwriter('UTF-8')(sys
.stdout
)
823 wordfile
= WordFile(options
.wortliste
)
828 words
= wordfile
.asdict()
829 for line
in filter_neuliste(sys
.stdin
, words
):
833 if options
.filter_ableitungen
:
834 for line
in filter_ableitungen(sys
.stdin
):
838 # `Wortliste` einlesen
840 # Wörter, Teilwörter und Kombinationen (siehe expand_teilwoerter.py)
841 # entweder vom "cache", oder "live" expandiert::
843 cache
= "wortliste-expandiert"
845 cache_mtime
= os
.path
.getmtime(cache
)
849 # Umschreiben in Dictionary, Aussortieren von Abkürzungen
851 for entry
in wordfile
:
852 if not entry
or u
'Abk.' in entry
.comment
:
854 words
[entry
[0]] = entry
856 if os
.path
.getmtime(options
.wortliste
) <= cache_mtime
:
857 words
.update(WordFile(cache
).asdict())
859 words
.update(expand_words(words
))
862 # Aussortieren von Wörtern, die zu "false positives" führen::
864 # Wörter, die oft als Endungen auftauchen:
865 for alt
, neu
in endungen
:
866 words
.pop(join_word(neu
), None)
868 for unwort
in [u
'ei', u
'Ei', u
'em', u
'est', u
'et', u
'Mc',
869 u
'in', u
'is', u
'so', u
'Tu', u
'Um', u
'Wa']:
870 words
.pop(unwort
, None)
873 # Erstellen der neuen Einträge::
879 proposals
= [WordEntry(line
.decode('utf8').strip())
880 for line
in sys
.stdin
881 if not line
.startswith('#')]
883 for newentry
in proposals
:
887 # print key, unicode(newentry)
890 # Test auf vorhandene (Teil-) Wörter:
892 entry
= words
.get(key
)
897 entry
= words
.get(key
.lower())
899 neue_grossklein
.append(entry
)
902 entry
= words
.get(key
.title())
904 neue_grossklein
.append(entry
)
909 for alt
, neu
in endungen
:
910 entry
= endungsabgleich(key
, alt
, neu
, grossklein
=False)
912 entry
.comment
= newentry
.comment
919 for alt
, neu
in endungen
:
920 entry
= endungsabgleich(key
, alt
, neu
, grossklein
=True)
922 entry
.comment
= newentry
.comment
923 neue_grossklein
.append(entry
)
931 for praefix
in praefixe
:
932 entry
= praefixabgleich(key
, praefix
, grossklein
=False)
934 entry
.comment
= newentry
.comment
938 entry
= praefixabgleich(key
, praefix
, grossklein
=True)
940 entry
.comment
= newentry
.comment
941 neue_grossklein
.append(entry
)
947 # Zerlegen und test auf Fugen::
949 entries
= trenne_key(key
, grossklein
=False)
953 entries
= trenne_key(key
, grossklein
=True)
955 neue_grossklein
.extend(entries
)
958 # Nicht gefundene Wörter::
960 rest
.append(newentry
)
962 # Mehrdeutige aussortieren::
966 doppelkeys_gleich
= defaultdict(int)
968 # doppelte keys finden:
969 for entry
in neue
+ neue_grossklein
:
970 key
= entry
[0].lower()
971 if key
in alle_neuen
:
972 if entry
== alle_neuen
[key
]:
973 doppelkeys_gleich
[key
] += 1
976 alle_neuen
[key
] = entry
978 # doppelte Einträge "verlegen":
980 eindeutige_grossklein
= []
984 key
= entry
[0].lower()
985 if key
in doppelkeys
:
986 doppelte
.append(entry
)
987 elif doppelkeys_gleich
[key
] > 0:
988 doppelkeys_gleich
[key
] -= 1
990 eindeutige
.append(entry
)
992 for entry
in neue_grossklein
:
993 key
= entry
[0].lower()
994 if key
in doppelkeys
:
995 doppelte
.append(entry
)
996 elif doppelkeys_gleich
[key
] > 0:
997 doppelkeys_gleich
[key
] -= 1
999 eindeutige_grossklein
.append(entry
)
1002 # Vergleich mit Original::
1005 for proposal
in proposals
:
1006 key
= proposal
[0].lower()
1007 newentry
= alle_neuen
.get(key
)
1008 if proposal
== newentry
:
1009 identische
[key
] = proposal
1012 newentry
.proposal
= proposal
1016 print u
'\n## identisch rekonstruiert:'
1017 for entry
in sorted(identische
.values(), key
=sortkey_duden
):
1018 print unicode(entry
)
1020 print u
'\n## eindeutig abgeleitet'
1021 for entry
in eindeutige
:
1022 if entry
[0].lower() not in identische
:
1023 print_proposal(entry
)
1024 print u
'\n## eindeutig abgeleitet (andere Großschreibung)'
1025 for entry
in eindeutige_grossklein
:
1026 if entry
[0].lower() not in identische
:
1027 print_proposal(entry
)
1029 print u
'\n## mehrdeutig abgeleitet'
1030 for entry
in doppelte
:
1031 print_proposal(entry
)
1037 print_proposal(entry
)