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 `Spezialfälle Lang-S`_ gelistet::
67 for fall
in spezialfaelle_lang_s
:
68 word
= word
.replace(fall
.replace(u
'ſ', u
's'), fall
)
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)
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).
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".
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')
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
162 u
'echſ-ler',# Dechsler, Wechsler
163 u
'äckſ-ler',# Häcksler
166 # Insel (Rheininsler), zünseln (Maiszünsler)
170 # unsre, unsrige, ...
173 # Häusl, Lisl, bissl, Glasl, Rössl
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 ...)
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
197 # Im Deutschen werden im Fraktursatz nicht eingedeutschte Fremdwörter
198 # lateinischen und romanischen Ursprungs in Antiqua mit rund-s geschrieben.
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]_.
209 spezialfaelle_lang_s
.extend([
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
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
= [
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
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 # ============================
347 if __name__
== '__main__':
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"',
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
375 if options
.infile
in ('-', 'stdin'):
376 wordfile
= (WordEntry(line
.rstrip().decode('utf-8'))
377 for line
in sys
.stdin
)
379 wordfile
= WordFile(options
.infile
)
382 if options
.outfile
in ('-', 'stdout'):
383 outstream
= sys
.stdout
385 outfile
= options
.outfile
or 'words-' + lang
+ '-Latf.txt'
386 outstream
= file(outfile
, 'w')
392 # Konvertiere die Wörter der Trennliste und sortiere die Ergebnisse in
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
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])
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
)
433 if not is_complete(lang_s_word
):
434 if lang_s_word
.find(u
's·') != -1:
435 unkategorisiert
.append(entry
)
440 completed
.append(join_word(lang_s_word
))
441 # completed.append(lang_s_word)
447 # Wortliste mit automatisch bestimmter S-Schreibung, ohne Trennstellen::
449 outstream
.write(u
'\n'.join(completed
).encode('utf8') + '\n')
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
)
467 print unicode(entry
).encode('utf8')
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
485 # Gesamtwortzahl (traditionelle Rechtschreibung): 427746
486 # Automatisch konvertiert: 427740
487 # Kategorisierung der Trennstellen fehlt: 0
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
497 # Das Resultat muß noch auf nicht erfaßte Ausnahmen und Sonderfälle geprüft
498 # werden. Fehlentscheidungen sind nicht auszuschließen.
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.
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!
539 # .. [Duden] `Der Große Duden` 16. Auflage, VEB Bibliographisches Institut
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
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
561 # .. _Wortliste der deutschsprachigen Trennmustermannschaft:
562 # http://mirrors.ctan.org/language/hyphenation/dehyph-exptl/projektbeschreibung.pdf