lingo 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/.rspec +1 -0
  2. data/COPYING +663 -0
  3. data/ChangeLog +754 -0
  4. data/README +322 -0
  5. data/Rakefile +100 -0
  6. data/TODO +28 -0
  7. data/bin/lingo +5 -0
  8. data/bin/lingoctl +6 -0
  9. data/de.lang +121 -0
  10. data/de/lingo-abk.txt +74 -0
  11. data/de/lingo-dic.txt +56822 -0
  12. data/de/lingo-mul.txt +3209 -0
  13. data/de/lingo-syn.txt +14841 -0
  14. data/de/test_dic.txt +24 -0
  15. data/de/test_mul.txt +17 -0
  16. data/de/test_mul2.txt +2 -0
  17. data/de/test_singleword.txt +2 -0
  18. data/de/test_syn.txt +4 -0
  19. data/de/test_syn2.txt +1 -0
  20. data/de/user-dic.txt +10 -0
  21. data/en.lang +113 -0
  22. data/en/lingo-dic.txt +55434 -0
  23. data/en/lingo-mul.txt +456 -0
  24. data/en/user-dic.txt +5 -0
  25. data/info/Objekte.png +0 -0
  26. data/info/Typen.png +0 -0
  27. data/info/database.png +0 -0
  28. data/info/db_small.png +0 -0
  29. data/info/download.png +0 -0
  30. data/info/gpl-hdr.txt +27 -0
  31. data/info/kerze.png +0 -0
  32. data/info/language.png +0 -0
  33. data/info/lingo.png +0 -0
  34. data/info/logo.png +0 -0
  35. data/info/meeting.png +0 -0
  36. data/info/types.png +0 -0
  37. data/lib/lingo.rb +321 -0
  38. data/lib/lingo/attendee/abbreviator.rb +119 -0
  39. data/lib/lingo/attendee/debugger.rb +111 -0
  40. data/lib/lingo/attendee/decomposer.rb +101 -0
  41. data/lib/lingo/attendee/dehyphenizer.rb +167 -0
  42. data/lib/lingo/attendee/multiworder.rb +301 -0
  43. data/lib/lingo/attendee/noneword_filter.rb +103 -0
  44. data/lib/lingo/attendee/objectfilter.rb +86 -0
  45. data/lib/lingo/attendee/sequencer.rb +190 -0
  46. data/lib/lingo/attendee/synonymer.rb +105 -0
  47. data/lib/lingo/attendee/textreader.rb +237 -0
  48. data/lib/lingo/attendee/textwriter.rb +196 -0
  49. data/lib/lingo/attendee/tokenizer.rb +218 -0
  50. data/lib/lingo/attendee/variator.rb +185 -0
  51. data/lib/lingo/attendee/vector_filter.rb +158 -0
  52. data/lib/lingo/attendee/wordsearcher.rb +96 -0
  53. data/lib/lingo/attendees.rb +289 -0
  54. data/lib/lingo/cli.rb +62 -0
  55. data/lib/lingo/config.rb +104 -0
  56. data/lib/lingo/const.rb +131 -0
  57. data/lib/lingo/ctl.rb +173 -0
  58. data/lib/lingo/database.rb +587 -0
  59. data/lib/lingo/language.rb +530 -0
  60. data/lib/lingo/modules.rb +98 -0
  61. data/lib/lingo/types.rb +285 -0
  62. data/lib/lingo/utilities.rb +40 -0
  63. data/lib/lingo/version.rb +27 -0
  64. data/lingo-all.cfg +85 -0
  65. data/lingo-call.cfg +15 -0
  66. data/lingo.cfg +78 -0
  67. data/lingo.rb +3 -0
  68. data/lir.cfg +72 -0
  69. data/porter/stem.cfg +311 -0
  70. data/porter/stem.rb +150 -0
  71. data/spec/spec_helper.rb +0 -0
  72. data/test.cfg +79 -0
  73. data/test/attendee/ts_abbreviator.rb +35 -0
  74. data/test/attendee/ts_decomposer.rb +31 -0
  75. data/test/attendee/ts_multiworder.rb +390 -0
  76. data/test/attendee/ts_noneword_filter.rb +19 -0
  77. data/test/attendee/ts_objectfilter.rb +19 -0
  78. data/test/attendee/ts_sequencer.rb +43 -0
  79. data/test/attendee/ts_synonymer.rb +33 -0
  80. data/test/attendee/ts_textreader.rb +58 -0
  81. data/test/attendee/ts_textwriter.rb +98 -0
  82. data/test/attendee/ts_tokenizer.rb +32 -0
  83. data/test/attendee/ts_variator.rb +24 -0
  84. data/test/attendee/ts_vector_filter.rb +62 -0
  85. data/test/attendee/ts_wordsearcher.rb +119 -0
  86. data/test/lir.csv +3 -0
  87. data/test/lir.txt +12 -0
  88. data/test/lir2.txt +12 -0
  89. data/test/mul.txt +1 -0
  90. data/test/ref/artikel.mul +1 -0
  91. data/test/ref/artikel.non +159 -0
  92. data/test/ref/artikel.seq +270 -0
  93. data/test/ref/artikel.syn +16 -0
  94. data/test/ref/artikel.vec +928 -0
  95. data/test/ref/artikel.ven +928 -0
  96. data/test/ref/artikel.ver +928 -0
  97. data/test/ref/lir.csv +328 -0
  98. data/test/ref/lir.mul +1 -0
  99. data/test/ref/lir.non +274 -0
  100. data/test/ref/lir.seq +249 -0
  101. data/test/ref/lir.syn +94 -0
  102. data/test/test_helper.rb +113 -0
  103. data/test/ts_database.rb +269 -0
  104. data/test/ts_language.rb +396 -0
  105. data/txt/artikel-en.txt +157 -0
  106. data/txt/artikel.txt +170 -0
  107. data/txt/lir.txt +1317 -0
  108. metadata +211 -0
