representable 0.0.1.alpha1

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 (103) hide show
  1. data/.gitignore +7 -0
  2. data/.gitmodules +3 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +3 -0
  5. data/History.txt +354 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +186 -0
  8. data/Rakefile +10 -0
  9. data/TODO +37 -0
  10. data/VERSION +1 -0
  11. data/examples/amazon.rb +35 -0
  12. data/examples/current_weather.rb +27 -0
  13. data/examples/dashed_elements.rb +20 -0
  14. data/examples/library.rb +40 -0
  15. data/examples/posts.rb +27 -0
  16. data/examples/rails.rb +70 -0
  17. data/examples/twitter.rb +37 -0
  18. data/examples/xml/active_record.xml +70 -0
  19. data/examples/xml/amazon.xml +133 -0
  20. data/examples/xml/current_weather.xml +89 -0
  21. data/examples/xml/dashed_elements.xml +52 -0
  22. data/examples/xml/posts.xml +23 -0
  23. data/examples/xml/twitter.xml +422 -0
  24. data/lib/representable.rb +257 -0
  25. data/lib/representable/definition.rb +109 -0
  26. data/lib/representable/nokogiri_extensions.rb +19 -0
  27. data/lib/representable/references.rb +153 -0
  28. data/lib/representable/version.rb +3 -0
  29. data/lib/representable/xml.rb +79 -0
  30. data/representable.gemspec +29 -0
  31. data/spec/definition_spec.rb +495 -0
  32. data/spec/examples/active_record_spec.rb +41 -0
  33. data/spec/examples/amazon_spec.rb +54 -0
  34. data/spec/examples/current_weather_spec.rb +37 -0
  35. data/spec/examples/dashed_elements_spec.rb +20 -0
  36. data/spec/examples/library_spec.rb +46 -0
  37. data/spec/examples/post_spec.rb +24 -0
  38. data/spec/examples/twitter_spec.rb +32 -0
  39. data/spec/roxml_integration_test.rb +289 -0
  40. data/spec/roxml_spec.rb +372 -0
  41. data/spec/shared_specs.rb +15 -0
  42. data/spec/spec_helper.rb +5 -0
  43. data/spec/support/libxml.rb +3 -0
  44. data/spec/support/nokogiri.rb +3 -0
  45. data/spec/xml/array_spec.rb +36 -0
  46. data/spec/xml/attributes_spec.rb +71 -0
  47. data/spec/xml/encoding_spec.rb +53 -0
  48. data/spec/xml/namespace_spec.rb +270 -0
  49. data/spec/xml/namespaces_spec.rb +67 -0
  50. data/spec/xml/object_spec.rb +82 -0
  51. data/spec/xml/parser_spec.rb +21 -0
  52. data/spec/xml/text_spec.rb +71 -0
  53. data/test/fixtures/book_malformed.xml +5 -0
  54. data/test/fixtures/book_pair.xml +8 -0
  55. data/test/fixtures/book_text_with_attribute.xml +5 -0
  56. data/test/fixtures/book_valid.xml +5 -0
  57. data/test/fixtures/book_with_authors.xml +7 -0
  58. data/test/fixtures/book_with_contributions.xml +9 -0
  59. data/test/fixtures/book_with_contributors.xml +7 -0
  60. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  61. data/test/fixtures/book_with_default_namespace.xml +9 -0
  62. data/test/fixtures/book_with_depth.xml +6 -0
  63. data/test/fixtures/book_with_octal_pages.xml +4 -0
  64. data/test/fixtures/book_with_publisher.xml +7 -0
  65. data/test/fixtures/book_with_wrapped_attr.xml +3 -0
  66. data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
  67. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  68. data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
  69. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  70. data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
  71. data/test/fixtures/dictionary_of_names.xml +4 -0
  72. data/test/fixtures/dictionary_of_texts.xml +10 -0
  73. data/test/fixtures/library.xml +30 -0
  74. data/test/fixtures/library_uppercase.xml +30 -0
  75. data/test/fixtures/muffins.xml +3 -0
  76. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  77. data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
  78. data/test/fixtures/node_with_name_conflicts.xml +4 -0
  79. data/test/fixtures/numerology.xml +4 -0
  80. data/test/fixtures/person.xml +1 -0
  81. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  82. data/test/fixtures/person_with_mothers.xml +10 -0
  83. data/test/mocks/dictionaries.rb +57 -0
  84. data/test/mocks/mocks.rb +279 -0
  85. data/test/roxml_test.rb +58 -0
  86. data/test/support/fixtures.rb +11 -0
  87. data/test/test_helper.rb +6 -0
  88. data/test/unit/definition_test.rb +235 -0
  89. data/test/unit/deprecations_test.rb +24 -0
  90. data/test/unit/to_xml_test.rb +81 -0
  91. data/test/unit/xml_attribute_test.rb +39 -0
  92. data/test/unit/xml_block_test.rb +81 -0
  93. data/test/unit/xml_bool_test.rb +122 -0
  94. data/test/unit/xml_convention_test.rb +150 -0
  95. data/test/unit/xml_hash_test.rb +115 -0
  96. data/test/unit/xml_initialize_test.rb +49 -0
  97. data/test/unit/xml_name_test.rb +141 -0
  98. data/test/unit/xml_namespace_test.rb +31 -0
  99. data/test/unit/xml_object_test.rb +206 -0
  100. data/test/unit/xml_required_test.rb +94 -0
  101. data/test/unit/xml_text_test.rb +71 -0
  102. data/website/index.html +98 -0
  103. metadata +248 -0
