lingo 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. data/ChangeLog +13 -0
  2. data/README +49 -29
  3. data/Rakefile +28 -4
  4. data/TODO +2 -9
  5. data/bin/lingo +24 -0
  6. data/bin/lingoctl +24 -0
  7. data/de/lingo-dic.txt +559 -74
  8. data/info/gpl-hdr.txt +21 -24
  9. data/lib/lingo.rb +83 -112
  10. data/lib/lingo/agenda_item.rb +53 -0
  11. data/lib/lingo/attendee.rb +261 -0
  12. data/lib/lingo/attendee/abbreviator.rb +95 -97
  13. data/lib/lingo/attendee/debugger.rb +94 -93
  14. data/lib/lingo/attendee/decomposer.rb +76 -83
  15. data/lib/lingo/attendee/dehyphenizer.rb +141 -144
  16. data/lib/lingo/attendee/formatter.rb +65 -0
  17. data/lib/lingo/attendee/multi_worder.rb +302 -0
  18. data/lib/lingo/attendee/noneword_filter.rb +89 -84
  19. data/lib/lingo/attendee/object_filter.rb +91 -0
  20. data/lib/lingo/attendee/sequencer.rb +159 -158
  21. data/lib/lingo/attendee/synonymer.rb +81 -84
  22. data/lib/lingo/attendee/text_reader.rb +242 -0
  23. data/lib/lingo/attendee/text_writer.rb +169 -0
  24. data/lib/lingo/attendee/tokenizer.rb +192 -191
  25. data/lib/lingo/attendee/variator.rb +152 -156
  26. data/lib/lingo/attendee/vector_filter.rb +140 -135
  27. data/lib/lingo/attendee/word_searcher.rb +98 -0
  28. data/lib/lingo/buffered_attendee.rb +69 -0
  29. data/lib/lingo/cachable.rb +58 -0
  30. data/lib/lingo/call.rb +72 -0
  31. data/lib/lingo/cli.rb +26 -0
  32. data/lib/lingo/config.rb +23 -26
  33. data/lib/lingo/core_ext.rb +42 -0
  34. data/lib/lingo/ctl.rb +239 -173
  35. data/lib/lingo/database.rb +148 -496
  36. data/lib/lingo/database/crypter.rb +85 -0
  37. data/lib/lingo/database/gdbm_store.rb +49 -0
  38. data/lib/lingo/database/hash_store.rb +67 -0
  39. data/lib/lingo/database/libcdb_store.rb +58 -0
  40. data/lib/lingo/database/sdbm_store.rb +64 -0
  41. data/lib/lingo/database/show_progress.rb +81 -0
  42. data/lib/lingo/database/source.rb +134 -0
  43. data/lib/lingo/database/source/key_value.rb +62 -0
  44. data/lib/lingo/database/source/multi_key.rb +65 -0
  45. data/lib/lingo/database/source/multi_value.rb +65 -0
  46. data/lib/lingo/database/source/single_word.rb +60 -0
  47. data/lib/lingo/database/source/word_class.rb +64 -0
  48. data/lib/lingo/error.rb +122 -0
  49. data/lib/lingo/language.rb +78 -518
  50. data/lib/lingo/language/dictionary.rb +173 -0
  51. data/lib/lingo/language/grammar.rb +211 -0
  52. data/lib/lingo/language/lexical.rb +66 -0
  53. data/lib/lingo/language/lexical_hash.rb +88 -0
  54. data/lib/lingo/language/token.rb +48 -0
  55. data/lib/lingo/language/word.rb +130 -0
  56. data/lib/lingo/language/word_form.rb +83 -0
  57. data/lib/lingo/reportable.rb +59 -0
  58. data/lib/lingo/version.rb +1 -1
  59. data/lingo-all.cfg +14 -10
  60. data/lingo-call.cfg +5 -5
  61. data/lingo.cfg +14 -12
  62. data/lingo.rb +26 -0
  63. data/lir.cfg +13 -9
  64. data/spec/spec_helper.rb +1 -0
  65. data/test.cfg +11 -11
  66. data/test/attendee/ts_abbreviator.rb +0 -6
  67. data/test/attendee/ts_decomposer.rb +0 -6
  68. data/test/attendee/{ts_multiworder.rb → ts_multi_worder.rb} +1 -7
  69. data/test/attendee/ts_noneword_filter.rb +1 -7
  70. data/test/attendee/{ts_objectfilter.rb → ts_object_filter.rb} +1 -7
  71. data/test/attendee/ts_sequencer.rb +0 -6
  72. data/test/attendee/ts_synonymer.rb +0 -6
  73. data/test/attendee/{ts_textreader.rb → ts_text_reader.rb} +1 -7
  74. data/test/attendee/{ts_textwriter.rb → ts_text_writer.rb} +1 -7
  75. data/test/attendee/ts_tokenizer.rb +0 -6
  76. data/test/attendee/ts_variator.rb +0 -6
  77. data/test/attendee/ts_vector_filter.rb +1 -7
  78. data/test/attendee/{ts_wordsearcher.rb → ts_word_searcher.rb} +1 -7
  79. data/test/ref/artikel.non +2 -29
  80. data/test/ref/artikel.seq +13 -8
  81. data/test/ref/artikel.vec +30 -15
  82. data/test/ref/artikel.ven +29 -14
  83. data/test/ref/artikel.ver +58 -43
  84. data/test/ref/lir.csv +146 -145
  85. data/test/ref/lir.non +186 -210
  86. data/test/ref/lir.seq +54 -50
  87. data/test/test_helper.rb +41 -36
  88. data/test/ts_database.rb +12 -11
  89. data/test/ts_language.rb +118 -68
  90. metadata +67 -29
  91. data/lib/lingo/attendee/multiworder.rb +0 -301
  92. data/lib/lingo/attendee/objectfilter.rb +0 -86
  93. data/lib/lingo/attendee/textreader.rb +0 -237
  94. data/lib/lingo/attendee/textwriter.rb +0 -196
  95. data/lib/lingo/attendee/wordsearcher.rb +0 -96
  96. data/lib/lingo/attendees.rb +0 -289
  97. data/lib/lingo/const.rb +0 -131
  98. data/lib/lingo/modules.rb +0 -98
  99. data/lib/lingo/types.rb +0 -285
  100. data/lib/lingo/utilities.rb +0 -40
