om 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/History.textile ADDED
@@ -0,0 +1 @@
1
+ Note: OX v.1 Does not handle treating attribute values as the changing "value" of a node
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Matt Zumwalt
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = opinionated-xml
2
+
3
+ A library to help you tame sprawling XML schemas like MODS.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Matt Zumwalt. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "om"
8
+ gem.summary = %Q{OM (Opinionated Metadata): A library to help you tame sprawling XML schemas like MODS.}
9
+ gem.description = %Q{OM (Opinionated Metadata): A library to help you tame sprawling XML schemas like MODS. Wraps Nokogiri documents in objects with miscellaneous helper methods for doing things like retrieve generated xpath queries or look up properties based on a simplified DSL}
10
+ gem.email = "matt.zumwalt@yourmediashelf.com"
11
+ gem.homepage = "http://github.com/mediashelf/om"
12
+ gem.authors = ["Matt Zumwalt"]
13
+
14
+ gem.add_dependency('nokogiri')
15
+ gem.add_dependency('facets')
16
+
17
+ gem.add_development_dependency "rspec", ">= 1.2.9"
18
+ gem.add_development_dependency "mocha", ">= 0.9.8"
19
+ gem.add_development_dependency "ruby-debug"
20
+
21
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
22
+ end
23
+ Jeweler::GemcutterTasks.new
24
+ rescue LoadError
25
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
26
+ end
27
+
28
+ require 'spec/rake/spectask'
29
+ Spec::Rake::SpecTask.new(:spec) do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.spec_files = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
35
+ spec.libs << 'lib' << 'spec'
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :spec => :check_dependencies
41
+
42
+ task :default => :spec
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "om #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,130 @@
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
+ # Returns the configuration info for the selected accessor.
42
+ # Ingores any integers in the array (ie. nodeset indices intended for use in other accessor convenience methods)
43
+ def accessor_info(*args)
44
+ # Ignore any nodeset indexes in the args array
45
+ keys = args.select {|x| !x.kind_of?(Integer) }
46
+ info = @accessors
47
+ keys.each do |k|
48
+ unless keys.index(k) == 0
49
+ info = info.fetch(:children, nil)
50
+ if info.nil?
51
+ debugger
52
+ return nil
53
+ end
54
+ end
55
+
56
+ info = info.fetch(k, nil)
57
+ if info.nil?
58
+ return nil
59
+ end
60
+ end
61
+ return info
62
+ end
63
+
64
+
65
+ def accessor_xpath(*args)
66
+ keys = even_values(args)
67
+ indices = odd_values(args)
68
+ xpath = "//"
69
+ keys.each do |k|
70
+ key_index = keys.index(k)
71
+ accessor_info = accessor_info(*keys[0..key_index])
72
+ relative_path = accessor_info[:relative_xpath]
73
+
74
+ if relative_path.kind_of?(Hash)
75
+ if relative_path.has_key?(:attribute)
76
+ relative_path = "@"+relative_path[:attribute]
77
+ end
78
+ else
79
+
80
+ if indices[key_index]
81
+ add_position_predicate!(relative_path, indices[key_index])
82
+ end
83
+
84
+ if accessor_info.has_key?(:default_content_path)
85
+ relative_path << "/"+accessor_info[:default_content_path]
86
+ end
87
+
88
+ end
89
+ if key_index > 0
90
+ relative_path.insert(0, "/")
91
+ end
92
+ xpath << relative_path
93
+ end
94
+
95
+ return xpath
96
+ end
97
+
98
+ def add_position_predicate!(xpath_query, array_index_value)
99
+ position_function = "position()=#{array_index_value + 1}"
100
+
101
+ if xpath_query.include?("]")
102
+ xpath_query.insert(xpath_query.rindex("]"), " and #{position_function}")
103
+ else
104
+ xpath_query << "[#{position_function}]"
105
+ end
106
+ end
107
+
108
+ def odd_values(array)
109
+ array.values_at(* array.each_index.select {|i| i.odd?})
110
+ end
111
+ def even_values(array)
112
+ array.values_at(* array.each_index.select {|i| i.even?})
113
+ end
114
+ end
115
+
116
+ # Instance Methods -- These methods will be available on instances of OM classes (ie. the actual xml documents)
117
+
118
+ def self.included(klass)
119
+ klass.extend(ClassMethods)
120
+ end
121
+
122
+ # *args Variable length array of values in format [:accessor_name, index, :accessor_name ...]
123
+ # example: [:person, 1, :first_name]
124
+ # Currently, indexes must be integers.
125
+ def retrieve(*args)
126
+ xpath = self.class.accessor_xpath(*args)
127
+ ng_xml.xpath(xpath, "oxns"=>"http://www.loc.gov/mods/v3")
128
+ end
129
+
130
+ end
@@ -0,0 +1,32 @@
1
+ module OM::XML::Container
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
+ # @xml Sting, File or Nokogiri::XML::Node
10
+ # @tmpl ActiveFedora::MetadataDatastream
11
+ def from_xml(xml, tmpl=self.new) # :nodoc:
12
+ if xml.kind_of? Nokogiri::XML::Node
13
+ tmpl.ng_xml = xml
14
+ else
15
+ tmpl.ng_xml = Nokogiri::XML::Document.parse(xml)
16
+ end
17
+ return tmpl
18
+ end
19
+
20
+ end
21
+
22
+ # Instance Methods -- These methods will be available on instances of classes that include this module
23
+
24
+ def self.included(klass)
25
+ klass.extend(ClassMethods)
26
+ end
27
+
28
+ def to_xml
29
+ ng_xml.to_xml
30
+ end
31
+
32
+ end
@@ -0,0 +1,380 @@
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, path, 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[: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
+ path_array = Array( path )
137
+ template = ""
138
+ template << "//" unless opts[:relative]
139
+ template << "#{prefix}:"
140
+ template << delimited_list(path_array, "/#{prefix}:")
141
+
142
+ predicates = []
143
+ default_content_path = property_info.has_key?(:default_content_path) ? property_info[:default_content_path] : ""
144
+ subelement_path_parts = []
145
+
146
+ # Skip everything if a template was provided
147
+ if opts.has_key?(:template)
148
+ template = eval('"' + opts[:template] + '"')
149
+ else
150
+ # Apply variations
151
+ if opts.has_key?(:variations)
152
+ if opts[:variations].has_key?(:attributes)
153
+ opts[:variations][:attributes].each_pair do |attr_name, attr_value|
154
+ predicates << "@#{attr_name}=\"#{attr_value}\""
155
+ end
156
+ end
157
+ if opts[:variations].has_key?(:subelement_path)
158
+ if opts[:variations][:subelement_path].instance_of?(Array)
159
+ opts[:variations][:subelement_path].each do |se_path|
160
+ subelement_path_parts << "#{prefix}:#{se_path}"
161
+ end
162
+ else
163
+ subelement_path_parts << "#{prefix}:#{opts[:variations][:subelement_path]}"
164
+ end
165
+ end
166
+ end
167
+
168
+ # Apply constraints
169
+ if opts.has_key?(:constraints)
170
+ arguments_for_contains_function = []
171
+ if opts[:constraints] == :default
172
+ if property_info.has_key?(:default_content_path)
173
+ default_content_path = property_info[:default_content_path]
174
+ arguments_for_contains_function << "#{prefix}:#{default_content_path}"
175
+ end
176
+ elsif opts[:constraints].has_key?(:path)
177
+ constraint_predicates = []
178
+ if opts.has_key?(:subelement_of)
179
+ constraint_path = "#{prefix}:#{opts[:constraints][:path]}"
180
+ if opts[:constraints].has_key?(:default_content_path)
181
+ constraint_path << "/#{prefix}:#{opts[:constraints][:default_content_path]}"
182
+ end
183
+ else
184
+ constraint_path = "#{prefix}:#{opts[:constraints][:path]}"
185
+ end
186
+ arguments_for_contains_function << constraint_path
187
+
188
+ if opts[:constraints].has_key?(:attributes) && opts[:constraints][:attributes].kind_of?(Hash)
189
+ opts[:constraints][:attributes].each_pair do |attr_name, attr_value|
190
+ constraint_predicates << "@#{attr_name}=\"#{attr_value}\""
191
+ end
192
+ end
193
+
194
+ unless constraint_predicates.empty?
195
+ arguments_for_contains_function.last << "[#{delimited_list(constraint_predicates)}]"
196
+ end
197
+
198
+ end
199
+ arguments_for_contains_function << "\":::constraint_value:::\""
200
+
201
+ predicates << "contains(#{delimited_list(arguments_for_contains_function)})"
202
+
203
+ end
204
+
205
+ unless predicates.empty?
206
+ template << "["
207
+ template << delimited_list(predicates, " and ")
208
+ template << "]"
209
+ end
210
+
211
+ unless subelement_path_parts.empty?
212
+ subelement_path_parts.each {|path_part| template << "/#{path_part}"}
213
+ end
214
+ end
215
+
216
+ # result = eval( '"' + template + '"' )
217
+ return template.gsub( /:::(.*?):::/ ) { '#{'+$1+'}' }
218
+ end
219
+
220
+ #
221
+ # Convenience Methods for Retrieving Generated Info
222
+ #
223
+
224
+ def xpath_query_for( property_ref, query_opts={}, opts={} )
225
+
226
+ if property_ref.instance_of?(String)
227
+ xpath_query = property_ref
228
+ else
229
+ property_info = property_info_for( property_ref )
230
+
231
+ if !property_info.nil?
232
+ if query_opts.kind_of?(String)
233
+ constraint_value = query_opts
234
+ xpath_template = property_info[:xpath_constrained]
235
+ xpath_query = eval( '"' + xpath_template + '"' )
236
+ elsif query_opts.kind_of?(Hash) && !query_opts.empty?
237
+ key_value_pair = query_opts.first
238
+ constraint_value = key_value_pair.last
239
+ xpath_template = property_info[:convenience_methods][key_value_pair.first][:xpath_constrained]
240
+ xpath_query = eval( '"' + xpath_template + '"' )
241
+ else
242
+ xpath_query = property_info[:xpath]
243
+ end
244
+ else
245
+ xpath_query = nil
246
+ end
247
+ end
248
+ return xpath_query
249
+ end
250
+
251
+ def property_info_for(property_ref)
252
+ if property_ref.instance_of?(Array) && property_ref.length == 1
253
+ property_ref = property_ref[0]
254
+ end
255
+ if property_ref.instance_of?(Symbol)
256
+ property_info = properties[property_ref]
257
+ elsif property_ref.kind_of?(Array)
258
+ prop_ref = property_ref[0]
259
+ cm_name = property_ref[1]
260
+ if properties.has_key?(prop_ref)
261
+ property_info = properties[prop_ref][:convenience_methods][cm_name]
262
+ end
263
+ else
264
+ property_info = nil
265
+ end
266
+ return property_info
267
+ end
268
+
269
+
270
+ def delimited_list( values_array, delimiter=", ")
271
+ result = values_array.collect{|a| a + delimiter}.to_s.chomp(delimiter)
272
+ end
273
+
274
+
275
+ #
276
+ # Builder Support
277
+ #
278
+
279
+ def builder_template(property_ref, opts={})
280
+ property_info = property_info_for(property_ref)
281
+
282
+ prop_info = property_info.merge(opts)
283
+
284
+ if prop_info.nil?
285
+ return nil
286
+ else
287
+ node_options = []
288
+ node_child_template = ""
289
+ if prop_info.has_key?(:default_content_path)
290
+ node_child_options = ["\':::builder_new_value:::\'"]
291
+ node_child_template = " { xml.#{property_info[:default_content_path]}( #{delimited_list(node_child_options)} ) }"
292
+ else
293
+ node_options = ["\':::builder_new_value:::\'"]
294
+ end
295
+ # if opts.has_key?(:attributes) ...
296
+ # ...
297
+ if prop_info.has_key?(:attributes)
298
+ applicable_attributes( prop_info[:attributes] ).each_pair do |k,v|
299
+ node_options << ":#{k}=>\'#{v}\'"
300
+ end
301
+ end
302
+ template = "xml.#{prop_info[:path]}( #{delimited_list(node_options)} )" + node_child_template
303
+ return template.gsub( /:::(.*?):::/ ) { '#{'+$1+'}' }
304
+ end
305
+ end
306
+
307
+ # @attributes_spec Array or Hash
308
+ # Returns a Hash where all of the values are strings
309
+ # {:type=>"date"} will return {:type=>"date"}
310
+ # {["authority", {:type=>["text","code"]}] will return {:type=>"text"}
311
+ def applicable_attributes(attributes)
312
+
313
+ if attributes.kind_of?(Hash)
314
+ attributes_hash = attributes
315
+ elsif attributes.kind_of?(Array)
316
+ attributes_hash = {}
317
+ attributes.each do |bute|
318
+ if bute.kind_of?(Hash)
319
+ attributes_hash.merge!(bute)
320
+ end
321
+ end
322
+ end
323
+
324
+ applicable_attributes = {}
325
+ attributes_hash.each_pair {|k,v| applicable_attributes[k] = condense_value(v) }
326
+
327
+
328
+ return applicable_attributes
329
+ end
330
+
331
+ def condense_value(value)
332
+ if value.kind_of?(Array)
333
+ return value.first
334
+ else
335
+ return value
336
+ end
337
+ end
338
+
339
+ def logger
340
+ @logger ||= defined?(RAILS_DEFAULT_LOGGER) ? RAILS_DEFAULT_LOGGER : Logger.new(STDOUT)
341
+ end
342
+
343
+ private :applicable_attributes
344
+ end
345
+
346
+ # Instance Methods -- These methods will be available on instances of classes that include this module
347
+
348
+ def self.included(klass)
349
+ klass.extend(ClassMethods)
350
+ end
351
+
352
+ # Applies the property's corresponding xpath query, returning the result Nokogiri::XML::NodeSet
353
+ def lookup( property_ref, query_opts={}, opts={} )
354
+ xpath_query = xpath_query_for( property_ref, query_opts, opts )
355
+
356
+ if xpath_query.nil?
357
+ result = []
358
+ else
359
+ result = ng_xml.xpath(xpath_query, ox_namespaces)
360
+ end
361
+
362
+ return result
363
+ end
364
+
365
+
366
+ # Returns a hash combining the current documents namespaces (provided by nokogiri) and any namespaces that have been set up by your class definiton.
367
+ # Most importantly, this matches the 'oxns' namespace to the namespace you provided in your root property config
368
+ def ox_namespaces
369
+ @ox_namespaces ||= ng_xml.namespaces.merge(self.class.ox_namespaces)
370
+ end
371
+
372
+ def xpath_query_for( property_ref, query_opts={}, opts={} )
373
+ self.class.xpath_query_for( property_ref, query_opts, opts )
374
+ end
375
+
376
+ def property_info_for(property_ref)
377
+ self.class.property_info_for(property_ref)
378
+ end
379
+
380
+ end