[Trennmuster] Entscheidungsmuster für Binnen- und Schluss-S in Frakturschriften

Guenter Milde milde at users.sf.net
Mi Feb 1 23:27:49 CET 2012


Hallo Stephan,

On 30.01.12, Stephan Hennig wrote:
> Am 26.01.2012 00:04, schrieb Guenter Milde:

> > * Erstellen eines freien Wörterbuchs für Rechtschreibprüfprogramme (aspell,
> >   ...).

> Selbstverständlich kann unsere Liste dafür verwendet werden.  Aber es
> gibt auch schon einiges,
> <URL:http://projekte.dante.de/Trennmuster/Korpora#Frei>

Ich meinte die generierte Wortliste mit Lang-ſ Schreibung.
Ist unter den Korpora so etwas schon dabei?

> > * Automatische s-Konversion von Dokumenten anhand der Wortliste.
> > 
> > * Übernahme der Regeln in ein LuaTeX Paket.

> Taco und Hans haben (schon vor einiger Zeit) den Vorschlag, die
> Rund-s/Lang-s-Unterscheidung in LuaTeX durch entsprechende Muster zu
> ermöglichen, positiv aufgenommen.  Den Trennalgorithmus wiederum wollen
> sie so erweitern, dass neben musterbasierter Trennung auch eine
> regelbasierte Trennung möglich wird (die Regeln wären dann vermutlich in
> LPEG zu formulieren).   Meinst du, die Unterscheidung Rund-s/Lang-s
> ließe sich rein formal durch Regeln erschlagen?

Wenn ein Trennalgorithmus existiert, der ein Wort mit eingetragenen Haupt-
und Nebentrennstellen zurückliefert, kann die S-Schreibung mit einfachen
Regeln bestimmt werden. Die Zahl der Ausnahmen (wie Pilſner) ist beschränkt,
Algorithmus und Ausnahmelisten hätten Platz in einem kleinen Lua-Paket.

Schwieriger wird es, wenn der Trennalgorithmus nach "guten" und "weniger
guten" Trennstellen unterscheidet, weil diese Unterscheidung deutlich
weniger mit den Regeln zur S-Schreibung korreliert.


> > * Wörter mit ſ am Wort oder Silbenende:

...

> >   - sz (und st, sp in Reformschreibung): aber rundes s am Wortende

> Was meinst du damit?

Die Verbindungen ss, sp, st und sz werden zu ſſ, ſp, ſt und ſz auch
wenn sie durch eine Nebentrennstelle getrennt sind.

s bleibt rund vor einer Haupttrennstelle, im Auslaut und  nach Vorsilben wie
(r)aus-,  dis-, konfis-, ple-bis- auch wenn p, s, t oder z folgt:

  Weſ-pe, aber dis=putieren
  
  Allgemeinwiſ-ſen aber Abfahrts=ſki

  Faſ-zination aber Abfahrts-zeichen

  abbürſ-ten (Reformtrennung) aber Abbildungs=technik

In einem Anfall von Rechtschreibschwäche war ich davon ausgegangen, daß in
traditioneller Rechtschreibung auch sp ungetrennt bleibt - damit habe ich mir
eine ganze Menge Fehlschreibungen (wie "Wes-pe" mit rund-s) eingehandelt.

Ein weiterer Fehler war die Behandlung von Doppel-s: da Vorsilben
gegenwärtig mit "-" abgetrennt sind, führt die Wandlung "s-s" -> "ſ-ſ" zu
Fehlern wie "Auſ-ſchuß".

Nach Beheben dieser Fehlerquellen ist die Bilanz des automatischen
Konversion erst einmal deutlich weniger beeindruckend. Die Zahl der
offenen Fälle steigt von 8 auf fast 10000:

  Gesamtwortzahl (traditionelle Rechtschreibung) 417629
  Automatisch konvertiert 401591
  Wichtung der Trennstellen fehlt 6426
  noch offen 9612


