blockscore-happymapper 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +605 -0
  4. data/lib/happymapper.rb +754 -0
  5. data/lib/happymapper/anonymous_mapper.rb +107 -0
  6. data/lib/happymapper/attribute.rb +21 -0
  7. data/lib/happymapper/element.rb +53 -0
  8. data/lib/happymapper/item.rb +156 -0
  9. data/lib/happymapper/supported_types.rb +137 -0
  10. data/lib/happymapper/text_node.rb +7 -0
  11. data/lib/happymapper/version.rb +3 -0
  12. data/spec/attribute_default_value_spec.rb +47 -0
  13. data/spec/attributes_spec.rb +33 -0
  14. data/spec/fixtures/address.xml +9 -0
  15. data/spec/fixtures/ambigous_items.xml +22 -0
  16. data/spec/fixtures/analytics.xml +61 -0
  17. data/spec/fixtures/analytics_profile.xml +127 -0
  18. data/spec/fixtures/atom.xml +19 -0
  19. data/spec/fixtures/commit.xml +52 -0
  20. data/spec/fixtures/current_weather.xml +89 -0
  21. data/spec/fixtures/current_weather_missing_elements.xml +18 -0
  22. data/spec/fixtures/default_namespace_combi.xml +6 -0
  23. data/spec/fixtures/dictionary.xml +20 -0
  24. data/spec/fixtures/family_tree.xml +21 -0
  25. data/spec/fixtures/inagy.xml +85 -0
  26. data/spec/fixtures/lastfm.xml +355 -0
  27. data/spec/fixtures/multiple_namespaces.xml +170 -0
  28. data/spec/fixtures/multiple_primitives.xml +5 -0
  29. data/spec/fixtures/optional_attributes.xml +6 -0
  30. data/spec/fixtures/pita.xml +133 -0
  31. data/spec/fixtures/posts.xml +23 -0
  32. data/spec/fixtures/product_default_namespace.xml +18 -0
  33. data/spec/fixtures/product_no_namespace.xml +10 -0
  34. data/spec/fixtures/product_single_namespace.xml +10 -0
  35. data/spec/fixtures/quarters.xml +19 -0
  36. data/spec/fixtures/radar.xml +21 -0
  37. data/spec/fixtures/set_config_options.xml +3 -0
  38. data/spec/fixtures/statuses.xml +422 -0
  39. data/spec/fixtures/subclass_namespace.xml +50 -0
  40. data/spec/fixtures/wrapper.xml +11 -0
  41. data/spec/happymapper/attribute_spec.rb +10 -0
  42. data/spec/happymapper/element_spec.rb +9 -0
  43. data/spec/happymapper/item_spec.rb +114 -0
  44. data/spec/happymapper/text_node_spec.rb +9 -0
  45. data/spec/happymapper_parse_spec.rb +99 -0
  46. data/spec/happymapper_spec.rb +1096 -0
  47. data/spec/has_many_empty_array_spec.rb +42 -0
  48. data/spec/ignay_spec.rb +87 -0
  49. data/spec/inheritance_spec.rb +105 -0
  50. data/spec/mixed_namespaces_spec.rb +58 -0
  51. data/spec/parse_with_object_to_update_spec.rb +108 -0
  52. data/spec/spec_helper.rb +8 -0
  53. data/spec/to_xml_spec.rb +197 -0
  54. data/spec/to_xml_with_namespaces_spec.rb +223 -0
  55. data/spec/wilcard_tag_name_spec.rb +94 -0
  56. data/spec/wrap_spec.rb +80 -0
  57. data/spec/xpath_spec.rb +86 -0
  58. metadata +183 -0
