Python-Skripte: kleine Korrekturen.
[wortliste.git] / skripte / python / edit_tools / stilfilter.py
blob345112c4809f23fadd83b23bc62b8d97f2e29ffe
1 #!/usr/bin/env python3
2 # -*- coding: utf8 -*-
3 # :Copyright: © 2018 Günter Milde.
4 # Released without warranty under the terms of the
5 # GNU General Public License (v. 2 or later)
6 # :Id: $Id: $
9 # Stilfilter
10 # **********
12 # Funktionen, die einen Trennstil (oder einen Aspekt eines Trennstils)
13 # implementieren, z.B.
15 # >>> from stilfilter import syllabisch, morphemisch
16 # >>> print(syllabisch("Pä-d<a-go-gik"))
17 # Pä-da-go-gik
18 # >>> print(morphemisch("Pä-d<a-go-gik"))
19 # Päd<a·go-gik
21 # .. contents::
23 # ::
25 """Filter für Trennstile
27 Siehe dokumentation/Trennstile.txt.
28 """
30 # Abhängigkeiten
31 # ==============
32 # ::
34 import re, sys
36 # Wahltrennungen
37 # --------------
39 # Alternativtrennungen in Fremdwörtern nach §112 und §113.
41 # _waehle_fremdwortsilben()
42 # ~~~~~~~~~~~~~~~~~~~~~~~~~
44 # Bei Konsonantenclustern mit l, n oder r wähle Trennung nach Herkunft.
46 # Regelwerk (1996) § 112:
47 # In Fremdwörtern können die Verbindungen aus Buchstaben für einen
48 # Konsonanten + l, n oder r entweder entsprechend § 110 getrennt werden,
49 # oder sie kommen ungetrennt auf die neue Zeile.
51 # >>> from stilfilter import _waehle_fremdwortsilben
52 # >>> fremdwoerter = ('no-b-le Zy-k-lus Ma-g-net Fe-b-ru-ar '
53 # ... 'Hy-d-rant Ar-th-ri-tis ge-r<i-a-t-risch '
54 # ... 'A·p-ri-ko-se Eth-ni-en')
55 # >>> for wort in fremdwoerter.split():
56 # ... print(wort, '->', _waehle_fremdwortsilben(wort))
57 # no-b-le -> no-ble
58 # Zy-k-lus -> Zy-klus
59 # Ma-g-net -> Ma-gnet
60 # Fe-b-ru-ar -> Fe-bru-ar
61 # Hy-d-rant -> Hy-drant
62 # Ar-th-ri-tis -> Ar-thri-tis
63 # ge-r<i-a-t-risch -> ge-r<i-a-trisch
64 # A·p-ri-ko-se -> A·pri-ko-se
65 # Eth-ni-en -> Eth-ni-en
67 # ::
69 def _waehle_fremdwortsilben(wort):
70 """"fremdländische" Sprechsilben (no-ble, Hy-drant, Ma-gnet)."""
71 return re.sub('([-·])([bcdfgkptv]|th)-(?=[lrn])', '\\1\\2', wort) # K86, K87
74 # _waehle_standardsilben()
75 # ~~~~~~~~~~~~~~~~~~~~~~~~
77 # Bei Konsonantenclustern mit l, n oder r wähle Trennung nach deutschen Regeln.
79 # >>> from stilfilter import _waehle_standardsilben
80 # >>> for wort in fremdwoerter.split():
81 # ... print(wort, '->', _waehle_standardsilben(wort))
82 # no-b-le -> nob-le
83 # Zy-k-lus -> Zyk-lus
84 # Ma-g-net -> Mag-net
85 # Fe-b-ru-ar -> Feb-ru-ar
86 # Hy-d-rant -> Hyd-rant
87 # Ar-th-ri-tis -> Arth-ri-tis
88 # ge-r<i-a-t-risch -> ge-r<i-at-risch
89 # A·p-ri-ko-se -> Ap-ri-ko-se
90 # Eth-ni-en -> Eth-ni-en
92 # ::
94 def _waehle_standardsilben(wort):
95 """Standard-Sprechsilbentrennung (nob-le, Hyd-rant, Mag-net)."""
96 return re.sub('[-·]([bcdfgkptv]|th)-(?=[lrn])', '\\1-', wort) # §112
98 # Tests:
100 # K86 Untrennbar sind in Fremdwörtern die Verbindungen von Verschluß- und
101 # Reibelauten mit l und r, ...
103 # >>> fremdwoerter = ('Pu-b-li-kum flexi-b-ler Zy-k-lone Qua-d-rat '
104 # ... 'Spek-t-rum manö-v-rieren')
105 # >>> for wort in fremdwoerter.split():
106 # ... print(wort, '->', _waehle_fremdwortsilben(wort))
107 # Pu-b-li-kum -> Pu-bli-kum
108 # flexi-b-ler -> flexi-bler
109 # Zy-k-lone -> Zy-klone
110 # Qua-d-rat -> Qua-drat
111 # Spek-t-rum -> Spek-trum
112 # manö-v-rieren -> manö-vrieren
114 # "s-t-r" -> "s-tr"
116 # >>> fremdwoerter = 'Di<s-t-rikt Ma-gis-t-rat la-kus-t-risch'
117 # >>> for wort in fremdwoerter.split():
118 # ... print(wort, '->', _waehle_fremdwortsilben(wort))
119 # Di<s-t-rikt -> Di<s-trikt
120 # Ma-gis-t-rat -> Ma-gis-trat
121 # la-kus-t-risch -> la-kus-trisch
123 # K 87 Untrennbar ist die Konsonantenverbindung "gn".
125 # >>> fremdwoerter = 'Ma-g-net Pro-g-nose Si-g-net'
126 # >>> for wort in fremdwoerter.split():
127 # ... print(wort, '->', _waehle_fremdwortsilben(wort))
128 # Ma-g-net -> Ma-gnet
129 # Pro-g-nose -> Pro-gnose
130 # Si-g-net -> Si-gnet
132 # Keine Übergeneralisierung:
134 # >>> woerter = 'Seg-ler bast-le Ad-ler'
135 # >>> for wort in woerter.split():
136 # ... print(wort, '->', _waehle_fremdwortsilben(wort))
137 # Seg-ler -> Seg-ler
138 # bast-le -> bast-le
139 # Ad-ler -> Ad-ler
141 # Mit Auszeichnung der "Randtrennstelle":
142 # >>> woerter = 'A·p-ri-kose i·g-no-rie-ren'
143 # >>> for wort in woerter.split():
144 # ... print(wort, '->', _waehle_fremdwortsilben(wort))
145 # A·p-ri-kose -> A·pri-kose
146 # i·g-no-rie-ren -> i·gno-rie-ren
148 # regelsilben()
149 # ~~~~~~~~~~~~~
151 # Mit regelsilben() werden alle Fremdwortsilbentrennungen nach §112 erkannt
152 # und zu Regelsilben gewandelt:
154 # >>> from stilfilter import regelsilben
155 # >>> fremdwoerter = ('no-ble Zy-klus Ma-gnet Fe-bru-ar '
156 # ... 'Hy-drant Ar-thri-tis ge-r<i-a-trisch '
157 # ... 'A·pri-ko-se')
158 # >>> for wort in fremdwoerter.split():
159 # ... print(wort, '->', regelsilben(wort))
160 # no-ble -> nob-le
161 # Zy-klus -> Zyk-lus
162 # Ma-gnet -> Mag-net
163 # Fe-bru-ar -> Feb-ru-ar
164 # Hy-drant -> Hyd-rant
165 # Ar-thri-tis -> Arth-ri-tis
166 # ge-r<i-a-trisch -> ge-r<i-at-risch
167 # A·pri-ko-se -> Ap-ri-ko-se
169 # ::
171 def regelsilben(wort):
172 """Regel-Sprechsilbentrennung (nob-le, Hyd-rant, Mag-net)."""
173 wort = re.sub('[-·]([bcdfgkptv]|th)(?=[lr][aeiouäöü])', '\\1-', wort)
174 wort = re.sub('[-·]gn(?=[aeiouäöü])', 'g-n', wort)
175 return wort
177 def trenne_gn(wort):
178 """Regelsilben bei gn (Si-gnal -> Sig-nal)."""
179 return re.sub('[-·]gn(?=[aeiouäöü])', 'g-n', wort)
182 # morphemisch()
183 # ~~~~~~~~~~~~~
185 # Entferne Alternativtrennungen nach §113 (verblasste Morphologie).
187 # Regelwerk (1996) §113:
188 # Wörter, die sprachhistorisch oder von der Herkunftssprache her gesehen
189 # Zusammensetzungen oder Präfigierungen sind, aber nicht mehr als solche
190 # empfunden oder erkannt werden, kann man entweder nach § 108 oder nach
191 # § 109 bis § 112 trennen.
193 # >>> from stilfilter import morphemisch
194 # >>> blasse = ('hi-n<auf wo-r=um Chry-s<an-the-me Hek-t<ar '
195 # ... 'He-li-ko<p-ter in-te-r<es-sant Li-n<oleum Pä-d<a-go-gik '
196 # ... 'au-to<ch-ton Psy-ch<i.a-trie '
197 # ... 'A<·s-phalt E·x<a-men Di-a<g-no-se A<·s-the-n=o-pie')
198 # >>> for wort in blasse.split():
199 # ... print(wort, '->', morphemisch(wort))
200 # hi-n<auf -> hin<auf
201 # wo-r=um -> wor=um
202 # Chry-s<an-the-me -> Chrys<an-the-me
203 # Hek-t<ar -> Hekt<ar
204 # He-li-ko<p-ter -> He-li-ko<pter
205 # in-te-r<es-sant -> in-ter<es-sant
206 # Li-n<oleum -> Lin<oleum
207 # Pä-d<a-go-gik -> Päd<a·go-gik
208 # au-to<ch-ton -> au-to<chton
209 # Psy-ch<i.a-trie -> Psych<i·a-trie
210 # A<·s-phalt -> A<·sphalt
211 # E·x<a-men -> Ex<a·men
212 # Di-a<g-no-se -> Di·a<gno-se
213 # A<·s-the-n=o-pie -> A<·sthen=o·pie
215 # Ersetze, wenn zwischen Haupttrennstelle und Nebentrennstelle nur ein
216 # Buchstabe liegt.
217 # (Die Haupttrennstelle kann vor oder nach der Nebentrennstelle liegen.)
218 # ::
220 def morphemisch(wort):
221 """Bei Alternativen, Trennung nach Morphologie (hin<auf, Päd<ago-ge).
223 Entferne Alternativtrennungen nach §113 (verblasste Morphologie).
225 # nach (mit Selbstlaut):
226 wort = re.sub('([<=]+[.·]*[aeiouyäöü])[-.]+', '\\1·', wort)
227 # nach
228 wort = re.sub('([<=]+[.·]*(.|ch))[-.]+', '\\1', wort)
229 # vor (mit Selbstlaut):
230 wort = re.sub('[-.]+([aeiouyäöü][<=]+)', \\1', wort)
231 # vor:
232 wort = re.sub('[-.]+((.|ch)[<=]+)', '\\1', wort)
233 # vor (mit Randtrennung: E·x<a-men, Di·a<g-no-se)
234 wort = re.sub('·(([^aeiouyäöü]|ch)[<=]+)', '\\1', wort)
235 return wort
237 # syllabisch()
238 # ~~~~~~~~~~~~
240 # >>> from stilfilter import syllabisch
241 # >>> for wort in blasse.split():
242 # ... print(wort, '->', syllabisch(wort))
243 # hi-n<auf -> hi-nauf
244 # wo-r=um -> wo-rum
245 # Chry-s<an-the-me -> Chry-san-the-me
246 # Hek-t<ar -> Hek-tar
247 # He-li-ko<p-ter -> He-li-kop-ter
248 # in-te-r<es-sant -> in-te-res-sant
249 # Li-n<oleum -> Li-noleum
250 # Pä-d<a-go-gik -> Pä-da-go-gik
251 # au-to<ch-ton -> au-toch-ton
252 # Psy-ch<i.a-trie -> Psy-chi.a-trie
253 # A<·s-phalt -> As-phalt
254 # E·x<a-men -> E·xa-men
255 # Di-a<g-no-se -> Di-ag-no-se
256 # A<·s-the-n=o-pie -> As-the-no-pie
258 # ::
260 def syllabisch(wort):
261 """Ignoriere "verblasste" Morphologie (hi-nauf, Hek-tar, Pä-da-go-ge)."""
262 # nach
263 wort = re.sub('[<=]+[.·]*((.|ch)[-.]+)', '\\1', wort)
264 # vor
265 wort = re.sub('([-.]+.)[<=]+[.]*', '\\1', wort)
266 return wort
269 # Tests:
271 # K44 „ſ“ (langes s) steht in Fremdwörtern...
273 # >>> blasse = ('tran<s-pirieren tran<s-zendent ab<s-tinent '
274 # ... 'Ab<s-zess Pro-s<odie')
275 # >>> for wort in blasse.split():
276 # ... print(wort, '->', morphemisch(wort))
277 # tran<s-pirieren -> tran<spirieren
278 # tran<s-zendent -> tran<szendent
279 # ab<s-tinent -> ab<stinent
280 # Ab<s-zess -> Ab<szess
281 # Pro-s<odie -> Pros<odie
283 # Trennstellen können als ungünstig markiert sein:
285 # >>> blasse = ('Bür-ger=in<.i-ti-a-ti-ve Pä-..d<e-..rast')
286 # >>> for wort in blasse.split():
287 # ... print(wort, '->', morphemisch(wort))
288 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=in<.i·ti-a-ti-ve
289 # Pä-..d<e-..rast -> Päd<e·rast
290 # >>> for wort in blasse.split():
291 # ... print(wort, '->', syllabisch(wort))
292 # Bür-ger=in<.i-ti-a-ti-ve -> Bür-ger=ini-ti-a-ti-ve
293 # Pä-..d<e-..rast -> Pä-..de-..rast
295 # Suffixe mit Vokalcluster nicht ändern:
297 # >>> morphemisch('Zy-klo>i-de')
298 # 'Zy-klo>i-de'
301 # Permissivität
302 # -------------
304 # Unterdrücken/Zulassen von regelkonformen Trennungen je nach Anwendungsfall.
305 # (Siehe Trennstile in sprachauszug.py.)
307 # _unguenstig()
308 # ~~~~~~~~~~~~~
310 # Entferne Randtrennstellen und explizit als ungünstig gekennzeichnete
311 # Trennstellen. Das Argument `level` bezeichnetwird die Zahl der
312 # Ungünstigmarker (Punkte) ab der eine Trennstelle entfernt wird.
314 # >>> from stilfilter import _unguenstig
315 # >>> _unguenstig('Text=il<..lu-stra-ti.on')
316 # 'Text=illu-stra-ti.on'
317 # >>> _unguenstig('Text=il<..lu-stra-ti.on', level=2)
318 # 'Text=illu-stra-ti.on'
319 # >>> _unguenstig('Text=il<..lu-stra-ti.on', level=3)
320 # 'Text=il<..lu-stra-ti.on'
321 # >>> print(_unguenstig('A·dress=er<.hö-hung'))
322 # Adress=erhö-hung
323 # >>> print(_unguenstig('A·dress=er<.hö-hung', level=0))
324 # Adress=er<.hö-hung
326 # ::
328 def _unguenstig(wort, level=1):
329 """Entferne ungünstige und Randtrennstellen ab `level`."""
330 wort = re.sub('[=<>]*·', '', wort)
331 if level:
332 marker = r'\.' * level
333 wort = re.sub('[-=<>]+%s+'%marker, '', wort)
334 # TODO: einzelne Punkte (Schwankungsfälle)
335 # wort = wort.replace('.', '-')
336 return wort
338 # standard()
339 # ~~~~~~~~~~
340 # Standardunterdrückung + keine Flattervokale::
342 def standard(wort):
343 """Entferne ungünstige Trennungen und Einvokalsilben."""
344 return keine_flattervokale(_unguenstig(wort))
346 # permissiv()
347 # ~~~~~~~~~~~
348 # Aussortieren sehr ungünstiger (mit „..“ markierter) Trennstellen.
349 # ::
351 def permissiv(wort):
352 """Aussortieren sehr ungünstiger (mit „..“ markierter) Trennstellen."""
353 return _unguenstig(wort, level=2)
355 # inklusiv()
356 # ~~~~~~~~~~
357 # ::
358 def inklusiv(wort):
359 """Zulassen aller Trennungen (auch ungünstige) außer Randtrennungen."""
360 return _unguenstig(wort, level=0)
362 # _add_randtrennung()
363 # ~~~~~~~~~~~~~~~~~~~
365 # Füge Trennmöglichkeiten am Wortanfang und -ende zu, die nach §107 E2
366 # des Regelwerkes (vorher K79) verboten sind aber in Gesangstexten gebraucht
367 # werden.
369 # >>> from stilfilter import _add_randtrennung
370 # >>> print(_add_randtrennung('Abend'))
371 # A·bend
372 # >>> print(_add_randtrennung('Ra-dio'))
373 # Ra-di·o
375 # Das gleiche gilt für Trennmöglichkeiten am Anfang/Ende von Teilwörtern:
377 # >>> print(_add_randtrennung('Eis=ano-ma-lie'))
378 # Eis=a·no-ma-lie
379 # >>> print(_add_randtrennung('Ra-dio<phon'), _add_randtrennung('Dia<gramm'))
380 # Ra-di·o<phon Di·a<gramm
382 # Ausnahmen müssen im Quelltext gekennzeichnet werden:
384 # >>> print(_add_randtrennung('Ai-chin-ger'), _add_randtrennung('Ai-da'))
385 # Ai-chin-ger Ai-da
386 # >>> print(_add_randtrennung('Ai-chin-ger'), _add_randtrennung('A·i-da'))
387 # Ai-chin-ger A·i-da
389 # ::
391 def _add_randtrennung(word):
392 """Zusätzliche Trennmöglichkeiten am Wortrand (Fei-er=a·bend, Zo·o<lo-ge)."""
394 # Führender Vokal, gefolgt von Silbenanfang
395 # (optionaler Konsonant (auch ch/ck/ph/rh/sch/sh/th) + Vokal)::
397 for match in re.finditer(
398 '(^|[<=])([aeiouäöüAEIOUÄÖÜ])'
399 '(([^·]|ch|ck|ph|sch|st|th|[bdfgkpt][rl]|[gmp]n|sb|sk|sp|sph)?'
400 '[aeiouyäöü])', word):
402 # Ausnahmen: Doppellaute, Diphthonge, Umlaute::
404 if re.search('(aa|ae|ai|au|äu|ei|eu|oe|oo|ou|ue)',
405 match.group(0), flags=re.IGNORECASE):
406 continue
408 # Ersetzen::
410 word = ''.join((word[:match.start()], match.expand('\\1\\\\3'),
411 _add_randtrennung(word[match.end():])))
412 break
414 # zwei Vokale am Wortende::
416 for match in re.finditer('([aeiouäöü])([aeiouäöü])([<>=]|$)', word):
417 # if 'ie' in match.group(0) and re.search('([a]-nie)', word, flags=re.IGNORECASE):
418 # sys.stderr.write(word+' '+match.group(0)+'\n')
420 # Ausnahme: Doppellaute, Diphtonge, Umlaute::
422 if re.search('(aa|ae|ai|au|äu|ee|ei|eu|ie|oe|oi|oo|ou|ue|ui)',
423 match.group(0)):
424 continue
426 # Ersetzen::
428 word = ''.join((word[:match.start()], match.expand('\\\\2\\3'),
429 _add_randtrennung(word[match.end():])))
430 break
432 return word
434 # Test:
436 # mehrere Konsonanten nach Anfangsvokal
437 # >>> sample = 'Achim Acker Apha-sie Asche Osten'
439 # Wahltrennung nach §112 oder §113
440 # >>> sample += 'Adria Agnes Agro<nom Akro-nym Apri-kose Asbest Aspekt Asphalt'
441 # >>> sample += ' Eklat Etrus-ker igno-riert Okla-ho-ma ukra.i-nisch'
442 # >>> for word in sample.split():
443 # ... print(_add_randtrennung(word))
444 # A·chim
445 # A·cker
446 # A·pha-sie
447 # A·sche
448 # O·stenAdri·a
449 # A·gnes
450 # A·gro<nom
451 # A·kro-nym
452 # A·pri-kose
453 # A·sbest
454 # A·spekt
455 # A·sphalt
456 # E·klat
457 # E·trus-ker
458 # i·gno-riert
459 # O·kla-ho-ma
460 # u·kra.i-nisch
462 # gesangstext()
463 # ~~~~~~~~~~~~~
465 # Zusätzliche Trennmöglichkeiten am Wortanfang und -ende behalten,
466 # Trennungen zwischen (Halb-)Vokalen im Schwankungsfall unterdrücken.
468 # >>> from stilfilter import gesangstext
469 # >>> for wort in "A<·sphalt O·bo·e ab<a·xi.al Gli·a=zel-le".split():
470 # ... print(gesangstext(wort))
471 # A<sphalt
472 # O-bo-e
473 # ab<a-xial
474 # Gli-a=zel-le
476 # ::
478 def gesangstext(wort):
479 """Trennungen für Gesangstext:
480 Randtrennungen behalten, Schwankungsfälle unterdrücken.
482 wort = wort.replace('·', '-').replace('<-', '<')
483 wort = re.sub('([aeiouyäöü])\\.([aeiouyäöü])', '\\1\\2', wort)
484 return wort
487 # keine_flattervokale()
488 # ~~~~~~~~~~~~~~~~~~~~~
490 # Traditionell enthalten die TeX-Trennmuster keine Trennstellen deren
491 # Abstand zur Nachbartrennstelle einen Buchstaben beträgt. Die Entscheidung
492 # „Flatterbuchstaben“ zu vermeiden folgt der Praxis im Wörterverzeichnis
493 # des „Einheitsdudens“ (Duden 1991) und Wahrig (1980).
495 # Bei einvokalischen Silben im Wortinneren, nimm die zweite:
497 # >>> from stilfilter import keine_flattervokale
498 # >>> einzelne = ('The-a-ter ge-ni.a-le')
499 # >>> for wort in einzelne.split():
500 # ... print(wort, '->', keine_flattervokale(wort))
501 # The-a-ter -> Thea-ter
502 # ge-ni.a-le -> ge-nia-le
504 # Allerdings nicht, wenn die zweite Trennstelle unterdrückt oder die erste
505 # eine Suffixtrennstelle ist:
507 # >>> einzelne = ('La-sal-le-a-.ner Athe>i-sten An<woh-ner=in<.i-ti-a-ti-ve')
508 # >>> for wort in einzelne.split():
509 # ... print(wort, '->', keine_flattervokale(wort))
510 # La-sal-le-a-.ner -> La-sal-le-a-.ner
511 # Athe>i-sten -> Athe>isten
512 # An<woh-ner=in<.i-ti-a-ti-ve -> An<woh-ner=in<.i-tia-ti-ve
514 # ::
516 def keine_flattervokale(wort):
517 """Entferne Trennmarker vor Einzevokalsilben (Thea-ter)."""
518 wort = re.sub('>([aeiouyäöü])[-]\.?', '>\\1', wort)
519 wort = re.sub('(?<![<>=])[-.]+([aeiouyäöüëï])(?=[->][^.])', '\\1', wort)
520 return wort
523 # righthyphenmin3()
524 # ~~~~~~~~~~~~~~~~~~
526 # „Es gilt als Buchdruckerregel, dass zwei Buchstaben am Wortende nur im
527 # Notfall abgetrennt werden“ (Duden 1934):
529 # >>> from stilfilter import righthyphenmin3
530 # >>> sample = 'fo-to<gra-fie-re Fe-ri-en=kurs'
531 # >>> sample += ' dar<an dar<an=mach-te'
532 # >>> sample += ' aben-teu-er>li-che hu-ma-no>id Amy-lo>id=er<kran-kung'
533 # >>> sample += ' il--le-gal an-ti<zi-pier-te'
534 # >>> for word in sample.split():
535 # ... print(righthyphenmin3(word))
536 # foto<gra-fiere
537 # Fe-rien=kurs
538 # daran
539 # daran=machte
540 # aben-teuer>li-che
541 # hu-ma-noid
542 # Amy-loid=er<kran-kung
543 # il--le-gal
544 # anti<zi-pierte
546 # ::
548 def righthyphenmin3(wort):
549 # -, < und > am Schluss und vor =
550 wort = re.sub('[-<>.]+(..$|..=)', '\\1', wort)
551 # - vor < und >
552 wort = re.sub('[-.]+(..[<>])', '\\1', wort)
553 return wort
555 # hyphenmin3()
556 # ~~~~~~~~~~~~
558 # Keine Trennung nach nur 2 Buchstaben am Wortanfang oder -ende.
560 # Ausnahme:
561 # morphemische oder günstige Trennung am Wortanfang
562 # (z.B. Ab<bruch, Ei=sa-lat, il--le-gal):
564 # >>> from stilfilter import hyphenmin3
565 # >>> sample += ' Ab<druck=er<.laub>nis-se ab<er<.kannt un<=ge<recht=fer-tigt'
566 # >>> sample += ' be<ru-fen ab<be<ru-fen ab=zu=be<ru-fen'
567 # >>> sample += ' zu=zu=be<rei-ten un<zu<ver<läs-sig'
568 # >>> for word in sample.split():
569 # ... print(hyphenmin3(word))
570 # foto<gra-fiere
571 # Ferien=kurs
572 # daran
573 # daran=machte
574 # aben-teuer>liche
575 # huma-noid
576 # Amy-loid=erkran-kung
577 # il--le-gal
578 # anti<zipierte
579 # Ab<druck=erlaub>nisse
580 # ab<erkannt
581 # un<=gerecht=fer-tigt
582 # be<rufen
583 # ab<berufen
584 # ab=zu=beru-fen
585 # zu=zu=berei-ten
586 # un<zuver<läs-sig
588 # ::
590 def hyphenmin3(wort):
591 """Entferne Trennungen im Abstand 2 vom Wortrand.
593 Wie \\lefthyphenmin = \\righthyphenmin = 3, aber auch in Komposita
594 und nicht bei "guten" Trennungen am Wortanfang.
596 # -, < und > am Schluss und vor =
597 wort = re.sub('[-<>.]+(..$|..=)', '\\1', wort)
598 # - und > am Anfang
599 wort = re.sub('^(..)[->.][.]*([^-])', '\\1\\2', wort)
600 # -, < und > nach =
601 wort = re.sub('(=..)[-<>.]+', '\\1', wort)
602 # - vor < und >
603 wort = re.sub('[-.]+(..[<>])', '\\1', wort)
604 # - nach < und >
605 wort = re.sub('([<>]..)[-.]+', '\\1', wort)
606 # < nach < (zuerst letztes von 3)
607 wort = re.sub('<(..<..)[<.]+', '<\\1', wort)
608 wort = re.sub('<(..)[<.]+', '<\\1', wort)
609 return wort
612 # morphemgrenzen()
613 # ~~~~~~~~~~~~~~~~
615 # Entferne alle syllabischen Trennstellen, behalte Trennstellen an Wortfugen,
616 # nach Präfix und vor Suffix.
617 # (Entspricht in etwa dem Make-Ziel "major".)
619 # >>> from stilfilter import morphemgrenzen
620 # >>> for word in sample.split():
621 # ... print(morphemgrenzen(word))
622 # foto<grafiere
623 # Ferien=kurs
624 # dar<an
625 # dar<an=machte
626 # abenteuer>liche
627 # humano>id
628 # Amylo>id=er<krankung
629 # illegal
630 # anti<zipierte
631 # Ab<druck=er<.laub>nisse
632 # ab<er<.kannt
633 # un<=ge<recht=fertigt
634 # be<rufen
635 # ab<be<rufen
636 # ab=zu=be<rufen
637 # zu=zu=be<reiten
638 # un<zu<ver<lässig
640 # Anwendung: Ligaturaufbruch, Tests
641 # ::
643 def morphemgrenzen(wort):
644 """Entferne syllabische Trennstellen."""
645 # Randtrennungen:
646 wort = re.sub('[=<>]*·', '', wort)
647 # syllabische Trennungen
648 wort = re.sub('-+[.]*', '', wort)
649 # Schwankungsfälle (einzelner Punkt)
650 wort = re.sub('(?<![<=>·])\\.+', '', wort)
651 return wort
654 # haupttrennstellen
655 # """""""""""""""""
656 # Nur Trennstellen an Wortfugen und nach Präfix.
658 # >>> from stilfilter import haupttrennstellen
659 # >>> print(haupttrennstellen('Ü·ber<=licht=ge<schwin-dig>keit'))
660 # Über<=licht=ge<schwindigkeit
662 # Anwendung: Wortanalyse, Tests
663 # ::
665 def haupttrennstellen(wort):
666 """Entferne Trennstellen außer an Wortfugen und nach Präfix."""
667 # Randtrennungen:
668 wort = re.sub('[=<>]*·', '', wort)
669 # syllabische Trennungen und Suffixe
670 wort = re.sub('[->]+', '', wort)
671 # Schwankungsfälle und ungünstige entfernte Trennung (einzelner Punkt)
672 wort = re.sub('(?<![<=·])\\.+', '', wort)
673 return wort
676 # einfach()
677 # ~~~~~~~~~
678 # Ersetze die Trennstellenauszeichnung durch ein einfaches Trennzeichen.
680 # >>> from stilfilter import einfach
681 # >>> print(einfach('Rah-men=ver<ein>ba-rung'), einfach('Re<gis-ter=ari·e'))
682 # Rah-men-ver-ein-ba-rung Re-gis-ter-ari-e
683 # >>> print(einfach('Ab<fa{ll/ll=l}a-ger'), einfach('Dru{ck/k-k}er'))
684 # Ab-falla-ger Drucker
685 # >>> print(einfach('An-ti<=geld=wä-sche===vor<rich-tung'))
686 # An-ti-geld-wä-sche-vor-rich-tung
688 # Das Argument `alternative` beschreibt die Auflösung von Mehrdeutigkeiten:
689 # 1: erste Alternative, 2: zweite Alternative, 0 ungetrennt:
691 # >>> print(einfach('Au-ssen=ma[-s/s-]se'), einfach('An-dro[/>]id'))
692 # Au-ssen-masse An-droid
693 # >>> print(einfach('Au-ssen=ma[-s/s-]se', 1), einfach('An-dro[/>]id', 1))
694 # Au-ssen-ma-sse An-droid
695 # >>> print(einfach('Au-ssen=ma[-s/s-]se', 2), einfach('An-dro[/>]id', 2))
696 # Au-ssen-mas-se An-dro-id
698 # >>> print(einfach('auf<fangen', symbol='"|'))
699 # auf"|fangen
701 # ::
703 def einfach(wort, alternative=0 , symbol='-'):
704 """Vereinheitliche Trennstellenmarker."""
706 # Spezielle Trennungen für die traditionelle Rechtschreibung
707 # (siehe ../../dokumente/README.wortliste)::
709 if '{' in wort:
710 wort = wort.replace('{ck/k-k}', 'ck')
711 # Konsonanthäufungen an Wortfuge: '{xx/xxx}' -> 'xx':
712 wort = re.sub(r'\{(.*)/.*}', r'\1', wort)
714 # Trennstellen in doppeldeutigen Wörtern. `alternative` wählt
715 # 1: erste Alternative, 2: zweite Alternative, 0 ungetrennt ::
717 if '[' in wort:
718 if alternative:
719 wort = re.sub(r'\[(.*)/(.*)]', r'\%s'%str(alternative), wort)
720 else:
721 match = re.search(r'\[(.*)/.*]', wort)
722 kompromiss = re.sub('[-=<>.·]', '', match.group(1))
723 wort = wort.replace(match.group(0), kompromiss)
725 # Trennstellen aller Kategorien::
727 wort = re.sub('[-=<>.·]+', symbol, wort)
729 return wort
732 # Hauptfunktion
733 # -------------
735 # ::
737 if __name__ == '__main__':
739 import argparse
741 from wortliste import WordEntry, ShortEntry, filelines, run_filters
744 # Optionen
745 # ~~~~~~~~
747 # ::
749 parser = argparse.ArgumentParser(description = __doc__,
750 formatter_class=argparse.RawDescriptionHelpFormatter)
751 parser.add_argument('INFILES', nargs='*', default=['-'],
752 help='Eingabedatei(en), Default: - (Standardeingabe).')
753 parser.add_argument('--stilliste', action="store_true",
754 help='Beschreibung der unterstützten Trennstile')
755 parser.add_argument('-k', '--kurzformat', action="store_true",
756 help='Input ist Wortliste im Kurzformat. '
757 '(Default: Langformat)', default=False)
758 parser.add_argument('-s', '--stil', metavar='STIL,[STIL...]',
759 help='Trennstil(e) (siehe --stilliste für Auswahl)',
760 default="")
761 parser.add_argument('--test-filter', action='store_true',
762 help='Schreibe nur geänderte Einträge.')
763 parser.add_argument('--output-filter', metavar='EXP',
764 help='Schreibe nur Einträge, die auf den regulären '
765 'Ausdruck EXP passen')
766 parser.add_argument('-1', '--test-1er', action='store_true',
767 help='Schreibe nur Einträge mit „Flattertrennung“. '
768 'Überschreibt --output-filter.')
770 args = parser.parse_args()
772 if args.stilliste:
773 help('stilfilter')
774 sys.exit()
776 if args.kurzformat:
777 entry_class = ShortEntry
778 start = 0
779 else:
780 entry_class = WordEntry
781 start = 1
783 if args.output_filter:
784 output_regexp = args.output_filter
785 else:
786 output_regexp = ''
788 # Keine Ausgabe, wenn alle "Flatterbuchstaben" entfernt
789 # (überschreibt "--output-filter")::
791 if args.test_1er:
792 output_regexp = '[-<>=.][^-<>=./][-<>=.]'
795 # Iteration über Eingabe
796 # """"""""""""""""""""""
798 # ::
800 for line in filelines(args.INFILES or '-'):
802 # Zeile lesen und in WordEntry oder ShortEntry Objekt wandeln::
804 if not line or line.startswith('#'):
805 continue
806 entry = entry_class(line)
808 # Iteration über die Felder des Eintrags::
810 changed = False
811 for i in range(start,len(entry)):
812 word = entry[i]
813 if not word:
814 continue
816 # Trennstil (Filter anwenden)::
818 entry[i] = run_filters(args.stil.split(','), word)
819 if entry[i] != word:
820 changed = True
822 # für Testzwecke: nur Worte ausgeben, die durch Filter geändert wurden::
824 if not changed and args.test_filter:
825 continue
827 out_line = str(entry)
829 # Nur Worte ausgeben, die auf den Ausgabefilter-Ausdruck passen::
831 if output_regexp and not re.search(output_regexp, out_line):
832 continue
834 # Auf Standardausgabe ausgeben::
836 print(out_line)