lingo 1.8.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,62 @@
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 Database
30
+
31
+ class Source
32
+
33
+ # Abgeleitet von Source behandelt die Klasse Dateien mit dem Format <tt>KeyValue</tt>.
34
+ # Eine Zeile <tt>"Fachbegriff*Fachterminus\n"</tt> wird gewandelt in <tt>[ 'fachbegriff', ['fachterminus#s'] ]</tt>.
35
+ # Die Wortklasse kann über den Parameter <tt>def-wc</tt> beeinflusst werden.
36
+ # Der Trenner zwischen Schlüssel und Projektion kann über den Parameter <tt>separator</tt> geändert werden.
37
+
38
+ class KeyValue < self
39
+
40
+ def initialize(id, lingo)
41
+ super
42
+
43
+ @separator = @config.fetch('separator', '*')
44
+ @line_pattern = Regexp.new('^(' + @legal_word + ')' + Regexp.escape(@separator) + '(' + @legal_word + ')$')
45
+ end
46
+
47
+ private
48
+
49
+ def convert_line(line, key, val)
50
+ key, val = key.strip, val.strip
51
+ val = '' if key == val
52
+ val = [val + '#' + @wordclass]
53
+ [key, val]
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
+ 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 Database
30
+
31
+ class Source
32
+
33
+ # Abgeleitet von Source behandelt die Klasse Dateien mit dem Format <tt>MultiKey</tt>.
34
+ # Eine Zeile <tt>"Triumph;Sieg;Erfolg\n"</tt> wird gewandelt in <tt>[ 'triumph', ['sieg', 'erfolg'] ]</tt>.
35
+ # Die Sonderbehandlung erfolgt in der Methode Database#convert, wo daraus Schlüssel-Werte-Paare in der Form
36
+ # <tt>[ 'sieg', ['triumph'] ]</tt> und <tt>[ 'erfolg', ['triumph'] ]</tt> erzeugt werden.
37
+ # Der Trenner zwischen Schlüssel und Projektion kann über den Parameter <tt>separator</tt> geändert werden.
38
+
39
+ class MultiKey < self
40
+
41
+ def initialize(id, lingo)
42
+ super
43
+
44
+ @separator = @config.fetch('separator', ';')
45
+ @line_pattern = Regexp.new('^' + @legal_word + '(?:' + Regexp.escape(@separator) + @legal_word + ')*$')
46
+ end
47
+
48
+ def set(db, key, val)
49
+ val.each { |v| db[v] = [key] }
50
+ end
51
+
52
+ private
53
+
54
+ def convert_line(line, key, val)
55
+ values = line.split(@separator).map { |value| value.strip }
56
+ [values[0], values[1..-1]]
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ 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 Database
30
+
31
+ class Source
32
+
33
+ # Abgeleitet von Source behandelt die Klasse Dateien mit dem Format <tt>MultiValue</tt>.
34
+ # Eine Zeile <tt>"Triumph;Sieg;Erfolg\n"</tt> wird gewandelt in <tt>[ nil, ['triumph', 'sieg', 'erfolg'] ]</tt>.
35
+ # Der Trenner zwischen Schlüssel und Projektion kann über den Parameter <tt>separator</tt> geändert werden.
36
+
37
+ class MultiValue < self
38
+
39
+ def initialize(id, lingo)
40
+ super
41
+
42
+ @separator = @config.fetch('separator', ';')
43
+ @line_pattern = Regexp.new('^' + @legal_word + '(?:' + Regexp.escape(@separator) + @legal_word + ')*$')
44
+
45
+ @idx = -1
46
+ end
47
+
48
+ def set(db, key, val)
49
+ db[key = "#{IDX_REF}#{@idx += 1}"] = val
50
+ val.each { |v| db[v] = [key] }
51
+ end
52
+
53
+ private
54
+
55
+ def convert_line(line, key, val)
56
+ [nil, line.split(@separator).map { |value| value.strip }]
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,60 @@
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 Database
30
+
31
+ class Source
32
+
33
+ # Abgeleitet von Source behandelt die Klasse Dateien mit dem Format <tt>SingleWord</tt>.
34
+ # Eine Zeile <tt>"Fachbegriff\n"</tt> wird gewandelt in <tt>[ 'fachbegriff', ['#s'] ]</tt>.
35
+ # Die Wortklasse kann über den Parameter <tt>def-wc</tt> beeinflusst werden.
36
+
37
+ class SingleWord < self
38
+
39
+ def initialize(id, lingo)
40
+ super
41
+
42
+ @wc = @config.fetch('def-wc', 's').downcase
43
+ @mul_wc = @config.fetch('def-mul-wc', @wc).downcase
44
+
45
+ @line_pattern = %r{^(#{@legal_word})$}
46
+ end
47
+
48
+ private
49
+
50
+ def convert_line(line, key, val)
51
+ [key = key.strip, %W[##{key =~ /\s/ ? @mul_wc : @wc}]]
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,64 @@
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 Database
30
+
31
+ class Source
32
+
33
+ # Abgeleitet von Source behandelt die Klasse Dateien mit dem Format <tt>WordClass</tt>.
34
+ # Eine Zeile <tt>"essen,essen #v essen #o esse #s\n"</tt> wird gewandelt in <tt>[ 'essen', ['esse#s', 'essen#v', 'essen#o'] ]</tt>.
35
+ # Der Trenner zwischen Schlüssel und Projektion kann über den Parameter <tt>separator</tt> geändert werden.
36
+
37
+ class WordClass < self
38
+
39
+ def initialize(id, lingo)
40
+ super
41
+
42
+ @separator = @config.fetch('separator', ',')
43
+ @line_pattern = Regexp.new('^(' + @legal_word + ')' + Regexp.escape(@separator) + '((?:' + @legal_word + '#\w)+)$')
44
+ end
45
+
46
+ private
47
+
48
+ def convert_line(line, key, val)
49
+ key, valstr = key.strip, val.strip
50
+ val = valstr.gsub(/\s+#/, '#').scan(/\S.+?\s*#\w/)
51
+ val = val.map do |str|
52
+ str =~ /^(.+)#(.)/
53
+ ($1 == key ? '' : $1) + '#' + $2
54
+ end
55
+ [key, val]
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,122 @@
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 LingoError < StandardError; end
30
+
31
+ class NoWritableStoreError < LingoError
32
+
33
+ attr_reader :file, :path
34
+
35
+ def initialize(file, path)
36
+ @file, @path = file, path
37
+ end
38
+
39
+ def to_s
40
+ 'No writable store found in search path'
41
+ end
42
+
43
+ end
44
+
45
+ class ConfigError < LingoError
46
+
47
+ attr_reader :id
48
+
49
+ def initialize(id)
50
+ @id = id
51
+ end
52
+
53
+ end
54
+
55
+ class ConfigLoadError < ConfigError
56
+
57
+ attr_reader :err
58
+
59
+ def initialize(err)
60
+ @err = err
61
+ end
62
+
63
+ def to_s
64
+ "Error loading config: #{err}"
65
+ end
66
+
67
+ end
68
+
69
+ class NoDatabaseConfigError < ConfigError
70
+
71
+ def to_s
72
+ "No such database `#{id}' defined."
73
+ end
74
+
75
+ end
76
+
77
+ class InvalidDatabaseConfigError < ConfigError
78
+
79
+ def to_s
80
+ "Invalid database configuration `#{id}'."
81
+ end
82
+
83
+ end
84
+
85
+ class MissingConfigError < ConfigError
86
+
87
+ def to_s
88
+ "Missing configuration for `#{id}'."
89
+ end
90
+
91
+ end
92
+
93
+ class FileNotFoundError < LingoError
94
+
95
+ attr_reader :name
96
+
97
+ def initialize(name)
98
+ @name = name
99
+ end
100
+
101
+ def to_s
102
+ "No such file `#{name}'."
103
+ end
104
+
105
+ end
106
+
107
+ class SourceFileNotFoundError < FileNotFoundError
108
+
109
+ attr_reader :id
110
+
111
+ def initialize(name, id)
112
+ super(name)
113
+ @id = id
114
+ end
115
+
116
+ def to_s
117
+ "No such source file `#{name}' for `#{id}'."
118
+ end
119
+
120
+ end
121
+
122
+ end
@@ -1,529 +1,89 @@
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
- require_relative 'const'
31
- require_relative 'modules'
32
- require_relative 'database'
27
+ require_relative 'language/lexical_hash'
28
+ require_relative 'language/dictionary'
29
+ require_relative 'language/grammar'
30
+ require_relative 'language/word_form'
31
+ require_relative 'language/token'
32
+ require_relative 'language/lexical'
33
+ require_relative 'language/word'
33
34
 
34
35
  class Lingo
35
36
 
36
- # Die Klasse LexicalHash ermöglicht den Zugriff auf die Lingodatenbanken. Im Gegensatz zur
37
- # Klasse DbmFile, welche nur Strings als Ergebnis zurück gibt, wird hier als Ergebnis ein
38
- # Array von Lexical-Objekten zurück gegeben.
39
-
40
- class LexicalHash
41
-
42
- include Cachable
43
- include Reportable
44
-
45
- def initialize(id, lingo)
46
- init_reportable
47
- init_cachable
48
- report_prefix( id )
49
-
50
- # Parameter aus de.lang:language/dictionary/databases auslesen
51
- config = lingo.config['language/dictionary/databases/' + id]
52
- Lingo.error( "LexicalHash kann Datenquelle mit ID '#{id}' in de.lang:language/dictionary/databases' nicht finden" ) if config.nil?
53
-
54
- @wordclass = config.fetch( 'def-wc', LA_UNKNOWN )
55
-
56
- # Store erzeugen
57
- @source = DbmFile.new(id, lingo)
58
- @source.open
59
- end
60
-
61
- def close
62
- @source.close
63
- end
64
-
65
- def [](ikey)
66
- # Schlüssel normalisieren
67
- inc('total requests')
68
- key = ikey.downcase
69
-
70
- # Cache abfragen
71
- if hit?(key)
72
- inc('cache hits')
73
- return retrieve(key)
74
- end
75
-
76
- # Wert aus Datenbank lesen
77
- inc('source reads')
78
- record = @source[key]
79
-
80
- # Werte in interne Objekte umwandeln
81
- record = record.collect do |str|
82
- case str
83
- when /^\*\d+$/
84
- # Hinweis für Multiworder, dass Multiword mit (\d) Wörtern länge zu prüfen ist
85
- str
86
- when /^#(.)$/
87
- # Alleinige Angabe der Wortklasse => Ergebniswort ist gleich dem Schlüssel
88
- Lexical.new(key, $1)
89
- when /^([^#]+?)\s*#(.)$/
90
- # Angabe Ergebniswort und Wortklasse
91
- Lexical.new($1, $2)
92
- when /^([^#]+)$/
93
- # Angabe Ergebniswort ohne Wortklasse
94
- Lexical.new($1, @wordclass)
95
- else
96
- str
97
- end
98
- end.compact.sort.uniq unless record.nil?
99
-
100
- # Ergebnis zurückgeben
101
- inc('data found') unless record.nil?
102
- store(key, record)
103
- end
104
-
105
- end
106
-
107
- class Dictionary
108
-
109
- include Cachable
110
- include Reportable
111
-
112
- def initialize(config, lingo)
113
- init_reportable
114
- init_cachable
115
-
116
- dictionary_config = lingo.dictionary_config
117
-
118
- # Parameter prüfen
119
- raise "Keine Sprach-Konfiguration angegeben!" if dictionary_config.nil?
120
- raise "Keine Parameter angegeben!" if config.nil?
121
- raise "Keine Datenquellen angegeben!" unless config.has_key?('source')
122
-
123
- # Parameter auslesen
124
- @all_sources = (config['mode'].nil? || config['mode'].downcase=='all')
125
-
126
- @sources = config['source'].map { |src| LexicalHash.new(src, lingo) }
127
-
128
- lingo.dictionaries << self
129
-
130
- # Parameter aus de.lang:language/dictionary auslesen
131
- @suffixes = []
132
- @infixes = []
133
-
134
- dictionary_config['suffix'].each {|arr|
135
- typ, sufli = arr
136
- typ.downcase!
137
- sufli.split.each {|suf|
138
- su, ex = suf.split('/')
139
- fix = [Regexp.new(su+'$', 'i'), ex.nil? ? '*' : ex, typ]
140
- (typ=='f' ? @infixes : @suffixes) << fix
141
- }
142
- } if dictionary_config.has_key?( 'suffix' )
143
- end
144
-
145
- def close
146
- @sources.each(&:close)
147
- end
148
-
149
- def report
150
- super.tap { |rep| @sources.each { |src| rep.update(src.report) } }
151
- end
152
-
153
- # _dic_.find_word( _aString_ ) -> _aNewWord_
154
- #
155
- # Erstellt aus dem String ein Wort und sucht nach diesem im Wörterbuch.
156
- def find_word(string)
157
- # Cache abfragen
158
- key = string.downcase
159
- if hit?(key)
160
- inc('cache hits')
161
- word = retrieve(key)
162
- word.form = string
163
- return word
164
- end
165
-
166
- word = Word.new(string, WA_UNKNOWN)
167
- lexicals = select_with_suffix(string)
168
- unless lexicals.empty?
169
- word.lexicals = lexicals
170
- word.attr = WA_IDENTIFIED
171
- end
172
- store(key, word)
173
- end
174
-
175
- def find_synonyms(obj)
176
- # alle Lexicals des Wortes
177
- lexis = obj.lexicals
178
- lexis = [obj] if lexis.empty? && obj.attr==WA_UNKNOWN
179
- # alle gefundenen Synonyme
180
- synos = []
181
- # multiworder optimization
182
- key_ref = %r{\A#{Regexp.escape(KEY_REF)}\d+}o
183
-
184
- lexis.each do |lex|
185
- # Synonyme für Teile eines Kompositum ausschließen
186
- next if obj.attr==WA_KOMPOSITUM && lex.attr!=LA_KOMPOSITUM
187
- # Synonyme für Synonyme ausschließen
188
- next if lex.attr==LA_SYNONYM
189
-
190
- select(lex.form).each do |syn|
191
- synos << syn unless syn =~ key_ref
192
- end
193
- end
194
-
195
- synos
196
- end
197
-
198
- # _dic_.select( _aString_ ) -> _ArrayOfLexicals_
199
- #
200
- # Sucht alle Wörterbücher durch und gibt den ersten Treffer zurück (+mode = first+), oder alle Treffer (+mode = all+)
201
- def select(string)
202
- lexicals = []
203
-
204
- @sources.each { |src|
205
- if lexis = src[string]
206
- lexicals += lexis
207
- break unless @all_sources
208
- end
209
- }
210
-
211
- lexicals.sort.uniq
212
- end
213
-
214
- # _dic_.select_with_suffix( _aString_ ) -> _ArrayOfLexicals_
215
- #
216
- # Sucht alle Wörterbücher durch und gibt den ersten Treffer zurück (+mode = first+), oder alle Treffer (+mode = all+).
217
- # Sucht dabei auch Wörter, die um wortklassenspezifische Suffixe bereinigt wurden.
218
- def select_with_suffix(string)
219
- lexicals = select(string)
220
- if lexicals.empty?
221
- suffix_lexicals(string).each { |suflex|
222
- select(suflex.form).each { |srclex|
223
- lexicals << srclex if suflex.attr == srclex.attr
224
- }
225
- }
226
- end
227
- lexicals
228
- end
229
-
230
- # _dic_.select_with_infix( _aString_ ) -> _ArrayOfLexicals_
231
- #
232
- # Sucht alle Wörterbücher durch und gibt den ersten Treffer zurück (+mode = first+), oder alle Treffer (+mode = all+).
233
- # Sucht dabei auch Wörter, die eine Fugung am Ende haben.
234
- def select_with_infix(string)
235
- lexicals = select(string)
236
- if lexicals.size == 0
237
- infix_lexicals(string).each { |inlex|
238
- select(inlex.form).each { |srclex|
239
- lexicals << srclex
240
- }
241
- }
242
- end
243
- lexicals
244
- end
245
-
246
- # _dic_.suffix_lexicals( _aString_ ) -> _ArrayOfLexicals_
247
- #
248
- # Gibt alle möglichen Lexicals zurück, die von der Endung her auf den String anwendbar sind:
249
- #
250
- # dic.suffix_lexicals("Hasens") -> [(hasen/s), (hasen/e), (has/e)]
251
- def suffix_lexicals(string)
252
- lexicals = []
253
- newform = regex = ext = type = nil
254
- @suffixes.each { |suf|
255
- regex, ext, type = suf
256
- if string =~ regex
257
- newform = $`+((ext=="*")?'':ext)+$'
258
- lexicals << Lexical.new(newform, type)
259
- end
260
- }
261
- lexicals
262
- end
263
-
264
- # _dic_.gap_lexicals( _aString_ ) -> _ArrayOfLexicals_
265
- #
266
- # Gibt alle möglichen Lexicals zurück, die von der Endung her auf den String anwendbar sind:
267
- def infix_lexicals(string)
268
- lexicals = []
269
- newform = regex = ext = type = nil
270
- @infixes.each { |suf|
271
- regex, ext, type = suf
272
- if string =~ regex
273
- newform = $`+((ext=="*")?'':ext)+$'
274
- lexicals << Lexical.new(newform, type)
275
- end
276
- }
277
- lexicals
278
- end
279
-
280
- end
281
-
282
- class Compositum
283
- end
284
-
285
- # Die Klasse Grammar beinhaltet grammatikalische Spezialitäten einer Sprache. Derzeit findet die
286
- # Kompositumerkennung hier ihren Platz, die mit der Methode find_compositum aufgerufen werden kann.
287
- # Die Klasse Grammar wird genau wie ein Dictionary initialisiert. Das bei der Initialisierung angegebene Wörterbuch ist Grundlage
288
- # für die Erkennung der Kompositumteile.
289
-
290
- class Grammar
291
-
292
- # Ergebnisse der Kompositumerkennung werden gespeichert und bei erneutem Aufruf mit gleichem Suchwort genutzt
293
- include Cachable
294
-
295
- # Die Verarbeitung wird statistisch erfasst und mit der Option -s angezeigt
296
- include Reportable
297
-
298
- # initialize(config, dictionary_config) -> _Grammar_
299
- # config = Attendee-spezifische Parameter
300
- # dictionary_config = Datenbankkonfiguration aus de.lang
301
- def initialize(config, lingo)
302
- init_reportable
303
- init_cachable
304
-
305
- @dictionary = Dictionary.new(config, lingo)
306
-
307
- # Sprachspezifische Einstellungen für Kompositumverarbeitung laden (die nachfolgenden Werte können in der
308
- # Konfigurationsdatei de.lang nach belieben angepasst werden)
309
- comp = lingo.dictionary_config['compositum']
310
-
311
- # Ein Wort muss mindestens 8 Zeichen lang sein, damit überhaupt eine Prüfung stattfindet.
312
- @comp_min_word_size = (comp['min-word-size'] || '8').to_i
313
-
314
- # Die durchschnittliche Länge der Kompositum-Wortteile muss mindestens 4 Zeichen lang sein, sonst ist es kein
315
- # gültiges Kompositum.
316
- @comp_min_avg_part_size = (comp['min-avg-part-size'] || '4').to_i
317
-
318
- # Der kürzeste Kompositum-Wortteil muss mindestens 1 Zeichen lang sein
319
- @comp_min_part_size = (comp['min-part-size'] || '1').to_i
320
-
321
- # Ein Kompositum darf aus höchstens 4 Wortteilen bestehen
322
- @comp_max_parts = (comp['max-parts'] || '4').to_i
323
-
324
- # Die Wortklasse eines Kompositum-Wortteils kann separat gekennzeichnet werden, um sie von Wortklassen normaler Wörter
325
- # unterscheiden zu können z.B. Hausmeister => ['haus/s', 'meister/s'] oder Hausmeister => ['haus/s+', 'meister/s+'] mit
326
- # append-wordclass = '+'
327
- @append_wc = comp.fetch( 'append-wordclass', '' )
328
-
329
- # Bestimmte Sequenzen können als ungültige Komposita erkannt werden, z.B. ist ein Kompositum aus zwei Adjetiven kein
330
- # Kompositum, also skip-sequence = 'aa'
331
- @sequences = comp.fetch( 'skip-sequences', [] ).collect { |sq| sq.downcase }
332
-
333
- # Liste der Vorschläge für eine Zerlegung
334
- @suggestions = []
335
- end
336
-
337
- def close
338
- @dictionary.close
339
- end
340
-
341
- alias_method :report_grammar, :report
342
-
343
- def report
344
- rep = report_grammar
345
- rep.update(@dictionary.report)
346
- rep
347
- end
348
-
349
- # find_compositum(string) -> word wenn level=1
350
- # find_compositum(string) -> [lexicals, stats] wenn level!=1
351
- #
352
- # find_compositum arbeitet in verschiedenen Leveln, da die Methode auch rekursiv aufgerufen wird. Ein Level größer 1
353
- # entspricht daher einem rekursiven Aufruf
354
- def find_compositum(string, level=1, has_tail=false)
355
- # Prüfen, ob string bereits auf Kompositum getestet wurde. Wenn ja, dann Ergebnis des letztes Aufrufs zurück geben.
356
- key = string.downcase
357
- if level == 1 && hit?(key)
358
- inc('cache hits')
359
- return retrieve(key)
360
- end
361
-
362
- # Ergebnis vorbelegen
363
- comp = Word.new(string, WA_UNKNOWN)
364
-
365
- # Validitätsprüfung: nur Strings mit Mindestlänge auf Kompositum prüfen
366
- if string.size <= @comp_min_word_size
367
- inc('String zu kurz')
368
- return (level==1) ? comp : [[],[],'']
369
- end
370
-
371
- # Kompositumerkennung initialisieren
372
- inc('Komposita geprüft')
373
- stats, lexis, seqs = permute_compositum(string.downcase, level, has_tail)
374
-
375
- if level==1
376
- # Auf Level 1 Kompositum zurück geben
377
- if lexis.size > 0 && is_valid?( string, stats, lexis, seqs )
378
- inc('Komposita erkannt')
379
- comp.attr = WA_KOMPOSITUM
380
- comp.lexicals = lexis.collect do |lex|
381
- (lex.attr==LA_KOMPOSITUM) ? lex : Lexical.new(lex.form, lex.attr+@append_wc)
382
- end
383
- end
384
-
385
- return store(key, comp)
386
- end
387
-
388
- # Validitätsprüfung
389
- if lexis.size > 0 && is_valid?(string, stats, lexis, seqs)
390
- [stats, lexis, seqs]
391
- else
392
- [[],[],'']
393
- end
394
- end
395
-
396
- private
397
-
398
- def is_valid?(string, stats, lexis, seqs)
399
- is_valid = true
400
- is_valid &&= (stats.size <= @comp_max_parts)
401
- is_valid &&= (stats.sort[0] >= @comp_min_part_size)
402
- is_valid &&= (string.size/stats.size) >= @comp_min_avg_part_size
403
- is_valid &&= @sequences.index( seqs ).nil? unless @sequences.empty?
404
- is_valid
405
- end
406
-
407
- # permute_string( _aString_ ) -> [lexicals, stats, seqs]
408
- def permute_compositum(string, level, has_tail)
409
- @suggestions[level] = [] if @suggestions[level].nil?
410
-
411
- # Finde letzten Bindesstrich im Wort
412
- if string =~ /^(.+)-([^-]+)$/
413
- test_compositum($1, '-', $2, level, has_tail)
414
- else
415
- length = string.length
416
-
417
- # Wortteilungen testen
418
- 1.upto(length - 1) do |p|
419
- # String teilen und testen
420
- fr_str, ba_str = string.slice(0...p), string.slice(p...length)
421
- stats, lexis, seqs = test_compositum(fr_str, '', ba_str, level, has_tail)
422
-
423
- unless lexis.empty?
424
- if lexis[-1].attr==LA_TAKEITASIS
425
- # => halbes Kompositum
426
- @suggestions[level] << [stats, lexis, seqs]
427
- else
428
- # => ganzes Kompositum
429
- return [stats, lexis, seqs]
430
- end
431
- end
432
- end
433
-
434
- # alle Wortteilungen durchprobiert und noch immer kein definitives Kompositum erkannt. Dann nehme besten Vorschlag.
435
- if @suggestions[level].empty?
436
- [[],[],'']
437
- else
438
- stats, lexis, seqs = @suggestions[level][0]
439
- @suggestions[level].clear
440
- [stats, lexis, seqs]
441
- end
442
- end
443
- end
444
-
445
- # test_compositum() -> [stats, lexicals, seq]
446
- #
447
- # Testet einen definiert zerlegten String auf Kompositum
448
- def test_compositum(front_string, infix, back_string, level, has_tail)
449
- # Statistik merken für Validitätsprüfung
450
- stats = [front_string.size, back_string.size]
451
- seqs = ['?', '?']
452
-
453
- # zuerst hinteren Teil auflösen
454
- # 1. Möglichkeit: Wort mit oder ohne Suffix
455
- back_lexicals = @dictionary.select_with_suffix(back_string)
456
- unless back_lexicals.empty?
457
- back_form = has_tail ? back_string : back_lexicals.sort[0].form
458
- seqs[1] = back_lexicals.sort[0].attr
459
- end
460
-
461
- # 2. Möglichkeit: Wort mit oder ohne Infix, wenn es nicht der letzte Teil des Wortes ist
462
- if back_lexicals.empty? && has_tail
463
- back_lexicals = @dictionary.select_with_infix(back_string)
464
- unless back_lexicals.empty?
465
- back_form = back_string
466
- seqs[1] = back_lexicals.sort[0].attr
467
- end
468
- end
469
-
470
- # 3. Möglichkeit: Selber ein Kompositum (nur im Bindestrich-Fall!)
471
- if back_lexicals.empty? && infix=='-'
472
- back_stats, back_lexicals, back_seqs = find_compositum(back_string, level+1, has_tail)
473
- unless back_lexicals.empty?
474
- back_form = back_lexicals.sort[0].form
475
- seqs[1] = back_seqs
476
- stats = stats[0..0] + back_stats
477
- end
478
- end
479
-
480
- # 4. Möglichkeit: Take it as is [Nimm's, wie es ist] (nur im Bindestrich-Fall!)
481
- if back_lexicals.empty? && infix=='-'
482
- back_lexicals = [Lexical.new(back_string, LA_TAKEITASIS)]
483
- back_form = back_string
484
- seqs[1] = back_lexicals.sort[0].attr
485
- end
486
-
487
- # wenn immer noch nicht erkannt, dann sofort zurück
488
- return [[],[],''] if back_lexicals.empty?
489
-
490
- # dann vorderen Teil auflösen
491
- #
492
- # 1. Möglichkeit: Wort mit oder ohne Infix
493
- front_lexicals = @dictionary.select_with_infix(front_string)
494
- unless front_lexicals.empty?
495
- front_form = front_string
496
- seqs[0] = front_lexicals.sort[0].attr
497
- end
498
-
499
- # 2. Möglichkeit: Selber ein Kompositum
500
- if front_lexicals.empty?
501
- front_stats, front_lexicals, front_seqs = find_compositum(front_string, level+1, true)
502
- unless front_lexicals.empty?
503
- front_form = front_lexicals.sort[0].form
504
- seqs[0] = front_seqs
505
- stats = front_stats + stats[1..-1]
506
- end
507
- end
508
-
509
- # 3. Möglichkeit: Take it as is [Nimm's, wie es ist] (nur im Bindestrich-Fall!)
510
- if front_lexicals.empty? && infix=='-'
511
- front_lexicals = [Lexical.new(front_string, LA_TAKEITASIS)]
512
- seqs[0] = front_lexicals.sort[0].attr
513
- front_form = front_string
514
- end
515
-
516
- # wenn immer noch nicht erkannt, dann sofort zurück
517
- return [[],[],''] if front_lexicals.empty?
518
-
519
- # Kompositum gefunden, Grundform bilden
520
- lexis = (front_lexicals + back_lexicals).collect { |lex|
521
- (lex.attr==LA_KOMPOSITUM) ? nil : lex
522
- }.compact
523
- lexis << Lexical.new(front_form + infix + back_form, LA_KOMPOSITUM)
524
-
525
- [stats, lexis.sort, seqs.join ]
526
- end
37
+ module Language
38
+
39
+ # String-Konstanten im Datenstrom
40
+ CHAR_PUNCT = '.'
41
+
42
+ TA_WORD = 'WORD'
43
+ TA_PUNCTUATION = 'PUNC'
44
+ TA_OTHER = 'OTHR'
45
+
46
+ # Standardattribut bei der Initialisierung eines Word-Objektes
47
+ WA_UNSET = '-'
48
+ # Status, nachdem das Word im Wörterbuch gefunden wurde
49
+ WA_IDENTIFIED = 'IDF'
50
+ # Status, wenn das Word nicht gefunden werden konnte
51
+ WA_UNKNOWN = '?'
52
+ # Wort ist als Kompositum erkannt worden
53
+ WA_KOMPOSITUM = 'KOM'
54
+ # Wort ist eine Mehrwortgruppe
55
+ WA_MULTIWORD = 'MUL'
56
+ # Wort ist eine Mehrwortgruppe
57
+ WA_SEQUENCE = 'SEQ'
58
+ # Word ist unbekannt, jedoch Teil einer Mehrwortgruppe
59
+ WA_UNKMULPART = 'MU?'
60
+
61
+ LA_SUBSTANTIV = 's'
62
+ LA_ADJEKTIV = 'a'
63
+ LA_VERB = 'v'
64
+ LA_EIGENNAME = 'e'
65
+ LA_KOMPOSITUM = 'k'
66
+ LA_MULTIWORD = 'm'
67
+ LA_SEQUENCE = 'q'
68
+ LA_WORTFORM = 'w'
69
+ LA_SYNONYM = 'y'
70
+ LA_STOPWORD = 't'
71
+ LA_TAKEITASIS = 'x'
72
+ LA_UNKNOWN = '?'
73
+
74
+ LA_SORTORDER = [
75
+ LA_MULTIWORD,
76
+ LA_KOMPOSITUM,
77
+ LA_SUBSTANTIV,
78
+ LA_VERB,
79
+ LA_ADJEKTIV,
80
+ LA_EIGENNAME,
81
+ LA_WORTFORM,
82
+ LA_STOPWORD,
83
+ LA_TAKEITASIS,
84
+ LA_SYNONYM,
85
+ LA_UNKNOWN
86
+ ].reverse.join
527
87
 
528
88
  end
529
89