xmlmapper 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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