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,3 @@
1
+ module Representable
2
+ VERSION = "0.0.1.alpha1"
3
+ end
@@ -0,0 +1,79 @@
1
+ module Representable
2
+ module Xml
3
+ module Declarations
4
+ # Sets the name of the XML element that represents this class. Use this
5
+ # to override the default lowercase class name.
6
+ #
7
+ # Example:
8
+ # class BookWithPublisher
9
+ # xml_name :book
10
+ # end
11
+ #
12
+ # Without the xml_name annotation, the XML mapped tag would have been "bookwithpublisher".
13
+ def xml_name(name)
14
+ self.explicit_representation_name = name
15
+ end
16
+
17
+ def xml_accessor(*args) # TODO: remove me, just for back-compat.
18
+ representable_accessor(*args)
19
+ end
20
+
21
+ end
22
+
23
+ module ClassMethods
24
+ #
25
+ # Creates a new Ruby object from XML using mapping information
26
+ # annotated in the class.
27
+ #
28
+ # The input data is either an XML::Node, String, Pathname, or File representing
29
+ # the XML document.
30
+ #
31
+ # Example
32
+ # book = Book.from_xml(File.read("book.xml"))
33
+ # or
34
+ # book = Book.from_xml("<book><name>Beyond Java</name></book>")
35
+ #
36
+ # _initialization_args_ passed into from_xml will be passed into
37
+ # the object's .new, prior to populating the xml_attrs.
38
+ #
39
+ # After the instatiation and xml population
40
+ #
41
+ # See also: xml_initialize
42
+ #
43
+ def from_xml(data, *args)
44
+ xml = Nokogiri::XML::Node.from(data)
45
+
46
+ create_from_xml(*args).tap do |inst|
47
+ refs = representable_attrs.map {|attr| attr.to_ref }
48
+
49
+ refs.each do |ref|
50
+ value = ref.value_in(xml)
51
+
52
+ inst.send(ref.definition.setter, value)
53
+ end
54
+ end
55
+ end
56
+
57
+ private
58
+ def create_from_xml(*args)
59
+ new(*args)
60
+ end
61
+ end
62
+
63
+ module InstanceMethods # :nodoc:
64
+ # Returns an XML object representing this object
65
+ def to_xml(params={})
66
+ params.reverse_merge!(:name => self.class.representation_name)
67
+
68
+ Nokogiri::XML::Node.new(params[:name].to_s, Nokogiri::XML::Document.new).tap do |root|
69
+ refs = self.class.representable_attrs.map {|attr| attr.to_ref }
70
+
71
+ refs.each do |ref|
72
+ value = public_send(ref.accessor) # DISCUSS: eventually move back to Ref.
73
+ ref.update_xml(root, value) if value
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end # Xml
79
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+
5
+ require 'representable/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "representable"
9
+ s.version = Representable::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ["Nick Sutterer"]
12
+ s.email = ["apotonick@gmail.com"]
13
+ s.homepage = "http://representable.apotomo.de"
14
+ s.summary = %q{Maps representation documents from and to Ruby objects. Includes XML and JSON support, plain properties and compositions.}
15
+ s.description = %q{CMaps representation documents from and to Ruby objects. Includes XML and JSON support, plain properties and compositions.}
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency "activesupport", "~> 3.0.0"
23
+ s.add_dependency "hooks"
24
+ s.add_dependency "nokogiri"
25
+ s.add_dependency "i18n"
26
+
27
+ s.add_development_dependency "rspec"
28
+ s.add_development_dependency "test_xml"
29
+ end
@@ -0,0 +1,495 @@
1
+ # encoding: utf-8
2
+ require_relative './spec_helper'
3
+
4
+ describe ROXML::Definition do
5
+ describe "#name_explicit?" do
6
+ it "should indicate whether from option is present" do
7
+ ROXML::Definition.new(:element, :from => 'somewhere').name_explicit?.should be_true
8
+ ROXML::Definition.new(:element).name_explicit?.should be_false
9
+ end
10
+
11
+ it "should not consider name proxies as explicit" do
12
+ ROXML::Definition.new(:element, :from => :attr).name_explicit?.should be_false
13
+ ROXML::Definition.new(:element, :from => :content).name_explicit?.should be_false
14
+ end
15
+ end
16
+
17
+ shared_examples_for "DateTime reference" do
18
+ it "should return nil on empty string" do
19
+ @subject.blocks.first.call(" ").should be_nil
20
+ end
21
+
22
+ it "should return a time version of the string" do
23
+ @subject.blocks.first.call("12:05pm, September 3rd, 1970").to_s == "1970-09-03T12:05:00+00:00"
24
+ end
25
+
26
+ context "when passed an array of values" do
27
+ it "should timify all of them" do
28
+ @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"]
29
+ end
30
+ end
31
+ end
32
+
33
+ shared_examples_for "Date reference" do
34
+ it "should return nil on empty string" do
35
+ @subject.blocks.first.call(" ").should be_nil
36
+ end
37
+
38
+ it "should return a time version of the string" do
39
+ @subject.blocks.first.call("September 3rd, 1970").to_s == "1970-09-03"
40
+ end
41
+
42
+ context "when passed an array of values" do
43
+ it "should timify all of them" do
44
+ @subject.blocks.first.call(["September 3rd, 1970", "1776-07-04"]).map(&:to_s).should == ["1970-09-03", "1776-07-04"]
45
+ end
46
+ end
47
+ end
48
+
49
+ it "should unescape xml entities" do
50
+ ROXML::Definition.new(:questions, :as => []).to_ref(RoxmlObject.new).value_in(%{
51
+ <xml>
52
+ <question>&quot;Wickard &amp; Filburn&quot; &gt;</question>
53
+ <question> &lt; McCulloch &amp; Maryland?</question>
54
+ </xml>
55
+ }).should == ["\"Wickard & Filburn\" >", " < McCulloch & Maryland?"]
56
+ end
57
+
58
+ it "should unescape utf characters in xml" do
59
+ ROXML::Definition.new(:questions, :as => []).to_ref(RoxmlObject.new).value_in(%{
60
+ <xml>
61
+ <question>ROXML\342\204\242</question>
62
+ </xml>
63
+ }).should == ["ROXML™"]
64
+ end
65
+
66
+ describe "attr name" do
67
+ context "when ending with '_at'" do
68
+ context "and without an :as argument" do
69
+ before(:all) do
70
+ @subject = ROXML::Definition.new(:time_at)
71
+ end
72
+ it_should_behave_like "DateTime reference"
73
+ end
74
+ end
75
+
76
+ context "when ending with '_on'" do
77
+ context "and without an :as argument" do
78
+ before(:all) do
79
+ @subject = ROXML::Definition.new(:created_on)
80
+ end
81
+ it_should_behave_like "Date reference"
82
+ end
83
+ end
84
+ end
85
+
86
+ describe ":as" do
87
+ describe "=> []" do
88
+ it "should means array of texts" do
89
+ opts = ROXML::Definition.new(:authors, :as => [])
90
+ opts.array?.should be_true
91
+ opts.sought_type.should == :text
92
+ end
93
+ end
94
+
95
+ describe "=> RoxmlClass" do
96
+ class RoxmlClass
97
+ include ROXML
98
+ end
99
+
100
+ it "should store type" do
101
+ opts = ROXML::Definition.new(:name, :as => RoxmlClass)
102
+ opts.sought_type.should == RoxmlClass
103
+ end
104
+ end
105
+
106
+ describe "=> NonRoxmlClassWithFromXmlDefined" do
107
+ class OctalInteger
108
+ def self.from_xml(val)
109
+ new(Integer(val.content))
110
+ end
111
+ end
112
+
113
+ it "should accept type" do
114
+ opts = ROXML::Definition.new(:name, :as => OctalInteger)
115
+ opts.sought_type.should == OctalInteger
116
+ end
117
+ end
118
+
119
+ describe "=> NonRoxmlClass" do
120
+ it "should fail with a warning" do
121
+ proc { ROXML::Definition.new(:authors, :as => Module) }.should raise_error(ArgumentError)
122
+ end
123
+ end
124
+
125
+ describe "=> [NonRoxmlClass]" do
126
+ it "should raise" do
127
+ proc { ROXML::Definition.new(:authors, :as => [Module]) }.should raise_error(ArgumentError)
128
+ end
129
+ end
130
+
131
+ describe "=> {}" do
132
+ shared_examples_for "hash options declaration" do
133
+ it "should represent a hash" do
134
+ @opts.hash?.should be_true
135
+ end
136
+
137
+ it "should have hash definition" do
138
+ {@opts.hash.key.sought_type => @opts.hash.key.name}.should == @hash_args[:key]
139
+ {@opts.hash.value.sought_type => @opts.hash.value.name}.should == @hash_args[:value]
140
+ end
141
+
142
+ it "should not represent an array" do
143
+ @opts.array?.should be_false
144
+ end
145
+ end
146
+
147
+ describe "hash with attr key and text val" do
148
+ before do
149
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => '@name',
150
+ :value => 'value'})
151
+ @hash_args = {:key => {:attr => 'name'},
152
+ :value => {:text => 'value'}}
153
+ end
154
+
155
+ it_should_behave_like "hash options declaration"
156
+ end
157
+
158
+ describe "hash with String class for type" do
159
+ before do
160
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => 'name',
161
+ :value => 'value'})
162
+ @hash_args = {:key => {:text => 'name'}, :value => {:text => 'value'}}
163
+ end
164
+
165
+ it_should_behave_like "hash options declaration"
166
+ end
167
+
168
+ describe "hash with attr key and content val" do
169
+ before do
170
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => '@name',
171
+ :value => :content})
172
+ @hash_args = {:key => {:attr => 'name'}, :value => {:text => '.'}}
173
+ end
174
+
175
+ it_should_behave_like "hash options declaration"
176
+ end
177
+
178
+ describe "hash with names as keys and content vals" do
179
+ before do
180
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => :name,
181
+ :value => :content})
182
+ @hash_args = {:key => {:text => '*'}, :value => {:text => '.'}}
183
+ end
184
+
185
+ it_should_behave_like "hash options declaration"
186
+ end
187
+ end
188
+
189
+ describe "for block shorthand" do
190
+ describe "in literal array" do
191
+ before do
192
+ @opts = ROXML::Definition.new(:intarray, :as => [Integer])
193
+ end
194
+
195
+ it "should be detected as array reference" do
196
+ @opts.array?.should be_true
197
+ end
198
+
199
+ it "should be normal otherwise" do
200
+ @opts.sought_type.should == :text
201
+ @opts.blocks.size.should == 1
202
+ end
203
+ end
204
+
205
+ it "should have no blocks without a shorthand" do
206
+ ROXML::Definition.new(:count).blocks.should be_empty
207
+ end
208
+
209
+ it "should raise on unknown :as" do
210
+ proc { ROXML::Definition.new(:count, :as => :bogus) }.should raise_error(ArgumentError)
211
+ proc { ROXML::Definition.new(:count, :as => :foat) }.should raise_error(ArgumentError)
212
+ end
213
+
214
+ shared_examples_for "block shorthand type declaration" do
215
+ it "should translate nil to nil" do
216
+ @definition.blocks.first.call(nil).should be_nil
217
+ end
218
+
219
+ it "should translate empty strings to nil" do
220
+ @definition.blocks.first.call("").should be_nil
221
+ @definition.blocks.first.call(" ").should be_nil
222
+ end
223
+ end
224
+
225
+ describe "Integer" do
226
+ before do
227
+ @definition = ROXML::Definition.new(:intvalue, :as => Integer)
228
+ end
229
+
230
+ it_should_behave_like "block shorthand type declaration"
231
+
232
+ it "should translate text to integers" do
233
+ @definition.blocks.first['3'].should == 3
234
+ @definition.blocks.first['792'].should == 792
235
+ end
236
+
237
+ it "should raise on non-integer values" do
238
+ proc { @definition.blocks.first['08'] }.should raise_error(ArgumentError)
239
+ proc { @definition.blocks.first['793.12'] }.should raise_error(ArgumentError)
240
+ proc { @definition.blocks.first['junk 11'] }.should raise_error(ArgumentError)
241
+ proc { @definition.blocks.first['11sttf'] }.should raise_error(ArgumentError)
242
+ end
243
+
244
+ context "when passed an array" do
245
+ it "should translate the array elements to integer" do
246
+ @definition.blocks.first.call(["792", "12", "328"]).should == [792, 12, 328]
247
+ end
248
+ end
249
+ end
250
+
251
+ describe "Float" do
252
+ before do
253
+ @definition = ROXML::Definition.new(:floatvalue, :as => Float)
254
+ end
255
+
256
+ it_should_behave_like "block shorthand type declaration"
257
+
258
+ it "should translate text to float" do
259
+ @definition.blocks.first['3'].should == 3.0
260
+ @definition.blocks.first['12.7'].should == 12.7
261
+ end
262
+
263
+ it "should raise on non-float values" do
264
+ proc { @definition.blocks.first['junk 11.3'] }.should raise_error(ArgumentError)
265
+ proc { @definition.blocks.first['11.1sttf'] }.should raise_error(ArgumentError)
266
+ end
267
+
268
+ context "when passed an array" do
269
+ it "should translate the array elements to integer" do
270
+ @definition.blocks.first.call(["792.13", "240", "3.14"]).should == [792.13, 240.0, 3.14]
271
+ end
272
+ end
273
+ end
274
+
275
+ describe "BigDecimal" do
276
+ before do
277
+ @definition = ROXML::Definition.new(:decimalvalue, :as => BigDecimal)
278
+ end
279
+
280
+ it_should_behave_like "block shorthand type declaration"
281
+
282
+ it "should translate text to decimal numbers" do
283
+ @definition.blocks.first['3'].should == BigDecimal.new("3.0")
284
+ @definition.blocks.first['0.3'].should == BigDecimal.new("0.3")
285
+ end
286
+
287
+ it "should extract what it can, and fall back to 0" do
288
+ @definition.blocks.first['junk 11'].should eql(BigDecimal.new("0"))
289
+ @definition.blocks.first['11sttf'].should eql(BigDecimal.new("11.0"))
290
+ end
291
+
292
+ context "when passed an array" do
293
+ it "should translate the array elements to integer" do
294
+ @definition.blocks.first.call(["12.1", "328.2"]).should == [BigDecimal.new("12.1"), BigDecimal.new("328.2")]
295
+ end
296
+ end
297
+ end
298
+
299
+ describe "Fixnum" do
300
+ before do
301
+ @definition = ROXML::Definition.new(:fixnumvalue, :as => Fixnum)
302
+ end
303
+
304
+ it_should_behave_like "block shorthand type declaration"
305
+
306
+ it "should translate text to integers" do
307
+ @definition.blocks.first['3'].should == 3
308
+ @definition.blocks.first['792'].should == 792
309
+ @definition.blocks.first['08'].should == 8
310
+ @definition.blocks.first['279.23'].should == 279
311
+ end
312
+
313
+ it "should extract whatever is possible and fall back to 0" do
314
+ @definition.blocks.first['junk 11'].should eql(0)
315
+ @definition.blocks.first['.?sttf'].should eql(0)
316
+ @definition.blocks.first['11sttf'].should eql(11)
317
+ end
318
+
319
+ context "when passed an array" do
320
+ it "should translate the array elements to integer" do
321
+ @definition.blocks.first.call(["792", "12", "328"]).should == [792, 12, 328]
322
+ end
323
+ end
324
+ end
325
+
326
+ describe ":bool" do
327
+ it "should boolify individual values" do
328
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("1").should be_true
329
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("True").should be_true
330
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("Yes").should be_true
331
+ end
332
+
333
+ context "when an array is passed in" do
334
+ it "should boolify arrays of values" do
335
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("0").should be_false
336
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("false").should be_false
337
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("nO").should be_false
338
+ end
339
+ end
340
+
341
+ context "when no value is detected" do
342
+ it "should return nil" do
343
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("junk").should be_nil
344
+ end
345
+
346
+ context "when a literal block is available" do
347
+ it "should pass the value itself to the block"
348
+ end
349
+ end
350
+ end
351
+
352
+ describe "Time" do
353
+ it "should return nil on empty string" do
354
+ ROXML::Definition.new(:floatvalue, :as => Time).blocks.first.call(" ").should be_nil
355
+ end
356
+
357
+ it "should return a time version of the string" do
358
+ ROXML::Definition.new(:datevalue, :as => Time).blocks.first.call("12:31am").min.should == 31
359
+ end
360
+
361
+ context "when passed an array of values" do
362
+ it "should timify all of them" do
363
+ ROXML::Definition.new(:datevalue, :as => Time).blocks.first.call(["12:31am", "3:00pm", "11:59pm"]).map(&:min).should == [31, 0, 59]
364
+ end
365
+ end
366
+ end
367
+
368
+ describe "Date" do
369
+ before do
370
+ @subject = ROXML::Definition.new(:datevalue, :as => Date)
371
+ end
372
+ it_should_behave_like "Date reference"
373
+ end
374
+
375
+ describe "DateTime" do
376
+ before do
377
+ @subject = ROXML::Definition.new(:datevalue, :as => DateTime)
378
+ end
379
+ it_should_behave_like "DateTime reference"
380
+ end
381
+
382
+ it "should prohibit multiple shorthands" do
383
+ proc { ROXML::Definition.new(:count, :as => [Float, Integer]) }.should raise_error(ArgumentError)
384
+ end
385
+
386
+ it "should stack block shorthands with explicit blocks" do
387
+ ROXML::Definition.new(:count, :as => Integer) {|val| val.to_i }.blocks.size.should == 2
388
+ ROXML::Definition.new(:count, :as => Float) {|val| val.object_id }.blocks.size.should == 2
389
+ end
390
+ end
391
+ end
392
+
393
+ describe ":from" do
394
+ shared_examples_for "attribute reference" do
395
+ it "should be interpreted as :attr" do
396
+ @opts.sought_type.should == :attr
397
+ end
398
+
399
+ it "should strip '@' from name" do
400
+ @opts.name.should == 'attr_name'
401
+ end
402
+
403
+ it "should unescape xml entities" do
404
+ @opts.to_ref(RoxmlObject.new).value_in(%{
405
+ <question attr_name="&quot;Wickard &amp; Filburn&quot; &gt; / &lt; McCulloch &amp; Marryland?" />
406
+ }).should == "\"Wickard & Filburn\" > / < McCulloch & Marryland?"
407
+ end
408
+ end
409
+
410
+ context ":attr" do
411
+ before do
412
+ @opts = ROXML::Definition.new(:attr_name, :from => :attr)
413
+ end
414
+
415
+ it_should_behave_like "attribute reference"
416
+ end
417
+
418
+ context "@attribute_name" do
419
+ before do
420
+ @opts = ROXML::Definition.new(:attr_name, :from => '@attr_name')
421
+ end
422
+
423
+ it_should_behave_like "attribute reference"
424
+ end
425
+
426
+ describe ":content" do
427
+ it "should be recognized" do
428
+ ROXML::Definition.new(:author).content?.should be_false
429
+ ROXML::Definition.new(:author, :from => :content).content?.should == true
430
+ end
431
+
432
+ it "should be equivalent to :from => '.'" do
433
+ ROXML::Definition.new(:author, :from => '.').content?.should == true
434
+ end
435
+ end
436
+ end
437
+
438
+ describe ":in" do
439
+ context "as xpath" do
440
+ it "should pass through as wrapper" do
441
+ ROXML::Definition.new(:manufacturer, :in => './').wrapper.should == './'
442
+ end
443
+ end
444
+
445
+ context "as xpath" do
446
+ it "should pass through as wrapper" do
447
+ ROXML::Definition.new(:manufacturer, :in => 'wrapper').wrapper.should == 'wrapper'
448
+ end
449
+ end
450
+ end
451
+
452
+ describe "options" do
453
+
454
+ shared_examples_for "boolean option" do
455
+ it "should be recognized" do
456
+ ROXML::Definition.new(:author, :from => :content, @option => true).respond_to?(:"#{@option}?")
457
+ ROXML::Definition.new(:author, :from => :content, @option => true).send(:"#{@option}?").should be_true
458
+ ROXML::Definition.new(:author, :from => :content, @option => false).send(:"#{@option}?").should be_false
459
+ end
460
+
461
+ it "should default to false" do
462
+ ROXML::Definition.new(:author, :from => :content).send(:"#{@option}?").should be_false
463
+ end
464
+ end
465
+
466
+ describe ":required" do
467
+ before do
468
+ @option = :required
469
+ end
470
+
471
+ it_should_behave_like "boolean option"
472
+
473
+ it "should not be allowed together with :else" do
474
+ proc { ROXML::Definition.new(:author, :from => :content, :required => true, :else => 'Johnny') }.should raise_error(ArgumentError)
475
+ proc { ROXML::Definition.new(:author, :from => :content, :required => false, :else => 'Johnny') }.should_not raise_error
476
+ end
477
+ end
478
+
479
+ describe ":frozen" do
480
+ before do
481
+ @option = :frozen
482
+ end
483
+
484
+ it_should_behave_like "boolean option"
485
+ end
486
+
487
+ describe ":cdata" do
488
+ before do
489
+ @option = :cdata
490
+ end
491
+
492
+ it_should_behave_like "boolean option"
493
+ end
494
+ end
495
+ end