@@ -1,165 +1,162 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  #--
4
- # LINGO ist ein Indexierungssystem mit Grundformreduktion, Kompositumzerlegung,
5
- # Mehrworterkennung und Relationierung.
6
- #
7
- # Copyright (C) 2005-2007 John Vorhauer
8
- # Copyright (C) 2007-2011 John Vorhauer, Jens Wille
9
- #
10
- # This program is free software; you can redistribute it and/or modify it under
11
- # the terms of the GNU Affero General Public License as published by the Free
12
- # Software Foundation; either version 3 of the License, or (at your option)
13
- # any later version.
14
- #
15
- # This program is distributed in the hope that it will be useful, but WITHOUT
16
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
- # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
18
- # details.
19
- #
20
- # You should have received a copy of the GNU Affero General Public License along
21
- # with this program; if not, write to the Free Software Foundation, Inc.,
22
- # 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
23
- #
24
- # For more information visit http://www.lex-lingo.de or contact me at
25
- # welcomeATlex-lingoDOTde near 50°55'N+6°55'E.
26
- #
27
- # Lex Lingo rules from here on
4
+ ###############################################################################
5
+ # #
6
+ # Lingo -- A full-featured automatic indexing system #
7
+ # #
8
+ # Copyright (C) 2005-2007 John Vorhauer #
9
+ # Copyright (C) 2007-2012 John Vorhauer, Jens Wille #
10
+ # #
11
+ # Lingo is free software; you can redistribute it and/or modify it under the #
12
+ # terms of the GNU Affero General Public License as published by the Free #
13
+ # Software Foundation; either version 3 of the License, or (at your option) #
14
+ # any later version. #
15
+ # #
16
+ # Lingo is distributed in the hope that it will be useful, but WITHOUT ANY #
17
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
18
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for #
19
+ # more details. #
20
+ # #
21
+ # You should have received a copy of the GNU Affero General Public License #
22
+ # along with Lingo. If not, see <http://www.gnu.org/licenses/>. #
23
+ # #
24
+ ###############################################################################
28
25
  #++
29
26
 
30
27
  class Lingo
31
28
 