@@ -0,0 +1,41 @@
1
+ require 'fileutils'
2
+ require_relative './../spec_helper'
3
+ require_relative './../../examples/rails'
4
+
5
+ describe ROXML, "under ActiveRecord" do
6
+ before do
7
+ @route = Route.from_xml(xml_for('active_record'))
8
+ end
9
+
10
+ before(:all) do
11
+ FileUtils.rm(DB_PATH) if File.exists?(DB_PATH)
12
+ end
13
+
14
+ it "should be parsed" do
15
+ @route.should_not == nil
16
+ @route.should be_an_instance_of(Route)
17
+ end
18
+
19
+ describe "xml attributes" do
20
+ it "should extract xml attributes" do
21
+ @route.totalHg.should == "640"
22
+ @route.lonlatx.should == "357865"
23
+ @route.lonlaty.should == "271635"
24
+ @route.grcenter.should == "SH 71635 57865"
25
+ @route.totalMins.should == "235.75000000000003"
26
+ @route.totalDist.should == "11185.321521477119"
27
+ end
28
+ end
29
+
30
+ describe "xml sub-objects" do
31
+ it "should extract xml sub-objects" do
32
+ @route.should have(6).waypoints
33
+ @route.waypoints.each {|waypoint| waypoint.should be_an_instance_of(Waypoint)}
34
+ end
35
+ it "should be usable as a ActiveRecord object" do
36
+ Waypoint.count.should == 0
37
+ @route.save!
38
+ Waypoint.count.should == 6
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,54 @@
1
+ require_relative './../spec_helper'
2
+ require_relative './../../examples/amazon'
3
+
4
+ describe PITA::ItemSearchResponse do
5
+ before do
6
+ @response = PITA::ItemSearchResponse.from_xml(xml_for('amazon'))
7
+ end
8
+
9
+ describe "#total_results" do
10
+ it "should be parsed as a number" do
11
+ @response.total_results.should > 0
12
+ end
13
+ end
14
+
15
+ describe "#total_pages" do
16
+ it "should be parsed as a number" do
17
+ @response.total_pages.should > 0
18
+ end
19
+ end
20
+
21
+ describe "#items" do
22
+ it "should return a collection of items" do
23
+ @response.items.should be_an_instance_of(Array)
24
+ @response.items.size.should > 0
25
+ @response.items.each {|item| item.should be_an_instance_of(PITA::Item) }
26
+ end
27
+
28
+ it "should have the some number less than or equal to #total_results" do
29
+ @response.items.size.should > 0
30
+ @response.items.size.should <= @response.total_results
31
+ end
32
+ end
33
+ end
34
+
35
+ describe PITA::Item do
36
+ before do
37
+ @items = PITA::ItemSearchResponse.from_xml(xml_for('amazon')).items
38
+ end
39
+
40
+ it "should extract asin" do
41
+ @items.each {|item| item.asin.should be_an_instance_of(String) }
42
+ @items.each {|item| item.asin.should_not be_empty }
43
+ end
44
+
45
+ it "should extract detail_page_url" do
46
+ @items.each {|item| item.detail_page_url.should be_an_instance_of(String) }
47
+ @items.each {|item| item.detail_page_url.should_not be_empty }
48
+ end
49
+
50
+ it "should extract manufacturer" do
51
+ @items.each {|item| item.manufacturer.should be_an_instance_of(String) }
52
+ @items.each {|item| item.manufacturer.should_not be_empty }
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ require_relative './../spec_helper'
2
+ require_relative './../../examples/current_weather'
3
+
4
+ describe Weather do
5
+ before do
6
+ @weather = Weather.from_xml(xml_for('current_weather'))
7
+ end
8
+
9
+ it "should extract observations" do
10
+ @weather.observation.should be_an_instance_of(WeatherObservation)
11
+ end
12
+ end
13
+
14
+ describe WeatherObservation do
15
+ before do
16
+ @observation = Weather.from_xml(xml_for('current_weather')).observation
17
+ end
18
+
19
+ it "should extract temperature" do
20
+ @observation.temperature.should > 0
21
+ end
22
+
23
+ it "should extract feels_like" do
24
+ @observation.feels_like.should > 0
25
+ end
26
+
27
+ describe "#current_condition" do
28
+ it "should extract current_condition" do
29
+ @observation.current_condition.should_not be_empty
30
+ end
31
+
32
+ it "should extract icon attribute" do
33
+ pending "need to think options through for HappyMapper-style :attributes extensions"
34
+ @observation.current_condition.icon.should_not be_empty
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ require_relative './../spec_helper'
2
+ require_relative './../../examples/dashed_elements'
3
+
4
+ describe GitHub::Commit do
5
+ before do
6
+ @commit = GitHub::Commit.from_xml(xml_for('dashed_elements'))
7
+ end
8
+
9
+ it "should extract committed date" do
10
+ @commit.committed_date.should be_an_instance_of(Date)
11
+ end
12
+
13
+ it "should extract url" do
14
+ @commit.url.should_not be_empty
15
+ end
16
+
17
+ it "should extract id" do
18
+ @commit.id.should_not be_empty
19
+ end
20
+ end
@@ -0,0 +1,46 @@
1
+ require_relative './../spec_helper'
2
+ require_relative './../../examples/library'
3
+
4
+ describe Library do
5
+ before :all do
6
+ book = Novel.new
7
+ book.isbn = "0201710897"
8
+ book.title = "The PickAxe"
9
+ book.description = "Best Ruby book out there!"
10
+ book.author = "David Thomas, Andrew Hunt, Dave Thomas"
11
+ book.publisher = Publisher.new('Addison Wesley Longman, Inc.')
12
+
13
+ @lib = Library.new
14
+ @lib.name = "Favorite Books"
15
+ @lib.novels = [book]
16
+ end
17
+
18
+ describe "#to_xml" do
19
+ it "should contain the expected information" do
20
+ @lib.to_xml.to_s.should == ROXML::XML.parse_string(%{<library><NAME><![CDATA[Favorite Books]]></NAME><novel ISBN='0201710897'><title>The PickAxe</title><description><![CDATA[Best Ruby book out there!]]></description><author>David Thomas, Andrew Hunt, Dave Thomas</author><publisher><name>Addison Wesley Longman, Inc.</name></publisher></novel></library>}).root.to_s
21
+ end
22
+
23
+ context "when written to a file" do
24
+ before :all do
25
+ @path = "spec/examples/library.xml"
26
+ @doc = ROXML::XML::Document.new
27
+ @doc.root = @lib.to_xml
28
+ ROXML::XML.save_doc(@doc, @path)
29
+ end
30
+
31
+ after :all do
32
+ FileUtils.rm @path
33
+ end
34
+
35
+ it "should be contain the expected xml" do
36
+ ROXML::XML.parse_string(File.read(@path)).to_s.should == ROXML::XML.parse_string(%{<?xml version="1.0"?><library><NAME><![CDATA[Favorite Books]]></NAME><novel ISBN='0201710897'><title>The PickAxe</title><description><![CDATA[Best Ruby book out there!]]></description><author>David Thomas, Andrew Hunt, Dave Thomas</author><publisher><name>Addison Wesley Longman, Inc.</name></publisher></novel></library>}).to_s
37
+ end
38
+
39
+ it "should be re-parsable via .from_xml" do
40
+ File.open("spec/examples/library.xml") do |file|
41
+ Library.from_xml(file).should == @lib
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,24 @@
1
+ require_relative './../spec_helper'
2
+ require_relative './../../examples/posts'
3
+
4
+ describe Post do
5
+ before do
6
+ @posts = Posts.from_xml(xml_for('posts')).posts
7
+ end
8
+
9
+ it "should extract description" do
10
+ @posts.each {|post| post.description.should_not be_empty }
11
+ end
12
+
13
+ it "should extract href" do
14
+ @posts.each {|post| post.href.should_not be_empty }
15
+ end
16
+
17
+ it "should extract extended" do
18
+ @posts.each {|post| post.extended.should_not be_empty }
19
+ end
20
+
21
+ it "should extract time" do
22
+ @posts.each {|post| post.created_at.should be_an_instance_of(DateTime) }
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ require_relative './../spec_helper'
2
+ require_relative './../../examples/twitter'
3
+
4
+ describe Statuses do
5
+ describe Status do
6
+ before do
7
+ @statuses = Statuses.from_xml(xml_for('twitter')).statuses
8
+ end
9
+
10
+ it "should extract text" do
11
+ @statuses.each {|status| status.text.should_not be_empty }
12
+ end
13
+
14
+ it "should extract source" do
15
+ @statuses.each {|status| status.source.should_not be_empty }
16
+ end
17
+
18
+ describe User do
19
+ before do
20
+ @users = @statuses.map(&:user)
21
+ end
22
+
23
+ it "should extract name" do
24
+ @users.each {|user| user.name.should == "John Nunemaker" }
25
+ end
26
+
27
+ it "should extract screen_name" do
28
+ @users.each {|user| user.screen_name.should == "jnunemaker" }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,289 @@
1
+ require 'spec_helper'
2
+
3
+ require 'test_xml/spec'
4
+
5
+ describe Representable, "#xml" do
6
+ class Contributor
7
+ include Representable
8
+
9
+ xml_accessor :role, :from => :attr
10
+ xml_accessor :name
11
+ end
12
+
13
+ class Band
14
+ include Representable
15
+ xml_accessor :name
16
+ end
17
+
18
+ class Label
19
+ def to_xml
20
+ "<label>Fat Wreck</label>"
21
+ end
22
+ end
23
+
24
+
25
+ describe "generic tests" do
26
+ context ".from_xml" do
27
+ it "works with empty string" do
28
+ album = Album.from_xml("")
29
+ album.band.should == []
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+ describe ":from => @rel" do
36
+ class Link
37
+ include Representable
38
+ xml_accessor :href, :from => "@href"
39
+ xml_accessor :title, :from => "@title"
40
+ end
41
+
42
+ context ".from_xml" do
43
+ it "creates correct accessors" do
44
+ link = Link.from_xml(%{
45
+ <a href="http://apotomo.de" title="Home, sweet home" />
46
+ })
47
+ link.href.should == "http://apotomo.de"
48
+ link.title.should == "Home, sweet home"
49
+ end
50
+ end
51
+
52
+ context "#to_xml" do
53
+ it "serializes correctly" do
54
+ link = Link.new
55
+ link.href = "http://apotomo.de/"
56
+
57
+ link.to_xml.to_s.should equal_xml %{<link href="http://apotomo.de/">}
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+ describe ":as => Item" do
64
+ class Albumy
65
+ include Representable
66
+ xml_accessor :band, :as => Band
67
+ xml_accessor :label, :as => Label
68
+ end
69
+
70
+ context ".from_xml" do
71
+ it "creates one Item instance" do
72
+ album = Albumy.from_xml(%{
73
+ <album>
74
+ <band><name>Bad Religion</name></band>
75
+ </album>
76
+ })
77
+ album.band.name.should == "Bad Religion"
78
+ end
79
+ end
80
+
81
+ context "#to_xml" do
82
+ it "doesn't escape xml from Band#to_xml" do
83
+ band = Band.new
84
+ band.name = "Bad Religion"
85
+ album = Albumy.new
86
+ album.band = band
87
+
88
+ album.to_xml.to_s.should equal_xml %{<album>
89
+ <band>
90
+ <name>Bad Religion</name>
91
+ </band>
92
+ </album>}
93
+ end
94
+
95
+ it "doesn't escape and wrap string from Label#to_xml" do
96
+ album = Albumy.new
97
+ album.label = Label.new
98
+
99
+ album.to_xml.to_s.should equal_xml %{<album>
100
+ <label>Fat Wreck</label>
101
+ </album>}
102
+ end
103
+ end
104
+ end
105
+
106
+
107
+ describe "collections" do
108
+ context ":as => [Item]" do
109
+ class Book
110
+ include Representable
111
+
112
+
113
+ xml_accessor :contributors, :as => [Contributor], :tag => :contributor
114
+ end
115
+
116
+ it ".from_xml pushes collection items to array" do
117
+ book = Book.from_xml(%{
118
+ <book>
119
+ <contributor><name>David Thomas</name></contributor>
120
+ <contributor><name>Andrew Hunt</name></contributor>
121
+ <contributor><name>Chad Fowler</name></contributor>
122
+ </book>
123
+ })
124
+ book.contributors.map(&:name).sort.should == ["David Thomas","Andrew Hunt","Chad Fowler"].sort
125
+ end
126
+
127
+ it "collections can be empty" do
128
+ book = Book.from_xml(%{
129
+ <book>
130
+ </book>
131
+ })
132
+ book.contributors.should == []
133
+ end
134
+
135
+ it "responds to #to_xml" do
136
+ book = Book.new
137
+ david = Contributor.new
138
+ david.name= "David Thomas"
139
+ chad = Contributor.new
140
+ chad.name= "Chad Fowler"
141
+ book.contributors = [david, chad]
142
+
143
+ book.to_xml.to_s.should equal_xml %{<book>
144
+ <contributor><name>David Thomas</name></contributor>
145
+ <contributor><name>Chad Fowler</name></contributor>
146
+ </book>}
147
+ end
148
+ end
149
+
150
+
151
+ context ":as => []" do
152
+ class Album
153
+ include Representable
154
+
155
+ xml_accessor :songs, :as => [], :tag => :song
156
+ end
157
+
158
+ it "collects untyped items" do
159
+ album = Album.from_xml(%{
160
+ <album>
161
+ <song>Two Kevins</song>
162
+ <song>Wright and Rong</song>
163
+ <song>Laundry Basket</song>
164
+ </album>
165
+ })
166
+ album.songs.sort.should == ["Laundry Basket", "Two Kevins", "Wright and Rong"].sort
167
+ end
168
+ end
169
+ end
170
+
171
+ def parse_xml(xml); Nokogiri::XML::Node.from(xml); end
172
+
173
+ describe "Reference" do
174
+ context "ObjectRef with []" do
175
+ subject do
176
+ Representable::XMLObjectRef.new(Representable::Definition.new(:songs, :as => [Album]))
177
+ end
178
+
179
+ it "responds to #default" do
180
+ subject.send(:default).should == []
181
+ end
182
+ end
183
+
184
+
185
+
186
+
187
+ context "TextRef#value_in" do
188
+ subject do
189
+ Representable::XMLTextRef.new(Representable::Definition.new(:song))
190
+ end
191
+
192
+ it "returns found value" do
193
+ subject.value_in(parse_xml("<a><song>Unkoil</song></a>")).should == "Unkoil"
194
+ end
195
+
196
+ end
197
+ end
198
+
199
+
200
+ describe "Definition" do
201
+ context "generic API" do
202
+ subject do
203
+ Representable::Definition.new(:songs)
204
+ end
205
+
206
+ it "responds to #typed?" do
207
+ subject.typed? == false
208
+ Representable::Definition.new(:songs, :as => Album).typed? == true
209
+ Representable::Definition.new(:songs, :as => [Album]).typed? == true
210
+ end
211
+
212
+
213
+ context "#apply" do
214
+ subject do
215
+ Representable::Definition.new(:song)
216
+ end
217
+
218
+ it "works with a single item" do
219
+ subject.apply(1) { |v| v+1 }.should == 2
220
+ end
221
+
222
+ it "works with collection" do
223
+ d = Representable::Definition.new(:song, :as => [])
224
+ d.apply([1,2,3]) { |v| v+1 }.should == [2,3,4]
225
+ end
226
+
227
+ it "skips with collection and nil" do
228
+ d = Representable::Definition.new(:song, :as => [])
229
+ d.apply(nil) { |v| v+1 }.should == nil
230
+ end
231
+
232
+ end
233
+ end
234
+
235
+ context ":as => []" do
236
+ subject do
237
+ Representable::Definition.new(:songs, :as => [], :tag => :song)
238
+ end
239
+
240
+ it "responds to #accessor" do
241
+ subject.accessor.should == "songs"
242
+ end
243
+
244
+ it "responds to #array?" do
245
+ subject.array?.should be_true
246
+ end
247
+
248
+ it "responds to #name" do
249
+ subject.accessor.should == "songs"
250
+ end
251
+
252
+ it "responds to #instance_variable_name" do
253
+ subject.instance_variable_name.should == :"@songs"
254
+ end
255
+
256
+ it "responds to #setter" do
257
+ subject.setter.should == :"songs="
258
+ end
259
+
260
+ it "responds to #sought_type" do
261
+ subject.sought_type.should == :text
262
+ end
263
+ end
264
+
265
+
266
+ context ":as => [Item]" do
267
+ subject do
268
+ class Song; end
269
+ Representable::Definition.new(:songs, :as => [Song])
270
+ end
271
+
272
+ it "responds to #sought_type" do
273
+ subject.sought_type.should == Song
274
+ end
275
+ end
276
+
277
+
278
+ context ":as => Item" do
279
+ subject do
280
+ class Song; end
281
+ Representable::Definition.new(:songs, :as => Song)
282
+ end
283
+
284
+ it "responds to #sought_type" do
285
+ subject.sought_type.should == Song
286
+ end
287
+ end
288
+ end
289
+ end