# # searchquery.rb - search query implementation # # Copyright (C) 2004-2005 Satoru Takabayashi # All rights reserved. # This is free software with ABSOLUTELY NO WARRANTY. # # You can redistribute it and/or modify it under the terms of # the GNU General Public License version 2. # module Gonzui QueryItem = Struct.new(:property, :value, :phrase_p) class QueryItem def to_s string = "" string << self.property.to_s << ":" if self.property if phrase? string << sprintf('"%s"', self.value.join(" ")) else string << self.value end return string end def phrase? self.phrase_p == true end end class SearchQuery include Enumerable include GetText def initialize(config, query_string, options = {}) @query_string = query_string @options = options @items = [] @package = nil @path = nil @format = nil @license = nil @error = nil @words = [] @ignored_words = [] @max_words = config.max_words @nwords = 0 parse_query_string end attr_accessor :path attr_reader :package attr_reader :format attr_reader :license attr_reader :ignored_words attr_reader :words attr_reader :options private KnownProperties = [] [:path, :package].each {|property| KnownProperties.push(property) } # :fundef, :funcall, etc. LangScan::Type.each_group {|group| group.each {|type_info| KnownProperties.push(type_info.type) } } def parse_query_string kp = KnownProperties.join("|") parts = @query_string.scan(/((?:#{kp}):)?(?:"(.+)"|(\S+))/) parts.each {|prefix, quoted, bare| phrase_p = if quoted then true else false end text = (quoted or bare) if prefix property = prefix.chop.intern case property when :package @error = QueryError.new(N_("package: duplicated.")) if @package @package = text when :path @error = QueryError.new(N_("path: duplicated.")) if @path @path = text else add_item(property, text, phrase_p) end else add_item(nil, text, phrase_p) end } @format = @options[:format] @license = @options[:license] if @package and @path message = N_("package: and path: cannot be specified together.") @error = QueryError.new(message) end make_words end def make_words @words = @items.map {|i| i.value }.flatten end def add_item_for_phrase(property, text) value = [] TextTokenizer.each_word(text) {|word, unused| if @nwords < @max_words @nwords += 1 value.push(word) else @ignored_words.push(word) end } unless value.empty? item = QueryItem.new(property, value, true) @items.push(item) end end def add_item_for_single_word(property, text) if @nwords < @max_words @nwords += 1 item = QueryItem.new(property, text, false) @items.push(item) else @ignored_words.push(text) end end def add_item(property, text, phrase_p) if phrase_p or has_multi_byte_char?(text) add_item_for_phrase(property, text) else add_item_for_single_word(property, text) end end def has_multi_byte_char?(text) /[^\x00-\x7f]/u.match(text) end def reset @ignored_words = [] @items = [] @nwords = 0 end public def path_only? @items.empty? and @path and @package.nil? end def package_only? @items.empty? and @path.nil? and @package end def string @query_string end def simplified_string @items.map {|item| item.to_s }.join(" ") end def string_without_properties @items.map {|item| item.value.to_s }.join(" ") end def empty? @items.empty? and @package.nil? and @path.nil? end def first @items.first end def last @items.last end def each @items.each {|item| yield(item) } end def length @items.length end def collect @items.find_all {|item| if block_given? yield(item) else true end }.map {|item| item.value } end def keywords collect {|item| not item.phrase? } end def phrases collect {|item| item.phrase? } end def tokenize_all original_items = @items.clone original_nwords = @nwords reset original_items.each {|item| if item.phrase? value = item.value.join(" ") add_item_for_phrase(item.property, value) else add_item_for_phrase(item.property, item.value) end } make_words modified = if @nwords != original_nwords then true else false end return modified end def validate raise @error if @error end end end