lingo 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. data/ChangeLog +13 -0
  2. data/README +49 -29
  3. data/Rakefile +28 -4
  4. data/TODO +2 -9
  5. data/bin/lingo +24 -0
  6. data/bin/lingoctl +24 -0
  7. data/de/lingo-dic.txt +559 -74
  8. data/info/gpl-hdr.txt +21 -24
  9. data/lib/lingo.rb +83 -112
  10. data/lib/lingo/agenda_item.rb +53 -0
  11. data/lib/lingo/attendee.rb +261 -0
  12. data/lib/lingo/attendee/abbreviator.rb +95 -97
  13. data/lib/lingo/attendee/debugger.rb +94 -93
  14. data/lib/lingo/attendee/decomposer.rb +76 -83
  15. data/lib/lingo/attendee/dehyphenizer.rb +141 -144
  16. data/lib/lingo/attendee/formatter.rb +65 -0
  17. data/lib/lingo/attendee/multi_worder.rb +302 -0
  18. data/lib/lingo/attendee/noneword_filter.rb +89 -84
  19. data/lib/lingo/attendee/object_filter.rb +91 -0
  20. data/lib/lingo/attendee/sequencer.rb +159 -158
  21. data/lib/lingo/attendee/synonymer.rb +81 -84
  22. data/lib/lingo/attendee/text_reader.rb +242 -0
  23. data/lib/lingo/attendee/text_writer.rb +169 -0
  24. data/lib/lingo/attendee/tokenizer.rb +192 -191
  25. data/lib/lingo/attendee/variator.rb +152 -156
  26. data/lib/lingo/attendee/vector_filter.rb +140 -135
  27. data/lib/lingo/attendee/word_searcher.rb +98 -0
  28. data/lib/lingo/buffered_attendee.rb +69 -0
  29. data/lib/lingo/cachable.rb +58 -0
  30. data/lib/lingo/call.rb +72 -0
  31. data/lib/lingo/cli.rb +26 -0
  32. data/lib/lingo/config.rb +23 -26
  33. data/lib/lingo/core_ext.rb +42 -0
  34. data/lib/lingo/ctl.rb +239 -173
  35. data/lib/lingo/database.rb +148 -496
  36. data/lib/lingo/database/crypter.rb +85 -0
  37. data/lib/lingo/database/gdbm_store.rb +49 -0
  38. data/lib/lingo/database/hash_store.rb +67 -0
  39. data/lib/lingo/database/libcdb_store.rb +58 -0
  40. data/lib/lingo/database/sdbm_store.rb +64 -0
  41. data/lib/lingo/database/show_progress.rb +81 -0
  42. data/lib/lingo/database/source.rb +134 -0
  43. data/lib/lingo/database/source/key_value.rb +62 -0
  44. data/lib/lingo/database/source/multi_key.rb +65 -0
  45. data/lib/lingo/database/source/multi_value.rb +65 -0
  46. data/lib/lingo/database/source/single_word.rb +60 -0
  47. data/lib/lingo/database/source/word_class.rb +64 -0
  48. data/lib/lingo/error.rb +122 -0
  49. data/lib/lingo/language.rb +78 -518
  50. data/lib/lingo/language/dictionary.rb +173 -0
  51. data/lib/lingo/language/grammar.rb +211 -0
  52. data/lib/lingo/language/lexical.rb +66 -0
  53. data/lib/lingo/language/lexical_hash.rb +88 -0
  54. data/lib/lingo/language/token.rb +48 -0
  55. data/lib/lingo/language/word.rb +130 -0
  56. data/lib/lingo/language/word_form.rb +83 -0
  57. data/lib/lingo/reportable.rb +59 -0
  58. data/lib/lingo/version.rb +1 -1
  59. data/lingo-all.cfg +14 -10
  60. data/lingo-call.cfg +5 -5
  61. data/lingo.cfg +14 -12
  62. data/lingo.rb +26 -0
  63. data/lir.cfg +13 -9
  64. data/spec/spec_helper.rb +1 -0
  65. data/test.cfg +11 -11
  66. data/test/attendee/ts_abbreviator.rb +0 -6
  67. data/test/attendee/ts_decomposer.rb +0 -6
  68. data/test/attendee/{ts_multiworder.rb → ts_multi_worder.rb} +1 -7
  69. data/test/attendee/ts_noneword_filter.rb +1 -7
  70. data/test/attendee/{ts_objectfilter.rb → ts_object_filter.rb} +1 -7
  71. data/test/attendee/ts_sequencer.rb +0 -6
  72. data/test/attendee/ts_synonymer.rb +0 -6
  73. data/test/attendee/{ts_textreader.rb → ts_text_reader.rb} +1 -7
  74. data/test/attendee/{ts_textwriter.rb → ts_text_writer.rb} +1 -7
  75. data/test/attendee/ts_tokenizer.rb +0 -6
  76. data/test/attendee/ts_variator.rb +0 -6
  77. data/test/attendee/ts_vector_filter.rb +1 -7
  78. data/test/attendee/{ts_wordsearcher.rb → ts_word_searcher.rb} +1 -7
  79. data/test/ref/artikel.non +2 -29
  80. data/test/ref/artikel.seq +13 -8
  81. data/test/ref/artikel.vec +30 -15
  82. data/test/ref/artikel.ven +29 -14
  83. data/test/ref/artikel.ver +58 -43
  84. data/test/ref/lir.csv +146 -145
  85. data/test/ref/lir.non +186 -210
  86. data/test/ref/lir.seq +54 -50
  87. data/test/test_helper.rb +41 -36
  88. data/test/ts_database.rb +12 -11
  89. data/test/ts_language.rb +118 -68
  90. metadata +67 -29
  91. data/lib/lingo/attendee/multiworder.rb +0 -301
  92. data/lib/lingo/attendee/objectfilter.rb +0 -86
  93. data/lib/lingo/attendee/textreader.rb +0 -237
  94. data/lib/lingo/attendee/textwriter.rb +0 -196
  95. data/lib/lingo/attendee/wordsearcher.rb +0 -96
  96. data/lib/lingo/attendees.rb +0 -289
  97. data/lib/lingo/const.rb +0 -131
  98. data/lib/lingo/modules.rb +0 -98
  99. data/lib/lingo/types.rb +0 -285
  100. data/lib/lingo/utilities.rb +0 -40
@@ -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