32
- # Der Dehyphenizer ... muss noch dokumentiert werden
33
- #
34
- # === Mögliche Verlinkung
35
- # Erwartet:: Daten vom Typ *Word* z.B. von Wordsearcher, Decomposer, Ocr_variator, Multiworder
36
- # Erzeugt:: Daten vom Typ *Word* (mit Attribut WA_MULTIWORD). Je erkannter Mehrwortgruppe wird ein zusätzliches Word-Objekt in den Datenstrom eingefügt. Z.B. für Ocr_variator, Sequencer, Noneword_filter, Vector_filter
37
- #
38
- # === Parameter
39
- # Kursiv dargestellte Parameter sind optional (ggf. mit Angabe der Voreinstellung).
40
- # Alle anderen Parameter müssen zwingend angegeben werden.
41
- # <b>in</b>:: siehe allgemeine Beschreibung des Attendee
42
- # <b>out</b>:: siehe allgemeine Beschreibung des Attendee
43
- # <b>source</b>:: siehe allgemeine Beschreibung des Dictionary
44
- # <b><i>mode</i></b>:: (Standard: all) siehe allgemeine Beschreibung des Dictionary
45
- # <b><i>stopper</i></b>:: (Standard: TA_PUNCTUATION, TA_OTHER) Gibt die Begrenzungen an, zwischen
46
- # denen der Multiworder suchen soll, i.d.R. Satzzeichen und Sonderzeichen,
47
- # weil sie kaum in einer Mehrwortgruppen vorkommen.
48
- #
49
- # === Beispiele
50
- # Bei der Verarbeitung einer normalen Textdatei mit der Ablaufkonfiguration <tt>t1.cfg</tt>
51
- # meeting:
52
- # attendees:
53
- # - textreader: { out: lines, files: '$(files)' }
54
- # - tokenizer: { in: lines, out: token }
55
- # - abbreviator: { in: token, out: abbrev, source: 'sys-abk' }
56
- # - wordsearcher: { in: abbrev, out: words, source: 'sys-dic' }
57
- # - decomposer: { in: words, out: comps, source: 'sys-dic' }
58
- # - multiworder: { in: comps, out: multi, source: 'sys-mul' }
59
- # - debugger: { in: multi, prompt: 'out>' }
60
- # ergibt die Ausgabe über den Debugger: <tt>lingo -c t1 test.txt</tt>
61
- # out> *FILE('test.txt')
62
- # out> <Sein = [(sein/s), (sein/v)]>
63
- # out> <Name = [(name/s)]>
64
- # out> <ist = [(sein/v)]>
65
- # out> <johann van siegen|MUL = [(johann van siegen/m)]>
66
- # out> <Johann = [(johann/e)]>
67
- # out> <van = [(van/w)]>
68
- # out> <Siegen = [(sieg/s), (siegen/v), (siegen/e)]>
69
- # out> :./PUNC:
70
- # out> *EOL('test.txt')
71
- # out> *EOF('test.txt')
72
-
73
- class Attendee::Dehyphenizer < BufferedAttendee
74
-
75
- protected
76
-
77
- def init
78
- # Parameter verwerten
79
- @stopper = get_array('stopper', TA_PUNCTUATION+','+TA_OTHER).collect {|s| s.upcase }
80
-
81
- # Wörterbuch bereitstellen
82
- src = get_array('source')
83
- mod = get_key('mode', 'all')
84
- @dic = Dictionary.new({'source'=>src, 'mode'=>mod}, @lingo)
85
- @gra = Grammar.new({'source'=>src, 'mode'=>mod}, @lingo)
86
-
87
- @number_of_expected_tokens_in_buffer = 2
88
- @eof_handling = false
89
-
90
- @skip = get_array('skip', "").collect { |wc| wc.downcase }
91
- end
92
-
93
- def control(cmd, par)
94
- @dic.report.each_pair { |key, value| set(key, value) } if cmd == STR_CMD_STATUS
95
-
96
- # Jedes Control-Object ist auch Auslöser der Verarbeitung
97
- if cmd == STR_CMD_RECORD || cmd == STR_CMD_EOF
98
- @eof_handling = true
99
- while number_of_valid_tokens_in_buffer > 1
100
- process_buffer
101
- end
102
- forward_number_of_token( @buffer.size, false )
29
+ class Attendee
30
+
31
+ # Der Dehyphenizer ... muss noch dokumentiert werden
32
+ #
33
+ # === Mögliche Verlinkung
34
+ # Erwartet:: Daten vom Typ *Word* z.B. von Wordsearcher, Decomposer, Ocr_variator, Multiworder
35
+ # Erzeugt:: Daten vom Typ *Word* (mit Attribut WA_MULTIWORD). Je erkannter Mehrwortgruppe wird ein zusätzliches Word-Objekt in den Datenstrom eingefügt. Z.B. für Ocr_variator, Sequencer, Noneword_filter, Vector_filter
36
+ #
37
+ # === Parameter
38
+ # Kursiv dargestellte Parameter sind optional (ggf. mit Angabe der Voreinstellung).
39
+ # Alle anderen Parameter müssen zwingend angegeben werden.
40
+ # <b>in</b>:: siehe allgemeine Beschreibung des Attendee
41
+ # <b>out</b>:: siehe allgemeine Beschreibung des Attendee
42
+ # <b>source</b>:: siehe allgemeine Beschreibung des Dictionary
43
+ # <b><i>mode</i></b>:: (Standard: all) siehe allgemeine Beschreibung des Dictionary
44
+ # <b><i>stopper</i></b>:: (Standard: TA_PUNCTUATION, TA_OTHER) Gibt die Begrenzungen an, zwischen
45
+ # denen der Multiworder suchen soll, i.d.R. Satzzeichen und Sonderzeichen,
46
+ # weil sie kaum in einer Mehrwortgruppen vorkommen.
47
+ #
48
+ # === Beispiele
49
+ # Bei der Verarbeitung einer normalen Textdatei mit der Ablaufkonfiguration <tt>t1.cfg</tt>
50
+ # meeting:
51
+ # attendees:
52
+ # - text_reader: { out: lines, files: '$(files)' }
53
+ # - tokenizer: { in: lines, out: token }
54
+ # - abbreviator: { in: token, out: abbrev, source: 'sys-abk' }
55
+ # - word_searcher: { in: abbrev, out: words, source: 'sys-dic' }
56
+ # - decomposer: { in: words, out: comps, source: 'sys-dic' }
57
+ # - multi_worder: { in: comps, out: multi, source: 'sys-mul' }
58
+ # - debugger: { in: multi, prompt: 'out>' }
59
+ # ergibt die Ausgabe über den Debugger: <tt>lingo -c t1 test.txt</tt>
60
+ # out> *FILE('test.txt')
61
+ # out> <Sein = [(sein/s), (sein/v)]>
62
+ # out> <Name = [(name/s)]>
63
+ # out> <ist = [(sein/v)]>
64
+ # out> <johann van siegen|MUL = [(johann van siegen/m)]>
65
+ # out> <Johann = [(johann/e)]>
66
+ # out> <van = [(van/w)]>
67
+ # out> <Siegen = [(sieg/s), (siegen/v), (siegen/e)]>
68
+ # out> :./PUNC:
69
+ # out> *EOL('test.txt')
70
+ # out> *EOF('test.txt')
71
+
72
+ class Dehyphenizer < BufferedAttendee
73
+
74
+ protected
75
+
76
+ def init
77
+ @stopper = get_array('stopper', TA_PUNCTUATION+','+TA_OTHER).map(&:upcase)
78
+
79
+ set_dic
80
+ set_gra
81
+
82
+ @skip = get_array('skip', '').map(&:downcase)
83
+
84
+ @number_of_expected_tokens_in_buffer = 2
103
85
  @eof_handling = false
