uva-happymapper 0.4.1

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