@@ -0,0 +1,587 @@
1
+ # encoding: utf-8
2
+
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
28
+ #++
29
+
30
+ require 'sdbm'
31
+ require 'pathname'
32
+ require 'fileutils'
33
+ require 'digest/sha1'
34
+
35
+ require_relative 'const'
36
+ require_relative 'types'
37
+ require_relative 'utilities'
38
+ require_relative 'modules'
39
+
40
+ class Lingo
41
+
42
+ class ShowProgress
43
+
44
+ def initialize(msg, active = true, out = $stderr)
45
+ @active, @out, format = active, out, ' [%3d%%]'
46
+
47
+ # To get the length of the formatted string we have
48
+ # to actually substitute the placeholder.
49
+ length = (format % 0).length
50
+
51
+ # Now we know how far to "go back" to
52
+ # overwrite the formatted string...
53
+ back = "\b" * length
54
+
55
+ @format = format + back
56
+ @clear = ' ' * length + back
57
+
58
+ print msg, ': '
59
+ end
60
+
61
+ def start(msg, max)
62
+ @ratio, @count, @next_step = max / 100.0, 0, 0
63
+ print msg, ' '
64
+ step
65
+ end
66
+
67
+ def stop(msg)
68
+ print @clear
69
+ print msg, "\n"
70
+ end
71
+
72
+ def tick(value)
73
+ @count = value
74
+ step if @count >= @next_step
75
+ end
76
+
77
+ private
78
+
79
+ def step
80
+ percent = @count / @ratio
81
+ @next_step = (percent + 1) * @ratio
82
+
83
+ print @format % percent
84
+ end
85
+
86
+ def print(*args)
87
+ @out.print(*args) if @active
88
+ end
89
+
90
+ end
91
+
92
+ # Crypter ermöglicht die Ver- und Entschlüsselung von Wörterbüchern
93
+
94
+ class Crypter
95
+
96
+ HEX_CHARS = '0123456789abcdef'.freeze
97
+
98
+ def digest(key)
99
+ Digest::SHA1.hexdigest(key)
100
+ end
101
+
102
+ def encode(key, val)
103
+ hex = ''
104
+
105
+ crypt(key, val).each_byte { |byte|
106
+ # To get a hex representation for a char we just utilize
107
+ # the quotient and the remainder of division by base 16.
108
+ q, r = byte.divmod(16)
109
+ hex << HEX_CHARS[q] << HEX_CHARS[r]
110
+ }
111
+
112
+ [digest(key), hex]
113
+ end
114
+
115
+ def decode(key, val)
116
+ str, q, first = '', 0, false
117
+
118
+ val.each_byte { |byte|
119
+ byte = byte.chr(ENC)
120
+
121
+ # Our hex chars are 2 bytes wide, so we have to keep track
122
+ # of whether it's the first or the second of the two.
123
+ if first = !first
124
+ q = HEX_CHARS.index(byte)
125
+ else
126
+ # Now we got both parts, so let's revert the divmod(16)
127
+ str << q * 16 + HEX_CHARS.index(byte)
128
+ end
129
+ }
130
+
131
+ crypt(key, str)
132
+ end
133
+
134
+ private
135
+
136
+ def crypt(k, v)
137
+ c, y = '', k.codepoints.reverse_each.cycle
138
+ v.each_codepoint { |x| c << (x ^ y.next).chr(ENC) }
139
+ c
140
+ end
141
+
142
+ end
143
+
144
+ # Die Klasse TxtFile stellt eine einheitliche Schnittstelle auf die unterschiedlichen Formate
145
+ # von Wörterbuch-Quelldateien bereit. Die Identifizierung der Quelldatei erfolgt über die ID
146
+ # der Datei, so wie sie in der Sprachkonfigurationsdatei <tt>de.lang</tt> unter
147
+ # <tt>language/dictionary/databases</tt> hinterlegt ist.
148
+ #
149
+ # Die Verarbeitung der Wörterbücher erfolgt mittels des Iterators <b>each</b>, der für jede
150
+ # Zeile der Quelldatei ein Array bereitstellt in der Form <tt>[ key, [val1, val2, ...] ]</tt>.
151
+ #
152
+ # Nicht korrekt erkannte Zeilen werden abgewiesen und in eine Revoke-Datei gespeichert, die
153
+ # an der Dateiendung <tt>.rev</tt> zu erkennen ist.
154
+
155
+ class TxtFile
156
+
157
+ attr_reader :position
158
+
159
+ def initialize(id, lingo)
160
+ # Konfiguration der Datenbank auslesen
161
+ @config = lingo.database_config(id)
162
+
163
+ source_file = Lingo.find(:dict, name = @config['name'])
164
+
165
+ @pn_source = Pathname.new(source_file)
166
+ @pn_reject = Pathname.new(Lingo.find(:store, source_file) << '.rev')
167
+
168
+ Lingo.error("No such source file `#{name}' for `#{id}'.") unless @pn_source.exist?
169
+
170
+ @wordclass = @config.fetch('def-wc', '?').downcase
171
+ @separator = @config['separator']
172
+
173
+ @legal_word = '(?:' + PRINTABLE_CHAR + '|[' + Regexp.escape('- /&()[].,') + '])+' # TODO: v1.60 - ',' bei TxtFile zulassen; in const.rb einbauen
174
+ @line_pattern = Regexp.new('^'+@legal_word+'$')
175
+
176
+ @position = 0
177
+ end
178
+
179
+ def size
180
+ @pn_source.size
181
+ end
182
+
183
+ def each
184
+ # Reject-Datei öffnen
185
+ fail_msg = "Fehler beim öffnen der Reject-Datei '#{@pn_reject.to_s}'"
186
+ reject_file = @pn_reject.open('w', encoding: ENC)
187
+
188
+ # Alle Zeilen der Quelldatei verarbeiten
189
+ fail_msg = "Fehler beim öffnen der Wörterbuch-Quelldatei '#{@pn_source.to_s}'"
190
+
191
+ @pn_source.each_line($/, encoding: ENC) do |raw_line|
192
+ @position += raw_line.size # Position innerhalb der Datei aktualisieren
193
+ line = raw_line.chomp.downcase # Zeile normieren
194
+
195
+ next if line =~ /^\s*\043/ || line.strip == '' # Kommentarzeilen und leere Zeilen überspringen
196
+
197
+ # Ungültige Zeilen protokollieren
198
+ unless line.length < 4096 && line =~ @line_pattern
199
+ fail_msg = "Fehler beim schreiben der Reject-Datei '#{@pn_reject.to_s}'"
200
+ reject_file.puts line
201
+ next
202
+ end
203
+
204
+ # Zeile in Werte konvertieren
205
+ yield convert_line(line, $1, $2)
206
+ end
207
+
208
+ fail_msg = "Fehler beim Schließen der Reject-Datei '#{@pn_reject.to_s}'"
209
+ reject_file.close
210
+ @pn_reject.delete if @pn_reject.size == 0
211
+
212
+ self
213
+ rescue RuntimeError
214
+ Lingo.error(fail_msg)
215
+ end
216
+
217
+ end
218
+
219
+ # Abgeleitet von TxtFile behandelt die Klasse Dateien mit dem Format <tt>SingleWord</tt>.
220
+ # Eine Zeile <tt>"Fachbegriff\n"</tt> wird gewandelt in <tt>[ 'fachbegriff', ['#s'] ]</tt>.
221
+ # Die Wortklasse kann über den Parameter <tt>def-wc</tt> beeinflusst werden.
222
+
223
+ class TxtFile_Singleword < TxtFile
224
+
225
+ def initialize(id, lingo)
226
+ super
227
+
228
+ @wc = @config.fetch('def-wc', 's').downcase
229
+ @mul_wc = @config.fetch('def-mul-wc', @wc).downcase
230
+
231
+ @line_pattern = %r{^(#{@legal_word})$}
232
+ end
233
+
234
+ private
235
+
236
+ def convert_line(line, key, val)
237
+ [key = key.strip, %W[##{key =~ /\s/ ? @mul_wc : @wc}]]
238
+ end
239
+
240
+ end
241
+
242
+ # Abgeleitet von TxtFile behandelt die Klasse Dateien mit dem Format <tt>KeyValue</tt>.
243
+ # Eine Zeile <tt>"Fachbegriff*Fachterminus\n"</tt> wird gewandelt in <tt>[ 'fachbegriff', ['fachterminus#s'] ]</tt>.
244
+ # Die Wortklasse kann über den Parameter <tt>def-wc</tt> beeinflusst werden.
245
+ # Der Trenner zwischen Schlüssel und Projektion kann über den Parameter <tt>separator</tt> geändert werden.
246
+
247
+ class TxtFile_Keyvalue < TxtFile
248
+
249
+ def initialize(id, lingo)
250
+ super
251
+
252
+ @separator = @config.fetch('separator', '*')
253
+ @line_pattern = Regexp.new('^(' + @legal_word + ')' + Regexp.escape(@separator) + '(' + @legal_word + ')$')
254
+ end
255
+
256
+ private
257
+
258
+ def convert_line(line, key, val)
259
+ key, val = key.strip, val.strip
260
+ val = '' if key == val
261
+ val = [val + '#' + @wordclass]
262
+ [key, val]
263
+ end
264
+
265
+ end
266
+
267
+ # Abgeleitet von TxtFile behandelt die Klasse Dateien mit dem Format <tt>WordClass</tt>.
268
+ # Eine Zeile <tt>"essen,essen #v essen #o esse #s\n"</tt> wird gewandelt in <tt>[ 'essen', ['esse#s', 'essen#v', 'essen#o'] ]</tt>.
269
+ # Der Trenner zwischen Schlüssel und Projektion kann über den Parameter <tt>separator</tt> geändert werden.
270
+
271
+ class TxtFile_Wordclass < TxtFile
272
+
273
+ def initialize(id, lingo)
274
+ super
275
+
276
+ @separator = @config.fetch('separator', ',')
277
+ @line_pattern = Regexp.new('^(' + @legal_word + ')' + Regexp.escape(@separator) + '((?:' + @legal_word + '\043\w)+)$')
278
+ end
279
+
280
+ private
281
+
282
+ def convert_line(line, key, val)
283
+ key, valstr = key.strip, val.strip
284
+ val = valstr.gsub(/\s+\043/, '#').scan(/\S.+?\s*\043\w/)
285
+ val = val.map do |str|
286
+ str =~ /^(.+)\043(.)/
287
+ ($1 == key ? '' : $1) + '#' + $2
288
+ end
289
+ [key, val]
290
+ end
291
+
292
+ end
293
+
294
+ # Abgeleitet von TxtFile behandelt die Klasse Dateien mit dem Format <tt>MultiValue</tt>.
295
+ # Eine Zeile <tt>"Triumph;Sieg;Erfolg\n"</tt> wird gewandelt in <tt>[ nil, ['triumph', 'sieg', 'erfolg'] ]</tt>.
296
+ # Der Trenner zwischen Schlüssel und Projektion kann über den Parameter <tt>separator</tt> geändert werden.
297
+
298
+ class TxtFile_Multivalue < TxtFile
299
+
300
+ def initialize(id, lingo)
301
+ super
302
+
303
+ @separator = @config.fetch('separator', ';')
304
+ @line_pattern = Regexp.new('^' + @legal_word + '(?:' + Regexp.escape(@separator) + @legal_word + ')*$')
305
+ end
306
+
307
+ private
308
+
309
+ def convert_line(line, key, val)
310
+ [nil, line.split(@separator).map { |value| value.strip }]
311
+ end
312
+
313
+ end
314
+
315
+ # Abgeleitet von TxtFile behandelt die Klasse Dateien mit dem Format <tt>MultiKey</tt>.
316
+ # Eine Zeile <tt>"Triumph;Sieg;Erfolg\n"</tt> wird gewandelt in <tt>[ 'triumph', ['sieg', 'erfolg'] ]</tt>.
317
+ # Die Sonderbehandlung erfolgt in der Klasse Txt2DbmConverter, wo daraus Schlüssel-Werte-Paare in der Form
318
+ # <tt>[ 'sieg', ['triumph'] ]</tt> und <tt>[ 'erfolg', ['triumph'] ]</tt> erzeugt werden.
319
+ # Der Trenner zwischen Schlüssel und Projektion kann über den Parameter <tt>separator</tt> geändert werden.
320
+
321
+ class TxtFile_Multikey < TxtFile
322
+
323
+ def initialize(id, lingo)
324
+ super
325
+
326
+ @separator = @config.fetch('separator', ';')
327
+ @line_pattern = Regexp.new('^' + @legal_word + '(?:' + Regexp.escape(@separator) + @legal_word + ')*$')
328
+ end
329
+
330
+ private
331
+
332
+ def convert_line(line, key, val)
333
+ values = line.split(@separator).map { |value| value.strip }
334
+ [values[0], values[1..-1]]
335
+ end
336
+
337
+ end
338
+
339
+ # Die Klasse DbmFile stellt eine einheitliche Schnittstelle auf Lingo-Datenbanken bereit.
340
+ # Die Identifizierung der Datenbank erfolgt über die ID der Datenbank, so wie sie in der
341
+ # Sprachkonfigurationsdatei <tt>de.lang</tt> unter <tt>language/dictionary/databases</tt>
342
+ # hinterlegt ist.
343
+ #
344
+ # Das Lesen und Schreiben der Datenbank erfolgt über die Funktionen []() und []=().
345
+
346
+ class DbmFile
347
+
348
+ include Cachable
349
+
350
+ INDEX_PATTERN = %r{\A#{Regexp.escape(IDX_REF)}\d+\z}
351
+
352
+ def self.open(*args)
353
+ dbm = new(*args)
354
+ dbm.open { yield dbm }
355
+ end
356
+
357
+ def initialize(id, lingo, read_mode = true)
358
+ @lingo = lingo
359
+
360
+ init_cachable
361
+
362
+ config = lingo.database_config(id)
363
+ raise "No such database `#{id}'." unless config && config.has_key?('name')
364
+
365
+ @id, @dbm = id, nil
366
+ @src_file = Lingo.find(:dict, config['name'])
367
+ @dbm_name = Lingo.find(:store, @src_file)
368
+
369
+ Txt2DbmConverter.new(id, lingo).convert if read_mode && !uptodate?
370
+
371
+ @crypter = config.has_key?('crypt') ? Crypter.new : nil
372
+
373
+ FileUtils.mkdir_p(File.dirname(@dbm_name))
374
+ end
375
+
376
+ # Überprüft die Aktualität des DbmFile
377
+ def uptodate?
378
+ begin
379
+ key = open { @dbm[SYS_KEY] }
380
+ rescue RuntimeError
381
+ end if File.exist?("#{@dbm_name}.pag")
382
+
383
+ key && (!(pn = Pathname.new(@src_file)).exist? || key == source_key(pn))
384
+ end
385
+
386
+ def open
387
+ if closed?
388
+ @dbm = SDBM.open(@dbm_name)
389
+ block_given? ? yield : self
390
+ else
391
+ Lingo.error("DbmFile #{@dbm_name} bereits geöffnet")
392
+ end
393
+ ensure
394
+ close if @dbm && block_given?
395
+ end
396
+
397
+ def to_h
398
+ hash = {}
399
+
400
+ @dbm.each { |key, val|
401
+ [key, val].each { |x| x.encode!(ENC) }
402
+ hash[key.freeze] = val
403
+ } unless closed?
404
+
405
+ hash
406
+ end
407
+
408
+ def clear
409
+ files = %w[pag dir].map { |ext| "#{@dbm_name}.#{ext}" }
410
+
411
+ if closed?
412
+ files.each { |file| File.delete(file) if File.exist?(file) }
413
+ else
414
+ close
415
+ files.each { |file| File.delete(file) }
416
+ open
417
+ end
418
+
419
+ self
420
+ end
421
+
422
+ def close
423
+ unless closed?
424
+ @dbm.close
425
+ @dbm = nil
426
+
427
+ self
428
+ else
429
+ #Lingo.error("DbmFile #{@dbm_name} nicht geöffnet")
430
+ end
431
+ end
432
+
433
+ def closed?
434
+ @dbm.nil? || @dbm.closed?
435
+ end
436
+
437
+ def [](key)
438
+ return if closed?
439
+
440
+ if val = _get(key)
441
+ # Äquvalenzklassen behandeln
442
+ val.split(FLD_SEP).map { |v|
443
+ v =~ INDEX_PATTERN ? _get(v) : v
444
+ }.compact.join(FLD_SEP).split(FLD_SEP)
445
+ end
446
+ end
447
+
448
+ def []=(key, val)
449
+ return if closed?
450
+
451
+ val += retrieve(key) if hit?(key)
452
+
453
+ store(key, val = val.sort.uniq)
454
+ _set(key, val.join(FLD_SEP))
455
+ end
456
+
457
+ def set_source_file(filename)
458
+ return if closed?
459
+ @dbm[SYS_KEY] = source_key(Pathname.new(Lingo.find(:dict, filename)))
460
+ end
461
+
462
+ private
463
+
464
+ def _get(key)
465
+ if val = @dbm[@crypter ? @crypter.digest(key) : key]
466
+ val.encode!(ENC)
467
+ @crypter ? @crypter.decode(key, val) : val
468
+ end
469
+ end
470
+
471
+ def _set(key, val)
472
+ key, val = @crypter.encode(key, val) if @crypter
473
+ @dbm[key] = (val.length < 950) ? val : val[0, 950]
474
+ end
475
+
476
+ def source_key(src)
477
+ [src.size, src.mtime].join(FLD_SEP)
478
+ end
479
+
480
+ end
481
+
482
+ # Die Klasse Txt2DbConverter steuert die Konvertierung von Wörterbuch-Quelldateien in
483
+ # Lingo-Datenbanken. Die Identifizierung der Quelldatei erfolgt über die ID
484
+ # der Datei, so wie sie in der Sprachkonfigurationsdatei <tt>de.lang</tt> unter
485
+ # <tt>language/dictionary/databases</tt> hinterlegt ist.
486
+
487
+ class Txt2DbmConverter
488
+
489
+ def initialize(id, lingo, verbose = lingo.config.stderr.tty?)
490
+ # Konfiguration der Datenbanken auslesen
491
+ @config, @index = lingo.database_config(id), 0
492
+
493
+ # Objekt für Quelldatei erzeugen
494
+ @format = @config.fetch( 'txt-format', 'KeyValue' ).downcase
495
+ @source = case @format
496
+ when 'singleword' then TxtFile_Singleword
497
+ when 'keyvalue' then TxtFile_Keyvalue
498
+ when 'wordclass' then TxtFile_Wordclass
499
+ when 'multivalue' then TxtFile_Multivalue
500
+ when 'multikey' then TxtFile_Multikey
501
+ else
502
+ Lingo.error("Unbekanntes Textformat '#{config['txt-format'].downcase}' bei '#{'language/dictionary/databases/' + id}'")
503
+ end.new(id, lingo)
504
+
505
+ # Zielobjekt erzeugen
506
+ @destination = DbmFile.new(id, lingo, false)
507
+
508
+ # Ausgabesteuerung
509
+ @progress = ShowProgress.new(@config['name'], verbose, lingo.config.stderr)
510
+
511
+ # Lexikalisierungen für Mehrwortgruppen vorbereiten
512
+ lex_dic = @config['use-lex']
513
+ lex_mod = @config['lex-mode']
514
+
515
+ begin
516
+ @lexicalize = true
517
+ @dictionary = Dictionary.new({ 'source' => lex_dic.split(STRING_SEPERATOR_PATTERN), 'mode' => lex_mod }, lingo)
518
+ @grammar = Grammar.new({ 'source' => lex_dic.split(STRING_SEPERATOR_PATTERN), 'mode' => lex_mod }, lingo)
519
+ rescue RuntimeError
520
+ Lingo.error("Auf das Wörterbuch (#{lex_dic}) für die Lexikalisierung der Mehrwortgruppen in (#{@config['name']}) konnte nicht zugegriffen werden")
521
+ end if lex_dic
522
+ end
523
+
524
+ def convert
525
+ @progress.start('convert', @source.size)
526
+
527
+ @destination.open
528
+ @destination.clear
529
+
530
+ @source.each do |key, value|
531
+ @progress.tick(@source.position)
532
+
533
+ # Behandle Mehrwortschlüssel
534
+ if @lexicalize && key =~ / /
535
+ # Schlüssel in Grundform wandeln
536
+ gkey = key.split(' ').map do |form|
537
+
538
+ # => Wortform ohne Satzendepunkt benutzen
539
+ wordform = form.gsub(/\.$/, '')
540
+
541
+ # => Wort suchen
542
+ result = @dictionary.find_word(wordform)
543
+
544
+ # => Kompositum suchen, wenn Wort nicht erkannt
545
+ if result.attr == WA_UNKNOWN
546
+ result = @grammar.find_compositum(wordform)
547
+ compo = result.compo_form
548
+ end
549
+
550
+ compo ? compo.form : result.norm
551
+ end.join(' ')
552
+
553
+ skey = gkey.split
554
+ # Zusatzschlüssel einfügen, wenn Anzahl Wörter > 3
555
+ @destination[skey[0...3].join(' ')] = [KEY_REF + skey.size.to_s] if skey.size > 3
556
+
557
+ value = value.map { |v| v =~ /^\043/ ? key + v : v }
558
+ key = gkey
559
+ end
560
+
561
+ # Format Sonderbehandlungen
562
+ key.gsub!(/\.$/, '') if key
563
+ case @format
564
+ when 'multivalue' # Äquvalenzklassen behandeln
565
+ key = IDX_REF + @index.to_s
566
+ @index += 1
567
+ @destination[key] = value
568
+ value.each { |v| @destination[v] = [key] }
569
+ when 'multikey' # Äquvalenzklassen behandeln
570
+ value.each { |v| @destination[v] = [key] }
571
+ else
572
+ @destination[key] = value
573
+ end
574
+
575
+ end
576
+
577
+ @destination.set_source_file(@config['name'])
578
+ @destination.close
579
+
580
+ @progress.stop('ok')
581
+
582
+ self
583
+ end
584
+
585
+ end
586
+
587
+ end