104
86
  end
105
- end
106
87
 
107
- def process_buffer?
108
- number_of_valid_tokens_in_buffer >= @number_of_expected_tokens_in_buffer
109
- end
110
-
111
- def process_buffer
112
- if @buffer[0].is_a?(Word) &&
113
- @buffer[0].form[-1..-1] == '-' &&
114
- @buffer[1].is_a?(Word) &&
115
- !(!( ttt = @buffer[1].get_class(/./) ).nil? &&
116
- !@skip.index( ttt[0].attr ).nil?)
117
-
118
- # Einfache Zusammensetzung versuchen
119
- form = @buffer[0].form[0...-1] + @buffer[1].form
120
- word = @dic.find_word( form )
121
- word = @gra.find_compositum( form ) unless word.attr == WA_IDENTIFIED
122
-
123
- unless word.attr == WA_IDENTIFIED || (word.attr == WA_KOMPOSITUM && word.get_class('x+').empty?)
124
- # Zusammensetzung mit Bindestrich versuchen
125
- form = @buffer[0].form + @buffer[1].form
126
- word = @dic.find_word( form )
127
- word = @gra.find_compositum( form ) unless word.attr == WA_IDENTIFIED
88
+ def control(cmd, par)
89
+ @dic.report.each_pair { |key, value| set(key, value) } if cmd == STR_CMD_STATUS
90
+
91
+ # Jedes Control-Object ist auch Auslöser der Verarbeitung
92
+ if cmd == STR_CMD_RECORD || cmd == STR_CMD_EOF
93
+ @eof_handling = true
94
+ while number_of_valid_tokens_in_buffer > 1
95
+ process_buffer
96
+ end
97
+ forward_number_of_token( @buffer.size, false )
98
+ @eof_handling = false
128
99
  end
100
+ end
129
101
 
130
- unless word.attr == WA_IDENTIFIED || (word.attr == WA_KOMPOSITUM && word.get_class('x+').empty?)
131
- # Zusammensetzung mit Bindestrich versuchen
132
- form = @buffer[0].form + @buffer[1].form
133
- word = @dic.find_word( form )
134
- word = @gra.find_compositum( form ) unless word.attr == WA_IDENTIFIED
135
- end
102
+ def process_buffer?
103
+ number_of_valid_tokens_in_buffer >= @number_of_expected_tokens_in_buffer
104
+ end
136
105
 
