om 1.0.2 → 1.1.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/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
|