プロジェクト

全般

プロフィール

プラグインDMSFの検索エンジンをHyper Estraierへ替える » 履歴 » バージョン 9

Masanori Machii, 2011/10/27 18:18

1 1 Masanori Machii
h1. プラグインDMSFの検索エンジンをHyper Estraierへ替える
2
3
h2. DMSFとRedmineの関係
4
5 2 Masanori Machii
DMSFはRedmineが標準で備える種々の文書管理を一つにまとめて置き換えようとしたものです.ユーザーインターフェイスは優れていますが,残念ながら検索エンジンにXapianを利用しており,そのままでは日本語に対応できません.そこで,Hyper Estraierへ置き換えるわけですが,改造はエンジンのみの置き換えで済むので,DMSFの基本的な構造は変わりません.
6 1 Masanori Machii
7
その要点は次のとおりです.
8
9
bq. 注)$REDMINEは自動的に設定される環境編ではありません.ここでは便宜的に使います.
10
11
# {{fn(DMSFが管理する文書,ファイル名が同じであっても,インデックスは異なるよう,接頭辞が付いています.)}}はすべて $REDMINE/files/dnsf へ置かれる.
12
# インデックスファイルを作成するプロセスが定期的に動作し,それは {{fn($REDMINE/files へ置かれる,これは管理者が設定できるのですが,わかりやすくするため,推奨としました.)}}.
13 3 Masanori Machii
14 5 Masanori Machii
【例】"whitemine" というディレクトリにRedmineがインストールされている場合
15
!ファイル保存フォルダ.png!
16 4 Masanori Machii
17 5 Masanori Machii
!インデックスデータベース.png!
18 4 Masanori Machii
19
これは,DMSFの設定画面にも現れています.またこの画面では,Xapianに由来する設定がありますが,それがHyper Estraierでは不要になります.
20
21 5 Masanori Machii
!Xapianの設定.png!
22 1 Masanori Machii
23
ここでは,オリジナルを(ちょっと手を抜いた)次の方針で改造することにします.
24
25 5 Masanori Machii
# Web画面に "Xapian" 由来の設定などがあれば,それはそのままにし,削除しない.
26
# Web画面に Estraier に対応するものがあれば,それは "Estraier" へと修正する.(現バージョンではない)
27
# エラーメッセージなどに "Xapian" という文字列があれば,それは  "Estraier" へと上書きする.
28 1 Masanori Machii
# ソースコード中 "Xapian" を呼び出す部分は削除する.(ロジックの変更)
29 5 Masanori Machii
# ソースコード中 "Xapian" という変数名がそのまま  "Estraier" へと置き換えられる部分は "Estraier" へと修正する.
30 8 Masanori Machii
# -ソースコード中 "Xapian" というラベル名称はそのまま利用する.-
31 9 Masanori Machii
# バージョンは,日本語版であることを示すため,1.yy.zz-JP とする. 
32 5 Masanori Machii
33
さて,変更するファイルは次の3つです.
34 1 Masanori Machii
35 9 Masanori Machii
# config/locales/ja.yml
36
# init.rb
37
# app/views/settings/_dmsf_settings.erb
38
# app/models/dmsf_file.rb
39 5 Masanori Machii
40
h2. config/locales/ja.ymlの修正
41
42 7 Masanori Machii
コード中のラベルも含めて,Xapian から Estraier へと修正しています.
43 5 Masanori Machii
44
<pre>
45
@@ -156,7 +156,7 @@
46
   :error_file_storage_directory_does_not_exist: "ファイル保存フォルダが存在せず作ることもできません" 
47
   :error_file_can_not_be_created: "ファイルを保存フォルダに作ることができません" 
48
   :error_wrong_zip_encoding: "Zip エンコーディングが正しくありません" 
49
-  :warning_xapian_not_available: "Xapian が利用できる状態になっていません" 
50 7 Masanori Machii
+  :warning_estraier_not_available: "Hyper Estraier が利用できる状態になっていません" 
51 5 Masanori Machii
   :menu_dmsf: "DMSF" 
52 1 Masanori Machii
   :label_physical_file_delete: "物理ファイルの削除" 
53
   :user_is_not_project_member: "あなたはプロジェクトのメンバーではありません"
54
</pre>
55 9 Masanori Machii
56
57
h2. init.rbの修正
58
59
先の方針に基づいて,バージョン番号をユニークにします.
60
61
<pre>
62
@@ -29,7 +29,7 @@
63
  name "DMSF"
64
  author "Vít Jonáš"
65
  description "Document Management System Features"
66
-  version "1.2.1"
67
+  version "1.2.1-JP"
68
  url "http://code.google.com/p/redmine-dmsf/"
69
  author_url "mailto:vit.jonas@gmail.com"
