ROXML 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/.gitignore +6 -0
  2. data/.gitmodules +3 -0
  3. data/History.txt +299 -0
  4. data/MIT-LICENSE +18 -0
  5. data/README.rdoc +161 -0
  6. data/Rakefile +95 -0
  7. data/TODO +39 -0
  8. data/VERSION +1 -0
  9. data/config/website.yml +2 -0
  10. data/examples/amazon.rb +35 -0
  11. data/examples/current_weather.rb +27 -0
  12. data/examples/dashed_elements.rb +20 -0
  13. data/examples/library.rb +40 -0
  14. data/examples/posts.rb +27 -0
  15. data/examples/rails.rb +70 -0
  16. data/examples/twitter.rb +37 -0
  17. data/examples/xml/active_record.xml +70 -0
  18. data/examples/xml/amazon.xml +133 -0
  19. data/examples/xml/current_weather.xml +89 -0
  20. data/examples/xml/dashed_elements.xml +52 -0
  21. data/examples/xml/posts.xml +23 -0
  22. data/examples/xml/twitter.xml +422 -0
  23. data/lib/roxml.rb +547 -0
  24. data/lib/roxml/definition.rb +236 -0
  25. data/lib/roxml/hash_definition.rb +25 -0
  26. data/lib/roxml/xml.rb +43 -0
  27. data/lib/roxml/xml/parsers/libxml.rb +91 -0
  28. data/lib/roxml/xml/parsers/nokogiri.rb +77 -0
  29. data/lib/roxml/xml/references.rb +297 -0
  30. data/roxml.gemspec +201 -0
  31. data/spec/definition_spec.rb +486 -0
  32. data/spec/examples/active_record_spec.rb +40 -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_spec.rb +372 -0
  40. data/spec/shared_specs.rb +15 -0
  41. data/spec/spec.opts +1 -0
  42. data/spec/spec_helper.rb +14 -0
  43. data/spec/support/libxml.rb +3 -0
  44. data/spec/support/nokogiri.rb +3 -0
  45. data/spec/xml/attributes_spec.rb +36 -0
  46. data/spec/xml/namespace_spec.rb +240 -0
  47. data/spec/xml/namespaces_spec.rb +32 -0
  48. data/spec/xml/parser_spec.rb +26 -0
  49. data/tasks/rdoc.rake +13 -0
  50. data/tasks/rspec.rake +25 -0
  51. data/tasks/test.rake +35 -0
  52. data/test/fixtures/book_malformed.xml +5 -0
  53. data/test/fixtures/book_pair.xml +8 -0
  54. data/test/fixtures/book_text_with_attribute.xml +5 -0
  55. data/test/fixtures/book_valid.xml +5 -0
  56. data/test/fixtures/book_with_authors.xml +7 -0
  57. data/test/fixtures/book_with_contributions.xml +9 -0
  58. data/test/fixtures/book_with_contributors.xml +7 -0
  59. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  60. data/test/fixtures/book_with_default_namespace.xml +9 -0
  61. data/test/fixtures/book_with_depth.xml +6 -0
  62. data/test/fixtures/book_with_octal_pages.xml +4 -0
  63. data/test/fixtures/book_with_publisher.xml +7 -0
  64. data/test/fixtures/book_with_wrapped_attr.xml +3 -0
  65. data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
  66. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  67. data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
  68. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  69. data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
  70. data/test/fixtures/dictionary_of_names.xml +4 -0
  71. data/test/fixtures/dictionary_of_texts.xml +10 -0
  72. data/test/fixtures/library.xml +30 -0
  73. data/test/fixtures/library_uppercase.xml +30 -0
  74. data/test/fixtures/muffins.xml +3 -0
  75. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  76. data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
  77. data/test/fixtures/node_with_name_conflicts.xml +4 -0
  78. data/test/fixtures/numerology.xml +4 -0
  79. data/test/fixtures/person.xml +1 -0
  80. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  81. data/test/fixtures/person_with_mothers.xml +10 -0
  82. data/test/mocks/dictionaries.rb +57 -0
  83. data/test/mocks/mocks.rb +279 -0
  84. data/test/support/fixtures.rb +11 -0
  85. data/test/test_helper.rb +34 -0
  86. data/test/unit/definition_test.rb +235 -0
  87. data/test/unit/deprecations_test.rb +24 -0
  88. data/test/unit/to_xml_test.rb +81 -0
  89. data/test/unit/xml_attribute_test.rb +39 -0
  90. data/test/unit/xml_block_test.rb +81 -0
  91. data/test/unit/xml_bool_test.rb +122 -0
  92. data/test/unit/xml_convention_test.rb +150 -0
  93. data/test/unit/xml_hash_test.rb +115 -0
  94. data/test/unit/xml_initialize_test.rb +49 -0
  95. data/test/unit/xml_name_test.rb +141 -0
  96. data/test/unit/xml_namespace_test.rb +31 -0
  97. data/test/unit/xml_object_test.rb +207 -0
  98. data/test/unit/xml_required_test.rb +94 -0
  99. data/test/unit/xml_text_test.rb +71 -0
  100. data/website/index.html +98 -0
  101. metadata +254 -0
