nokogiri-happymapper 0.5.9 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 249c78b638ec21c3296253579fc39a43c8a9ab63
4
- data.tar.gz: 65cbf3b6b5bb79dd5daa2946f4af6da9de91e0ff
3
+ metadata.gz: f94968fd78baee9196d17340ee0fa5731c6f6fd1
4
+ data.tar.gz: 7ca5b00acc1126a265473d583ee331beb00a50e0
5
5
  SHA512:
6
- metadata.gz: 19de97b5d193847cbfbec6306fe58d4f4fc99ab90807b301e60f6cbc5263cb9440044b2de8d04076c43f69d40ae93af662f580fd601f3211d122eda3d527142a
7
- data.tar.gz: 378289efba3dcfc33709ddd400a71bf7d25f93dc4af568ecaaa2803788890b300490f052613844ca678eba1e822f69feced3dd7dd5ba52510f0f0e75ea1a8fc9
6
+ metadata.gz: e1d779907f83b14a9b392d4dbcca7515dbda14bc8e9932b48a78cbaf7924df5e65adef12454c294947a7e86167dca1866900133985da9ed6cc14bf57fb392e37
7
+ data.tar.gz: 5205073663f778a8b699d4d54aa63a1d171f12ee300cbc991bd7e3fb4ee2cc252fc2d4540b270ecadb75e0567bf8abceda913dae822f6c9f103275fe70822785
@@ -1,3 +1,12 @@
1
+ ## 0.6.0 / Unreleased
2
+
3
+ * Prevent parsing of empty string for Date, DateTime (wushugene)
4
+ * Rescue nil dates (sarsena)
5
+ * Preserve XML value (benoist)
6
+ * Restore after_parse callback support (codekitchen)
7
+ * Parse specific types before general types (Ivo Wever)
8
+ * Higher priority for namespace on element declarations (Ivo Wever)
9
+
1
10
  ## 0.5.9 / 2014-02-18
2
11
 
3
12
  * Correctly output boolean element value 'false' (confusion)
@@ -32,4 +41,4 @@
32
41
  ## 0.5.3/ 2012-09-23
33
42
 
34
43
  * String is the default type for parsed fields. (crv)
35
- * Update the attributes of an existing HappyMapper instance with new XML (benoist)
44
+ * Update the attributes of an existing HappyMapper instance with new XML (benoist)
data/License ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 John Nunemaker
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.
@@ -109,8 +109,8 @@ module HappyMapper
109
109
  #
110
110
  def element(name, type, options={})
111
111
  element = Element.new(name, type, options)
112
+ attr_accessor element.method_name.intern unless @elements[name]
112
113
  @elements[name] = element
113
- attr_accessor element.method_name.intern
114
114
  end
115
115
 
116
116
  #
@@ -184,6 +184,22 @@ module HappyMapper
184
184
  element name, type, {:single => false}.merge(options)
185
185
  end
186
186
 
187
+ #
188
+ # The list of registered after_parse callbacks.
189
+ #
190
+ def after_parse_callbacks
191
+ @after_parse_callbacks ||= []
192
+ end
193
+
194
+ #
195
+ # Register a new after_parse callback, given as a block.
196
+ #
197
+ # @yield [object] Yields the newly-parsed object to the block after parsing.
198
+ # Sub-objects will be already populated.
199
+ def after_parse(&block)
200
+ after_parse_callbacks.push(block)
201
+ end
202
+
187
203
  #
188
204
  # Specify a namespace if a node and all its children are all namespaced
189
205
  # elements. This is simpler than passing the :namespace option to each
@@ -194,7 +210,7 @@ module HappyMapper
194
210
  #
195
211
  def namespace(namespace = nil)
196
212
  @namespace = namespace if namespace
197
- @namespace
213
+ @namespace if defined? @namespace
198
214
  end
199
215
 
200
216
  #
@@ -249,9 +265,7 @@ module HappyMapper
249
265
  #