137
- if word.attr == WA_IDENTIFIED || (word.attr == WA_KOMPOSITUM && word.get_class('x+').empty?)
138
- @buffer[0] = word
139
- @buffer.delete_at( 1 )
106
+ def process_buffer
107
+ if @buffer[0].is_a?(Word) &&
108
+ @buffer[0].form[-1..-1] == '-' &&
109
+ @buffer[1].is_a?(Word) &&
110
+ !(!( ttt = @buffer[1].get_class(/./) ).nil? &&
111
+ !@skip.index( ttt[0].attr ).nil?)
112
+
113
+ # Einfache Zusammensetzung versuchen
114
+ form = @buffer[0].form[0...-1] + @buffer[1].form
115
+ word = @dic.find_word(form)
116
+ word = @gra.find_compositum(form) unless word.identified?
117
+
118
+ unless word.identified? || (word.attr == WA_KOMPOSITUM && word.get_class('x+').empty?)
119
+ # Zusammensetzung mit Bindestrich versuchen
120
+ form = @buffer[0].form + @buffer[1].form
121
+ word = @dic.find_word(form)
122
+ word = @gra.find_compositum(form) unless word.identified?
123
+ end
124
+
125
+ unless word.identified? || (word.attr == WA_KOMPOSITUM && word.get_class('x+').empty?)
126
+ # Zusammensetzung mit Bindestrich versuchen
127
+ form = @buffer[0].form + @buffer[1].form
128
+ word = @dic.find_word(form)
129
+ word = @gra.find_compositum(form) unless word.identified?
130
+ end
131
+
132
+ if word.identified? || (word.attr == WA_KOMPOSITUM && word.get_class('x+').empty?)
133
+ @buffer[0] = word
134
+ @buffer.delete_at( 1 )
135
+ end
140
136
  end
141
- end
142
137
 
143
- # Buffer weiterschaufeln
144
- forward_number_of_token( 1, false )
145
- end
138
+ # Buffer weiterschaufeln
139
+ forward_number_of_token( 1, false )
140
+ end
146
141
 
147
- private
142
+ private
143
+
144
+ # Leitet 'len' Token weiter
145
+ def forward_number_of_token( len, count_punc = true )
146
+ begin
147
+ unless @buffer.empty?
148
+ forward( @buffer[0] )
149
+ len -= 1 unless count_punc && @buffer[0].form == CHAR_PUNCT
150
+ @buffer.delete_at( 0 )
151
+ end
152
+ end while len > 0
153
+ end
148
154
 
149
- # Leitet 'len' Token weiter
150
- def forward_number_of_token( len, count_punc = true )
151
- begin
152
- unless @buffer.empty?
153
- forward( @buffer[0] )
154
- len -= 1 unless count_punc && @buffer[0].form == CHAR_PUNCT
155
- @buffer.delete_at( 0 )
156
- end
157
- end while len > 0
158
- end
155
+ # Liefert die Anzahl gültiger Token zurück
156
+ def number_of_valid_tokens_in_buffer
157
+ @buffer.collect { |token| (token.form == CHAR_PUNCT) ? nil : 1 }.compact.size
158
+ end
159
159
 
160
- # Liefert die Anzahl gültiger Token zurück
161
- def number_of_valid_tokens_in_buffer
162
- @buffer.collect { |token| (token.form == CHAR_PUNCT) ? nil : 1 }.compact.size
163
160
  end
164
161
 
165
162
  end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ ###############################################################################