@@ -0,0 +1,486 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ROXML::Definition do
4
+ describe "#name_explicit?" do
5
+ it "should indicate whether from option is present" do
6
+ ROXML::Definition.new(:element, :from => 'somewhere').name_explicit?.should be_true
7
+ ROXML::Definition.new(:element).name_explicit?.should be_false
8
+ end
9
+
10
+ it "should not consider name proxies as explicit" do
11
+ ROXML::Definition.new(:element, :from => :attr).name_explicit?.should be_false
12
+ ROXML::Definition.new(:element, :from => :content).name_explicit?.should be_false
13
+ end
14
+ end
15
+
16
+ describe "DateTime reference", :shared => true do
17
+ it "should return nil on empty string" do
18
+ @subject.blocks.first.call(" ").should be_nil
19
+ end
20
+
21
+ it "should return a time version of the string" do
22
+ @subject.blocks.first.call("12:05pm, September 3rd, 1970").to_s == "1970-09-03T12:05:00+00:00"
23
+ end
24
+
25
+ context "when passed an array of values" do
26
+ it "should timify all of them" do
27
+ @subject.blocks.first.call(["12:05pm, September 3rd, 1970", "3:00pm, May 22, 1700"]).map(&:to_s).should == ["1970-09-03T12:05:00+00:00", "1700-05-22T15:00:00+00:00"]
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "Date reference", :shared => true do
33
+ it "should return nil on empty string" do
34
+ @subject.blocks.first.call(" ").should be_nil
35
+ end
36
+
37
+ it "should return a time version of the string" do
38
+ @subject.blocks.first.call("September 3rd, 1970").to_s == "1970-09-03"
39
+ end
40
+
41
+ context "when passed an array of values" do
42
+ it "should timify all of them" do
43
+ @subject.blocks.first.call(["September 3rd, 1970", "1776-07-04"]).map(&:to_s).should == ["1970-09-03", "1776-07-04"]
44
+ end
45
+ end
46
+ end
47
+
48
+ it "should unescape xml entities" do
49
+ ROXML::Definition.new(:questions, :as => []).to_ref(RoxmlObject.new).value_in(%{
50
+ <xml>
51
+ <question>&quot;Wickard &amp; Filburn&quot; &gt;</question>
52
+ <question> &lt; McCulloch &amp; Maryland?</question>
53
+ </xml>
54
+ }).should == ["\"Wickard & Filburn\" >", "< McCulloch & Maryland?"]
55
+ end
56
+
57
+ describe "attr name" do
58
+ context "when ending with '_at'" do
59
+ context "and without an :as argument" do
60
+ before(:all) do
61
+ @subject = ROXML::Definition.new(:time_at)
62
+ end
63
+ it_should_behave_like "DateTime reference"
64
+ end
65
+ end
66
+
67
+ context "when ending with '_on'" do
68
+ context "and without an :as argument" do
69
+ before(:all) do
70
+ @subject = ROXML::Definition.new(:created_on)
71
+ end
72
+ it_should_behave_like "Date reference"
73
+ end
74
+ end
75
+ end
76
+
77
+ describe ":as" do
78
+ describe "=> []" do
79
+ it "should means array of texts" do
80
+ opts = ROXML::Definition.new(:authors, :as => [])
81
+ opts.array?.should be_true
82
+ opts.type.should == :text
83
+ end
84
+ end
85
+
86
+ describe "=> RoxmlClass" do
87
+ class RoxmlClass
88
+ include ROXML
89
+ end
90
+
91
+ it "should store type" do
92
+ opts = ROXML::Definition.new(:name, :as => RoxmlClass)
93
+ opts.type.should == RoxmlClass
94
+ end
95
+ end
96
+
97
+ describe "=> NonRoxmlClassWithFromXmlDefined" do
98
+ class OctalInteger
99
+ def self.from_xml(val)
100
+ new(Integer(val.content))
101
+ end
102
+ end
103
+
104
+ it "should accept type" do
105
+ opts = ROXML::Definition.new(:name, :as => OctalInteger)
106
+ opts.type.should == OctalInteger
107
+ end
108
+ end
109
+
110
+ describe "=> NonRoxmlClass" do
111
+ it "should fail with a warning" do
112
+ proc { ROXML::Definition.new(:authors, :as => Module) }.should raise_error(ArgumentError)
113
+ end
114
+ end
115
+
116
+ describe "=> [NonRoxmlClass]" do
117
+ it "should raise" do
118
+ proc { ROXML::Definition.new(:authors, :as => [Module]) }.should raise_error(ArgumentError)
119
+ end
120
+ end
121
+
122
+ describe "=> {}" do
123
+ describe "hash options declaration", :shared => true do
124
+ it "should represent a hash" do
125
+ @opts.hash?.should be_true
126
+ end
127
+
128
+ it "should have hash definition" do
129
+ {@opts.hash.key.type => @opts.hash.key.name}.should == @hash_args[:key]
130
+ {@opts.hash.value.type => @opts.hash.value.name}.should == @hash_args[:value]
131
+ end
132
+
133
+ it "should not represent an array" do
134
+ @opts.array?.should be_false
135
+ end
136
+ end
137
+
138
+ describe "hash with attr key and text val" do
139
+ before do
140
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => '@name',
141
+ :value => 'value'})
142
+ @hash_args = {:key => {:attr => 'name'},
143
+ :value => {:text => 'value'}}
144
+ end
145
+
146
+ it_should_behave_like "hash options declaration"
147
+ end
148
+
149
+ describe "hash with String class for type" do
150
+ before do
151
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => 'name',
152
+ :value => 'value'})
153
+ @hash_args = {:key => {:text => 'name'}, :value => {:text => 'value'}}
154
+ end
155
+
156
+ it_should_behave_like "hash options declaration"
157
+ end
158
+
159
+ describe "hash with attr key and content val" do
160
+ before do
161
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => '@name',
162
+ :value => :content})
163
+ @hash_args = {:key => {:attr => 'name'}, :value => {:text => '.'}}
164
+ end
165
+
166
+ it_should_behave_like "hash options declaration"
167
+ end
168
+
169
+ describe "hash with names as keys and content vals" do
170
+ before do
171
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => :name,
172
+ :value => :content})
173
+ @hash_args = {:key => {:text => '*'}, :value => {:text => '.'}}
174
+ end
175
+
176
+ it_should_behave_like "hash options declaration"
177
+ end
178
+ end
179
+
180
+ describe "for block shorthand" do
181
+ describe "in literal array" do
182
+ before do
183
+ @opts = ROXML::Definition.new(:intarray, :as => [Integer])
184
+ end
185
+
186
+ it "should be detected as array reference" do
187
+ @opts.array?.should be_true
188
+ end
189
+
190
+ it "should be normal otherwise" do
191
+ @opts.type.should == :text
192
+ @opts.blocks.size.should == 1
193
+ end
194
+ end
195
+
196
+ it "should have no blocks without a shorthand" do
197
+ ROXML::Definition.new(:count).blocks.should be_empty
198
+ end
199
+
200
+ it "should raise on unknown :as" do
201
+ proc { ROXML::Definition.new(:count, :as => :bogus) }.should raise_error(ArgumentError)
202
+ proc { ROXML::Definition.new(:count, :as => :foat) }.should raise_error(ArgumentError)
203
+ end
204
+
205
+ describe "block shorthand type declaration", :shared => true do
206
+ it "should translate nil to nil" do
207
+ @definition.blocks.first.call(nil).should be_nil
208
+ end
209
+
210
+ it "should translate empty strings to nil" do
211
+ @definition.blocks.first.call("").should be_nil
212
+ @definition.blocks.first.call(" ").should be_nil
213
+ end
214
+ end
215
+
216
+ describe "Integer" do
217
+ before do
218
+ @definition = ROXML::Definition.new(:intvalue, :as => Integer)
219
+ end
220
+
221
+ it_should_behave_like "block shorthand type declaration"
222
+
223
+ it "should translate text to integers" do
224
+ @definition.blocks.first['3'].should == 3
225
+ @definition.blocks.first['792'].should == 792
226
+ end
227
+
228
+ it "should raise on non-integer values" do
229
+ proc { @definition.blocks.first['08'] }.should raise_error(ArgumentError)
230
+ proc { @definition.blocks.first['793.12'] }.should raise_error(ArgumentError)
231
+ proc { @definition.blocks.first['junk 11'] }.should raise_error(ArgumentError)
232
+ proc { @definition.blocks.first['11sttf'] }.should raise_error(ArgumentError)
233
+ end
234
+
235
+ context "when passed an array" do
236
+ it "should translate the array elements to integer" do
237
+ @definition.blocks.first.call(["792", "12", "328"]).should == [792, 12, 328]
238
+ end
239
+ end
240
+ end
241
+
242
+ describe "Float" do
243
+ before do
244
+ @definition = ROXML::Definition.new(:floatvalue, :as => Float)
245
+ end
246
+
247
+ it_should_behave_like "block shorthand type declaration"
248
+
249
+ it "should translate text to float" do
250
+ @definition.blocks.first['3'].should == 3.0
251
+ @definition.blocks.first['12.7'].should == 12.7
252
+ end
253
+
254
+ it "should raise on non-float values" do
255
+ proc { @definition.blocks.first['junk 11.3'] }.should raise_error(ArgumentError)
256
+ proc { @definition.blocks.first['11.1sttf'] }.should raise_error(ArgumentError)
257
+ end
258
+
259
+ context "when passed an array" do
260
+ it "should translate the array elements to integer" do
261
+ @definition.blocks.first.call(["792.13", "240", "3.14"]).should == [792.13, 240.0, 3.14]
262
+ end
263
+ end
264
+ end
265
+
266
+ describe "BigDecimal" do
267
+ before do
268
+ @definition = ROXML::Definition.new(:decimalvalue, :as => BigDecimal)
269
+ end
270
+
271
+ it_should_behave_like "block shorthand type declaration"
272
+
273
+ it "should translate text to decimal numbers" do
274
+ @definition.blocks.first['3'].should == BigDecimal.new("3.0")
275
+ @definition.blocks.first['0.3'].should == BigDecimal.new("0.3")
276
+ end
277
+
278
+ it "should extract what it can, and fall back to 0" do
279
+ @definition.blocks.first['junk 11'].should eql(BigDecimal.new("0"))
280
+ @definition.blocks.first['11sttf'].should eql(BigDecimal.new("11.0"))
281
+ end
282
+
283
+ context "when passed an array" do
284
+ it "should translate the array elements to integer" do
285
+ @definition.blocks.first.call(["12.1", "328.2"]).should == [BigDecimal.new("12.1"), BigDecimal.new("328.2")]
286
+ end
287
+ end
288
+ end
289
+
290
+ describe "Fixnum" do
291
+ before do
292
+ @definition = ROXML::Definition.new(:fixnumvalue, :as => Fixnum)
293
+ end
294
+
295
+ it_should_behave_like "block shorthand type declaration"
296
+
297
+ it "should translate text to integers" do
298
+ @definition.blocks.first['3'].should == 3
299
+ @definition.blocks.first['792'].should == 792
300
+ @definition.blocks.first['08'].should == 8
301
+ @definition.blocks.first['279.23'].should == 279
302
+ end
303
+
304
+ it "should extract whatever is possible and fall back to 0" do
305
+ @definition.blocks.first['junk 11'].should eql(0)
306
+ @definition.blocks.first['.?sttf'].should eql(0)
307
+ @definition.blocks.first['11sttf'].should eql(11)
308
+ end
309
+
310
+ context "when passed an array" do
311
+ it "should translate the array elements to integer" do
312
+ @definition.blocks.first.call(["792", "12", "328"]).should == [792, 12, 328]
313
+ end
314
+ end
315
+ end
316
+
317
+ describe ":bool" do
318
+ it "should boolify individual values" do
319
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("1").should be_true
320
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("True").should be_true
321
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("Yes").should be_true
322
+ end
323
+
324
+ context "when an array is passed in" do
325
+ it "should boolify arrays of values" do
326
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("0").should be_false
327
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("false").should be_false
328
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("nO").should be_false
329
+ end
330
+ end
331
+
332
+ context "when no value is detected" do
333
+ it "should return nil" do
334
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("junk").should be_nil
335
+ end
336
+
337
+ context "when a literal block is available" do
338
+ it "should pass the value itself to the block"
339
+ end
340
+ end
341
+ end
342
+
343
+ describe "Time" do
344
+ it "should return nil on empty string" do
345
+ ROXML::Definition.new(:floatvalue, :as => Time).blocks.first.call(" ").should be_nil
346
+ end
347
+
348
+ it "should return a time version of the string" do
349
+ ROXML::Definition.new(:datevalue, :as => Time).blocks.first.call("12:31am").min.should == 31
350
+ end
351
+
352
+ context "when passed an array of values" do
353
+ it "should timify all of them" do
354
+ ROXML::Definition.new(:datevalue, :as => Time).blocks.first.call(["12:31am", "3:00pm", "11:59pm"]).map(&:min).should == [31, 0, 59]
355
+ end
356
+ end
357
+ end
358
+
359
+ describe "Date" do
360
+ before do
361
+ @subject = ROXML::Definition.new(:datevalue, :as => Date)
362
+ end
363
+ it_should_behave_like "Date reference"
364
+ end
365
+
366
+ describe "DateTime" do
367
+ before do
368
+ @subject = ROXML::Definition.new(:datevalue, :as => DateTime)
369
+ end
370
+ it_should_behave_like "DateTime reference"
371
+ end
372
+
373
+ it "should prohibit multiple shorthands" do
374
+ proc { ROXML::Definition.new(:count, :as => [Float, Integer]) }.should raise_error(ArgumentError)
375
+ end
376
+
377
+ it "should stack block shorthands with explicit blocks" do
378
+ ROXML::Definition.new(:count, :as => Integer) {|val| val.to_i }.blocks.size.should == 2
379
+ ROXML::Definition.new(:count, :as => Float) {|val| val.object_id }.blocks.size.should == 2
380
+ end
381
+ end
382
+ end
383
+
384
+ describe ":from" do
385
+ describe "attribute reference", :shared => true do
386
+ it "should be interpreted as :attr" do
387
+ @opts.type.should == :attr
388
+ end
389
+
390
+ it "should strip '@' from name" do
391
+ @opts.name.should == 'attr_name'
392
+ end
393
+
394
+ it "should unescape xml entities" do
395
+ @opts.to_ref(RoxmlObject.new).value_in(%{
396
+ <question attr_name="&quot;Wickard &amp; Filburn&quot; &gt; / &lt; McCulloch &amp; Marryland?" />
397
+ }).should == "\"Wickard & Filburn\" > / < McCulloch & Marryland?"
398
+ end
399
+ end
400
+
401
+ context ":attr" do
402
+ before do
403
+ @opts = ROXML::Definition.new(:attr_name, :from => :attr)
404
+ end
405
+
406
+ it_should_behave_like "attribute reference"
407
+ end
408
+
409
+ context "@attribute_name" do
410
+ before do
411
+ @opts = ROXML::Definition.new(:attr_name, :from => '@attr_name')
412
+ end
413
+
414
+ it_should_behave_like "attribute reference"
415
+ end
416
+
417
+ describe ":content" do
418
+ it "should be recognized" do
419
+ ROXML::Definition.new(:author).content?.should be_false
420
+ ROXML::Definition.new(:author, :from => :content).content?.should == true
421
+ end
422
+
423
+ it "should be equivalent to :from => '.'" do
424
+ ROXML::Definition.new(:author, :from => '.').content?.should == true
425
+ end
426
+ end
427
+ end
428
+
429
+ describe ":in" do
430
+ context "as xpath" do
431
+ it "should pass through as wrapper" do
432
+ ROXML::Definition.new(:manufacturer, :in => './').wrapper.should == './'
433
+ end
434
+ end
435
+
436
+ context "as xpath" do
437
+ it "should pass through as wrapper" do
438
+ ROXML::Definition.new(:manufacturer, :in => 'wrapper').wrapper.should == 'wrapper'
439
+ end
440
+ end
441
+ end
442
+
443
+ describe "options" do
444
+
445
+ describe "boolean option", :shared => true do
446
+ it "should be recognized" do
447
+ ROXML::Definition.new(:author, :from => :content, @option => true).respond_to?(:"#{@option}?")
448
+ ROXML::Definition.new(:author, :from => :content, @option => true).send(:"#{@option}?").should be_true
449
+ ROXML::Definition.new(:author, :from => :content, @option => false).send(:"#{@option}?").should be_false
450
+ end
451
+
452
+ it "should default to false" do
453
+ ROXML::Definition.new(:author, :from => :content).send(:"#{@option}?").should be_false
454
+ end
455
+ end
456
+
457
+ describe ":required" do
458
+ before do
459
+ @option = :required
460
+ end
461
+
462
+ it_should_behave_like "boolean option"
463
+
464
+ it "should not be allowed together with :else" do
465
+ proc { ROXML::Definition.new(:author, :from => :content, :required => true, :else => 'Johnny') }.should raise_error(ArgumentError)
466
+ proc { ROXML::Definition.new(:author, :from => :content, :required => false, :else => 'Johnny') }.should_not raise_error
467
+ end
468
+ end
469
+
470
+ describe ":frozen" do
471
+ before do
472
+ @option = :frozen
473
+ end
474
+
475
+ it_should_behave_like "boolean option"
476
+ end
477
+
478
+ describe ":cdata" do
479
+ before do
480
+ @option = :cdata
481
+ end
482
+
483
+ it_should_behave_like "boolean option"
484
+ end
485
+ end
486
+ end