om 1.4.4 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/History.textile +4 -0
- data/lib/om/version.rb +1 -1
- data/lib/om/xml/document.rb +33 -29
- data/lib/om/xml/term.rb +62 -53
- data/lib/om/xml/terminology.rb +49 -48
- data/spec/fixtures/no_namespace.xml +7 -0
- data/spec/unit/term_spec.rb +30 -33
- data/spec/unit/term_xpath_generator_spec.rb +37 -29
- data/spec/unit/terminology_spec.rb +79 -53
- metadata +9 -8
data/Gemfile.lock
CHANGED
data/History.textile
CHANGED
data/lib/om/version.rb
CHANGED
data/lib/om/xml/document.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
module OM::XML::Document
|
2
|
-
|
3
|
-
|
4
|
-
# Class Methods -- These methods will be available on classes that include this Module
|
5
|
-
|
2
|
+
|
3
|
+
|
4
|
+
# Class Methods -- These methods will be available on classes that include this Module
|
5
|
+
|
6
6
|
module ClassMethods
|
7
|
-
|
7
|
+
|
8
8
|
attr_accessor :terminology, :template_registry
|
9
|
-
|
9
|
+
|
10
10
|
# Sets the OM::XML::Terminology for the Document
|
11
11
|
# Expects +&block+ that will be passed into OM::XML::Terminology::Builder.new
|
12
12
|
def set_terminology &block
|
13
13
|
@terminology = OM::XML::Terminology::Builder.new( &block ).build
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
# Define a new node template with the OM::XML::TemplateRegistry.
|
17
17
|
# * +name+ is a Symbol indicating the name of the new template.
|
18
|
-
# * The +block+ does the work of creating the new node, and will receive
|
18
|
+
# * The +block+ does the work of creating the new node, and will receive
|
19
19
|
# a Nokogiri::XML::Builder and any other args passed to one of the node instantiation methods.
|
20
20
|
def define_template name, &block
|
21
21
|
@template_registry ||= OM::XML::TemplateRegistry.new
|
22
22
|
@template_registry.define name, &block
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
# Returns any namespaces defined by the Class' Terminology
|
26
26
|
def ox_namespaces
|
27
27
|
if @terminology.nil?
|
@@ -30,16 +30,16 @@ module OM::XML::Document
|
|
30
30
|
return @terminology.namespaces
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# Instance Methods -- These methods will be available on instances of classes that include this module
|
37
|
-
|
37
|
+
|
38
38
|
attr_accessor :ox_namespaces
|
39
|
-
|
39
|
+
|
40
40
|
def self.included(klass)
|
41
41
|
klass.extend(ClassMethods)
|
42
|
-
|
42
|
+
|
43
43
|
klass.send(:include, OM::XML::Container)
|
44
44
|
klass.send(:include, OM::XML::TermValueOperators)
|
45
45
|
klass.send(:include, OM::XML::Validation)
|
@@ -54,7 +54,7 @@ module OM::XML::Document
|
|
54
54
|
node.val=args
|
55
55
|
else
|
56
56
|
super
|
57
|
-
end
|
57
|
+
end
|
58
58
|
else
|
59
59
|
term = self.class.terminology.retrieve_term(name)
|
60
60
|
if (term)
|
@@ -63,25 +63,29 @@ module OM::XML::Document
|
|
63
63
|
super
|
64
64
|
end
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
|
68
68
|
end
|
69
69
|
|
70
70
|
|
71
71
|
def find_by_xpath(xpath)
|
72
|
-
|
72
|
+
if ox_namespaces.values.compact.empty?
|
73
|
+
ng_xml.xpath(xpath)
|
74
|
+
else
|
75
|
+
ng_xml.xpath(xpath, ox_namespaces)
|
76
|
+
end
|
73
77
|
end
|
74
|
-
|
75
|
-
|
78
|
+
|
79
|
+
|
76
80
|
# Applies the property's corresponding xpath query, returning the result Nokogiri::XML::NodeSet
|
77
81
|
def find_by_terms_and_value(*term_pointer)
|
78
|
-
xpath = self.class.terminology.xpath_for(*term_pointer)
|
82
|
+
xpath = self.class.terminology.xpath_for(*term_pointer)
|
79
83
|
find_by_xpath(xpath) unless xpath.nil?
|
80
84
|
end
|
81
|
-
|
85
|
+
|
82
86
|
|
83
87
|
# +term_pointer+ Variable length array of values in format [:accessor_name, :accessor_name ...] or [{:accessor_name=>index}, :accessor_name ...]
|
84
|
-
# @example:
|
88
|
+
# @example:
|
85
89
|
# find_by_terms( {:person => 1}, :first_name )
|
86
90
|
# @example
|
87
91
|
# find_by_terms( [:person, 1, :first_name] )
|
@@ -89,10 +93,10 @@ module OM::XML::Document
|
|
89
93
|
# @example Pass in your own xpath query if you don't want to bother with Term pointers but do want OM to handle namespaces for you.
|
90
94
|
# find_by_terms('//oxns:name[@type="personal"][contains(oxns:role, "donor")]')
|
91
95
|
def find_by_terms(*term_pointer)
|
92
|
-
xpath = self.class.terminology.xpath_with_indexes(*term_pointer)
|
96
|
+
xpath = self.class.terminology.xpath_with_indexes(*term_pointer)
|
93
97
|
find_by_xpath(xpath) unless xpath.nil?
|
94
98
|
end
|
95
|
-
|
99
|
+
|
96
100
|
# Test whether the document has a node corresponding to the given term_pointer
|
97
101
|
# @param [Array] term_pointer to test
|
98
102
|
def node_exists?(*term_pointer)
|
@@ -103,11 +107,11 @@ module OM::XML::Document
|
|
103
107
|
def template_registry
|
104
108
|
self.class.template_registry
|
105
109
|
end
|
106
|
-
|
110
|
+
|
107
111
|
def template(node_type, *args)
|
108
112
|
template_registry.instantiate(node_type, *args)
|
109
113
|
end
|
110
|
-
|
114
|
+
|
111
115
|
# Instantiate a +node_type+ template and add it as a child of +target_node+, where +target_node+ is one of:
|
112
116
|
# * a Nokogiri::XML::Node
|
113
117
|
# * a single-element Nokogiri::XML::NodeSet
|
@@ -118,8 +122,8 @@ module OM::XML::Document
|
|
118
122
|
def add_child_node(target_node, node_type, *args, &block)
|
119
123
|
manipulate_node(:add_child, target_node, node_type, *args, &block)
|
120
124
|
end
|
121
|
-
|
122
|
-
# Instantiate a +node_type+ template and insert it as the following sibling of +target_node+.
|
125
|
+
|
126
|
+
# Instantiate a +node_type+ template and insert it as the following sibling of +target_node+.
|
123
127
|
# Returns the new Nokogiri::XML::Node.
|
124
128
|
def add_next_sibling_node(target_node, node_type, *args, &block)
|
125
129
|
manipulate_node(:add_next_sibling, target_node, node_type, *args, &block)
|
@@ -154,7 +158,7 @@ module OM::XML::Document
|
|
154
158
|
def swap_node(target_node, node_type, *args, &block)
|
155
159
|
manipulate_node(:swap, target_node, node_type, *args, &block)
|
156
160
|
end
|
157
|
-
|
161
|
+
|
158
162
|
# Returns a hash combining the current documents namespaces (provided by nokogiri) and any namespaces that have been set up by your Terminology.
|
159
163
|
# Most importantly, this matches the 'oxns' namespace to the namespace you provided in your Terminology's root term config
|
160
164
|
def ox_namespaces
|
data/lib/om/xml/term.rb
CHANGED
@@ -1,37 +1,37 @@
|
|
1
|
-
# Special options:
|
2
|
-
# data_type, index_as, attributes,
|
1
|
+
# Special options:
|
2
|
+
# data_type, index_as, attributes,
|
3
3
|
# is_root_term, required
|
4
4
|
#
|
5
5
|
class OM::XML::Term
|
6
|
-
|
6
|
+
|
7
7
|
# Term::Builder Class Definition
|
8
8
|
#
|
9
9
|
# @example
|
10
|
-
# tb2 = OM::XML::Term::Builder.new("my_term_name").path("fooPath").attributes({:lang=>"foo"}).index_as([:searchable, :facetable]).required(true).data_type(:text)
|
10
|
+
# tb2 = OM::XML::Term::Builder.new("my_term_name").path("fooPath").attributes({:lang=>"foo"}).index_as([:searchable, :facetable]).required(true).data_type(:text)
|
11
|
+
#
|
11
12
|
#
|
12
|
-
#
|
13
13
|
#
|
14
|
-
# When coding against Builders, remember that they rely on MethodMissing,
|
15
|
-
# so any time you call a method on the Builder that it doesn't explicitly recognize,
|
14
|
+
# When coding against Builders, remember that they rely on MethodMissing,
|
15
|
+
# so any time you call a method on the Builder that it doesn't explicitly recognize,
|
16
16
|
# the Builder will add your method & arguments to the it's settings and return itself.
|
17
17
|
class Builder
|
18
18
|
attr_accessor :name, :settings, :children, :terminology_builder
|
19
|
-
|
19
|
+
|
20
20
|
def initialize(name, terminology_builder=nil)
|
21
21
|
@name = name.to_sym
|
22
22
|
@terminology_builder = terminology_builder
|
23
23
|
@settings = {:required=>false, :data_type=>:string}
|
24
24
|
@children = {}
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def add_child(child)
|
28
28
|
@children[child.name] = child
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def retrieve_child(child_name)
|
32
32
|
child = @children.fetch(child_name, nil)
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def lookup_refs(nodes_visited=[])
|
36
36
|
result = []
|
37
37
|
if @settings[:ref]
|
@@ -40,7 +40,7 @@ class OM::XML::Term
|
|
40
40
|
raise "Cannot perform lookup_ref for the #{self.name} builder. It doesn't have a reference to any terminology builder"
|
41
41
|
end
|
42
42
|
target = self.terminology_builder.retrieve_term_builder(*@settings[:ref])
|
43
|
-
|
43
|
+
|
44
44
|
# Fail on circular references and return an intelligible error message
|
45
45
|
if nodes_visited.include?(target)
|
46
46
|
nodes_visited << self
|
@@ -59,12 +59,12 @@ class OM::XML::Term
|
|
59
59
|
end
|
60
60
|
return result
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
# If a :ref value has been set, looks up the target of that ref and merges the target's settings & children with the current builder's settings & children
|
64
64
|
# operates recursively, so it is possible to apply refs that in turn refer to other nodes.
|
65
65
|
def resolve_refs!
|
66
66
|
name_of_last_ref = nil
|
67
|
-
lookup_refs.each_with_index do |ref,z|
|
67
|
+
lookup_refs.each_with_index do |ref,z|
|
68
68
|
@settings = two_layer_merge(@settings, ref.settings)
|
69
69
|
@children.merge!(ref.children)
|
70
70
|
name_of_last_ref = ref.name
|
@@ -75,22 +75,22 @@ class OM::XML::Term
|
|
75
75
|
@settings.delete :ref
|
76
76
|
return self
|
77
77
|
end
|
78
|
-
|
78
|
+
|
79
79
|
# Returns a new Hash that merges +downstream_hash+ with +upstream_hash+
|
80
|
-
# similar to calling +upstream_hash+.merge(+downstream_hash+) only it also merges
|
80
|
+
# similar to calling +upstream_hash+.merge(+downstream_hash+) only it also merges
|
81
81
|
# any internal values that are themselves Hashes.
|
82
82
|
def two_layer_merge(downstream_hash, upstream_hash)
|
83
83
|
up = upstream_hash.dup
|
84
84
|
dn = downstream_hash.dup
|
85
85
|
up.each_pair do |setting_name, value|
|
86
|
-
if value.kind_of?(Hash) && downstream_hash.has_key?(setting_name)
|
86
|
+
if value.kind_of?(Hash) && downstream_hash.has_key?(setting_name)
|
87
87
|
dn[setting_name] = value.merge(downstream_hash[setting_name])
|
88
88
|
up.delete(setting_name)
|
89
89
|
end
|
90
90
|
end
|
91
91
|
return up.merge(dn)
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
# Builds a new OM::XML::Term based on the Builder object's current settings
|
95
95
|
# If no path has been provided, uses the Builder object's name as the term's path
|
96
96
|
# Recursively builds any children, appending the results as children of the Term that's being built.
|
@@ -100,9 +100,9 @@ class OM::XML::Term
|
|
100
100
|
if term.self.settings.has_key?(:proxy)
|
101
101
|
term = OM::XML::NamedTermProxy.new(self.name, self.settings[:proxy], terminology, self.settings)
|
102
102
|
else
|
103
|
-
term = OM::XML::Term.new(self.name)
|
104
|
-
|
105
|
-
self.settings.each do |name, values|
|
103
|
+
term = OM::XML::Term.new(self.name, {}, terminology)
|
104
|
+
|
105
|
+
self.settings.each do |name, values|
|
106
106
|
if term.respond_to?(name.to_s+"=")
|
107
107
|
term.instance_variable_set("@#{name}", values)
|
108
108
|
end
|
@@ -112,12 +112,12 @@ class OM::XML::Term
|
|
112
112
|
end
|
113
113
|
term.generate_xpath_queries!
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
return term
|
117
117
|
end
|
118
|
-
|
118
|
+
|
119
119
|
# Any unknown method calls will add an entry to the settings hash and return the current object
|
120
|
-
def method_missing method, *args, &block
|
120
|
+
def method_missing method, *args, &block
|
121
121
|
if args.length == 1
|
122
122
|
args = args.first
|
123
123
|
end
|
@@ -125,16 +125,16 @@ class OM::XML::Term
|
|
125
125
|
return self
|
126
126
|
end
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
#
|
130
|
-
# Class Definition for Term
|
130
|
+
# Class Definition for Term
|
131
131
|
#
|
132
132
|
|
133
133
|
include OM::TreeNode
|
134
134
|
|
135
135
|
attr_accessor :name, :xpath, :xpath_constrained, :xpath_relative, :path, :index_as, :required, :data_type, :variant_of, :path, :default_content_path, :is_root_term
|
136
136
|
attr_accessor :children, :internal_xml, :terminology
|
137
|
-
|
137
|
+
|
138
138
|
# Any XML attributes that qualify the Term.
|
139
139
|
#
|
140
140
|
# @example Declare a Term that has a given attribute (ie. //title[@xml:lang='eng'])
|
@@ -142,19 +142,19 @@ class OM::XML::Term
|
|
142
142
|
# @example Use nil to point to nodes that do not have a given attribute (ie. //title[not(@xml:lang)])
|
143
143
|
# t.title_without_lang_attribute(:path=>"title", :attributes=>{"xml:lang"=>nil})
|
144
144
|
attr_accessor :attributes
|
145
|
-
|
145
|
+
|
146
146
|
# Namespace Prefix (xmlns) for the Term.
|
147
147
|
#
|
148
148
|
# By default, OM assumes that all terms in a Terminology have the namespace set in the root of the document. If you want to set a different namespace for a Term, pass :namespace_prefix into its initializer (or call .namespace_prefix= on its builder)
|
149
149
|
# If a node has _no_ namespace, you must explicitly set namespace_prefix to nil. Currently you have to do this on _each_ term, you can't set namespace_prefix to nil for an entire Terminology.
|
150
|
-
#
|
150
|
+
#
|
151
151
|
# @example
|
152
152
|
# # For xml like this
|
153
153
|
# <foo xmlns="http://foo.com/schemas/fooschema" xmlns:bar="http://bar.com/schemas/barschema">
|
154
154
|
# <address>1400 Pennsylvania Avenue</address>
|
155
155
|
# <bar:latitude>56</bar:latitude>
|
156
156
|
# </foo>
|
157
|
-
#
|
157
|
+
#
|
158
158
|
# # The Terminology would look like this
|
159
159
|
# OM::XML::Terminology::Builder.new do |t|
|
160
160
|
# t.root(:name=>:foo, :path=>"foo", :xmlns=>"http://foo.com/schemas/fooschema", "xmlns:bar"=>"http://bar.com/schemas/barschema")
|
@@ -163,23 +163,32 @@ class OM::XML::Term
|
|
163
163
|
# end
|
164
164
|
#
|
165
165
|
attr_accessor :namespace_prefix
|
166
|
-
|
167
|
-
|
166
|
+
|
167
|
+
|
168
168
|
# h2. Namespaces
|
169
|
-
# By default, OM assumes
|
170
|
-
# If
|
171
|
-
|
172
|
-
|
169
|
+
# By default, OM assumes you have no namespace defined unless it is explicitly defined at the root of your document.
|
170
|
+
# If you want to specify which namespace a term is using, use:
|
171
|
+
# namspace_prefix => "bar"
|
172
|
+
# This value defaults to nil, in which case if a default namespace is set in the termnology, that namespace will be used.
|
173
|
+
def initialize(name, opts={}, terminology=nil)
|
174
|
+
opts = {:ancestors=>[], :children=>{}}.merge(opts)
|
173
175
|
[:children, :ancestors,:path, :index_as, :required, :type, :variant_of, :path, :attributes, :default_content_path, :namespace_prefix].each do |accessor_name|
|
174
|
-
instance_variable_set("@#{accessor_name}", opts.fetch(accessor_name, nil) )
|
176
|
+
instance_variable_set("@#{accessor_name}", opts.fetch(accessor_name, nil) )
|
177
|
+
end
|
178
|
+
unless terminology.nil?
|
179
|
+
if opts[:namespace_prefix].nil?
|
180
|
+
unless terminology.namespaces["xmlns"].nil?
|
181
|
+
@namespace_prefix = "oxns"
|
182
|
+
end
|
183
|
+
end
|
175
184
|
end
|
176
185
|
@name = name
|
177
186
|
if @path.nil? || @path.empty?
|
178
187
|
@path = name.to_s
|
179
188
|
end
|
180
189
|
end
|
181
|
-
|
182
|
-
def self.from_node(mapper_xml)
|
190
|
+
|
191
|
+
def self.from_node(mapper_xml)
|
183
192
|
name = mapper_xml.attribute("name").text.to_sym
|
184
193
|
attributes = {}
|
185
194
|
mapper_xml.xpath("./attribute").each do |a|
|
@@ -189,19 +198,19 @@ class OM::XML::Term
|
|
189
198
|
[:index_as, :required, :type, :variant_of, :path, :default_content_path, :namespace_prefix].each do |accessor_name|
|
190
199
|
attribute = mapper_xml.attribute(accessor_name.to_s)
|
191
200
|
unless attribute.nil?
|
192
|
-
new_mapper.instance_variable_set("@#{accessor_name}", attribute.text )
|
193
|
-
end
|
201
|
+
new_mapper.instance_variable_set("@#{accessor_name}", attribute.text )
|
202
|
+
end
|
194
203
|
end
|
195
204
|
new_mapper.internal_xml = mapper_xml
|
196
|
-
|
205
|
+
|
197
206
|
mapper_xml.xpath("./mapper").each do |child_node|
|
198
207
|
child = self.from_node(child_node)
|
199
208
|
new_mapper.add_child(child)
|
200
209
|
end
|
201
|
-
|
210
|
+
|
202
211
|
return new_mapper
|
203
212
|
end
|
204
|
-
|
213
|
+
|
205
214
|
# crawl down into mapper's children hash to find the desired mapper
|
206
215
|
# ie. @test_mapper.retrieve_mapper(:conference, :role, :text)
|
207
216
|
def retrieve_term(*pointers)
|
@@ -220,19 +229,19 @@ class OM::XML::Term
|
|
220
229
|
end
|
221
230
|
return target
|
222
231
|
end
|
223
|
-
|
232
|
+
|
224
233
|
def is_root_term?
|
225
234
|
@is_root_term == true
|
226
235
|
end
|
227
|
-
|
236
|
+
|
228
237
|
def xpath_absolute
|
229
238
|
@xpath
|
230
239
|
end
|
231
|
-
|
240
|
+
|
232
241
|
# +term_pointers+ reference to the property you want to generate a builder template for
|
233
242
|
# @opts
|
234
243
|
def xml_builder_template(extra_opts = {})
|
235
|
-
extra_attributes = extra_opts.fetch(:attributes, {})
|
244
|
+
extra_attributes = extra_opts.fetch(:attributes, {})
|
236
245
|
|
237
246
|
node_options = []
|
238
247
|
node_child_template = ""
|
@@ -260,7 +269,7 @@ class OM::XML::Term
|
|
260
269
|
end
|
261
270
|
return template.gsub( /:::(.*?):::/ ) { '#{'+$1+'}' }
|
262
271
|
end
|
263
|
-
|
272
|
+
|
264
273
|
# Generates absolute, relative, and constrained xpaths for the term, setting xpath, xpath_relative, and xpath_constrained accordingly.
|
265
274
|
# Also triggers update_xpath_values! on all child nodes, as their absolute paths rely on those of their parent nodes.
|
266
275
|
def generate_xpath_queries!
|
@@ -270,7 +279,7 @@ class OM::XML::Term
|
|
270
279
|
self.children.each_value {|child| child.generate_xpath_queries! }
|
271
280
|
return self
|
272
281
|
end
|
273
|
-
|
282
|
+
|
274
283
|
# Return an XML representation of the Term
|
275
284
|
# @param [Hash] options, the term will be added to it. If :children=>false, skips rendering child Terms
|
276
285
|
# @param [Nokogiri::XML::Document] (optional) document to insert the term xml into
|
@@ -314,7 +323,7 @@ class OM::XML::Term
|
|
314
323
|
xml.constrained xpath_constrained
|
315
324
|
}
|
316
325
|
if options.fetch(:children, true)
|
317
|
-
xml.children
|
326
|
+
xml.children
|
318
327
|
end
|
319
328
|
}
|
320
329
|
end
|
@@ -324,7 +333,7 @@ class OM::XML::Term
|
|
324
333
|
end
|
325
334
|
return doc
|
326
335
|
end
|
327
|
-
|
336
|
+
|
328
337
|
# private :update_xpath_values
|
329
|
-
|
338
|
+
|
330
339
|
end
|