5
+ # #
6
+ # Lingo -- A full-featured automatic indexing system #
7
+ # #
8
+ # Copyright (C) 2005-2007 John Vorhauer #
9
+ # Copyright (C) 2007-2012 John Vorhauer, Jens Wille #
10
+ # #
11
+ # Lingo is free software; you can redistribute it and/or modify it under the #
12
+ # terms of the GNU Affero General Public License as published by the Free #
13
+ # Software Foundation; either version 3 of the License, or (at your option) #
14
+ # any later version. #
15
+ # #
16
+ # Lingo is distributed in the hope that it will be useful, but WITHOUT ANY #
17
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
18
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for #
19
+ # more details. #
20
+ # #
21
+ # You should have received a copy of the GNU Affero General Public License #
22
+ # along with Lingo. If not, see <http://www.gnu.org/licenses/>. #
23
+ # #
24
+ ###############################################################################
25
+ #++
26
+
27
+ class Lingo
28
+
29
+ class Attendee
30
+
31
+ class Formatter < TextWriter
32
+
33
+ protected
34
+
35
+ def init
36
+ super
37
+
38
+ @ext = get_key('ext', '-')
39
+ @format = get_key('format', '%s')
40
+ @map = get_key('map', Hash.new { |h, k| h[k] = k })
41
+
42
+ @no_puts = true
43
+ end
44
+
45
+ def process(obj)
46
+ if obj.is_a?(Word) || obj.is_a?(Token)
47
+ str = obj.form
48
+
49
+ if obj.respond_to?(:lexicals)
50
+ lex = obj.lexicals.first # TODO
51
+ att = @map[lex.attr] if lex
52
+ str = @format % [str, lex.form, att] if att
53
+ end
54
+ else
55
+ str = obj.to_s
56
+ end
57
+
58
+ @lir ? @lir_rec_buf << str : @file.print(str)
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,302 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ ###############################################################################
5
+ # #
6
+ # Lingo -- A full-featured automatic indexing system #
7
+ # #
8
+ # Copyright (C) 2005-2007 John Vorhauer #
9
+ # Copyright (C) 2007-2012 John Vorhauer, Jens Wille #
10
+ # #
11
+ # Lingo is free software; you can redistribute it and/or modify it under the #
12
+ # terms of the GNU Affero General Public License as published by the Free #
13
+ # Software Foundation; either version 3 of the License, or (at your option) #
14
+ # any later version. #
15
+ # #
16
+ # Lingo is distributed in the hope that it will be useful, but WITHOUT ANY #
17
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
18
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for #
19
+ # more details. #
20
+ # #
21
+ # You should have received a copy of the GNU Affero General Public License #
22
+ # along with Lingo. If not, see <http://www.gnu.org/licenses/>. #
23
+ # #
24
+ ###############################################################################
25
+ #++
26
+
27
+ class Lingo
28
+
29
+ class Attendee
30
+
31
+ # Mit der bisher beschriebenen Vorgehensweise werden die durch den Tokenizer erkannten
32
+ # Token aufgelöst und in Words verwandelt und über den Abbreviator und Decomposer auch
33
+ # Spezialfälle behandelt, die einzelne Wörter betreffen.
34
+ # Um jedoch auch Namen wie z.B. John F. Kennedy als Sinneinheit erkennen zu können, muss
35
+ # eine Analyse über mehrere Objekte erfolgen. Dies ist die Hauptaufgabe des MultiWorders.
36
+ # Der MultiWorder analysiert die Teile des Datenstroms, die z.B. durch Satzzeichen oder
37
+ # weiteren Einzelzeichen (z.B. '(') begrenzt sind. Erkannte Mehrwortgruppen werden als
38
+ # zusätzliches Objekt in den Datenstrom mit eingefügt.
39
+ #
40
+ # === Mögliche Verlinkung
41
+ # Erwartet:: Daten vom Typ *Word* z.B. von Wordsearcher, Decomposer, Ocr_variator, MultiWorder
42
+ # Erzeugt:: Daten vom Typ *Word* (mit Attribut WA_MULTIWORD). Je erkannter Mehrwortgruppe wird ein zusätzliches Word-Objekt in den Datenstrom eingefügt. Z.B. für Ocr_variator, Sequencer, Noneword_filter, Vector_filter
43
+ #
44
+ # === Parameter
45
+ # Kursiv dargestellte Parameter sind optional (ggf. mit Angabe der Voreinstellung).
46
+ # Alle anderen Parameter müssen zwingend angegeben werden.
47
+ # <b>in</b>:: siehe allgemeine Beschreibung des Attendee
48
+ # <b>out</b>:: siehe allgemeine Beschreibung des Attendee
49
+ # <b>source</b>:: siehe allgemeine Beschreibung des Dictionary
50
+ # <b><i>mode</i></b>:: (Standard: all) siehe allgemeine Beschreibung des Dictionary
51
+ # <b><i>stopper</i></b>:: (Standard: TA_PUNCTUATION, TA_OTHER) Gibt die Begrenzungen an, zwischen
52
+ # denen der MultiWorder suchen soll, i.d.R. Satzzeichen und Sonderzeichen,
53
+ # weil sie kaum in einer Mehrwortgruppen vorkommen.
54
+ #
55
+ # === Beispiele
56
+ # Bei der Verarbeitung einer normalen Textdatei mit der Ablaufkonfiguration <tt>t1.cfg</tt>
57
+ # meeting:
58
+ # attendees:
59
+ # - text_reader: { out: lines, files: '$(files)' }
60
+ # - tokenizer: { in: lines, out: token }
61
+ # - abbreviator: { in: token, out: abbrev, source: 'sys-abk' }
62
+ # - word_searcher: { in: abbrev, out: words, source: 'sys-dic' }
63
+ # - decomposer: { in: words, out: comps, source: 'sys-dic' }
64
+ # - multi_worder: { in: comps, out: multi, source: 'sys-mul' }
65
+ # - debugger: { in: multi, prompt: 'out>' }
66
+ # ergibt die Ausgabe über den Debugger: <tt>lingo -c t1 test.txt</tt>
67
+ # out> *FILE('test.txt')
68
+ # out> <Sein = [(sein/s), (sein/v)]>
69
+ # out> <Name = [(name/s)]>
70
+ # out> <ist = [(sein/v)]>
71
+ # out> <johann van siegen|MUL = [(johann van siegen/m)]>
72
+ # out> <Johann = [(johann/e)]>
73
+ # out> <van = [(van/w)]>
74
+ # out> <Siegen = [(sieg/s), (siegen/v), (siegen/e)]>
75
+ # out> :./PUNC:
76
+ # out> *EOL('test.txt')
77
+ # out> *EOF('test.txt')
78
+
79
+ class MultiWorder < BufferedAttendee
80
+
81
+ protected
82
+
83
+ def init
84
+ @stopper = get_array('stopper', TA_PUNCTUATION+','+TA_OTHER).map(&:upcase)
85
+ @mul_dic = dictionary(mul_src = get_array('source'), get_key('mode', 'all'))
86
+
87
+ # combine lexical variants?
88
+ #
89
+ # false = old behaviour
90
+ # true = first match
91
+ # 'all' = all matches
92
+ @combine = get_key('combine', false)
93
+ @all_keys = @combine.is_a?(String) && @combine.downcase == 'all'
94
+
95
+ lex_src, lex_mod, databases = nil, nil, @lingo.dictionary_config['databases']
96
+
97
+ mul_src.each { |src|
98
+ this_src, this_mod = databases[src].values_at('use-lex', 'lex-mode')
99
+ if lex_src.nil? || lex_src == this_src
100
+ lex_src, lex_mod = this_src, this_mod
101
+ else
102
+ @lingo.warn "#{self.class}: Dictionaries don't match: #{mul_src.join(',')}"
103
+ end
104
+ }
105
+
106
+ lex_src = lex_src.split(STRING_SEPARATOR_RE)
107
+ lex_mod = get_key('lex-mode', lex_mod || 'first')
108
+
109
+ @lex_dic = dictionary(lex_src, lex_mod)
110
+ @lex_gra = grammar(lex_src, lex_mod)
111
+
112
+ if @combine && has_key?('use-syn')
113
+ @syn_dic = dictionary(get_array('use-syn'), get_key('syn-mode', 'all'))
114
+ end
115
+
116
+ @number_of_expected_tokens_in_buffer = 3
117
+ @eof_handling = false
118
+ end
119
+
120
+ def control(cmd, par)
121
+ @mul_dic.report.each_pair { |key, value| set(key, value) } if cmd == STR_CMD_STATUS
122
+
123
+ # Jedes Control-Object ist auch Auslöser der Verarbeitung
124
+ if cmd == STR_CMD_RECORD || cmd == STR_CMD_EOF
125
+ @eof_handling = true
126
+ while number_of_valid_tokens_in_buffer > 1
127
+ process_buffer
128
+ end
129
+ forward_number_of_token( @buffer.size, false )
130
+ @eof_handling = false
131
+ end
132
+ end
133
+
134
+ def process_buffer?
135
+ number_of_valid_tokens_in_buffer >= @number_of_expected_tokens_in_buffer
136
+ end
137
+
138
+ def process_buffer
139
+ unless @buffer[0].form == CHAR_PUNCT
140
+ # Prüfe 3er Schlüssel
141
+ result = check_multiword_key( 3 )
142
+ unless result.empty?
143
+ # 3er Schlüssel gefunden
144
+ lengths = sort_result_len( result )
145
+ unless lengths[0] > 3
146
+ # Längster erkannter Schlüssel = 3
147
+ create_and_forward_multiword( 3, result )
148
+ forward_number_of_token( 3 )
149
+ return
150
+ else
151
+ # Längster erkannter Schlüssel > 3, Buffer voll genug?
152
+ unless @buffer.size >= lengths[0] || @eof_handling
153
+ @number_of_expected_tokens_in_buffer = lengths[0]
154
+ return
155
+ else
156
+ # Buffer voll genug, Verarbeitung kann beginnen
157
+ catch( :forward_one ) do
158
+ lengths.each do |len|
159
+ result = check_multiword_key( len )
160
+ unless result.empty?
161
+ create_and_forward_multiword( len, result )
162
+ forward_number_of_token( len )
163
+ throw :forward_one
164
+ end
165
+ end
166
+
167
+ # Keinen Match gefunden
168
+ forward_number_of_token( 1 )
169
+ end
170
+
171
+ @number_of_expected_tokens_in_buffer = 3
172
+ process_buffer if process_buffer?
173
+ return
174
+ end
175
+ end
176
+ end
177
+
178
+ # Prüfe 2er Schlüssel
179
+ result = check_multiword_key( 2 )
180
+ unless result.empty?
181
+ create_and_forward_multiword( 2, result )
182
+ forward_number_of_token( 1 )
183
+ end
184
+ end
185
+
186
+ # Buffer weiterschaufeln
187
+ forward_number_of_token( 1, false )
188
+ @number_of_expected_tokens_in_buffer = 3
189
+ end
190
+
191
+ private
192
+
193
+ def create_and_forward_multiword( len, lexicals )
194
+ # Form aus Buffer auslesen und Teile markieren
195
+ pos = 0
196
+ form_parts = []
197
+ begin
198
+ if @buffer[pos].form == CHAR_PUNCT
199
+ @buffer.delete_at( pos )
200
+ form_parts[-1] += CHAR_PUNCT
201
+ else
202
+ @buffer[pos].attr = WA_UNKMULPART if @buffer[pos].unknown?
203
+ form_parts << @buffer[pos].form
204
+ pos += 1
205
+ end
206
+ end while pos < len
207
+
208
+ form = form_parts.join( ' ' )
209
+
210
+ # Multiword erstellen
211
+ word = Word.new( form, WA_MULTIWORD )
212
+ word << lexicals.collect { |lex| (lex.is_a?(Lexical)) ? lex : nil }.compact # FIXME 1.60 - Ausstieg bei "*5" im Synonymer
213
+
214
+ # Forword Multiword
215
+ forward( word )
216
+ end
217
+
218
+ # Leitet 'len' Token weiter
219
+ def forward_number_of_token( len, count_punc = true )
220
+ begin
221
+ unless @buffer.empty?
222
+ forward( @buffer[0] )
223
+ len -= 1 unless count_punc && @buffer[0].form == CHAR_PUNCT
224
+ @buffer.delete_at( 0 )
225
+ end
226
+ end while len > 0
227
+ end
228
+
229
+ # Ermittelt die maximale Ergebnislänge
230
+ def sort_result_len( result )
231
+ result.collect do |res|
232
+ if res.is_a?( Lexical )
233
+ res.form.split( ' ' ).size
234
+ else
235
+ res =~ /^\*(\d+)/
236
+ $1.to_i
237
+ end
238
+ end.sort.reverse
239
+ end
240
+
241
+ # Prüft einen definiert langen Schlüssel ab Position 0 im Buffer
242
+ def check_multiword_key( len )
243
+ return [] if number_of_valid_tokens_in_buffer < len
244
+
245
+ # Wortformen aus der Wortliste auslesen
246
+ sequence = @buffer.map { |obj|
247
+ next [obj] unless obj.is_a?(WordForm)
248
+
249
+ form = obj.form
250
+ next if form == CHAR_PUNCT
251
+
252
+ word = @lex_dic.find_word(form)
253
+ word = @lex_gra.find_compositum(form) if word.unknown?
254
+
255
+ lexicals = word.attr == WA_KOMPOSITUM ?
256
+ [word.lexicals.first] : word.lexicals.dup
257
+
258
+ lexicals << word if lexicals.empty?
259
+ lexicals += @syn_dic.find_synonyms(word) if @syn_dic
260
+
261
+ lexicals.map { |lex| lex.form }.uniq
262
+ }.compact[0, len]
263
+
264
+ if @combine
265
+ keys, muls = [], []
266
+
267
+ sequence.each { |forms|
268
+ keys = forms.map { |form|
269
+ keys.empty? ? form : keys.map { |key| "#{key} #{form}" }
270
+ }.flatten(1)
271
+ }
272
+
273
+ keys.each { |key|
274
+ mul = @mul_dic.select(key.downcase)
275
+
276
+ unless mul.empty?
277
+ muls.concat(mul)
278
+ break unless @all_keys
279
+ end
280
+ }
281
+
282
+ muls.uniq
283
+ else
284
+ key = sequence.map { |forms| forms.first }.join(' ')
285
+ @mul_dic.select(key.downcase)
286
+ end
287
+ end
288
+
289
+ # Liefert die Anzahl gültiger Token zurück
290
+ def number_of_valid_tokens_in_buffer
291
+ @buffer.collect { |token| (token.form == CHAR_PUNCT) ? nil : 1 }.compact.size
292
+ end
293
+
294
+ end
295
+
296
+ # For backwards compatibility.
297
+ Multiworder = MultiWorder
298
+ Multi_worder = MultiWorder
299
+
300
+ end
301
+
302
+ end