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)
8 # :Version: 0.3 (2014-06-14)
10 # ===================================================================
11 # Langes oder rundes S: Automatische Konversion nach Silbentrennung
12 # ===================================================================
17 Automatische Bestimmung der S-Schreibung auf Basis der Silbentrennung
18 in der `Wortliste der deutschsprachigen Trennmustermannschaft`."""
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
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
44 # \- Nebentrennstellen
45 # < Trennstellen nach Vorsilben
46 # > Trennstellen vor Suffixen
47 # == ================================================================
56 # Siehe [wikipedia]_ und ("DDR"-) [Duden]_ (Regeln K 44,45)::
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 `Ausnahmen Lang-S`_ gelistet::
67 for fall
in ausnahmen_lang_s
:
68 word
= word
.replace(fall
.replace(u
'ſ', u
's'), fall
)
73 # ſ steht im Silbenanlaut::
75 word
= re
.sub(ur
'(^|[-<>=·.])s', ur
'\1ſ', word
)
77 # ſ steht im Inlaut als stimmhaftes s zwischen Vokalen
78 # (gilt auch für ungetrenntes ss zwischen Selbstlauten, z.B. Hausse, Baisse)::
80 word
= re
.sub(ur
'([AEIOUYÄÖÜaeiouäöüé])s([aeiouyäöüé])', ur
'\1ſ\2', word
)
81 word
= re
.sub(ur
'([AEIOUYÄÖÜaeiouäöüé])ss([aeiouyäöüé])', ur
'\1ſſ\2', word
)
87 # Wenn kein ß vorhanden ist (GROSSSCHREIBUNG) und in der Schweiz wird ss
88 # statt ß geschrieben. Seit 1996 wird auch am Wort-/Silbenende und vor t nach
89 # kurzem Vokal ss geschrieben. Der "Reformduden" empfielt im Fraktursatz die
90 # Schreibung "ſs" (die auch vor 1901 in Gebrauch war).
92 # Wir übernehmen diese Schreibung am Wort-/Silbenende::
94 word
= re
.sub(u
'ss($|[-=<>.])', ur
'ſs\1', word
)
96 # Vor t schreiben wir nach kurzem Vokal Doppel-ſ::
98 word
= word
.replace(u
'sst', u
'ſſt')
100 # Nach langem Vokal steht auch in de-1996 ein ß, in de-x-GROSS ist keine
101 # ſ-Wandlung nötig/möglich.
102 # TODO: in der Schweizer Orthographie müßte nach langem Vokal oder Zwielaut
103 # auch vor t ein ſs stehen (beißt -> beiſst).
105 # Verbindungen und Digraphen
106 # --------------------------
108 # ſ steht in den Verbindungen sp, st, sch und in Digraphen::
110 word
= word
.replace(u
'st', u
'ſt')
111 word
= word
.replace(u
'sp', u
'ſp')
112 word
= word
.replace(u
'sch', u
'ſch')
114 # word = word.replace(u'ps', u'pſ')
115 word
= word
.replace(u
'Ps', u
'Pſ') # Ψ
116 word
= re
.sub(ur
'^ps', ur
'pſ', word
) # ψ (ps am Wortanfang)
117 word
= re
.sub(ur
'([-<>=·.])ps', ur
'\1pſ', word
) # ψ (ps am Silbenanfang)
123 # Die Verbindungen ss, sp¹, st werden zu ſſ, ſp und ſt, auch wenn sie
124 # durch eine Nebentrennstelle (Trennung innerhalb eines Wortbestandteiles)
125 # getrennt sind. Das s bleibt rund im Auslaut, d.h. am Wortende und vor
126 # einer Haupttrennstelle (Trennung an der Grenze zweier Wortbestandteile
127 # (Vorsilb<Stamm, Bestimmungswort=Grundwort).
129 # ¹ s bleibt rund vor Nebentrennstelle wenn ph folgt (Phos-phor).
133 word
= re
.sub(ur
's([-.]+)ſ([aeiouyäöüé])', ur
'ſ\1ſ\2', word
)
134 word
= re
.sub(ur
's([-.]+)p([^h])', ur
'ſ\1p\2', word
)
135 word
= re
.sub(ur
'(^|[^s])s([-.]+)t', ur
'\1ſ\2t', word
) # Reformschreibung
137 # ſ wird auch geschrieben, wenn der S-Laut nur scheinbar im Auslaut steht,
138 # weil ein folgendes unbetontes e ausfällt:
140 # "ss-l", aber nicht bei "...eiss-l" und "Ess-lingen" mit ss statt ß::
142 word
= re
.sub(ur
'([^iE])ss-l', ur
'\1ſſ-l', word
) # Droſſ-lung, ...
144 # "s-l" (Baſ-ler, pinſ-le, Kapſ-lung, Wechſ-ler, wechſ-le, Ries-ling),
146 # M.s-l: Mus-lim, Mos-lem, ...
147 # [iys]s-l: Gris-ly, is-lam, Crys-ler, ... (ss-l siehe obige Regel)
148 # s-la: Bra-tis-la-va, Gos-lar, Bres-lau,
150 word
= re
.sub(ur
'([^mM][^siy])s-l([^a])', ur
'\1ſ-l\2', word
)
152 # (für weitere Fälle siehe auch `Ausnahmen Lang-S`_):
155 # Fremdwörter und Eigennamen mit Schluss-ß
156 # """"""""""""""""""""""""""""""""""""""""
158 # Der 1971er [Duden]_ führt zu englischen Fremdwörtern mit Schluß-ß die
159 # österreichische Schreibung mit "ss" auf (Miß, engl. und österr. Schreibung
160 # Miss) wobei das Schluß-s nicht unterstrichen ist (also lang sein müßte?). So
161 # auch Boss, Business, Stewardess.
163 # Dagegen sagt [en.wiktionary.org]_: "the digraph «ss» was often written «ſs»
164 # rather than «ſſ»". Die Lang-S Seite der [wikipedia]_ zeigt Beispiele der
165 # englischen Schreibung "Congreſs".
169 # TODO ſſ oder ſs (wie in de-1996)? :
170 # if lang == 'de-1901':
171 # word = re.sub(ur'ss$', ur'ſſ', word)
172 # word = word.replace(u'ss=', u'ſſ=')
173 # word = word.replace(u'ss-ſch', u'ſſ-ſch')
179 # ~~~~~~~~~~~~~~~~~~~
181 # Teilstrings mit Lang-S vor Trennstelle.
183 # ſ wird geschrieben, wenn der S-Laut nur scheinbar im Auslaut steht,
184 # weil ein folgendes unbetontes e ausfällt::
188 u
'Pilſ-ner', # < Pilsen, aber Mes-ner, Meiss-ner (de-ch)
189 u
'Klauſ-ner', # < Klause, aber Gleiss-ner (de-ch)
190 u
'riſſ-ne', # ge<riss-ne, ... (de-ch)
191 u
'oſſ-ne', # ge<schoss-ne, ge<schloss-ne, ... (de-ch)
192 u
'er<leſ-ne', # auserlesne
193 u
'unſ-r', # unsre, unsrige, ...
195 u
'ſl', # Beiſl, Häuſl
199 u
'Pſſſt', # im Duden pst!
202 # ſ steht in Abkürzungen, wenn es im abgekürzten Wort steht
203 # (Abſ. - Abſatz/Abſender, (de)creſc. - (de)creſcendo, daſ. - daſelbst ...)
206 ausnahmen_lang_s
.extend([
208 u
'creſc', # creſcendo
209 u
'Diſſ', # Diſſertation
210 u
'Maſſ', # Maſſachuſetts
214 # Alternativtrennung, wo beide Fälle ſ verlangen::
216 ausnahmen_lang_s
.extend([
218 u
'er<.]ſa' # Kind=er<.satz/Kin-der=satz
219 u
'ſ[-ter=/t' # Tes[-ter=/t=er<.]ken-nung
223 # Fremdwörter und Eigennamen
224 # --------------------------
226 # Schreibung nach Regeln der Herkunftssprache. Dabei ist zu bedenken, daß zu
227 # der Zeit, als das lange s im Antiquasatz noch üblich war (bis ca. 1800), die
228 # Rechtschreibung freier gehandhabt wurde und mehrfach Wandlungen unterworfen
231 # Im Deutschen werden im Fraktursatz nicht eingedeutschte Fremdwörter
232 # lateinischen und romanischen Ursprungs in Antiqua mit rund-s geschrieben.
235 # The long, medial, or descending ess, as distinct from the short or
236 # terminal ess. In Roman script, the long ess was used everywhere except at
237 # the end of words, where the short ess was used, and frequently in what is
238 # now the digraph «ss», which was often written «ſs» rather than «ſſ»
239 # [en.wiktionary.org]_. See also [Typefounder08]_ and [West06]_.
243 ausnahmen_lang_s
.extend([
246 # u'Diſc', # (englisch) TODO: so, oder Disc (wie eingedeutscht Disk)
247 u
'Cſar', # Cs -> Tsch (Csardas, ... ungarisch)
248 u
'ſz', # polnisch, ungarisch (Liszt, Puszta)
250 # ts am Silbenanfang (chinesisch, japanisch, griechisch)::
252 u
'Tſa', u
'Tſe', u
'tſe', u
'tſi', u
'Tſu', u
'tſu',
254 # In vielen (aber nicht allen) Fremdwörtern steht ſz trotz Trennzeichen::
256 u
'ſ-zen', # Adoleszenz, Aszendent, ...
257 u
'ſ-ze-n', # Damaszener, ...
258 u
'ſ-ze-r', # vis-ze-ral...
259 u
'aſ-zi', # [Ll]asziv, laszive, ...
260 u
'viſ-zi-do', # Mukoviszidose
261 u
'ſ-zil-l' , # Oszillation, Oszilloskop, ...
262 u
'asſ-zi', # faszinieren, fasziniert, ...
263 u
'gnoſ-zie', # rekognoszieren,
264 u
'o-reſ-z', # fluoreszieren, phosporeszieren, ...
265 u
'-reſ-c', # Fluo-res-ce-in
266 u
'le-biſ-zi', # Plebiszit, ...
268 # ſ steht in der Endung sk in Wörtern und Namen slawischen Ursprungs
278 # ſſ steht wenn auf die Vorsilbe "dis-" oder "as-" ein "s" folgt::
280 u
'aſ<ſ', # Assoziation, ...
281 u
'diſ<ſ', # Dissoziation, ...
282 u
'Diſ<ſ', # dissonant, ...
289 # Test auf verbliebene Unklarheiten
291 # Wenn ein Wort "s" nur an Stellen enthält wo die Regeln rundes S vorsehen,
292 # ist die automatische Konversion abgeschlossen.
294 # Ausnahmen und spezielle Regeln
296 # Liste von Teilstrings, welche stets rund-s behalten ::
302 u
'Ausg', # Ausgan, Ausgabe
304 u
'desgl', # des<gleichen
305 u
'hrsg', # herausgegeben
306 u
'Hrsg', # Herausgeber
309 # ausgelassenes flüchtiges e::
311 u
'Dresd-ne', # Dresd-ner/Dresd-ne-rin
313 # s steht auch in einigen Fremdwörtern vor z und c::
315 u
'on<fis-zie', # konfiszieren, ...
316 # u'le-bis-z', # plebiszit (nach [duden]_ mit Lang-S)
317 u
'is-zi-pl', # Disziplin (nach [duden]_, aber Duden (1934) Diſziplin)
318 u
'mas-ze-ner', # Damaszener
319 u
'Disc', # TODO: rund oder Diſc (aber eingedeutscht Disk)
321 # ss im Auslaut (vgl. `Fremdwörter und Eigennamen`_)::
323 # u'Gauss', # vgl. "Briefwechsel zwischen C.F. Gauss und H.C. Schumacher, herausg. von C.A.F. Peters"
324 # aber Boſſ, Busineſſ, Dreſſ
329 def is_complete(word
):
331 # Ersetze s an Stellen, wo es rund zu schreiben ist, durch ~ und teste auf
332 # verbliebene Vorkommen.
334 # Einzelfälle mit rundem S (substrings)::
336 for fall
in ausnahmen_rund_s
:
337 word
= word
.replace(fall
, fall
.replace('s', '~'))
339 # s steht am Wortende, auch in Zusammensetzungen (vor Haupttrennstellen)::
341 word
= re
.sub(ur
's($|[=<>])', ur
'~\1', word
)
343 # Einige ältere Quellen schreiben ss am Schluss von Fremdwörtern oder Namen
344 # (Gauss). Andere schreiben ſs oder ſſ. (Vgl. `Fremdwörter und Eigennamen
345 # mit Schluss-ß`_) Wir verwenden ſs (TODO oder?)::
347 #word = re.sub(ur'ss(=|$)', ur'~~\1', word)
349 # s steht am Silbenende (vor Nebentrennstellen), wenn kein p, t, z oder ſ
350 # folgt (in der traditionellen Schreibung wird st nicht getrennt)::
352 word
= re
.sub(ur
'ss?([·.\-][^ptzſ])', ur
'~\1', word
) # konservativ
354 # s steht auch vor Nebentrennstellen, wenn ph oder sch folgt::
356 word
= word
.replace(u
's-ph', u
'~-ph')
357 word
= word
.replace(u
's-ſch', u
'~-ſch')
359 # s steht nach Vorsilben (wie aus<) auch wenn s, p, t, oder z folgt::
361 word
= word
.replace(u
's<', u
'~<')
363 # s steht vor Trennstellen am Suffixanfang (wie >sen, >son),
364 # auch wenn s, p, t, oder z folgt::
366 word
= word
.replace(u
's>', u
'~>')
368 # s steht im Inlaut vor k, n, w::
370 word
= re
.sub(ur
's([knw])', ur
'~\1', word
)
372 # s steht in der Verbindung sst, die in der Schweiz und
373 # bei fehlendem ß (GROSS) für ßt steht::
375 # TODO: nur nach Zielaut und langem Vokal.
376 # word = word.replace(u'ſst', u'ſ~t')
377 # word = word.replace(u'ſs-t', u'ſ~-t')
379 # s steht als zweiter Buchstabe im ersetzten ß::
381 word
= word
.replace(u
'-ſs', u
'-ſ~') # traditionelle Orthographie
383 # und suche nach übrigen Vorkommen::
385 return 's' not in word
388 # Aufruf von der Kommandozeile
389 # ============================
393 if __name__
== '__main__':
397 usage
= u
'%prog [Optionen]\n' + __doc__
398 parser
= optparse
.OptionParser(usage
=usage
)
399 parser
.add_option('-i', '--infile', dest
='infile',
400 help=u
'Eingangsdatei ("-" oder "stdin" für '
401 u
'Standardeingabe), Vorgabe "../../wortliste"',
402 default
='../../wortliste')
403 parser
.add_option('-o', '--outfile', dest
='outfile',
404 help=u
'Ausgangsdatei, ("-" oder "stdout" für '
405 u
'Standardausgabe) Vorgabe: "words-<language>-Latf.txt"',
406 default
='') # wird später ausgefüllt
407 parser
.add_option('-l', '--language', dest
='language',
408 help=u
'Sprachvariante(n) (kommagetrennte Liste von '
409 u
'ISO Sprachtags), Vorgabe "de-1901"',
411 parser
.add_option('-d', '--drop-homonyms', action
="store_true",
413 help=u
'Bei mehrdeutigen Wörtern, die sich nur in '
414 'Lang-S-Schreibung unterscheiden, nimm nur das erste.')
416 (options
, args
) = parser
.parse_args()
418 # Angabe der Sprachvariante nach [BCP47]_ (Reformschreibung 'de' oder
419 # 'de-1996', Schweiz 'de-CH', ...)::
421 lang
= options
.language
425 if options
.infile
in ('-', 'stdin'):
426 wordfile
= (WordEntry(line
.rstrip().decode('utf-8'))
427 for line
in sys
.stdin
)
429 wordfile
= WordFile(options
.infile
)
432 if options
.outfile
in ('-', 'stdout'):
433 outstream
= sys
.stdout
435 outfile
= options
.outfile
or 'words-' + lang
.replace(',','-')
437 outstream
= file(outfile
, 'w')
443 # Konvertiere die Wörter der Trennliste und sortiere die Ergebnisse in
446 no_of_words
= 0 # Gesamtwortzahl der gewählten Sprache(n)
447 completed
= [] # Automatisch konvertiert
448 irreversible
= [] # Rückkonversion ungleich Original (Fehler)
449 unkategorisiert
= [] # Unterscheidung in Haupt- und Nebentrennstellen fehlt
450 offen
= [] # Der Algorithmus kann die Schreibweise (noch) nicht ermitteln
452 # Iteration über alle Zeilen der Wortliste::
454 for entry
in wordfile
:
456 word
= entry
.get(lang
) # Wort mit Trennstellen
457 if word
is None: # Wort existiert nicht in der Sprachvariante
464 # Wörter ohne Binnen-s müssen nicht konvertiert werden. Damit wird die
465 # Wortliste ungefähr um die Hälfte kürzer::
467 if 's' not in entry
[0][:-1]:
468 completed
.append(entry
[0])
471 # Regelbasierte s/ſ-Schreibung::
473 lang_s_word
= s_ersetzen(word
)
475 # Einsortieren nach Vollständigkeit der Ersetzungen::
477 entry
.set(lang_s_word
, lang
) # Rückschreiben von teilweisen Ersetzungen
479 if lang_s_word
.replace(u
'ſ', u
's') != word
:
480 entry
.comment
= lang_s_word
.replace(u
'ſ', u
's') + u
" != " + word
481 irreversible
.append(entry
)
484 if not is_complete(lang_s_word
):
485 if lang_s_word
.find(u
's·') != -1:
486 unkategorisiert
.append(entry
)
491 # Mehrdeutigkeiten [ſ/s] oder [s/ſ] auflösen::
493 lang_s_word
= join_word(lang_s_word
)
494 if u
'/' in lang_s_word
:
496 completed
.append(re
.sub(ur
'\[(.+)/.+\]', ur
'\1', lang_s_word
))
497 if not options
.drop_homonyms
:
498 completed
.append(re
.sub(ur
'\[.+/(.+)\]', ur
'\1', lang_s_word
))
500 completed
.append(lang_s_word
)
501 # completed.append(lang_s_word)
507 # Wortliste mit automatisch bestimmter S-Schreibung, ohne Trennstellen::
509 outstream
.write(u
'\n'.join(completed
).encode('utf8') + '\n')
516 sys
.stderr
.write("# Gesamtwortzahl %s %s\n" % (lang
, no_of_words
))
517 sys
.stderr
.write("# Automatisch konvertiert: %d\n" % len(completed
))
518 sys
.stderr
.write("# erkannte Konvertierungsfehler: %d\n"
520 for entry
in irreversible
:
521 sys
.stderr
.write(unicode(entry
).encode('utf8')+'\n')
522 sys
.stderr
.write("# Kategorisierung der Trennstellen fehlt: %d\n"
523 % len(unkategorisiert
))
524 for entry
in unkategorisiert
:
525 sys
.stderr
.write(unicode(entry
).encode('utf8')+'\n')
526 sys
.stderr
.write("# noch offen/unklar: %d\n" % len(offen
))
528 sys
.stderr
.write(unicode(entry
).encode('utf8')+'\n')
534 # Für gebrochene Schriften gibt es den `ISO Sprachtag`_
536 # :Latf: Latin (Fraktur variant)
538 # also "Lateinisches Alphabet, gebrochen". Dieser Tag wird and die
539 # Ausgabedateien angehängt (e.g. "de-1901-Latf", "de-1996-Latf").
541 # .. _ISO Sprachtag: http://www.unicode.org/iso15924/iso15924-codes.html
546 # Gesamtwortzahl (traditionelle Rechtschreibung): 427746
547 # Automatisch konvertiert: 427740
548 # Kategorisierung der Trennstellen fehlt: 0
551 # Die Mehrzahl der Wörter der Trennliste wurde nach den Regeln des Dudens in
552 # die Schreibung mit langem `S` (ſ) konvertiert (wobei ungefähr die Hälfte der
553 # Wörter kein kleines `s` enthält womit die Konversion trivial wird).
555 # Für eine beschränke Anzahl offener Fälle wurden Ausnahmeregeln und Ausnahmen
558 # Das Resultat muß noch auf nicht erfaßte Ausnahmen und Sonderfälle geprüft
559 # werden. Fehlentscheidungen sind nicht auszuschließen.
565 # Wörter mit identischer Schreibung ohne Lang-S
566 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
568 # Einige mehrdeutige Zusammensetzungen unterscheiden sich in der
569 # Lang-S-Schreibung, z.B.
571 # * Wach[s/ſ]tube: Wach-Stube / Wachs-Tube
572 # * Ga[s/ſ]traſſe: Gas-Trasse / Gast-Rasse
573 # * Schiff[ſ/s]tau: Schiffs-Tau / Schiff-Stau
575 # Im Normalfall schreibt s2long-s.py beide Varianten in die Ausgabedatei. Die
576 # Option --drop_homonyms kann verwendet werden, wenn dies nicht erwünscht ist.
581 # * Ersetztes SZ in reformierter Rechtschreibung, wenn ein Selbstlaut folgt
582 # (z.B. "Straſ-ſe" oder "Straſ-se).
584 # - Während in 1901-er Rechtschreibung die Trennung vor dem "ss" erfolgt
585 # (was Ersetzung mit "ſs" impliziert) wäre bei Trennung "wie normales
586 # Doppel-S" dann ein rundes S am Silbenanfang.
588 # Korrekte Fallunterscheidung geht nur bei Betrachtung der Nachbarfelder.
590 # * Tonarten (As-Dur oder Aſ-Dur)
592 # - Im Fraktur-Duden steht As *in Antiqua* mit rundem s, also
593 # keine Aussage zur Schreibung in Fraktur.
594 # - Im 1976-er [Duden]_ steht As ohne Unterstreichung des `s`,
595 # das wäre Lang-S, obgleich am Wortende!
600 # .. [Duden] `Der Große Duden` 16. Auflage, VEB Bibliographisches Institut
603 # Kennzeichnet im Stichwortteil rundes s durch Unterstreichen.
605 # .. [wikipedia] Langes s
606 # http://de.wikipedia.org/wiki/Langes_s
608 # .. [en.wiktionary.org]
609 # http://en.wiktionary.org/wiki/%C5%BF
612 # http://typefoundry.blogspot.com/2008/01/long-s.html
614 # .. [West06] Andrew West, `The rules for long s`, 2006
615 # http://babelstone.blogspot.com/2006/06/rules-for-long-s.html
617 # .. [BCP47] A. Phillips und M. Davis, (Editoren.),
618 # `Tags for Identifying Languages`, http://www.rfc-editor.org/rfc/bcp/bcp47.txt
622 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
623 # http://mirrors.ctan.org/language/hyphenation/dehyph-exptl/projektbeschreibung.pdf