3 # :Copyright: © 2018 Günter Milde.
4 # Released without warranty under the terms of the
5 # GNU General Public License (v. 2 or later)
12 # Funktionen, die einen Trennstil (oder einen Aspekt eines Trennstils)
13 # implementieren, z.B.
15 # >>> from stilfilter import syllabisch,morphemisch
16 # >>> print syllabisch(u"Pä-d<a-go-gik")
18 # >>> print morphemisch(u"Pä-d<a-go-gik")
21 # (siehe auch dokumentation/Trennstile.txt).
24 """Filter für Trennstile"""
29 # Python 2.7 mit den Standardbibliotheken::
36 # Alternativtrennungen in Fremdwörtern nach §112 und §113.
41 # Bei Konsonantenclustern mit l, n oder r wähle Trennung nach Herkunft.
43 # Regelwerk (1996) § 112:
44 # In Fremdwörtern können die Verbindungen aus Buchstaben für einen
45 # Konsonanten + l, n oder r entweder entsprechend § 110 getrennt werden,
46 # oder sie kommen ungetrennt auf die neue Zeile.
48 # >>> from stilfilter import fremdwortsilben
49 # >>> fremdwoerter = (u'no-b-le Zy-k-lus Ma-g-net Fe-b-ru-ar '
50 # ... u'Hy-d-rant Ar-th-ri-tis ge-r<i-a-t-risch '
51 # ... u'A·p-ri-ko-se')
52 # >>> for wort in fremdwoerter.split():
53 # ... print wort, '->', fremdwortsilben(wort)
57 # Fe-b-ru-ar -> Fe-bru-ar
58 # Hy-d-rant -> Hy-drant
59 # Ar-th-ri-tis -> Ar-thri-tis
60 # ge-r<i-a-t-risch -> ge-r<i-a-trisch
61 # A·p-ri-ko-se -> A·pri-ko-se
65 def fremdwortsilben(wort
):
66 """"fremdländische" Sprechsilben (no-ble, Hy-drant, Ma-gnet)."""
67 return re
.sub(u
'([-·][bcdfgkptv]|th)-(?=[lrn])', u
'\\1', wort
) # K86, K87
73 # Bei Konsonantenclustern mit l, n oder r wähle Trennung nach deutschen Regeln.
75 # >>> from stilfilter import standardsilben
76 # >>> for wort in fremdwoerter.split():
77 # ... print wort, '->', standardsilben(wort)
81 # Fe-b-ru-ar -> Feb-ru-ar
82 # Hy-d-rant -> Hyd-rant
83 # Ar-th-ri-tis -> Arth-ri-tis
84 # ge-r<i-a-t-risch -> ge-r<i-at-risch
85 # A·p-ri-ko-se -> Ap-ri-ko-se
89 def standardsilben(wort
):
90 """Standard-Sprechsilbentrennung (nob-le, Hyd-rant, Mag-net)."""
91 return re
.sub(u
'[-·]([bcdfgkptv]|th)-(?=[lrn])', u
'\\1-', wort
) # §112
95 # K86 Untrennbar sind in Fremdwörtern die Verbindungen von Verschluß- und
96 # Reibelauten mit l und r, ...
98 # >>> fremdwoerter = (u'Pu-b-li-kum flexi-b-ler Zy-k-lone Qua-d-rat '
99 # ... u'Spek-t-rum manö-v-rieren')
100 # >>> for wort in fremdwoerter.split():
101 # ... print wort, '->', fremdwortsilben(wort)
102 # Pu-b-li-kum -> Pu-bli-kum
103 # flexi-b-ler -> flexi-bler
104 # Zy-k-lone -> Zy-klone
105 # Qua-d-rat -> Qua-drat
106 # Spek-t-rum -> Spek-trum
107 # manö-v-rieren -> manö-vrieren
111 # >>> fremdwoerter = u'Di<s-t-rikt Ma-gis-t-rat la-kus-t-risch'
112 # >>> for wort in fremdwoerter.split():
113 # ... print wort, '->', fremdwortsilben(wort)
114 # Di<s-t-rikt -> Di<s-trikt
115 # Ma-gis-t-rat -> Ma-gis-trat
116 # la-kus-t-risch -> la-kus-trisch
118 # K 87 Untrennbar ist die Konsonantenverbindung "gn".
120 # >>> fremdwoerter = u'Ma-g-net Pro-g-nose Si-g-net'
121 # >>> for wort in fremdwoerter.split():
122 # ... print wort, '->', fremdwortsilben(wort)
123 # Ma-g-net -> Ma-gnet
124 # Pro-g-nose -> Pro-gnose
125 # Si-g-net -> Si-gnet
127 # Keine Übergeneralisierung:
129 # >>> woerter = u'Seg-ler bast-le Ad-ler'
130 # >>> for wort in woerter.split():
131 # ... print wort, '->', fremdwortsilben(wort)
136 # Mit Auszeichnung der "Randtrennstelle":
137 # >>> woerter = u'A·p-ri-kose i·g-no-rie-ren'
138 # >>> for wort in woerter.split():
139 # ... print wort, '->', fremdwortsilben(wort)
140 # A·p-ri-kose -> A·pri-kose
141 # i·g-no-rie-ren -> i·gno-rie-ren
146 # Mit regelsilben() werden (auch nicht als Alternativtrennung ausgezeichnete)
147 # Fremdwortsilbentrennungen erkannt und zu Regelsilben gewandelt:
149 # >>> from stilfilter import regelsilben
150 # >>> fremdwoerter = (u'no-ble Zy-klus Ma-gnet Fe-bru-ar '
151 # ... u'Hy-drant Ar-thri-tis ge-r<i-a-trisch '
152 # ... u'A·pri-ko-se')
153 # >>> for wort in fremdwoerter.split():
154 # ... print wort, '->', regelsilben(wort)
158 # Fe-bru-ar -> Feb-ru-ar
159 # Hy-drant -> Hyd-rant
160 # Ar-thri-tis -> Arth-ri-tis
161 # ge-r<i-a-trisch -> ge-r<i-at-risch
162 # A·pri-ko-se -> Ap-ri-ko-se
166 def regelsilben(wort
):
167 """Regelsilben auch ohne Auszeichnung."""
168 wort
= re
.sub(u
'[-·]([bcdfgkptv]|th)(?=[lr][aeiouäöü])', u
'\\1-', wort
)
169 wort
= re
.sub(u
'[-·]gn(?=[aeiouäöü])', u
'g-n', wort
)
173 """Regelsilben bei gn (Si-gnal -> Sig-nal)."""
174 return re
.sub(u
'[-·]gn(?=[aeiouäöü])', u
'g-n', wort
)
180 # Entferne Alternativtrennungen nach §113 (verblasste Morphologie).
182 # Regelwerk (1996) §113:
183 # Wörter, die sprachhistorisch oder von der Herkunftssprache her gesehen
184 # Zusammensetzungen oder Präfigierungen sind, aber nicht mehr als solche
185 # empfunden oder erkannt werden, kann man entweder nach § 108 oder nach
186 # § 109 bis § 112 trennen.
188 # >>> from stilfilter import morphemisch
189 # >>> blasse = (u'hi-n<auf he-r<an da-r<um Chry-s<an-the-me Hek-t<ar '
190 # ... u'He-li-ko<p-ter in-te-r<es-sant Li-n<oleum Pä-d<a-go-gik '
191 # ... u'ge-r<i-a-t-risch au-to<ch-ton')
192 # >>> for wort in blasse.split():
193 # ... print wort, '->', morphemisch(wort)
194 # hi-n<auf -> hin<auf
197 # Chry-s<an-the-me -> Chrys<an-the-me
198 # Hek-t<ar -> Hekt<ar
199 # He-li-ko<p-ter -> He-li-ko<pter
200 # in-te-r<es-sant -> in-ter<es-sant
201 # Li-n<oleum -> Lin<oleum
202 # Pä-d<a-go-gik -> Päd<ago-gik
203 # ge-r<i-a-t-risch -> ger<ia-t-risch
204 # au-to<ch-ton -> au-to<chton
207 # Ersetze, wenn zwischen Haupttrennstelle und Nebentrennstelle nur ein
209 # (Die Haupttrennstelle kann vor oder nach der Nebentrennstelle liegen.)
212 def morphemisch(wort
):
213 """Trennung nach Morphologie (hin<auf, Hekt<ar Päd<ago-ge)."""
214 wort
= re
.sub(u
'([<=]+[.]*(.|ch))[-.]+', u
'\\1', wort
)
215 wort
= re
.sub(u
'[-.]+(.[<=]+)', u
'\\1', wort
)
221 # >>> from stilfilter import syllabisch
222 # >>> for wort in blasse.split():
223 # ... print wort, '->', syllabisch(wort)
224 # hi-n<auf -> hi-nauf
227 # Chry-s<an-the-me -> Chry-san-the-me
228 # Hek-t<ar -> Hek-tar
229 # He-li-ko<p-ter -> He-li-kop-ter
230 # in-te-r<es-sant -> in-te-res-sant
231 # Li-n<oleum -> Li-noleum
232 # Pä-d<a-go-gik -> Pä-da-go-gik
233 # ge-r<i-a-t-risch -> ge-ri-a-t-risch
234 # au-to<ch-ton -> au-toch-ton
238 def syllabisch(wort
):
239 """Ignoriere "verblasste" Morphologie (hi-nauf, Hek-tar, Pä-da-go-ge)."""
240 wort
= re
.sub(u
'[<=]+[.]*((.|ch)[-.]+)', u
'\\1', wort
)
241 wort
= re
.sub(u
'([-.]+.)[<=]+[.]*', u
'\\1', wort
)
247 # K44 „ſ“ (langes s) steht in Fremdwörtern...
249 # >>> blasse = (u'tran<s-pirieren tran<s-zendent ab<s-tinent '
250 # ... u'Ab<s-zess Pro-s<odie')
251 # >>> for wort in blasse.split():
252 # ... print wort, '->', morphemisch(wort)
253 # tran<s-pirieren -> tran<spirieren
254 # tran<s-zendent -> tran<szendent
255 # ab<s-tinent -> ab<stinent
256 # Ab<s-zess -> Ab<szess
257 # Pro-s<odie -> Pros<odie
259 # Trennstellen können als ungünstig markiert sein:
261 # >>> blasse = (u'Bür-ger=in<.i-ti-a-ti-ve Pä-..d<e-..rast')
262 # >>> for wort in blasse.split():
263 # ... print wort, '->', morphemisch(wort)
264 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=in<.iti-a-ti-ve
265 # Pä-..d<e-..rast -> Päd<erast
266 # >>> for wort in blasse.split():
267 # ... print wort, '->', syllabisch(wort)
268 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=ini-ti-a-ti-ve
269 # Pä-..d<e-..rast -> Pä-..de-..rast
271 # Suffixe mit Vokalcluster nicht ändern:
273 # >>> morphemisch(u'Zy-klo>i-de')
277 # kombinierte Trennstile
278 # ----------------------
282 def etymologisch(wort
):
283 """Etymologische Trennungen auch bei verblasster Etymologie."""
284 return morphemisch(fremdwortsilben(wort
))
287 """Sprechsilbentrennungen bei verblasster Etymologie."""
288 return regelsilben(syllabisch(wort
))
294 # Unterdrücken/Zulassen von regelkonformen Trennungen je nach Anwendungsfall.
295 # (Siehe Trennstile in sprachauszug.py.)
300 # Füge Trennmöglichkeiten am Wortanfang und -ende zu, die nach §107 E2
301 # des Regelwerkes (vorher K79) verboten sind aber in Notentexten gebraucht
304 # >>> from stilfilter import notentext
305 # >>> print notentext(u'Abend')
307 # >>> print notentext(u'Ra-dio')
310 # Das gleiche gilt für Trennmöglichkeiten am Anfang/Ende von Teilwörtern:
312 # >>> print notentext(u'Eis=ano-ma-lie')
314 # >>> print notentext(u'Ra-dio<phon')
319 # >>> print notentext(u'Ai-chin-ger'), notentext(u'Ai-da')
321 # >>> print notentext(u'Ma-rie'), notentext(u'Li-nie')
323 # >>> print notentext(u'Ta-too'), notentext(u'Zoo<lo-gie')
325 # >>> print notentext(u'A-pnoe'), notentext(u'O-boe')
327 # >>> print notentext(u'Plaque'), notentext(u'treue')
329 # >>> print notentext(u'Fon-due=pfan-ne'), notentext(u'Aue')
330 # Fon-due=pfan-ne Au·e
331 # >>> print notentext(u'Ge-nie'), notentext(u'Iphi-ge-nie')
332 # Ge-nie I·phi-ge-nie
333 # >>> print notentext(u'Ago-nie'), notentext(u'Be-go-nie')
334 # A·go-nie Be-go-ni·e
335 # >>> print notentext(u'Kom-pa-nie'), notentext(u'Kas-ta-nie'), notentext(u'Ge-ra-nie')
336 # Kom-pa-nie Kas-ta-ni·e Ge-ra-ni·e
338 # ungelöst: Knie / Kni·e # pl.
342 """Zusätzliche Trennmöglichkeiten am Wortrand (Fei-er=a·bend, Zo·o<lo-ge)."""
344 # Führender Vokal, gefolgt von Silbenanfang
345 # (optionaler Konsonant (auch ch/ck/ph/rh/sch/sh/th) + Vokal)::
347 for match
in re
.finditer(
348 u
'(^|[<=])([aeiouäöü])((.|ch|ck|ph|sch|th)?[aeiouäöü])',
349 word
, flags
=re
.IGNORECASE
):
351 # Ausnahmen: Doppellaute, Diphtonge (außer A-i-da), Umlaute::
353 if (re
.search(u
'(aa|ae|ai|au|äu|ei|eu|oe|oo|ou|ue)',
354 match
.group(0), flags
=re
.IGNORECASE
)
355 and word
!= u
'Ai-da'):
360 word
= ''.join((word
[:match
.start()], match
.expand(u
'\\1\\2·\\3'),
361 notentext(word
[match
.end():])))
364 # zwei Vokale am Wortende::
366 for match
in re
.finditer(u
'([aeiouäöü])([aeiouäöü])([<>=]|$)', word
):
367 # if re.search(u'(oo)', match.group(0)):
368 # if 'ie' in match.group(0) and re.search(u'([a]-nie)', word, flags=re.IGNORECASE):
369 # sys.stderr.write(word+' '+match.group(0)+'\n')
371 # Ausnahme: Doppellaute, Diphtonge, Umlaute::
373 if (re
.search(u
'(aa|ae|ai|au|äu|ee|ei|eu|oe|oi|ou|ui)', match
.group(0))
375 # Ausnahmen der Ausnahme::
377 and not word
[:match
.end()].endswith(u
'waii') # ! Hawaii
378 and not word
[:match
.end()].endswith(u
'boe')): # ! Oboe
381 # Weitere Ausnahmen der Ausnahme::
383 # …oo außer zoo<, ...
384 if 'oo' in match
.group(0) and match
.group(0) != 'oo<':
387 # …ie außer "-(l)inie", Iphigenie, Kastanie, Geranie, Begonie
388 if ('ie' in match
.group(0)
389 and not word
[:match
.end()].endswith(u
'i-nie') # Linie,
390 and not word
[:match
.end()].endswith(u
'ta-nie') # Kastanie,
391 and not word
[:match
.end()].endswith(u
'ra-nie') # Geranie,
392 and not word
[:match
.end()].endswith(u
'e-go-nie') # Begonie != Agonie
393 and not word
== u
'Iphi-ge-nie'):
396 # …ue (Plaque, Re-vue, vogue) außer "-tue, -aue, ... -äue"
397 if 'ue' in match
.group(0) and re
.search(u
'[^aeät]ue([<>=]|$)',
398 word
[:match
.end()], flags
=re
.IGNORECASE
):
403 word
= ''.join((word
[:match
.start()], match
.expand(u
'\\1·\\2\\3'),
404 notentext(word
[match
.end():])))
410 # keine_einzelvokale()
411 # ~~~~~~~~~~~~~~~~~~~~
413 # Traditionell enthalten die TeX-Trennmuster keine Trennstellen deren Abstand
414 # zur Nachbartrennstelle einen Buchstaben beträgt. Dies ist eine „ästhetische“
415 # Entscheidung um „Flatterbuchstaben“ zu vermeiden
417 # Bei einvokalischen Silben im Wortinneren, nimm die zweite:
419 # >>> from stilfilter import keine_einzelvokale
420 # >>> einzelne = (u'The-a-ter me-ri-di-.o-nal')
421 # >>> for wort in einzelne.split():
422 # ... print wort, '->', keine_einzelvokale(wort)
423 # The-a-ter -> Thea-ter
424 # me-ri-di-.o-nal -> me-ri-dio-nal
426 # Allerdings nicht, wenn die zweite Trennstelle unterdrückt ist:
428 # >>> einzelne = (u'La-sal-le-a-.ner Athe-i.sten')
429 # >>> for wort in einzelne.split():
430 # ... print wort, '->', keine_einzelvokale(wort)
431 # La-sal-le-a-.ner -> La-sal-le-a-.ner
432 # Athe-i.sten -> Athe-i.sten
436 def keine_einzelvokale(wort
):
437 """Entferne Trennmarker vor Einzevokalsilben (Thea-ter)."""
438 wort
= re
.sub(u
'>([aeiouyäöü])-', u
'>\\1', wort
)
439 wort
= re
.sub(u
'-[.]*([aeiouyäöü])(?=[->][^.])', u
'\\1', wort
)
446 # Entferne die explizit als ungünstig gekennzeichneten Trennstellen:
448 # >>> from stilfilter import unguenstig
449 # >>> unguenstig(u'Text=il<..lu-stra-ti-.on')
450 # u'Text=illu-stra-tion'
451 # >>> unguenstig(u'Text=il<..lu-stra-ti-.on', level=2)
452 # u'Text=illu-stra-ti-on'
456 def unguenstig(wort
, level
=1):
457 """Entferne als ungünstig markierte Trennungen."""
458 marker
= r
'\.' * level
459 wort
= re
.sub(u
'[-=<>]+%s+'%marker
, u
'', wort
)
460 return wort
.replace(u
'.', u
'')
465 # Alias für normale Trennhäufigkeit::
467 standard
= unguenstig
473 """Zulassen aller Trennungen (auch ungünstige)."""
474 return unguenstig(wort
, level
=10)
478 # Aussortieren sehr ungünstiger (mit „..“ markierter) Trennstellen.
482 """Aussortieren sehr ungünstiger (mit „..“ markierter) Trennstellen."""
483 return unguenstig(wort
, level
=2)
488 # Standardunterdrückung + keine Einzelvokale::
491 """Entferne ungünstige Trennungen und Einvokalsilben."""
492 return keine_einzelvokale(unguenstig(wort
))
497 # Nach alter Buchdruckerregel werden zwei Buchstaben am Wortanfang oder -ende
500 # >>> from stilfilter import buchdruckerregel
501 # >>> buchdruckerregel(u'Il<lu-stra-tion')
503 # >>> buchdruckerregel(u'An<ga-be')
505 # >>> buchdruckerregel(u'dar<an=mach-te')
507 # >>> buchdruckerregel(u'ab<zu<be<ru-fen')
510 # TODO: Günstigste Trennung bei Zusammensetzungen mit ..<..< ::
512 # >>> buchdruckerregel(u'ab<be<ru-fen')
514 # >>> buchdruckerregel(u'zu<zu<be<rei-ten')
516 # >>> print buchdruckerregel(u'un<zu<ver<läs-sig')
521 def buchdruckerregel(wort
):
522 """Experimentell: Entferne Trennungen im Abstand 2 vom Wortrand."""
523 wort
= re
.sub(u
'^(..)[-<>.]+', u
'\\1', wort
)
524 wort
= re
.sub(u
'[-<>.]+(..)$', u
'\\1', wort
)
525 wort
= re
.sub(u
'[-<>.]*(..=+..)[-<>.]*', u
'\\1', wort
)
526 wort
= re
.sub(u
'[-<>.]*(..<..)[-<>.]*', u
'\\1', wort
)