Die korrigierte Version des Konversionsskripts hängt an.

Günter

#!/usr/bin/env python
# -*- coding: utf8 -*-
# :Copyright: © 2012 Günter Milde.
# :Licence:   This work may be distributed and/or modified under
#             the conditions of the `LaTeX Project Public License`,
#             either version 1.3 of this license or (at your option)
#             any later  version.
# :Version:   0.1 (2012-01-25)

# ===================================================================
# s2long-s.py: Langes oder rundes S: Entscheidung nach Silbentrennung
# ===================================================================
# 
# ::

"""Bestimme die Schreibung mit Rund- oder Lang-s
aus der `Wortliste der deutschsprachigen Trennmustermannschaft`.
"""

# Importe
# =======
# 
# Funktionen und Klassen für reguläre Ausdrücke::

import re


# Ausgangsbasis
# =============
# 
# Die freie `Wortliste der deutschsprachigen Trennmustermannschaft`_
# ("Lehmansche Liste")
# 
# ::

wordfile = file('wortliste') # volle Liste (≅ 400 000 Wörter
# wordfile = file('wortliste-binnen-s') # vorsortierte Liste (≅ 200 000 Wörter)

# Trennzeichen
# ------------
# 
# Die Trennzeichen der Wortliste sind
# 
# == ================================================================
# \· ungewichtete Trennstellen (solche, wo noch niemand sich um die
#    Gewichtung gekümmert hat)
# .  unerwünschte Trennstellen (sinnverwirrend), z.B. Ur-in.stinkt
# =  Haupttrennstellen
# \- Nebentrennstellen
# _  ungünstige Nebentrennstellen, z.B. Pol=ge_bie-te
# == ================================================================
# 
# 
# Funktionen
# ==========
# 
# ſ-Regeln
# --------
# 
# Siehe [wikipedia]_ und ("DDR"-) [Duden]_ (Regeln K 44,45)::

def s_ersetzen(word):

# ſ steht im Silbenanlaut::

    word = re.sub(ur'^s', ur'ſ', word)
    word = re.sub(ur'([·\-=.])s', ur'\1ſ', word)

# ſ steht im Inlaut als stimmhaftes s zwischen Vokalen
# (gilt auch für ungetrenntes ss zwischen Selbstlauten, z.B. Hausse, Baisse)::

    word = re.sub(ur'([AEIOUYÄÖÜaeiouäöü])s([aeiouyäöü])', ur'\1ſ\2', word)
    word = re.sub(ur'([AEIOUYÄÖÜaeiouäöü])ss([aeiouyäöü])', ur'\1ſſ\2', word)

# ſ steht in den Verbindungen sp, st, sch und in Digraphen::

    word = word.replace(u'st', u'ſt')
    word = word.replace(u'sp', u'ſp')
    word = word.replace(u'sch', u'ſch')

    word = word.replace(u'ps', u'pſ')  # ψ
    word = word.replace(u'Ps', u'Pſ')  # Ψ


# ſ vor Trennstellen
# ~~~~~~~~~~~~~~~~~~
# 
# Die Verbindungen ss, sp, st und sz werden zu ſſ, ſp, ſt und ſz auch
# wenn sie durch eine Nebentrennstelle (Trennung innerhalb eines
# Wortbestandteiles) getrennt sind.
# 
# s bleibt rund vor einer Haupttrennstelle (Trennung an der Grenze zweier
# Wortbestandteile (Vorsilbe=Stamm, Bestimmungswort=Grundwort), im Auslaut und
# nach Vorsilben wie (r)aus-, dis-, konfis-, ple-bis- (zur Zeit mit
# Nebentrennstelle markiert)::

    # word = word.replace(u's-ſ', u'ſ-ſ')
    word = word.replace(u's.ſ', u'ſ.ſ')
    # word = word.replace(u's-p', u'ſ-p')
    word = word.replace(u's.p', u'ſ.p')
    # word = word.replace(u's-t', u'ſ-t') # Reformschreibung
    word = word.replace(u's.t', u'ſ.t')

    # für sz/ſz wurden Spezialregeln erstellt, die Vorkommnisse
    # in der Wortliste erfassen

