om 1.4.4 → 1.5.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.
- 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
|