roxml 2.4.3 → 2.5.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 (47) hide show
  1. data/History.txt +54 -0
  2. data/Manifest.txt +9 -6
  3. data/README.rdoc +24 -17
  4. data/Rakefile +2 -1
  5. data/TODO +30 -31
  6. data/examples/active_record.rb +69 -0
  7. data/examples/amazon.rb +1 -1
  8. data/examples/current_weather.rb +1 -1
  9. data/examples/posts.rb +8 -8
  10. data/examples/twitter.rb +2 -2
  11. data/examples/xml/active_record.xml +70 -0
  12. data/lib/roxml.rb +174 -174
  13. data/lib/roxml/definition.rb +165 -89
  14. data/lib/roxml/extensions/deprecation.rb +5 -0
  15. data/lib/roxml/extensions/string/conversions.rb +2 -3
  16. data/lib/roxml/hash_definition.rb +26 -25
  17. data/lib/roxml/xml.rb +15 -6
  18. data/lib/roxml/xml/parsers/libxml.rb +9 -6
  19. data/lib/roxml/xml/parsers/rexml.rb +1 -1
  20. data/lib/roxml/xml/references.rb +14 -17
  21. data/roxml.gemspec +8 -5
  22. data/spec/definition_spec.rb +563 -0
  23. data/spec/examples/active_record_spec.rb +43 -0
  24. data/spec/roxml_spec.rb +372 -0
  25. data/spec/shared_specs.rb +15 -0
  26. data/spec/spec_helper.rb +21 -4
  27. data/spec/string_spec.rb +15 -0
  28. data/spec/xml/parser_spec.rb +22 -0
  29. data/test/fixtures/book_valid.xml +1 -1
  30. data/test/fixtures/person_with_guarded_mothers.xml +3 -3
  31. data/test/mocks/mocks.rb +57 -45
  32. data/test/unit/definition_test.rb +161 -12
  33. data/test/unit/deprecations_test.rb +97 -0
  34. data/test/unit/to_xml_test.rb +30 -1
  35. data/test/unit/xml_bool_test.rb +15 -3
  36. data/test/unit/xml_construct_test.rb +6 -6
  37. data/test/unit/xml_hash_test.rb +18 -0
  38. data/test/unit/xml_initialize_test.rb +6 -3
  39. data/test/unit/xml_object_test.rb +66 -5
  40. data/test/unit/xml_text_test.rb +3 -0
  41. metadata +23 -15
  42. data/test/unit/array_test.rb +0 -16
  43. data/test/unit/freeze_test.rb +0 -71
  44. data/test/unit/inheritance_test.rb +0 -63
  45. data/test/unit/overriden_output_test.rb +0 -33
  46. data/test/unit/roxml_test.rb +0 -60
  47. data/test/unit/string_test.rb +0 -11
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require example('active_record')
3
+
4
+ describe ROXML, "under ActiveRecord" do
5
+ before do
6
+ @route = Route.from_xml(xml_for('active_record'))
7
+ end
8
+
9
+ after(:all) do
10
+ db = File.dirname(__FILE__) + "/../../examples/active_record/active_record.sqlite3"
11
+ if File.exists?(db)
12
+ FileUtils.rm(db)
13
+ end
14
+ end
15
+
16
+ it "should be parsed" do
17
+ @route.should_not == nil
18
+ @route.should be_an_instance_of(Route)
19
+ end
20
+
21
+ describe "xml attributes" do
22
+ it "should extract xml attributes" do
23
+ @route.totalHg.should == "640"
24
+ @route.lonlatx.should == "357865"
25
+ @route.lonlaty.should == "271635"
26
+ @route.grcenter.should == "SH 71635 57865"
27
+ @route.totalMins.should == "235.75000000000003"
28
+ @route.totalDist.should == "11185.321521477119"
29
+ end
30
+ end
31
+
32
+ describe "xml sub-objects" do
33
+ it "should extract xml sub-objects" do
34
+ @route.should have(6).waypoints
35
+ @route.waypoints.each {|waypoint| waypoint.should be_an_instance_of(Waypoint)}
36
+ end
37
+ it "should be usable as a ActiveRecord object" do
38
+ Waypoint.count.should == 0
39
+ @route.save!
40
+ Waypoint.count.should == 6
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,372 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe ROXML, "#from_xml" do
4
+ describe "from_xml call", :shared => true do
5
+ it "should fetch values" do
6
+ book = BookWithContributors.from_xml(@path)
7
+ book.title.should == "Programming Ruby - 2nd Edition"
8
+ book.contributors.map(&:name).should == ["David Thomas","Andrew Hunt","Chad Fowler"]
9
+ end
10
+ end
11
+
12
+ context "called with PathName" do
13
+ before do
14
+ @path = Pathname.new(fixture_path(:book_with_contributors))
15
+ end
16
+ it_should_behave_like "from_xml call"
17
+ end
18
+
19
+ context "called with File" do
20
+ before do
21
+ @path = File.new(fixture_path(:book_with_contributors))
22
+ end
23
+ it_should_behave_like "from_xml call"
24
+ end
25
+
26
+ context "called with URI" do
27
+ before do
28
+ require 'uri'
29
+ @path = URI.parse("file://#{File.expand_path(File.expand_path(fixture_path(:book_with_contributors)))}")
30
+ end
31
+ it_should_behave_like "from_xml call"
32
+ end
33
+ end
34
+
35
+ describe ROXML, "#xml" do
36
+ class DescriptionReadonly
37
+ include ROXML
38
+
39
+ xml_reader :writable, :from => :content
40
+ xml_reader :readonly, :from => :content, :frozen => true
41
+ end
42
+
43
+ class Contributor
44
+ include ROXML
45
+
46
+ xml_reader :role, :from => :attr
47
+ xml_reader :name
48
+ end
49
+
50
+ class BookWithContributions
51
+ include ROXML
52
+
53
+ xml_name :book
54
+ xml_reader :isbn, :from => :attr
55
+ xml_reader :title
56
+ xml_reader :description, DescriptionReadonly
57
+ xml_reader :contributions, [Contributor], :from => 'contributor', :in => "contributions"
58
+ end
59
+
60
+ class BookWithContributionsReadonly
61
+ include ROXML
62
+
63
+ xml_name :book
64
+ xml_reader :isbn, :from => :attr, :frozen => true
65
+ xml_reader :title, :frozen => true
66
+ xml_reader :description, DescriptionReadonly, :frozen => true
67
+ xml_reader :contributions, [Contributor], :from => 'contributor', :in => "contributions", :frozen => true
68
+ end
69
+
70
+ before do
71
+ @writable = BookWithContributions.from_xml(fixture(:book_with_contributions))
72
+ @readonly = BookWithContributionsReadonly.from_xml(fixture(:book_with_contributions))
73
+ end
74
+
75
+ it "should raise on duplicate accessor name" do
76
+ proc do
77
+ Class.new do
78
+ include ROXML
79
+
80
+ xml_reader :id
81
+ xml_accessor :id
82
+ end
83
+ end.should raise_error(RuntimeError)
84
+ end
85
+
86
+ class OctalInteger
87
+ def self.from_xml(val)
88
+ new(Integer(val.content))
89
+ end
90
+
91
+ def initialize(value)
92
+ @val = value
93
+ end
94
+
95
+ def ==(other)
96
+ @val == other
97
+ end
98
+
99
+ def to_xml
100
+ sprintf("%#o", @val)
101
+ end
102
+ end
103
+
104
+ describe "overriding output" do
105
+ class BookWithOctalPages
106
+ include ROXML
107
+
108
+ xml_accessor :pages_with_as, :as => Integer, :to_xml => proc {|val| sprintf("%#o", val) }, :required => true
109
+ xml_accessor :pages_with_type, OctalInteger, :required => true
110
+ end
111
+
112
+ # to_xml_test :book_with_octal_pages
113
+
114
+ describe "with :to_xml option" do
115
+ it "should output with to_xml filtering"
116
+ end
117
+
118
+ describe "with #to_xml on the object" do
119
+ it "should output with to_xml filtering"
120
+ end
121
+ end
122
+
123
+ describe "overriding input" do
124
+ before do
125
+ @book_with_octal_pages_xml = %{
126
+ <book>
127
+ <pages>0357</pages>
128
+ </book>
129
+ }
130
+
131
+ @expected_pages = 239
132
+ end
133
+
134
+ describe "with :as block shorthand" do
135
+ class BookWithOctalPagesBlockShorthand
136
+ include ROXML
137
+
138
+ xml_accessor :pages, :as => Integer, :required => true
139
+ end
140
+
141
+ it "should apply filtering on input" do
142
+ book = BookWithOctalPagesBlockShorthand.from_xml(@book_with_octal_pages_xml)
143
+ book.pages.should == @expected_pages
144
+ end
145
+ end
146
+
147
+ describe "with #from_xml defined on the object" do
148
+ class BookWithOctalPagesType
149
+ include ROXML
150
+
151
+ xml_accessor :pages, OctalInteger, :required => true
152
+ end
153
+
154
+ it "should apply filtering on input" do
155
+ book = BookWithOctalPagesType.from_xml(@book_with_octal_pages_xml)
156
+ book.pages.should == @expected_pages
157
+ end
158
+ end
159
+ end
160
+
161
+ describe "attribute reference" do
162
+ before do
163
+ @frozen = @readonly.isbn
164
+ @unfrozen = @writable.isbn
165
+ end
166
+
167
+ it_should_behave_like "freezable xml reference"
168
+ end
169
+
170
+ describe "text reference" do
171
+ before do
172
+ @frozen = @readonly.title
173
+ @unfrozen = @writable.title
174
+ end
175
+
176
+ it_should_behave_like "freezable xml reference"
177
+ end
178
+
179
+ describe "object reference" do
180
+ before do
181
+ @frozen = @readonly.description
182
+ @unfrozen = @writable.description
183
+ end
184
+
185
+ it_should_behave_like "freezable xml reference"
186
+
187
+ describe "indirect reference via an object" do
188
+ it "does not inherit the frozen status from its parent" do
189
+ @frozen.writable.frozen?.should be_false
190
+ @frozen.readonly.frozen?.should be_true
191
+
192
+ @unfrozen.writable.frozen?.should be_false
193
+ @unfrozen.readonly.frozen?.should be_true
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "array reference" do
199
+ before do
200
+ @frozen = @readonly.contributions
201
+ @unfrozen = @writable.contributions
202
+ end
203
+
204
+ it_should_behave_like "freezable xml reference"
205
+
206
+ it "should apply :frozen to the constituent elements" do
207
+ @frozen.all?(&:frozen?).should be_true
208
+ @unfrozen.any?(&:frozen?).should be_false
209
+ end
210
+
211
+ context "no elements are present in root, no :in is specified" do
212
+ class BookWithContributors
213
+ include ROXML
214
+
215
+ xml_name :book
216
+ xml_reader :isbn, :from => :attr
217
+ xml_reader :title
218
+ xml_reader :description
219
+ xml_reader :contributors, [Contributor]
220
+ end
221
+
222
+ it "should look for elements :in the plural of name" do
223
+ book = BookWithContributors.from_xml(%{
224
+ <book isbn="0974514055">
225
+ <contributors>
226
+ <contributor role="author"><name>David Thomas</name></contributor>
227
+ <contributor role="supporting author"><name>Andrew Hunt</name></contributor>
228
+ <contributor role="supporting author"><name>Chad Fowler</name></contributor>
229
+ </contributors>
230
+ </book>
231
+ })
232
+ book.contributors.map(&:name).sort.should == ["David Thomas","Andrew Hunt","Chad Fowler"].sort
233
+ end
234
+ end
235
+ end
236
+
237
+ describe "hash reference" do
238
+ class DictionaryOfGuardedNames
239
+ include ROXML
240
+
241
+ xml_name :dictionary
242
+ xml_reader :definitions, {:key => :name,
243
+ :value => :content}, :in => :definitions
244
+ end
245
+
246
+ class DictionaryOfGuardedNamesReadonly
247
+ include ROXML
248
+
249
+ xml_name :dictionary
250
+ xml_reader :definitions, {:key => :name,
251
+ :value => :content}, :in => :definitions, :frozen => true
252
+ end
253
+
254
+ before do
255
+ @frozen = DictionaryOfGuardedNamesReadonly.from_xml(fixture(:dictionary_of_guarded_names)).definitions
256
+ @unfrozen = DictionaryOfGuardedNames.from_xml(fixture(:dictionary_of_guarded_names)).definitions
257
+ end
258
+
259
+ it_should_behave_like "freezable xml reference"
260
+
261
+ it "should have frozen keys, as with all hashes" do
262
+ @frozen.keys.all?(&:frozen?).should be_true
263
+ @unfrozen.keys.all?(&:frozen?).should be_true
264
+ end
265
+
266
+ it "should apply :frozen to the constituent values" do
267
+ @frozen.values.all?(&:frozen?).should be_true
268
+ @unfrozen.values.any?(&:frozen?).should be_false
269
+ end
270
+ end
271
+ end
272
+
273
+ describe ROXML, "inheritance" do
274
+ class Book
275
+ include ROXML
276
+
277
+ xml_accessor :isbn, :attr => 'ISBN'
278
+ xml_reader :title
279
+ xml_reader :description, :as => :cdata
280
+ xml_reader :author
281
+ xml_accessor :pages, :text => 'pagecount', :as => Integer
282
+ end
283
+
284
+ class Measurement
285
+ include ROXML
286
+
287
+ xml_reader :units, :from => :attr
288
+ xml_reader :value, :from => :content, :as => Float
289
+
290
+ def initialize(value = 0, units = 'pixels')
291
+ @value = Float(value)
292
+ @units = units.to_s
293
+ normalize_hundredths
294
+ end
295
+
296
+ def to_s
297
+ "#{value} #{units}"
298
+ end
299
+
300
+ def ==(other)
301
+ other.units == @units && other.value == @value
302
+ end
303
+
304
+ private
305
+ def after_parse
306
+ normalize_hundredths
307
+ end
308
+
309
+ def normalize_hundredths
310
+ if @units.starts_with? 'hundredths-'
311
+ @value /= 100
312
+ @units = @units.split('hundredths-')[1]
313
+ end
314
+ end
315
+ end
316
+
317
+ class InheritedBookWithDepth < Book
318
+ xml_reader :depth, Measurement
319
+ end
320
+
321
+ before do
322
+ @book_xml = %{
323
+ <book ISBN="0201710897">
324
+ <title>The PickAxe</title>
325
+ <description><![CDATA[Probably the best Ruby book out there]]></description>
326
+ <author>David Thomas, Andrew Hunt, Dave Thomas</author>
327
+ <depth units="hundredths-meters">1130</depth>
328
+ <publisher>Pragmattic Programmers</publisher>
329
+ </book>
330
+ }
331
+
332
+ @parent = Book.from_xml(@book_xml)
333
+ @child = InheritedBookWithDepth.from_xml(@book_xml)
334
+ end
335
+
336
+ describe "parent" do
337
+ it "should include its attributes" do
338
+ @child.isbn.should == "0201710897"
339
+ @child.title.should == "The PickAxe"
340
+ @child.description.should == "Probably the best Ruby book out there"
341
+ @child.author.should == 'David Thomas, Andrew Hunt, Dave Thomas'
342
+ @child.pages.should == nil
343
+ end
344
+
345
+ it "should not include its child's attributes" do
346
+ @parent.should_not respond_to(:depth)
347
+ end
348
+ end
349
+
350
+ describe "child" do
351
+ it "should include its parent's attributes" do
352
+ @child.isbn.should == @parent.isbn
353
+ @child.title.should == @parent.title
354
+ @child.description.should == @parent.description
355
+ @child.author.should == @parent.author
356
+ @child.pages.should == @parent.pages
357
+ end
358
+
359
+ it "should include its attributes" do
360
+ @child.depth.to_s.should == '11.3 meters'
361
+ end
362
+
363
+ it "should include parent's attributes added after declaration" do
364
+ Book.class_eval do
365
+ xml_reader :publisher, :require => true
366
+ end
367
+
368
+ book = InheritedBookWithDepth.from_xml(@book_xml)
369
+ book.publisher.should == "Pragmattic Programmers"
370
+ end
371
+ end
372
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe "freezable xml reference", :shared => true do
4
+ describe "with :frozen option" do
5
+ it "should be frozen" do
6
+ @frozen.frozen?.should be_true
7
+ end
8
+ end
9
+
10
+ describe "without :frozen option" do
11
+ it "should not be frozen" do
12
+ @unfrozen.frozen?.should be_false
13
+ end
14
+ end
15
+ end
@@ -4,13 +4,30 @@ module ROXML
4
4
  SILENCE_XML_NAME_WARNING = true