@@ -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>
@@ -0,0 +1,11 @@
1
+ <root attr1="somevalue">
2
+ <name>myname</name>
3
+ <mywraptag>
4
+ <description>some description</description>
5
+ <subclass myattr='attrvalue'>
6
+ <item>item1</item>
7
+ <item>item2</item>
8
+ </subclass>
9
+ </mywraptag>
10
+ <number>12345</number>
11
+ </root>
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe HappyMapper::Attribute do
4
+ describe 'initialization' do
5
+ it 'accepts :default option' do
6
+ attr = described_class.new(:foo, String, default: 'foobar')
7
+ attr.default.should == 'foobar'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe HappyMapper::Element do
4
+ describe 'initialization' do
5
+ before do
6
+ @attr = HappyMapper::Element.new(:foo, String)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ module Foo
4
+ class Bar; end
5
+ end
6
+
7
+ describe HappyMapper::Item do
8
+ describe 'new instance' do
9
+ before do
10
+ @item = HappyMapper::Item.new(:foo, String, tag: 'foobar')
11
+ end
12
+
13
+ it 'should accept a name' do
14
+ @item.name.should == 'foo'
15
+ end
16
+
17
+ it 'should accept a type' do
18
+ @item.type.should == String
19
+ end
20
+
21
+ it 'should accept :tag as an option' do
22
+ @item.tag.should == 'foobar'
23
+ end
24
+
25
+ it 'should have a method_name' do
26
+ @item.method_name.should == 'foo'
27
+ end
28
+ end
29
+
30
+ describe '#constant' do
31
+ it 'should just use type if constant' do
32
+ item = HappyMapper::Item.new(:foo, String)
33
+ item.constant.should == String
34
+ end
35
+
36
+ it 'should convert string type to constant' do
37
+ item = HappyMapper::Item.new(:foo, 'String')
38
+ item.constant.should == String
39
+ end
40
+
41
+ it 'should convert string with :: to constant' do
42
+ item = HappyMapper::Item.new(:foo, 'Foo::Bar')
43
+ item.constant.should == Foo::Bar
44
+ end
45
+ end
46
+
47
+ describe '#method_name' do
48
+ it 'should convert dashes to underscores' do
49
+ item = HappyMapper::Item.new(:'foo-bar', String, tag: 'foobar')
50
+ item.method_name.should == 'foo_bar'
51
+ end
52
+ end
53
+
54
+ describe '#xpath' do
55
+ it 'should default to tag' do
56
+ item = HappyMapper::Item.new(:foo, String, tag: 'foobar')
57
+ item.xpath.should == 'foobar'
58
+ end
59
+
60
+ it 'should prepend with .// if options[:deep] true' do
61
+ item = HappyMapper::Item.new(:foo, String, tag: 'foobar', deep: true)
62
+ item.xpath.should == './/foobar'
63
+ end
64
+
65
+ it 'should prepend namespace if namespace exists' do
66
+ item = HappyMapper::Item.new(:foo, String, tag: 'foobar')
67
+ item.namespace = 'v2'
68
+ item.xpath.should == 'v2:foobar'
69
+ end
70
+ end
71
+
72
+ describe 'typecasting' do
73
+ it 'should work with Strings' do
74
+ item = HappyMapper::Item.new(:foo, String)
75
+ [21, '21'].each do |a|
76
+ item.typecast(a).should == '21'
77
+ end
78
+ end
79
+
80
+ it 'should work with Integers' do
81
+ item = HappyMapper::Item.new(:foo, Integer)
82
+ [21, 21.0, '21'].each do |a|
83
+ item.typecast(a).should == 21
84
+ end
85
+ end
86
+
87
+ it 'should work with Floats' do
88
+ item = HappyMapper::Item.new(:foo, Float)
89
+ [21, 21.0, '21'].each do |a|
90
+ item.typecast(a).should == 21.0
91
+ end
92
+ end
93
+
94
+ it 'should work with Times' do
95
+ item = HappyMapper::Item.new(:foo, Time)
96
+ item.typecast('2000-01-01 01:01:01.123456').should == Time.local(2000, 1, 1, 1, 1, 1, 123_456)
97
+ end
98
+
99
+ it 'should work with Dates' do
100
+ item = HappyMapper::Item.new(:foo, Date)
101
+ item.typecast('2000-01-01').should == Date.new(2000, 1, 1)
102
+ end
103
+
104
+ it 'should work with DateTimes' do
105
+ item = HappyMapper::Item.new(:foo, DateTime)
106
+ item.typecast('2000-01-01 00:00:00').should == DateTime.new(2000, 1, 1, 0, 0, 0)
107
+ end
108
+
109
+ it 'should work with Boolean' do
110
+ item = HappyMapper::Item.new(:foo, HappyMapper::Boolean)
111
+ item.typecast('false').should == false
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe HappyMapper::Attribute do
4
+ describe 'initialization' do
5
+ before do
6
+ @attr = HappyMapper::TextNode.new(:foo, String)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe HappyMapper do
4
+ context '.parse' do
5
+ context 'on a single root node' do
6
+ subject { described_class.parse fixture_file('address.xml') }
7
+
8
+ it 'should parse child elements' do
9
+ subject.street.should == 'Milchstrasse'
10
+ subject.housenumber.should == '23'
11
+ subject.postcode.should == '26131'
12
+ subject.city.should == 'Oldenburg'
13
+ end
14
+
15
+ it 'should not create a content entry when the xml contents no text content' do
16
+ subject.should_not respond_to :content
17
+ end
18
+
19
+ context 'child elements with attributes' do
20
+ it 'should parse the attributes' do
21
+ subject.country.code.should == 'de'
22
+ end
23
+
24
+ it 'should parse the content' do
25
+ subject.country.content.should == 'Germany'
26
+ end
27
+ end
28
+ end
29
+
30
+ context 'element names with special characters' do
31
+ subject { described_class.parse fixture_file('ambigous_items.xml') }
32
+
33
+ it 'should create accessor methods with similar names' do
34
+ subject.my_items.item.should be_kind_of Array
35
+ end
36
+ end
37
+
38
+ context 'element names with camelCased elements and Capital Letters' do
39
+ subject { described_class.parse fixture_file('subclass_namespace.xml') }
40
+
41
+ it 'should parse the elements and values correctly' do
42
+ subject.title.should == 'article title'
43
+ subject.photo.publish_options.author.should == 'Stephanie'
44
+ subject.gallery.photo.title.should == 'photo title'
45
+ end
46
+ end
47
+
48
+ context 'several elements nested deep' do
49
+ subject { described_class.parse fixture_file('ambigous_items.xml') }
50
+
51
+ it 'should parse the entire relationship' do
52
+ subject.my_items.item.first.item.name.should == 'My first internal item'
53
+ end
54
+ end
55
+
56
+ context 'xml that contains multiple entries' do
57
+ subject { described_class.parse fixture_file('multiple_primitives.xml') }
58
+
59
+ it "should parse the elements as it would a 'has_many'" do
60
+ subject.name.should == 'value'
61
+ subject.image.should == %w(image1 image2)
62
+ end
63
+ end
64
+
65
+ context 'xml with multiple namespaces' do
66
+ subject { described_class.parse fixture_file('subclass_namespace.xml') }
67
+
68
+ it 'should parse the elements an values correctly' do
69
+ subject.title.should == 'article title'
70
+ end
71
+ end
72
+
73
+ context 'after_parse callbacks' do
74
+ module AfterParseSpec
75
+ class Address
76
+ include HappyMapper
77
+ element :street, String
78
+ end
79
+ end
80
+
81
+ after do
82
+ AfterParseSpec::Address.after_parse_callbacks.clear
83
+ end
84
+
85
+ it 'should callback with the newly created object' do
86
+ from_cb = nil
87
+ called = false
88
+ cb1 = proc { |object| from_cb = object }
89
+ cb2 = proc { called = true }
90
+ AfterParseSpec::Address.after_parse(&cb1)
91
+ AfterParseSpec::Address.after_parse(&cb2)
92
+
93
+ object = AfterParseSpec::Address.parse fixture_file('address.xml')
94
+ from_cb.should == object
95
+ called.should == true
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,1096 @@
1
+ require 'spec_helper'
2
+ require 'uri'
3
+
4
+ module Analytics
5
+ class Property
6
+ include HappyMapper
7
+
8
+ tag 'property'
9
+ namespace 'dxp'
10
+ attribute :name, String
11
+ attribute :value, String
12
+ end
13
+
14
+ class Goal
15
+ include HappyMapper
16
+
17
+ # Google Analytics does a dirtry trick where a user with no goals
18
+ # returns a profile without any goals data or the declared namespace
19
+ # which means Nokogiri does not pick up the namespace automatically.
20
+ # To fix this, we manually register the namespace to avoid bad XPath
21
+ # expression. Dirty, but works.
22
+
23
+ register_namespace 'ga', 'http://schemas.google.com/ga/2009'
24
+ namespace 'ga'
25
+
26
+ tag 'goal'
27
+ attribute :active, Boolean
28
+ attribute :name, String
29
+ attribute :number, Integer
30
+ attribute :value, Float
31
+
32
+ def clean_name
33
+ name.gsub(/ga:/, '')
34
+ end
35
+ end
36
+
37
+ class Profile
38
+ include HappyMapper
39
+
40
+ tag 'entry'
41
+ element :title, String
42
+ element :tableId, String, namespace: 'dxp'
43
+
44
+ has_many :properties, Property
45
+ has_many :goals, Goal
46
+ end
47
+
48
+ class Entry
49
+ include HappyMapper
50
+
51
+ tag 'entry'
52
+ element :id, String
53
+ element :updated, DateTime
54
+ element :title, String
55
+ element :table_id, String, namespace: 'dxp', tag: 'tableId'
56
+ has_many :properties, Property
57
+ end
58
+
59
+ class Feed
60
+ include HappyMapper
61
+
62
+ tag 'feed'
63
+ element :id, String
64
+ element :updated, DateTime
65
+ element :title, String
66
+ has_many :entries, Entry
67
+ end
68
+ end
69
+
70
+ module Atom
71
+ class Feed
72
+ include HappyMapper
73
+ tag 'feed'
74
+
75
+ attribute :xmlns, String, single: true
76
+ element :id, String, single: true
77
+ element :title, String, single: true
78
+ element :updated, DateTime, single: true
79
+ element :link, String, single: false, attributes: {
80
+ rel: String,
81
+ type: String,
82
+ href: String
83
+ }
84
+ # has_many :entries, Entry # nothing interesting in the entries
85
+ end
86
+ end
87
+
88
+ class Address
89
+ include HappyMapper
90
+
91
+ tag 'address'
92
+ element :street, String
93
+ element :postcode, String
94
+ element :housenumber, String
95
+ element :city, String
96
+ element :country, String
97
+ end
98
+
99
+ class Feature
100
+ include HappyMapper
101
+ element :name, String, xpath: './/text()'
102
+ end
103
+
104
+ class FeatureBullet
105
+ include HappyMapper
106
+
107
+ tag 'features_bullets'
108
+ has_many :features, Feature
109
+ element :bug, String
110
+ end
111
+
112
+ class Product
113
+ include HappyMapper
114
+
115
+ element :title, String
116
+ has_one :feature_bullets, FeatureBullet
117
+ has_one :address, Address
118
+ end
119
+
120
+ class Rate
121
+ include HappyMapper
122
+ end
123
+
124
+ module FamilySearch
125
+ class AlternateIds
126
+ include HappyMapper
127
+
128
+ tag 'alternateIds'
129
+ has_many :ids, String, tag: 'id'
130
+ end
131
+
132
+ class Information
133
+ include HappyMapper
134
+
135
+ has_one :alternateIds, AlternateIds
136
+ end
137
+
138
+ class Person
139
+ include HappyMapper
140
+
141
+ attribute :version, String
142
+ attribute :modified, Time
143
+ attribute :id, String
144
+ has_one :information, Information
145
+ end
146
+
147
+ class Persons
148
+ include HappyMapper
149
+ has_many :person, Person
150
+ end
151
+
152
+ class FamilyTree
153
+ include HappyMapper
154
+
155
+ tag 'familytree'
156
+ attribute :version, String
157
+ attribute :status_message, String, tag: 'statusMessage'
158
+ attribute :status_code, String, tag: 'statusCode'
159
+ has_one :persons, Persons
160
+ end
161
+ end
162
+
163
+ module FedEx
164
+ class Address
165
+ include HappyMapper
166
+
167
+ tag 'Address'
168
+ namespace 'v2'
169
+ element :city, String, tag: 'City'
170
+ element :state, String, tag: 'StateOrProvinceCode'
171
+ element :zip, String, tag: 'PostalCode'
172
+ element :countrycode, String, tag: 'CountryCode'
173
+ element :residential, Boolean, tag: 'Residential'
174
+ end
175
+
176
+ class Event
177
+ include HappyMapper
178
+
179
+ tag 'Events'
180
+ namespace 'v2'
181
+ element :timestamp, String, tag: 'Timestamp'
182
+ element :eventtype, String, tag: 'EventType'
183
+ element :eventdescription, String, tag: 'EventDescription'
184
+ has_one :address, Address
185
+ end
186
+
187
+ class PackageWeight
188
+ include HappyMapper
189
+
190
+ tag 'PackageWeight'
191
+ namespace 'v2'
192
+ element :units, String, tag: 'Units'
193
+ element :value, Integer, tag: 'Value'
194
+ end
195
+
196
+ class TrackDetails
197
+ include HappyMapper
198
+
199
+ tag 'TrackDetails'
200
+ namespace 'v2'
201
+ element :tracking_number, String, tag: 'TrackingNumber'
202
+ element :status_code, String, tag: 'StatusCode'
203
+ element :status_desc, String, tag: 'StatusDescription'
204
+ element :carrier_code, String, tag: 'CarrierCode'
205
+ element :service_info, String, tag: 'ServiceInfo'
206
+ has_one :weight, PackageWeight, tag: 'PackageWeight'
207
+ element :est_delivery, String, tag: 'EstimatedDeliveryTimestamp'
208
+ has_many :events, Event
209
+ end
210
+
211
+ class Notification
212
+ include HappyMapper
213
+
214
+ tag 'Notifications'
215
+ namespace 'v2'
216
+ element :severity, String, tag: 'Severity'
217
+ element :source, String, tag: 'Source'
218
+ element :code, Integer, tag: 'Code'
219
+ element :message, String, tag: 'Message'
220
+ element :localized_message, String, tag: 'LocalizedMessage'
221
+ end
222
+
223
+ class TransactionDetail
224
+ include HappyMapper
225
+
226
+ tag 'TransactionDetail'
227
+ namespace 'v2'
228
+ element :cust_tran_id, String, tag: 'CustomerTransactionId'
229
+ end
230
+
231
+ class TrackReply
232
+ include HappyMapper
233
+
234
+ tag 'TrackReply'
235
+ namespace 'v2'
236
+ element :highest_severity, String, tag: 'HighestSeverity'
237
+ element :more_data, Boolean, tag: 'MoreData'
238
+ has_many :notifications, Notification, tag: 'Notifications'
239
+ has_many :trackdetails, TrackDetails, tag: 'TrackDetails'
240
+ has_one :tran_detail, TransactionDetail, tab: 'TransactionDetail'
241
+ end
242
+ end
243
+
244
+ class Place
245
+ include HappyMapper
246
+ element :name, String
247
+ end
248
+
249
+ class Radar
250
+ include HappyMapper
251
+ has_many :places, Place, tag: :place
252
+ end
253
+
254
+ class Post
255
+ include HappyMapper
256
+
257
+ attribute :href, String
258
+ attribute :hash, String
259
+ attribute :description, String
260
+ attribute :tag, String
261
+ attribute :time, Time
262
+ attribute :others, Integer
263
+ attribute :extended, String
264
+ end
265
+
266
+ class User
267
+ include HappyMapper
268
+
269
+ element :id, Integer
270
+ element :name, String
271
+ element :screen_name, String
272
+ element :location, String
273
+ element :description, String
274
+ element :profile_image_url, String
275
+ element :url, String
276
+ element :protected, Boolean
277
+ element :followers_count, Integer
278
+ end
279
+
280
+ class Status
281
+ include HappyMapper
282
+
283
+ register_namespace 'fake', 'faka:namespace'
284
+
285
+ element :id, Integer
286
+ element :text, String
287
+ element :created_at, Time
288
+ element :source, String
289
+ element :truncated, Boolean
290
+ element :in_reply_to_status_id, Integer
291
+ element :in_reply_to_user_id, Integer
292
+ element :favorited, Boolean
293
+ element :non_existent, String, tag: 'dummy', namespace: 'fake'
294
+ has_one :user, User
295
+ end
296
+
297
+ class CurrentWeather
298
+ include HappyMapper
299
+
300
+ tag 'ob'
301
+ namespace 'aws'
302
+ element :temperature, Integer, tag: 'temp'
303
+ element :feels_like, Integer, tag: 'feels-like'
304
+ element :current_condition, String, tag: 'current-condition', attributes: { icon: String }
305
+ end
306
+
307
+ class Country
308
+ include HappyMapper
309
+
310
+ attribute :code, String
311
+ content :name, String
312
+ end
313
+
314
+ class State
315
+ include HappyMapper
316
+ end
317
+
318
+ class Address
319
+ include HappyMapper
320
+
321
+ tag 'address'
322
+ element :street, String
323
+ element :postcode, String
324
+ element :housenumber, String
325
+ element :city, String
326
+ has_one :country, Country
327
+ has_one :state, State
328
+ end
329
+
330
+ # for type coercion
331
+ class ProductGroup < String; end
332
+
333
+ module PITA
334
+ class Item
335
+ include HappyMapper
336
+
337
+ tag 'Item' # if you put class in module you need tag
338
+ element :asin, String, tag: 'ASIN'
339
+ element :detail_page_url, URI, tag: 'DetailPageURL', parser: :parse
340
+ element :manufacturer, String, tag: 'Manufacturer', deep: true
341
+ element :point, String, tag: 'point', namespace: 'georss'
342
+ element :product_group, ProductGroup, tag: 'ProductGroup', deep: true, parser: :new, raw: true
343
+ end
344
+
345
+ class Items
346
+ include HappyMapper
347
+
348
+ tag 'Items' # if you put class in module you need tag
349
+ element :total_results, Integer, tag: 'TotalResults'
350
+ element :total_pages, Integer, tag: 'TotalPages'
351
+ has_many :items, Item
352
+ end
353
+ end
354
+
355
+ module GitHub
356
+ class Commit
357
+ include HappyMapper
358
+
359
+ tag 'commit'
360
+ element :url, String
361
+ element :tree, String
362
+ element :message, String
363
+ element :id, String
364
+ element :'committed-date', Date
365
+ end
366
+ end
367
+
368
+ module QuarterTest
369
+ class Quarter
370
+ include HappyMapper
371
+
372
+ element :start, String
373
+ end
374
+
375
+ class Details
376
+ include HappyMapper
377
+
378
+ element :round, Integer
379
+ element :quarter, Integer
380
+ end
381
+
382
+ class Game
383
+ include HappyMapper
384
+
385
+ # in an ideal world, the following elements would all be
386
+ # called 'quarter' with an attribute indicating which quarter
387
+ # it represented, but the refactoring that allows a single class
388
+ # to be used for all these differently named elements is the next
389
+ # best thing
390
+ has_one :details, QuarterTest::Details
391
+ has_one :q1, QuarterTest::Quarter, tag: 'q1'
392
+ has_one :q2, QuarterTest::Quarter, tag: 'q2'
393
+ has_one :q3, QuarterTest::Quarter, tag: 'q3'
394
+ has_one :q4, QuarterTest::Quarter, tag: 'q4'
395
+ end
396
+ end
397
+
398
+ # To check for multiple primitives
399
+ class Artist
400
+ include HappyMapper
401
+
402
+ tag 'artist'
403
+ element :images, String, tag: 'image', single: false
404
+ element :name, String
405
+ end
406
+
407
+ class Location
408
+ include HappyMapper
409
+
410
+ tag 'point'
411
+ namespace 'geo'
412
+ element :latitude, String, tag: 'lat'
413
+ end
414
+
415
+ # Testing the XmlContent type
416
+ module Dictionary
417
+ class Variant
418
+ include HappyMapper
419
+ tag 'var'
420
+ has_xml_content
421
+
422
+ def to_html
423
+ xml_content.gsub('<tag>', '<em>').gsub('</tag>', '</em>')
424
+ end
425
+ end
426
+
427
+ class Definition
428
+ include HappyMapper
429
+
430
+ tag 'def'
431
+ element :text, XmlContent, tag: 'dtext'
432
+ end
433
+
434
+ class Record
435
+ include HappyMapper
436
+
437
+ tag 'record'
438
+ has_many :definitions, Definition
439
+ has_many :variants, Variant, tag: 'var'
440
+ end
441
+ end
442
+
443
+ module AmbigousItems
444
+ class Item
445
+ include HappyMapper
446
+
447
+ tag 'item'
448
+ element :name, String
449
+ element :item, String
450
+ end
451
+ end
452
+
453
+ class PublishOptions
454
+ include HappyMapper
455
+
456
+ tag 'publishOptions'
457
+
458
+ element :author, String, tag: 'author'
459
+
460
+ element :draft, Boolean, tag: 'draft'
461
+ element :scheduled_day, String, tag: 'scheduledDay'
462
+ element :scheduled_time, String, tag: 'scheduledTime'
463
+ element :published_day, String, tag: 'publishDisplayDay'
464
+ element :published_time, String, tag: 'publishDisplayTime'
465
+ element :created_day, String, tag: 'publishDisplayDay'
466
+ element :created_time, String, tag: 'publishDisplayTime'
467
+ end
468
+
469
+ class Article
470
+ include HappyMapper
471
+
472
+ tag 'Article'
473
+ namespace 'article'
474
+
475
+ attr_writer :xml_value
476
+
477
+ element :title, String
478
+ element :text, String
479
+ has_many :photos, 'Photo', tag: 'Photo', namespace: 'photo', xpath: '/article:Article'
480
+ has_many :galleries, 'Gallery', tag: 'Gallery', namespace: 'gallery'
481
+
482
+ element :publish_options, PublishOptions, tag: 'publishOptions', namespace: 'article'
483
+ end
484
+
485
+ class PartiallyBadArticle
486
+ include HappyMapper
487
+
488
+ attr_writer :xml_value
489
+
490
+ tag 'Article'
491
+ namespace 'article'
492
+
493
+ element :title, String
494
+ element :text, String
495
+ has_many :photos, 'Photo', tag: 'Photo', namespace: 'photo', xpath: '/article:Article'
496
+ has_many :videos, 'Video', tag: 'Video', namespace: 'video'
497
+
498
+ element :publish_options, PublishOptions, tag: 'publishOptions', namespace: 'article'
499
+ end
500
+
501
+ class Photo
502
+ include HappyMapper
503
+
504
+ tag 'Photo'
505
+ namespace 'photo'
506
+
507
+ attr_writer :xml_value
508
+
509
+ element :title, String
510
+ element :publish_options, PublishOptions, tag: 'publishOptions', namespace: 'photo'
511
+ end
512
+
513
+ class Gallery
514
+ include HappyMapper
515
+
516
+ tag 'Gallery'
517
+ namespace 'gallery'
518
+
519
+ attr_writer :xml_value
520
+
521
+ element :title, String
522
+ end
523
+
524
+ class Video
525
+ include HappyMapper
526
+
527
+ tag 'Video'
528
+ namespace 'video'
529
+
530
+ attr_writer :xml_value
531
+
532
+ element :title, String
533
+ element :publish_options, PublishOptions, tag: 'publishOptions', namespace: 'video'
534
+ end
535
+
536
+ class OptionalAttribute
537
+ include HappyMapper
538
+ tag 'address'
539
+
540
+ attribute :street, String
541
+ end
542
+
543
+ class DefaultNamespaceCombi
544
+ include HappyMapper
545
+
546
+ register_namespace 'bk', 'urn:loc.gov:books'
547
+ register_namespace 'isbn', 'urn:ISBN:0-395-36341-6'
548
+ register_namespace 'p', 'urn:loc.gov:people'
549
+ namespace 'bk'
550
+
551
+ tag 'book'
552
+
553
+ element :title, String, namespace: 'bk', tag: 'title'
554
+ element :number, String, namespace: 'isbn', tag: 'number'
555
+ element :author, String, namespace: 'p', tag: 'author'
556
+ end
557
+
558
+ describe HappyMapper do
559
+ describe 'being included into another class' do
560
+ before do
561
+ @klass = Class.new do
562
+ include HappyMapper
563
+
564
+ def self.to_s
565
+ 'Boo'
566
+ end
567
+ end
568
+ end
569
+
570
+ class Boo; include HappyMapper end
571
+
572
+ it 'should set attributes to an array' do
573
+ @klass.attributes.should == []
574
+ end
575
+
576
+ it 'should set @elements to a hash' do
577
+ @klass.elements.should == []
578
+ end
579
+
580
+ it 'should allow adding an attribute' do
581
+ lambda do
582
+ @klass.attribute :name, String
583
+ end.should change(@klass, :attributes)
584
+ end
585
+
586
+ it 'should allow adding an attribute containing a dash' do
587
+ lambda do
588
+ @klass.attribute :'bar-baz', String
589
+ end.should change(@klass, :attributes)
590
+ end
591
+
592
+ it 'should be able to get all attributes in array' do
593
+ @klass.attribute :name, String
594
+ @klass.attributes.size.should == 1
595
+ end
596
+
597
+ it 'should allow adding an element' do
598
+ lambda do
599
+ @klass.element :name, String
600
+ end.should change(@klass, :elements)
601
+ end
602
+
603
+ it 'should allow adding an element containing a dash' do
604
+ lambda do
605
+ @klass.element :'bar-baz', String
606
+ end.should change(@klass, :elements)
607
+ end
608
+
609
+ it 'should be able to get all elements in array' do
610
+ @klass.element(:name, String)
611
+ @klass.elements.size.should == 1
612
+ end
613
+
614
+ it 'should allow has one association' do
615
+ @klass.has_one(:user, User)
616
+ element = @klass.elements.first
617
+ element.name.should == 'user'
618
+ element.type.should == User
619
+ element.options[:single].should == true
620
+ end
621
+
622
+ it 'should allow has many association' do
623
+ @klass.has_many(:users, User)
624
+ element = @klass.elements.first
625
+ element.name.should == 'users'
626
+ element.type.should == User
627
+ element.options[:single].should == false
628
+ end
629
+
630
+ it 'should default tag name to lowercase class' do
631
+ @klass.tag_name.should == 'boo'
632
+ end
633
+
634
+ it 'should default tag name of class in modules to the last constant lowercase' do
635
+ module Bar; class Baz; include HappyMapper; end; end
636
+ Bar::Baz.tag_name.should == 'baz'
637
+ end
638
+
639
+ it 'should allow setting tag name' do
640
+ @klass.tag('FooBar')
641
+ @klass.tag_name.should == 'FooBar'
642
+ end
643
+
644
+ it 'should allow setting a namespace' do
645
+ @klass.namespace(namespace = 'boo')
646
+ @klass.namespace.should == namespace
647
+ end
648
+
649
+ it 'should provide #parse' do
650
+ @klass.should respond_to(:parse)
651
+ end
652
+ end
653
+
654
+ describe '#attributes' do
655
+ it 'should only return attributes for the current class' do
656
+ Post.attributes.size.should == 7
657
+ Status.attributes.size.should == 0
658
+ end
659
+ end
660
+
661
+ describe '#elements' do
662
+ it 'should only return elements for the current class' do
663
+ Post.elements.size.should == 0
664
+ Status.elements.size.should == 10
665
+ end
666
+ end
667
+
668
+ describe '#content' do
669
+ it 'should take String as default argument for type' do
670
+ State.content :name
671
+ address = Address.parse(fixture_file('address.xml'))
672
+ address.state.name.should == 'Lower Saxony'
673
+ address.state.name.class == String
674
+ end
675
+
676
+ it 'should work when specific type is provided' do
677
+ Rate.content :value, Float
678
+ Product.has_one :rate, Rate
679
+ product = Product.parse(fixture_file('product_default_namespace.xml'), single: true)
680
+ product.rate.value.should == 120.25
681
+ product.rate.class == Float
682
+ end
683
+ end
684
+
685
+ it 'should parse xml attributes into ruby objects' do
686
+ posts = Post.parse(fixture_file('posts.xml'))
687
+ posts.size.should == 20
688
+ first = posts.first
689
+ first.href.should == 'http://roxml.rubyforge.org/'
690
+ first.hash.should == '19bba2ab667be03a19f67fb67dc56917'
691
+ first.description.should == 'ROXML - Ruby Object to XML Mapping Library'
692
+ first.tag.should == 'ruby xml gems mapping'
693
+ first.time.should == Time.utc(2008, 8, 9, 5, 24, 20)
694
+ first.others.should == 56
695
+ first.extended.should == 'ROXML is a Ruby library designed to make it easier for Ruby developers to work with XML. Using simple annotations, it enables Ruby classes to be custom-mapped to XML. ROXML takes care of the marshalling and unmarshalling of mapped attributes so that developers can focus on building first-class Ruby classes.'
696
+ end
697
+
698
+ it 'should parse xml elements to ruby objcts' do
699
+ statuses = Status.parse(fixture_file('statuses.xml'))
700
+ statuses.size.should == 20
701
+ first = statuses.first
702
+ first.id.should == 882_281_424
703
+ first.created_at.should == Time.utc(2008, 8, 9, 5, 38, 12)
704
+ first.source.should == 'web'
705
+ first.truncated.should be_falsey
706
+ first.in_reply_to_status_id.should == 1234
707
+ first.in_reply_to_user_id.should == 12_345
708
+ first.favorited.should be false
709
+ first.user.id.should == 4243
710
+ first.user.name.should == 'John Nunemaker'
711
+ first.user.screen_name.should == 'jnunemaker'
712
+ first.user.location.should == 'Mishawaka, IN, US'
713
+ first.user.description.should == 'Loves his wife, ruby, notre dame football and iu basketball'
714
+ first.user.profile_image_url.should == 'http://s3.amazonaws.com/twitter_production/profile_images/53781608/Photo_75_normal.jpg'
715
+ first.user.url.should == 'http://addictedtonew.com'
716
+ first.user.protected.should be false
717
+ first.user.followers_count.should == 486
718
+ end
719
+
720
+ it 'should parse xml containing the desired element as root node' do
721
+ address = Address.parse(fixture_file('address.xml'), single: true)
722
+ address.street.should == 'Milchstrasse'
723
+ address.postcode.should == '26131'
724
+ address.housenumber.should == '23'
725
+ address.city.should == 'Oldenburg'
726
+ address.country.class.should == Country
727
+ end
728
+
729
+ it 'should parse text node correctly' do
730
+ address = Address.parse(fixture_file('address.xml'), single: true)
731
+ address.country.name.should == 'Germany'
732
+ address.country.code.should == 'de'
733
+ end
734
+
735
+ it 'should treat Nokogiri::XML::Document as root' do
736
+ doc = Nokogiri::XML(fixture_file('address.xml'))
737
+ address = Address.parse(doc)
738
+ address.class.should == Address
739
+ end
740
+
741
+ it 'should parse xml with default namespace (amazon)' do
742
+ file_contents = fixture_file('pita.xml')
743
+ items = PITA::Items.parse(file_contents, single: true)
744
+ items.total_results.should == 22
745
+ items.total_pages.should == 3
746
+ first = items.items[0]
747
+ second = items.items[1]
748
+ first.asin.should == '0321480791'
749
+ first.point.should == '38.5351715088 -121.7948684692'
750
+ first.detail_page_url.should be_a_kind_of(URI)
751
+ first.detail_page_url.to_s.should == 'http://www.amazon.com/gp/redirect.html%3FASIN=0321480791%26tag=ws%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0321480791%253FSubscriptionId=dontbeaswoosh'
752
+ first.manufacturer.should == 'Addison-Wesley Professional'
753
+ first.product_group.should == '<ProductGroup>Book</ProductGroup>'
754
+ second.asin.should == '047022388X'
755
+ second.manufacturer.should == 'Wrox'
756
+ end
757
+
758
+ it 'should parse xml that has attributes of elements' do
759
+ items = CurrentWeather.parse(fixture_file('current_weather.xml'))
760
+ first = items[0]
761
+ first.temperature.should == 51
762
+ first.feels_like.should == 51
763
+ first.current_condition.should == 'Sunny'
764
+ first.current_condition.icon.should == 'http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif'
765
+ end
766
+
767
+ it "parses xml with attributes of elements that aren't :single => true" do
768
+ feed = Atom::Feed.parse(fixture_file('atom.xml'))
769
+ feed.link.first.href.should == 'http://www.example.com'
770
+ feed.link.last.href.should == 'http://www.example.com/tv_shows.atom'
771
+ end
772
+
773
+ it 'parses xml with optional elements with embedded attributes' do
774
+ expect { CurrentWeather.parse(fixture_file('current_weather_missing_elements.xml')) }.to_not raise_error
775
+ end
776
+
777
+ it 'returns nil rather than empty array for absent values when :single => true' do
778
+ address = Address.parse('<?xml version="1.0" encoding="UTF-8"?><foo/>', single: true)
779
+ address.should be_nil
780
+ end
781
+
782
+ it 'should return same result for absent values when :single => true, regardless of :in_groups_of' do
783
+ addr1 = Address.parse('<?xml version="1.0" encoding="UTF-8"?><foo/>', single: true)
784
+ addr2 = Address.parse('<?xml version="1.0" encoding="UTF-8"?><foo/>', single: true, in_groups_of: 10)
785
+ addr1.should == addr2
786
+ end
787
+
788
+ it 'should parse xml with nested elements' do
789
+ radars = Radar.parse(fixture_file('radar.xml'))
790
+ first = radars[0]
791
+ first.places.size.should == 1
792
+ first.places[0].name.should == 'Store'
793
+ second = radars[1]
794
+ second.places.size.should == 0
795
+ third = radars[2]
796
+ third.places.size.should == 2
797
+ third.places[0].name.should == 'Work'
798
+ third.places[1].name.should == 'Home'
799
+ end
800
+
801
+ it 'should parse xml with element name different to class name' do
802
+ game = QuarterTest::Game.parse(fixture_file('quarters.xml'))
803
+ game.q1.start.should == '4:40:15 PM'
804
+ game.q2.start.should == '5:18:53 PM'
805
+ end
806
+
807
+ it 'should parse xml that has elements with dashes' do
808
+ commit = GitHub::Commit.parse(fixture_file('commit.xml'))
809
+ commit.message.should == 'move commands.rb and helpers.rb into commands/ dir'
810
+ commit.url.should == 'http://github.com/defunkt/github-gem/commit/c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b'
811
+ commit.id.should == 'c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b'
812
+ commit.committed_date.should == Date.parse('2008-03-02T16:45:41-08:00')
813
+ commit.tree.should == '28a1a1ca3e663d35ba8bf07d3f1781af71359b76'
814
+ end
815
+
816
+ it 'should parse xml with no namespace' do
817
+ product = Product.parse(fixture_file('product_no_namespace.xml'), single: true)
818
+ product.title.should == 'A Title'
819
+ product.feature_bullets.bug.should == 'This is a bug'
820
+ product.feature_bullets.features.size.should == 2
821
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
822
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
823
+ end
824
+
825
+ it 'should parse xml with default namespace' do
826
+ product = Product.parse(fixture_file('product_default_namespace.xml'), single: true)
827
+ product.title.should == 'A Title'
828
+ product.feature_bullets.bug.should == 'This is a bug'
829
+ product.feature_bullets.features.size.should == 2
830
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
831
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
832
+ end
833
+
834
+ it 'should parse xml with single namespace' do
835
+ product = Product.parse(fixture_file('product_single_namespace.xml'), single: true)
836
+ product.title.should == 'A Title'
837
+ product.feature_bullets.bug.should == 'This is a bug'
838
+ product.feature_bullets.features.size.should == 2
839
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
840
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
841
+ end
842
+
843
+ it 'should parse xml with multiple namespaces' do
844
+ track = FedEx::TrackReply.parse(fixture_file('multiple_namespaces.xml'))
845
+ track.highest_severity.should == 'SUCCESS'
846
+ track.more_data.should be false
847
+ notification = track.notifications.first
848
+ notification.code.should == 0
849
+ notification.localized_message.should == 'Request was successfully processed.'
850
+ notification.message.should == 'Request was successfully processed.'
851
+ notification.severity.should == 'SUCCESS'
852
+ notification.source.should == 'trck'
853
+ detail = track.trackdetails.first
854
+ detail.carrier_code.should == 'FDXG'
855
+ detail.est_delivery.should == '2009-01-02T00:00:00'
856
+ detail.service_info.should == 'Ground-Package Returns Program-Domestic'
857
+ detail.status_code.should == 'OD'
858
+ detail.status_desc.should == 'On FedEx vehicle for delivery'
859
+ detail.tracking_number.should == '9611018034267800045212'
860
+ detail.weight.units.should == 'LB'
861
+ detail.weight.value.should == 2
862
+ events = detail.events
863
+ events.size.should == 10
864
+ first_event = events[0]
865
+ first_event.eventdescription.should == 'On FedEx vehicle for delivery'
866
+ first_event.eventtype.should == 'OD'
867
+ first_event.timestamp.should == '2009-01-02T06:00:00'
868
+ first_event.address.city.should == 'WICHITA'
869
+ first_event.address.countrycode.should == 'US'
870
+ first_event.address.residential.should be false
871
+ first_event.address.state.should == 'KS'
872
+ first_event.address.zip.should == '67226'
873
+ last_event = events[-1]
874
+ last_event.eventdescription.should == 'In FedEx possession'
875
+ last_event.eventtype.should == 'IP'
876
+ last_event.timestamp.should == '2008-12-27T09:40:00'
877
+ last_event.address.city.should == 'LONGWOOD'
878
+ last_event.address.countrycode.should == 'US'
879
+ last_event.address.residential.should be false
880
+ last_event.address.state.should == 'FL'
881
+ last_event.address.zip.should == '327506398'
882
+ track.tran_detail.cust_tran_id.should == '20090102-111321'
883
+ end
884
+
885
+ it 'should be able to parse google analytics api xml' do
886
+ data = Analytics::Feed.parse(fixture_file('analytics.xml'))
887
+ data.id.should == 'http://www.google.com/analytics/feeds/accounts/nunemaker@gmail.com'
888
+ data.entries.size.should == 4
889
+
890
+ entry = data.entries[0]
891
+ entry.title.should == 'addictedtonew.com'
892
+ entry.properties.size.should == 4
893
+
894
+ property = entry.properties[0]
895
+ property.name.should == 'ga:accountId'
896
+ property.value.should == '85301'
897
+ end
898
+
899
+ it 'should be able to parse google analytics profile xml with manually declared namespace' do
900
+ data = Analytics::Profile.parse(fixture_file('analytics_profile.xml'))
901
+ data.entries.size.should == 6
902
+
903
+ entry = data.entries[0]
904
+ entry.title.should == 'www.homedepot.com'
905
+ entry.properties.size.should == 6
906
+ entry.goals.size.should == 0
907
+ end
908
+
909
+ it 'should allow instantiating with a string' do
910
+ module StringFoo
911
+ class Bar
912
+ include HappyMapper
913
+ has_many :things, 'StringFoo::Thing'
914
+ end
915
+
916
+ class Thing
917
+ include HappyMapper
918
+ end
919
+ end
920
+ end
921
+
922
+ it 'should parse family search xml' do
923
+ tree = FamilySearch::FamilyTree.parse(fixture_file('family_tree.xml'))
924
+ tree.version.should == '1.0.20071213.942'
925
+ tree.status_message.should == 'OK'
926
+ tree.status_code.should == '200'
927
+ tree.persons.person.size.should == 1
928
+ tree.persons.person.first.version.should == '1199378491000'
929
+ tree.persons.person.first.modified.should == Time.utc(2008, 1, 3, 16, 41, 31) # 2008-01-03T09:41:31-07:00
930
+ tree.persons.person.first.id.should == 'KWQS-BBQ'
931
+ tree.persons.person.first.information.alternateIds.ids.should_not be_kind_of(String)
932
+ tree.persons.person.first.information.alternateIds.ids.size.should == 8
933
+ end
934
+
935
+ it 'should parse multiple images' do
936
+ artist = Artist.parse(fixture_file('multiple_primitives.xml'))
937
+ artist.name.should == 'value'
938
+ artist.images.size.should == 2
939
+ end
940
+
941
+ it 'should parse lastfm namespaces' do
942
+ l = Location.parse(fixture_file('lastfm.xml'))
943
+ l.first.latitude.should == '51.53469'
944
+ end
945
+
946
+ describe 'Parse optional attributes' do
947
+ it 'should parse an empty String as empty' do
948
+ a = OptionalAttribute.parse(fixture_file('optional_attributes.xml'))
949
+ a[0].street.should == ''
950
+ end
951
+
952
+ it 'should parse a String with value' do
953
+ a = OptionalAttribute.parse(fixture_file('optional_attributes.xml'))
954
+ a[1].street.should == 'Milchstrasse'
955
+ end
956
+
957
+ it 'should parse a String with value' do
958
+ a = OptionalAttribute.parse(fixture_file('optional_attributes.xml'))
959
+ a[2].street.should be_nil
960
+ end
961
+ end
962
+
963
+ describe 'Default namespace combi' do
964
+ before(:each) do
965
+ file_contents = fixture_file('default_namespace_combi.xml')
966
+ @book = DefaultNamespaceCombi.parse(file_contents, single: true)
967
+ end
968
+
969
+ it 'should parse author' do
970
+ @book.author.should == 'Frank Gilbreth'
971
+ end
972
+
973
+ it 'should parse title' do
974
+ @book.title.should == 'Cheaper by the Dozen'
975
+ end
976
+
977
+ it 'should parse number' do
978
+ @book.number.should == '1568491379'
979
+ end
980
+ end
981
+
982
+ describe 'Xml Content' do
983
+ before(:each) do
984
+ file_contents = fixture_file('dictionary.xml')
985
+ @records = Dictionary::Record.parse(file_contents)
986
+ end
987
+
988
+ it 'should parse XmlContent' do
989
+ @records.first.definitions.first.text.should ==
990
+ 'a large common parrot, <bn>Cacatua galerita</bn>, predominantly white, with yellow on the undersides of wings and tail and a forward curving yellow crest, found in Australia, New Guinea and nearby islands.'
991
+ end
992
+
993
+ it "should save object's xml content" do
994
+ @records.first.variants.first.xml_content.should ==
995
+ 'white <tag>cockatoo</tag>'
996
+ @records.first.variants.last.to_html.should ==
997
+ '<em>white</em> cockatoo'
998
+ end
999
+ end
1000
+
1001
+ it 'should parse ambigous items' do
1002
+ items = AmbigousItems::Item.parse(fixture_file('ambigous_items.xml'), xpath: '/ambigous/my-items')
1003
+ items.map(&:name).should == %w(first second third).map { |s| "My #{s} item" }
1004
+ end
1005
+
1006
+ context Article do
1007
+ it 'should parse the publish options for Article and Photo' do
1008
+ @article.title.should_not be_nil
1009
+ @article.text.should_not be_nil
1010
+ @article.photos.should_not be_nil
1011
+ @article.photos.first.title.should_not be_nil
1012
+ end
1013
+
1014
+ it 'should parse the publish options for Article' do
1015
+ @article.publish_options.should_not be_nil
1016
+ end
1017
+
1018
+ it 'should parse the publish options for Photo' do
1019
+ @article.photos.first.publish_options.should_not be_nil
1020
+ end
1021
+
1022
+ it 'should only find only items at the parent level' do
1023
+ @article.photos.length.should == 1
1024
+ end
1025
+
1026
+ before(:all) do
1027
+ @article = Article.parse(fixture_file('subclass_namespace.xml'))
1028
+ end
1029
+ end
1030
+
1031
+ context 'Namespace is missing because an optional element that uses it is not present' do
1032
+ it 'should parse successfully' do
1033
+ @article = PartiallyBadArticle.parse(fixture_file('subclass_namespace.xml'))
1034
+ @article.should_not be_nil
1035
+ @article.title.should_not be_nil
1036
+ @article.text.should_not be_nil
1037
+ @article.photos.should_not be_nil
1038
+ @article.photos.first.title.should_not be_nil
1039
+ end
1040
+ end
1041
+
1042
+ describe 'with limit option' do
1043
+ it 'should return results with limited size: 6' do
1044
+ sizes = []
1045
+ posts = Post.parse(fixture_file('posts.xml'), in_groups_of: 6) do |a|
1046
+ sizes << a.size
1047
+ end
1048
+ sizes.should == [6, 6, 6, 2]
1049
+ end
1050
+
1051
+ it 'should return results with limited size: 10' do
1052
+ sizes = []
1053
+ posts = Post.parse(fixture_file('posts.xml'), in_groups_of: 10) do |a|
1054
+ sizes << a.size
1055
+ end
1056
+ sizes.should == [10, 10]
1057
+ end
1058
+ end
1059
+
1060
+ context 'when letting user set Nokogiri::XML::ParseOptions' do
1061
+ let(:default) do
1062
+ Class.new do
1063
+ include HappyMapper
1064
+ element :item, String
1065
+ end
1066
+ end
1067
+ let(:custom) do
1068
+ Class.new do
1069
+ include HappyMapper
1070
+ element :item, String
1071
+ with_nokogiri_config(&:default_xml)
1072
+ end
1073
+ end
1074
+
1075
+ it 'initializes @nokogiri_config_callback to nil' do
1076
+ default.nokogiri_config_callback.should be_nil
1077
+ end
1078
+
1079
+ it 'defaults to Nokogiri::XML::ParseOptions::STRICT' do
1080
+ expect { default.parse(fixture_file('set_config_options.xml')) }.to raise_error(Nokogiri::XML::SyntaxError)
1081
+ end
1082
+
1083
+ it 'accepts .on_config callback' do
1084
+ custom.nokogiri_config_callback.should_not be_nil
1085
+ end
1086
+
1087
+ it 'parses according to @nokogiri_config_callback' do
1088
+ expect { custom.parse(fixture_file('set_config_options.xml')) }.to_not raise_error
1089
+ end
1090
+
1091
+ it 'can clear @nokogiri_config_callback' do
1092
+ custom.with_nokogiri_config {}
1093
+ expect { custom.parse(fixture_file('set_config_options.xml')) }.to raise_error(Nokogiri::XML::SyntaxError)
1094
+ end
1095
+ end
1096
+ end