# TODO: Vorsilben mit Haupttrennstelle markieren oder Ausnahmeliste erstellen.
# 
# Spezialfälle
# ~~~~~~~~~~~~
# 
# ſz trotz Trennzeichen::

    word = re.sub(ur'es[-·]zen', ur'eſ-zen', word) # Adoleszenz, ...
    word = re.sub(ur's[-·]zil', ur'ſ-zil', word) # Os-zil-la-ti-on
    word = re.sub(ur's[-·]zi-n', ur'ſ-zi-n', word) # faszinieren, ...

# ſ wird geschrieben, wenn der S-Laut nur scheinbar im Auslaut steht weil ein
# folgendes unbetontes e ausfällt::

   # Basel, Beisel, Pilsen, drechseln, wechseln, häckseln
    word = word.replace(u'Bas-ler', u'Baſ-ler')
    word = word.replace(u'Pils·ner', u'Pilſ-ner')
    word = word.replace(u'Pils-ner', u'Pilſ-ner')
    word = word.replace(u'echs-ler', u'echſ-ler') # Dechsler, Wechsler
    word = word.replace(u'äcks-ler', u'äckſ-ler') # Häcksler

    # Insel (Rheininsler), zünseln (Maiszünsler)
    word = word.replace(u'ins·ler', u'inſ-ler')
    word = word.replace(u'ins-ler', u'inſ-ler')
    word = word.replace(u'üns·ler', u'ünſ-ler')
    word = word.replace(u'üns-ler', u'ünſ-ler')

    # unsre, unsrige, ...
    word = word.replace(u'uns-r', u'unſ-r')

    # Häusl, Lisl, bissl, Glasl, Rössl
    word = word.replace(u'sl', u'ſl')
    word = word.replace(u'ssl', u'ſſl')
    # word = re.sub(ur'sl$', ur'ſl', word)
    # word = re.sub(ur'ssl$', ur'ſſl', word)


# Fremdwörter
# ~~~~~~~~~~~
# 
# Schreibung nach Regeln der Herkunftssprache (im Fraktursatz werden nicht
# eingedeutschte Wörter in Antiqua mit rund-s geschrieben).
# 
# English: 
#   The long, medial, or descending ess, as distinct from the short or
#   terminal ess. In Roman script, the long ess was used everywhere except at
#   the end of words, where the short ess was used, and frequently in what is
#   now the digraph <ss>, which was often written <ſs> rather than <ſſ>
#   [en.wiktionary.org]_.  
# 
# ::

    word = word.replace(u'sh', u'ſh')  # (englisch)
    word = word.replace(u'sc', u'ſc')  # (englisch)
    word = word.replace(u'Csar', u'Cſar') # Cs -> Tsch (ungarisch)
    word = word.replace(u'sz', u'ſz')  # polnisch, ungarisch
    word = re.sub(ur'([Tt])s([aeiouy])', ur'\1ſ\2', word) # ts (chinesisch)


    return word

# s-Regeln
# --------
# 
# Test auf verbliebene Unklarheiten
# 
# Wenn ein Wort "s" nur an Stellen enthält wo die Regeln rundes S vorsehen,
# ist die automatische Konversion abgeschlossen. ::

def is_complete(word):
    
# Ersetze s an Stellen wo es rund zu schreiben ist durch ~ und teste auf
# verbliebene Vorkommen:
# 
# Einzelfälle mit rundem S (substrings)::

    spezialfaelle = [u'Dresd·ne', # Dresd·ner/Dresd·ner·in
                    ]

    for fall in spezialfaelle:
        word = word.replace(fall, u'~')

