opinionated-xml 0.0.1

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 = "opinionated-xml"
8
+ gem.summary = %Q{A library to help you tame sprawling XML schemas like MODS.}
9
+ gem.description = %Q{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/opinionated-xml"
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 = "opinionated-xml #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,430 @@
1
+ require "open-uri"
2
+ require "logger"
3
+
4
+ module OX
5
+
6
+ # Class Methods -- These methods will be available on classes that include OX (ie. the OX MODS class)
7
+
8
+ module ClassMethods
9
+
10
+ attr_accessor :root_property_ref, :root_config, :ox_namespaces, :schema_url
11
+ attr_writer :schema_file
12
+ attr_reader :properties
13
+
14
+ def root_property( property_ref, path, namespace, opts={})
15
+ # property property_ref, path, opts.merge({:path=>path, :ref=>property_ref})
16
+ @root_config = opts.merge({:namespace=>namespace, :path=>path, :ref=>property_ref})
17
+ @root_property_ref = property_ref
18
+ @ox_namespaces = {'oxns'=>root_config[:namespace]}
19
+ @schema_url = opts[:schema]
20
+ end
21
+
22
+ def property( property_ref, opts={})
23
+ @properties ||= {}
24
+ @properties[property_ref] = opts.merge({:ref=>property_ref})
25
+ configure_property @properties[property_ref]
26
+ configure_paths @properties[property_ref]
27
+ end
28
+
29
+ def configure_property(prop_hash=root_config)
30
+ if prop_hash.has_key?(:variant_of)
31
+ properties[prop_hash[:ref]] = properties[prop_hash[:variant_of]].deep_copy.merge(prop_hash)
32
+ end
33
+ if !prop_hash.has_key?(:convenience_methods)
34
+ prop_hash[:convenience_methods] = {}
35
+ end
36
+ end
37
+
38
+ # Generates appropriate xpath queries for a property described by the given hash
39
+ # putting the results into the hash under keys like :xpath and :xpath_constrained
40
+ # This is also done recursively for all subelements and convenience methods described in the hash.
41
+ def configure_paths(prop_hash=root_config)
42
+ prop_hash[:path] ||= ""
43
+ # prop_hash[:path] = Array( prop_hash[:path] )
44
+ xpath_opts = {}
45
+
46
+ if prop_hash.has_key?(:variant_of)
47
+ xpath_opts[:variations] = prop_hash
48
+ end
49
+
50
+ xpath_constrained_opts = xpath_opts.merge({:constraints=>:default})
51
+
52
+ relative_xpath = generate_xpath(prop_hash, xpath_opts.merge(:relative=>true))
53
+ # prop_hash[:xpath] = generate_xpath(prop_hash, xpath_opts)
54
+ prop_hash[:xpath] = "//#{relative_xpath}"
55
+ prop_hash[:xpath_relative] = relative_xpath
56
+ prop_hash[:xpath_constrained] = generate_xpath(prop_hash, xpath_constrained_opts).gsub('"', '\"')
57
+
58
+ prop_hash[:convenience_methods].each_pair do |cm_name, cm_props|
59
+ cm_xpath_relative_opts = cm_props.merge(:variations => cm_props, :relative=>true)
60
+ cm_constrained_opts = xpath_opts.merge(:constraints => cm_props)
61
+
62
+ cm_relative_xpath = generate_xpath(cm_props, cm_xpath_relative_opts)
63
+ prop_hash[:convenience_methods][cm_name][:xpath_relative] = cm_relative_xpath
64
+ # prop_hash[:convenience_methods][cm_name][:xpath] = generate_xpath(cm_xpath_hash, cm_xpath_opts)
65
+ prop_hash[:convenience_methods][cm_name][:xpath] = prop_hash[:xpath] + "/" + cm_relative_xpath
66
+ prop_hash[:convenience_methods][cm_name][:xpath_constrained] = generate_xpath(prop_hash, cm_constrained_opts).gsub('"', '\"')
67
+ end
68
+
69
+ if prop_hash.has_key?(:subelements)
70
+ prop_hash[:subelements].each do |se|
71
+ configure_subelement_paths(se, prop_hash, xpath_opts)
72
+ end
73
+ end
74
+
75
+ if properties[:unresolved].has_key?(prop_hash[:ref])
76
+ ref = prop_hash[:ref]
77
+ properties[:unresolved][ref].each do |parent_prop_hash|
78
+ logger.debug "Resolving #{ref} subelement for #{parent_prop_hash[:ref]} property"
79
+ configure_subelement_paths(ref, parent_prop_hash, xpath_opts)
80
+ end
81
+ properties[:unresolved].delete(ref)
82
+ end
83
+
84
+ end
85
+
86
+ def configure_subelement_paths(se, parent_prop_hash, parent_xpath_opts)
87
+ # se_xpath_opts = parent_xpath_opts.merge(:subelement_of => parent_prop_hash[:ref])
88
+ se_xpath_opts = parent_xpath_opts
89
+ new_se_props = {}
90
+ if parent_prop_hash.has_key?(:variant_of)
91
+ se_xpath_opts[:variations] = parent_prop_hash
92
+ end
93
+
94
+ if se.instance_of?(String)
95
+
96
+ new_se_props[:path] = se
97
+ new_se_props[:xpath_relative] = generate_xpath({:path=>se}, :relative=>true)
98
+ new_se_props[:xpath] = parent_prop_hash[:xpath] + "/" + new_se_props[:xpath_relative]
99
+ se_xpath_constrained_opts = se_xpath_opts.merge({:constraints=>{:path=>se}})
100
+ new_se_props[:xpath_constrained] = generate_xpath(parent_prop_hash, se_xpath_constrained_opts)
101
+
102
+ elsif se.instance_of?(Symbol)
103
+
104
+ if properties.has_key?(se)
105
+
106
+ se_props = properties[se]
107
+
108
+ new_se_props = se_props.deep_copy
109
+ # new_se_props[:path] = se_props[:path]
110
+ # new_se_props[:xpath_relative] = se_props[:xpath_relative]
111
+ new_se_props[:xpath] = parent_prop_hash[:xpath] + "/" + new_se_props[:xpath_relative]
112
+ se_xpath_constrained_opts = parent_xpath_opts.merge(:constraints => se_props, :subelement_of => parent_prop_hash[:ref])
113
+ new_se_props[:xpath_constrained] = generate_xpath(parent_prop_hash, se_xpath_constrained_opts)
114
+
115
+ else
116
+ properties[:unresolved] ||= {}
117
+ properties[:unresolved][se] ||= []
118
+ properties[:unresolved][se] << parent_prop_hash
119
+ logger.debug("Added #{se.inspect} to unresolved properties with parent #{parent_prop_hash[:ref]}")
120
+ end
121
+ else
122
+ logger.info("failed to generate path for #{se.inspect}")
123
+ se_xpath = ""
124
+ se_xpath_constrained = ""
125
+ end
126
+
127
+ if new_se_props.has_key?(:xpath_constrained)
128
+ new_se_props[:xpath_constrained].gsub!('"', '\"')
129
+ end
130
+
131
+ properties[ parent_prop_hash[:ref] ][:convenience_methods][se.to_sym] = new_se_props
132
+
133
+ end
134
+
135
+ def generate_xpath( property_info, opts={})
136
+ prefix = "oxns"
137
+ property_info[:path] ||= ""
138
+ path = property_info[:path]
139
+ path_array = Array( path )
140
+ template = ""
141
+ template << "//" unless opts[:relative]
142
+ template << "#{prefix}:"
143
+ template << delimited_list(path_array, "/#{prefix}:")
144
+
145
+ predicates = []
146
+ default_content_path = property_info.has_key?(:default_content_path) ? property_info[:default_content_path] : ""
147
+ subelement_path_parts = []
148
+
149
+ # Skip everything if a template was provided
150
+ if opts.has_key?(:template)
151
+ template = eval('"' + opts[:template] + '"')
152
+ else
153
+ # Apply variations
154
+ if opts.has_key?(:variations)
155
+ if opts[:variations].has_key?(:attributes)
156
+ opts[:variations][:attributes].each_pair do |attr_name, attr_value|
157
+ predicates << "@#{attr_name}=\"#{attr_value}\""
158
+ end
159
+ end
160
+ if opts[:variations].has_key?(:subelement_path)
161
+ if opts[:variations][:subelement_path].instance_of?(Array)
162
+ opts[:variations][:subelement_path].each do |se_path|
163
+ subelement_path_parts << "#{prefix}:#{se_path}"
164
+ end
165
+ else
166
+ subelement_path_parts << "#{prefix}:#{opts[:variations][:subelement_path]}"
167
+ end
168
+ end
169
+ end
170
+
171
+ # Apply constraints
172
+ if opts.has_key?(:constraints)
173
+ arguments_for_contains_function = []
174
+ if opts[:constraints] == :default
175
+ if property_info.has_key?(:default_content_path)
176
+ default_content_path = property_info[:default_content_path]
177
+ arguments_for_contains_function << "#{prefix}:#{default_content_path}"
178
+ end
179
+ elsif opts[:constraints].has_key?(:path)
180
+ constraint_predicates = []
181
+ if opts.has_key?(:subelement_of)
182
+ constraint_path = "#{prefix}:#{opts[:constraints][:path]}"
183
+ if opts[:constraints].has_key?(:default_content_path)
184
+ constraint_path << "/#{prefix}:#{opts[:constraints][:default_content_path]}"
185
+ end
186
+ else
187
+ constraint_path = "#{prefix}:#{opts[:constraints][:path]}"
188
+ end
189
+ arguments_for_contains_function << constraint_path
190
+
191
+ if opts[:constraints].has_key?(:attributes) && opts[:constraints][:attributes].kind_of?(Hash)
192
+ opts[:constraints][:attributes].each_pair do |attr_name, attr_value|
193
+ constraint_predicates << "@#{attr_name}=\"#{attr_value}\""
194
+ end
195
+ end
196
+
197
+ unless constraint_predicates.empty?
198
+ arguments_for_contains_function.last << "[#{delimited_list(constraint_predicates)}]"
199
+ end
200
+
201
+ end
202
+ arguments_for_contains_function << "\":::constraint_value:::\""
203
+
204
+ predicates << "contains(#{delimited_list(arguments_for_contains_function)})"
205
+
206
+ end
207
+
208
+ unless predicates.empty?
209
+ template << "["
210
+ template << delimited_list(predicates, " and ")
211
+ template << "]"
212
+ end
213
+
214
+ unless subelement_path_parts.empty?
215
+ subelement_path_parts.each {|path_part| template << "/#{path_part}"}
216
+ end
217
+ end
218
+
219
+ # result = eval( '"' + template + '"' )
220
+ return template.gsub( /:::(.*?):::/ ) { '#{'+$1+'}' }
221
+ end
222
+
223
+ #
224
+ # Convenience Methods for Retrieving Generated Info
225
+ #
226
+
227
+ def xpath_query_for( property_ref, query_opts={}, opts={} )
228
+
229
+ if property_ref.instance_of?(String)
230
+ xpath_query = property_ref
231
+ else
232
+ property_info = property_info_for( property_ref )
233
+
234
+ if !property_info.nil?
235
+ if query_opts.kind_of?(String)
236
+ constraint_value = query_opts
237
+ xpath_template = property_info[:xpath_constrained]
238
+ xpath_query = eval( '"' + xpath_template + '"' )
239
+ elsif query_opts.kind_of?(Hash) && !query_opts.empty?
240
+ key_value_pair = query_opts.first
241
+ constraint_value = key_value_pair.last
242
+ xpath_template = property_info[:convenience_methods][key_value_pair.first][:xpath_constrained]
243
+ xpath_query = eval( '"' + xpath_template + '"' )
244
+ else
245
+ xpath_query = property_info[:xpath]
246
+ end
247
+ else
248
+ xpath_query = nil
249
+ end
250
+ end
251
+ return xpath_query
252
+ end
253
+
254
+ def property_info_for(property_ref)
255
+ if property_ref.instance_of?(Array) && property_ref.length == 1
256
+ property_ref = property_ref[0]
257
+ end
258
+ if property_ref.instance_of?(Symbol)
259
+ property_info = properties[property_ref]
260
+ elsif property_ref.kind_of?(Array)
261
+ prop_ref = property_ref[0]
262
+ cm_name = property_ref[1]
263
+ if properties.has_key?(prop_ref)
264
+ property_info = properties[prop_ref][:convenience_methods][cm_name]
265
+ end
266
+ else
267
+ property_info = nil
268
+ end
269
+ return property_info
270
+ end
271
+
272
+ ##
273
+ # Validation Support
274
+ ##
275
+
276
+ # Validate the given document against the Schema provided by the root_property for this class
277
+ def validate(doc)
278
+ schema.validate(doc).each do |error|
279
+ puts error.message
280
+ end
281
+ end
282
+
283
+ # Retrieve the Nokogiri Schema for this class
284
+ def schema
285
+ @schema ||= Nokogiri::XML::Schema(schema_file.read)
286
+ end
287
+
288
+ # Retrieve the schema file for this class
289
+ # If the schema file is not already set, it will be loaded from the schema url provided in the root_property configuration for the class
290
+ def schema_file
291
+ @schema_file ||= file_from_url(schema_url)
292
+ end
293
+
294
+ # Retrieve file from a url (used by schema_file method to retrieve schema file from the schema url)
295
+ def file_from_url( url )
296
+ # parsed_url = URI.parse( url )
297
+ #
298
+ # if parsed_url.class != URI::HTTP
299
+ # raise "Invalid URL. Could not parse #{url} as a HTTP url."
300
+ # end
301
+
302
+ begin
303
+ file = open( url )
304
+ return file
305
+ rescue OpenURI::HTTPError => e
306
+ raise "Could not retrieve file from #{url}. Error: #{e}"
307
+ rescue Exception => e
308
+ raise "Could not retrieve file from #{url}. Error: #{e}"
309
+ end
310
+ end
311
+
312
+ def delimited_list( values_array, delimiter=", ")
313
+ result = values_array.collect{|a| a + delimiter}.to_s.chomp(delimiter)
314
+ end
315
+
316
+
317
+ #
318
+ # Builder Support
319
+ #
320
+
321
+ def builder_template(property_ref, opts={})
322
+ property_info = property_info_for(property_ref)
323
+
324
+ prop_info = property_info.merge(opts)
325
+
326
+ if prop_info.nil?
327
+ return nil
328
+ else
329
+ node_options = []
330
+ node_child_template = ""
331
+ if prop_info.has_key?(:default_content_path)
332
+ node_child_options = ["\':::builder_new_value:::\'"]
333
+ node_child_template = " { xml.#{property_info[:default_content_path]}( #{delimited_list(node_child_options)} ) }"
334
+ else
335
+ node_options = ["\':::builder_new_value:::\'"]
336
+ end
337
+ # if opts.has_key?(:attributes) ...
338
+ # ...
339
+ if prop_info.has_key?(:attributes)
340
+ applicable_attributes( prop_info[:attributes] ).each_pair do |k,v|
341
+ node_options << ":#{k}=>\'#{v}\'"
342
+ end
343
+ end
344
+ template = "xml.#{prop_info[:path]}( #{delimited_list(node_options)} )" + node_child_template
345
+ return template.gsub( /:::(.*?):::/ ) { '#{'+$1+'}' }
346
+ end
347
+ end
348
+
349
+ # @attributes_spec Array or Hash
350
+ # Returns a Hash where all of the values are strings
351
+ # {:type=>"date"} will return {:type=>"date"}
352
+ # {["authority", {:type=>["text","code"]}] will return {:type=>"text"}
353
+ def applicable_attributes(attributes)
354
+
355
+ if attributes.kind_of?(Hash)
356
+ attributes_hash = attributes
357
+ elsif attributes.kind_of?(Array)
358
+ attributes_hash = {}
359
+ attributes.each do |bute|
360
+ if bute.kind_of?(Hash)
361
+ attributes_hash.merge!(bute)
362
+ end
363
+ end
364
+ end
365
+
366
+ applicable_attributes = {}
367
+ attributes_hash.each_pair {|k,v| applicable_attributes[k] = condense_value(v) }
368
+
369
+
370
+ return applicable_attributes
371
+ end
372
+
373
+ def condense_value(value)
374
+ if value.kind_of?(Array)
375
+ return value.first
376
+ else
377
+ return value
378
+ end
379
+ end
380
+
381
+ def logger
382
+ @logger ||= defined?(RAILS_DEFAULT_LOGGER) ? RAILS_DEFAULT_LOGGER : Logger.new(STDOUT)
383
+ end
384
+
385
+ private :file_from_url, :applicable_attributes
386
+
387
+
388
+
389
+ end
390
+
391
+ # Instance Methods -- These methods will be available on instances of OX classes (ie. the actual xml documents)
392
+
393
+ def self.included(klass)
394
+ klass.extend(ClassMethods)
395
+ klass.send(:include, OX::PropertyValuesHelper)
396
+ end
397
+
398
+ # Applies the property's corresponding xpath query, returning the result Nokogiri::XML::NodeSet
399
+ def lookup( property_ref, query_opts={}, opts={} )
400
+ xpath_query = xpath_query_for( property_ref, query_opts, opts )
401
+
402
+ if xpath_query.nil?
403
+ result = []
404
+ else
405
+ result = xpath(xpath_query, ox_namespaces)
406
+ end
407
+
408
+ return result
409
+ end
410
+
411
+
412
+ # Returns a hash combining the current documents namespaces (provided by nokogiri) and any namespaces that have been set up by your class definiton.
413
+ # Most importantly, this matches the 'oxns' namespace to the namespace you provided in your root property config
414
+ def ox_namespaces
415
+ @ox_namespaces ||= namespaces.merge(self.class.ox_namespaces)
416
+ end
417
+
418
+ def validate
419
+ self.class.validate(self)
420
+ end
421
+
422
+ def xpath_query_for( property_ref, query_opts={}, opts={} )
423
+ self.class.xpath_query_for( property_ref, query_opts, opts )
424
+ end
425
+
426
+ def property_info_for(property_ref)
427
+ self.class.property_info_for(property_ref)
428
+ end
429
+
430
+ end
@@ -0,0 +1,62 @@
1
+ require "open-uri"
2
+ require "logger"
3
+
4
+ class OX::ParentNodeNotFoundError < RuntimeError; end
5
+ module OX::PropertyValuesHelper
6
+
7
+ def property_values(lookup_args)
8
+ result = []
9
+ lookup(lookup_args).each {|node| result << node.text }
10
+ return result
11
+ end
12
+
13
+ def property_values_append(opts={})
14
+ parent_select = opts[:parent_select]
15
+ parent_index = opts[:parent_index]
16
+ template = opts[:template]
17
+ new_values = opts[:values]
18
+
19
+ # If template is a string, use it as the template, otherwise use it as arguments to builder_template
20
+ unless template.instance_of?(String)
21
+ template_args = Array(template)
22
+ if template_args.last.kind_of?(Hash)
23
+ template_opts = template_args.delete_at(template_args.length - 1)
24
+ else
25
+ template_opts = {}
26
+ end
27
+ template = self.class.builder_template( template_args, template_opts )
28
+ end
29
+
30
+ new_values = Array(new_values)
31
+ # template = self.class.builder_template(property_ref)
32
+
33
+ parent_select = Array(parent_select)
34
+ parent_nodeset = lookup(parent_select[0], parent_select[1])
35
+
36
+ if parent_index.kind_of?(Integer)
37
+ parent_node = parent_nodeset[parent_index]
38
+ elsif parent_index.kind_of?(Symbol) && parent_nodeset.respond_to?(parent_index)
39
+ parent_node = parent_nodeset.send(parent_index)
40
+ end
41
+
42
+ if parent_node.nil?
43
+ raise OX::ParentNodeNotFoundError, "Failed to find a parent node to insert values into based on :parent_select #{parent_select.inspect} with :parent_index #{parent_index.inspect}"
44
+ end
45
+
46
+ builder = Nokogiri::XML::Builder.with(parent_node) do |xml|
47
+ new_values.each do |builder_new_value|
48
+ builder_arg = eval('"'+ template + '"') # this inserts builder_new_value into the builder template
49
+ eval(builder_arg)
50
+ end
51
+ end
52
+
53
+ # Nokogiri::XML::Node.new(builder.to_xml, foo)
54
+
55
+ return parent_node
56
+
57
+ end
58
+
59
+ def property_value_set(property_ref, query_opts, node_index, new_value)
60
+ end
61
+
62
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+
3
+ require 'nokogiri'
4
+ require "facets"
5
+
6
+ module OX; end
7
+
8
+ require "opinionated-xml/ox_property_values_helper"
9
+ require File.join(File.dirname(__FILE__), 'opinionated-xml', 'ox.rb')