doxo-roxml 2.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/History.txt +279 -0
  2. data/MIT-LICENSE +18 -0
  3. data/Manifest.txt +103 -0
  4. data/README.rdoc +158 -0
  5. data/Rakefile +96 -0
  6. data/TODO +62 -0
  7. data/config/website.yml +2 -0
  8. data/examples/active_record.rb +70 -0
  9. data/examples/amazon.rb +33 -0
  10. data/examples/current_weather.rb +27 -0
  11. data/examples/dashed_elements.rb +20 -0
  12. data/examples/library.rb +40 -0
  13. data/examples/posts.rb +27 -0
  14. data/examples/twitter.rb +37 -0
  15. data/examples/xml/active_record.xml +70 -0
  16. data/examples/xml/amazon.xml +133 -0
  17. data/examples/xml/current_weather.xml +89 -0
  18. data/examples/xml/dashed_elements.xml +52 -0
  19. data/examples/xml/posts.xml +23 -0
  20. data/examples/xml/twitter.xml +422 -0
  21. data/lib/roxml.rb +511 -0
  22. data/lib/roxml/definition.rb +234 -0
  23. data/lib/roxml/extensions.rb +6 -0
  24. data/lib/roxml/extensions/array.rb +13 -0
  25. data/lib/roxml/extensions/array/conversions.rb +12 -0
  26. data/lib/roxml/extensions/deprecation.rb +33 -0
  27. data/lib/roxml/extensions/string.rb +6 -0
  28. data/lib/roxml/extensions/string/conversions.rb +5 -0
  29. data/lib/roxml/extensions/string/iterators.rb +12 -0
  30. data/lib/roxml/hash_definition.rb +25 -0
  31. data/lib/roxml/xml.rb +40 -0
  32. data/lib/roxml/xml/parsers/libxml.rb +86 -0
  33. data/lib/roxml/xml/parsers/rexml.rb +84 -0
  34. data/lib/roxml/xml/references.rb +299 -0
  35. data/roxml.gemspec +50 -0
  36. data/spec/definition_spec.rb +490 -0
  37. data/spec/examples/active_record_spec.rb +40 -0
  38. data/spec/examples/amazon_spec.rb +53 -0
  39. data/spec/examples/current_weather_spec.rb +37 -0
  40. data/spec/examples/dashed_elements_spec.rb +20 -0
  41. data/spec/examples/library_spec.rb +46 -0
  42. data/spec/examples/post_spec.rb +24 -0
  43. data/spec/examples/twitter_spec.rb +32 -0
  44. data/spec/roxml_spec.rb +372 -0
  45. data/spec/shared_specs.rb +15 -0
  46. data/spec/spec.opts +1 -0
  47. data/spec/spec_helper.rb +33 -0
  48. data/spec/xml/parser_spec.rb +47 -0
  49. data/tasks/rspec.rake +21 -0
  50. data/tasks/test.rake +42 -0
  51. data/test/bugs/rexml_bugs.rb +15 -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/test_helper.rb +45 -0
  85. data/test/unit/definition_test.rb +235 -0
  86. data/test/unit/deprecations_test.rb +24 -0
  87. data/test/unit/to_xml_test.rb +81 -0
  88. data/test/unit/xml_attribute_test.rb +39 -0
  89. data/test/unit/xml_block_test.rb +81 -0
  90. data/test/unit/xml_bool_test.rb +122 -0
  91. data/test/unit/xml_convention_test.rb +150 -0
  92. data/test/unit/xml_hash_test.rb +115 -0
  93. data/test/unit/xml_initialize_test.rb +49 -0
  94. data/test/unit/xml_name_test.rb +141 -0
  95. data/test/unit/xml_namespace_test.rb +76 -0
  96. data/test/unit/xml_object_test.rb +207 -0
  97. data/test/unit/xml_required_test.rb +93 -0
  98. data/test/unit/xml_text_test.rb +71 -0
  99. data/vendor/override_rake_task/README +30 -0
  100. data/vendor/override_rake_task/init.rb +1 -0
  101. data/vendor/override_rake_task/install.rb +46 -0
  102. data/vendor/override_rake_task/lib/override_rake_task.rb +16 -0
  103. data/website/index.html +98 -0
  104. metadata +234 -0
