nokogiri-happymapper 0.5.9 → 0.6.0

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