xmlmapper 0.5.9

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