nokogiri-happymapper 0.5.5 → 0.5.6
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/lib/happymapper.rb +137 -117
- data/spec/fixtures/set_config_options.xml +3 -0
- data/spec/happymapper_spec.rb +86 -47
- metadata +9 -8
data/lib/happymapper.rb
CHANGED
@@ -7,7 +7,7 @@ class XmlContent; end
|
|
7
7
|
|
8
8
|
module HappyMapper
|
9
9
|
|
10
|
-
VERSION = "0.5.
|
10
|
+
VERSION = "0.5.6"
|
11
11
|
|
12
12
|
DEFAULT_NS = "happymapper"
|
13
13
|
|
@@ -21,33 +21,33 @@ module HappyMapper
|
|
21
21
|
end
|
22
22
|
|
23
23
|
module ClassMethods
|
24
|
-
|
24
|
+
|
25
25
|
#
|
26
26
|
# The xml has the following attributes defined.
|
27
|
-
#
|
27
|
+
#
|
28
28
|
# @example
|
29
|
-
#
|
29
|
+
#
|
30
30
|
# "<country code='de'>Germany</country>"
|
31
|
-
#
|
31
|
+
#
|
32
32
|
# # definition of the 'code' attribute within the class
|
33
33
|
# attribute :code, String
|
34
|
-
#
|
34
|
+
#
|
35
35
|
# @param [Symbol] name the name of the accessor that is created
|
36
36
|
# @param [String,Class] type the class name of the name of the class whcih
|
37
37
|
# the object will be converted upon parsing
|
38
38
|
# @param [Hash] options additional parameters to send to the relationship
|
39
|
-
#
|
39
|
+
#
|
40
40
|
def attribute(name, type, options={})
|
41
41
|
attribute = Attribute.new(name, type, options)
|
42
42
|
@attributes[to_s] ||= []
|
43
43
|
@attributes[to_s] << attribute
|
44
44
|
attr_accessor attribute.method_name.intern
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
#
|
48
48
|
# The elements defined through {#attribute}.
|
49
|
-
#
|
50
|
-
# @return [Array<Attribute>] a list of the attributes defined for this class;
|
49
|
+
#
|
50
|
+
# @return [Array<Attribute>] a list of the attributes defined for this class;
|
51
51
|
# an empty array is returned when there have been no attributes defined.
|
52
52
|
#
|
53
53
|
def attributes
|
@@ -57,37 +57,37 @@ module HappyMapper
|
|
57
57
|
#
|
58
58
|
# Register a namespace that is used to persist the object namespace back to
|
59
59
|
# XML.
|
60
|
-
#
|
60
|
+
#
|
61
61
|
# @example
|
62
|
-
#
|
62
|
+
#
|
63
63
|
# register_namespace 'prefix', 'http://www.unicornland.com/prefix'
|
64
|
-
#
|
64
|
+
#
|
65
65
|
# # the output will contain the namespace defined
|
66
|
-
#
|
66
|
+
#
|
67
67
|
# "<outputXML xmlns:prefix="http://www.unicornland.com/prefix">
|
68
68
|
# ...
|
69
69
|
# </outputXML>"
|
70
|
-
#
|
70
|
+
#
|
71
71
|
# @param [String] namespace the xml prefix
|
72
72
|
# @param [String] ns url for the xml namespace
|
73
73
|
#
|
74
74
|
def register_namespace(namespace, ns)
|
75
75
|
@registered_namespaces.merge!({namespace => ns})
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
#
|
79
79
|
# An element defined in the XML that is parsed.
|
80
|
-
#
|
80
|
+
#
|
81
81
|
# @example
|
82
|
-
#
|
82
|
+
#
|
83
83
|
# "<address location='home'>
|
84
84
|
# <city>Oldenburg</city>
|
85
85
|
# </address>"
|
86
86
|
#
|
87
87
|
# # definition of the 'city' element within the class
|
88
|
-
#
|
88
|
+
#
|
89
89
|
# element :city, String
|
90
|
-
#
|
90
|
+
#
|
91
91
|
# @param [Symbol] name the name of the accessor that is created
|
92
92
|
# @param [String,Class] type the class name of the name of the class whcih
|
93
93
|
# the object will be converted upon parsing
|
@@ -102,7 +102,7 @@ module HappyMapper
|
|
102
102
|
|
103
103
|
#
|
104
104
|
# The elements defined through {#element}, {#has_one}, and {#has_many}.
|
105
|
-
#
|
105
|
+
#
|
106
106
|
# @return [Array<Element>] a list of the elements contained defined for this
|
107
107
|
# class; an empty array is returned when there have been no elements
|
108
108
|
# defined.
|
@@ -110,18 +110,18 @@ module HappyMapper
|
|
110
110
|
def elements
|
111
111
|
@elements[to_s] || []
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
#
|
115
115
|
# The value stored in the text node of the current element.
|
116
116
|
#
|
117
117
|
# @example
|
118
|
-
#
|
118
|
+
#
|
119
119
|
# "<firstName>Michael Jackson</firstName>"
|
120
120
|
#
|
121
121
|
# # definition of the 'firstName' text node within the class
|
122
|
-
#
|
122
|
+
#
|
123
123
|
# content :first_name, String
|
124
|
-
#
|
124
|
+
#
|
125
125
|
# @param [Symbol] name the name of the accessor that is created
|
126
126
|
# @param [String,Class] type the class name of the name of the class whcih
|
127
127
|
# the object will be converted upon parsing. By Default String class will be taken.
|
@@ -145,21 +145,21 @@ module HappyMapper
|
|
145
145
|
#
|
146
146
|
# The object has one of these elements in the XML. If there are multiple,
|
147
147
|
# the last one will be set to this value.
|
148
|
-
#
|
148
|
+
#
|
149
149
|
# @param [Symbol] name the name of the accessor that is created
|
150
150
|
# @param [String,Class] type the class name of the name of the class whcih
|
151
151
|
# the object will be converted upon parsing
|
152
152
|
# @param [Hash] options additional parameters to send to the relationship
|
153
153
|
#
|
154
154
|
# @see #element
|
155
|
-
#
|
155
|
+
#
|
156
156
|
def has_one(name, type, options={})
|
157
157
|
element name, type, {:single => true}.merge(options)
|
158
158
|
end
|
159
|
-
|
159
|
+
|
160
160
|
#
|
161
161
|
# The object has many of these elements in the XML.
|
162
|
-
#
|
162
|
+
#
|
163
163
|
# @param [Symbol] name the name of accessor that is created
|
164
164
|
# @param [String,Class] type the class name or the name of the class which
|
165
165
|
# the object will be converted upon parsing.
|
@@ -170,7 +170,7 @@ module HappyMapper
|
|
170
170
|
def has_many(name, type, options={})
|
171
171
|
element name, type, {:single => false}.merge(options)
|
172
172
|
end
|
173
|
-
|
173
|
+
|
174
174
|
#
|
175
175
|
# Specify a namespace if a node and all its children are all namespaced
|
176
176
|
# elements. This is simpler than passing the :namespace option to each
|
@@ -193,13 +193,13 @@ module HappyMapper
|
|
193
193
|
|
194
194
|
#
|
195
195
|
# The name of the tag
|
196
|
-
#
|
196
|
+
#
|
197
197
|
# @return [String] the name of the tag as a string, downcased
|
198
198
|
#
|
199
199
|
def tag_name
|
200
200
|
@tag_name ||= to_s.split('::')[-1].downcase
|
201
201
|
end
|
202
|
-
|
202
|
+
|
203
203
|
# There is an XML tag that needs to be known for parsing and should be generated
|
204
204
|
# during a to_xml. But it doesn't need to be a class and the contained elements should
|
205
205
|
# be made available on the parent class
|
@@ -207,7 +207,7 @@ module HappyMapper
|
|
207
207
|
# @param [String] name the name of the element that is just a place holder
|
208
208
|
# @param [Proc] blk the element definitions inside the place holder tag
|
209
209
|
#
|
210
|
-
def wrap(name, &blk)
|
210
|
+
def wrap(name, &blk)
|
211
211
|
# Get an anonymous HappyMapper that has 'name' as its tag and defined
|
212
212
|
# in '&blk'. Then save that to a class instance variable for later use
|
213
213
|
wrapper = AnonymousWrapperClassFactory.get(name, &blk)
|
@@ -215,8 +215,8 @@ module HappyMapper
|
|
215
215
|
|
216
216
|
# Create getter/setter for each element and attribute defined on the anonymous HappyMapper
|
217
217
|
# onto this class. They get/set the value by passing thru to the anonymous class.
|
218
|
-
passthrus = wrapper.attributes + wrapper.elements
|
219
|
-
passthrus.each do |item|
|
218
|
+
passthrus = wrapper.attributes + wrapper.elements
|
219
|
+
passthrus.each do |item|
|
220
220
|
class_eval %{
|
221
221
|
def #{item.method_name}
|
222
222
|
@#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper.inspect}'].new
|
@@ -226,39 +226,59 @@ module HappyMapper
|
|
226
226
|
@#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper.inspect}'].new
|
227
227
|
@#{name}.#{item.method_name} = value
|
228
228
|
end
|
229
|
-
}
|
230
|
-
end
|
229
|
+
}
|
230
|
+
end
|
231
231
|
|
232
232
|
has_one name, wrapper
|
233
233
|
end
|
234
234
|
|
235
|
+
# The callback defined through {.with_nokogiri_config}.
|
236
|
+
#
|
237
|
+
# @return [Proc] the proc to pass to Nokogiri to setup parse options. nil if empty.
|
238
|
+
#
|
239
|
+
def nokogiri_config_callback
|
240
|
+
@nokogiri_config_callback
|
241
|
+
end
|
242
|
+
|
243
|
+
# Register a config callback according to the block Nokogori expects when calling Nokogiri::XML::Document.parse().
|
244
|
+
# See http://nokogiri.org/Nokogiri/XML/Document.html#method-c-parse
|
245
|
+
#
|
246
|
+
# @param [Proc] the proc to pass to Nokogiri to setup parse options
|
247
|
+
#
|
248
|
+
def with_nokogiri_config(&blk)
|
249
|
+
@nokogiri_config_callback = blk
|
250
|
+
end
|
251
|
+
|
235
252
|
#
|
236
|
-
# @param [Nokogiri::XML::Node,Nokogiri:XML::Document,String] xml the XML
|
253
|
+
# @param [Nokogiri::XML::Node,Nokogiri:XML::Document,String] xml the XML
|
237
254
|
# contents to convert into Object.
|
238
255
|
# @param [Hash] options additional information for parsing. :single => true
|
239
|
-
# if requesting a single object, otherwise it defaults to retuning an
|
256
|
+
# if requesting a single object, otherwise it defaults to retuning an
|
240
257
|
# array of multiple items. :xpath information where to start the parsing
|
241
258
|
# :namespace is the namespace to use for additional information.
|
242
259
|
#
|
243
260
|
def parse(xml, options = {})
|
244
|
-
|
261
|
+
|
245
262
|
# create a local copy of the objects namespace value for this parse execution
|
246
263
|
namespace = @namespace
|
247
|
-
|
264
|
+
|
248
265
|
# If the XML specified is an Node then we have what we need.
|
249
266
|
if xml.is_a?(Nokogiri::XML::Node) && !xml.is_a?(Nokogiri::XML::Document)
|
250
267
|
node = xml
|
251
268
|
else
|
252
|
-
|
269
|
+
|
253
270
|
# If xml is an XML document select the root node of the document
|
254
271
|
if xml.is_a?(Nokogiri::XML::Document)
|
255
272
|
node = xml.root
|
256
273
|
else
|
257
|
-
|
274
|
+
|
258
275
|
# Attempt to parse the xml value with Nokogiri XML as a document
|
259
276
|
# and select the root element
|
260
|
-
|
261
|
-
|
277
|
+
xml = Nokogiri::XML(
|
278
|
+
xml, nil, nil,
|
279
|
+
Nokogiri::XML::ParseOptions::STRICT,
|
280
|
+
&nokogiri_config_callback
|
281
|
+
)
|
262
282
|
node = xml.root
|
263
283
|
end
|
264
284
|
|
@@ -272,13 +292,13 @@ module HappyMapper
|
|
272
292
|
# if any namespaces have been provied then we should capture those and then
|
273
293
|
# merge them with any namespaces found on the xml node and merge all that
|
274
294
|
# with any namespaces that have been registered on the object
|
275
|
-
|
295
|
+
|
276
296
|
namespaces = options[:namespaces] || {}
|
277
297
|
namespaces = namespaces.merge(xml.collect_namespaces) if xml.respond_to?(:collect_namespaces)
|
278
298
|
namespaces = namespaces.merge(@registered_namespaces)
|
279
|
-
|
299
|
+
|
280
300
|
# if a namespace has been provided then set the current namespace to it
|
281
|
-
# or set the default namespace to the one defined under 'xmlns'
|
301
|
+
# or set the default namespace to the one defined under 'xmlns'
|
282
302
|
# or set the default namespace to the namespace that matches 'happymapper's
|
283
303
|
|
284
304
|
if options[:namespace]
|
@@ -291,17 +311,17 @@ module HappyMapper
|
|
291
311
|
elsif namespaces.has_key?(DEFAULT_NS)
|
292
312
|
namespace ||= DEFAULT_NS
|
293
313
|
end
|
294
|
-
|
314
|
+
|
295
315
|
# from the options grab any nodes present and if none are present then
|
296
|
-
# perform the following to find the nodes for the given class
|
297
|
-
|
316
|
+
# perform the following to find the nodes for the given class
|
317
|
+
|
298
318
|
nodes = options.fetch(:nodes) do
|
299
|
-
|
319
|
+
|
300
320
|
# when at the root use the xpath '/' otherwise use a more gready './/'
|
301
321
|
# unless an xpath has been specified, which should overwrite default
|
302
322
|
# and finally attach the current namespace if one has been defined
|
303
|
-
#
|
304
|
-
|
323
|
+
#
|
324
|
+
|
305
325
|
xpath = (root ? '/' : './/')
|
306
326
|
xpath = options[:xpath].to_s.sub(/([^\/])$/, '\1/') if options[:xpath]
|
307
327
|
xpath += "#{namespace}:" if namespace
|
@@ -334,21 +354,21 @@ module HappyMapper
|
|
334
354
|
# a large result set of data.
|
335
355
|
|
336
356
|
limit = options[:in_groups_of] || nodes.size
|
337
|
-
|
357
|
+
|
338
358
|
# If the limit of 0 has been specified then the user obviously wants
|
339
359
|
# none of the nodes that we are serving within this batch of nodes.
|
340
|
-
|
360
|
+
|
341
361
|
return [] if limit == 0
|
342
362
|
|
343
363
|
collection = []
|
344
|
-
|
364
|
+
|
345
365
|
nodes.each_slice(limit) do |slice|
|
346
|
-
|
347
|
-
part = slice.map do |n|
|
348
|
-
|
366
|
+
|
367
|
+
part = slice.map do |n|
|
368
|
+
|
349
369
|
# If an existing HappyMapper object is provided, update it with the
|
350
370
|
# values from the xml being parsed. Otherwise, create a new object
|
351
|
-
|
371
|
+
|
352
372
|
obj = options[:update] ? options[:update] : new
|
353
373
|
|
354
374
|
attributes.each do |attr|
|
@@ -362,16 +382,16 @@ module HappyMapper
|
|
362
382
|
if @content
|
363
383
|
obj.send("#{@content.method_name}=",@content.from_xml_node(n, namespace, namespaces))
|
364
384
|
end
|
365
|
-
|
366
|
-
# If the HappyMapper class has the method #xml_value=,
|
385
|
+
|
386
|
+
# If the HappyMapper class has the method #xml_value=,
|
367
387
|
# attr_writer :xml_value, or attr_accessor :xml_value then we want to
|
368
388
|
# assign the current xml that we just parsed to the xml_value
|
369
|
-
|
389
|
+
|
370
390
|
if obj.respond_to?('xml_value=')
|
371
391
|
n.namespaces.each {|name,path| n[name] = path }
|
372
392
|
obj.xml_value = n.to_xml
|
373
393
|
end
|
374
|
-
|
394
|
+
|
375
395
|
# If the HappyMapper class has the method #xml_content=,
|
376
396
|
# attr_write :xml_content, or attr_accessor :xml_content then we want to
|
377
397
|
# assign the child xml that we just parsed to the xml_content
|
@@ -380,12 +400,12 @@ module HappyMapper
|
|
380
400
|
n = n.children if n.respond_to?(:children)
|
381
401
|
obj.xml_content = n.to_xml
|
382
402
|
end
|
383
|
-
|
403
|
+
|
384
404
|
# collect the object that we have created
|
385
|
-
|
405
|
+
|
386
406
|
obj
|
387
407
|
end
|
388
|
-
|
408
|
+
|
389
409
|
# If a block has been provided and the user has requested that the objects
|
390
410
|
# be handled in groups then we should yield the slice of the objects to them
|
391
411
|
# otherwise continue to lump them together
|
@@ -395,7 +415,7 @@ module HappyMapper
|
|
395
415
|
else
|
396
416
|
collection += part
|
397
417
|
end
|
398
|
-
|
418
|
+
|
399
419
|
end
|
400
420
|
|
401
421
|
# per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
|
@@ -411,9 +431,9 @@ module HappyMapper
|
|
411
431
|
collection
|
412
432
|
end
|
413
433
|
end
|
414
|
-
|
434
|
+
|
415
435
|
end
|
416
|
-
|
436
|
+
|
417
437
|
#
|
418
438
|
# Create an xml representation of the specified class based on defined
|
419
439
|
# HappyMapper elements and attributes. The method is defined in a way
|
@@ -424,7 +444,7 @@ module HappyMapper
|
|
424
444
|
# is being used when called recursively.
|
425
445
|
# @param [String] default_namespace the name of the namespace which is the
|
426
446
|
# default for the xml being produced; this is specified by the element
|
427
|
-
# declaration when calling #to_xml recursively.
|
447
|
+
# declaration when calling #to_xml recursively.
|
428
448
|
# @param [String] tag_from_parent the xml tag to use on the element when being
|
429
449
|
# called recursively. This lets the parent doc define its own structure.
|
430
450
|
# Otherwise the element uses the tag it has defined for itself. Should only
|
@@ -435,35 +455,35 @@ module HappyMapper
|
|
435
455
|
# and Nokogiri::XML::Builder object.
|
436
456
|
#
|
437
457
|
def to_xml(builder = nil,default_namespace = nil,tag_from_parent = nil)
|
438
|
-
|
458
|
+
|
439
459
|
#
|
440
460
|
# If to_xml has been called without a passed in builder instance that
|
441
461
|
# means we are going to return xml output. When it has been called with
|
442
462
|
# a builder instance that means we most likely being called recursively
|
443
|
-
# and will return the end product as a builder instance.
|
463
|
+
# and will return the end product as a builder instance.
|
444
464
|
#
|
445
465
|
unless builder
|
446
466
|
write_out_to_xml = true
|
447
467
|
builder = Nokogiri::XML::Builder.new
|
448
468
|
end
|
449
|
-
|
469
|
+
|
450
470
|
#
|
451
471
|
# Find the attributes for the class and collect them into an array
|
452
472
|
# that will be placed into a Hash structure
|
453
473
|
#
|
454
474
|
attributes = self.class.attributes.collect do |attribute|
|
455
|
-
|
475
|
+
|
456
476
|
#
|
457
477
|
# If an attribute is marked as read_only then we want to ignore the attribute
|
458
478
|
# when it comes to saving the xml document; so we wiill not go into any of
|
459
479
|
# the below process
|
460
|
-
#
|
480
|
+
#
|
461
481
|
unless attribute.options[:read_only]
|
462
|
-
|
482
|
+
|
463
483
|
value = send(attribute.method_name)
|
464
|
-
|
484
|
+
|
465
485
|
#
|
466
|
-
# If the attribute defines an on_save lambda/proc or value that maps to
|
486
|
+
# If the attribute defines an on_save lambda/proc or value that maps to
|
467
487
|
# a method that the class has defined, then call it with the value as a
|
468
488
|
# parameter.
|
469
489
|
#
|
@@ -474,7 +494,7 @@ module HappyMapper
|
|
474
494
|
value = send(on_save_action,value)
|
475
495
|
end
|
476
496
|
end
|
477
|
-
|
497
|
+
|
478
498
|
#
|
479
499
|
# Attributes that have a nil value should be ignored unless they explicitly
|
480
500
|
# state that they should be expressed in the output.
|
@@ -485,27 +505,27 @@ module HappyMapper
|
|
485
505
|
else
|
486
506
|
[]
|
487
507
|
end
|
488
|
-
|
508
|
+
|
489
509
|
else
|
490
510
|
[]
|
491
511
|
end
|
492
|
-
|
512
|
+
|
493
513
|
end.flatten
|
494
|
-
|
514
|
+
|
495
515
|
attributes = Hash[ *attributes ]
|
496
|
-
|
516
|
+
|
497
517
|
#
|
498
518
|
# Create a tag in the builder that matches the class's tag name unless a tag was passed
|
499
519
|
# in a recursive call from the parent doc. Then append
|
500
520
|
# any attributes to the element that were defined above.
|
501
521
|
#
|
502
522
|
builder.send("#{tag_from_parent || self.class.tag_name}_",attributes) do |xml|
|
503
|
-
|
523
|
+
|
504
524
|
#
|
505
525
|
# Add all the registered namespaces to the root element.
|
506
526
|
# When this is called recurisvely by composed classes the namespaces
|
507
527
|
# are still added to the root element
|
508
|
-
#
|
528
|
+
#
|
509
529
|
# However, we do not want to add the namespace if the namespace is 'xmlns'
|
510
530
|
# which means that it is the default namesapce of the code.
|
511
531
|
#
|
@@ -515,7 +535,7 @@ module HappyMapper
|
|
515
535
|
builder.doc.root.add_namespace(name,href)
|
516
536
|
end
|
517
537
|
end
|
518
|
-
|
538
|
+
|
519
539
|
#
|
520
540
|
# If the object we are persisting has a namespace declaration we will want
|
521
541
|
# to use that namespace or we will use the default namespace.
|
@@ -528,17 +548,17 @@ module HappyMapper
|
|
528
548
|
xml.parent.namespace = builder.doc.root.namespace_definitions.find { |x| x.prefix == default_namespace }
|
529
549
|
end
|
530
550
|
|
531
|
-
|
551
|
+
|
532
552
|
#
|
533
553
|
# When a content has been defined we add the resulting value
|
534
554
|
# the output xml
|
535
555
|
#
|
536
556
|
if content = self.class.instance_variable_get('@content')
|
537
|
-
|
557
|
+
|
538
558
|
unless content.options[:read_only]
|
539
559
|
text_accessor = content.tag || content.name
|
540
560
|
value = send(text_accessor)
|
541
|
-
|
561
|
+
|
542
562
|
if on_save_action = content.options[:on_save]
|
543
563
|
if on_save_action.is_a?(Proc)
|
544
564
|
value = on_save_action.call(value)
|
@@ -546,10 +566,10 @@ module HappyMapper
|
|
546
566
|
value = send(on_save_action,value)
|
547
567
|
end
|
548
568
|
end
|
549
|
-
|
569
|
+
|
550
570
|
builder.text(value)
|
551
571
|
end
|
552
|
-
|
572
|
+
|
553
573
|
end
|
554
574
|
|
555
575
|
#
|
@@ -557,15 +577,15 @@ module HappyMapper
|
|
557
577
|
# going to persist each one
|
558
578
|
#
|
559
579
|
self.class.elements.each do |element|
|
560
|
-
|
580
|
+
|
561
581
|
#
|
562
|
-
# If an element is marked as read only do not consider at all when
|
582
|
+
# If an element is marked as read only do not consider at all when
|
563
583
|
# saving to XML.
|
564
|
-
#
|
584
|
+
#
|
565
585
|
unless element.options[:read_only]
|
566
|
-
|
586
|
+
|
567
587
|
tag = element.tag || element.name
|
568
|
-
|
588
|
+
|
569
589
|
#
|
570
590
|
# The value to store is the result of the method call to the element,
|
571
591
|
# by default this is simply utilizing the attr_accessor defined. However,
|
@@ -575,7 +595,7 @@ module HappyMapper
|
|
575
595
|
|
576
596
|
#
|
577
597
|
# If the element defines an on_save lambda/proc then we will call that
|
578
|
-
# operation on the specified value. This allows for operations to be
|
598
|
+
# operation on the specified value. This allows for operations to be
|
579
599
|
# performed to convert the value to a specific value to be saved to the xml.
|
580
600
|
#
|
581
601
|
if on_save_action = element.options[:on_save]
|
@@ -583,7 +603,7 @@ module HappyMapper
|
|
583
603
|
value = on_save_action.call(value)
|
584
604
|
elsif respond_to?(on_save_action)
|
585
605
|
value = send(on_save_action,value)
|
586
|
-
end
|
606
|
+
end
|
587
607
|
end
|
588
608
|
|
589
609
|
#
|
@@ -593,7 +613,7 @@ module HappyMapper
|
|
593
613
|
if value.nil? && element.options[:single] && element.options[:state_when_nil]
|
594
614
|
xml.send("#{tag}_","")
|
595
615
|
end
|
596
|
-
|
616
|
+
|
597
617
|
#
|
598
618
|
# To allow for us to treat both groups of items and singular items
|
599
619
|
# equally we wrap the value and treat it as an array.
|
@@ -605,7 +625,7 @@ module HappyMapper
|
|
605
625
|
else
|
606
626
|
values = [value]
|
607
627
|
end
|
608
|
-
|
628
|
+
|
609
629
|
values.each do |item|
|
610
630
|
|
611
631
|
if item.is_a?(HappyMapper)
|
@@ -618,9 +638,9 @@ module HappyMapper
|
|
618
638
|
item.to_xml(xml,element.options[:namespace],element.options[:tag] || nil)
|
619
639
|
|
620
640
|
elsif item
|
621
|
-
|
622
|
-
item_namespace = element.options[:namespace] || self.class.namespace || default_namespace
|
623
|
-
|
641
|
+
|
642
|
+
item_namespace = element.options[:namespace] || self.class.namespace || default_namespace
|
643
|
+
|
624
644
|
#
|
625
645
|
# When a value exists we should append the value for the tag
|
626
646
|
#
|
@@ -641,7 +661,7 @@ module HappyMapper
|
|
641
661
|
end
|
642
662
|
|
643
663
|
end
|
644
|
-
|
664
|
+
|
645
665
|
end
|
646
666
|
end
|
647
667
|
|
@@ -650,24 +670,24 @@ module HappyMapper
|
|
650
670
|
# Write out to XML, this value was set above, based on whether or not an XML
|
651
671
|
# builder object was passed to it as a parameter. When there was no parameter
|
652
672
|
# we assume we are at the root level of the #to_xml call and want the actual
|
653
|
-
# xml generated from the object. If an XML builder instance was specified
|
654
|
-
# then we assume that has been called recursively to generate a larger
|
673
|
+
# xml generated from the object. If an XML builder instance was specified
|
674
|
+
# then we assume that has been called recursively to generate a larger
|
655
675
|
# XML document.
|
656
676
|
write_out_to_xml ? builder.to_xml : builder
|
657
|
-
|
677
|
+
|
658
678
|
end
|
659
|
-
|
679
|
+
|
660
680
|
# Parse the xml and update this instance. This does not update instances
|
661
681
|
# of HappyMappers that are children of this object. New instances will be
|
662
|
-
# created for any HappyMapper children of this object.
|
663
|
-
#
|
682
|
+
# created for any HappyMapper children of this object.
|
683
|
+
#
|
664
684
|
# Params and return are the same as the class parse() method above.
|
665
685
|
def parse(xml, options = {})
|
666
686
|
self.class.parse(xml, options.merge!(:update => self))
|
667
|
-
end
|
668
|
-
|
687
|
+
end
|
688
|
+
|
669
689
|
private
|
670
|
-
|
690
|
+
|
671
691
|
# Factory for creating anonmyous HappyMappers
|
672
692
|
class AnonymousWrapperClassFactory
|
673
693
|
def self.get(name, &blk)
|
@@ -675,10 +695,10 @@ module HappyMapper
|
|
675
695
|
include HappyMapper
|
676
696
|
tag name
|
677
697
|
instance_eval &blk
|
678
|
-
end
|
698
|
+
end
|
679
699
|
end
|
680
700
|
end
|
681
|
-
|
701
|
+
|
682
702
|
end
|
683
703
|
|
684
704
|
require File.dirname(__FILE__) + '/happymapper/item'
|
data/spec/happymapper_spec.rb
CHANGED
@@ -120,7 +120,7 @@ class Product
|
|
120
120
|
end
|
121
121
|
|
122
122
|
class Rate
|
123
|
-
include HappyMapper
|
123
|
+
include HappyMapper
|
124
124
|
end
|
125
125
|
|
126
126
|
module FamilySearch
|
@@ -313,7 +313,7 @@ end
|
|
313
313
|
|
314
314
|
|
315
315
|
class State
|
316
|
-
include HappyMapper
|
316
|
+
include HappyMapper
|
317
317
|
end
|
318
318
|
|
319
319
|
class Address
|
@@ -453,9 +453,9 @@ end
|
|
453
453
|
|
454
454
|
class PublishOptions
|
455
455
|
include HappyMapper
|
456
|
-
|
456
|
+
|
457
457
|
tag 'publishOptions'
|
458
|
-
|
458
|
+
|
459
459
|
element :author, String, :tag => 'author'
|
460
460
|
|
461
461
|
element :draft, Boolean, :tag => 'draft'
|
@@ -465,41 +465,41 @@ class PublishOptions
|
|
465
465
|
element :published_time, String, :tag => 'publishDisplayTime'
|
466
466
|
element :created_day, String, :tag => 'publishDisplayDay'
|
467
467
|
element :created_time, String, :tag => 'publishDisplayTime'
|
468
|
-
|
468
|
+
|
469
469
|
end
|
470
470
|
|
471
471
|
class Article
|
472
472
|
include HappyMapper
|
473
|
-
|
473
|
+
|
474
474
|
tag 'Article'
|
475
475
|
namespace 'article'
|
476
|
-
|
476
|
+
|
477
477
|
attr_writer :xml_value
|
478
|
-
|
478
|
+
|
479
479
|
element :title, String
|
480
480
|
element :text, String
|
481
481
|
has_many :photos, 'Photo', :tag => 'Photo', :namespace => 'photo', :xpath => '/article:Article'
|
482
482
|
has_many :galleries, 'Gallery', :tag => 'Gallery', :namespace => 'gallery'
|
483
|
-
|
483
|
+
|
484
484
|
element :publish_options, PublishOptions, :tag => 'publishOptions', :namespace => 'article'
|
485
|
-
|
485
|
+
|
486
486
|
end
|
487
487
|
|
488
488
|
class PartiallyBadArticle
|
489
489
|
include HappyMapper
|
490
|
-
|
490
|
+
|
491
491
|
attr_writer :xml_value
|
492
|
-
|
492
|
+
|
493
493
|
tag 'Article'
|
494
494
|
namespace 'article'
|
495
|
-
|
495
|
+
|
496
496
|
element :title, String
|
497
497
|
element :text, String
|
498
498
|
has_many :photos, 'Photo', :tag => 'Photo', :namespace => 'photo', :xpath => '/article:Article'
|
499
499
|
has_many :videos, 'Video', :tag => 'Video', :namespace => 'video'
|
500
|
-
|
500
|
+
|
501
501
|
element :publish_options, PublishOptions, :tag => 'publishOptions', :namespace => 'article'
|
502
|
-
|
502
|
+
|
503
503
|
end
|
504
504
|
|
505
505
|
class Photo
|
@@ -507,45 +507,45 @@ class Photo
|
|
507
507
|
|
508
508
|
tag 'Photo'
|
509
509
|
namespace 'photo'
|
510
|
-
|
510
|
+
|
511
511
|
attr_writer :xml_value
|
512
|
-
|
512
|
+
|
513
513
|
element :title, String
|
514
514
|
element :publish_options, PublishOptions, :tag => 'publishOptions', :namespace => 'photo'
|
515
|
-
|
515
|
+
|
516
516
|
end
|
517
517
|
|
518
518
|
class Gallery
|
519
519
|
include HappyMapper
|
520
|
-
|
520
|
+
|
521
521
|
tag 'Gallery'
|
522
522
|
namespace 'gallery'
|
523
523
|
|
524
524
|
attr_writer :xml_value
|
525
525
|
|
526
526
|
element :title, String
|
527
|
-
|
527
|
+
|
528
528
|
end
|
529
529
|
|
530
530
|
class Video
|
531
531
|
include HappyMapper
|
532
|
-
|
532
|
+
|
533
533
|
tag 'Video'
|
534
534
|
namespace 'video'
|
535
535
|
|
536
536
|
attr_writer :xml_value
|
537
|
-
|
537
|
+
|
538
538
|
element :title, String
|
539
539
|
element :publish_options, PublishOptions, :tag => 'publishOptions', :namespace => 'video'
|
540
|
-
|
540
|
+
|
541
541
|
end
|
542
542
|
|
543
543
|
class OptionalAttribute
|
544
544
|
include HappyMapper
|
545
545
|
tag 'address'
|
546
|
-
|
546
|
+
|
547
547
|
attribute :street, String
|
548
|
-
end
|
548
|
+
end
|
549
549
|
|
550
550
|
class DefaultNamespaceCombi
|
551
551
|
include HappyMapper
|
@@ -673,23 +673,23 @@ describe HappyMapper do
|
|
673
673
|
end
|
674
674
|
end
|
675
675
|
|
676
|
-
describe "#content" do
|
677
|
-
it "should take String as default argument for type" do
|
676
|
+
describe "#content" do
|
677
|
+
it "should take String as default argument for type" do
|
678
678
|
State.content :name
|
679
679
|
address = Address.parse(fixture_file('address.xml'))
|
680
680
|
address.state.name.should == "Lower Saxony"
|
681
681
|
address.state.name.class == String
|
682
682
|
end
|
683
|
-
|
684
|
-
it "should work when specific type is provided" do
|
683
|
+
|
684
|
+
it "should work when specific type is provided" do
|
685
685
|
Rate.content :value, Float
|
686
686
|
Product.has_one :rate, Rate
|
687
|
-
product = Product.parse(fixture_file('product_default_namespace.xml'), :single => true)
|
687
|
+
product = Product.parse(fixture_file('product_default_namespace.xml'), :single => true)
|
688
688
|
product.rate.value.should == 120.25
|
689
689
|
product.rate.class == Float
|
690
|
-
end
|
690
|
+
end
|
691
691
|
end
|
692
|
-
|
692
|
+
|
693
693
|
it "should parse xml attributes into ruby objects" do
|
694
694
|
posts = Post.parse(fixture_file('posts.xml'))
|
695
695
|
posts.size.should == 20
|
@@ -946,26 +946,26 @@ describe HappyMapper do
|
|
946
946
|
l = Location.parse(fixture_file('lastfm.xml'))
|
947
947
|
l.first.latitude.should == "51.53469"
|
948
948
|
end
|
949
|
-
|
949
|
+
|
950
950
|
describe "Parse optional attributes" do
|
951
|
-
|
951
|
+
|
952
952
|
it "should parse an empty String as empty" do
|
953
953
|
a = OptionalAttribute.parse(fixture_file('optional_attributes.xml'))
|
954
954
|
a[0].street.should == ""
|
955
955
|
end
|
956
|
-
|
956
|
+
|
957
957
|
it "should parse a String with value" do
|
958
958
|
a = OptionalAttribute.parse(fixture_file('optional_attributes.xml'))
|
959
959
|
a[1].street.should == "Milchstrasse"
|
960
960
|
end
|
961
|
-
|
961
|
+
|
962
962
|
it "should parse a String with value" do
|
963
963
|
a = OptionalAttribute.parse(fixture_file('optional_attributes.xml'))
|
964
964
|
a[2].street.should be_nil
|
965
965
|
end
|
966
|
-
|
966
|
+
|
967
967
|
end
|
968
|
-
|
968
|
+
|
969
969
|
describe "Default namespace combi" do
|
970
970
|
before(:each) do
|
971
971
|
file_contents = fixture_file('default_namespace_combi.xml')
|
@@ -1005,8 +1005,8 @@ describe HappyMapper do
|
|
1005
1005
|
items = AmbigousItems::Item.parse(fixture_file('ambigous_items.xml'), :xpath => '/ambigous/my-items')
|
1006
1006
|
items.map(&:name).should == %w(first second third).map{|s| "My #{s} item" }
|
1007
1007
|
end
|
1008
|
-
|
1009
|
-
|
1008
|
+
|
1009
|
+
|
1010
1010
|
context Article do
|
1011
1011
|
it "should parse the publish options for Article and Photo" do
|
1012
1012
|
@article.title.should_not be_nil
|
@@ -1014,7 +1014,7 @@ describe HappyMapper do
|
|
1014
1014
|
@article.photos.should_not be_nil
|
1015
1015
|
@article.photos.first.title.should_not be_nil
|
1016
1016
|
end
|
1017
|
-
|
1017
|
+
|
1018
1018
|
it "should parse the publish options for Article" do
|
1019
1019
|
@article.publish_options.should_not be_nil
|
1020
1020
|
end
|
@@ -1026,13 +1026,13 @@ describe HappyMapper do
|
|
1026
1026
|
it "should only find only items at the parent level" do
|
1027
1027
|
@article.photos.length.should == 1
|
1028
1028
|
end
|
1029
|
-
|
1029
|
+
|
1030
1030
|
before(:all) do
|
1031
1031
|
@article = Article.parse(fixture_file('subclass_namespace.xml'))
|
1032
1032
|
end
|
1033
|
-
|
1033
|
+
|
1034
1034
|
end
|
1035
|
-
|
1035
|
+
|
1036
1036
|
context "Namespace is missing because an optional element that uses it is not present" do
|
1037
1037
|
it "should parse successfully" do
|
1038
1038
|
@article = PartiallyBadArticle.parse(fixture_file('subclass_namespace.xml'))
|
@@ -1043,8 +1043,8 @@ describe HappyMapper do
|
|
1043
1043
|
@article.photos.first.title.should_not be_nil
|
1044
1044
|
end
|
1045
1045
|
end
|
1046
|
-
|
1047
|
-
|
1046
|
+
|
1047
|
+
|
1048
1048
|
describe "with limit option" do
|
1049
1049
|
it "should return results with limited size: 6" do
|
1050
1050
|
sizes = []
|
@@ -1062,5 +1062,44 @@ describe HappyMapper do
|
|
1062
1062
|
sizes.should == [10, 10]
|
1063
1063
|
end
|
1064
1064
|
end
|
1065
|
-
|
1065
|
+
|
1066
|
+
context "when letting user set Nokogiri::XML::ParseOptions" do
|
1067
|
+
let(:default) {
|
1068
|
+
Class.new do
|
1069
|
+
include HappyMapper
|
1070
|
+
element :item, String
|
1071
|
+
end
|
1072
|
+
}
|
1073
|
+
let(:custom) {
|
1074
|
+
Class.new do
|
1075
|
+
include HappyMapper
|
1076
|
+
element :item, String
|
1077
|
+
with_nokogiri_config do |config|
|
1078
|
+
config.default_xml
|
1079
|
+
end
|
1080
|
+
end
|
1081
|
+
}
|
1082
|
+
|
1083
|
+
it 'initializes @nokogiri_config_callback to nil' do
|
1084
|
+
default.nokogiri_config_callback.should be_nil
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
it 'defaults to Nokogiri::XML::ParseOptions::STRICT' do
|
1088
|
+
expect { default.parse(fixture_file('set_config_options.xml')) }.to raise_error(Nokogiri::XML::SyntaxError)
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
it 'accepts .on_config callback' do
|
1092
|
+
custom.nokogiri_config_callback.should_not be_nil
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
it 'parses according to @nokogiri_config_callback' do
|
1096
|
+
expect { custom.parse(fixture_file('set_config_options.xml')) }.to_not raise_error(Nokogiri::XML::SyntaxError)
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
it 'can clear @nokogiri_config_callback' do
|
1100
|
+
custom.with_nokogiri_config {}
|
1101
|
+
expect { custom.parse(fixture_file('set_config_options.xml')) }.to raise_error(Nokogiri::XML::SyntaxError)
|
1102
|
+
end
|
1103
|
+
end
|
1104
|
+
|
1066
1105
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nokogiri-happymapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date:
|
17
|
+
date: 2012-10-29 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: nokogiri
|
@@ -23,7 +23,7 @@ dependencies:
|
|
23
23
|
requirements:
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.5'
|
27
27
|
type: :runtime
|
28
28
|
prerelease: false
|
29
29
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -31,7 +31,7 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - ~>
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '1.
|
34
|
+
version: '1.5'
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: rspec
|
37
37
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
version: '2.8'
|
51
51
|
description: Object to XML Mapping Library, using Nokogiri (fork from John Nunemaker's
|
52
52
|
Happymapper)
|
53
|
-
email:
|
53
|
+
email:
|
54
54
|
executables: []
|
55
55
|
extensions: []
|
56
56
|
extra_rdoc_files:
|
@@ -88,6 +88,7 @@ files:
|
|
88
88
|
- spec/fixtures/product_single_namespace.xml
|
89
89
|
- spec/fixtures/quarters.xml
|
90
90
|
- spec/fixtures/radar.xml
|
91
|
+
- spec/fixtures/set_config_options.xml
|
91
92
|
- spec/fixtures/statuses.xml
|
92
93
|
- spec/fixtures/subclass_namespace.xml
|
93
94
|
- spec/fixtures/wrapper.xml
|
@@ -106,9 +107,8 @@ files:
|
|
106
107
|
- spec/xpath_spec.rb
|
107
108
|
homepage: http://github.com/dam5s/happymapper
|
108
109
|
licenses: []
|
109
|
-
post_install_message: ! "\n Thank you for installing nokogiri-happymapper 0.5.
|
110
|
-
|
111
|
-
they parse correctly (zrob)\n \n\n\n"
|
110
|
+
post_install_message: ! "\n Thank you for installing nokogiri-happymapper 0.5.6 /
|
111
|
+
.\n\n Changes:\n \n\n"
|
112
112
|
rdoc_options: []
|
113
113
|
require_paths:
|
114
114
|
- lib
|
@@ -153,6 +153,7 @@ test_files:
|
|
153
153
|
- spec/fixtures/product_single_namespace.xml
|
154
154
|
- spec/fixtures/quarters.xml
|
155
155
|
- spec/fixtures/radar.xml
|
156
|
+
- spec/fixtures/set_config_options.xml
|
156
157
|
- spec/fixtures/statuses.xml
|
157
158
|
- spec/fixtures/subclass_namespace.xml
|
158
159
|
- spec/fixtures/wrapper.xml
|