250
266
  # @return [Proc] the proc to pass to Nokogiri to setup parse options. nil if empty.
251
267
  #
252
- def nokogiri_config_callback
253
- @nokogiri_config_callback
254
- end
268
+ attr_reader :nokogiri_config_callback
255
269
 
256
270
  # Register a config callback according to the block Nokogori expects when calling Nokogiri::XML::Document.parse().
257
271
  # See http://nokogiri.org/Nokogiri/XML/Document.html#method-c-parse
@@ -273,7 +287,7 @@ module HappyMapper
273
287
  def parse(xml, options = {})
274
288
 
275
289
  # create a local copy of the objects namespace value for this parse execution
276
- namespace = @namespace
290
+ namespace = (@namespace if defined? @namespace)
277
291
 
278
292
  # If the XML specified is an Node then we have what we need.
279
293
  if xml.is_a?(Nokogiri::XML::Node) && !xml.is_a?(Nokogiri::XML::Document)
@@ -410,7 +424,7 @@ module HappyMapper
410
424
  obj.send("#{elem.method_name}=",elem.from_xml_node(n, namespace, namespaces))
411
425
  end
412
426
 
413
- if @content
427
+ if (defined? @content) && @content
414
428
  obj.send("#{@content.method_name}=",@content.from_xml_node(n, namespace, namespaces))
415
429
  end
416
430
 
@@ -420,7 +434,7 @@ module HappyMapper
420
434
 
421
435
  if obj.respond_to?('xml_value=')
422
436
  n.namespaces.each {|name,path| n[name] = path }
423
- obj.xml_value = n.to_xml
437
+ obj.xml_value = n.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
424
438
  end
425
439
 
426
440
  # If the HappyMapper class has the method #xml_content=,
@@ -429,9 +443,13 @@ module HappyMapper
429
443
 
430
444
  if obj.respond_to?('xml_content=')
431
445
  n = n.children if n.respond_to?(:children)
432
- obj.xml_content = n.to_xml
446
+ obj.xml_content = n.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
433
447
  end
434
448
 
449
+ # Call any registered after_parse callbacks for the object's class
450
+
451
+ obj.class.after_parse_callbacks.each { |callback| callback.call(obj) }
452
+
435
453
  # collect the object that we have created
436
454
 
437
455
  obj
@@ -480,10 +498,13 @@ module HappyMapper
480
498
  #
481
499
  # @param [Nokogiri::XML::Builder] builder an instance of the XML builder which
482
500
  # is being used when called recursively.
483
- # @param [String] default_namespace the name of the namespace which is the
484
- # default for the xml being produced; this is specified by the element
485
- # declaration when calling #to_xml recursively.
486
- # @param [String] tag_from_parent the xml tag to use on the element when being
501
+ # @param [String] default_namespace The name of the namespace which is the
502
+ # default for the xml being produced; this is the namespace of the
503
+ # parent
504
+ # @param [String] namespace_override The namespace specified with the element
505
+ # declaration in the parent. Overrides the namespace declaration in the
506
+ # element class itself when calling #to_xml recursively.
507
+ # @param [String] tag_from_parent The xml tag to use on the element when being
487
508
  # called recursively. This lets the parent doc define its own structure.
488
509
  # Otherwise the element uses the tag it has defined for itself. Should only
489
510
  # apply when calling a child HappyMapper element.
@@ -492,7 +513,8 @@ module HappyMapper
492
513
  # HappyMapper object; when called recursively this is going to return
493
514
  # and Nokogiri::XML::Builder object.
494
515
  #
495
- def to_xml(builder = nil,default_namespace = nil,tag_from_parent = nil)
516
+ def to_xml(builder = nil, default_namespace = nil, namespace_override = nil,
517
+ tag_from_parent = nil)
496
518
 
497
519
  #
498
520
  # If to_xml has been called without a passed in builder instance that
@@ -539,7 +561,7 @@ module HappyMapper
539
561
  # state that they should be expressed in the output.
540
562
  #
541
563
  if not value.nil? || attribute.options[:state_when_nil]