# s steht am Wortende, auch in Zusammensetzungen (vor Haupttrennstellen).
# Dasselbe gilt für Doppel-s (aus Fremdwörtern) in der traditionellen
# Rechtschreibung::

    word = re.sub(ur's(=|$)', ur'~\1', word)
    word = re.sub(ur'ss(=|$)', ur'~~\1', word)

# s steht am Silbenende (nach Nebentrennstellen), wenn kein p, t, z oder ſ
# folgt (in der traditionellen Schreibung, wird st nicht getrennt)::

    # word = re.sub(ur'ss?([·.\-][^ptzſ])', ur'~\1', word) # konservativ
    word = re.sub(ur'ss?([·.\-][^pzſ])', ur'~\1', word)   # traditionell

# s steht nach den Vorsilben (r)aus-, dis-, konfis-,
# ple-bis-, (ergänzen)::

    word = re.sub(ur'(^|[·.\-=])[Rr]?[Aa]us([·.\-=])', ur'\1~\2', word)
    word = re.sub(ur'(^|[·.\-=])[Dd]is([·.\-=])', ur'\1~\2', word)
    word = word.replace(u'on-fis-zie', u'on-fi~-zie')
    word = word.replace(u'le-bis-z', u'le-bi~-z')

# s steht im Inlaut vor k, n, w::

    word = re.sub(ur's([knw])', ur'~\1', word)

# und suche nach übrigen Vorkommen::

    return 's' not in word


# Trennzeichen entfernen
# ----------------------
# 
# ::

def join_word(word):

# Spezielle Trennungen für die traditionelle Rechtschreibung::

    if '{' in word:
            word = word.replace(u'{ck/k·k}',  u'ck')
            word = word.replace(u'{ff/ff·f}', u'ff')
            word = word.replace(u'{ll/ll·l}', u'll')
            word = word.replace(u'{mm/mm·m}', u'mm')
            word = word.replace(u'{nn/nn·n}', u'nn')
            word = word.replace(u'{rr/rr·r}', u'rr')

# Trennstellen in doppeldeutigen Wörtern::

    if '[' in word:
        word = word.replace(u'[cker·/ck·er.]',  u'cker')
        word = word.replace(u'[·cker·/ck·er.]', u'cker')
        word = word.replace(u'[ll·/ll]',        u'll')
        word = word.replace(u'[·ker·/k·er.]',   u'ker')
        word = word.replace(u'[·ſt/ſt·]',       u'ſt')

    # Verbliebene komplexe Trennstellen::
    # if ('[' in word) or ('{' in word):
    #     print word.encode('utf8')

# Einfache Trennzeichen::

    table = {ord(u'·'): None,
             ord('='): None,
             ord('-'): None,
             ord('_'): None,
             ord('.'): None,
            }
    return word.translate(table)


# Kategorien
# ==========
# 
# Der Algorithmus sortiert die Wörter der Trennliste in die folgenden
# Kategorien:
# 
# 
# Menge aller Wörter der Liste (ohne Trennmuster)::

words = set()

# Automatisch konvertierte Wörter (ohne Trennmuster)::

completed = []

# Offene Fälle mit Trennmuster-Feldern wie im Original:
# 
# Zur automatischen Konvertierung fehlt die Unterscheidung in Haupt- und
# Nebentrennstellen (Wichtung)::

ungewichtet = []

# Der Algorithmus kann die Schreibweise (noch) nicht ermitteln
# (mit teilweisen Ersetzungen)::

offen = []

# (Noch) nicht behandelt::

reformschreibung = [] # Schreibweisen nach neuer Rechtschreibung
sz_ersetzung = []     # Schreibweisen mit ß-Ersetzungen


# Die originale Wortliste ist mit 'latin-1' kodiert aber lokal nach
# utf8 konvertiert. Versuche zunächst ::

source_encoding = 'utf8'