@@ -0,0 +1,84 @@
1
+ require 'rexml/document'
2
+
3
+ module ROXML
4
+ module XML # :nodoc:all
5
+ Document = REXML::Document
6
+ Node = REXML::Element
7
+
8
+ module Error
9
+ def self.reset_handler
10
+ # noop
11
+ end
12
+ end
13
+ [REXML::ParseException, REXML::UndefinedNamespaceException, REXML::Validation::ValidationException].each do |exception|
14
+ exception.send(:include, Error)
15
+ end
16
+
17
+ class Node
18
+ class << self
19
+ def new_cdata(content)
20
+ REXML::CData.new(content)
21
+ end
22
+ end
23
+
24
+ alias_attribute :content, :text
25
+
26
+ def search(xpath)
27
+ begin
28
+ REXML::XPath.match(self, xpath)
29
+ rescue Exception => ex
30
+ raise ex, xpath
31
+ end
32
+ end
33
+
34
+ def add_child(element)
35
+ if element.is_a?(REXML::CData)
36
+ REXML::CData.new(element, true, self)
37
+ else
38
+ add_element(element)
39
+ end
40
+ end
41
+
42
+ def ==(other)
43
+ to_s == other.to_s
44
+ end
45
+ end
46
+
47
+ class Parser
48
+ class << self
49
+ def parse(source)
50
+ REXML::Document.new(source, :ignore_whitespace_nodes => :all)
51
+ end
52
+
53
+ def parse_file(path) #:nodoc:
54
+ path = path.sub('file:', '') if path.starts_with?('file:')
55
+ parse(open(path))
56
+ end
57
+
58
+ def parse_io(path) #:nodoc:
59
+ parse(path)
60
+ end
61
+
62
+ def register_error_handler(&block)
63
+ end
64
+ end
65
+ ParseError = REXML::ParseException
66
+ end
67
+
68
+ class Document
69
+ delegate :search, :to => :root
70
+
71
+ def root=(node)
72
+ raise ArgumentError, "Root is already defined" if root
73
+ add(node)
74
+ end
75
+
76
+ def save(destination, opts = {:formatter => REXML::Formatters::Default.new})
77
+ self << REXML::XMLDecl.new unless xml_decl != REXML::XMLDecl.default # always output xml declaration
78
+ File.open(destination, "w") do |f|
79
+ opts[:formatter].write(self, f)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,299 @@
1
+ module ROXML
2
+ class RequiredElementMissing < Exception # :nodoc:
3
+ end
4
+
5
+ #
6
+ # Internal base class that represents an XML - Class binding.
7
+ #
8
+ class XMLRef # :nodoc:
9
+ delegate :required?, :array?, :blocks, :accessor, :default, :to => :opts
10
+
11
+ def initialize(opts, instance)
12
+ @opts = opts
13
+ @instance = instance
14
+ end
15
+
16
+ def to_xml
17
+ val = @instance.__send__(accessor)
18
+ opts.to_xml.respond_to?(:call) ? opts.to_xml.call(val) : val
19
+ end
20
+
21
+ def update_xml(xml, value)
22
+ returning wrap(xml) do |xml|
23
+ write_xml(xml, value)
24
+ end
25
+ end
26
+
27
+ def name
28
+ opts.name_explicit? ? opts.name : conventionize(opts.name)
29
+ end
30
+
31
+ def xpath_name
32
+ if !opts.name_explicit? && namespace = @instance.class.roxml_namespace
33
+ "#{namespace}:#{name}"
34
+ else
35
+ name
36
+ end
37
+ end
38
+
39
+ def value_in(xml)
40
+ xml = XML::Node.from(xml)
41
+ value = fetch_value(xml)
42
+ value = default if value.nil?
43
+
44
+ freeze(apply_blocks(value))
45
+ end
46
+
47
+ private
48
+ attr_reader :opts
49
+
50
+ def conventionize(what)
51
+ convention ||= @instance.class.respond_to?(:roxml_naming_convention) && @instance.class.roxml_naming_convention
52
+ if !what.blank? && convention.respond_to?(:call)
53
+ URI.unescape(convention.call(URI.escape(what, /\/|::/)))
54
+ else
55
+ what
56
+ end
57
+ end
58
+
59
+ def wrapper
60
+ conventionize(opts.wrapper)
61
+ end
62
+
63
+ def apply_blocks(val)
64
+ begin
65
+ blocks.apply_to(val)
66
+ rescue Exception => ex
67
+ raise ex, "#{accessor}: #{ex.message}"
68
+ end
69
+ end
70
+
71
+ def freeze(val)
72
+ if opts.frozen?
73
+ val.each(&:freeze) if val.is_a?(Enumerable)
74
+ val.freeze
75
+ else
76
+ val
77
+ end
78
+ end
79
+
80
+ def xpath
81
+ wrapper ? "#{wrapper}/#{xpath_name}" : xpath_name.to_s
82
+ end
83
+
84
+ def auto_xpath
85
+ "#{conventionize(opts.name.pluralize)}/#{xpath_name}" if array?
86
+ end
87
+
88
+ def wrap(xml)
89
+ return xml if !wrapper || xml.name == wrapper
90
+ if child = xml.children.find {|c| c.name == wrapper }
91
+ return child
92
+ end
93
+ xml.add_child(XML::Node.new(wrapper.to_s))
94
+ end
95
+
96
+ def nodes_in(xml)
97
+ vals = xml.search(xpath)
98
+
99
+ if (opts.hash? || opts.array?) && vals.empty? && !wrapper && auto_xpath
100
+ vals = xml.search(auto_xpath)
101
+ @auto_vals = !vals.empty?
102
+ end
103
+
104
+ if vals.empty?
105
+ raise RequiredElementMissing, "#{name} from #{xml} for #{accessor}" if required?
106
+ default
107
+ else
108
+ yield(vals)
109
+ end
110
+ end
111
+ end
112
+
113
+ # Interal class representing an XML attribute binding
114
+ #
115
+ # In context:
116
+ # <element attribute="XMLAttributeRef">
117
+ # XMLTextRef
118
+ # </element>
119
+ class XMLAttributeRef < XMLRef # :nodoc:
120
+ private
121
+ # Updates the attribute in the given XML block to
122
+ # the value provided.
123
+ def write_xml(xml, value)
124
+ xml.attributes[name] = value.to_s
125
+ end
126
+
127
+ def fetch_value(xml)
128
+ nodes_in(xml) do |nodes|
129
+ nodes.first.value
130
+ end
131
+ end
132
+
133
+ def xpath_name
134
+ "@#{name}"
135
+ end
136
+ end
137
+
138
+ # Interal class representing XML content text binding
139
+ #
140
+ # In context:
141
+ # <element attribute="XMLAttributeRef">
142
+ # XMLTextRef
143
+ # </element>
144
+ class XMLTextRef < XMLRef # :nodoc:
145
+ delegate :cdata?, :content?, :name?, :to => :opts
146
+
147
+ private
148
+ # Updates the text in the given _xml_ block to
149
+ # the _value_ provided.
150
+ def write_xml(xml, value)
151
+ if content?
152
+ add(xml, value)
153
+ elsif name?
154
+ xml.name = value
155
+ elsif array?
156
+ value.each do |v|
157
+ add(xml.add_child(XML::Node.new(name)), v)
158
+ end
159
+ else
160
+ add(xml.add_child(XML::Node.new(name)), value)
161
+ end
162
+ end
163
+
164
+ def fetch_value(xml)
165
+ if content? || name?
166
+ value =
167
+ if content?
168
+ xml.content.to_s.strip
169
+ elsif name?
170
+ xml.name
171
+ end
172
+
173
+ if value.empty?
174
+ raise RequiredElementMissing, "#{name} from #{xml} for #{accessor}" if required?
175
+ default
176
+ else
177
+ value
178
+ end
179
+ else
180
+ nodes_in(xml) do |nodes|
181
+ if array?
182
+ nodes.collect do |e|
183
+ e.content.strip
184
+ end
185
+ else
186
+ nodes.first.content
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ def add(dest, value)
193
+ if cdata?
194
+ dest.child_add(XML::Node.new_cdata(value.to_s))
195
+ else
196
+ dest.content = value.to_s
197
+ end
198
+ end
199
+ end
200
+
201
+ class XMLHashRef < XMLTextRef # :nodoc:
202
+ delegate :hash, :to => :opts
203
+
204
+ def initialize(opts, inst)
205
+ super(opts, inst)
206
+ @key = opts.hash.key.to_ref(inst)
207
+ @value = opts.hash.value.to_ref(inst)
208
+ end
209
+
210
+ private
211
+ # Updates the composed XML object in the given XML block to
212
+ # the value provided.
213
+ def write_xml(xml, value)
214
+ value.each_pair do |k, v|
215
+ node = xml.child_add(XML::Node.new(hash.wrapper))
216
+ @key.update_xml(node, k)
217
+ @value.update_xml(node, v)
218
+ end
219
+ end
220
+
221
+ def fetch_value(xml)
222
+ nodes_in(xml) do |nodes|
223
+ nodes.collect do |e|
224
+ [@key.value_in(e), @value.value_in(e)]
225
+ end
226
+ end
227
+ end
228
+
229
+ def apply_blocks(vals)
230
+ unless blocks.empty?
231
+ vals.collect! do |kvp|
232
+ super(kvp)
233
+ end
234
+ end
235
+ to_hash(vals) if vals
236
+ end
237
+
238
+ def freeze(vals)
239
+ if opts.frozen?
240
+ vals.each_pair{|k, v| k.freeze; v.freeze }
241
+ vals.freeze
242
+ else
243
+ vals
244
+ end
245
+ end
246
+
247
+ def to_hash(array)
248
+ hash = array.inject({}) do |result, (k, v)|
249
+ result[k] ||= []
250
+ result[k] << v
251
+ result
252
+ end
253
+ hash.each_pair do |k, v|
254
+ hash[k] = v.first if v.one?
255
+ end
256
+ end
257
+ end
258
+
259
+ class XMLObjectRef < XMLTextRef # :nodoc:
260
+ delegate :type, :to => :opts
261
+
262
+ private
263
+ # Updates the composed XML object in the given XML block to
264
+ # the value provided.
265
+ def write_xml(xml, value)
266
+ if array?
267
+ value.each do |v|
268
+ xml.child_add(v.to_xml(name))
269
+ end
270
+ elsif value.is_a?(ROXML)
271
+ xml.child_add(value.to_xml(name))
272
+ else
273
+ node = XML::Node.new(name)
274
+ node.content = value.to_xml
275
+ xml.child_add(node)
276
+ end
277
+ end
278
+
279
+ def fetch_value(xml)
280
+ nodes_in(xml) do |nodes|
281
+ unless array?
282
+ instantiate(nodes.first)
283
+ else
284
+ nodes.collect do |e|
285
+ instantiate(e)
286
+ end
287
+ end
288
+ end
289
+ end
290
+
291
+ def instantiate(elem)
292
+ if type.respond_to? :from_xml
293
+ type.from_xml(elem)
294
+ else
295
+ type.new(elem)
296
+ end
297
+ end
298
+ end
299
+ end
data/roxml.gemspec ADDED
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{roxml}
5
+ s.version = "2.5.3"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ben Woosley", "Zak Mandhro", "Anders Engstrom", "Russ Olsen"]
9
+ s.date = %q{2009-03-22}
10
+ s.description = %q{ROXML is a Ruby library designed to make it easier for Ruby developers to work with XML. Using simple annotations, it enables Ruby classes to be mapped to XML. ROXML takes care of the marshalling and unmarshalling of mapped attributes so that developers can focus on building first-class Ruby classes. As a result, ROXML simplifies the development of RESTful applications, Web Services, and XML-RPC.}
11
+ s.email = %q{ben.woosley@gmail.com}
12
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.rdoc"]
13
+ s.files = ["History.txt", "MIT-LICENSE", "Manifest.txt", "README.rdoc", "Rakefile", "TODO", "config/website.yml", "examples/active_record.rb", "examples/amazon.rb", "examples/current_weather.rb", "examples/dashed_elements.rb", "examples/library.rb", "examples/posts.rb", "examples/twitter.rb", "examples/xml/active_record.xml", "examples/xml/amazon.xml", "examples/xml/current_weather.xml", "examples/xml/dashed_elements.xml", "examples/xml/posts.xml", "examples/xml/twitter.xml", "lib/roxml.rb", "lib/roxml/definition.rb", "lib/roxml/extensions.rb", "lib/roxml/extensions/array.rb", "lib/roxml/extensions/array/conversions.rb", "lib/roxml/extensions/deprecation.rb", "lib/roxml/extensions/string.rb", "lib/roxml/extensions/string/conversions.rb", "lib/roxml/extensions/string/iterators.rb", "lib/roxml/hash_definition.rb", "lib/roxml/xml.rb", "lib/roxml/xml/parsers/libxml.rb", "lib/roxml/xml/parsers/rexml.rb", "lib/roxml/xml/references.rb", "roxml.gemspec", "spec/definition_spec.rb", "spec/examples/active_record_spec.rb", "spec/examples/amazon_spec.rb", "spec/examples/current_weather_spec.rb", "spec/examples/dashed_elements_spec.rb", "spec/examples/library_spec.rb", "spec/examples/post_spec.rb", "spec/examples/twitter_spec.rb", "spec/roxml_spec.rb", "spec/shared_specs.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/xml/parser_spec.rb", "tasks/rspec.rake", "tasks/test.rake", "test/bugs/rexml_bugs.rb", "test/fixtures/book_malformed.xml", "test/fixtures/book_pair.xml", "test/fixtures/book_text_with_attribute.xml", "test/fixtures/book_valid.xml", "test/fixtures/book_with_authors.xml", "test/fixtures/book_with_contributions.xml", "test/fixtures/book_with_contributors.xml", "test/fixtures/book_with_contributors_attrs.xml", "test/fixtures/book_with_default_namespace.xml", "test/fixtures/book_with_depth.xml", "test/fixtures/book_with_octal_pages.xml", "test/fixtures/book_with_publisher.xml", "test/fixtures/book_with_wrapped_attr.xml", "test/fixtures/dictionary_of_attr_name_clashes.xml", "test/fixtures/dictionary_of_attrs.xml", "test/fixtures/dictionary_of_guarded_names.xml", "test/fixtures/dictionary_of_mixeds.xml", "test/fixtures/dictionary_of_name_clashes.xml", "test/fixtures/dictionary_of_names.xml", "test/fixtures/dictionary_of_texts.xml", "test/fixtures/library.xml", "test/fixtures/library_uppercase.xml", "test/fixtures/muffins.xml", "test/fixtures/nameless_ageless_youth.xml", "test/fixtures/node_with_attr_name_conflicts.xml", "test/fixtures/node_with_name_conflicts.xml", "test/fixtures/numerology.xml", "test/fixtures/person.xml", "test/fixtures/person_with_guarded_mothers.xml", "test/fixtures/person_with_mothers.xml", "test/mocks/dictionaries.rb", "test/mocks/mocks.rb", "test/test_helper.rb", "test/unit/definition_test.rb", "test/unit/deprecations_test.rb", "test/unit/to_xml_test.rb", "test/unit/xml_attribute_test.rb", "test/unit/xml_block_test.rb", "test/unit/xml_bool_test.rb", "test/unit/xml_convention_test.rb", "test/unit/xml_hash_test.rb", "test/unit/xml_initialize_test.rb", "test/unit/xml_name_test.rb", "test/unit/xml_namespace_test.rb", "test/unit/xml_object_test.rb", "test/unit/xml_required_test.rb", "test/unit/xml_text_test.rb", "vendor/override_rake_task/README", "vendor/override_rake_task/init.rb", "vendor/override_rake_task/install.rb", "vendor/override_rake_task/lib/override_rake_task.rb", "website/index.html"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://roxml.rubyforge.org}
16
+ s.rdoc_options = ["--main", "README.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{roxml}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{Ruby Object to XML mapping library}
21
+ s.test_files = ["test/unit/definition_test.rb", "test/unit/deprecations_test.rb", "test/unit/to_xml_test.rb", "test/unit/xml_attribute_test.rb", "test/unit/xml_block_test.rb", "test/unit/xml_bool_test.rb", "test/unit/xml_convention_test.rb", "test/unit/xml_hash_test.rb", "test/unit/xml_initialize_test.rb", "test/unit/xml_name_test.rb", "test/unit/xml_namespace_test.rb", "test/unit/xml_object_test.rb", "test/unit/xml_required_test.rb", "test/unit/xml_text_test.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.1.0"])
29
+ s.add_runtime_dependency(%q<libxml-ruby>, [">= 1.0.0"])
30
+ s.add_development_dependency(%q<newgem>, [">= 1.3.0"])
31
+ s.add_development_dependency(%q<sqlite3-ruby>, [">= 1.2.4"])
32
+ s.add_development_dependency(%q<activerecord>, [">= 2.2.2"])
33
+ s.add_development_dependency(%q<hoe>, [">= 1.8.0"])
34
+ else
35
+ s.add_dependency(%q<activesupport>, [">= 2.1.0"])
36
+ s.add_dependency(%q<libxml-ruby>, [">= 1.0.0"])
37
+ s.add_dependency(%q<newgem>, [">= 1.3.0"])
38
+ s.add_dependency(%q<sqlite3-ruby>, [">= 1.2.4"])
39
+ s.add_dependency(%q<activerecord>, [">= 2.2.2"])
40
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
41
+ end
42
+ else
43
+ s.add_dependency(%q<activesupport>, [">= 2.1.0"])
44
+ s.add_dependency(%q<libxml-ruby>, [">= 1.0.0"])
45
+ s.add_dependency(%q<newgem>, [">= 1.3.0"])
46
+ s.add_dependency(%q<sqlite3-ruby>, [">= 1.2.4"])
47
+ s.add_dependency(%q<activerecord>, [">= 2.2.2"])
48
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
49
+ end
50
+ end
@@ -0,0 +1,490 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ class RoxmlObject
4
+ include ROXML
5
+ end
6
+
7
+ describe ROXML::Definition do
8
+ describe "#name_explicit?" do
9
+ it "should indicate whether from option is present" do
10
+ ROXML::Definition.new(:element, :from => 'somewhere').name_explicit?.should be_true
11
+ ROXML::Definition.new(:element).name_explicit?.should be_false
12
+ end
13
+
14
+ it "should not consider name proxies as explicit" do
15
+ ROXML::Definition.new(:element, :from => :attr).name_explicit?.should be_false
16
+ ROXML::Definition.new(:element, :from => :content).name_explicit?.should be_false
17
+ end
18
+ end
19
+
20
+ describe "DateTime reference", :shared => true do
21
+ it "should return nil on empty string" do
22
+ @subject.blocks.first.call(" ").should be_nil
23
+ end
24
+
25
+ it "should return a time version of the string" do
26
+ @subject.blocks.first.call("12:05pm, September 3rd, 1970").to_s == "1970-09-03T12:05:00+00:00"
27
+ end
28
+
29
+ context "when passed an array of values" do
30
+ it "should timify all of them" do
31
+ @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"]
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "Date reference", :shared => true do
37
+ it "should return nil on empty string" do
38
+ @subject.blocks.first.call(" ").should be_nil
39
+ end
40
+
41
+ it "should return a time version of the string" do
42
+ @subject.blocks.first.call("September 3rd, 1970").to_s == "1970-09-03"
43
+ end
44
+
45
+ context "when passed an array of values" do
46
+ it "should timify all of them" do
47
+ @subject.blocks.first.call(["September 3rd, 1970", "1776-07-04"]).map(&:to_s).should == ["1970-09-03", "1776-07-04"]
48
+ end
49
+ end
50
+ end
51
+
52
+ it "should unescape xml entities" do
53
+ ROXML::Definition.new(:questions, :as => []).to_ref(RoxmlObject.new).value_in(%{
54
+ <xml>
55
+ <question>&quot;Wickard &amp; Filburn&quot; &gt;</question>
56
+ <question> &lt; McCulloch &amp; Maryland?</question>
57
+ </xml>
58
+ }).should == ["\"Wickard & Filburn\" >", "< McCulloch & Maryland?"]
59
+ end
60
+
61
+ describe "attr name" do
62
+ context "when ending with '_at'" do
63
+ context "and without an :as argument" do
64
+ before(:all) do
65
+ @subject = ROXML::Definition.new(:time_at)
66
+ end
67
+ it_should_behave_like "DateTime reference"
68
+ end
69
+ end
70
+
71
+ context "when ending with '_on'" do
72
+ context "and without an :as argument" do
73
+ before(:all) do
74
+ @subject = ROXML::Definition.new(:created_on)
75
+ end
76
+ it_should_behave_like "Date reference"
77
+ end
78
+ end
79
+ end
80
+
81
+ describe ":as" do
82
+ describe "=> []" do
83
+ it "should means array of texts" do
84
+ opts = ROXML::Definition.new(:authors, :as => [])
85
+ opts.array?.should be_true
86
+ opts.type.should == :text
87
+ end
88
+ end
89
+
90
+ describe "=> RoxmlClass" do
91
+ class RoxmlClass
92
+ include ROXML
93
+ end
94
+
95
+ it "should store type" do
96
+ opts = ROXML::Definition.new(:name, :as => RoxmlClass)
97
+ opts.type.should == RoxmlClass
98
+ end
99
+ end
100
+
101
+ describe "=> NonRoxmlClassWithFromXmlDefined" do
102
+ class OctalInteger
103
+ def self.from_xml(val)
104
+ new(Integer(val.content))
105
+ end
106
+ end
107
+
108
+ it "should accept type" do
109
+ opts = ROXML::Definition.new(:name, :as => OctalInteger)
110
+ opts.type.should == OctalInteger
111
+ end
112
+ end
113
+
114
+ describe "=> NonRoxmlClass" do
115
+ it "should fail with a warning" do
116
+ proc { ROXML::Definition.new(:authors, :as => Module) }.should raise_error(ArgumentError)
117
+ end
118
+ end
119
+
120
+ describe "=> [NonRoxmlClass]" do
121
+ it "should raise" do
122
+ proc { ROXML::Definition.new(:authors, :as => [Module]) }.should raise_error(ArgumentError)
123
+ end
124
+ end
125
+
126
+ describe "=> {}" do
127
+ describe "hash options declaration", :shared => true do
128
+ it "should represent a hash" do
129
+ @opts.hash?.should be_true
130
+ end
131
+
132
+ it "should have hash definition" do
133
+ {@opts.hash.key.type => @opts.hash.key.name}.should == @hash_args[:key]
134
+ {@opts.hash.value.type => @opts.hash.value.name}.should == @hash_args[:value]
135
+ end
136
+
137
+ it "should not represent an array" do
138
+ @opts.array?.should be_false
139
+ end
140
+ end
141
+
142
+ describe "hash with attr key and text val" do
143
+ before do
144
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => '@name',
145
+ :value => 'value'})
146
+ @hash_args = {:key => {:attr => 'name'},
147
+ :value => {:text => 'value'}}
148
+ end
149
+
150
+ it_should_behave_like "hash options declaration"
151
+ end
152
+
153
+ describe "hash with String class for type" do
154
+ before do
155
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => 'name',
156
+ :value => 'value'})
157
+ @hash_args = {:key => {:text => 'name'}, :value => {:text => 'value'}}
158
+ end
159
+
160
+ it_should_behave_like "hash options declaration"
161
+ end
162
+
163
+ describe "hash with attr key and content val" do
164
+ before do
165
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => '@name',
166
+ :value => :content})
167
+ @hash_args = {:key => {:attr => 'name'}, :value => {:text => '.'}}
168
+ end
169
+
170
+ it_should_behave_like "hash options declaration"
171
+ end
172
+
173
+ describe "hash with names as keys and content vals" do
174
+ before do
175
+ @opts = ROXML::Definition.new(:attributes, :as => {:key => :name,
176
+ :value => :content})
177
+ @hash_args = {:key => {:text => '*'}, :value => {:text => '.'}}
178
+ end
179
+
180
+ it_should_behave_like "hash options declaration"
181
+ end
182
+ end
183
+
184
+ describe "for block shorthand" do
185
+ describe "in literal array" do
186
+ before do
187
+ @opts = ROXML::Definition.new(:intarray, :as => [Integer])
188
+ end
189
+
190
+ it "should be detected as array reference" do
191
+ @opts.array?.should be_true
192
+ end
193
+
194
+ it "should be normal otherwise" do
195
+ @opts.type.should == :text
196
+ @opts.blocks.size.should == 1
197
+ end
198
+ end
199
+
200
+ it "should have no blocks without a shorthand" do
201
+ ROXML::Definition.new(:count).blocks.should be_empty
202
+ end
203
+
204
+ it "should raise on unknown :as" do
205
+ proc { ROXML::Definition.new(:count, :as => :bogus) }.should raise_error(ArgumentError)
206
+ proc { ROXML::Definition.new(:count, :as => :foat) }.should raise_error(ArgumentError)
207
+ end
208
+
209
+ describe "block shorthand type declaration", :shared => true do
210
+ it "should translate nil to nil" do
211
+ @definition.blocks.first.call(nil).should be_nil
212
+ end
213
+
214
+ it "should translate empty strings to nil" do
215
+ @definition.blocks.first.call("").should be_nil
216
+ @definition.blocks.first.call(" ").should be_nil
217
+ end
218
+ end
219
+
220
+ describe "Integer" do
221
+ before do
222
+ @definition = ROXML::Definition.new(:intvalue, :as => Integer)
223
+ end
224
+
225
+ it_should_behave_like "block shorthand type declaration"
226
+
227
+ it "should translate text to integers" do
228
+ @definition.blocks.first['3'].should == 3
229
+ @definition.blocks.first['792'].should == 792
230
+ end
231
+
232
+ it "should raise on non-integer values" do
233
+ proc { @definition.blocks.first['08'] }.should raise_error(ArgumentError)
234
+ proc { @definition.blocks.first['793.12'] }.should raise_error(ArgumentError)
235
+ proc { @definition.blocks.first['junk 11'] }.should raise_error(ArgumentError)
236
+ proc { @definition.blocks.first['11sttf'] }.should raise_error(ArgumentError)
237
+ end
238
+
239
+ context "when passed an array" do
240
+ it "should translate the array elements to integer" do
241
+ @definition.blocks.first.call(["792", "12", "328"]).should == [792, 12, 328]
242
+ end
243
+ end
244
+ end
245
+
246
+ describe "Float" do
247
+ before do
248
+ @definition = ROXML::Definition.new(:floatvalue, :as => Float)
249
+ end
250
+
251
+ it_should_behave_like "block shorthand type declaration"
252
+
253
+ it "should translate text to float" do
254
+ @definition.blocks.first['3'].should == 3.0
255
+ @definition.blocks.first['12.7'].should == 12.7
256
+ end
257
+
258
+ it "should raise on non-float values" do
259
+ proc { @definition.blocks.first['junk 11.3'] }.should raise_error(ArgumentError)
260
+ proc { @definition.blocks.first['11.1sttf'] }.should raise_error(ArgumentError)
261
+ end
262
+
263
+ context "when passed an array" do
264
+ it "should translate the array elements to integer" do
265
+ @definition.blocks.first.call(["792.13", "240", "3.14"]).should == [792.13, 240.0, 3.14]
266
+ end
267
+ end
268
+ end
269
+
270
+ describe "BigDecimal" do
271
+ before do
272
+ @definition = ROXML::Definition.new(:decimalvalue, :as => BigDecimal)
273
+ end
274
+
275
+ it_should_behave_like "block shorthand type declaration"
276
+
277
+ it "should translate text to decimal numbers" do
278
+ @definition.blocks.first['3'].should == BigDecimal.new("3.0")
279
+ @definition.blocks.first['0.3'].should == BigDecimal.new("0.3")
280
+ end
281
+
282
+ it "should extract what it can, and fall back to 0" do
283
+ @definition.blocks.first['junk 11'].should eql(BigDecimal.new("0"))
284
+ @definition.blocks.first['11sttf'].should eql(BigDecimal.new("11.0"))
285
+ end
286
+
287
+ context "when passed an array" do
288
+ it "should translate the array elements to integer" do
289
+ @definition.blocks.first.call(["12.1", "328.2"]).should == [BigDecimal.new("12.1"), BigDecimal.new("328.2")]
290
+ end
291
+ end
292
+ end
293
+
294
+ describe "Fixnum" do
295
+ before do
296
+ @definition = ROXML::Definition.new(:fixnumvalue, :as => Fixnum)
297
+ end
298
+
299
+ it_should_behave_like "block shorthand type declaration"
300
+
301
+ it "should translate text to integers" do
302
+ @definition.blocks.first['3'].should == 3
303
+ @definition.blocks.first['792'].should == 792
304
+ @definition.blocks.first['08'].should == 8
305
+ @definition.blocks.first['279.23'].should == 279
306
+ end
307
+
308
+ it "should extract whatever is possible and fall back to 0" do
309
+ @definition.blocks.first['junk 11'].should eql(0)
310
+ @definition.blocks.first['.?sttf'].should eql(0)
311
+ @definition.blocks.first['11sttf'].should eql(11)
312
+ end
313
+
314
+ context "when passed an array" do
315
+ it "should translate the array elements to integer" do
316
+ @definition.blocks.first.call(["792", "12", "328"]).should == [792, 12, 328]
317
+ end
318
+ end
319
+ end
320
+
321
+ describe ":bool" do
322
+ it "should boolify individual values" do
323
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("1").should be_true
324
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("True").should be_true
325
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("Yes").should be_true
326
+ end
327
+
328
+ context "when an array is passed in" do
329
+ it "should boolify arrays of values" do
330
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("0").should be_false
331
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("false").should be_false
332
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("nO").should be_false
333
+ end
334
+ end
335
+
336
+ context "when no value is detected" do
337
+ it "should return nil" do
338
+ ROXML::Definition.new(:floatvalue, :as => :bool).blocks.first.call("junk").should be_nil
339
+ end
340
+
341
+ context "when a literal block is available" do
342
+ it "should pass the value itself to the block"
343
+ end
344
+ end
345
+ end
346
+
347
+ describe "Time" do
348
+ it "should return nil on empty string" do
349
+ ROXML::Definition.new(:floatvalue, :as => Time).blocks.first.call(" ").should be_nil
350
+ end
351
+
352
+ it "should return a time version of the string" do
353
+ ROXML::Definition.new(:datevalue, :as => Time).blocks.first.call("12:31am").min.should == 31
354
+ end
355
+
356
+ context "when passed an array of values" do
357
+ it "should timify all of them" do
358
+ ROXML::Definition.new(:datevalue, :as => Time).blocks.first.call(["12:31am", "3:00pm", "11:59pm"]).map(&:min).should == [31, 0, 59]
359
+ end
360
+ end
361
+ end
362
+
363
+ describe "Date" do
364
+ before do
365
+ @subject = ROXML::Definition.new(:datevalue, :as => Date)
366
+ end
367
+ it_should_behave_like "Date reference"
368
+ end
369
+
370
+ describe "DateTime" do
371
+ before do
372
+ @subject = ROXML::Definition.new(:datevalue, :as => DateTime)
373
+ end
374
+ it_should_behave_like "DateTime reference"
375
+ end
376
+
377
+ it "should prohibit multiple shorthands" do
378
+ proc { ROXML::Definition.new(:count, :as => [Float, Integer]) }.should raise_error(ArgumentError)
379
+ end
380
+
381
+ it "should stack block shorthands with explicit blocks" do
382
+ ROXML::Definition.new(:count, :as => Integer) {|val| val.to_i }.blocks.size.should == 2
383
+ ROXML::Definition.new(:count, :as => Float) {|val| val.object_id }.blocks.size.should == 2
384
+ end
385
+ end
386
+ end
387
+
388
+ describe ":from" do
389
+ describe "attribute reference", :shared => true do
390
+ it "should be interpreted as :attr" do
391
+ @opts.type.should == :attr
392
+ end
393
+
394
+ it "should strip '@' from name" do
395
+ @opts.name.should == 'attr_name'
396
+ end
397
+
398
+ it "should unescape xml entities" do
399
+ @opts.to_ref(RoxmlObject.new).value_in(%{
400
+ <question attr_name="&quot;Wickard &amp; Filburn&quot; &gt; / &lt; McCulloch &amp; Marryland?" />
401
+ }).should == "\"Wickard & Filburn\" > / < McCulloch & Marryland?"
402
+ end
403
+ end
404
+
405
+ context ":attr" do
406
+ before do
407
+ @opts = ROXML::Definition.new(:attr_name, :from => :attr)
408
+ end
409
+
410
+ it_should_behave_like "attribute reference"
411
+ end
412
+
413
+ context "@attribute_name" do
414
+ before do
415
+ @opts = ROXML::Definition.new(:attr_name, :from => '@attr_name')
416
+ end
417
+
418
+ it_should_behave_like "attribute reference"
419
+ end
420
+
421
+ describe ":content" do
422
+ it "should be recognized" do
423
+ ROXML::Definition.new(:author).content?.should be_false
424
+ ROXML::Definition.new(:author, :from => :content).content?.should == true
425
+ end
426
+
427
+ it "should be equivalent to :from => '.'" do
428
+ ROXML::Definition.new(:author, :from => '.').content?.should == true
429
+ end
430
+ end
431
+ end
432
+
433
+ describe ":in" do
434
+ context "as xpath" do
435
+ it "should pass through as wrapper" do
436
+ ROXML::Definition.new(:manufacturer, :in => './').wrapper.should == './'
437
+ end
438
+ end
439
+
440
+ context "as xpath" do
441
+ it "should pass through as wrapper" do
442
+ ROXML::Definition.new(:manufacturer, :in => 'wrapper').wrapper.should == 'wrapper'
443
+ end
444
+ end
445
+ end
446
+
447
+ describe "options" do
448
+
449
+ describe "boolean option", :shared => true do
450
+ it "should be recognized" do
451
+ ROXML::Definition.new(:author, :from => :content, @option => true).respond_to?(:"#{@option}?")
452
+ ROXML::Definition.new(:author, :from => :content, @option => true).send(:"#{@option}?").should be_true
453
+ ROXML::Definition.new(:author, :from => :content, @option => false).send(:"#{@option}?").should be_false
454
+ end
455
+
456
+ it "should default to false" do
457
+ ROXML::Definition.new(:author, :from => :content).send(:"#{@option}?").should be_false
458
+ end
459
+ end
460
+
461
+ describe ":required" do
462
+ before do
463
+ @option = :required
464
+ end
465
+
466
+ it_should_behave_like "boolean option"
467
+
468
+ it "should not be allowed together with :else" do
469
+ proc { ROXML::Definition.new(:author, :from => :content, :required => true, :else => 'Johnny') }.should raise_error(ArgumentError)
470
+ proc { ROXML::Definition.new(:author, :from => :content, :required => false, :else => 'Johnny') }.should_not raise_error
471
+ end
472
+ end
473
+
474
+ describe ":frozen" do
475
+ before do
476
+ @option = :frozen
477
+ end
478
+
479
+ it_should_behave_like "boolean option"
480
+ end
481
+
482
+ describe ":cdata" do
483
+ before do
484
+ @option = :cdata
485
+ end
486
+
487
+ it_should_behave_like "boolean option"
488
+ end
489
+ end
490
+ end