xtf-ruby 1.0.0

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/APACHE-2-LICENSE +13 -0
  6. data/CHANGELOG.md +12 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +50 -0
  10. data/LICENSE.txt +202 -0
  11. data/NOTICE.txt +1 -0
  12. data/README.md +51 -0
  13. data/Rakefile +9 -0
  14. data/lib/xtf/result/element/base.rb +54 -0
  15. data/lib/xtf/result/element/doc_hit.rb +61 -0
  16. data/lib/xtf/result/element/facet.rb +29 -0
  17. data/lib/xtf/result/element/group.rb +37 -0
  18. data/lib/xtf/result/element/result.rb +91 -0
  19. data/lib/xtf/result/element.rb +13 -0
  20. data/lib/xtf/result.rb +6 -0
  21. data/lib/xtf/ruby/version.rb +7 -0
  22. data/lib/xtf/ruby.rb +10 -0
  23. data/lib/xtf/search/element/all_docs.rb +23 -0
  24. data/lib/xtf/search/element/and.rb +13 -0
  25. data/lib/xtf/search/element/base.rb +32 -0
  26. data/lib/xtf/search/element/clause.rb +57 -0
  27. data/lib/xtf/search/element/exact.rb +8 -0
  28. data/lib/xtf/search/element/facet.rb +26 -0
  29. data/lib/xtf/search/element/near.rb +17 -0
  30. data/lib/xtf/search/element/not.rb +7 -0
  31. data/lib/xtf/search/element/or.rb +13 -0
  32. data/lib/xtf/search/element/or_near.rb +11 -0
  33. data/lib/xtf/search/element/phrase.rb +28 -0
  34. data/lib/xtf/search/element/query.rb +42 -0
  35. data/lib/xtf/search/element/range.rb +44 -0
  36. data/lib/xtf/search/element/result_data.rb +18 -0
  37. data/lib/xtf/search/element/section_type.rb +19 -0
  38. data/lib/xtf/search/element/term.rb +51 -0
  39. data/lib/xtf/search/element.rb +15 -0
  40. data/lib/xtf/search.rb +11 -0
  41. data/lib/xtf/xml.rb +85 -0
  42. data/lib/xtf.rb +31 -0
  43. data/sig/xtf/ruby.rbs +6 -0
  44. metadata +158 -0
