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.
- data/.rspec +1 -0
- data/COPYING +663 -0
- data/ChangeLog +754 -0
- data/README +322 -0
- data/Rakefile +100 -0
- data/TODO +28 -0
- data/bin/lingo +5 -0
- data/bin/lingoctl +6 -0
- data/de.lang +121 -0
- data/de/lingo-abk.txt +74 -0
- data/de/lingo-dic.txt +56822 -0
- data/de/lingo-mul.txt +3209 -0
- data/de/lingo-syn.txt +14841 -0
- data/de/test_dic.txt +24 -0
- data/de/test_mul.txt +17 -0
- data/de/test_mul2.txt +2 -0
- data/de/test_singleword.txt +2 -0
- data/de/test_syn.txt +4 -0
- data/de/test_syn2.txt +1 -0
- data/de/user-dic.txt +10 -0
- data/en.lang +113 -0
- data/en/lingo-dic.txt +55434 -0
- data/en/lingo-mul.txt +456 -0
- data/en/user-dic.txt +5 -0
- data/info/Objekte.png +0 -0
- data/info/Typen.png +0 -0
- data/info/database.png +0 -0
- data/info/db_small.png +0 -0
- data/info/download.png +0 -0
- data/info/gpl-hdr.txt +27 -0
- data/info/kerze.png +0 -0
- data/info/language.png +0 -0
- data/info/lingo.png +0 -0
- data/info/logo.png +0 -0
- data/info/meeting.png +0 -0
- data/info/types.png +0 -0
- data/lib/lingo.rb +321 -0
- data/lib/lingo/attendee/abbreviator.rb +119 -0
- data/lib/lingo/attendee/debugger.rb +111 -0
- data/lib/lingo/attendee/decomposer.rb +101 -0
- data/lib/lingo/attendee/dehyphenizer.rb +167 -0
- data/lib/lingo/attendee/multiworder.rb +301 -0
- data/lib/lingo/attendee/noneword_filter.rb +103 -0
- data/lib/lingo/attendee/objectfilter.rb +86 -0
- data/lib/lingo/attendee/sequencer.rb +190 -0
- data/lib/lingo/attendee/synonymer.rb +105 -0
- data/lib/lingo/attendee/textreader.rb +237 -0
- data/lib/lingo/attendee/textwriter.rb +196 -0
- data/lib/lingo/attendee/tokenizer.rb +218 -0
- data/lib/lingo/attendee/variator.rb +185 -0
- data/lib/lingo/attendee/vector_filter.rb +158 -0
- data/lib/lingo/attendee/wordsearcher.rb +96 -0
- data/lib/lingo/attendees.rb +289 -0
- data/lib/lingo/cli.rb +62 -0
- data/lib/lingo/config.rb +104 -0
- data/lib/lingo/const.rb +131 -0
- data/lib/lingo/ctl.rb +173 -0
- data/lib/lingo/database.rb +587 -0
- data/lib/lingo/language.rb +530 -0
- data/lib/lingo/modules.rb +98 -0
- data/lib/lingo/types.rb +285 -0
- data/lib/lingo/utilities.rb +40 -0
- data/lib/lingo/version.rb +27 -0
- data/lingo-all.cfg +85 -0
- data/lingo-call.cfg +15 -0
- data/lingo.cfg +78 -0
- data/lingo.rb +3 -0
- data/lir.cfg +72 -0
- data/porter/stem.cfg +311 -0
- data/porter/stem.rb +150 -0
- data/spec/spec_helper.rb +0 -0
- data/test.cfg +79 -0
- data/test/attendee/ts_abbreviator.rb +35 -0
- data/test/attendee/ts_decomposer.rb +31 -0
- data/test/attendee/ts_multiworder.rb +390 -0
- data/test/attendee/ts_noneword_filter.rb +19 -0
- data/test/attendee/ts_objectfilter.rb +19 -0
- data/test/attendee/ts_sequencer.rb +43 -0
- data/test/attendee/ts_synonymer.rb +33 -0
- data/test/attendee/ts_textreader.rb +58 -0
- data/test/attendee/ts_textwriter.rb +98 -0
- data/test/attendee/ts_tokenizer.rb +32 -0
- data/test/attendee/ts_variator.rb +24 -0
- data/test/attendee/ts_vector_filter.rb +62 -0
- data/test/attendee/ts_wordsearcher.rb +119 -0
- data/test/lir.csv +3 -0
- data/test/lir.txt +12 -0
- data/test/lir2.txt +12 -0
- data/test/mul.txt +1 -0
- data/test/ref/artikel.mul +1 -0
- data/test/ref/artikel.non +159 -0
- data/test/ref/artikel.seq +270 -0
- data/test/ref/artikel.syn +16 -0
- data/test/ref/artikel.vec +928 -0
- data/test/ref/artikel.ven +928 -0
- data/test/ref/artikel.ver +928 -0
- data/test/ref/lir.csv +328 -0
- data/test/ref/lir.mul +1 -0
- data/test/ref/lir.non +274 -0
- data/test/ref/lir.seq +249 -0
- data/test/ref/lir.syn +94 -0
- data/test/test_helper.rb +113 -0
- data/test/ts_database.rb +269 -0
- data/test/ts_language.rb +396 -0
- data/txt/artikel-en.txt +157 -0
- data/txt/artikel.txt +170 -0
- data/txt/lir.txt +1317 -0
- 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
|