70
</pre>
71
72
73 1 Masanori Machii
74 5 Masanori Machii
h2. app/views/settings/_dmsf_settings.erb
75 1 Masanori Machii
76 7 Masanori Machii
ライブラリ名称を xapian から estraier に変更します.加えて,先の ya.yml に対応する警告のラベルと一致させます.
77
抽出方針(Stem)に相当する部分も,文字列を xapian から estraier へと修正していますが,Xapian は機能しませんので,これはおまけと考えて下さい. 
78 5 Masanori Machii
79 7 Masanori Machii
80 5 Masanori Machii
<pre>
81 7 Masanori Machii
@@ -75,22 +75,22 @@
82 1 Masanori Machii
83
 <hr />
84
 <% begin
85
-      require 'xapian'
86 7 Masanori Machii
-      xapian_disabled = false
87 1 Masanori Machii
+      require 'estraier'
88 7 Masanori Machii
+      estraier_disabled = false
89 1 Masanori Machii
    rescue LoadError    %>
90 7 Masanori Machii
-    <p class="warning"><%= l(:warning_xapian_not_available) %></p> 
91
-<%     xapian_disabled = true
92
+    <p class="warning"><%= l(:warning_estraier_not_available) %></p> 
93
+<%     estraier_disabled = true
94
    end %>
95
96
 <p>
97
   <%=content_tag(:label, l(:label_index_database) + ":") %>
98
-  <%=text_field_tag 'settings[dmsf_index_database]', @settings['dmsf_index_database'], :disabled => xapian_disabled, :size=>50 %><br/>
99
+  <%=text_field_tag 'settings[dmsf_index_database]', @settings['dmsf_index_database'], :disabled => estraier_disabled, :size=>50 %><br/>
100
   (<%=l(:label_default)%>: <%="#{RAILS_ROOT}/files/dmsf_index"%>)
101
 </p>
102
103
 <p>
104
   <%=content_tag(:label, l(:label_stemming_language) + ":") %>
105
-  <%=text_field_tag 'settings[dmsf_stemming_lang]', @settings['dmsf_stemming_lang'], :disabled => xapian_disabled %><br/>
106
+  <%=text_field_tag 'settings[dmsf_stemming_lang]', @settings['dmsf_stemming_lang'], :disabled => estraier_disabled %><br/>
107
   (<%=l(:label_default)%>: english )<br/>
108
   <br/>
109
   <%=l(:note_possible_values)%>: danish dutch english finnish french german german2 hungarian italian kraaij_pohlmann lovins norwegian porter portuguese romanian russian spanish swedish turkish (<%=l(:note_pass_none_to_disable_stemming)%>)
110
@@ -98,9 +98,9 @@
111
112
 <p>
113
   <%=content_tag(:label, l(:label_stem_strategy) + ":")%>
114
-  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_NONE', @settings['dmsf_stemming_strategy'] == 'STEM_NONE', :disabled => xapian_disabled, :checked=>true  %> <%=l(:option_stem_none)%><br>
115
-  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_SOME', @settings['dmsf_stemming_strategy'] == 'STEM_SOME', :disabled => xapian_disabled  %> <%=l(:option_stem_some)%><br>
116
-  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_ALL', @settings['dmsf_stemming_strategy'] == 'STEM_ALL', :disabled => xapian_disabled  %> <%=l(:option_stem_all)%><br>
117
+  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_NONE', @settings['dmsf_stemming_strategy'] == 'STEM_NONE', :disabled => estraier_disabled, :checked=>true  %> <%=l(:option_stem_none)%><br>
118
+  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_SOME', @settings['dmsf_stemming_strategy'] == 'STEM_SOME', :disabled => estraier_disabled  %> <%=l(:option_stem_some)%><br>
119
+  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_ALL', @settings['dmsf_stemming_strategy'] == 'STEM_ALL', :disabled => estraier_disabled  %> <%=l(:option_stem_all)%><br>
120
   <br/>
121
   <%=l(:label_stemming_description)%>:
122
   <br>
123 1 Masanori Machii
</pre>
124 5 Masanori Machii
125 1 Masanori Machii
126
h2. app/models/dmsf_file.rb
127
128
検索機能本体の修正をします.主な修正の要点は次のとおりです.
129
130
XapianではなくてEstraier を呼び出します.呼び出し時のエラー処理はXapianと同様です.
131 7 Masanori Machii
エラーや警告のメッセージも, Estraier のそれへと修正します.
132 1 Masanori Machii
インデックスデータベースへのアクセスメソッドは,Xapianとは大幅に異なりますので,ざっくりと削除し,上書きします.
133 7 Masanori Machii
134
まず,コードの冒頭には,ライブラリの存在の有無によるエラー処理があります.
135
136
<pre>
137
@@ -17,11 +17,11 @@
138
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
139
140
 begin
