Python-Skript Update und Fix.
[wortliste.git] / skripte / python / s2long-s.py
blob6894b97713efd402991d31de3a72d87be7a735aa
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
3 # :Copyright: © 2012, 2014 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.3 (2014-06-14)
10 # ===================================================================
11 # Langes oder rundes S: Automatische Konversion nach Silbentrennung
12 # ===================================================================
14 # ::
16 """
17 Automatische Bestimmung der S-Schreibung auf Basis der Silbentrennung
18 in der `Wortliste der deutschsprachigen Trennmustermannschaft`.
19 """
21 # .. contents::
23 # Vorspann
24 # ========
26 # Lade Funktionen und Klassen für reguläre Ausdrücke::
28 import re
29 from werkzeug import WordFile, join_word
31 # Ausgangsbasis
32 # =============
34 # Die freie `Wortliste der deutschsprachigen Trennmustermannschaft`_
35 # ("Lembergsche Liste")
37 # ::
39 wordfile = WordFile('../../wortliste') # volle Liste (≅ 400 000 Wörter
40 # wordfile = WordFile('../../wortliste-binnen-s') # vorsortierte Liste (≅ 200 000 Wörter)
42 # Trennzeichen
43 # ------------
45 # Die Trennzeichen der Wortliste sind
47 # == ================================================================
48 # \· ungewichtete Trennstellen (solche, wo noch niemand sich um die
49 # Gewichtung gekümmert hat)
50 # . unerwünschte Trennstellen (sinnverwirrend), z.B. Ur-in.stinkt
51 # = Haupttrennstellen
52 # \- Nebentrennstellen
53 # < Trennstellen nach Vorsilben
54 # > Trennstellen vor Suffixen
55 # == ================================================================
58 # Funktionen
59 # ==========
61 # ſ-Regeln
62 # --------
64 # Siehe [wikipedia]_ und ("DDR"-) [Duden]_ (Regeln K 44,45)::
66 def s_ersetzen(word):
68 # ſ steht im Silbenanlaut::
70 word = re.sub(ur'^s', ur'ſ', word)
71 word = re.sub(ur'([-<>=·.])s', ur'\1ſ', word)
73 # ſ steht im Inlaut als stimmhaftes s zwischen Vokalen
74 # (gilt auch für ungetrenntes ss zwischen Selbstlauten, z.B. Hausse, Baisse)::
76 word = re.sub(ur'([AEIOUYÄÖÜaeiouäöüé])s([aeiouyäöüé])', ur'\1ſ\2', word)
77 word = re.sub(ur'([AEIOUYÄÖÜaeiouäöüé])ss([aeiouyäöüés])', ur'\1ſſ\2', word)
79 # ſ steht in den Verbindungen sp, st, sch und in Digraphen::
81 word = word.replace(u'st', u'ſt')
82 word = word.replace(u'sp', u'ſp')
83 word = word.replace(u'sch', u'ſch')
85 # word = word.replace(u'ps', u'pſ')
86 word = word.replace(u'Ps', u'Pſ') # Ψ
87 word = re.sub(ur'^ps', ur'pſ', word) # ψ (ps am Wortanfang)
88 word = re.sub(ur'([-<>=·.])ps', ur'\1pſ', word) # ψ (ps am Silbenanfang)
90 # word = word.replace(u'ſsſt', u'ſſſt') # Pssst!
92 # ſ vor Trennstellen
93 # ~~~~~~~~~~~~~~~~~~
95 # Die Verbindungen ss, sp¹, st werden zu ſſ, ſp und ſt, auch wenn sie
96 # durch eine Nebentrennstelle (Trennung innerhalb eines Wortbestandteiles)
97 # getrennt sind. Das s bleibt rund im Auslaut, d.h. am Wortende und vor
98 # einer Haupttrennstelle (Trennung an der Grenze zweier Wortbestandteile
99 # (Vorsilb<Stamm, Bestimmungswort=Grundwort). Für sz und sk gelten
100 # Spezialregeln, die die Herkunft der Wörter beachten (siehe `Spezialfälle`_
101 # sowie `Fremdwörter und Eingennamen`_).
103 # ¹ s bleibt rund vor Nebentrennstelle wenn ph folgt (Phos-phor).
105 # ::
107 word = re.sub(ur's[-.]+ſ([aeiouyäöüé])', ur'ſ-ſ\1', word)
108 word = re.sub(ur's[-.]+p([^h])', ur'ſ-p\1', word)
109 word = re.sub(ur's[-.]+t', ur'ſ-p', word) # Reformschreibung
112 # Spezialfälle
113 # ~~~~~~~~~~~~
115 # ſz trotz Trennzeichen::
117 word = word.replace(u's-zen', ur'ſ-zen') # Adoleszenz, Aszendent, ...
118 word = word.replace(u's-ze-n', ur'ſ-ze-n') # Damaszener, ...
119 word = word.replace(u's-zi-n', ur'ſ-zi-n') # faszinieren, ...
120 word = word.replace(u'as-zi', u'aſ-zi') # [Ll]asziv, ...
121 word = word.replace(u'vis-zi', u'viſ-zi') # Mukoviszidose
122 word = word.replace(u's-zil', ur'ſ-zil') # Os-zil-la-ti-on
123 word = word.replace(u's-zie', ur'ſ-zie') # fluo-res-zie-ren, ...
125 # ſ wird geschrieben, wenn der S-Laut nur scheinbar im Auslaut steht,
126 # weil ein folgendes unbetontes e ausfällt::
128 # Basel, Beisel, Pilsen, drechseln, wechseln, häckseln
129 word = word.replace(u'Bas-ler', u'Baſ-ler')
130 word = word.replace(u'Pils-ner', u'Pilſ-ner')
131 word = word.replace(u'echs-ler', u'echſ-ler') # Dechsler, Wechsler
132 word = word.replace(u'äcks-ler', u'äckſ-ler') # Häcksler
133 word = word.replace(u'Rössl', u'Röſſl')
135 # Insel (Rheininsler), zünseln (Maiszünsler)
136 word = word.replace(u'ins-ler', u'inſ-ler')
137 word = word.replace(u'üns-ler', u'ünſ-ler')
139 # unsre, unsrige, ...
140 word = word.replace(u'uns-r', u'unſ-r')
142 # Häusl, Lisl, bissl, Glasl, Rössl
143 word = word.replace(u'sl', u'ſl')
144 word = word.replace(u'ssl', u'ſſl')
146 # ſ steht auch am Ende von Abkürzungen, wenn es im abgekürzten Wort steht
147 # (Abſ. - Abſatz/Abſender, (de)creſc. - (de)creſcendo, daſ. - daſelbst ...)
149 word = word.replace(u'cresc', u'creſc')
151 # Alternativtrennung, wo beide Fälle ſ verlangen:
153 word = word.replace(u'er<.]sa', u'er<.]ſa') # Kind=er<.satz/Kin-der=satz
155 # Fremdwörter und Eigennamen
156 # ~~~~~~~~~~~~~~~~~~~~~~~~~~
158 # Schreibung nach Regeln der Herkunftssprache. Dabei ist zu bedenken, daß zu
159 # der Zeit, als das lange s im Antiquasatz noch üblich war (bis ca. 1800), die
160 # Rechtschreibung freier gehandhabt wurde und mehrfach Wandlungen unterworfen
161 # war [West06]_.
163 # Im Deutschen werden im Fraktursatz nicht eingedeutschte Fremdwörter
164 # lateinischen und romanischen Ursprungs in Antiqua mit rund-s geschrieben.
166 # English:
167 # The long, medial, or descending ess, as distinct from the short or
168 # terminal ess. In Roman script, the long ess was used everywhere except at
169 # the end of words, where the short ess was used, and frequently in what is
170 # now the digraph «ss», which was often written «ſs» rather than «ſſ»
171 # [en.wiktionary.org]_. See also [Typefounder08]_ and [West06]_.
173 # ::
175 word = word.replace(u'sh', u'ſh') # (englisch)
176 word = word.replace(u'Disc', u'Diſc') # (englisch)
177 word = word.replace(u'Csar', u'Cſar') # Cs -> Tsch (ungarisch)
178 # word = word.replace(u'sz', u'ſz') # polnisch, ungarisch
179 word = word.replace(u'Liszt', u'Liſzt') # ungarisch
180 word = word.replace(u'Pusz', u'Puſz') # Pusz-ta ungarisch
181 word = re.sub(ur'([Tt])s([aeiouy])', ur'\1ſ\2', word) # ts (chinesisch)
184 # Schreibung von Fremdwörtern und Eigennamen mit Schluss-ß:
186 # Der 1971er [Duden]_ führt zu englischen Fremdwörtern mit Schluß-ß die
187 # österreichische Schreibung mit "ss" auf (Miß, engl. und österr. Schreibung
188 # Miss) wobei das Schluß-s nicht unterstrichen ist (also lang sein müßte).
189 # So auch Boss, Business, Stewardess.
191 # Dagegen sagt [en.wiktionary.org]_: "the digraph «ss» was often
192 # written «ſs» rather than «ſſ»".
194 # ::
196 # TODO so machen, oder ſs (wie in de-1996)? :
197 # if sprachvariante == 'de-1901':
198 # word = re.sub(ur'ss$', ur'ſſ', word)
199 # word = word.replace(u'ss=', u'ſſ=')
200 # word = word.replace(u'ss-ſch', u'ſſ-ſch')
202 word = re.sub(u'ss$', u'ſs', word)
203 word = word.replace(u'ss=', u'ſs=')
204 word = word.replace(u'ss-ſch', u'ſs-ſch')
207 return word
209 # s-Regeln
210 # --------
212 # Test auf verbliebene Unklarheiten
214 # Wenn ein Wort "s" nur an Stellen enthält wo die Regeln rundes S vorsehen,
215 # ist die automatische Konversion abgeschlossen.
217 # Ausnahmen und spezielle Regeln
219 # Liste von Teilstrings, welche stets rund-s behalten ::
221 spezialfaelle_rund_s = [
223 # Abkürzungen::
225 u'Ausg', u'ausſchl', u'desgl', u'hrsg', u'insb',
227 # ausgelassenes flüchtiges e::
229 u'Dresd-ne', # Dresd·ner/Dresd·ner·in
231 # s steht auch in einigen Fremdwörtern vor z::
233 u'on-fis-zie', # konfiszieren
234 u'le-bis-z', # plebiszit
235 u'is-zi-pl', # Disziplin (nach Duden (1934) auch Diſziplin).
236 u'mas-ze-ner' # Damaszener
238 # ss im Auslaut (vgl. `Fremdwörter und Eigennamen`_)::
240 # u'Gauss', # vgl. "Briefwechsel zwischen C.F. Gauss und H.C. Schumacher, herausg. von C.A.F. Peters"
241 # aber Boſſ, Busineſſ, Dreſſ
242 u'ſs-ſch' # Graſs-ſcher, Weiſs-ſches, Zeiſs-ſche
246 spezialfaelle_rund_s = [(fall, fall.replace('s', '~'))
247 for fall in spezialfaelle_rund_s]
249 def is_complete(word):
251 # Ersetze s an Stellen, wo es rund zu schreiben ist, durch ~ und teste auf
252 # verbliebene Vorkommen.
254 # Einzelfälle mit rundem S (substrings)::
256 for fall, ersatz in spezialfaelle_rund_s:
257 word = word.replace(fall, ersatz)
259 # s steht am Wortende, auch in Zusammensetzungen (vor Haupttrennstellen)::
261 word = re.sub(ur's($|[=<>])', ur'~\1', word)
263 # Einige ältere Quellen schreiben ss am Schluss von Fremdwörtern oder Namen
264 # (Gauss). Andere schreiben ſs oder ſſ. ::
266 #word = re.sub(ur'ss(=|$)', ur'~~\1', word)
268 # s steht am Silbenende (vor Nebentrennstellen), wenn kein p, t, z oder ſ
269 # folgt (in der traditionellen Schreibung wird st nicht getrennt)::
271 # word = re.sub(ur'ss?([·.\-][^ptzſ])', ur'~\1', word) # konservativ
272 word = re.sub(ur'ss?([·.\-][^pzſ])', ur'~\1', word) # traditionell
274 # s steht auch vor Nebentrennstellen, wenn ph folgt::
276 word = word.replace(u's-ph', u'~-ph')
278 # s steht nach Vorsilben (wie aus<, dis<) auch wenn s, p, t, oder z folgt::
280 word = word.replace(u's<', u'~<')
282 # s steht vor Trennstellen am Suffixanfang (wie >sen, >son),
283 # auch wenn s, p, t, oder z folgt::
285 word = word.replace(u's>', u'~>')
287 # s steht im Inlaut vor k, n, w::
289 word = re.sub(ur's([knw])', ur'~\1', word)
291 # und suche nach übrigen Vorkommen::
293 return 's' not in word
296 # Globale Variablen
297 # =================
299 # Rechtschreibvariante
300 # --------------------
302 # Angabe der Sprachvariante nach [BCP47]_ (Reformschreibung 'de' oder 'de-1996',
303 # Schweiz 'de-CH', ...)
305 sprachtag = 'de-1901'
307 # Für gebrochene Schriften gibt es den ISO Sprachtag
309 # :Latf: Latin (Fraktur variant)
311 # also "Lateinisches Alphabet, gebrochen". Wir hätten dann "de-1901-Latf" und
312 # "de-1996-Latf"
313 # http://www.unicode.org/iso15924/iso15924-codes.html
315 # Kategorien
316 # ----------
318 # Der Algorithmus sortiert die Wörter der Trennliste in die folgenden
319 # Kategorien:
321 # Menge aller Wörter der Liste (ohne Trennmuster)::
323 words = set()
325 # Automatisch konvertierte Wörter (ohne Trennmuster)::
327 completed = []
329 # Offene Fälle mit Trennmuster-Feldern wie im Original:
331 # Zur automatischen Konvertierung fehlt die Unterscheidung in Haupt- und
332 # Nebentrennstellen (Wichtung)::
334 ungewichtet = []
336 # Der Algorithmus kann die Schreibweise (noch) nicht ermitteln
337 # (mit teilweisen Ersetzungen)::
339 offen = []
341 # Hauptschleife
342 # =============
344 # Iteration über alle Zeilen der Wortliste::
346 for entry in wordfile:
348 word = entry.get(sprachtag) # Wort mit Trennstellen
349 if word is None: # Wort existiert nicht in der Sprachvariante
350 continue
352 # Menge aller Wörter der gewählten Schreibweise (ohne Trennstellen)::
354 words.add(entry[0])
356 # Vorsortieren
357 # ------------
359 # Wörter ohne Binnen-s müssen nicht konvertiert werden. Damit wird die
360 # Wortliste ungefähr um die Hälfte kürzer::
362 if 's' not in entry[0][:-1]:
363 completed.append(entry[0])
364 continue
366 # Regelbasierte s-ſ-Schreibung::
368 word = s_ersetzen(word)
370 # Einsortieren nach Vollständigkeit der Ersetzungen::
372 if is_complete(word):
373 completed.append(join_word(word))
374 continue
376 entry.set(word, sprachtag) # Rückschreiben von teilweisen Ersetzungen
378 if word.find(u's·') != -1:
379 ungewichtet.append(entry)
380 else:
381 offen.append(entry)
384 # Ausgabe
385 # =======
387 # Wortliste mit automatisch bestimmter S-Schreibung, ohne Trennstellen::
389 completed_file = file('words-' + sprachtag + 'Latf.txt', 'w')
390 completed_file.write(u'\n'.join(completed).encode('utf8') + '\n')
392 # Auswertung
393 # ==========
395 # ::
397 print "# Gesamtwortzahl (traditionelle Rechtschreibung):", len(words)
398 print "# Automatisch konvertiert:", len(completed)
399 print "# Kategorisierung der Trennstellen fehlt:", len(ungewichtet)
400 for entry in ungewichtet:
401 print unicode(entry).encode('utf8')
402 print "# noch offen/unklar:", len(offen)
403 for entry in offen:
404 print unicode(entry).encode('utf8')
406 # print "# konvertiert+nichtklassifiziert+offen:",
407 # print len(completed) + len(ungewichtet) + len(offen)
411 # Diskussion
412 # ==========
414 # Statistik
415 # ---------
417 # Gesamtwortzahl (traditionelle Rechtschreibung): 427746
418 # Automatisch konvertiert: 427740
419 # Kategorisierung der Trennstellen fehlt: 0
420 # noch offen: 6
422 # Die Mehrzahl der Wörter der Trennliste wurde nach den Regeln des Dudens in
423 # die Schreibung mit langem `S` (ſ) konvertiert (wobei ungefähr die Hälfte der
424 # Wörter kein kleines `s` enthält womit die Konversion trivial wird).
426 # Der größte Teil der ca. 16 000 noch offenen Fälle kann durch Unterscheidung
427 # in Haupt- und Nebentrennstellen (z.B. mit dem SiSiSi_-Algorithmus) gelöst
428 # werden. CTAN enthält eine (alte) Variante mit Atomlisten im Text-Format
429 # (`Ur-SiSiSi`_). Die im Rahmen einer Diplomarbeit [gruber03]_ entstandene
430 # Variante `Java-SiSiSi` enthält eine Schnittstelle zum
431 # Wortanalysealgorithmus, die es ermöglicht SiSiSi als Bibliothek in fremde
432 # Programme einzubinden.
434 # Für eine beschränke Anzahl offener Fälle wurden Ausnahmeregeln und Ausnahmen
435 # implementiert.
437 # Das Resultat muß noch auf nicht erfaßte Ausnahmen und Sonderfälle geprüft
438 # werden. Fehlentscheidungen sind nicht auszuschließen.
441 # Offene Fälle
442 # ------------
444 # Wörter mit ſ am Wort oder Silbenende
445 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
447 # * sp, ss, st und sz wird zu ſp, ſſ, ſt und ſz, auch wenn das ſ vor einer
448 # Nebentrennstelle steht (z.B. Weſ-pe, eſ-ſen, abbürſ-ten (Reformtrennung)
449 # und Faſ-zination)
451 # **Aber** rundes s am Wortende und nach Vorsilben, z.B.
452 # dis=putieren, Aus=ſage, aus=tragen, aus=zeichnen.
455 # Wörter mit identischer Schreibung ohne lang-s
456 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
458 # * Wach[s/ſ]tube: Wach-stube oder Wachs-Tube
459 # * Ga[s/ſ]traſſe: Gas-Trasse oder Gast-Rasse
461 # Unklare Schreibung
462 # ~~~~~~~~~~~~~~~~~~
464 # * Tonarten (As-Dur oder Aſ-Dur)
466 # - Im Fraktur-Duden steht As in Antiqua mit rundem s, aber
467 # - im 1976-er [Duden]_ steht As ohne Unterstreichung des `s`.
471 # Quellen
472 # =======
474 # .. [Duden] `Der Große Duden` 16. Auflage, VEB Bibliographisches Institut
475 # Leipzig, 1971
477 # Kennzeichnet im Stichwortteil rundes s durch Unterstreichen.
479 # .. [wikipedia] Langes s
480 # http://de.wikipedia.org/wiki/Langes_s
482 # .. [en.wiktionary.org]
483 # http://en.wiktionary.org/wiki/%C5%BF
485 # .. [Typefounder08]
486 # http://typefoundry.blogspot.com/2008/01/long-s.html
488 # .. [West06] Andrew West, `The rules for long s`, 2006
489 # http://babelstone.blogspot.com/2006/06/rules-for-long-s.html
491 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
492 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
494 # .. [gruber03] Martin Gruber,
495 # `Effiziente Gestaltung der Wortanalyse in SiSiSi`, Diplomarbeit, 2003,
496 # http://www.ads.tuwien.ac.at/publications/bib/pdf/gruber-03.pdf
498 # .. Links:
500 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
501 # http://mirrors.ctan.org/language/hyphenation/dehyph-exptl/projektbeschreibung.pdf
503 # .. _SiSiSi: http://www.ads.tuwien.ac.at/research/SiSiSi.html
505 # .. _Ur-SiSiSi: ftp://ftp.dante.de/pub/tex/systems/unix/sisisi/
508 # .. Fragen, Spezialfälle, Beispiele
510 # http://www.e-welt.net/bfds_2003/bund/fragen/13_Langes%20oder%20rundes%20S.pdf
511 # http://www.e-welt.net/bfds_2003/bund/fragen/12_hs%20und%20scharfes%20s_1.pdf