xtf-ruby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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