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