# und stelle bei Fehlern auf 'latin-1' um.
# 
# 
# Hauptschleife
# =============
# 
# Iteration über alle Zeilen der Wortliste::

for line in wordfile:

# Dekodieren und Entfernen der Zeilenendezeichen
# ----------------------------------------------
#  
# ::

    try:
        line = line.rstrip().decode(source_encoding)
    except UnicodeError:
        source_encoding = 'latin1'
        line = line.rstrip().decode(source_encoding)

# Kommentare ignorieren (werden mit # eingeleitet)::

    line = line.split(u'#')[0]

# Zerlegen in Felder
# ------------------
# 
# 1. Wort ungetrennt
# 2. Wort mit Trennungen, falls für alle Varianten identisch,
#    anderenfalls leer
# 3. falls Feld 2 leer, Trennung nach traditioneller Rechtschreibung
# 4. falls Feld 2 leer, Trennung nach reformierter Rechtschreibung (2006)
# 5. falls Feld 2 leer, Trennung für Wortform, die entweder in
#    der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird
#    und für traditionelle und reformierte Rechtschreibung identisch ist
# 6. falls Feld 5 leer, Trennung für Wortform, die entweder in
#    der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
#    traditionelle Rechtschreibung
# 7. falls Feld 5 leer, Trennung für Wortform, die entweder in
#    der Schweiz oder mit Großbuchstaben oder Kapitälchen benutzt wird,
#    reformierte Rechtschreibung (2006)
# 8. falls Feld 5 leer, Trennung nach (deutsch)schweizerischer
#    Rechtschreibung; insbesondere Wörter mit "sss" gefolgt von
#    einem Vokal, die wie andere Dreifachkonsonanten gehandhabt wurden
#    (also anders, als der Duden früher vorgeschrieben hat), z.B.
#    "süssauer"
# 
# ::

    fields = line.split(';')

# Auswahl der Schreibweise
# ------------------------
# 
# Zur Zeit werden nur Wörter in traditioneller Schreibweise behandelt::

    i = 1                  # 2. Feld: allgemeingültige Trennung
    if fields[i] == '-2-': # keine allgemeingültige Trennung
        i = 2              # 2. Feld: traditionelle Trennung
    if fields[i] == '-3-': # Wort existiert nicht in traditioneller Schreibung
        if fields[3] == '-4-': # Wort existiert nicht in Reformschreibung
            sz_ersetzung.append(fields)
        else:
            reformschreibung.append(fields)
        continue
    word = fields[i]  # Wort mit Trennstellen

# Menge aller Wörter der gewählten Schreibweise (ohne Trennstellen)::

    words.add(fields[0])

# Vorsortieren
# ------------
# 
# Wörter ohne Binnen-s müssen nicht konvertiert werden. Damit wird die
# Wortliste ungefähr um die Hälfte kürzer::

    if 's' not in fields[0][:-1]:
        completed.append(fields[0])
        continue

    # # nur vorsortieren:
    # offen.append(fields)
    # continue

# Regelbasierte s-ſ-Schreibung::

    word = s_ersetzen(word)

# Einsortieren nach Vollständigkeit der Ersetzungen::

    if is_complete(word):
        completed.append(join_word(word))
        continue

    fields[i] = word # Rückschreiben von teilweisen Ersetzungen

    if word.find(u's·') != -1:
        ungewichtet.append(fields)
        continue

    offen.append(fields)

# --------------------------------------------------------------------------
# 
# Ende der Hauptschleife
# 
# Feedback
# ========
# 
# ::

print "Gesamtwortzahl (traditionelle Rechtschreibung)", len(words)
print "Automatisch konvertiert", len(completed)
print "nur in neuer Rechtschreibung", len(reformschreibung)
print "Schweizer und Großschreibvarianten", len(sz_ersetzung)
print "Wichtung der Trennstellen fehlt", len(ungewichtet)
print "noch offen", len(offen)

