プラグインXapian search pluginの検索エンジンをHyper Estraierへ替える¶
Xapian search pluginは,チケットの添付ファイル内のテキストを検索するプラグインです.これもまたその名が示すように,Xapianを使っており,そのままでは日本語に対応できません.
加えて,改造はDMSFよりも厄介です.プラグインの名称に,大胆にも Xapian という文字列を使っていることから想像されるように,モジュール名,変数名,メソッド名に現れる xapian を
すべて estraier に書き換えると,動きません!それを一部ずつ修正しても,かえってバグを混入させてしまいます.
なので,修正のポリシーは“プラグインDMSFの検索エンジンをHyper Estraierへ替える”とは同様にならず,少し複雑です.
まず,同様な方から示すと,
- Web画面に "Xapian" 由来の設定などがあれば,それはそのままにし,削除しない.(stem,languageらは機能しないまま残る)
- Web画面に Estraier に対応するものがあれば,それは "Estraier" へと修正する.(現バージョンではない)
- エラーメッセージなどに "Xapian" という文字列があれば,それは "Estraier" へと上書きする.
- ソースコード中 "Xapian" を呼び出す部分は削除する.(ロジックの変更)
- ソースコード中 "Xapian" という変数名を "Estraier" へと置き換える部分を極力少なくする.
- ソースコード中 "xapian" というラベル名称や,"redmine_xapian" のようにプラグインの由来の変数名があれば,そのまま利用する.
- 何らの理由により "Estraier" 検索環境が整備できない場合でも,添付ファイルの「タイトルのみ」検索はできるようにする.
- "Xapian search plugin" に,その仕掛けから生じたと思われる,添付ファイル検索の権限に関する外部仕様がある.「文書」と関連するこれを独立させた.
プラグインDMSFの場合は,アップロードしたファイルは,$REDMINE/files よりも一つ下へ下げたディレクトリ dmsf へファイルを置きましたが,添付ファイルはこの files 下へ置かれます.そのインデックスファイルはどこに置かれるべきか?と考えた場合,もちろん files 下へは置けません.なので,一つ上のディレクトリ $REDMINE へ置くことになります.そういうわけで,インデックス・ファイルの名称は files_index になります. - 検索用インデックスのディレクトリは言語毎に分けない.
- バージョンは,日本語版であることを示すため,1.yy.zz-JP とする.
さて,変更するファイルは次の6つです.
- config/locales/ja.yml
- init.rb
- app/controllers/search_controller.rb
- app/views/search/index.rhtml
- lib/acts_as_searchable.rb
- lib/xapian_search.rb
ところで,本サイトからコピーする場合は "¥"(バックスラッシュ)に注意して下さい.円記号ではありません.
config/locales/ja.yml¶
奇妙ですが,DMSFの方は最初から ja.yml がありました.ですが,さすがに当該プラグインにはそれはありません.なので,ja.yml は新たに(en.ymlを基にして)作ります.
@@ -0,0 +1,29 @@ +ja: + + label_enable_redmine_xapian: "hyper estraier による添付ファイル検索を可能にする" + label_index_database: "hyper estraier 検索インデックスディレクトリ" + label_stemming_text: "set the stemming language, the default is 'english'. + Possible values: danish dutch english finnish french + german german2 hungarian italian kraaij_pohlmann + lovins norwegian porter portuguese romanian russian + spanish swedish turkish (pass 'none' to disable + stemming)" + label_search2: "Extended Search" + text_search1: "This plugin does same redmine search plus searches into attachment. To enable xapian you have to build database with omindex utility and configure index database on plugin settings page" + label_document: "ドキュメント" + label_issue: "チケット" + label_wiki: "Wiki" + label_message: "メッセージ" + label_article: "Article" + label_article_plural: "Articles" + label_stemming_lang: "Stemming Language(注:estraier版では未使用)" + label_enable_xapian_on_search: "検索画面でhyper estraier使用可" + label_search_languages: "Stemming languages on search screen(注:estraier版では未使用)" + label_stemming_strategy: "Stemming strategy(注:estraier版では未使用)" + label_stem_none: "Stem none" + label_stem_some: "Stem some" + label_stem_all: "Stem all" + label_default_stemming_lang: "Set default stemming lang(注:estraier版では未使用)" + label_default_stemming_strategy: "Set default stemming strategy(注:estraier版では未使用)" + label_database_error: "検索インデックスエラー。Hyper Estraier利用環境を構築してください。" +
init.rb¶
ライブラリ名称を xapian から estraier に変更します.加えて,先の ya.yml に対応する警告のラベルと一致させます.
@ -7,10 +7,10 @@
begin
- require 'xapian'
+ require 'estraier'
$xapian_bindings_available = true
rescue LoadError
- Rails.logger.info "REDMAIN_XAPIAN ERROR: No Ruby bindings for Xapian installed !!. PLEASE install Xapian search engine interface for Ruby."
+ Rails.logger.info "REDMAIN_XAPIAN ERROR: No Ruby bindings for Hyper Estraier installed !!. PLEASE install Hyper Estraier search engine interface for Ruby."
$xapian_bindings_available = false
else
require 'redmine'
@@ -28,7 +28,7 @@
author_url 'http://undefinederror.org'
description 'With this plugin you will be able to do searches by file name and by strings inside your documents'
- version '1.2.1'
+ version '1.2.1-JP'
requires_redmine :version_or_higher => '1.0.0'
settings :partial => 'settings/redmine_xapian_settings',
app/controllers/search_controller.rb¶
検索エンジンが利用できない場合は,警告メッセージを出力する.なお,「タイトルのみ」検索の場合はこの限りではない.
@@ -96,6 +96,7 @@
end
end
+ flash[:warning] = "warning: #{l(:label_database_error)}" unless @titles_only || $xapian_bindings_available
@results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
if params[:previous].nil?
@pagination_previous_date = @results[0].event_datetime if offset && @results[0]
app/views/search/index.rhtml¶
オリジナルでは,検索結果画面に表示される Stemらの設定部分画面を表示されないようにする.
@@ -16,6 +16,7 @@ <% end %> </p> <% logger.debug "DEBUG: object_types from search: " + Redmine::Search.available_search_types.inspect %> +<% if false then %> <% Setting.plugin_redmine_xapian['stem_langs'].push(Setting.plugin_redmine_xapian['stemming_lang']) unless Setting.plugin_redmine_xapian['stem_langs'].include?(Setting.plugin_redmine_xapian['stemming_lang']) %> <p> @@ -31,6 +32,7 @@ <%end%> +<%end%> <p><%= submit_tag l(:button_submit), :name => 'submit' %></p> <% end %> </div>
lib/acts_as_searchable.rb¶
サーバー側に検索環境が整備されていない場合でも,少なくともタイトルの検索を可能にする修正.
また,すべての添付ファイルの検索の権限が「文書の閲覧」で集約されていたものを分割する.すなわち,ファイルが添付されているチケットやwiki,それぞれの閲覧権限に従うようにする.
@@ -15,7 +15,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-require 'xapian_search'
+require 'xapian_search' if $xapian_bindings_available
module Redmine
module Acts
@@ -116,6 +116,10 @@
#Attahcment on documents
results_doc= []
results_count_doc=0
+ project_conditions = []
+ project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) :
+ Project.allowed_to_condition(User.current, :view_documents))
+ project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil?
find_options_tmp=Hash.new
find_options_tmp=find_options_tmp.merge(find_options)
find_options_tmp[:conditions] = merge_conditions (find_options_tmp[:conditions], :container_type=>"Document" )
@@ -129,6 +133,10 @@
results +=results_doc
results_count += results_count_doc
#Attachemnts on WikiPage
+ project_conditions = []
+ project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) :
+ Project.allowed_to_condition(User.current, :view_wiki_pages))
+ project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil?
find_options_tmp=Hash.new
find_options_tmp=find_options_tmp.merge(find_options)
results_wiki= []
@@ -146,6 +154,10 @@
results+=results_wiki
results_count+= results_count_wiki
#Attachemnts on Message
+ project_conditions = []
+ project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) :
+ Project.allowed_to_condition(User.current, :view_messages))
+ project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil?
find_options_tmp =Hash.new
find_options_tmp=find_options_tmp.merge(find_options)
results_message= []
@@ -163,6 +175,10 @@
results+=results_message
results_count+= results_count_message
#Attachemnts on Issues
+ project_conditions = []
+ project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) :
+ Project.allowed_to_condition(User.current, :view_issues))
+ project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil?
find_options_tmp =Hash.new
find_options_tmp=find_options_tmp.merge(find_options)
results_issue= []
@@ -180,6 +196,9 @@
#Attachments on Articles
if Redmine::Search.available_search_types.include?("articles")
logger.debug "DEBUG: knowledgebase plugin installed"
+ project_conditions = []
+ project_conditions << Project.visible_by(User.current)
+ project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil?
find_options_tmp=Hash.new
find_options_tmp=find_options_tmp.merge(find_options)
results_article = []
@@ -194,7 +213,7 @@
results_count += results_count_article
end
# Search attachments over xapian
- if !options[:titles_only]
+ if !options[:titles_only] && $xapian_bindings_available
begin
xapianresults, xapianresults_count = XapianSearch.search_attachments( tokens, limit_options,
options[:offset], projects, options[:all_words], options[:user_stem_lang],
lib/xapian_search.rb¶
【注意】このファイルについては,後のコードレビューにてさらに変更する可能性があります.特に権限の部分がそうです.
検索インデックスは言語毎に分けてはいない.
@@ -11,69 +11,56 @@
Rails.logger.debug "DEBUG: user_stem_lang: " + user_stem_lang.inspect
Rails.logger.debug "DEBUG: user_stem_strategy: " + user_stem_strategy.inspect
Rails.logger.debug "DEBUG: databasepath: " + getDatabasePath(user_stem_lang)
- databasepath = getDatabasePath(user_stem_lang)
+ databasepath = getDatabasePath('')
begin
- database = Xapian::Database.new(databasepath)
+ database = Estraier::Database::new
+ unless database.open(databasepath, Estraier::Database::DBREADER)
+ return [xpattachments,0]
+ end
rescue => error
raise databasepath
- return [xpattachments,0]
end
# Start an enquire session.
- enquire = Xapian::Enquire.new(database)
+ enquire = Estraier::Condition::new
+
+ queryString = tokens.join(all_words ? ' AND ': ' OR ')
+ enquire.set_phrase(queryString)
+ enquire.set_max(100)
- # Combine the rest of the command line arguments with spaces between
- # them, so that simple queries don't have to be quoted at the shell
- # level.
- #queryString = ARGV[1..-1].join(' ')
- queryString = tokens.join(' ')
- # Parse the query string to produce a Xapian::Query object.
- qp = Xapian::QueryParser.new()
- stemmer = Xapian::Stem.new($user_stem_lang)
- qp.stemmer = stemmer
- qp.database = database
- case @user_stem_strategy
- when "STEM_NONE" then qp.stemming_strategy = Xapian::QueryParser::STEM_NONE
- when "STEM_SOME" then qp.stemming_strategy = Xapian::QueryParser::STEM_SOME
- when "STEM_ALL" then qp.stemming_strategy = Xapian::QueryParser::STEM_ALL
- end
- if all_words
- qp.default_op = Xapian::Query::OP_AND
- else
- qp.default_op = Xapian::Query::OP_OR
- end
- query = qp.parse_query(queryString)
Rails.logger.debug "DEBUG queryString is: #{queryString}"
- Rails.logger.debug "DEBUG: Parsed query is: #{query.description()} "
- # Find the top 100 results for the query.
- enquire.query = query
- matchset = enquire.mset(0, 1000)
+ matchset = database.search(enquire)
return [xpattachments,0] if matchset.nil?
# Display the results.
- #logger.debug "#{@matchset.matches_estimated()} results found."
- Rails.logger.debug "DEBUG: Matches 1-#{matchset.size}:¥n"
+ Rails.logger.debug "DEBUG: Matches 1-#{matchset.doc_num}:¥n"
- matchset.matches.each {|m|
+ dnum = matchset.doc_num
+ for i in 0...dnum
+ doc = database.get_doc(matchset.get_doc_id(i), 0)
+ next unless doc
#Rails.logger.debug "#{m.rank + 1}: #{m.percent}% docid=#{m.docid} [#{m.document.data}]¥n"
#logger.debug "DEBUG: m: " + m.document.data.inspect
- docdata=m.document.data{url}
- dochash=Hash[*docdata.scan(/(url|sample|modtime|type|size)=¥/?([^¥n¥]]+)/).flatten]
- if not dochash.nil? then
- find_conditions = Attachment.merge_conditions (limit_options[:conditions], :disk_filename => dochash.fetch('url') )
+
+ uri = doc.attr("@uri")
+ if uri then
+ filename = uri.sub(/.*¥//, '')
+
+ find_conditions = Attachment.merge_conditions (limit_options[:conditions], :disk_filename => filename)
docattach=Attachment.find (:first, :conditions => find_conditions )
if not docattach.nil? then
if docattach["container_type"] == "Article" and not Redmine::Search.available_search_types.include?("articles")
Rails.logger.debug "DEBUG: Knowledgebase plugin in not installed.."
elsif not docattach.container.nil? then
Rails.logger.debug "DEBUG: adding attach.. "
- allowed = User.current.allowed_to?("view_documents".to_sym, docattach.container.project) || docattach.container_type == "Article"
+ # allowed = User.current.allowed_to?("view_documents".to_sym, docattach.container.project) || docattach.container_type == "Article"
+ allowed = true
if ( allowed and project_included(docattach.container.project.id, projects_to_search ) )
- docattach[:description]=dochash["sample"]
+ docattach[:description] = doc.make_snippet(tokens,50,0,10).to_s
xpattachments.push ( docattach )
else
Rails.logger.debug "DEBUG: user without permissions"
@@ -81,7 +68,10 @@
end
end
end
- }
+ end
+
+ database.close
+
@@numattach=xpattachments.size if offset.nil?
xpattachments=xpattachments.sort_by{|x| x[:created_on] }
[xpattachments, @@numattach]
修正は以上です.
Redmine-1.3へ対応したバージョン 1.2.3 の場合の差分ファイルを添付しておきます.
Updated by Masanori Machii almost 14 years ago · 9 revisions