542
- attribute_namespace = attribute.options[:namespace] || default_namespace
564
+ attribute_namespace = attribute.options[:namespace]
543
565
  [ "#{attribute_namespace ? "#{attribute_namespace}:" : ""}#{attribute.tag}", value ]
544
566
  else
545
567
  []
@@ -576,39 +598,44 @@ module HappyMapper
576
598
  end
577
599
 
578
600
  #
579
- # If the object we are persisting has a namespace declaration we will want
601
+ # If the object we are serializing has a namespace declaration we will want
580
602
  # to use that namespace or we will use the default namespace.
581
603
  # When neither are specifed we are simply using whatever is default to the
582
604
  # builder
583
605
  #
606
+ namespace_for_parent = namespace_override
584
607
  if self.class.respond_to?(:namespace) && self.class.namespace
585
- xml.parent.namespace = builder.doc.root.namespace_definitions.find { |x| x.prefix == self.class.namespace }
586
- elsif default_namespace
587
- xml.parent.namespace = builder.doc.root.namespace_definitions.find { |x| x.prefix == default_namespace }
608
+ namespace_for_parent ||= self.class.namespace
588
609
  end
610
+ namespace_for_parent ||= default_namespace
611
+
612
+ xml.parent.namespace =
613
+ builder.doc.root.namespace_definitions.find { |x| x.prefix == namespace_for_parent }
589
614
 
590
615
 
591
616
  #
592
617
  # When a content has been defined we add the resulting value
593
618
  # the output xml
594
619
  #
595
- if content = self.class.instance_variable_get('@content')
596
-
597
- unless content.options[:read_only]
598
- text_accessor = content.tag || content.name
599
- value = send(text_accessor)
600
-
601
- if on_save_action = content.options[:on_save]
602
- if on_save_action.is_a?(Proc)
603
- value = on_save_action.call(value)
604
- elsif respond_to?(on_save_action)
605
- value = send(on_save_action,value)
620
+ if self.class.instance_variable_defined?('@content')
621
+ if content = self.class.instance_variable_get('@content')
622
+
623
+ unless content.options[:read_only]
624
+ text_accessor = content.tag || content.name
625
+ value = send(text_accessor)
626
+
627
+ if on_save_action = content.options[:on_save]
628
+ if on_save_action.is_a?(Proc)
629
+ value = on_save_action.call(value)
630
+ elsif respond_to?(on_save_action)
631
+ value = send(on_save_action,value)
632
+ end
606
633
  end
634
+
635
+ builder.text(value)
607
636
  end
608
637
 
609
- builder.text(value)
610
638
  end
611
-
612
639
  end
613
640
 
614
641
  #
@@ -674,7 +701,9 @@ module HappyMapper
674
701
  # process should have their contents retrieved and attached
675
702
  # to the builder structure
676
703
  #
677
- item.to_xml(xml,element.options[:namespace],element.options[:tag] || nil)
704
+ item.to_xml(xml, self.class.namespace || default_namespace,
705
+ element.options[:namespace],
706
+ element.options[:tag] || nil)
678
707
 
679
708
  elsif !item.nil?
680
709
 
@@ -733,7 +762,7 @@ module HappyMapper
733
762
  Class.new do
734
763
  include HappyMapper
735
764
  tag name
736
- instance_eval &blk
765
+ instance_eval(&blk)
737
766
  end
738
767
  end
739
768
  end
@@ -64,8 +64,8 @@ module HappyMapper
64
64
  define_attribute_on_class(happymapper_class,attribute)
65
65
  end
66
66
 
67
- element.children.each do |element|
68
- define_element_on_class(happymapper_class,element)
67
+ element.children.each do |child|
68
+ define_element_on_class(happymapper_class,child)
69
69
  end
70
70
 
71
71
  happymapper_class
@@ -111,4 +111,4 @@ module HappyMapper
111
111
 
112
112
  end
113
113
 
114
- end
114
+ end
@@ -23,9 +23,9 @@ module HappyMapper
23
23
  end
24
24
  else
25
25
  target_path = options[:xpath] ? options[:xpath] : xpath(namespace)
26
- node.xpath(target_path, xpath_options).collect do |result|
27
- value = yield(result)
28
- handle_attributes_option(result, value, xpath_options)
26
+ node.xpath(target_path, xpath_options).collect do |item|
27
+ value = yield(item)
28
+ handle_attributes_option(item, value, xpath_options)
29
29
  value
30
30
  end
31
31
  end
@@ -109,12 +109,12 @@ module HappyMapper
109
109
  Time.parse(value.to_s) rescue Time.at(value.to_i)
110
110
  end
111
111
 
112
- register_type Date do |value|
113
- Date.parse(value.to_s)
112
+ register_type DateTime do |value|
113
+ DateTime.parse(value.to_s) if value && !value.empty?
114
114
  end
115
115
 
116
- register_type DateTime do |value|
117
- DateTime.parse(value.to_s)
116
+ register_type Date do |value|
117
+ Date.parse(value.to_s) if value && !value.empty?
118
118
  end
119
119
 
120
120
  register_type Boolean do |value|
@@ -137,4 +137,4 @@ module HappyMapper
137
137
 
138
138
  end
139
139
 
140
- end
140
+ end
@@ -1,3 +1,3 @@
1
1
  module HappyMapper
2
- VERSION = "0.5.9"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -0,0 +1 @@
1
+ <address><street>Milchstrasse</street><housenumber>23</housenumber></address>
@@ -5,7 +5,7 @@ describe HappyMapper::Attribute do
5
5
  describe "initialization" do
6
6
  it 'accepts :default option' do
7
7
  attr = described_class.new(:foo, String, :default => 'foobar')
8
- attr.default.should == 'foobar'
8
+ expect(attr.default).to eq('foobar')
9
9
  end
10
10
  end
11
11
 
@@ -12,61 +12,61 @@ describe HappyMapper::Item do
12
12
  end
13
13
 
14
14
  it "should accept a name" do
15
- @item.name.should == 'foo'
15
+ expect(@item.name).to eq('foo')
16
16
  end
17
17
 
18
18
  it 'should accept a type' do
19
- @item.type.should == String
19
+ expect(@item.type).to eq(String)
20
20
  end
21
21
 
22
22
  it 'should accept :tag as an option' do
23
- @item.tag.should == 'foobar'
23
+ expect(@item.tag).to eq('foobar')
24
24
  end
25
25
 
26
26
  it "should have a method_name" do
27
- @item.method_name.should == 'foo'
27
+ expect(@item.method_name).to eq('foo')
28
28
  end
29
29
  end
30
30
 
31
31
  describe "#constant" do
32
32
  it "should just use type if constant" do
33
33
  item = HappyMapper::Item.new(:foo, String)
34
- item.constant.should == String
34
+ expect(item.constant).to eq(String)
35
35
  end
36
36
 
37
37
  it "should convert string type to constant" do
38
38
  item = HappyMapper::Item.new(:foo, 'String')
39
- item.constant.should == String
39
+ expect(item.constant).to eq(String)
40
40
  end
41
41
 
42
42
  it "should convert string with :: to constant" do
43
43
  item = HappyMapper::Item.new(:foo, 'Foo::Bar')
44
- item.constant.should == Foo::Bar
44
+ expect(item.constant).to eq(Foo::Bar)
45
45
  end
46
46
  end
47
47
 
48
48
  describe "#method_name" do
49
49
  it "should convert dashes to underscores" do
50
50
  item = HappyMapper::Item.new(:'foo-bar', String, :tag => 'foobar')
51
- item.method_name.should == 'foo_bar'
51
+ expect(item.method_name).to eq('foo_bar')
52
52
  end
53
53
  end
54
54
 
55
55
  describe "#xpath" do
56
56
  it "should default to tag" do
57
57
  item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
58
- item.xpath.should == 'foobar'
58
+ expect(item.xpath).to eq('foobar')
59
59
  end
60
60
 
61
61
  it "should prepend with .// if options[:deep] true" do
62
62
  item = HappyMapper::Item.new(:foo, String, :tag => 'foobar', :deep => true)
63
- item.xpath.should == './/foobar'
63
+ expect(item.xpath).to eq('.//foobar')
64
64
  end
65
65
 
66
66
  it "should prepend namespace if namespace exists" do
67
67
  item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
68
68
  item.namespace = 'v2'
69
- item.xpath.should == 'v2:foobar'
69
+ expect(item.xpath).to eq('v2:foobar')
70
70
  end
71
71
  end
72
72
 
@@ -74,42 +74,63 @@ describe HappyMapper::Item do
74
74
  it "should work with Strings" do
75
75
  item = HappyMapper::Item.new(:foo, String)
76
76
  [21, '21'].each do |a|
77
- item.typecast(a).should == '21'
77
+ expect(item.typecast(a)).to eq('21')
78
78
  end
79
79
  end
80
80
 
81
81
  it "should work with Integers" do
82
82
  item = HappyMapper::Item.new(:foo, Integer)
83
83
  [21, 21.0, '21'].each do |a|
84
- item.typecast(a).should == 21
84
+ expect(item.typecast(a)).to eq(21)
85
85
  end
86
86
  end
87
87
 
88
88
  it "should work with Floats" do
89
89
  item = HappyMapper::Item.new(:foo, Float)
90
90
  [21, 21.0, '21'].each do |a|
91
- item.typecast(a).should == 21.0
91
+ expect(item.typecast(a)).to eq(21.0)
92
92
  end
93
93
  end
94
94
 
95
95
  it "should work with Times" do
96
96
  item = HappyMapper::Item.new(:foo, Time)
97
- item.typecast('2000-01-01 01:01:01.123456').should == Time.local(2000, 1, 1, 1, 1, 1, 123456)
97
+ expect(item.typecast('2000-01-01 01:01:01.123456')).to eq(Time.local(2000, 1, 1, 1, 1, 1, 123456))
98
98
  end
99
99
 
100
100
  it "should work with Dates" do
101
101
  item = HappyMapper::Item.new(:foo, Date)
102
- item.typecast('2000-01-01').should == Date.new(2000, 1, 1)
102
+ expect(item.typecast('2000-01-01')).to eq(Date.new(2000, 1, 1))
103
+ end
104
+
105
+ it "should handle nil Dates" do
106
+ item = HappyMapper::Item.new(:foo, Date)
107
+ expect(item.typecast(nil)).to eq(nil)
108
+ end
109
+
110
+ it "should handle empty string Dates" do
111
+ item = HappyMapper::Item.new(:foo, Date)
112
+ expect(item.typecast("")).to eq(nil)
103
113
  end
104
114
 
105
115
  it "should work with DateTimes" do
106
116
  item = HappyMapper::Item.new(:foo, DateTime)
107
- item.typecast('2000-01-01 00:00:00').should == DateTime.new(2000, 1, 1, 0, 0, 0)
117
+ expect(item.typecast('2000-01-01 00:00:00')).to eq(DateTime.new(2000, 1, 1, 0, 0, 0))
108
118
  end
109
119
 
120
+ it "should handle nil DateTimes" do
121
+ item = HappyMapper::Item.new(:foo, DateTime)
122
+ expect(item.typecast(nil)).to eq(nil)
123
+ end
124
+
125
+ it "should handle empty string DateTimes" do
126
+ item = HappyMapper::Item.new(:foo, DateTime)
127
+ expect(item.typecast("")).to eq(nil)
128
+ end
129
+
130
+
110
131
  it "should work with Boolean" do
111
132
  item = HappyMapper::Item.new(:foo, HappyMapper::Boolean)
112
- item.typecast('false').should == false
133
+ expect(item.typecast('false')).to eq(false)
113
134
  end
114
135
  end
115
136
  end