Lang-S-Konversion: Ausnahmeliste.
[wortliste.git] / skripte / python / s2long-s.py
blob4350d5ea23ce84782e8414dde843cf9c2045ea34
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`."""
20 # .. contents::
22 # Vorspann
23 # ========
25 # Lade Funktionen und Klassen für reguläre Ausdrücke::
27 import re, sys, os, optparse
29 # path for local Python modules
30 sys.path.append(os.path.dirname(__file__))
31 from werkzeug import WordFile, WordEntry, join_word
34 # Trennzeichen
35 # ------------
37 # Die Trennzeichen der Wortliste sind
39 # == ================================================================
40 # \· ungewichtete Trennstellen (solche, wo noch niemand sich um die
41 # Gewichtung gekümmert hat)
42 # . unerwünschte Trennstellen (sinnverwirrend), z.B. Ur-in.stinkt
43 # = Haupttrennstellen
44 # \- Nebentrennstellen
45 # < Trennstellen nach Vorsilben
46 # > Trennstellen vor Suffixen
47 # == ================================================================
50 # Funktionen
51 # ==========
53 # ſ-Regeln
54 # --------
56 # Siehe [wikipedia]_ und ("DDR"-) [Duden]_ (Regeln K 44,45)::
58 def s_ersetzen(word):
60 # Spezielle Fälle
61 # ~~~~~~~~~~~~~~~
63 # Für sz und sk gelten Regeln, welche die Herkunft der Wörter beachten.
64 # Diese und weitere spezielle Fälle, welche Lang-S vor Trennstellen verlangen
65 # sind in `Spezialfälle Lang-S`_ gelistet::
67 for fall in spezialfaelle_lang_s:
68 word = word.replace(fall.replace(u'ſ', u's'), fall)
70 # Allgemeine Regeln
71 # ~~~~~~~~~~~~~~~~~
73 # ſ steht im Silbenanlaut::
75 word = re.sub(ur'^s', ur'ſ', word)
76 word = re.sub(ur'([-<>=·.])s', ur'\1ſ', word)
78 # ſ steht im Inlaut als stimmhaftes s zwischen Vokalen
79 # (gilt auch für ungetrenntes ss zwischen Selbstlauten, z.B. Hausse, Baisse)::
81 word = re.sub(ur'([AEIOUYÄÖÜaeiouäöüé])s([aeiouyäöüé])', ur'\1ſ\2', word)
82 word = re.sub(ur'([AEIOUYÄÖÜaeiouäöüé])ss([aeiouyäöüé])', ur'\1ſſ\2', word)
84 # ſ steht in den Verbindungen sp, st, sch und in Digraphen, allerdings nicht
85 # in der Verbindung "sst", die seit 1996 für "ßt" steht::
87 word = re.sub(u'(^|[^s])st', ur'\1ſt', word)
88 word = word.replace(u'sp', u'ſp')
89 word = word.replace(u'sch', u'ſch')
91 # word = word.replace(u'ps', u'pſ')
92 word = word.replace(u'Ps', u'Pſ') # Ψ
93 word = re.sub(ur'^ps', ur'pſ', word) # ψ (ps am Wortanfang)
94 word = re.sub(ur'([-<>=·.])ps', ur'\1pſ', word) # ψ (ps am Silbenanfang)
97 # ſ vor Trennstellen
98 # ------------------
100 # Die Verbindungen ss, sp¹, st werden zu ſſ, ſp und ſt, auch wenn sie
101 # durch eine Nebentrennstelle (Trennung innerhalb eines Wortbestandteiles)
102 # getrennt sind. Das s bleibt rund im Auslaut, d.h. am Wortende und vor
103 # einer Haupttrennstelle (Trennung an der Grenze zweier Wortbestandteile
104 # (Vorsilb<Stamm, Bestimmungswort=Grundwort).
106 # ¹ s bleibt rund vor Nebentrennstelle wenn ph folgt (Phos-phor).
108 # ::
110 word = re.sub(ur's([-.]+)ſ([aeiouyäöüé])', ur'ſ\1ſ\2', word)
111 word = re.sub(ur's([-.]+)p([^h])', ur'ſ\1p\2', word)
112 word = re.sub(ur'(^|[^s])s([-.]+)t', ur'\1ſ\2t', word) # Reformschreibung
115 # Doppel-S statt SZ am Silbenende und vor t
116 # -----------------------------------------
118 # Wenn kein ß vorhanden ist, in der Schweiz und seit der Reform 1996 allgemein
119 # nach kurzem Mitlaut wird ss statt ß geschrieben. Der "Reformduden" empfielt
120 # im Fraktursatz die Schreibung ſs (die auch vor 1901 in Gebrauch war)::
122 word = re.sub(u'ss($|[-=<>.])', ur'ſs\1', word)
123 word = word.replace(u'sst', u'ſst')
125 # Fremdwörter und Eigennamen mit Schluss-ß
126 # """"""""""""""""""""""""""""""""""""""""
128 # Der 1971er [Duden]_ führt zu englischen Fremdwörtern mit Schluß-ß die
129 # österreichische Schreibung mit "ss" auf (Miß, engl. und österr. Schreibung
130 # Miss) wobei das Schluß-s nicht unterstrichen ist (also lang sein müßte?). So
131 # auch Boss, Business, Stewardess.
133 # Dagegen sagt [en.wiktionary.org]_: "the digraph «ss» was often written «ſs»
134 # rather than «ſſ»". Die Lang-S Seite der [wikipedia]_ zeigt Beispiele der
135 # englischen Schreibung "Congreſs".
137 # ::
139 # TODO ſſ oder ſs (wie in de-1996)? :
140 # if sprachvariante == 'de-1901':
141 # word = re.sub(ur'ss$', ur'ſſ', word)
142 # word = word.replace(u'ss=', u'ſſ=')
143 # word = word.replace(u'ss-ſch', u'ſſ-ſch')
145 return word
148 # Spezialfälle Lang-S
149 # ~~~~~~~~~~~~~~~~~~~
151 # Teilstrings mit Lang-S vor Trennstelle.
153 # ſ wird geschrieben, wenn der S-Laut nur scheinbar im Auslaut steht,
154 # weil ein folgendes unbetontes e ausfällt::
156 spezialfaelle_lang_s = [
158 # Basel, Beisel, Pilsen, drechseln, wechseln, häckseln
159 u'Baſ-ler',
160 u'Kaſſ-ler',
161 u'Pilſ-ner',
162 u'echſ-ler',# Dechsler, Wechsler
163 u'äckſ-ler',# Häcksler
164 u'Röſſl',
166 # Insel (Rheininsler), zünseln (Maiszünsler)
167 u'inſ-ler',
168 u'ünſ-ler',
170 # unsre, unsrige, ...
171 u'unſ-r',
173 # Häusl, Lisl, bissl, Glasl, Rössl
174 u'ſſl',
175 u'ſl',
178 # ſ steht auch am Ende von Abkürzungen, wenn es im abgekürzten Wort steht
179 # (Abſ. - Abſatz/Abſender, (de)creſc. - (de)creſcendo, daſ. - daſelbst ...)
180 # ::
182 spezialfaelle_lang_s.append(u'creſc')
184 # Alternativtrennung, wo beide Fälle ſ verlangen::
186 spezialfaelle_lang_s.append(u'er<.]ſa') # Kind=er<.satz/Kin-der=satz
187 spezialfaelle_lang_s.append(u'ſ[-ter=/t') # Tes[-ter=/t=er<.]ken-nung
189 # Fremdwörter und Eigennamen
190 # --------------------------
192 # Schreibung nach Regeln der Herkunftssprache. Dabei ist zu bedenken, daß zu
193 # der Zeit, als das lange s im Antiquasatz noch üblich war (bis ca. 1800), die
194 # Rechtschreibung freier gehandhabt wurde und mehrfach Wandlungen unterworfen
195 # war [West06]_.
197 # Im Deutschen werden im Fraktursatz nicht eingedeutschte Fremdwörter
198 # lateinischen und romanischen Ursprungs in Antiqua mit rund-s geschrieben.
200 # English:
201 # The long, medial, or descending ess, as distinct from the short or
202 # terminal ess. In Roman script, the long ess was used everywhere except at
203 # the end of words, where the short ess was used, and frequently in what is
204 # now the digraph «ss», which was often written «ſs» rather than «ſſ»
205 # [en.wiktionary.org]_. See also [Typefounder08]_ and [West06]_.
207 # ::
209 spezialfaelle_lang_s.extend([
211 u'ſh', # (englisch)
212 # u'Diſc', # (englisch) TODO: so, oder Disc (wie eingedeutscht Disk)
213 u'Cſar', # Cs -> Tsch (Csardas, ... ungarisch)
214 u'ſz', # polnisch, ungarisch (Liszt, Puszta)
216 # ts am Silbenanfang (chinesisch, japanisch, griechisch)::
218 u'Tſa', u'Tſe', u'tſe', u'tſi', u'Tſu', u'tſu',
220 # In vielen (aber nicht allen) Fremdwörtern steht ſz trotz Trennzeichen::
222 u'ſ-zen', # Adoleszenz, Aszendent, ...
223 u'ſ-ze-n', # Damaszener, ...
224 u'ſ-ze-r', # vis-ze-ral...
225 u'aſ-zi', # [Ll]asziv, laszive, ...
226 u'viſ-zi-do', # Mukoviszidose
227 u'ſ-zil-l' , # Oszillation, Oszilloskop, ...
228 u'asſ-zi', # faszinieren, fasziniert, ...
229 u'gnoſ-zie', # rekognoszieren,
230 u'o-reſ-z', # fluoreszieren, phosporeszieren, ...
231 u'-reſ-c', # Fluo-res-ce-in
232 u'le-biſ-zi', # Plebiszit, ...
234 # ſ steht in der Endung sk in Wörtern und Namen slawischen Ursprungs
236 # ::
238 u'Gdanſk',
239 u'Minſk',
240 u'Mur-manſk',
241 u'ſi-birſk',
242 u'Smo-lenſk',
245 # s-Regeln
246 # --------
248 # Test auf verbliebene Unklarheiten
250 # Wenn ein Wort "s" nur an Stellen enthält wo die Regeln rundes S vorsehen,
251 # ist die automatische Konversion abgeschlossen.
253 # Ausnahmen und spezielle Regeln
255 # Liste von Teilstrings, welche stets rund-s behalten ::
257 spezialfaelle_rund_s = [
259 # Abkürzungen::
261 u'Ausg', u'ausſchl', u'desgl', u'hrsg', u'insb',
263 # ausgelassenes flüchtiges e::
265 u'Dresd-ne', # Dresd·ner/Dresd·ner·in
266 u'Wiesn',
268 # s steht auch in einigen Fremdwörtern vor z und c::
270 u'on<fis-zie', # konfiszieren, ...
271 # u'le-bis-z', # plebiszit (nach [duden]_ mit Lang-S)
272 u'is-zi-pl', # Disziplin (nach [duden]_, aber Duden (1934) Diſziplin)
273 u'mas-ze-ner', # Damaszener
274 u'Disc', # TODO: rund oder Diſc (aber eingedeutscht Disk)
277 # ss im Auslaut (vgl. `Fremdwörter und Eigennamen`_)::
279 # u'Gauss', # vgl. "Briefwechsel zwischen C.F. Gauss und H.C. Schumacher, herausg. von C.A.F. Peters"
280 # aber Boſſ, Busineſſ, Dreſſ
284 def is_complete(word):
286 # Ersetze s an Stellen, wo es rund zu schreiben ist, durch ~ und teste auf
287 # verbliebene Vorkommen.
289 # Einzelfälle mit rundem S (substrings)::
291 for fall in spezialfaelle_rund_s:
292 word = word.replace(fall, fall.replace('s', '~'))
294 # s steht am Wortende, auch in Zusammensetzungen (vor Haupttrennstellen)::
296 word = re.sub(ur's($|[=<>])', ur'~\1', word)
298 # Einige ältere Quellen schreiben ss am Schluss von Fremdwörtern oder Namen
299 # (Gauss). Andere schreiben ſs oder ſſ. (Vgl. `Fremdwörter und Eigennamen
300 # mit Schluss-ß`_) Wir verwenden ſs (TODO oder?)::
302 #word = re.sub(ur'ss(=|$)', ur'~~\1', word)
304 # s steht am Silbenende (vor Nebentrennstellen), wenn kein p, t, z oder ſ
305 # folgt (in der traditionellen Schreibung wird st nicht getrennt)::
307 word = re.sub(ur'ss?([·.\-][^ptzſ])', ur'~\1', word) # konservativ
308 # word = re.sub(ur'ss?([·.\-][^pzſ])', ur'~\1', word) # traditionell
310 # s steht auch vor Nebentrennstellen, wenn ph oder sch folgt::
312 word = word.replace(u's-ph', u'~-ph')
313 word = word.replace(u's-ſch', u'~-ſch')
315 # s steht nach Vorsilben (wie aus<, dis<) auch wenn s, p, t, oder z folgt::
317 word = word.replace(u's<', u'~<')
319 # s steht vor Trennstellen am Suffixanfang (wie >sen, >son),
320 # auch wenn s, p, t, oder z folgt::
322 word = word.replace(u's>', u'~>')
324 # s steht im Inlaut vor k, n, w::
326 word = re.sub(ur's([knw])', ur'~\1', word)
328 # s steht in der Verbindung sst, die seit 1995 für ßt steht::
330 word = word.replace(u'ſst', u'ſ~t')
331 word = word.replace(u'ſs-t', u'ſ~-t')
333 # s steht als zweiter Buchstabe im ersetzten ß::
335 word = word.replace(u'-ſs', u'-ſ~') # traditionelle Orthographie
337 # und suche nach übrigen Vorkommen::
339 return 's' not in word
342 # Aufruf von der Kommandozeile
343 # ============================
345 # ::
347 if __name__ == '__main__':
349 # Optionen::
351 usage = u'%prog [Optionen]\n' + __doc__
352 parser = optparse.OptionParser(usage=usage)
353 parser.add_option('-i', '--infile', dest='infile',
354 help=u'Eingangsdatei ("-" oder "stdin" für '
355 u'Standardeingabe), Vorgabe "../../wortliste"',
356 default='../../wortliste')
357 parser.add_option('-o', '--outfile', dest='outfile',
358 help=u'Ausgangsdatei, ("-" oder "stdout" für '
359 u'Standardausgabe) Vorgabe: "words-<language>-Latf.txt"',
360 default='') # wird später ausgefüllt
361 parser.add_option('-l', '--language', dest='language',
362 help=u'Sprachvariante (ISO Sprachtag), '
363 u'Vorgabe "de-1901"',
364 default='de-1901')
366 (options, args) = parser.parse_args()
368 # Angabe der Sprachvariante nach [BCP47]_ (Reformschreibung 'de' oder
369 # 'de-1996', Schweiz 'de-CH', ...)::
371 lang = options.language
373 # Iterator::
375 if options.infile in ('-', 'stdin'):
376 wordfile = (WordEntry(line.rstrip().decode('utf-8'))
377 for line in sys.stdin)
378 else:
379 wordfile = WordFile(options.infile)
381 # Ausgabedatei::
382 if options.outfile in ('-', 'stdout'):
383 outstream = sys.stdout
384 else:
385 outfile = options.outfile or 'words-' + lang + '-Latf.txt'
386 outstream = file(outfile, 'w')
389 # Hauptschleife
390 # =============
392 # Konvertiere die Wörter der Trennliste und sortiere die Ergebnisse in
393 # Listen::
395 no_of_words = 0 # Gesamtwortzahl der gewählten Sprache
396 completed = [] # Automatisch konvertiert
397 irreversible = [] # Rückkonversion ungleich Original (Fehler)
398 unkategorisiert = [] # Unterscheidung in Haupt- und Nebentrennstellen fehlt
399 offen = [] # Der Algorithmus kann die Schreibweise (noch) nicht ermitteln
401 # Iteration über alle Zeilen der Wortliste::
403 for entry in wordfile:
405 word = entry.get(lang) # Wort mit Trennstellen
406 if word is None: # Wort existiert nicht in der Sprachvariante
407 continue
408 no_of_words += 1
410 # Vorsortieren
411 # ------------
413 # Wörter ohne Binnen-s müssen nicht konvertiert werden. Damit wird die
414 # Wortliste ungefähr um die Hälfte kürzer::
416 if 's' not in entry[0][:-1]:
417 completed.append(entry[0])
418 continue
420 # Regelbasierte s/ſ-Schreibung::
422 lang_s_word = s_ersetzen(word)
424 # Einsortieren nach Vollständigkeit der Ersetzungen::
426 entry.set(lang_s_word, lang) # Rückschreiben von teilweisen Ersetzungen
428 if lang_s_word.replace(u'ſ', u's') != word:
429 entry.comment = lang_s_word.replace(u'ſ', u's') + u" != " + word
430 irreversible.append(entry)
431 continue
433 if not is_complete(lang_s_word):
434 if lang_s_word.find(u's·') != -1:
435 unkategorisiert.append(entry)
436 else:
437 offen.append(entry)
438 continue
440 completed.append(join_word(lang_s_word))
441 # completed.append(lang_s_word)
444 # Ausgabe
445 # =======
447 # Wortliste mit automatisch bestimmter S-Schreibung, ohne Trennstellen::
449 outstream.write(u'\n'.join(completed).encode('utf8') + '\n')
451 # Auswertung
452 # ==========
454 # ::
456 if options.outfile not in ('-', 'stdout'):
457 print "# Gesamtwortzahl", lang, no_of_words
458 print "# Automatisch konvertiert:", len(completed)
459 print "# erkannte Konvertierungsfehler:", len(irreversible)
460 for entry in irreversible:
461 print unicode(entry).encode('utf8')
462 print "# Kategorisierung der Trennstellen fehlt:", len(unkategorisiert)
463 for entry in unkategorisiert:
464 print unicode(entry).encode('utf8')
465 print "# noch offen/unklar:", len(offen)
466 for entry in offen:
467 print unicode(entry).encode('utf8')
470 # Diskussion
471 # ==========
473 # Für gebrochene Schriften gibt es den `ISO Sprachtag`_
475 # :Latf: Latin (Fraktur variant)
477 # also "Lateinisches Alphabet, gebrochen". Dieser Tag wird and die
478 # Ausgabedateien angehängt (e.g. "de-1901-Latf", "de-1996-Latf").
480 # .. _ISO Sprachtag: http://www.unicode.org/iso15924/iso15924-codes.html
482 # Statistik
483 # ---------
485 # Gesamtwortzahl (traditionelle Rechtschreibung): 427746
486 # Automatisch konvertiert: 427740
487 # Kategorisierung der Trennstellen fehlt: 0
488 # noch offen: 6
490 # Die Mehrzahl der Wörter der Trennliste wurde nach den Regeln des Dudens in
491 # die Schreibung mit langem `S` (ſ) konvertiert (wobei ungefähr die Hälfte der
492 # Wörter kein kleines `s` enthält womit die Konversion trivial wird).
494 # Für eine beschränke Anzahl offener Fälle wurden Ausnahmeregeln und Ausnahmen
495 # implementiert.
497 # Das Resultat muß noch auf nicht erfaßte Ausnahmen und Sonderfälle geprüft
498 # werden. Fehlentscheidungen sind nicht auszuschließen.
501 # Offene Fälle
502 # ------------
504 # Wörter mit identischer Schreibung ohne Lang-S
505 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
507 # Einige mehrdeutige Zusammensetzungen unterscheiden sich in der
508 # Lang-S-Schreibung, z.B.
510 # * Wach[s/ſ]tube: Wach-Stube / Wachs-Tube
511 # * Ga[s/ſ]traſſe: Gas-Trasse / Gast-Rasse
512 # * Schiff[ſ/s]tau: Schiffs-Tau / Schiff-Stau
514 # Die Ausgangsdatei schreibt diese mit der "Alternativsyntax" [s/ſ] oder [ſ/s]
515 # wie in den obigen Beispielen.
517 # Unklare Schreibung
518 # ~~~~~~~~~~~~~~~~~~
520 # * Ersetztes SZ in reformierter Rechtschreibung, wenn ein Selbstlaut folgt
521 # (z.B. "Straſ-ſe" oder "Straſ-se).
523 # - Während in 1901-er Rechtschreibung die Trennung vor dem "ss" erfolgt
524 # (was Ersetzung mit "ſs" impliziert) wäre bei Trennung "wie normales
525 # Doppel-S" dann ein rundes S am Silbenanfang.
527 # Korrekte Fallunterscheidung geht nur bei Betrachtung der Nachbarfelder.
529 # * Tonarten (As-Dur oder Aſ-Dur)
531 # - Im Fraktur-Duden steht As *in Antiqua* mit rundem s, also
532 # keine Aussage zur Schreibung in Fraktur.
533 # - Im 1976-er [Duden]_ steht As ohne Unterstreichung des `s`,
534 # das wäre Lang-S, obgleich am Wortende!
536 # Quellen
537 # =======
539 # .. [Duden] `Der Große Duden` 16. Auflage, VEB Bibliographisches Institut
540 # Leipzig, 1971
542 # Kennzeichnet im Stichwortteil rundes s durch Unterstreichen.
544 # .. [wikipedia] Langes s
545 # http://de.wikipedia.org/wiki/Langes_s
547 # .. [en.wiktionary.org]
548 # http://en.wiktionary.org/wiki/%C5%BF
550 # .. [Typefounder08]
551 # http://typefoundry.blogspot.com/2008/01/long-s.html
553 # .. [West06] Andrew West, `The rules for long s`, 2006
554 # http://babelstone.blogspot.com/2006/06/rules-for-long-s.html
556 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
557 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
559 # .. Links:
561 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
562 # http://mirrors.ctan.org/language/hyphenation/dehyph-exptl/projektbeschreibung.pdf