ROXML 3.0.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 (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