activedocument 0.1
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.
- data/lib/ActiveDocument/active_document.rb +225 -0
- data/lib/ActiveDocument/finder.rb +100 -0
- data/lib/ActiveDocument/inheritable.rb +25 -0
- data/lib/ActiveDocument/mark_logic_http.rb +97 -0
- data/lib/ActiveDocument/mark_logic_query_builder.rb +76 -0
- data/lib/ActiveDocument/search_match.rb +54 -0
- data/lib/ActiveDocument/search_options.rb +59 -0
- data/lib/ActiveDocument/search_result.rb +65 -0
- data/lib/ActiveDocument/search_results.rb +65 -0
- data/lib/active_document.rb +10 -0
- data/lib/activedocument.rb +1 -0
- metadata +84 -0
@@ -0,0 +1,225 @@
|
|
1
|
+
# Copyright 2010 Mark Logic, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require 'ActiveDocument/mark_logic_http'
|
17
|
+
require 'rubygems'
|
18
|
+
require 'nokogiri'
|
19
|
+
require 'yaml'
|
20
|
+
require 'ActiveDocument/mark_logic_query_builder'
|
21
|
+
require 'ActiveDocument/search_results'
|
22
|
+
require 'ActiveDocument/finder'
|
23
|
+
require "ActiveDocument/inheritable"
|
24
|
+
|
25
|
+
# The ActiveXML module is used as a namespace for all classes relating to the ActiveXML functionality.
|
26
|
+
# ActiveXML::Base is the class that should be extended in order to make use of this functionality in your own
|
27
|
+
# domain objects.
|
28
|
+
#
|
29
|
+
module ActiveDocument
|
30
|
+
|
31
|
+
# Developers should extend this class to create their own domain classes
|
32
|
+
# -------------------
|
33
|
+
# = Usage
|
34
|
+
# -------------------
|
35
|
+
# == Dynamic Finders
|
36
|
+
# ActiveDocument::Base provides extensive methods for finding matching documents based on a variety of criteria.
|
37
|
+
# === Find By Element
|
38
|
+
# Accessed via find_by_ELEMENT method where Element = the name of your element. Executes a search for all documents
|
39
|
+
# with an element ELEMENT that contains the value passed in to the method call.
|
40
|
+
# The signature of this dynamic finder is:
|
41
|
+
# <tt>find_by_ELEMENT(value, root [optional], element_namespace [optional], root_namespace [optional])</tt>
|
42
|
+
#
|
43
|
+
# Parameters details are as follows:
|
44
|
+
# <b>Value:</b> the text to be found within the given element. This is a mandatory parameter
|
45
|
+
# <b>Namespace:</b> The namespace in which the element being searched occurs. This is an optional element. If provided,
|
46
|
+
# it will be used in the search and will override any default values. If no namespace if provided then the code will
|
47
|
+
# attempt to dynamically determine the namespace. First, if the element name is contained in the namespaces hash
|
48
|
+
# then that namespace is used. If the element name is not found then the _default_namespace_
|
49
|
+
# is used. If there is no default namespace, then no namespace is used.
|
50
|
+
# -------------------
|
51
|
+
# == Dynamic Accessors
|
52
|
+
# In addition to the ability to access the underlying XML document (as a Nokogiri XML Document) you have the ability
|
53
|
+
# to access the XML as attributes of your domain object via dynamic attribute accessors (eg. domain_object.element_name) The rules for using accessors
|
54
|
+
# are as follows:
|
55
|
+
# 1. If the element_name is a simple type (i.e. text node with no children <tt><example>text</exmample></tt>)
|
56
|
+
# 1. If there is only one occurence of the element, return its text value
|
57
|
+
# 2. If there are multiple occurences of the element, return a list of each text value
|
58
|
+
# 2. If the element_name is a complex type (e.g. <tt><example><text>hi</text></example></tt>)
|
59
|
+
# 1. If there is only one ocurence of the element then return it as a Nokoguri Element
|
60
|
+
# 2. If there are multiple occurences of the element, return a list of Nokoguri Elements
|
61
|
+
#
|
62
|
+
# More complex dynamic accessors are also supported. They still adhere to the rules above, but instead of just looking
|
63
|
+
# for an element anywhere in the document, you can be more specific. For example, domain_object.chapter.paragraph
|
64
|
+
# will find all paragraph elements that are children of chapter elements.
|
65
|
+
# -------------------
|
66
|
+
class Base < Finder
|
67
|
+
include ClassLevelInheritableAttributes
|
68
|
+
inheritable_attributes_list :namespaces, :default_namespace, :root
|
69
|
+
@namespaces = Hash.new
|
70
|
+
@default_namespace = String.new
|
71
|
+
attr_reader :document, :uri
|
72
|
+
|
73
|
+
|
74
|
+
# create a new instance with an optional xml string to use for constructing the model
|
75
|
+
def initialize(xml_string = nil, uri = nil)
|
76
|
+
unless xml_string.nil?
|
77
|
+
@document = Nokogiri::XML(xml_string) do |config|
|
78
|
+
config.noblanks
|
79
|
+
end
|
80
|
+
end
|
81
|
+
@root = self.class.to_s
|
82
|
+
@uri = uri
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the root element for this object
|
86
|
+
def root
|
87
|
+
@root
|
88
|
+
end
|
89
|
+
|
90
|
+
# Sets the root element for this object
|
91
|
+
def root=(value)
|
92
|
+
@root = value
|
93
|
+
end
|
94
|
+
|
95
|
+
# enables the dynamic finders
|
96
|
+
def method_missing(method_id, *arguments, &block)
|
97
|
+
@@log.debug("ActiveDocument::Base at line #{__LINE__}: method called is #{method_id} with arguments #{arguments}")
|
98
|
+
method = method_id.to_s
|
99
|
+
if method =~ /^(\w*)$/ # methods with no '.' in them and not ending in '='
|
100
|
+
if arguments.length > 0
|
101
|
+
super
|
102
|
+
end
|
103
|
+
access_element $1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def access_element(element)
|
108
|
+
xpath = ""
|
109
|
+
xpath = "//" unless self.instance_of? PartialResult
|
110
|
+
namespace = self.class.namespace_for_element(element)
|
111
|
+
element = "ns:#{element}" unless namespace.nil? || namespace.empty?
|
112
|
+
xpath << element
|
113
|
+
if namespace.nil?
|
114
|
+
nodes = @document.xpath(xpath)
|
115
|
+
else
|
116
|
+
nodes = @document.xpath(xpath, {'ns' => namespace})
|
117
|
+
end
|
118
|
+
evaluate_nodeset(nodes)
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def evaluate_nodeset(result_nodeset)
|
124
|
+
if result_nodeset.length == 1 # found one match
|
125
|
+
if result_nodeset[0].children.length == 1 and result_nodeset[0].children[0].type == Nokogiri::XML::Node::TEXT_NODE
|
126
|
+
result_nodeset[0].text
|
127
|
+
elsif result_nodeset[0].children.length >1 # we are now dealing with complex nodes
|
128
|
+
PartialResult.new(result_nodeset)
|
129
|
+
end
|
130
|
+
elsif result_nodeset.length >1 # multiple matches
|
131
|
+
if result_nodeset.all? {|node| node.children.length == 1} and result_nodeset.all? {|node| node.children[0].type == Nokogiri::XML::Node::TEXT_NODE}
|
132
|
+
# we have multiple simple text nodes
|
133
|
+
result_nodeset.collect {|node| node.text}
|
134
|
+
else
|
135
|
+
# we have multiple complex elements
|
136
|
+
PartialResult.new(result_nodeset)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class PartialResult < self
|
142
|
+
def initialize(nodeset)
|
143
|
+
@document = nodeset
|
144
|
+
@root = nodeset[0].name
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_s
|
148
|
+
@document.to_s
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
class << self
|
155
|
+
attr_reader :namespaces, :default_namespace, :root
|
156
|
+
|
157
|
+
def namespaces(namespace_hash)
|
158
|
+
@namespaces = namespace_hash
|
159
|
+
end
|
160
|
+
|
161
|
+
def add_namespace(element, uri)
|
162
|
+
@namespaces[element.to_s] == uri
|
163
|
+
end
|
164
|
+
|
165
|
+
def remove_namespace(element)
|
166
|
+
@namespaces.delete element
|
167
|
+
end
|
168
|
+
|
169
|
+
def default_namespace(namespace)
|
170
|
+
@default_namespace = namespace # todo should this just be an entry in namespaces?
|
171
|
+
end
|
172
|
+
|
173
|
+
# enables the dynamic finders
|
174
|
+
def method_missing(method_id, *arguments, &block)
|
175
|
+
@@log.debug("ActiveDocument::Base at line #{__LINE__}: method called is #{method_id} with arguments #{arguments}")
|
176
|
+
method = method_id.to_s
|
177
|
+
# identify element search methods
|
178
|
+
if method =~ /find_by_(.*)$/ and arguments.length > 0
|
179
|
+
value = arguments[0]
|
180
|
+
element = $1.to_sym
|
181
|
+
if arguments[1]
|
182
|
+
root = arguments[1]
|
183
|
+
else
|
184
|
+
root = @root
|
185
|
+
end
|
186
|
+
if arguments[2]
|
187
|
+
element_namespace = arguments[2]
|
188
|
+
else
|
189
|
+
element_namespace = namespace_for_element(element)
|
190
|
+
end
|
191
|
+
if arguments[3]
|
192
|
+
root_namespace = arguments[3]
|
193
|
+
else
|
194
|
+
root_namespace = namespace_for_element(root)
|
195
|
+
end
|
196
|
+
execute_finder(element, value, root, element_namespace, root_namespace)
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns an ActiveXML object representing the requested information
|
202
|
+
def load(uri)
|
203
|
+
self.new(@@ml_http.send_xquery(@@xquery_builder.load(uri)), uri)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Finds all documents of this type that contain the word anywhere in their structure
|
207
|
+
def find_by_word(word, root=@root, namespace=@default_namespace)
|
208
|
+
SearchResults.new(@@ml_http.send_xquery(@@xquery_builder.find_by_word(word, root, namespace)))
|
209
|
+
end
|
210
|
+
|
211
|
+
def namespace_for_element(element)
|
212
|
+
namespace = nil
|
213
|
+
if @namespaces[element]
|
214
|
+
namespace = @namespaces[element]
|
215
|
+
else
|
216
|
+
namespace = @default_namespace unless @default_namespace.nil?
|
217
|
+
end
|
218
|
+
namespace
|
219
|
+
end
|
220
|
+
end # end inner class
|
221
|
+
|
222
|
+
end # end class
|
223
|
+
|
224
|
+
|
225
|
+
end # end module
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# Copyright 2010 Mark Logic, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'ActiveDocument/mark_logic_http'
|
16
|
+
require 'ActiveDocument/mark_logic_query_builder'
|
17
|
+
require 'rubygems'
|
18
|
+
require 'nokogiri'
|
19
|
+
require 'ActiveDocument/search_results'
|
20
|
+
require 'logger'
|
21
|
+
|
22
|
+
module ActiveDocument
|
23
|
+
|
24
|
+
class Finder
|
25
|
+
|
26
|
+
|
27
|
+
@@xquery_builder = ActiveDocument::MarkLogicQueryBuilder.new
|
28
|
+
|
29
|
+
def self.config(yaml_file)
|
30
|
+
config = YAML.load_file(yaml_file)
|
31
|
+
@@ml_http = ActiveDocument::MarkLogicHTTP.new(config['uri'], config['user_name'], config['password'])
|
32
|
+
configure_logger(config)
|
33
|
+
end
|
34
|
+
|
35
|
+
# enables the dynamic finders
|
36
|
+
def self.method_missing(method_id, *arguments, &block)
|
37
|
+
@@log.debug("ActiveDocument::Finder at line #{__LINE__}: method called is #{method_id} with arguments #{arguments}")
|
38
|
+
method = method_id.to_s
|
39
|
+
# identify finder methods
|
40
|
+
if method =~ /find_by_(.*)$/ and arguments.length > 0
|
41
|
+
namespace = arguments[1] if arguments.length == 2
|
42
|
+
execute_finder($1.to_sym, arguments[0], nil, namespace)
|
43
|
+
else
|
44
|
+
puts "missed"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.execute_finder(element, value, root = nil, element_namespace = nil, root_namespace = nil)
|
49
|
+
xquery = @@xquery_builder.find_by_element(element, value, root, element_namespace, root_namespace)
|
50
|
+
@@log.info("Finder.execute_finder at line #{__LINE__}: #{xquery}")
|
51
|
+
SearchResults.new(@@ml_http.send_xquery(xquery))
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.search(search_string, start = 1, page_length = 10, options = nil)
|
55
|
+
if start.nil? : start = 1 end
|
56
|
+
if page_length.nil? : page_length = 10 end
|
57
|
+
search_text = @@xquery_builder.search(search_string, start, page_length, options)
|
58
|
+
SearchResults.new(@@ml_http.send_xquery(search_text)) # todo support options node
|
59
|
+
end
|
60
|
+
private
|
61
|
+
|
62
|
+
def self.configure_logger(config)
|
63
|
+
|
64
|
+
begin
|
65
|
+
log_location = if config['logger']['file']
|
66
|
+
config['logger']['file']
|
67
|
+
else
|
68
|
+
STDERR
|
69
|
+
end
|
70
|
+
log_level = case config['logger']['level']
|
71
|
+
when "debug" then
|
72
|
+
Logger::DEBUG
|
73
|
+
when "info" then
|
74
|
+
Logger::INFO
|
75
|
+
when "warn" then
|
76
|
+
Logger::WARN
|
77
|
+
when "error" then
|
78
|
+
Logger::ERROR
|
79
|
+
when "fatal" then
|
80
|
+
Logger::FATAL
|
81
|
+
else
|
82
|
+
Logger::WARN
|
83
|
+
end
|
84
|
+
|
85
|
+
rotation = if config['logger']['rotation']
|
86
|
+
config['logger']['rotation']
|
87
|
+
else
|
88
|
+
"daily"
|
89
|
+
end
|
90
|
+
file = open(log_location, File::WRONLY | File::APPEND | File::CREAT)
|
91
|
+
@@log = Logger.new(file, rotation)
|
92
|
+
@@log.level = log_level
|
93
|
+
rescue StandardError => oops
|
94
|
+
@@log = Logger.new(STDERR)
|
95
|
+
@@log.level = Logger::WARN
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ClassLevelInheritableAttributes
|
2
|
+
def self.included(base)
|
3
|
+
base.extend(ClassMethods)
|
4
|
+
end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def inheritable_attributes_list(*args)
|
8
|
+
@inheritable_attributes_list ||= [:inheritable_attributes_list]
|
9
|
+
@inheritable_attributes_list += args
|
10
|
+
args.each do |arg|
|
11
|
+
class_eval %(
|
12
|
+
class << self; attr_accessor :#{arg} end
|
13
|
+
)
|
14
|
+
end
|
15
|
+
@inheritable_attributes_list
|
16
|
+
end
|
17
|
+
|
18
|
+
def inherited(subclass)
|
19
|
+
@inheritable_attributes_list.each do |inheritable_attribute|
|
20
|
+
instance_var = "@#{inheritable_attribute}"
|
21
|
+
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# Copyright 2010 Mark Logic, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'net/http'
|
16
|
+
require 'uri'
|
17
|
+
require 'digest/md5'
|
18
|
+
|
19
|
+
module Net
|
20
|
+
module HTTPHeader
|
21
|
+
@@nonce_count = -1
|
22
|
+
CNONCE = Digest::MD5.new.update("%x" % (Time.now.to_i + rand(65535))).hexdigest
|
23
|
+
|
24
|
+
def digest_auth(user, password, response)
|
25
|
+
# based on http://segment7.net/projects/ruby/snippets/digest_auth.rb
|
26
|
+
@@nonce_count += 1
|
27
|
+
|
28
|
+
response['www-authenticate'] =~ /^(\w+) (.*)/
|
29
|
+
|
30
|
+
params = {}
|
31
|
+
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
|
32
|
+
|
33
|
+
a_1 = "#{user}:#{params['realm']}:#{password}"
|
34
|
+
a_2 = "#{@method}:#{@path}"
|
35
|
+
request_digest = ''
|
36
|
+
request_digest << Digest::MD5.new.update(a_1).hexdigest
|
37
|
+
request_digest << ':' << params['nonce']
|
38
|
+
request_digest << ':' << ('%08x' % @@nonce_count)
|
39
|
+
request_digest << ':' << CNONCE
|
40
|
+
request_digest << ':' << params['qop']
|
41
|
+
request_digest << ':' << Digest::MD5.new.update(a_2).hexdigest
|
42
|
+
|
43
|
+
header = []
|
44
|
+
header << "Digest username=\"#{user}\""
|
45
|
+
header << "realm=\"#{params['realm']}\""
|
46
|
+
|
47
|
+
header << "qop=#{params['qop']}"
|
48
|
+
|
49
|
+
header << "algorithm=MD5"
|
50
|
+
header << "uri=\"#{@path}\""
|
51
|
+
header << "nonce=\"#{params['nonce']}\""
|
52
|
+
header << "nc=#{'%08x' % @@nonce_count}"
|
53
|
+
header << "cnonce=\"#{CNONCE}\""
|
54
|
+
header << "response=\"#{Digest::MD5.new.update(request_digest).hexdigest}\""
|
55
|
+
|
56
|
+
@header['Authorization'] = header
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module ActiveDocument
|
62
|
+
|
63
|
+
class MarkLogicHTTP
|
64
|
+
|
65
|
+
def initialize(uri, user_name, password)
|
66
|
+
@url = URI.parse(uri)
|
67
|
+
@user_name = user_name
|
68
|
+
@password = password
|
69
|
+
end
|
70
|
+
|
71
|
+
def send_xquery(xquery)
|
72
|
+
if xquery.nil? or xquery.empty? then
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
req = authenticate()
|
76
|
+
req.set_form_data({'request'=>"#{xquery}"})
|
77
|
+
res = Net::HTTP.new(@url.host, @url.port).start {|http| http.request(req) }
|
78
|
+
case res
|
79
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
80
|
+
# puts res.body
|
81
|
+
res.body
|
82
|
+
else
|
83
|
+
res.error!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def authenticate
|
89
|
+
req = Net::HTTP::Post.new(@url.path)
|
90
|
+
Net::HTTP.start(@url.host, @url.port) do |http|
|
91
|
+
res = http.head(@url.request_uri)
|
92
|
+
req.digest_auth(@user_name, @password, res)
|
93
|
+
end
|
94
|
+
return req
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Copyright 2010 Mark Logic, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module ActiveDocument
|
16
|
+
|
17
|
+
class MarkLogicQueryBuilder
|
18
|
+
|
19
|
+
def load(uri)
|
20
|
+
"fn:doc('#{uri}')"
|
21
|
+
end
|
22
|
+
|
23
|
+
# This method does a full text search
|
24
|
+
def find_by_word(word, root, namespace)
|
25
|
+
xquery = <<GENERATED
|
26
|
+
import module namespace search = "http://marklogic.com/appservices/search"at "/MarkLogic/appservices/search/search.xqy";
|
27
|
+
search:search("#{word}",
|
28
|
+
<options xmlns="http://marklogic.com/appservices/search">
|
29
|
+
GENERATED
|
30
|
+
unless root.nil?
|
31
|
+
xquery << "<searchable-expression"
|
32
|
+
|
33
|
+
xquery << " xmlns:a=\"#{namespace}\"" unless namespace.nil? or namespace.empty?
|
34
|
+
xquery << '>/'
|
35
|
+
xquery << "a:" unless namespace.nil? or namespace.empty?
|
36
|
+
xquery << "#{root}</searchable-expression>"
|
37
|
+
end
|
38
|
+
xquery << "</options>)"
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_by_element(element, value, root = nil, element_namespace = nil, root_namespace = nil)
|
42
|
+
xquery = <<GENERATED
|
43
|
+
import module namespace search = "http://marklogic.com/appservices/search"at "/MarkLogic/appservices/search/search.xqy";
|
44
|
+
search:search("word:#{value}",
|
45
|
+
<options xmlns="http://marklogic.com/appservices/search">
|
46
|
+
GENERATED
|
47
|
+
unless root.nil?
|
48
|
+
xquery << "<searchable-expression"
|
49
|
+
xquery << " xmlns:a=\"#{root_namespace}\"" unless root_namespace.nil?
|
50
|
+
xquery << '>/'
|
51
|
+
xquery << "a:" unless root_namespace.nil?
|
52
|
+
xquery << "#{root}</searchable-expression>)"
|
53
|
+
end
|
54
|
+
xquery << <<CONSTRAINT
|
55
|
+
<constraint name="word">
|
56
|
+
<word>
|
57
|
+
<element ns="#{element_namespace unless element_namespace.nil?}" name="#{element}"/>
|
58
|
+
</word>
|
59
|
+
</constraint></options>)
|
60
|
+
CONSTRAINT
|
61
|
+
end
|
62
|
+
|
63
|
+
def search(search_text, start, page_length, options)
|
64
|
+
if options.nil?
|
65
|
+
option = '()'
|
66
|
+
else
|
67
|
+
option = options.to_s
|
68
|
+
end
|
69
|
+
<<-GENERATED
|
70
|
+
import module namespace search = "http://marklogic.com/appservices/search"at "/MarkLogic/appservices/search/search.xqy";
|
71
|
+
search:search("#{search_text}",#{option},#{start}, #{page_length})
|
72
|
+
GENERATED
|
73
|
+
end
|
74
|
+
|
75
|
+
end # end class
|
76
|
+
end # end module
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Copyright 2010 Mark Logic, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'rubygems'
|
16
|
+
require 'nokogiri'
|
17
|
+
|
18
|
+
module ActiveDocument
|
19
|
+
class SearchMatch
|
20
|
+
include Enumerable
|
21
|
+
|
22
|
+
def initialize(node)
|
23
|
+
@node = node
|
24
|
+
end
|
25
|
+
|
26
|
+
def path
|
27
|
+
@node.xpath("./@path").to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
value = @node.xpath("./node()").to_s
|
32
|
+
begin
|
33
|
+
value[/<search:highlight>/] = ""
|
34
|
+
value[/<\/search:highlight>/] = ""
|
35
|
+
rescue IndexError
|
36
|
+
end
|
37
|
+
return value
|
38
|
+
end
|
39
|
+
|
40
|
+
def highlighted_match(highlight_tag = nil)
|
41
|
+
value = @node.xpath("./node()").to_s
|
42
|
+
unless highlight_tag.nil?
|
43
|
+
begin
|
44
|
+
value[/<search:highlight>/] = "<#{highlight_tag}>"
|
45
|
+
value[/<\/search:highlight>/] = "</#{highlight_tag}>"
|
46
|
+
rescue IndexError
|
47
|
+
value
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
return value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright 2010 Mark Logic, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
# SearchOptions allow you to control exactly how the ActiveDocument::Finder#search method behaves and what additional
|
17
|
+
# information may be returned in the ActiveDocument::SearchResults object
|
18
|
+
# == Attributes
|
19
|
+
# * return_facets - if true then facet information is returned in the resultant ActiveDocument::SearchResults object. Default is true
|
20
|
+
# * value_constraints - this is a #Hash of value constraint names to their options. e.g. search_options_object.value_constraints["Region"] = {"namespace" => "http://wits.nctc.gov", "element" => "Region"}
|
21
|
+
module ActiveDocument
|
22
|
+
class SearchOptions
|
23
|
+
attr_accessor :return_facets, :value_constraints
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@return_facets = true;
|
27
|
+
@value_constraints = Hash.new
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# outputs the object in correctly formatted XML suitable for use in a search
|
32
|
+
def to_s
|
33
|
+
value_constraints = String.new
|
34
|
+
@value_constraints.each do |key, value|
|
35
|
+
value_constraints << <<-XML
|
36
|
+
<constraint name="#{key}">
|
37
|
+
<value>
|
38
|
+
<element ns="#{value["namespace"]}" name="#{value["element"]}"/>
|
39
|
+
</value>
|
40
|
+
</constraint>
|
41
|
+
XML
|
42
|
+
end
|
43
|
+
|
44
|
+
value = <<-XML
|
45
|
+
<options xmlns="http://marklogic.com/appservices/search">
|
46
|
+
<return-facets>#{@return_facets}</return-facets>
|
47
|
+
XML
|
48
|
+
|
49
|
+
# add in constraints
|
50
|
+
unless value_constraints.empty?
|
51
|
+
value << value_constraints
|
52
|
+
end
|
53
|
+
|
54
|
+
# close the options node
|
55
|
+
value << "</options>"
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Copyright 2010 Mark Logic, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'rubygems'
|
16
|
+
require 'nokogiri'
|
17
|
+
require 'ActiveDocument/search_match'
|
18
|
+
|
19
|
+
module ActiveDocument
|
20
|
+
class SearchResult
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
def initialize(node)
|
24
|
+
@node = node
|
25
|
+
end
|
26
|
+
|
27
|
+
def index
|
28
|
+
Integer(@node.xpath("./@index").to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def uri
|
32
|
+
@node.xpath("./@uri").to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def path
|
36
|
+
@node.xpath("./@path").to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def score
|
40
|
+
Float(@node.xpath("./@score").to_s)
|
41
|
+
end
|
42
|
+
|
43
|
+
def confidence
|
44
|
+
Float(@node.xpath("./@confidence").to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fitness
|
48
|
+
Integer(@node.xpath("./@fitness").to_s)
|
49
|
+
end
|
50
|
+
|
51
|
+
def each(&block)
|
52
|
+
@node.xpath("./search:snippet/search:match").each {|node| yield SearchMatch.new(node)}
|
53
|
+
end
|
54
|
+
|
55
|
+
def root_type
|
56
|
+
full_path = @node.xpath("./search:snippet/search:match")[1].xpath("./@path").to_s
|
57
|
+
root = full_path.match(/:[[:alpha:]]+\//) # find the first :something/ which should indicate the root
|
58
|
+
root.to_s.delete(":/") # remove the : and / to get the root element name
|
59
|
+
end
|
60
|
+
|
61
|
+
def realize(klass)
|
62
|
+
klass.load(uri)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Copyright 2010 Mark Logic, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'rubygems'
|
16
|
+
require 'nokogiri'
|
17
|
+
require 'ActiveDocument/search_result'
|
18
|
+
|
19
|
+
module ActiveDocument
|
20
|
+
class SearchResults
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
def initialize(results)
|
24
|
+
@results_document = Nokogiri::XML(results)
|
25
|
+
end
|
26
|
+
|
27
|
+
def total
|
28
|
+
Integer(@results_document.xpath("/search:response/@total").to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def start
|
32
|
+
Integer(@results_document.xpath("/search:response/@start").to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
def page_length
|
36
|
+
Integer(@results_document.xpath("/search:response/@page-length").to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
def search_text
|
40
|
+
@results_document.xpath("/search:response/search:qtext/text()").to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def query_resolution_time
|
44
|
+
@results_document.xpath("/search:response/search:metrics/search:query-resolution-time/text()").to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
def snippet_resolution_time
|
48
|
+
@results_document.xpath("/search:response/search:metrics/search:snippet-resolution-time/text()").to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def facet_resolution_time
|
52
|
+
@results_document.xpath("/search:response/search:metrics/search:facet-resolution-time/text()").to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def total_time
|
56
|
+
@results_document.xpath("/search:response/search:metrics/search:total-time/text()").to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def each(&block)
|
60
|
+
@results_document.xpath("/search:response/search:result").each {|node| yield SearchResult.new(node)}
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'ActiveDocument/active_document'
|
2
|
+
require 'ActiveDocument/finder'
|
3
|
+
require 'ActiveDocument/inheritable'
|
4
|
+
require 'ActiveDocument/mark_logic_http'
|
5
|
+
require 'ActiveDocument/mark_logic_query_builder'
|
6
|
+
require 'ActiveDocument/search_match'
|
7
|
+
require 'ActiveDocument/search_options'
|
8
|
+
require 'ActiveDocument/search_result'
|
9
|
+
require 'ActiveDocument/search_results'
|
10
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'active_document'
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activedocument
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Clark D. Richey, Jr.
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-04-02 00:00:00 -04:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: nokogiri
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
segments:
|
27
|
+
- 1
|
28
|
+
- 4
|
29
|
+
- 1
|
30
|
+
version: 1.4.1
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
description: Object Mapper for XML Database. Initially setup for connection to MarkLogic
|
34
|
+
email: clark@clarkrichey.com
|
35
|
+
executables: []
|
36
|
+
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
files:
|
42
|
+
- lib/active_document.rb
|
43
|
+
- lib/ActiveDocument/active_document.rb
|
44
|
+
- lib/ActiveDocument/finder.rb
|
45
|
+
- lib/ActiveDocument/inheritable.rb
|
46
|
+
- lib/ActiveDocument/mark_logic_http.rb
|
47
|
+
- lib/ActiveDocument/mark_logic_query_builder.rb
|
48
|
+
- lib/ActiveDocument/search_match.rb
|
49
|
+
- lib/ActiveDocument/search_options.rb
|
50
|
+
- lib/ActiveDocument/search_result.rb
|
51
|
+
- lib/ActiveDocument/search_results.rb
|
52
|
+
- lib/activedocument.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/crichey/ActiveDocument
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- .
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.6
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Object Mapper for XML Database
|
83
|
+
test_files: []
|
84
|
+
|