nokogiri-happymapper 0.3.6 → 0.5.1
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/README.md +521 -0
- data/lib/happymapper.rb +493 -43
- data/lib/happymapper/item.rb +61 -8
- data/spec/fixtures/ambigous_items.xml +1 -1
- data/spec/fixtures/atom.xml +19 -0
- data/spec/fixtures/inagy.xml +85 -0
- data/spec/fixtures/optional_attributes.xml +6 -0
- data/spec/fixtures/subclass_namespace.xml +50 -0
- data/spec/happymapper_spec.rb +220 -6
- data/spec/happymapper_to_xml_namespaces_spec.rb +196 -0
- data/spec/happymapper_to_xml_spec.rb +196 -0
- data/spec/ignay_spec.rb +95 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/xpath_spec.rb +88 -0
- metadata +70 -68
- data/README +0 -62
- data/spec/spec.opts +0 -1
data/lib/happymapper/item.rb
CHANGED
@@ -26,14 +26,25 @@ module HappyMapper
|
|
26
26
|
def constant
|
27
27
|
@constant ||= constantize(type)
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
|
+
#
|
31
|
+
# @param [XMLNode] node the xml node that is being parsed
|
32
|
+
# @param [String] namespace the name of the namespace
|
33
|
+
# @param [Hash] xpath_options additional xpath options
|
34
|
+
#
|
30
35
|
def from_xml_node(node, namespace, xpath_options)
|
36
|
+
|
37
|
+
# If the item is defined as a primitive type then cast the value to that type
|
38
|
+
# else if the type is XMLContent then store the xml value
|
39
|
+
# else the type, specified, needs to handle the parsing.
|
40
|
+
#
|
41
|
+
|
31
42
|
if primitive?
|
32
43
|
find(node, namespace, xpath_options) do |n|
|
33
44
|
if n.respond_to?(:content)
|
34
45
|
typecast(n.content)
|
35
46
|
else
|
36
|
-
typecast(n
|
47
|
+
typecast(n)
|
37
48
|
end
|
38
49
|
end
|
39
50
|
elsif constant == XmlContent
|
@@ -42,6 +53,11 @@ module HappyMapper
|
|
42
53
|
n.respond_to?(:to_xml) ? n.to_xml : n.to_s
|
43
54
|
end
|
44
55
|
else
|
56
|
+
|
57
|
+
# When not a primitive type or XMLContent then default to using the
|
58
|
+
# class method #parse of the type class. If the option 'parser' has been
|
59
|
+
# defined then call that method on the type class instead of #parse
|
60
|
+
|
45
61
|
if options[:parser]
|
46
62
|
find(node, namespace, xpath_options) do |n|
|
47
63
|
if n.respond_to?(:content) && !options[:raw]
|
@@ -67,10 +83,12 @@ module HappyMapper
|
|
67
83
|
xpath += './/' if options[:deep]
|
68
84
|
xpath += "#{namespace}:" if namespace
|
69
85
|
xpath += tag
|
70
|
-
#
|
86
|
+
#puts "xpath: #{xpath}"
|
71
87
|
xpath
|
72
88
|
end
|
73
|
-
|
89
|
+
|
90
|
+
# @return [Boolean] true if the type defined for the item is defined in the
|
91
|
+
# list of primite types {Types}.
|
74
92
|
def primitive?
|
75
93
|
Types.include?(constant)
|
76
94
|
end
|
@@ -91,6 +109,17 @@ module HappyMapper
|
|
91
109
|
@method_name ||= name.tr('-', '_')
|
92
110
|
end
|
93
111
|
|
112
|
+
#
|
113
|
+
# When the type of the item is a primitive type, this will convert value specifed
|
114
|
+
# to the particular primitive type. If it fails during this process it will
|
115
|
+
# return the original String value.
|
116
|
+
#
|
117
|
+
# @param [String] value the string value parsed from the XML value that will
|
118
|
+
# be converted to the particular primitive type.
|
119
|
+
#
|
120
|
+
# @return [String,Float,Time,Date,DateTime,Boolean,Integer] the converted value
|
121
|
+
# to the new type.
|
122
|
+
#
|
94
123
|
def typecast(value)
|
95
124
|
return value if value.kind_of?(constant) || value.nil?
|
96
125
|
begin
|
@@ -123,6 +152,14 @@ module HappyMapper
|
|
123
152
|
|
124
153
|
private
|
125
154
|
|
155
|
+
#
|
156
|
+
# Convert any String defined types into their constant version so that
|
157
|
+
# the method #parse or the custom defined parser method would be used.
|
158
|
+
#
|
159
|
+
# @param [String,Constant] type is the name of the class or the constant
|
160
|
+
# for the class.
|
161
|
+
# @return [Constant] the constant of the type
|
162
|
+
#
|
126
163
|
def constantize(type)
|
127
164
|
if type.is_a?(String)
|
128
165
|
names = type.split('::')
|
@@ -152,7 +189,14 @@ module HappyMapper
|
|
152
189
|
|
153
190
|
if element?
|
154
191
|
if options[:single]
|
155
|
-
|
192
|
+
|
193
|
+
result = nil
|
194
|
+
|
195
|
+
if options[:xpath]
|
196
|
+
result = node.xpath(options[:xpath], xpath_options)
|
197
|
+
else
|
198
|
+
result = node.xpath(xpath(namespace), xpath_options)
|
199
|
+
end
|
156
200
|
|
157
201
|
if result
|
158
202
|
value = options[:single] ? yield(result.first) : result.map {|r| yield r }
|
@@ -161,7 +205,10 @@ module HappyMapper
|
|
161
205
|
value
|
162
206
|
end
|
163
207
|
else
|
164
|
-
|
208
|
+
|
209
|
+
target_path = options[:xpath] ? options[:xpath] : xpath(namespace)
|
210
|
+
|
211
|
+
results = node.xpath(target_path, xpath_options).collect do |result|
|
165
212
|
value = yield(result)
|
166
213
|
handle_attributes_option(result, value, xpath_options)
|
167
214
|
value
|
@@ -169,7 +216,13 @@ module HappyMapper
|
|
169
216
|
results
|
170
217
|
end
|
171
218
|
elsif attribute?
|
172
|
-
|
219
|
+
|
220
|
+
if options[:xpath]
|
221
|
+
yield(node.xpath(options[:xpath],xpath_options))
|
222
|
+
else
|
223
|
+
yield(node[tag])
|
224
|
+
end
|
225
|
+
|
173
226
|
else # text node
|
174
227
|
yield(node.children.detect{|c| c.text?})
|
175
228
|
end
|
@@ -177,7 +230,7 @@ module HappyMapper
|
|
177
230
|
|
178
231
|
def handle_attributes_option(result, value, xpath_options)
|
179
232
|
if options[:attributes].is_a?(Hash)
|
180
|
-
result = result.first
|
233
|
+
result = result.first unless result.respond_to?(:attribute_nodes)
|
181
234
|
|
182
235
|
result.attribute_nodes.each do |xml_attribute|
|
183
236
|
if attribute_options = options[:attributes][xml_attribute.name.to_sym]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
|
3
|
+
<id>tag:www.example.com,2005:/tv_shows</id>
|
4
|
+
<link rel="alternate" type="text/html" href="http://www.example.com"/>
|
5
|
+
<link rel="self" type="application/atom+xml" href="http://www.example.com/tv_shows.atom"/>
|
6
|
+
<title>TV Shows</title>
|
7
|
+
<updated>2011-07-08T13:47:01Z</updated>
|
8
|
+
<entry>
|
9
|
+
<id>tag:www.example.com,2005:TvShow/17</id>
|
10
|
+
<published>2011-07-08T13:47:01Z</published>
|
11
|
+
<updated>2011-07-08T13:47:01Z</updated>
|
12
|
+
<link rel="alternate" type="text/html" href="http://www.example.com/sources/channel-twenty-seven/tv_shows/name-goes-here.atom"/>
|
13
|
+
<title>Name goes here</title>
|
14
|
+
<content type="html">Name goes here (0 episodes)</content>
|
15
|
+
<author>
|
16
|
+
<name>Source URL goes here</name>
|
17
|
+
</author>
|
18
|
+
</entry>
|
19
|
+
</feed>
|
@@ -0,0 +1,85 @@
|
|
1
|
+
<?xml version="1.0" encoding="ISO-8859-1"?>
|
2
|
+
<CatalogTree code="NLD" xmlns="urn:eventis:prodis:onlineapi:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
3
|
+
<Node vodBackOfficeId="69fcb5eb-1020-4053-8fce-6bce030cac5b">
|
4
|
+
<Name>Parent 1</Name>
|
5
|
+
<Description>
|
6
|
+
111#111#120#NPO-BrowseMode.jpg#NPO-ExtendedMode.jpg
|
7
|
+
</Description>
|
8
|
+
<Translations>
|
9
|
+
<Translation Language="en-GB">
|
10
|
+
<Name>Parent 1 en</Name>
|
11
|
+
</Translation>
|
12
|
+
<Translation Language="de-DE">
|
13
|
+
<Name>Parent 1 de</Name>
|
14
|
+
</Translation>
|
15
|
+
</Translations>
|
16
|
+
<Rating>0</Rating>
|
17
|
+
<Adult>false</Adult>
|
18
|
+
<CatalogCode>NLD</CatalogCode>
|
19
|
+
<Position>1</Position>
|
20
|
+
<SubNodes>
|
21
|
+
<Node vodBackOfficeId="27feedad-dead-4721-ba61-c3f9f17d0956">
|
22
|
+
<Name>First</Name>
|
23
|
+
<Translations>
|
24
|
+
<Translation Language="en-GB">
|
25
|
+
<Name>First en</Name>
|
26
|
+
</Translation>
|
27
|
+
</Translations>
|
28
|
+
<Rating>0</Rating>
|
29
|
+
<Adult>false</Adult>
|
30
|
+
<CatalogCode>NLD</CatalogCode>
|
31
|
+
<ParentId>69fcb5eb-1020-4053-8fce-6bce030cac5b</ParentId>
|
32
|
+
<Position>0</Position>
|
33
|
+
<SubNodes>
|
34
|
+
<Node vodBackOfficeId="7edbcc13-2d03-4254-aaf6-307ba553d4fd">
|
35
|
+
<Name>Second</Name>
|
36
|
+
<Translations>
|
37
|
+
<Translation Language="en-GB">
|
38
|
+
<Name>Second en</Name>
|
39
|
+
</Translation>
|
40
|
+
</Translations>
|
41
|
+
<Rating>0</Rating>
|
42
|
+
<Adult>false</Adult>
|
43
|
+
<CatalogCode>NLD</CatalogCode>
|
44
|
+
<ParentId>b17dd22b-c028-44ad-8e75-e676e2619ceb</ParentId>
|
45
|
+
<Position>0</Position>
|
46
|
+
<SubNodes>
|
47
|
+
<Node vodBackOfficeId="27feedad-dead-4721-ba61-c3f9f17d1111">
|
48
|
+
<Name>Third</Name>
|
49
|
+
<Translations>
|
50
|
+
<Translation Language="en-GB">
|
51
|
+
<Name>Third en</Name>
|
52
|
+
</Translation>
|
53
|
+
</Translations>
|
54
|
+
<Rating>0</Rating>
|
55
|
+
<Adult>false</Adult>
|
56
|
+
<CatalogCode>NLD</CatalogCode>
|
57
|
+
<ParentId>69fcb5eb-1020-4053-8fce-6bce030cac5b</ParentId>
|
58
|
+
<Position>0</Position>
|
59
|
+
</Node>
|
60
|
+
</SubNodes>
|
61
|
+
</Node>
|
62
|
+
</SubNodes>
|
63
|
+
</Node>
|
64
|
+
</SubNodes>
|
65
|
+
</Node>
|
66
|
+
<Node vodBackOfficeId="69fcb5eb-1020-4053-8fce-12312312312">
|
67
|
+
<Name>Parent 2</Name>
|
68
|
+
<Description>
|
69
|
+
111#111#120#NPO-BrowseMode.jpg#NPO-ExtendedMode.jpg
|
70
|
+
</Description>
|
71
|
+
<Translations>
|
72
|
+
<Translation Language="en-GB">
|
73
|
+
<Name>Parent 2 en</Name>
|
74
|
+
</Translation>
|
75
|
+
<Translation Language="de-DE">
|
76
|
+
<Name>Parent 2 de</Name>
|
77
|
+
</Translation>
|
78
|
+
</Translations>
|
79
|
+
<Rating>0</Rating>
|
80
|
+
<Adult>false</Adult>
|
81
|
+
<CatalogCode>NLD</CatalogCode>
|
82
|
+
<Position>1</Position>
|
83
|
+
<SubNodes/>
|
84
|
+
</Node>
|
85
|
+
</CatalogTree>
|
@@ -0,0 +1,50 @@
|
|
1
|
+
<article:Article xmlns:article="http://www.wetpaint.com/alfresco/article"
|
2
|
+
xmlns:photo="http://www.wetpaint.com/alfresco/photo"
|
3
|
+
xmlns:gallery="http://www.wetpaint.com/alfresco/gallery"
|
4
|
+
xmlns:alf="http://www.alfresco.org"
|
5
|
+
alf:name="title">
|
6
|
+
<article:title>article title</article:title>
|
7
|
+
<article:text>article text</article:text>
|
8
|
+
<article:publishOptions>
|
9
|
+
<article:author>Nathan</article:author>
|
10
|
+
<article:draft>false</article:draft>
|
11
|
+
<article:scheduledDay>2011-01-14</article:scheduledDay>
|
12
|
+
<article:scheduledTime>11:31:45.0</article:scheduledTime>
|
13
|
+
<article:publishDisplayDay>2011-01-14</article:publishDisplayDay>
|
14
|
+
<article:publishDisplayTime>11:31:45.0</article:publishDisplayTime>
|
15
|
+
<article:createdDay>2011-01-14</article:createdDay>
|
16
|
+
<article:createdTime>11:52:24.0</article:createdTime>
|
17
|
+
</article:publishOptions>
|
18
|
+
|
19
|
+
<photo:Photo>
|
20
|
+
<photo:title>photo title</photo:title>
|
21
|
+
<photo:publishOptions>
|
22
|
+
<photo:author>Stephanie</photo:author>
|
23
|
+
<photo:draft>false</photo:draft>
|
24
|
+
<photo:scheduledDay>2011-01-13</photo:scheduledDay>
|
25
|
+
<photo:scheduledTime>15:47:30.0</photo:scheduledTime>
|
26
|
+
<photo:publishDisplayDay>2011-01-13</photo:publishDisplayDay>
|
27
|
+
<photo:publishDisplayTime>15:47:30.0</photo:publishDisplayTime>
|
28
|
+
<photo:createdDay>2011-01-13</photo:createdDay>
|
29
|
+
<photo:createdTime>15:51:47.0</photo:createdTime>
|
30
|
+
</photo:publishOptions>
|
31
|
+
</photo:Photo>
|
32
|
+
|
33
|
+
<gallery:Gallery>
|
34
|
+
<gallery:title>gallery title</gallery:title>
|
35
|
+
<photo:Photo>
|
36
|
+
<photo:title>photo title</photo:title>
|
37
|
+
<photo:publishOptions>
|
38
|
+
<photo:author>Stephanie</photo:author>
|
39
|
+
<photo:draft>false</photo:draft>
|
40
|
+
<photo:scheduledDay>2011-01-13</photo:scheduledDay>
|
41
|
+
<photo:scheduledTime>15:47:30.0</photo:scheduledTime>
|
42
|
+
<photo:publishDisplayDay>2011-01-13</photo:publishDisplayDay>
|
43
|
+
<photo:publishDisplayTime>15:47:30.0</photo:publishDisplayTime>
|
44
|
+
<photo:createdDay>2011-01-13</photo:createdDay>
|
45
|
+
<photo:createdTime>15:51:47.0</photo:createdTime>
|
46
|
+
</photo:publishOptions>
|
47
|
+
</photo:Photo>
|
48
|
+
</gallery:Gallery>
|
49
|
+
|
50
|
+
</article:Article>
|
data/spec/happymapper_spec.rb
CHANGED
@@ -69,6 +69,24 @@ module Analytics
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
+
module Atom
|
73
|
+
class Feed
|
74
|
+
include HappyMapper
|
75
|
+
tag 'feed'
|
76
|
+
|
77
|
+
attribute :xmlns, String, :single => true
|
78
|
+
element :id, String, :single => true
|
79
|
+
element :title, String, :single => true
|
80
|
+
element :updated, DateTime, :single => true
|
81
|
+
element :link, String, :single => false, :attributes => {
|
82
|
+
:rel => String,
|
83
|
+
:type => String,
|
84
|
+
:href => String
|
85
|
+
}
|
86
|
+
# has_many :entries, Entry # nothing interesting in the entries
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
72
90
|
class Address
|
73
91
|
include HappyMapper
|
74
92
|
|
@@ -82,7 +100,7 @@ end
|
|
82
100
|
|
83
101
|
class Feature
|
84
102
|
include HappyMapper
|
85
|
-
element :name, String, :
|
103
|
+
element :name, String, :xpath => './/text()'
|
86
104
|
end
|
87
105
|
|
88
106
|
class FeatureBullet
|
@@ -286,7 +304,7 @@ class Country
|
|
286
304
|
include HappyMapper
|
287
305
|
|
288
306
|
attribute :code, String
|
289
|
-
|
307
|
+
content :name, String
|
290
308
|
end
|
291
309
|
|
292
310
|
class Address
|
@@ -423,6 +441,103 @@ module AmbigousItems
|
|
423
441
|
end
|
424
442
|
end
|
425
443
|
|
444
|
+
class PublishOptions
|
445
|
+
include HappyMapper
|
446
|
+
|
447
|
+
tag 'publishOptions'
|
448
|
+
|
449
|
+
element :author, String, :tag => 'author'
|
450
|
+
|
451
|
+
element :draft, Boolean, :tag => 'draft'
|
452
|
+
element :scheduled_day, String, :tag => 'scheduledDay'
|
453
|
+
element :scheduled_time, String, :tag => 'scheduledTime'
|
454
|
+
element :published_day, String, :tag => 'publishDisplayDay'
|
455
|
+
element :published_time, String, :tag => 'publishDisplayTime'
|
456
|
+
element :created_day, String, :tag => 'publishDisplayDay'
|
457
|
+
element :created_time, String, :tag => 'publishDisplayTime'
|
458
|
+
|
459
|
+
end
|
460
|
+
|
461
|
+
class Article
|
462
|
+
include HappyMapper
|
463
|
+
|
464
|
+
tag 'Article'
|
465
|
+
namespace 'article'
|
466
|
+
|
467
|
+
attr_writer :xml_value
|
468
|
+
|
469
|
+
element :title, String
|
470
|
+
element :text, String
|
471
|
+
has_many :photos, 'Photo', :tag => 'Photo', :namespace => 'photo', :xpath => '/article:Article'
|
472
|
+
has_many :galleries, 'Gallery', :tag => 'Gallery', :namespace => 'gallery'
|
473
|
+
|
474
|
+
element :publish_options, PublishOptions, :tag => 'publishOptions', :namespace => 'article'
|
475
|
+
|
476
|
+
end
|
477
|
+
|
478
|
+
class PartiallyBadArticle
|
479
|
+
include HappyMapper
|
480
|
+
|
481
|
+
attr_writer :xml_value
|
482
|
+
|
483
|
+
tag 'Article'
|
484
|
+
namespace 'article'
|
485
|
+
|
486
|
+
element :title, String
|
487
|
+
element :text, String
|
488
|
+
has_many :photos, 'Photo', :tag => 'Photo', :namespace => 'photo', :xpath => '/article:Article'
|
489
|
+
has_many :videos, 'Video', :tag => 'Video', :namespace => 'video'
|
490
|
+
|
491
|
+
element :publish_options, PublishOptions, :tag => 'publishOptions', :namespace => 'article'
|
492
|
+
|
493
|
+
end
|
494
|
+
|
495
|
+
class Photo
|
496
|
+
include HappyMapper
|
497
|
+
|
498
|
+
tag 'Photo'
|
499
|
+
namespace 'photo'
|
500
|
+
|
501
|
+
attr_writer :xml_value
|
502
|
+
|
503
|
+
element :title, String
|
504
|
+
element :publish_options, PublishOptions, :tag => 'publishOptions', :namespace => 'photo'
|
505
|
+
|
506
|
+
end
|
507
|
+
|
508
|
+
class Gallery
|
509
|
+
include HappyMapper
|
510
|
+
|
511
|
+
tag 'Gallery'
|
512
|
+
namespace 'gallery'
|
513
|
+
|
514
|
+
attr_writer :xml_value
|
515
|
+
|
516
|
+
element :title, String
|
517
|
+
|
518
|
+
end
|
519
|
+
|
520
|
+
class Video
|
521
|
+
include HappyMapper
|
522
|
+
|
523
|
+
tag 'Video'
|
524
|
+
namespace 'video'
|
525
|
+
|
526
|
+
attr_writer :xml_value
|
527
|
+
|
528
|
+
element :title, String
|
529
|
+
element :publish_options, PublishOptions, :tag => 'publishOptions', :namespace => 'video'
|
530
|
+
|
531
|
+
end
|
532
|
+
|
533
|
+
class OptionalAttribute
|
534
|
+
include HappyMapper
|
535
|
+
tag 'address'
|
536
|
+
|
537
|
+
attribute :street, String
|
538
|
+
end
|
539
|
+
|
540
|
+
|
426
541
|
describe HappyMapper do
|
427
542
|
|
428
543
|
describe "being included into another class" do
|
@@ -486,7 +601,7 @@ describe HappyMapper do
|
|
486
601
|
element = @klass.elements.first
|
487
602
|
element.name.should == 'user'
|
488
603
|
element.type.should == User
|
489
|
-
element.options[:single]
|
604
|
+
element.options[:single].should == true
|
490
605
|
end
|
491
606
|
|
492
607
|
it "should allow has many association" do
|
@@ -494,7 +609,7 @@ describe HappyMapper do
|
|
494
609
|
element = @klass.elements.first
|
495
610
|
element.name.should == 'users'
|
496
611
|
element.type.should == User
|
497
|
-
element.options[:single]
|
612
|
+
element.options[:single].should == false
|
498
613
|
end
|
499
614
|
|
500
615
|
it "should default tag name to lowercase class" do
|
@@ -585,6 +700,12 @@ describe HappyMapper do
|
|
585
700
|
address.country.code.should == 'de'
|
586
701
|
end
|
587
702
|
|
703
|
+
it "should treat Nokogiri::XML::Document as root" do
|
704
|
+
doc = Nokogiri::XML(fixture_file('address.xml'))
|
705
|
+
address = Address.parse(doc)
|
706
|
+
address.class.should == Address
|
707
|
+
end
|
708
|
+
|
588
709
|
it "should parse xml with default namespace (amazon)" do
|
589
710
|
file_contents = fixture_file('pita.xml')
|
590
711
|
items = PITA::Items.parse(file_contents, :single => true)
|
@@ -611,6 +732,23 @@ describe HappyMapper do
|
|
611
732
|
first.current_condition.icon.should == 'http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif'
|
612
733
|
end
|
613
734
|
|
735
|
+
it "parses xml with attributes of elements that aren't :single => true" do
|
736
|
+
feed = Atom::Feed.parse(fixture_file('atom.xml'))
|
737
|
+
feed.link.first.href.should == 'http://www.example.com'
|
738
|
+
feed.link.last.href.should == 'http://www.example.com/tv_shows.atom'
|
739
|
+
end
|
740
|
+
|
741
|
+
it "returns nil rather than empty array for absent values when :single => true" do
|
742
|
+
address = Address.parse('<?xml version="1.0" encoding="UTF-8"?><foo/>', :single => true)
|
743
|
+
address.should be_nil
|
744
|
+
end
|
745
|
+
|
746
|
+
it "should return same result for absent values when :single => true, regardless of :in_groups_of" do
|
747
|
+
addr1 = Address.parse('<?xml version="1.0" encoding="UTF-8"?><foo/>', :single => true)
|
748
|
+
addr2 = Address.parse('<?xml version="1.0" encoding="UTF-8"?><foo/>', :single => true, :in_groups_of => 10)
|
749
|
+
addr1.should == addr2
|
750
|
+
end
|
751
|
+
|
614
752
|
it "should parse xml with nested elements" do
|
615
753
|
radars = Radar.parse(fixture_file('radar.xml'))
|
616
754
|
first = radars[0]
|
@@ -768,6 +906,25 @@ describe HappyMapper do
|
|
768
906
|
l = Location.parse(fixture_file('lastfm.xml'))
|
769
907
|
l.first.latitude.should == "51.53469"
|
770
908
|
end
|
909
|
+
|
910
|
+
describe "Parse optional attributes" do
|
911
|
+
|
912
|
+
it "should parse an empty String as empty" do
|
913
|
+
a = OptionalAttribute.parse(fixture_file('optional_attributes.xml'))
|
914
|
+
a[0].street.should == ""
|
915
|
+
end
|
916
|
+
|
917
|
+
it "should parse a String with value" do
|
918
|
+
a = OptionalAttribute.parse(fixture_file('optional_attributes.xml'))
|
919
|
+
a[1].street.should == "Milchstrasse"
|
920
|
+
end
|
921
|
+
|
922
|
+
it "should parse a String with value" do
|
923
|
+
a = OptionalAttribute.parse(fixture_file('optional_attributes.xml'))
|
924
|
+
a[2].street.should be_nil
|
925
|
+
end
|
926
|
+
|
927
|
+
end
|
771
928
|
|
772
929
|
describe 'Xml Content' do
|
773
930
|
before(:each) do
|
@@ -789,8 +946,65 @@ describe HappyMapper do
|
|
789
946
|
end
|
790
947
|
|
791
948
|
it "should parse ambigous items" do
|
792
|
-
items = AmbigousItems::Item.parse(fixture_file('ambigous_items.xml'),
|
793
|
-
:xpath => '/ambigous/my-items')
|
949
|
+
items = AmbigousItems::Item.parse(fixture_file('ambigous_items.xml'), :xpath => '/ambigous/my-items')
|
794
950
|
items.map(&:name).should == %w(first second third).map{|s| "My #{s} item" }
|
795
951
|
end
|
952
|
+
|
953
|
+
|
954
|
+
context Article do
|
955
|
+
it "should parse the publish options for Article and Photo" do
|
956
|
+
@article.title.should_not be_nil
|
957
|
+
@article.text.should_not be_nil
|
958
|
+
@article.photos.should_not be_nil
|
959
|
+
@article.photos.first.title.should_not be_nil
|
960
|
+
end
|
961
|
+
|
962
|
+
it "should parse the publish options for Article" do
|
963
|
+
@article.publish_options.should_not be_nil
|
964
|
+
end
|
965
|
+
|
966
|
+
it "should parse the publish options for Photo" do
|
967
|
+
@article.photos.first.publish_options.should_not be_nil
|
968
|
+
end
|
969
|
+
|
970
|
+
it "should only find only items at the parent level" do
|
971
|
+
@article.photos.length.should == 1
|
972
|
+
end
|
973
|
+
|
974
|
+
before(:all) do
|
975
|
+
@article = Article.parse(fixture_file('subclass_namespace.xml'))
|
976
|
+
end
|
977
|
+
|
978
|
+
end
|
979
|
+
|
980
|
+
context "Namespace is missing because an optional element that uses it is not present" do
|
981
|
+
it "should parse successfully" do
|
982
|
+
@article = PartiallyBadArticle.parse(fixture_file('subclass_namespace.xml'))
|
983
|
+
@article.should_not be_nil
|
984
|
+
@article.title.should_not be_nil
|
985
|
+
@article.text.should_not be_nil
|
986
|
+
@article.photos.should_not be_nil
|
987
|
+
@article.photos.first.title.should_not be_nil
|
988
|
+
end
|
989
|
+
end
|
990
|
+
|
991
|
+
|
992
|
+
describe "with limit option" do
|
993
|
+
it "should return results with limited size: 6" do
|
994
|
+
sizes = []
|
995
|
+
posts = Post.parse(fixture_file('posts.xml'), :in_groups_of => 6) do |a|
|
996
|
+
sizes << a.size
|
997
|
+
end
|
998
|
+
sizes.should == [6, 6, 6, 2]
|
999
|
+
end
|
1000
|
+
|
1001
|
+
it "should return results with limited size: 10" do
|
1002
|
+
sizes = []
|
1003
|
+
posts = Post.parse(fixture_file('posts.xml'), :in_groups_of => 10) do |a|
|
1004
|
+
sizes << a.size
|
1005
|
+
end
|
1006
|
+
sizes.should == [10, 10]
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
|
796
1010
|
end
|