om 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.textile +14 -0
- data/VERSION +1 -1
- data/lib/om/samples/mods_article.rb +6 -2
- data/lib/om/xml.rb +0 -9
- data/lib/om/xml/node_generator.rb +7 -0
- data/lib/om/xml/term.rb +3 -0
- data/lib/om/xml/term_value_operators.rb +8 -2
- data/lib/om/xml/term_xpath_generator.rb +58 -29
- data/om.gemspec +2 -14
- data/spec/fixtures/mods_articles/hydrangea_article1.xml +3 -0
- data/spec/unit/document_spec.rb +5 -1
- data/spec/unit/term_value_operators_spec.rb +13 -2
- data/spec/unit/term_xpath_generator_spec.rb +11 -1
- data/spec/unit/terminology_spec.rb +6 -1
- data/spec/unit/xml_spec.rb +0 -3
- metadata +3 -15
- data/lib/om/xml/accessors.rb +0 -217
- data/lib/om/xml/generator.rb +0 -26
- data/lib/om/xml/properties.rb +0 -396
- data/lib/om/xml/property_value_operators.rb +0 -160
- data/spec/unit/accessors_spec.rb +0 -216
- data/spec/unit/generator_spec.rb +0 -67
- data/spec/unit/properties_spec.rb +0 -315
- data/spec/unit/property_value_operators_spec.rb +0 -399
@@ -36,11 +36,12 @@ describe "OM::XML::Terminology" do
|
|
36
36
|
t.family_name(:path=>"namePart", :attributes=>{:type=>"family"})
|
37
37
|
t.given_name(:path=>"namePart", :attributes=>{:type=>"given"}, :label=>"first name")
|
38
38
|
t.terms_of_address(:path=>"namePart", :attributes=>{:type=>"termsOfAddress"})
|
39
|
+
t.person_id(:path=>"namePart", :attributes=>{:type=>:none})
|
39
40
|
}
|
40
41
|
# lookup :person, :first_name
|
41
42
|
t.person(:ref=>:name, :attributes=>{:type=>"personal"})
|
42
43
|
t.conference(:ref=>:name, :attributes=>{:type=>"conference"})
|
43
|
-
|
44
|
+
|
44
45
|
t.role {
|
45
46
|
t.text(:path=>"roleTerm",:attributes=>{:type=>"text"})
|
46
47
|
t.code(:path=>"roleTerm",:attributes=>{:type=>"code"})
|
@@ -78,6 +79,7 @@ describe "OM::XML::Terminology" do
|
|
78
79
|
|
79
80
|
@test_full_terminology.retrieve_term(:person).xpath.should == '//oxns:name[@type="personal"]'
|
80
81
|
@test_full_terminology.retrieve_term(:person).xpath_relative.should == 'oxns:name[@type="personal"]'
|
82
|
+
@test_full_terminology.retrieve_term(:person, :person_id).xpath_relative.should == 'oxns:namePart[not(@type)]'
|
81
83
|
end
|
82
84
|
|
83
85
|
it "constructs templates for value-driven searches" do
|
@@ -221,6 +223,9 @@ describe "OM::XML::Terminology" do
|
|
221
223
|
@test_full_terminology.xpath_for(:person, :date).should == '//oxns:name[@type="personal"]/oxns:namePart[@type="date"]'
|
222
224
|
|
223
225
|
@test_full_terminology.xpath_for(:person, :date, "2010").should == '//oxns:name[@type="personal"]/oxns:namePart[@type="date" and contains(., "2010")]'
|
226
|
+
|
227
|
+
@test_full_terminology.xpath_for(:person, :person_id).should == '//oxns:name[@type="personal"]/oxns:namePart[not(@type)]'
|
228
|
+
|
224
229
|
end
|
225
230
|
|
226
231
|
it "should support including root terms in term pointer" do
|
data/spec/unit/xml_spec.rb
CHANGED
@@ -12,10 +12,7 @@ describe "OM::XML::Container" do
|
|
12
12
|
|
13
13
|
it "should automatically include the other modules" do
|
14
14
|
XMLTest.included_modules.should include(OM::XML::Container)
|
15
|
-
XMLTest.included_modules.should include(OM::XML::Accessors)
|
16
15
|
XMLTest.included_modules.should include(OM::XML::Validation)
|
17
|
-
XMLTest.included_modules.should include(OM::XML::Properties)
|
18
|
-
XMLTest.included_modules.should include(OM::XML::PropertyValueOperators)
|
19
16
|
end
|
20
17
|
|
21
18
|
describe "#sanitize_pointer" do
|
metadata
CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 1.0.2
|
10
|
+
version: 1.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matt Zumwalt
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-03-05 00:00:00 -06:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -208,14 +208,10 @@ files:
|
|
208
208
|
- lib/om/samples/mods_article.rb
|
209
209
|
- lib/om/tree_node.rb
|
210
210
|
- lib/om/xml.rb
|
211
|
-
- lib/om/xml/accessors.rb
|
212
211
|
- lib/om/xml/container.rb
|
213
212
|
- lib/om/xml/document.rb
|
214
|
-
- lib/om/xml/generator.rb
|
215
213
|
- lib/om/xml/named_term_proxy.rb
|
216
214
|
- lib/om/xml/node_generator.rb
|
217
|
-
- lib/om/xml/properties.rb
|
218
|
-
- lib/om/xml/property_value_operators.rb
|
219
215
|
- lib/om/xml/term.rb
|
220
216
|
- lib/om/xml/term_value_operators.rb
|
221
217
|
- lib/om/xml/term_xpath_generator.rb
|
@@ -231,15 +227,11 @@ files:
|
|
231
227
|
- spec/integration/rights_metadata_integration_example_spec.rb
|
232
228
|
- spec/spec.opts
|
233
229
|
- spec/spec_helper.rb
|
234
|
-
- spec/unit/accessors_spec.rb
|
235
230
|
- spec/unit/container_spec.rb
|
236
231
|
- spec/unit/document_spec.rb
|
237
|
-
- spec/unit/generator_spec.rb
|
238
232
|
- spec/unit/named_term_proxy_spec.rb
|
239
233
|
- spec/unit/node_generator_spec.rb
|
240
234
|
- spec/unit/om_spec.rb
|
241
|
-
- spec/unit/properties_spec.rb
|
242
|
-
- spec/unit/property_value_operators_spec.rb
|
243
235
|
- spec/unit/term_builder_spec.rb
|
244
236
|
- spec/unit/term_spec.rb
|
245
237
|
- spec/unit/term_value_operators_spec.rb
|
@@ -285,15 +277,11 @@ summary: "OM (Opinionated Metadata): A library to help you tame sprawling XML sc
|
|
285
277
|
test_files:
|
286
278
|
- spec/integration/rights_metadata_integration_example_spec.rb
|
287
279
|
- spec/spec_helper.rb
|
288
|
-
- spec/unit/accessors_spec.rb
|
289
280
|
- spec/unit/container_spec.rb
|
290
281
|
- spec/unit/document_spec.rb
|
291
|
-
- spec/unit/generator_spec.rb
|
292
282
|
- spec/unit/named_term_proxy_spec.rb
|
293
283
|
- spec/unit/node_generator_spec.rb
|
294
284
|
- spec/unit/om_spec.rb
|
295
|
-
- spec/unit/properties_spec.rb
|
296
|
-
- spec/unit/property_value_operators_spec.rb
|
297
285
|
- spec/unit/term_builder_spec.rb
|
298
286
|
- spec/unit/term_spec.rb
|
299
287
|
- spec/unit/term_value_operators_spec.rb
|
data/lib/om/xml/accessors.rb
DELETED
@@ -1,217 +0,0 @@
|
|
1
|
-
module OM::XML::Accessors
|
2
|
-
|
3
|
-
module ClassMethods
|
4
|
-
attr_accessor :accessors
|
5
|
-
|
6
|
-
def accessor(accessor_name, opts={})
|
7
|
-
@accessors ||= {}
|
8
|
-
insert_accessor(accessor_name, opts)
|
9
|
-
end
|
10
|
-
|
11
|
-
def insert_accessor(accessor_name, accessor_opts, parent_names=[])
|
12
|
-
unless accessor_opts.has_key?(:relative_xpath)
|
13
|
-
accessor_opts[:relative_xpath] = "oxns:#{accessor_name}"
|
14
|
-
end
|
15
|
-
|
16
|
-
destination = @accessors
|
17
|
-
parent_names.each do |parent_name|
|
18
|
-
destination = destination[parent_name][:children]
|
19
|
-
end
|
20
|
-
|
21
|
-
destination[accessor_name] = accessor_opts
|
22
|
-
|
23
|
-
# Recursively call insert_accessor for any children
|
24
|
-
if accessor_opts.has_key?(:children)
|
25
|
-
children_array = accessor_opts[:children].dup
|
26
|
-
accessor_opts[:children] = {}
|
27
|
-
children_array.each do |child|
|
28
|
-
if child.kind_of?(Hash)
|
29
|
-
child_name = child.keys.first
|
30
|
-
child_opts = child.values.first
|
31
|
-
else
|
32
|
-
child_name = child
|
33
|
-
child_opts = {}
|
34
|
-
end
|
35
|
-
insert_accessor(child_name, child_opts, parent_names+[accessor_name] )
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
# Generates accessors from the object's @properties hash.
|
42
|
-
# If no properties have been declared, it doesn't do anything.
|
43
|
-
def generate_accessors_from_properties
|
44
|
-
if self.properties.nil? || self.properties.empty?
|
45
|
-
return nil
|
46
|
-
end
|
47
|
-
@accessors ||= {}
|
48
|
-
# Skip the :unresolved portion of the properties hash
|
49
|
-
accessorizables = self.properties.dup
|
50
|
-
accessorizables.delete(:unresolved)
|
51
|
-
accessorizables.each_pair do |property_ref, property_info|
|
52
|
-
insert_accessor_from_property(property_ref, property_info)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Recurses through a property's info and convenience methods, adding accessors as necessary
|
57
|
-
def insert_accessor_from_property(property_ref, property_info, parent_names=[])
|
58
|
-
insert_accessor(property_ref,{:relative_xpath => property_info[:xpath_relative], :children=>[]}, parent_names)
|
59
|
-
property_info.fetch(:convenience_methods,{}).each_pair do |cm_name, cm_info|
|
60
|
-
insert_accessor_from_property(cm_name, cm_info, parent_names+[property_ref] )
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Returns the configuration info for the selected accessor.
|
65
|
-
# Ingores any integers in the array (ie. nodeset indices intended for use in other accessor convenience methods)
|
66
|
-
def accessor_info(*pointers)
|
67
|
-
info = @accessors
|
68
|
-
|
69
|
-
# flatten the pointers array, excluding node indices
|
70
|
-
pointers = pointers_to_flat_array(pointers, false)
|
71
|
-
|
72
|
-
pointers.each do |pointer|
|
73
|
-
|
74
|
-
unless pointers.index(pointer) == 0
|
75
|
-
info = info.fetch(:children, nil)
|
76
|
-
if info.nil?
|
77
|
-
return nil
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
info = info.fetch(pointer, nil)
|
82
|
-
if info.nil?
|
83
|
-
return nil
|
84
|
-
end
|
85
|
-
end
|
86
|
-
return info
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
def accessor_xpath(*pointers)
|
91
|
-
if pointers.first.kind_of?(String)
|
92
|
-
return pointers.first
|
93
|
-
end
|
94
|
-
|
95
|
-
keys = []
|
96
|
-
xpath = "//"
|
97
|
-
pointers.each do |pointer|
|
98
|
-
|
99
|
-
if pointer.kind_of?(Hash)
|
100
|
-
k = pointer.keys.first
|
101
|
-
index = pointer[k]
|
102
|
-
else
|
103
|
-
k = pointer
|
104
|
-
index = nil
|
105
|
-
end
|
106
|
-
|
107
|
-
keys << k
|
108
|
-
|
109
|
-
# key_index = keys.index(k)
|
110
|
-
pointer_index = pointers.index(pointer)
|
111
|
-
# accessor_info = accessor_info(*keys[0..key_index])
|
112
|
-
accessor_info = accessor_info(*keys)
|
113
|
-
|
114
|
-
# Return nil if there is no accessor info to work with
|
115
|
-
if accessor_info.nil? then return nil end
|
116
|
-
|
117
|
-
relative_path = accessor_info[:relative_xpath]
|
118
|
-
|
119
|
-
if relative_path.kind_of?(Hash)
|
120
|
-
if relative_path.has_key?(:attribute)
|
121
|
-
relative_path = "@"+relative_path[:attribute]
|
122
|
-
end
|
123
|
-
else
|
124
|
-
|
125
|
-
unless index.nil?
|
126
|
-
relative_path = add_node_index_predicate(relative_path, index)
|
127
|
-
end
|
128
|
-
|
129
|
-
if accessor_info.has_key?(:default_content_path)
|
130
|
-
relative_path << "/"+accessor_info[:default_content_path]
|
131
|
-
end
|
132
|
-
|
133
|
-
end
|
134
|
-
if pointer_index > 0
|
135
|
-
relative_path = "/"+relative_path
|
136
|
-
end
|
137
|
-
xpath << relative_path
|
138
|
-
end
|
139
|
-
|
140
|
-
return xpath
|
141
|
-
end
|
142
|
-
|
143
|
-
def accessor_constrained_xpath(pointers, constraint)
|
144
|
-
constraint_function = "contains(., \"#{constraint}\")"
|
145
|
-
xpath = self.accessor_xpath(*pointers)
|
146
|
-
xpath = self.add_predicate(xpath, constraint_function)
|
147
|
-
end
|
148
|
-
|
149
|
-
# Adds xpath xpath node index predicate to the end of your xpath query
|
150
|
-
# Example:
|
151
|
-
# add_node_index_predicate("//oxns:titleInfo",0)
|
152
|
-
# => "//oxns:titleInfo[1]"
|
153
|
-
#
|
154
|
-
# add_node_index_predicate("//oxns:titleInfo[@lang=\"finnish\"]",0)
|
155
|
-
# => "//oxns:titleInfo[@lang=\"finnish\"][1]"
|
156
|
-
def add_node_index_predicate(xpath_query, array_index_value)
|
157
|
-
modified_query = xpath_query.dup
|
158
|
-
modified_query << "[#{array_index_value + 1}]"
|
159
|
-
end
|
160
|
-
|
161
|
-
# Adds xpath:position() method call to the end of your xpath query
|
162
|
-
# Examples:
|
163
|
-
#
|
164
|
-
# add_position_predicate("//oxns:titleInfo",0)
|
165
|
-
# => "//oxns:titleInfo[position()=1]"
|
166
|
-
#
|
167
|
-
# add_position_predicate("//oxns:titleInfo[@lang=\"finnish\"]",0)
|
168
|
-
# => "//oxns:titleInfo[@lang=\"finnish\" and position()=1]"
|
169
|
-
def add_position_predicate(xpath_query, array_index_value)
|
170
|
-
position_function = "position()=#{array_index_value + 1}"
|
171
|
-
self.add_predicate(xpath_query, position_function)
|
172
|
-
end
|
173
|
-
|
174
|
-
def add_predicate(xpath_query, predicate)
|
175
|
-
modified_query = xpath_query.dup
|
176
|
-
# if xpath_query.include?("]")
|
177
|
-
if xpath_query[xpath_query.length-1..xpath_query.length] == "]"
|
178
|
-
modified_query.insert(xpath_query.rindex("]"), " and #{predicate}")
|
179
|
-
else
|
180
|
-
modified_query << "[#{predicate}]"
|
181
|
-
end
|
182
|
-
return modified_query
|
183
|
-
end
|
184
|
-
|
185
|
-
def accessor_generic_name(*pointers)
|
186
|
-
pointers_to_flat_array(pointers, false).join("_")
|
187
|
-
end
|
188
|
-
|
189
|
-
def accessor_hierarchical_name(*pointers)
|
190
|
-
pointers_to_flat_array(pointers, true).join("_")
|
191
|
-
end
|
192
|
-
|
193
|
-
def pointers_to_flat_array(pointers, include_indices=true)
|
194
|
-
OM.pointers_to_flat_array(pointers, include_indices)
|
195
|
-
end
|
196
|
-
|
197
|
-
end
|
198
|
-
|
199
|
-
# Instance Methods -- These methods will be available on instances of OM classes (ie. the actual xml documents)
|
200
|
-
|
201
|
-
def self.included(klass)
|
202
|
-
klass.extend(ClassMethods)
|
203
|
-
end
|
204
|
-
|
205
|
-
# *pointers Variable length array of values in format [:accessor_name, :accessor_name ...] or [{:accessor_name=>index}, :accessor_name ...]
|
206
|
-
# example: [:person, 1, :first_name]
|
207
|
-
# Currently, indexes must be integers.
|
208
|
-
def retrieve(*pointers)
|
209
|
-
xpath = self.class.accessor_xpath(*pointers)
|
210
|
-
if xpath.nil?
|
211
|
-
return nil
|
212
|
-
else
|
213
|
-
return ng_xml.xpath(xpath, ox_namespaces)
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
end
|
data/lib/om/xml/generator.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
module OM::XML::Generator
|
2
|
-
|
3
|
-
attr_accessor :ng_xml
|
4
|
-
|
5
|
-
# Class Methods -- These methods will be available on classes that include this Module
|
6
|
-
|
7
|
-
module ClassMethods
|
8
|
-
|
9
|
-
def generate(property_ref, builder_new_value, opts={})
|
10
|
-
template = builder_template(property_ref, opts)
|
11
|
-
builder_call_body = eval('"' + template + '"')
|
12
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
13
|
-
eval( builder_call_body )
|
14
|
-
end
|
15
|
-
|
16
|
-
return builder.doc
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
# Instance Methods -- These methods will be available on instances of classes that include this module
|
22
|
-
|
23
|
-
def self.included(klass)
|
24
|
-
klass.extend(ClassMethods)
|
25
|
-
end
|
26
|
-
end
|
data/lib/om/xml/properties.rb
DELETED
@@ -1,396 +0,0 @@
|
|
1
|
-
module OM::XML::Properties
|
2
|
-
|
3
|
-
attr_accessor :ng_xml
|
4
|
-
|
5
|
-
# Class Methods -- These methods will be available on classes that include this Module
|
6
|
-
|
7
|
-
module ClassMethods
|
8
|
-
attr_accessor :root_property_ref, :root_config, :ox_namespaces
|
9
|
-
attr_reader :properties
|
10
|
-
|
11
|
-
def root_property( property_ref, path, namespace, opts={})
|
12
|
-
property property_ref, opts.merge({:path=>path, :ref=>property_ref})
|
13
|
-
@root_config = opts.merge({:namespace=>namespace, :path=>path, :ref=>property_ref})
|
14
|
-
@root_property_ref = property_ref
|
15
|
-
@ox_namespaces = {'oxns'=>root_config[:namespace]}
|
16
|
-
@schema_url = opts[:schema]
|
17
|
-
end
|
18
|
-
|
19
|
-
def property( property_ref, opts={})
|
20
|
-
@properties ||= {}
|
21
|
-
@properties[property_ref] = opts.merge({:ref=>property_ref})
|
22
|
-
configure_property @properties[property_ref]
|
23
|
-
configure_paths @properties[property_ref]
|
24
|
-
end
|
25
|
-
|
26
|
-
def configure_property(prop_hash=root_config)
|
27
|
-
if prop_hash.has_key?(:variant_of)
|
28
|
-
properties[prop_hash[:ref]] = properties[prop_hash[:variant_of]].deep_copy.merge(prop_hash)
|
29
|
-
end
|
30
|
-
if !prop_hash.has_key?(:convenience_methods)
|
31
|
-
prop_hash[:convenience_methods] = {}
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Generates appropriate xpath queries for a property described by the given hash
|
36
|
-
# putting the results into the hash under keys like :xpath and :xpath_constrained
|
37
|
-
# This is also done recursively for all subelements and convenience methods described in the hash.
|
38
|
-
def configure_paths(prop_hash=root_config)
|
39
|
-
prop_hash[:path] ||= ""
|
40
|
-
# prop_hash[:path] = Array( prop_hash[:path] )
|
41
|
-
xpath_opts = {}
|
42
|
-
|
43
|
-
if prop_hash.has_key?(:variant_of)
|
44
|
-
xpath_opts[:variations] = prop_hash
|
45
|
-
end
|
46
|
-
|
47
|
-
xpath_constrained_opts = xpath_opts.merge({:constraints=>:default})
|
48
|
-
|
49
|
-
relative_xpath = generate_xpath(prop_hash, xpath_opts.merge(:relative=>true))
|
50
|
-
# prop_hash[:xpath] = generate_xpath(prop_hash, xpath_opts)
|
51
|
-
prop_hash[:xpath] = "//#{relative_xpath}"
|
52
|
-
prop_hash[:xpath_relative] = relative_xpath
|
53
|
-
prop_hash[:xpath_constrained] = generate_xpath(prop_hash, xpath_constrained_opts).gsub('"', '\"')
|
54
|
-
|
55
|
-
prop_hash[:convenience_methods].each_pair do |cm_name, cm_props|
|
56
|
-
cm_xpath_relative_opts = cm_props.merge(:variations => cm_props, :relative=>true)
|
57
|
-
cm_constrained_opts = xpath_opts.merge(:constraints => cm_props)
|
58
|
-
|
59
|
-
cm_relative_xpath = generate_xpath(cm_props, cm_xpath_relative_opts)
|
60
|
-
prop_hash[:convenience_methods][cm_name][:xpath_relative] = cm_relative_xpath
|
61
|
-
# prop_hash[:convenience_methods][cm_name][:xpath] = generate_xpath(cm_xpath_hash, cm_xpath_opts)
|
62
|
-
prop_hash[:convenience_methods][cm_name][:xpath] = prop_hash[:xpath] + "/" + cm_relative_xpath
|
63
|
-
prop_hash[:convenience_methods][cm_name][:xpath_constrained] = generate_xpath(prop_hash, cm_constrained_opts).gsub('"', '\"')
|
64
|
-
end
|
65
|
-
|
66
|
-
if prop_hash.has_key?(:subelements)
|
67
|
-
prop_hash[:subelements].each do |se|
|
68
|
-
configure_subelement_paths(se, prop_hash, xpath_opts)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
if properties.fetch(:unresolved, {}).has_key?(prop_hash[:ref])
|
73
|
-
ref = prop_hash[:ref]
|
74
|
-
properties[:unresolved][ref].each do |parent_prop_hash|
|
75
|
-
logger.debug "Resolving #{ref} subelement for #{parent_prop_hash[:ref]} property"
|
76
|
-
configure_subelement_paths(ref, parent_prop_hash, xpath_opts)
|
77
|
-
end
|
78
|
-
properties[:unresolved].delete(ref)
|
79
|
-
end
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
def configure_subelement_paths(se, parent_prop_hash, parent_xpath_opts)
|
84
|
-
# se_xpath_opts = parent_xpath_opts.merge(:subelement_of => parent_prop_hash[:ref])
|
85
|
-
se_xpath_opts = parent_xpath_opts
|
86
|
-
new_se_props = {}
|
87
|
-
if parent_prop_hash.has_key?(:variant_of)
|
88
|
-
se_xpath_opts[:variations] = parent_prop_hash
|
89
|
-
end
|
90
|
-
|
91
|
-
if se.instance_of?(String)
|
92
|
-
|
93
|
-
new_se_props[:path] = se
|
94
|
-
new_se_props[:xpath_relative] = generate_xpath({:path=>se}, :relative=>true)
|
95
|
-
new_se_props[:xpath] = parent_prop_hash[:xpath] + "/" + new_se_props[:xpath_relative]
|
96
|
-
se_xpath_constrained_opts = se_xpath_opts.merge({:constraints=>{:path=>se}})
|
97
|
-
new_se_props[:xpath_constrained] = generate_xpath(parent_prop_hash, se_xpath_constrained_opts)
|
98
|
-
|
99
|
-
elsif se.instance_of?(Symbol)
|
100
|
-
|
101
|
-
if properties.has_key?(se)
|
102
|
-
|
103
|
-
se_props = properties[se]
|
104
|
-
|
105
|
-
new_se_props = se_props.deep_copy
|
106
|
-
# new_se_props[:path] = se_props[:path]
|
107
|
-
# new_se_props[:xpath_relative] = se_props[:xpath_relative]
|
108
|
-
new_se_props[:xpath] = parent_prop_hash[:xpath] + "/" + new_se_props[:xpath_relative]
|
109
|
-
se_xpath_constrained_opts = parent_xpath_opts.merge(:constraints => se_props, :subelement_of => parent_prop_hash[:ref])
|
110
|
-
new_se_props[:xpath_constrained] = generate_xpath(parent_prop_hash, se_xpath_constrained_opts)
|
111
|
-
|
112
|
-
else
|
113
|
-
properties[:unresolved] ||= {}
|
114
|
-
properties[:unresolved][se] ||= []
|
115
|
-
properties[:unresolved][se] << parent_prop_hash
|
116
|
-
logger.debug("Added #{se.inspect} to unresolved properties with parent #{parent_prop_hash[:ref]}")
|
117
|
-
end
|
118
|
-
else
|
119
|
-
logger.info("failed to generate path for #{se.inspect}")
|
120
|
-
se_xpath = ""
|
121
|
-
se_xpath_constrained = ""
|
122
|
-
end
|
123
|
-
|
124
|
-
if new_se_props.has_key?(:xpath_constrained)
|
125
|
-
new_se_props[:xpath_constrained].gsub!('"', '\"')
|
126
|
-
end
|
127
|
-
|
128
|
-
properties[ parent_prop_hash[:ref] ][:convenience_methods][se.to_sym] = new_se_props
|
129
|
-
|
130
|
-
end
|
131
|
-
|
132
|
-
def generate_xpath( property_info, opts={})
|
133
|
-
prefix = "oxns"
|
134
|
-
property_info[:path] ||= ""
|
135
|
-
path = property_info[:path]
|
136
|
-
unless path.kind_of?(Hash)
|
137
|
-
path = Array( path )
|
138
|
-
end
|
139
|
-
template = ""
|
140
|
-
template << "//" unless opts[:relative]
|
141
|
-
template << "#{prefix}:"
|
142
|
-
|
143
|
-
if path.kind_of?(Hash)
|
144
|
-
if path.has_key?(:attribute)
|
145
|
-
template = "@"+path[:attribute]
|
146
|
-
end
|
147
|
-
else
|
148
|
-
template << delimited_list(path, "/#{prefix}:")
|
149
|
-
end
|
150
|
-
|
151
|
-
predicates = []
|
152
|
-
default_content_path = property_info.has_key?(:default_content_path) ? property_info[:default_content_path] : ""
|
153
|
-
subelement_path_parts = []
|
154
|
-
|
155
|
-
# Skip everything if a template was provided
|
156
|
-
if opts.has_key?(:template)
|
157
|
-
template = eval('"' + opts[:template] + '"')
|
158
|
-
else
|
159
|
-
# Apply variations
|
160
|
-
if opts.has_key?(:variations)
|
161
|
-
if opts[:variations].has_key?(:attributes)
|
162
|
-
opts[:variations][:attributes].each_pair do |attr_name, attr_value|
|
163
|
-
predicates << "@#{attr_name}=\"#{attr_value}\""
|
164
|
-
end
|
165
|
-
end
|
166
|
-
if opts[:variations].has_key?(:subelement_path)
|
167
|
-
if opts[:variations][:subelement_path].instance_of?(Array)
|
168
|
-
opts[:variations][:subelement_path].each do |se_path|
|
169
|
-
subelement_path_parts << "#{prefix}:#{se_path}"
|
170
|
-
end
|
171
|
-
else
|
172
|
-
subelement_path_parts << "#{prefix}:#{opts[:variations][:subelement_path]}"
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
# Apply constraints
|
178
|
-
if opts.has_key?(:constraints)
|
179
|
-
arguments_for_contains_function = []
|
180
|
-
if opts[:constraints] == :default
|
181
|
-
if property_info.has_key?(:default_content_path)
|
182
|
-
default_content_path = property_info[:default_content_path]
|
183
|
-
arguments_for_contains_function << "#{prefix}:#{default_content_path}"
|
184
|
-
end
|
185
|
-
elsif opts[:constraints].has_key?(:path)
|
186
|
-
constraint_predicates = []
|
187
|
-
|
188
|
-
constraints_path_arg = opts[:constraints][:path]
|
189
|
-
if constraints_path_arg.kind_of?(Hash)
|
190
|
-
if constraints_path_arg.has_key?(:attribute)
|
191
|
-
constraint_path = "@"+constraints_path_arg[:attribute]
|
192
|
-
end
|
193
|
-
else
|
194
|
-
constraint_path = "#{prefix}:#{constraints_path_arg}"
|
195
|
-
end
|
196
|
-
if opts.has_key?(:subelement_of) && opts[:constraints].has_key?(:default_content_path)
|
197
|
-
# constraint_path = "#{prefix}:#{opts[:constraints][:path]}"
|
198
|
-
constraint_path << "/#{prefix}:#{opts[:constraints][:default_content_path]}"
|
199
|
-
end
|
200
|
-
arguments_for_contains_function << constraint_path
|
201
|
-
|
202
|
-
if opts[:constraints].has_key?(:attributes) && opts[:constraints][:attributes].kind_of?(Hash)
|
203
|
-
opts[:constraints][:attributes].each_pair do |attr_name, attr_value|
|
204
|
-
constraint_predicates << "@#{attr_name}=\"#{attr_value}\""
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
unless constraint_predicates.empty?
|
209
|
-
arguments_for_contains_function.last << "[#{delimited_list(constraint_predicates)}]"
|
210
|
-
end
|
211
|
-
|
212
|
-
end
|
213
|
-
arguments_for_contains_function << "\":::constraint_value:::\""
|
214
|
-
|
215
|
-
predicates << "contains(#{delimited_list(arguments_for_contains_function)})"
|
216
|
-
|
217
|
-
end
|
218
|
-
|
219
|
-
unless predicates.empty?
|
220
|
-
template << "["
|
221
|
-
template << delimited_list(predicates, " and ")
|
222
|
-
template << "]"
|
223
|
-
end
|
224
|
-
|
225
|
-
unless subelement_path_parts.empty?
|
226
|
-
subelement_path_parts.each {|path_part| template << "/#{path_part}"}
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
# result = eval( '"' + template + '"' )
|
231
|
-
return template.gsub( /:::(.*?):::/ ) { '#{'+$1+'}' }
|
232
|
-
end
|
233
|
-
|
234
|
-
#
|
235
|
-
# Convenience Methods for Retrieving Generated Info
|
236
|
-
#
|
237
|
-
|
238
|
-
def xpath_query_for( property_ref, query_opts={}, opts={} )
|
239
|
-
|
240
|
-
if property_ref.instance_of?(String)
|
241
|
-
xpath_query = property_ref
|
242
|
-
else
|
243
|
-
property_info = property_info_for( property_ref )
|
244
|
-
|
245
|
-
if !property_info.nil?
|
246
|
-
if query_opts.kind_of?(String)
|
247
|
-
constraint_value = query_opts
|
248
|
-
xpath_template = property_info[:xpath_constrained]
|
249
|
-
xpath_query = eval( '"' + xpath_template + '"' )
|
250
|
-
elsif query_opts.kind_of?(Hash) && !query_opts.empty?
|
251
|
-
key_value_pair = query_opts.first
|
252
|
-
constraint_value = key_value_pair.last
|
253
|
-
xpath_template = property_info[:convenience_methods][key_value_pair.first][:xpath_constrained]
|
254
|
-
xpath_query = eval( '"' + xpath_template + '"' )
|
255
|
-
else
|
256
|
-
xpath_query = property_info[:xpath]
|
257
|
-
end
|
258
|
-
else
|
259
|
-
xpath_query = nil
|
260
|
-
end
|
261
|
-
end
|
262
|
-
return xpath_query
|
263
|
-
end
|
264
|
-
|
265
|
-
def property_info_for(property_ref)
|
266
|
-
if property_ref.instance_of?(Array) && property_ref.length < 2
|
267
|
-
property_ref = property_ref[0]
|
268
|
-
end
|
269
|
-
if property_ref.instance_of?(Symbol)
|
270
|
-
property_info = properties[property_ref]
|
271
|
-
elsif property_ref.kind_of?(Array)
|
272
|
-
prop_ref = property_ref[property_ref.length-2]
|
273
|
-
cm_name = property_ref[property_ref.length-1]
|
274
|
-
if properties.has_key?(prop_ref)
|
275
|
-
property_info = properties[prop_ref][:convenience_methods][cm_name]
|
276
|
-
end
|
277
|
-
else
|
278
|
-
property_info = nil
|
279
|
-
end
|
280
|
-
return property_info
|
281
|
-
end
|
282
|
-
|
283
|
-
|
284
|
-
def delimited_list( values_array, delimiter=", ")
|
285
|
-
result = values_array.collect{|a| a + delimiter}.to_s.chomp(delimiter)
|
286
|
-
end
|
287
|
-
|
288
|
-
|
289
|
-
#
|
290
|
-
# Builder Support
|
291
|
-
#
|
292
|
-
|
293
|
-
# @property_ref reference to the property you want to generate a builder template for
|
294
|
-
# @opts
|
295
|
-
def builder_template(property_ref, opts={})
|
296
|
-
property_info = property_info_for(property_ref)
|
297
|
-
|
298
|
-
prop_info = property_info.merge(opts)
|
299
|
-
|
300
|
-
if prop_info.nil?
|
301
|
-
return nil
|
302
|
-
else
|
303
|
-
node_options = []
|
304
|
-
node_child_template = ""
|
305
|
-
if prop_info.has_key?(:default_content_path)
|
306
|
-
node_child_options = ["\':::builder_new_value:::\'"]
|
307
|
-
node_child_template = " { xml.#{property_info[:default_content_path]}( #{delimited_list(node_child_options)} ) }"
|
308
|
-
else
|
309
|
-
node_options = ["\':::builder_new_value:::\'"]
|
310
|
-
end
|
311
|
-
# if opts.has_key?(:attributes) ...
|
312
|
-
# ...
|
313
|
-
if prop_info.has_key?(:attributes)
|
314
|
-
applicable_attributes( prop_info[:attributes] ).each_pair do |k,v|
|
315
|
-
node_options << ":#{k}=>\'#{v}\'"
|
316
|
-
end
|
317
|
-
end
|
318
|
-
template = "xml.#{prop_info[:path]}( #{delimited_list(node_options)} )" + node_child_template
|
319
|
-
return template.gsub( /:::(.*?):::/ ) { '#{'+$1+'}' }
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
# @attributes_spec Array or Hash
|
324
|
-
# Returns a Hash where all of the values are strings
|
325
|
-
# {:type=>"date"} will return {:type=>"date"}
|
326
|
-
# {["authority", {:type=>["text","code"]}] will return {:type=>"text"}
|
327
|
-
def applicable_attributes(attributes)
|
328
|
-
|
329
|
-
if attributes.kind_of?(Hash)
|
330
|
-
attributes_hash = attributes
|
331
|
-
elsif attributes.kind_of?(Array)
|
332
|
-
attributes_hash = {}
|
333
|
-
attributes.each do |bute|
|
334
|
-
if bute.kind_of?(Hash)
|
335
|
-
attributes_hash.merge!(bute)
|
336
|
-
end
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
applicable_attributes = {}
|
341
|
-
attributes_hash.each_pair {|k,v| applicable_attributes[k] = condense_value(v) }
|
342
|
-
|
343
|
-
|
344
|
-
return applicable_attributes
|
345
|
-
end
|
346
|
-
|
347
|
-
def condense_value(value)
|
348
|
-
if value.kind_of?(Array)
|
349
|
-
return value.first
|
350
|
-
else
|
351
|
-
return value
|
352
|
-
end
|
353
|
-
end
|
354
|
-
|
355
|
-
def logger
|
356
|
-
@logger ||= defined?(RAILS_DEFAULT_LOGGER) ? RAILS_DEFAULT_LOGGER : Logger.new(STDOUT)
|
357
|
-
end
|
358
|
-
|
359
|
-
private :applicable_attributes
|
360
|
-
end
|
361
|
-
|
362
|
-
# Instance Methods -- These methods will be available on instances of classes that include this module
|
363
|
-
|
364
|
-
def self.included(klass)
|
365
|
-
klass.extend(ClassMethods)
|
366
|
-
end
|
367
|
-
|
368
|
-
# Applies the property's corresponding xpath query, returning the result Nokogiri::XML::NodeSet
|
369
|
-
def lookup( property_ref, query_opts={}, opts={} )
|
370
|
-
xpath_query = xpath_query_for( property_ref, query_opts, opts )
|
371
|
-
|
372
|
-
if xpath_query.nil?
|
373
|
-
result = []
|
374
|
-
else
|
375
|
-
result = ng_xml.xpath(xpath_query, ox_namespaces)
|
376
|
-
end
|
377
|
-
|
378
|
-
return result
|
379
|
-
end
|
380
|
-
|
381
|
-
|
382
|
-
# Returns a hash combining the current documents namespaces (provided by nokogiri) and any namespaces that have been set up by your class definiton.
|
383
|
-
# Most importantly, this matches the 'oxns' namespace to the namespace you provided in your root property config
|
384
|
-
def ox_namespaces
|
385
|
-
@ox_namespaces ||= ng_xml.namespaces.merge(self.class.ox_namespaces)
|
386
|
-
end
|
387
|
-
|
388
|
-
def xpath_query_for( property_ref, query_opts={}, opts={} )
|
389
|
-
self.class.xpath_query_for( property_ref, query_opts, opts )
|
390
|
-
end
|
391
|
-
|
392
|
-
def property_info_for(property_ref)
|
393
|
-
self.class.property_info_for(property_ref)
|
394
|
-
end
|
395
|
-
|
396
|
-
end
|