print "\nkonvertiert+offen", len(completed) + len(ungewichtet) + len(offen)


# Diskussion
# ==========
# 
# Statistik
# ---------
# 
# | Gesamtwortzahl (traditionelle Rechtschreibung) 417629
# | Automatisch konvertiert 401591
# | nur in neuer Rechtschreibung 4653
# | Schweizer und Großschreibvarianten 8745
# | Wichtung der Trennstellen fehlt 6426
# | noch offen 9612
# 
# 
# Die große Mehrzahl der Wörter der Trennliste wurde nach den Regeln des
# Dudens in die Schreibung mit langem s (ſ) konvertiert.
# 
# Der größte Teil der noch offenen Fälle kann durch Unterscheidung in Haupt-
# und Nebentrennstellen (z.B. mit dem SiSiSi_-Algorithmus) gelöst werden.
# 
# Für eine beschränke Anzahl offener Fälle wurden Ausnahmeregeln und Ausnahmen
# implementiert.
# 
# Das Resultat muß noch auf nicht erfaßte Ausnahmen und Sonderfälle geprüft
# werden. Fehlentscheidungen sind nicht auszuschließen.
# 
# 
# Problemfälle
# ------------
# 
# Wörter mit ſ am Wort oder Silbenende:
# 
# * sp, ss, st und sz wird zu ſp, ſſ, ſt und ſz, auch wenn das ſ vor einer
#   Nebentrennstelle steht (z.B. Weſ-pe, eſ-ſen, abbürſ-ten (Reformtrennung)
#   und Faſ-zination)
#   
#   **Aber** rundes s am Wortende und nach Vorsilben, z.B.
#   dis=putieren, Aus=ſage, aus-tragen, aus-zeichnen.
#   
#   Wie sollen Trennungen nach Vorsilben (schwache Hautptrennstellen) markiert
#   werden?  
# 
# Wörter mit identischer Schreibung ohne lang-s:
# 
# * Wach[s/ſ]tube, As/Aſ, ...?
# 
# Ausgabe
# =======
# 
# Wortliste mit automatisch bestimmter S-Schreibung, ohne Trennstellen::

completed.append(u'') # für das letzte Zeilenendezeichen
completed_file = file('wortliste-lang-s', 'w')
completed_file.write(u'\n'.join(completed).encode('utf8'))

# Wortlisten mit noch offenen Fällen::

for todo in ['ungewichtet', 'offen']:
    todo_file = file('wortliste-lang-s-'+todo, 'w')
    todo = globals()[todo] # get variable from string
    todo = [u';'.join(fields) for fields in todo]
    todo.append(u'') # für das letzte Zeilenendezeichen
    todo_file.write(u'\n'.join(todo).encode('utf8'))



# Quellen
# =======
# 
# 
# 
# .. [Duden] `Der Große Duden` 16. Auflage, VEB Bibliographisches Institut
#    Leipzig, 1971
# 
#    Kennzeichnet im Stichwortteil rundes s durch Unterstreichen.
# 
# .. [wikipedia]
#    http://de.wikipedia.org/wiki/Langes_s
# 
# .. [en.wiktionary.org]
#    http://en.wiktionary.org/wiki/%C5%BF
# 
# .. _SiSiSi: https://www.ads.tuwien.ac.at/research/SiSiSi.html
# 
# .. _Wortliste der deutschsprachigen Trennmustermannschaft:
#    http://mirrors.ctan.org/language/hyphenation/dehyph-exptl/projektbeschreibung.pdf
# 
# .. Fragen, Spezialfälle, Beispiele
# 
#  http://www.e-welt.net/bfds_2003/bund/fragen/13_Langes%20oder%20rundes%20S.pdf
#  http://www.e-welt.net/bfds_2003/bund/fragen/12_hs%20und%20scharfes%20s_1.pdf



Mehr Informationen über die Mailingliste Trennmuster