unhappymapper 0.4.0

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 (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