@@ -0,0 +1,91 @@
1
+ class XTF::Result::Element::Result
2
+ # require 'rubygems'
3
+ # require 'libxml_helper'
4
+ DEFAULT_DOCS_PER_PAGE = 50
5
+ attr_reader :tag_name,
6
+ :xml,
7
+ :doc,
8
+ :search,
9
+ :query,
10
+ :query_time,
11
+ :total_docs,
12
+ :start_doc,
13
+ :end_doc,
14
+ :doc_hits,
15
+ :docs_per_page,
16
+ :facets
17
+ alias :hits :doc_hits
18
+
19
+ def initialize(xml=nil, search=nil)
20
+ start = Time.now
21
+ @tag_name = "crossQueryResult"
22
+ @doc_hits = []
23
+ @facets = []
24
+ parse_xml(xml) if xml
25
+ @search = search
26
+ # RAILS_DEFAULT_LOGGER.debug("~~~~~ #{self.class.name} initialized #{@doc_hits.size} hits and #{@facets.size} facets in #{Time.now - start} seconds.")
27
+ end
28
+
29
+ # Parses the important bits out of the XTF search results using LibXML-Ruby.
30
+ def parse_xml(xml)
31
+ @xml = xml.to_s.gsub(/\s+/, " ").strip
32
+ @doc = XML::Document.parse_string(@xml).root
33
+
34
+ # the query metadata
35
+ @query_time = @doc.at('/crossQueryResult')['queryTime']
36
+ @total_docs = @doc.at('/crossQueryResult')['totalDocs']
37
+ @start_doc = @doc.at('/crossQueryResult')['startDoc']
38
+ @end_doc = @doc.at('/crossQueryResult')['endDoc']
39
+ @docs_per_page = @doc.at("/crossQueryResult/query")['maxDocs'] rescue DEFAULT_DOCS_PER_PAGE
40
+
41
+ @query = @doc.at('/crossQueryResult/query').to_s
42
+ # the docHits
43
+ # TODO deal with PDFS. This currently filters them from the results
44
+ @doc_hits = @doc.search('./docHit').collect { |h| XTF::Result::Element::DocHit.create(h, @query) }.compact
45
+
46
+ @facets = @doc.search('/crossQueryResult/facet').collect { |f| XTF::Result::Element::Facet.new(f, @query) }
47
+ end
48
+
49
+ def empty?
50
+ self.hits.size < 1
51
+ end
52
+
53
+ def previous_start_doc
54
+ diff = start_doc.to_i - docs_per_page.to_i
55
+ diff < 1 ? "1" : diff.to_s
56
+ end
57
+ alias :prev_start_doc :previous_start_doc
58
+
59
+ def next_start_doc
60
+ sum = start_doc.to_i + docs_per_page.to_i
61
+ sum > total_docs.to_i ? start_doc : sum.to_s
62
+ end
63
+
64
+ def last_start_doc
65
+ (((total_docs.to_i-1) / docs_per_page.to_i) * docs_per_page.to_i + 1).to_s
66
+ end
67
+
68
+ def total_pages
69
+ (((total_docs.to_i-1) / docs_per_page.to_i) + 1).to_s
70
+ end
71
+
72
+ def current_page
73
+ (((start_doc.to_i-1) / docs_per_page.to_i) + 1).to_s
74
+ end
75
+
76
+ def start_doc_for_page(page)
77
+ return "1" if page.to_i <= 1
78
+ return self.last_start_doc if page.to_i >= self.total_pages.to_i
79
+ (docs_per_page.to_i * (page.to_i - 1) + 1).to_s
80
+ end
81
+
82
+ def next_page_query_string()
83
+ "startDoc=#{self.next_start_doc}&docsPerPage=#{self.docs_per_page}"
84
+ end
85
+
86
+ def previous_page_query_string()
87
+ "startDoc=#{self.previous_start_doc}&docsPerPage=#{self.docs_per_page}"
88
+ end
89
+ alias :prev_page_query_string :previous_page_query_string
90
+
91
+ end
@@ -0,0 +1,13 @@
1
+ module XTF
2
+ module Result
3
+ module Element
4
+ end
5
+ end
6
+ end
7
+ $:.unshift(File.dirname(__FILE__))
8
+ require 'element/base'
9
+ require 'element/doc_hit'
10
+ require 'element/group'
11
+ require 'element/facet'
12
+ require 'element/result'
13
+ #Dir[File.dirname(__FILE__) + "/element/*.rb"].each { |file| require(file) }
data/lib/xtf/result.rb ADDED
@@ -0,0 +1,6 @@
1
+ module XTF
2
+ module Result
3
+ end
4
+ end
5
+ $:.unshift(File.dirname(__FILE__))
6
+ require 'result/element'
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XTF
4
+ module Ruby
5
+ VERSION = "1.0.0"
6
+ end
7
+ end
data/lib/xtf/ruby.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ruby/version"
4
+
5
+ module XTF
6
+ module Ruby
7
+ class Error < StandardError; end
8
+ # placeholder
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ # This class is used inside a +Query+ to retrieve all docs. It's primary purpose is
2
+ # for use with +Facet+s:
3
+ #
4
+ # query = Query.new
5
+ # query.content << AllDocs.new
6
+ # query.content << Facet.new('word')
7
+ #
8
+ class XTF::Search::Element::AllDocs
9
+
10
+ attr_reader :tag_name
11
+
12
+ def initialize
13
+ @tag_name = "all_docs"
14
+ super
15
+ end
16
+
17
+ def to_xml_node
18
+ XTF::XML::Element.new(self.tag_name.to_s.camelize(:lower))
19
+ end
20
+ def to_xml
21
+ to_xml_node.to_s
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ class XTF::Search::Element::And < XTF::Search::Element::Clause
2
+ attribute_keys BASE_ATTRIBUTE_KEYS, :fields, :use_proximity, :slop
3
+
4
+ def initialize(*args)
5
+ @tag_name = "and"
6
+ params = args[0] || {}
7
+ raise ArgumentError, "Provide :field or :fields, but not both" if params.key?(:field) && params.key?(:fields)
8
+ raise ArgumentError, ":fields requires :slop" if params.key?(:fields) && !params.key?(:slop)
9
+ raise ArgumentError, ":slop requires :fields" if params.key?(:slop) && !params.key?(:fields)
10
+ super
11
+ end
12
+
13
+ end
@@ -0,0 +1,32 @@
1
+ class XTF::Search::Element::Base
2
+ BASE_ATTRIBUTE_KEYS = [:field, :max_snippets, :boost]
3
+
4
+ def self.attribute_keys(*args)
5
+ array = args.flatten
6
+ list = array.inspect
7
+ class_eval(%Q{def attribute_keys() #{list} end}, __FILE__, __LINE__)
8
+ array.each do |k|
9
+ attr_accessor k
10
+ end
11
+ end
12
+
13
+ attribute_keys *BASE_ATTRIBUTE_KEYS
14
+
15
+ attr_reader :tag_name
16
+
17
+ # Takes a +Hash+ of attributes and sets them. Silently ignores erroneous keys.
18
+ def initialize(*args)
19
+ params = args[0]
20
+ attribute_keys.each { |k| self.__send__("#{k}=", params[k]) if params.key?(k) } if params
21
+ end
22
+
23
+ # Returns a +Hash+ of the attributes listed in +ATTRIBUTE_KEYS+ with their values.
24
+ # The keys are +Symbol+s.
25
+ def attributes
26
+ self.attribute_keys.inject({}) do |hash, key|
27
+ hash[key] = self.__send__(key) if self.__send__(key)
28
+ hash
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,57 @@
1
+ class XTF::Search::Element::Clause < XTF::Search::Element::Base
2
+ VALID_TAG_NAMES = %w{phrase exact and or or_near orNear not near range}
3
+
4
+ # an Array that contains any number of clauses and/or terms
5
+ attr_accessor :content
6
+
7
+ # convenience to create a +Term+ from a +String+ and insert it into +content+
8
+ attr_accessor :term
9
+
10
+ attr_accessor :section_type #available on all elements except <not> and <facet>
11
+
12
+ # This is a factory method for creating subclasses directly from +Clause+.
13
+ # The tag_name may be passed as the first argument or as the value to the key
14
+ # +:tag_name+ or +'tag_name'+
15
+ def self.create(*args)
16
+ tag_name = args.shift.to_s if args[0].is_a?(String) || args[0].is_a?(Symbol)
17
+ params = (args[0] || {}).symbolize_keys
18
+ tag_name = params.delete(:tag_name) unless tag_name
19
+
20
+ raise ArgumentError, "need tag_name for XTF::Search::Element::Clause" unless tag_name
21
+ raise ArgumentError, "tag_name #{tag_name} not valid for XTF::Search::Element::Clause. Must be one of: #{VALID_TAG_NAMES.join(', ')}" unless VALID_TAG_NAMES.include?(tag_name)
22
+
23
+ klass = eval("XTF::Search::Element::#{tag_name.to_s.camelize}") # scope the name to avoid conflicts, especially with Range
24
+ klass.new(params)
25
+ end
26
+
27
+ def initialize(*args)
28
+ params = args[0] || {}
29
+ self.content = params.delete(:content) || []
30
+ self.term = params.delete(:term) if params.has_key?(:term)
31
+ super(params)
32
+
33
+ raise ArgumentError, "need tag_name for XTF::Search::Element::Clause (maybe you should call Clause.create(:tag_name) ? )" unless @tag_name
34
+ raise ArgumentError, "tag_name #{@tag_name} not valid for XTF::Search::Element::Clause. Must be one of: #{VALID_TAG_NAMES.join(', ')}" unless VALID_TAG_NAMES.include?(@tag_name)
35
+ end
36
+
37
+ # Accepts a +Term+ or a +String+ which is converted to a +Term+ and adds it to the +content+.
38
+ def term=(value)
39
+ self.content << (value.is_a?(XTF::Search::Element::Term) ? value : XTF::Search::Element::Term.new(value))
40
+ end
41
+
42
+ def content=(value)
43
+ value = [value] unless value.is_a?(Array)
44
+ @content = value
45
+ end
46
+
47
+ # TODO add section_type
48
+ def to_xml_node
49
+ xml = XTF::XML::Element.new self.tag_name.camelize(:lower)
50
+ self.attributes.each_pair { |key, value| xml.attributes[key.to_s.camelize(:lower)] = value if value}
51
+ self.content.each {|node| xml.add_element(node.to_xml_node)}
52
+ xml
53
+ end
54
+ def to_xml
55
+ to_xml_node.to_s
56
+ end
57
+ end
@@ -0,0 +1,8 @@
1
+ class XTF::Search::Element::Exact < XTF::Search::Element::Phrase
2
+
3
+ def initialize(*args)
4
+ super
5
+ @tag_name = "exact"
6
+ end
7
+
8
+ end
@@ -0,0 +1,26 @@
1
+ class XTF::Search::Element::Facet < XTF::Search::Element::Base
2
+ attribute_keys :field, :select, :sort_groups_by, :sort_docs_by, :include_empty_groups
3
+
4
+ # can take a String or Symbol as first argument for required attribute #field
5
+ def initialize(*args)
6
+ @tag_name = "facet"
7
+ @field = args.shift.to_s if args[0].is_a?(String) or args[0].is_a?(Symbol)
8
+ params = args[0] || {}
9
+ raise ArgumentError, "supply field as first argument or as attribute of Hash, but not both!" if @field && params.key?(:field)
10
+
11
+ @field = params.delete(:field) unless @field
12
+ raise ArgumentError, "field is required." unless @field
13
+
14
+ super
15
+ end
16
+
17
+ def to_xml_node
18
+ xml = XTF::XML::Element.new(self.tag_name)
19
+ self.attributes.each_pair { |key, value| xml.attributes[key.to_s.camelize(:lower)] = value if value}
20
+ xml
21
+ end
22
+ def to_xml
23
+ to_xml_node.to_s
24
+ end
25
+
26
+ end
@@ -0,0 +1,17 @@
1
+ class XTF::Search::Element::Near < XTF::Search::Element::Clause
2
+ attribute_keys BASE_ATTRIBUTE_KEYS, :slop
3
+
4
+ # +slop+ is required. You can pass it in as the first argument or in the attributes +Hash+
5
+ # with the key +:slop+.
6
+ def initialize(*args)
7
+ @tag_name = "near"
8
+ @slop = args.shift.to_s if args[0].is_a?(String) || args[0].is_a?(Integer)
9
+ params = args[0] || {}
10
+ raise ArgumentError, "supply slop as first argument or as attribute of Hash, but not both!" if @slop && params.key?(:slop)
11
+
12
+ @slop = params.delete(:slop) unless @slop
13
+ raise ArgumentError, "slop is required." unless @slop
14
+
15
+ super
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ class XTF::Search::Element::Not < XTF::Search::Element::Clause
2
+ def initialize(*args)
3
+ @tag_name = "not"
4
+ super
5
+ end
6
+
7
+ end
@@ -0,0 +1,13 @@
1
+ class XTF::Search::Element::Or < XTF::Search::Element::Clause
2
+ attribute_keys BASE_ATTRIBUTE_KEYS, :fields, :slop
3
+
4
+ def initialize(*args)
5
+ @tag_name = "or"
6
+ params = args[0] || {}
7
+ raise ArgumentError, "Provide :field or :fields, but not both" if params.key?(:field) && params.key?(:fields)
8
+ raise ArgumentError, ":fields requires :slop" if params.key?(:fields) && !params.key?(:slop)
9
+ raise ArgumentError, ":slop requires :fields" if params.key?(:slop) && !params.key?(:fields)
10
+ super
11
+ end
12
+
13
+ end
@@ -0,0 +1,11 @@
1
+ # Extends +Near+, as they are identical except for +tag_name+
2
+ class XTF::Search::Element::OrNear < XTF::Search::Element::Near
3
+ attribute_keys BASE_ATTRIBUTE_KEYS, :slop
4
+
5
+ # +slop+ is required. You can pass it in as the first argument or in the attributes +Hash+
6
+ # with the key +:slop+.
7
+ def initialize(*args)
8
+ @tag_name = "orNear"
9
+ super
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ class XTF::Search::Element::Phrase < XTF::Search::Element::Clause
2
+ # Takes a +String+ and breaks it up into +Term+s:
3
+ attr_accessor :phrase
4
+
5
+ def initialize(*args)
6
+ @tag_name = "phrase"
7
+ params = args[0] || {}
8
+ _phrase = params.delete(:phrase)
9
+ super(params)
10
+ self.phrase = _phrase if _phrase
11
+ end
12
+
13
+ # If the last term is a wildcard, then append it to the previous term.
14
+ def phrase=(terms)
15
+ raise ArgumentError unless terms.is_a?(String)
16
+ terms = terms.split(XTF::Search::Constants.phrase_delimiters)
17
+ terms.first.gsub!(/^"/, "")
18
+ terms.shift if terms.first == ""
19
+ terms.last.gsub!(/"$/,"")
20
+ terms.pop if terms.last == ""
21
+ if terms.last == "*"
22
+ terms.pop
23
+ terms.last << "*"
24
+ end
25
+ terms.each { |t| self.content << XTF::Search::Element::Term.new(t) }
26
+ end
27
+
28
+ end
@@ -0,0 +1,42 @@
1
+ class XTF::Search::Element::Query < XTF::Search::Element::Base
2
+
3
+ STYLE_DEFAULT = "style/crossQuery/resultFormatter/default/resultFormatter.xsl"
4
+ INDEX_PATH_DEFAULT = "index"
5
+
6
+ attribute_keys :index_path, :style, :sort_docs_by, :start_doc, :max_docs, :term_limit, :work_limit,
7
+ :max_context, :max_snippets, :term_mode, :field, :normalize_scores, :explain_scores
8
+
9
+ attr_accessor :content # one Term or Clause, spellcheck, and any number of facet tags all in an array
10
+
11
+ def initialize(*args)
12
+ @tag_name = 'query'
13
+ params = args[0] || {}
14
+ self.content = params.delete(:content) || []
15
+ super(params)
16
+
17
+ @style ||= STYLE_DEFAULT
18
+ @index_path ||= INDEX_PATH_DEFAULT
19
+ end
20
+
21
+ # Accepts a +Term+ or a +String+ which is converted to a +Term+ and adds it to the +content+.
22
+ def term=(value)
23
+ self.content << (value.is_a?(XTF::Search::Element::Term) ? value : XTF::Search::Element::Term.new(value))
24
+ end
25
+
26
+ def content=(value)
27
+ value = [value] unless value.is_a?(Array)
28
+ @content = value
29
+ end
30
+
31
+ def to_xml_node
32
+ xml = XTF::XML::Element.new(self.tag_name)
33
+ self.attributes.each_pair { |key, value| xml.attributes[key.to_s.camelize(:lower)] = value if value}
34
+ # TODO validate only one term or clause present?
35
+ self.content.each {|node| xml.add_element(node.to_xml_node)}
36
+ xml
37
+ end
38
+ def to_xml
39
+ to_xml_node.to_s
40
+ end
41
+
42
+ end
@@ -0,0 +1,44 @@
1
+ class XTF::Search::Element::Range < XTF::Search::Element::Clause
2
+
3
+ attribute_keys BASE_ATTRIBUTE_KEYS, :inclusive, :numeric
4
+
5
+ attr_accessor :lower, :upper
6
+
7
+ # +lower+ and +upper+ may be passed as the first and second arguments.
8
+ # If one is present, then both must be. Otherwise, they may be passed as
9
+ # part of the attributes +Hash+ with the keys +:lower+ and +:upper+.
10
+ # An +ArgumentError+ will be raised if they are not provided.
11
+ def initialize(*args)
12
+ @tag_name = "range"
13
+
14
+ # retrieve lower and upper if passed as first two arguments
15
+ @lower = args.shift.to_s if args[0].is_a?(String) || args[0].is_a?(Integer)
16
+ @upper = args.shift.to_s if args[0].is_a?(String) || args[0].is_a?(Integer)
17
+ raise ArgumentError, "If you provide --lower-- as first argument, you must provide --upper-- as second." if @lower && !@upper
18
+
19
+ params = args[0] || {}
20
+ raise ArgumentError, "You have provided --lower-- and --upper-- as both arguments and attributes! Pass them as one or the other, but not both." if @lower && (params[:lower] || params[:upper])
21
+
22
+ @lower = params[:lower] && params[:lower].to_s unless @lower
23
+ @upper = params[:upper] && params[:upper].to_s unless @upper
24
+
25
+ raise ArgumentError, "You must provide --lower-- and --upper-- as the first two arguments to new() or as members of the attributes Hash." unless @lower && @upper
26
+
27
+ super
28
+ end
29
+
30
+ # TODO add section_type
31
+ def to_xml_node
32
+ xml = XTF::XML::Element.new self.tag_name.camelize(:lower)
33
+ self.attributes.each_pair { |key, value| xml.attributes[key.to_s.camelize(:lower)] = value if value}
34
+ lnode = XTF::XML::Element.new("lower")
35
+ lnode.text = self.lower
36
+ unode = XTF::XML::Element.new("upper")
37
+ unode.text = self.upper
38
+ xml.add_element(lnode)
39
+ xml.add_element(unode)
40
+ xml
41
+ end
42
+
43
+
44
+ end
@@ -0,0 +1,18 @@
1
+ class XTF::Search::Element::ResultData
2
+ attr_accessor :value
3
+ attr_reader :tag_name
4
+
5
+ def initialize(data=nil)
6
+ @tag_name = "resultData"
7
+ @value = data
8
+ end
9
+
10
+ def to_xml_node
11
+ xml = XTF::XML::Element.new(self.tag_name)
12
+ xml.text = self.value
13
+ xml
14
+ end
15
+ def to_xml
16
+ to_xml_node.to_s
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ class XTF::Search::Element::SectionType
2
+
3
+ attr_accessor :content # one Term or Clause
4
+ attr_reader :tag_name
5
+
6
+ def initialize(content = nil)
7
+ @tag_name = "sectionType"
8
+ @content = content
9
+ end
10
+
11
+ def to_xml_node
12
+ xml = XTF::XML::Element.new("sectionType")
13
+ xml.add_element(self.content.to_xml_node) if self.content
14
+ xml
15
+ end
16
+ def to_xml
17
+ to_xml_node.to_s
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ # Models a single XTF Term. However, if the term is surrounded by double-quotes, then
2
+ # a Phrase will be emitted for to_xml_node().
3
+
4
+ class XTF::Search::Element::Term < XTF::Search::Element::Base
5
+ attr_accessor :value
6
+ attr_accessor :section_type
7
+
8
+ # Should a phrase be parsed? Defaults to +true+
9
+ attr_accessor :parse_phrase
10
+
11
+ # +new+ accepts an optional first argument for +value+ as well as an optional
12
+ # first or second argument +Hash+ of the +Term+'s +attributes+.
13
+ # +value+ may be passed as the first argument or in attributes +Hash+ with key +:value+.
14
+ # +section_type+ may be passed in the attributes +Hash+ with key +:section_type+.
15
+ def initialize(*args)
16
+ @tag_name = "term"
17
+ @value = args.shift if args[0].kind_of?(String)
18
+ params = args[0] || {}
19
+ @value = params.delete(:value) unless @value
20
+ @section_type = params.delete(:section_type)
21
+ @parse_phrase = params.key?(:parse_phrase) ? params.delete(:parse_phrase) : false
22
+ super
23
+ @value.strip! unless @value.nil?
24
+ end
25
+
26
+ # For convenience, if the Term's value matches /[\-\s\\\/.,;:]+/, it will be parsed as a Phrase.
27
+ # Double quotes on either end will be removed.
28
+ #
29
+ # "this phrase" woud yield:
30
+ #
31
+ # <phrase>
32
+ # <term>this</term
33
+ # <term>phrase</term
34
+ # </phrase>
35
+ #
36
+ def to_xml_node
37
+ if self.parse_phrase && self.value =~ XTF::Search::Constants.phrase_delimiters
38
+ phrase = XTF::Search::Element::Phrase.new(self.attributes)
39
+ phrase.phrase = self.value
40
+ phrase.to_xml_node
41
+ else
42
+ xml = XTF::XML::Element.new(self.tag_name)
43
+ self.attributes.each_pair { |key, value| xml.attributes[key.to_s.camelize(:lower)] = value if value}
44
+ xml.text = self.value
45
+ xml
46
+ end
47
+ end
48
+ def to_xml
49
+ to_xml_node.to_s
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ module XTF
2
+ module Search
3
+ module Element
4
+ end
5
+ end
6
+ end
7
+
8
+ $:.unshift(File.dirname(__FILE__))
9
+ require 'element/base'
10
+ require 'element/section_type'
11
+ require 'element/result_data'
12
+ require 'element/clause'
13
+ require 'element/near'
14
+ require 'element/phrase'
15
+ Dir[File.dirname(__FILE__) + "/element/*.rb"].each { |file| require(file) }
data/lib/xtf/search.rb ADDED
@@ -0,0 +1,11 @@
1
+ module XTF
2
+ module Search
3
+ class Constants
4
+ def self.phrase_delimiters
5
+ /[\-\s\\\/.,;:()]+/
6
+ end
7
+ end
8
+ end
9
+ end
10
+ $:.unshift(File.dirname(__FILE__))
11
+ require 'search/element'
data/lib/xtf/xml.rb ADDED
@@ -0,0 +1,85 @@
1
+ # Code in this file is derived from Apache's Solr-Ruby project.
2
+
3
+ module XTF::XML
4
+ end
5
+
6
+ begin
7
+
8
+ # If we can load rubygems and libxml-ruby...
9
+ require 'rubygems'
10
+ require 'xml/libxml'
11
+
12
+ class XML::Attributes
13
+ def size
14
+ length
15
+ end
16
+ end
17
+
18
+ # Some Hpricot-like convenience methods for LibXml
19
+ class XML::Document
20
+ def self.parse_string(xml)
21
+ xml_parser = XML::Parser.string(xml)
22
+ xml_parser.parse
23
+ end
24
+ end
25
+
26
+ # then make a few modifications to XML::Node so it can stand in for REXML::Element
27
+ class XML::Node
28
+ # element.add_element(another_element) should work
29
+ alias_method :add_element, :<<
30
+
31
+ # element.attributes['blah'] should work
32
+ # def attributes
33
+ # self
34
+ # end
35
+
36
+ def size
37
+ self.attributes.length
38
+ end
39
+ alias :length :size
40
+
41
+ # element.text = "blah" should work
42
+ def text=(x)
43
+ self << x.to_s
44
+ end
45
+
46
+ def text
47
+ self.content
48
+ end
49
+
50
+ def at(xpath)
51
+ self.find_first(xpath)
52
+ end
53
+
54
+ # find the array of child nodes matching the given xpath
55
+ # TODO add these to Rexml?
56
+ def search(xpath)
57
+ results = self.find(xpath).to_a
58
+ if block_given?
59
+ results.each do |result|
60
+ yield result
61
+ end
62
+ end
63
+ return results
64
+ end
65
+
66
+ def inner_xml
67
+ child.to_s
68
+ end
69
+ alias inner_html inner_xml
70
+
71
+ def inner_text
72
+ self.content
73
+ end
74
+ end #XML::Node
75
+
76
+ # And use XML::Node for our XML generation
77
+ XTF::XML::Element = XML::Node
78
+ # raise LoadError
79
+ rescue LoadError => e # If we can't load either rubygems or libxml-ruby
80
+
81
+ # Just use REXML.
82
+ require 'rexml/document'
83
+ XTF::XML::Element = REXML::Element
84
+
85
+ end