nokogiri-happymapper 0.5.5 → 0.5.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|