unhappymapper 0.4.0

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