representable 0.0.1.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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