5
5
  end
6
6
 
7
- DIR = Pathname.new(__FILE__).dirname
8
- require DIR.join('../lib/roxml').expand_path
7
+ DIR = Pathname.new(__FILE__ + '../..').expand_path.dirname
8
+ LOAD_PATH = DIR.join('lib').to_s
9
+ $LOAD_PATH.unshift(LOAD_PATH) unless
10
+ $LOAD_PATH.include?(LOAD_PATH) || $LOAD_PATH.include?(File.expand_path(LOAD_PATH))
11
+ require 'roxml'
12
+
13
+ require File.join(File.dirname(__FILE__), 'shared_specs')
9
14
 
10
15
  def example(name)
11
- DIR.join("../examples/#{name}.rb").expand_path
16
+ DIR.join("examples/#{name}.rb")
17
+ end
18
+
19
+ def fixture(name)
20
+ File.read(fixture_path(name))
21
+ end
22
+
23
+ def xml_fixture(name)
24
+ ROXML::XML::Parser.parse_file(fixture_path(name)).root
25
+ end
26
+
27
+ def fixture_path(name)
28
+ "test/fixtures/#{name}.xml"
12
29
  end
13
30
 
14
31
  def xml_for(name)
15
- DIR.join("../examples/xml/#{name}.xml").expand_path
32
+ DIR.join("examples/xml/#{name}.xml")
16
33
  end