141
-  require 'xapian'
142
-  $xapian_bindings_available = true
143
+  require 'estraier'
144
+  $estraier_bindings_available = true
145
 rescue LoadError
146
-  Rails.logger.info "REDMAIN_XAPIAN ERROR: No Ruby bindings for Xapian installed !!. PLEASE install Xapian search engine interface for Ruby." 
147
-  $xapian_bindings_available = false
148
+  Rails.logger.info "REDMAIN_ESTRAIER ERROR: No Ruby bindings for Estraier installed !!. PLEASE install Hyper Estraier search engine interface for Ruby." 
149
+  $estraier_bindings_available = false
150
 end
151
152
</pre>
153
154
次に,インデックスデータベースへのアクセスについて,エラー処理があります.
155
156
<pre>
157
@@ -260,46 +260,37 @@
158
       end
159
     end
160
161
-    if !options[:titles_only] && $xapian_bindings_available
162
+    if !options[:titles_only] && $estraier_bindings_available
163
       database = nil
164
       begin
165
-        database = Xapian::Database.new(Setting.plugin_redmine_dmsf["dmsf_index_database"].strip)
166
+        database = Estraier::Database::new
167
+        database.open(Setting.plugin_redmine_dmsf["dmsf_index_database"].strip, Estraier::Database::DBREADER) 
168
       rescue
169
-        Rails.logger.warn "REDMAIN_XAPIAN ERROR: Xapian database is not properly set or initiated or is corrupted." 
170
+        Rails.logger.warn "REDMAIN_ESTRAIER ERROR: Estraier database is not properly set or initiated or is corrupted." 
171
       end
172
</pre>
173
174
175
database.nil 以下は大幅に構造が異なりますので,ざっくりと入れ替えましょう.
176
177
<pre>
178
       unless database.nil?
179
-        enquire = Xapian::Enquire.new(database)
180
+        # create a search condition object
181
+        cond = Estraier::Condition::new
182
183
-        queryString = tokens.join(' ')
184
-        qp = Xapian::QueryParser.new()
185
-        stemmer = Xapian::Stem.new(Setting.plugin_redmine_dmsf['dmsf_stemming_lang'].strip)
186
-        qp.stemmer = stemmer
187
-        qp.database = database
188
-        
189
-        case Setting.plugin_redmine_dmsf['dmsf_stemming_strategy'].strip
190
-          when "STEM_NONE" then qp.stemming_strategy = Xapian::QueryParser::STEM_NONE
191
-          when "STEM_SOME" then qp.stemming_strategy = Xapian::QueryParser::STEM_SOME
192
-          when "STEM_ALL" then qp.stemming_strategy = Xapian::QueryParser::STEM_ALL
193
-        end
194
-      
195
-        if options[:all_words]
196
-          qp.default_op = Xapian::Query::OP_AND
197
-        else  
198
-          qp.default_op = Xapian::Query::OP_OR
199
-        end
200
-        
201
-        query = qp.parse_query(queryString)
202
-  
203
-        enquire.query = query
204
-        matchset = enquire.mset(0, 1000)
205
-    
206
-        unless matchset.nil?
207
-          matchset.matches.each {|m|
208
-            docdata = m.document.data{url}
209
-            dochash = Hash[*docdata.scan(/(url|sample|modtime|type|size)=\/?([^\n\]]+)/).flatten]
210
-            filename = dochash["url"]
211
-            if !filename.nil?
212
+        # set the search phrase to the search condition object
213
+        queryString = tokens.join(options[:all_words] ? ' AND ': ' OR ')
214
+        cond.set_phrase(queryString )
215
+
216
+        # get the result of search
217
+        result = database.search(cond)
218
+
219
+        if result
220
+          # for each document in the result
221
+          dnum = result.doc_num
222
+          for i in 0...dnum
223
+            # retrieve the document object
224
+            doc = database.get_doc(result.get_doc_id(i), 0)
225
+            next unless doc
226
+            # display attributes
227
+            uri = doc.attr("@uri")
228
+            if uri
229
+              filename = uri.sub(/.*\//, '')
230
               dmsf_attrs = filename.split("_")
231
               next if dmsf_attrs[1].blank?
232
               next unless results.select{|f| f.id.to_s == dmsf_attrs[1]}.empty?
233
</pre>
234
235
最後にインデックスデータベースを閉じて終了です.
236
237
<pre>
238
239
@@ -331,9 +322,14 @@
240
                 end
241
               end
242
             end
243
-          }
244
         end
245
       end    
246
+
247
+        # close the database
248
+        unless database.close
249
+          Rails.logger.warn(database.err_msg(database.error))
250
+        end
251
+      end # unless database.nil?
252
     end
253
254
     [results, results_count]
255
</pre>
256
257
修正は以上です.