lingo 1.8.0

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 (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