extract-tex.pl: Kleine Dokumentationskorrekturen.
[wortliste.git] / skripte / wortliste / extract-tex.pl
blob50dac0e782b9e4e90a286413b34501a077bd4473
1 #! /usr/bin/perl -w
3 # extract-tex.pl
5 # Dieses Perl-Skript extrahiert einfache Wortlisten aus der
6 # »wortliste«-Datenbank im Langformat (oder ähnlichen Dateien mit gleichem
7 # Dateiformat), die beispielsweise als Eingabedateien für »patgen« verwendet
8 # werden können.
10 # Aufruf:
12 # perl extract-tex.pl [Optionen...] [liste1 liste2 ...] > input.patgen
14 # Die Eingabedateien müssen in UTF-8 kodiert sein; ist keine Eingabedatei
15 # angegeben, verwendet das Skript die Standardeingabe. Beispiele:
17 # perl extract-tex.pl -l < ../wortliste > wortliste.ref.latin9
18 # perl extract-tex.pl -t ../wortliste > wortliste.trad.utf8
20 # Die Aufrufe
22 # perl extract-tex.pl -1 ...
23 # perl extract-tex.pl -t -1 ...
24 # perl extract-tex.pl -s -1 ...
26 # liefern jeweils die gleiche Ausgabe wie
28 # sprachauszug.py -l de-1996,de-1996-x-versal ...
29 # sprachauszug.py -l de-1901,de-1901-x-versal ...
30 # sprachauszug.py -l de-CH-1901,de-1901 ...
33 # Optionen
34 # --------
36 # -t
37 # -s Option »-t« wählt die traditionelle deutsche Rechtschreibung aus,
38 # Option »-s« die traditionelle (deutsch)schweizerische Rechtschreibung.
39 # Wenn weder »-s« noch »-t« gesetzt ist, wird die reformierte deutsche
40 # Rechtschreibung ausgewählt.
42 # -x Ignoriere Optionen »-g«, »-u« sowie »-1« und gib die
43 # sprachspezifischen Felder unbearbeitet aus (inklusive Kommentare).
45 # -g Gib Wörter mit gewichteten Trennstellen aus. Optional kann ein
46 # ganzzahliges Argument angegeben werden: Wert 0 gibt alle gewichtete
47 # Trennstellen aus inklusive »-« (das ist der Standardwert), Wert 1 nur
48 # die Trennstellen mit der höchsten Wichtung (ohne »-«), Wert 2 die
49 # Trennstellen mit der höchsten und zweithöchsten Wichtung (ohne »-«),
50 # usw.
52 # Beachte, dass bei nahe beieinanderstehenden Trennstellen derzeit keine
53 # zusätzliche Wichtung vorgenommen wird. Beispielsweise ist in dem Wort
55 # ab<be<ru-fen
57 # die Trennung »abbe-rufen« schlecht, weil ganz nahe der optimalen
58 # Trennstelle (nach »ab«). Das Skript gibt trotzdem diese Trennstelle
59 # als zweitbeste aus.
61 # -u Verhindere die Ausgabe von Wörtern mit Markern für unerwünschte
62 # Trennungen (z.B. »An<=al-.pha=bet«). Wenn nicht gesetzt, werden als
63 # ungünstig markierte Trennstellen entfernt (z.B. »An<=alpha=bet«).
65 # -U Gib Wörter mit Nicht-ASCII-Zeichen auch in Umschrift aus (z.B.
66 # »loe-sen«, »Haen-de«). Ausgenommen davon sind Wörter mit »ß«, weil
67 # die entsprechenden Formen mit »ss« bereits in der Wortliste enthalten
68 # sind.
70 # -1 (Ziffer 1) Verhindere einbuchstabige Trennungen. Ist die Option
71 # gesetzt, wird die erste dieser Trennungen unterdrückt, falls beide
72 # Trennstellen gleichwertig sind (z.B. »eu-ro-päi-sche« statt
73 # »eu-ro-pä-i-sche«), anderenfalls bleibt die stärkere erhalten (z.B.
74 # »päd<ago-gisch« statt »pä-d<a-go-gisch«).
76 # -v Verhindere die Ausgabe von Versalformen, wo »ß« durch »ss« ersetzt
77 # ist.
79 # -l (Kleinbuchstabe L) Konvertiere die Ausgabe von UTF-8 nach latin-9.
81 # Wir verwenden »<<>>« statt »<>« aus Sicherheitsgründen.
82 require 5.22.0;
84 use strict;
85 use warnings;
86 use English '-no_match_vars';
87 use utf8; # String-Literals direkt als UTF-8.
88 use open qw(:std :utf8);
89 use Getopt::Long qw(:config bundling);
92 my ($opt_g, $opt_l, $opt_s, $opt_t, $opt_u, $opt_U, $opt_v, $opt_x, $opt_1);
93 $opt_g = -1;
95 GetOptions("g:i" => \$opt_g,
96 "l" => \$opt_l,
97 "s" => \$opt_s,
98 "t" => \$opt_t,
99 "u" => \$opt_u,
100 "U" => \$opt_U,
101 "v" => \$opt_v,
102 "x" => \$opt_x,
103 "1" => \$opt_1);
106 my $prog = $0;
107 $prog =~ s@.*/@@;
110 # Kodierung:
111 binmode(STDOUT, ":encoding(iso-8859-15)") if $opt_l;
114 # Einige Konstanten für reguläre Ausdrücke, um die Lesbarkeit zu
115 # erhöhen.
116 my $marker = qr/[.·<>=-]/x;
117 my $buchstabe = qr/(?: [^.·<>=-] | ch)/x;
118 my $vokal = qr/[aeiouäöüy]/x;
119 # Konsonant: nicht Vokal, aber Buchstabe.
120 my $konsonant = qr/(?! $vokal ) $buchstabe/;
123 sub entferne_marker {
124 my $arg = shift;
125 $arg =~ s/$marker//g;
126 return $arg;
129 # Wenn Option »-U« gesetzt ist, müssen wir erkennen können, ob Wörter in
130 # Umschrift in der Wortliste existieren. Wir benutzen dafür zwei Hashes.
131 my %wortliste;
132 my %wortliste_umschrift;
134 while (<<>>) {
135 # Gebe Kommentarzeilen direkt aus, falls verlangt.
136 if (/^ \s* \#/x) {
137 print if $opt_x;
138 next;
141 chop;
143 # Isoliere Kommentare.
144 s/(\# .*) $//x;
146 my $kommentar = $1 // "";
148 # Entferne Leerzeichen aller Art.
149 s/\s+//g;
151 my @feld = split(';');
152 next if $#feld < 1;
154 # reformiert: Felder 2, 4, 5, 7
155 # traditionell: Felder 2, 3, 5, 6
156 # traditionell Schweiz: Felder 2, 3, 5, 6, 8
158 # Beachte: Feld n hat Index n-1.
159 my $zeile = "";
160 $zeile = $feld[2] if defined $feld[2]
161 && $feld[2] ne "-3-" && ($opt_t || $opt_s);
162 $zeile = $feld[3] if defined $feld[3]
163 && $feld[3] ne "-4-" && !($opt_t || $opt_s);
164 if (!$zeile) {
165 # Wir nehmen Versalformen nur dann, wenn es keine normalen Formen (in
166 # Feld 2 oder 3) gibt.
167 $zeile = $feld[4] if defined $feld[4]
168 && $feld[4] ne "-5-" && !$opt_v;
169 $zeile = $feld[5] if defined $feld[5]
170 && $feld[5] ne "-6-" && ($opt_t || $opt_s) && !$opt_v;
171 $zeile = $feld[6] if defined $feld[6]
172 && $feld[6] ne "-7-" && !($opt_t || $opt_s) && !$opt_v;
175 $zeile = $feld[7] if defined $feld[7] && $opt_s;
177 if (!$zeile) {
178 $zeile = $feld[1];
181 next if $zeile eq "-2-";
183 if (!$opt_x) {
184 # Entferne spezielle Trennungen.
185 $zeile =~ s|\{ (.*?) / .*? \}|$1|gx;
187 # Entferne Doppeldeutigkeiten.
188 $zeile =~ s|\[ (.*?) / .*? \]|entferne_marker($1)|egx;
190 # Hier der Algorithmus, um die verbliebenen Markierungen in
191 # Trennstellen aufzulösen. Die Schritte sind in der gegebenen
192 # Reihenfolge abzuarbeiten.
194 # Dieses Skript implementiert ausschließlich den morphemischen
195 # Trennstil (siehe Punkt 1), unter weiterer Anwendung der Regeln
196 # 2, 3, 4 und 6. Regel 5 wird nicht angewendet.
198 # (1) Auflösung von Wahlmöglichkeiten zwischen morphemischem und
199 # syllabischem Trennstil (einer der beiden Stile muß gewählt
200 # werden). Ungünstigkeitsmarker und Gesangstrennstellen
201 # werden in diesem Schritt nicht berücksichtigt (wohl aber
202 # entfernt, wenn die entsprechende Trennstelle entfällt).
204 # (a) Die Bezeichnungen
206 # <x- und -x<
208 # sind Kurzschreibungen für
210 # {<x/x-} und {x</-x} (morphemisch/syllabisch) ,
212 # wobei »x« ein Konsonant oder »ch« ist. Diese Regel gilt
213 # nicht für die Suffixe »>x-« und »-x>«. Die
214 # Zusammensetzungen »=x-« und »-x=« werden gegenwärtig
215 # nicht beachtet, da sie in der Wortliste nicht vorkommen
216 # (Beispiel: Lö-b=au).
218 # (b) Die Bezeichnungen
220 # <i- und -i<
222 # sind Kurzschreibungen für
224 # {<i·/i-} und {·i</-i} (morphemisch/syllabisch) ,
226 # wobei »i« ein Vokal ist (einschließlich »y«). Diese
227 # Regel gilt nicht für die Suffixe »>i-« und »-i>«. Die
228 # Zusammensetzungen »=i-« und »-i=« werden gegenwärtig
229 # nicht beachtet, da sie in der Wortliste nicht vorkommen
230 # (Beispiel: Ei-se-n=a-ch-er Motorenwerke).
232 # Beispielsweise bleibt die Markierung
234 # al-ge-bra>i-sche
236 # in diesem Schritt unverändert; wegen »>« gibt es keine
237 # Wahlmöglichkeit.
239 # (2) Behandle (angehängte) ».«-Marker, falls ungünstige
240 # Trennstellen unterdrückt werden sollen.
242 # (3) Entferne Flattervokale (also Einbuchstaben-Silben), falls
243 # verlangt. Beachte, daß »ch« wie ein Buchstabe behandelt
244 # wird und auch Schwankungsfälle berücksichtigt werden.
245 # Gesangstrennstellen dagegen werden ignoriert (aber
246 # gegebenenfalls entfernt).
248 # (a) Ist eine Trennstelle »stärker« als die andere, wird die
249 # stärkere Trennstelle genommen (z.B. ist »>« stärker als
250 # »-«, »-« stärker als ».«).
252 # (b) Sind die Trennstellen gleich stark, wird die rechte
253 # Trennstelle genommen.
255 # (4) Entferne Gesangstrennstellen, falls verlangt. Beachte, daß
256 # die Markierung für Gesangstrennstellen, ähnlich zu
257 # Ungünstigkeitsmarkern, auch zu anderen Markern treten kann
258 # (die dann ebenfalls entfernt werden).
260 # (5) Entferne restliche Schwankungsfälle, falls verlangt.
262 # (6) Alle verbliebenen Markierungen werden zu »-« aufgelöst.
265 # Beispiele:
266 # Re<s-tau-rant
267 # Re<stau-rant (1a, morphemisch)
268 # Re-stau-rant (6)
270 # Re<s-tau-rant
271 # Res-tau-rant (1a, syllabisch)
273 # Ge-r<i.a-trie
274 # Ger<i.a-trie (1a, morphemisch)
275 # Ger<ia-trie (3)
276 # Ger-ia-trie (6)
278 # Ge-r<i.a-trie
279 # Ge-ri.a-trie (1a, syllabisch)
280 # Ge-ria-trie (3)
282 # Ärz-te=i·n<.i-ti.a-ti-ve
283 # Ärz-te=i·n<.i·ti.a-ti-ve (1b, morphemisch)
284 # Ärz-te=i·ni·ti.a-ti-ve (2)
285 # Ärz-te=i·ni·tia-ti-ve (3a)
286 # Ärz-te=initia-ti-ve (4)
287 # Ärz-te-initia-ti-ve (6)
289 # Ärz-te=i·n<.i-ti.a-ti-ve
290 # Ärz-te=i·ni-ti.a-ti-ve (1b, syllabisch)
291 # Ärz-te=i·ni-tia-ti-ve (3a)
292 # Ärz-te=ini-tia-ti-ve (4)
293 # Ärz-te-ini-tia-ti-ve (6)
295 # Di-a<s-po-ra
296 # Di·a<s-po-ra (1b, morphemisch)
297 # Di·a<spo-ra (1a)
298 # Dia<spo-ra (4)
299 # Dia-spo-ra (6)
301 # Di-a<s-po-ra
302 # Di-as-po-ra (1b, syllabisch)
304 # Kaf-ka=ken-.ner
305 # Kaf-ka=kenner (2)
306 # Kaf-ka-kenner (6)
308 # al-ge-bra>i-sche
309 # al-ge-bra>ische (3a)
310 # al-ge-bra-ische (6)
312 # Ru-i-ne
313 # Rui-ne (3b)
315 # A<·s-phalt
316 # A<·sphalt (1a, morphemisch)
317 # Asphalt (4)
319 # A<·s-phalt
320 # As-phalt (1b, syllabisch)
322 # ge-ni.al
323 # ge-nial (5)
325 # Schritt 1a.
326 $zeile =~ s/< \.* $konsonant \K - \.*//gx;
327 $zeile =~ s/- \.* ($konsonant \.*) (?= <)/$1/gx;
329 # Schritt 1b.
330 $zeile =~ s/(< \.* $vokal) -/$1·/gx;
331 $zeile =~ s/- (\.* $vokal) (?= <)/·$1/gx;
333 # Ausgabe von Wörtern mit unerwünschten Trennungen?
334 next if $zeile =~ /\./ and $opt_u;
336 # Schritt 2.
337 $zeile =~ s/[·<>=-]+ \.+//gx;
339 if ($opt_1) {
340 # Schritt 3a: »a<=b-c« wird zu »a-bc«.
341 $zeile =~ s/[-.]+ (?= $buchstabe [<>=] [·<>=]* )//gx;
342 $zeile =~ s/[<>=] [·<>=]* $buchstabe \K [-.]+//gx;
344 # Schritt 3a: »a-b.c« wird zu »a-bc«.
345 $zeile =~ s/\. ( $buchstabe - )/$1/gx;
346 $zeile =~ s/( - $buchstabe ) \./$1/gx;
348 # Schritt 3b.
349 $zeile =~ s/- ( $buchstabe - )/$1/gx;
352 # Schritt 4.
353 # TODO: Ergänze Option für »Gesangstext-Trennmuster«
354 $zeile =~ s/($buchstabe) [.·<>=-]* ·+ [.·<>=-]*/$1/gx;
356 # Schritt 5.
357 $zeile =~ s/\./-/gx;
359 if ($opt_g > 0) {
360 # Berechne Wichtungen. Wir verwenden folgende Werte:
362 # -2 Wortteil
363 # -1 -
364 # 0 --
365 # 1 <, >
366 # 2 =
367 # 3 ==, <=, =>
368 # 4 ===, <==, ==>
369 # ..
371 # Bei mehrfachem Auftreten von »<« hat das am meisten links stehende
372 # den höchsten Rang. Bei mehrfachem Auftreten von »>« hat das am
373 # meisten rechts stehende den höchsten Rang. Beispiel:
375 # Mit<ver<ant-wort>lich>keit
376 # ^ ^
378 # Das bezieht sich auch auf Ketten mit »=>« u.ä:
380 # Ei-gen=wirt>schaft=>lich>keit
383 my $g;
384 my $m;
385 my ($r, $r_vorher);
386 my ($w, $w_vorher);
388 # Wir zerlegen mit `split' unter Beibehaltung der Begrenzer.
389 my @zerlegung = split /([<>=-]+)/, $zeile;
391 # Wir speichern Wichtung und Rang als Felder.
392 my @wichtung = (-2) x ($#zerlegung + 1);
393 my @rang = (0) x ($#zerlegung + 1);
395 # Erster Durchgang: Ermittle Wichtungswerte.
397 # Wir starten bei erstem Marker (mit Index 1).
398 foreach my $i (1 .. ($#zerlegung - 1)) {
399 # Ignoriere Nicht-Marker.
400 next if not $i % 2;
402 $m = $zerlegung[$i];
404 if ($m =~ /^-$/) {
405 $w = -1;
407 elsif ($m =~ /^--$/) {
408 $w = 0;
410 elsif ($m =~ /^[<>]$/) {
411 $w = 1;
413 elsif ($m =~ /^=$/) {
414 $w = 2;
416 elsif ($m =~ /( ==*>? | <?=*= )/x) {
417 $w = length($1) + 1;
419 else {
420 warn "Zeile $INPUT_LINE_NUMBER:"
421 . " unbekannter Marker »$m« behandelt als »-«\n";
422 $w = -1;
425 $wichtung[$i] = $w;
428 # Zweiter Durchgang: Adjustiere Wichtung von »<« und »>«.
430 # Behandle »<« von rechts nach links gehend.
431 $w_vorher = -2;
432 foreach my $i (reverse(1 .. ($#zerlegung - 1))) {
433 # Ignoriere Nicht-Marker.
434 next if not $i % 2;
436 if (index ($zerlegung[$i], "<") >= 0) {
437 # Hat der rechte Marker in einer Kette von »<« eine höhere
438 # Wichtung, wird diese übernommen.
439 $w = $wichtung[$i];
441 if ($w_vorher >= $w) {
442 $wichtung[$i] = $w_vorher;
444 else {
445 $w_vorher = $w;
448 # »-«-Marker zwischen zwei »<« ändert nicht deren Wichtung.
449 elsif ($zerlegung[$i] ne "-") {
450 $w_vorher = -2;
454 # Behandle »>« von links nach rechts gehend.
455 $w_vorher = -2;
456 foreach my $i (1 .. ($#zerlegung - 1)) {
457 # Ignoriere Nicht-Marker.
458 next if not $i % 2;
460 if (index ($zerlegung[$i], ">") >= 0) {
461 # Hat der linke Marker in einer Kette von »>« eine höhere
462 # Wichtung, wird diese übernommen.
463 $w = $wichtung[$i];
465 if ($w_vorher >= $w) {
466 $wichtung[$i] = $w_vorher;
468 else {
469 $w_vorher = $w;
472 # »-«-Marker zwischen zwei »>« ändert nicht deren Wichtung.
473 elsif ($zerlegung[$i] ne "-") {
474 $w_vorher = -2;
478 # Dritter Durchgang: Ermittle Rang von »<« und »>«.
480 # Behandle »<« von links nach rechts gehend.
481 $r = 0;
482 foreach my $i (1 .. ($#zerlegung - 1)) {
483 # Ignoriere Nicht-Marker.
484 next if not $i % 2;
486 if (index ($zerlegung[$i], "<") >= 0) {
487 $rang[$i] = $r--;
489 # »-«-Marker zwischen zwei »<« ändert nicht den Rang.
490 elsif ($zerlegung[$i] ne "-") {
491 $r = 0;
495 # Behandle »>« von rechts nach links gehend.
496 $r = 0;
497 foreach my $i (reverse(1 .. ($#zerlegung - 1))) {
498 # Ignoriere Nicht-Marker.
499 next if not $i % 2;
501 if (index ($zerlegung[$i], ">") >= 0) {
502 $rang[$i] = $r--;
504 # »-«-Marker zwischen zwei »>« ändert nicht den Rang.
505 elsif ($zerlegung[$i] ne "-") {
506 $r = 0;
510 # Sortiere Indexfeld für Marker mit absteigender Wichtung.
511 my @wichtungsindices =
512 sort {
513 # Benutze Rang für Sekundärsortierung.
514 if ($wichtung[$a] == $wichtung[$b]) {
515 -($rang[$a] <=> $rang[$b]);
517 else {
518 -($wichtung[$a] <=> $wichtung[$b]);
520 } (0 .. $#zerlegung);
522 # Entferne Trennstellen unter Berücksichtigung des Arguments von »-g«.
523 $g = 0;
524 $w_vorher = -2;
525 $r_vorher = 0;
527 foreach my $i (@wichtungsindices) {
528 # Alle Wortteile haben einen geraden Index und sind stets am Schluß
529 # von @wichtungsindices.
530 last if not $i % 2;
532 $w = $wichtung[$i];
533 $r = $rang[$i];
535 if ($w_vorher == $w) {
536 $g++ if $r_vorher != $r;
538 else {
539 $g++;
542 $w_vorher = $w;
543 $r_vorher = $r;
545 # Entferne Trennung mit zu geringer Wichtung.
546 $zerlegung[$i] = "" if $g > $opt_g || $w < 0;
549 $zeile = join '', @zerlegung;
551 elsif ($opt_g < 0) {
552 # Schritt 6.
553 $zeile =~ s/$marker+/-/g;
557 print "$zeile";
558 print " " . $kommentar if $kommentar && $opt_x;
559 print "\n";
561 # Der Schlüssel im Hash ist das ungetrennte Wort, konvertiert zu
562 # Kleinbuchstaben; Wert wird keiner gebraucht.
563 $wortliste{lc($feld[0])} = ();
565 if ($opt_U) {
566 my $orig_zeile = $zeile;
568 $zeile =~ tr[ÀàÁáÂâÃãÇçÈèÉéÊêËëÌìÍíÎîÏïÑñÒòÓóÔôÕõŠšÙùÚúÛûÝýŸÿŽž]
569 [AaAaAaAaCcEeEeEeEeIiIiIiIiNnOoOoOoOoSsUuUuUuYyYyZz];
571 $zeile =~ s/Ä/Ae/g;
572 $zeile =~ s/ä/ae/g;
573 $zeile =~ s/Å/Aa/g;
574 $zeile =~ s/å/aa/g;
575 $zeile =~ s/Æ/Ae/g;
576 $zeile =~ s/æ/Ae/g;
578 $zeile =~ s/Ö/Oe/g;
579 $zeile =~ s/ö/oe/g;
580 $zeile =~ s/Ø/Oe/g;
581 $zeile =~ s/ø/oe/g;
582 $zeile =~ s/Œ/Oe/g;
583 $zeile =~ s/œ/oe/g;
585 $zeile =~ s/Ü/Ue/g;
586 $zeile =~ s/ü/ue/g;
588 $wortliste_umschrift{lc($feld[0])} = $zeile if $orig_zeile ne $zeile;
592 if ($opt_U) {
593 # Wir geben nur Wörter aus, die nicht bereits in der originalen Wortliste
594 # existieren.
595 foreach my $wort (sort(keys %wortliste_umschrift)) {
596 my $umschrift = $wortliste_umschrift{$wort};
597 my $test = lc(entferne_marker($umschrift));
599 print "$umschrift\n" if not exists ($wortliste{$test});
603 # eof