roxml 2.5.3 → 3.1.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 (76) hide show
  1. data/.gitignore +7 -0
  2. data/.gitmodules +3 -0
  3. data/History.txt +38 -1
  4. data/README.rdoc +8 -5
  5. data/Rakefile +35 -36
  6. data/TODO +12 -35
  7. data/VERSION +1 -0
  8. data/examples/amazon.rb +8 -6
  9. data/examples/posts.rb +1 -1
  10. data/examples/{active_record.rb → rails.rb} +2 -2
  11. data/examples/twitter.rb +1 -1
  12. data/lib/roxml.rb +86 -151
  13. data/lib/roxml/definition.rb +64 -152
  14. data/lib/roxml/hash_definition.rb +5 -40
  15. data/lib/roxml/xml.rb +12 -9
  16. data/lib/roxml/xml/parsers/libxml.rb +22 -17
  17. data/lib/roxml/xml/parsers/nokogiri.rb +77 -0
  18. data/lib/roxml/xml/references.rb +66 -57
  19. data/roxml.gemspec +170 -19
  20. data/spec/definition_spec.rb +121 -198
  21. data/spec/examples/active_record_spec.rb +2 -2
  22. data/spec/examples/amazon_spec.rb +3 -2
  23. data/spec/examples/current_weather_spec.rb +2 -2
  24. data/spec/examples/dashed_elements_spec.rb +2 -2
  25. data/spec/examples/library_spec.rb +11 -6
  26. data/spec/examples/post_spec.rb +3 -3
  27. data/spec/examples/twitter_spec.rb +2 -2
  28. data/spec/roxml_spec.rb +15 -15
  29. data/spec/shared_specs.rb +1 -1
  30. data/spec/spec_helper.rb +8 -27
  31. data/spec/support/libxml.rb +3 -0
  32. data/spec/support/nokogiri.rb +3 -0
  33. data/spec/xml/attributes_spec.rb +36 -0
  34. data/spec/xml/namespace_spec.rb +240 -0
  35. data/spec/xml/namespaces_spec.rb +32 -0
  36. data/spec/xml/parser_spec.rb +9 -30
  37. data/tasks/rdoc.rake +13 -0
  38. data/tasks/rspec.rake +21 -17
  39. data/tasks/test.rake +13 -20
  40. data/test/mocks/dictionaries.rb +8 -7
  41. data/test/mocks/mocks.rb +20 -20
  42. data/test/support/fixtures.rb +11 -0
  43. data/test/test_helper.rb +3 -14
  44. data/test/unit/definition_test.rb +21 -95
  45. data/test/unit/deprecations_test.rb +1 -74
  46. data/test/unit/to_xml_test.rb +3 -3
  47. data/test/unit/xml_attribute_test.rb +1 -1
  48. data/test/unit/xml_block_test.rb +3 -3
  49. data/test/unit/xml_bool_test.rb +4 -4
  50. data/test/unit/xml_convention_test.rb +3 -3
  51. data/test/unit/xml_hash_test.rb +5 -14
  52. data/test/unit/xml_initialize_test.rb +2 -6
  53. data/test/unit/xml_name_test.rb +5 -24
  54. data/test/unit/xml_namespace_test.rb +1 -46
  55. data/test/unit/xml_object_test.rb +6 -6
  56. data/test/unit/xml_required_test.rb +3 -2
  57. data/test/unit/xml_text_test.rb +2 -2
  58. data/website/index.html +1 -1
  59. metadata +68 -51
  60. data/Manifest.txt +0 -106
  61. data/lib/roxml/extensions.rb +0 -6
  62. data/lib/roxml/extensions/array.rb +0 -13
  63. data/lib/roxml/extensions/array/conversions.rb +0 -35
  64. data/lib/roxml/extensions/deprecation.rb +0 -33
  65. data/lib/roxml/extensions/string.rb +0 -21
  66. data/lib/roxml/extensions/string/conversions.rb +0 -43
  67. data/lib/roxml/extensions/string/iterators.rb +0 -12
  68. data/lib/roxml/xml/parsers/rexml.rb +0 -84
  69. data/spec/string_spec.rb +0 -15
  70. data/test/bugs/rexml_bugs.rb +0 -15
  71. data/test/release/dependencies_test.rb +0 -32
  72. data/test/unit/xml_construct_test.rb +0 -77
  73. data/vendor/override_rake_task/README +0 -30
  74. data/vendor/override_rake_task/init.rb +0 -1
  75. data/vendor/override_rake_task/install.rb +0 -46
  76. data/vendor/override_rake_task/lib/override_rake_task.rb +0 -16
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), 'hash_definition')
1
+ require 'lib/roxml/hash_definition'
2
2
 
3
3
  class Module
4
4
  def bool_attr_reader(*attrs)
@@ -11,48 +11,41 @@ class Module
11
11
  end
12
12
 
13
13
  module ROXML
14
+ class ContradictoryNamespaces < StandardError
15
+ end
16
+
14
17
  class Definition # :nodoc:
15
- attr_reader :name, :type, :wrapper, :hash, :blocks, :accessor, :to_xml
18
+ attr_reader :name, :type, :wrapper, :hash, :blocks, :accessor, :to_xml, :attr_name, :namespace
16
19
  bool_attr_reader :name_explicit, :array, :cdata, :required, :frozen
17
20
 
18
- class << self
19
- def silence_xml_name_warning?
20
- @silence_xml_name_warning || (ROXML.const_defined?('SILENCE_XML_NAME_WARNING') && ROXML::SILENCE_XML_NAME_WARNING)
21
- end
22
-
23
- def silence_xml_name_warning!
24
- @silence_xml_name_warning = true
25
- end
26
- end
27
-
28
- def initialize(sym, *args, &block)
29
- @accessor = sym
30
- if @accessor.to_s.ends_with?('_on')
31
- ActiveSupport::Deprecation.warn "In 3.0, attributes with names ending with _on will default to Date type, rather than :text"
32
- end
33
- if @accessor.to_s.ends_with?('_at')
34
- ActiveSupport::Deprecation.warn "In 3.0, attributes with names ending with _at will default to DateTime type, rather than :text"
35
- end
36
-
37
- opts = extract_options!(args)
38
- opts[:as] ||= :bool if @accessor.to_s.ends_with?('?')
21
+ def initialize(sym, opts = {}, &block)
22
+ opts.assert_valid_keys(:from, :in, :as, :namespace,
23
+ :else, :required, :frozen, :cdata, :to_xml)
24
+ @default = opts.delete(:else)
25
+ @to_xml = opts.delete(:to_xml)
26
+ @name_explicit = opts.has_key?(:from) && opts[:from].is_a?(String)
27
+ @cdata = opts.delete(:cdata)
28
+ @required = opts.delete(:required)
29
+ @frozen = opts.delete(:frozen)
30
+ @wrapper = opts.delete(:in)
31
+ @namespace = opts.delete(:namespace)
32
+
33
+ @accessor = sym.to_s
34
+ opts[:as] ||=
35
+ if @accessor.ends_with?('?')
36
+ :bool
37
+ elsif @accessor.ends_with?('_on')
38
+ Date
39
+ elsif @accessor.ends_with?('_at')
40
+ DateTime
41
+ end
39
42
 
40
- @array = opts[:as].is_a?(Array) || extract_from_as(opts, :array, "Please use [] around your usual type declaration")
43
+ @array = opts[:as].is_a?(Array)
41
44
  @blocks = collect_blocks(block, opts[:as])
42
45
 
43
- if opts.has_key?(:readonly)
44
- raise ArgumentError, "There is no 'readonly' option. You probably mean to use :frozen => true"
45
- end
46
-
47
- @type = extract_type(args, opts)
48
- if @type.respond_to?(:xml_name_without_deprecation?) && @type.xml_name_without_deprecation?
49
- unless self.class.silence_xml_name_warning?
50
- warn "WARNING: As of 2.3, a breaking change has been in the naming of sub-objects. " +
51
- "ROXML now considers the xml_name of the sub-object before falling back to the accessor name of the parent. " +
52
- "Use :from on the parent declaration to override this behavior. Set ROXML::SILENCE_XML_NAME_WARNING to avoid this message."
53
- self.class.silence_xml_name_warning!
54
- end
55
- opts[:from] ||= @type.tag_name
46
+ @type = extract_type(opts[:as])
47
+ if @type.respond_to?(:roxml_tag_name)
48
+ opts[:from] ||= @type.roxml_tag_name
56
49
  end
57
50
 
58
51
  if opts[:from] == :content
@@ -62,22 +55,30 @@ module ROXML
62
55
  elsif opts[:from] == :attr
63
56
  @type = :attr
64
57
  opts[:from] = nil
58
+ elsif opts[:from] == :name
59
+ opts[:from] = '*'
65
60
  elsif opts[:from].to_s.starts_with?('@')
66
61
  @type = :attr
67
62
  opts[:from].sub!('@', '')
68
63
  end
69
64
 
70
- @name = (opts[:from] || variable_name).to_s
65
+ @attr_name = accessor.to_s.chomp('?')
66
+ @name = (opts[:from] || @attr_name).to_s
71
67
  @name = @name.singularize if hash? || array?
72
68
  if hash? && (hash.key.name? || hash.value.name?)
73
69
  @name = '*'
74
70
  end
71
+ raise ContradictoryNamespaces if @name.include?(':') && (@namespace.present? || @namespace == false)
75
72
 
76
73
  raise ArgumentError, "Can't specify both :else default and :required" if required? && @default
77
74
  end
78
75
 
79
- def variable_name
80
- accessor.to_s.chomp('?')
76
+ def instance_variable_name
77
+ :"@#{attr_name}"
78
+ end
79
+
80
+ def setter
81
+ :"#{attr_name}="
81
82
  end
82
83
 
83
84
  def hash
@@ -127,23 +128,11 @@ module ROXML
127
128
  array ? results : results.first
128
129
  end
129
130
 
130
- BLOCK_TO_FLOAT = lambda do |val|
131
- all(val) do |v|
132
- Float(v) unless v.blank?
133
- end
134
- end
135
-
136
- BLOCK_TO_INT = lambda do |val|
137
- all(val) do |v|
138
- Integer(v) unless v.blank?
139
- end
140
- end
141
-
142
131
  def self.fetch_bool(value, default)
143
132
  value = value.to_s.downcase
144
- if %w{true yes 1}.include? value
133
+ if %w{true yes 1 t}.include? value
145
134
  true
146
- elsif %w{false no 0}.include? value
135
+ elsif %w{false no 0 f}.include? value
147
136
  false
148
137
  else
149
138
  default
@@ -152,10 +141,16 @@ module ROXML
152
141
 
153
142
  CORE_BLOCK_SHORTHANDS = {
154
143
  # Core Shorthands
155
- :integer => BLOCK_TO_INT, # deprecated
156
- Integer => BLOCK_TO_INT,
157
- :float => BLOCK_TO_FLOAT, # deprecated
158
- Float => BLOCK_TO_FLOAT,
144
+ Integer => lambda do |val|
145
+ all(val) do |v|
146
+ Integer(v) unless v.blank?
147
+ end
148
+ end,
149
+ Float => lambda do |val|
150
+ all(val) do |v|
151
+ Float(v) unless v.blank?
152
+ end
153
+ end,
159
154
  Fixnum => lambda do |val|
160
155
  all(val) do |v|
161
156
  v.to_i unless v.blank?
@@ -181,7 +176,7 @@ module ROXML
181
176
  def self.block_shorthands
182
177
  # dynamically load these shorthands at class definition time, but
183
178
  # only if they're already availbable
184
- returning CORE_BLOCK_SHORTHANDS do |blocks|
179
+ CORE_BLOCK_SHORTHANDS.tap do |blocks|
185
180
  blocks.reverse_merge!(BigDecimal => lambda do |val|
186
181
  all(val) do |v|
187
182
  BigDecimal.new(v) unless v.blank?
@@ -203,11 +198,8 @@ module ROXML
203
198
  end
204
199
 
205
200
  def collect_blocks(block, as)
206
- ActiveSupport::Deprecation.warn ":as => :float is deprecated. Use :as => Float instead" if as == :float
207
- ActiveSupport::Deprecation.warn ":as => :integer is deprecated. Use :as => Integer instead" if as == :integer
208
-
209
201
  if as.is_a?(Array)
210
- unless as.one? || as.empty?
202
+ if as.size > 1
211
203
  raise ArgumentError, "multiple :as types (#{as.map(&:inspect).join(', ')}) is not supported. Use a block if you want more complicated behavior."
212
204
  end
213
205
 
@@ -221,103 +213,23 @@ module ROXML
221
213
  end
222
214
  as = self.class.block_shorthands.fetch(as) do
223
215
  unless as.respond_to?(:from_xml) || (as.respond_to?(:first) && as.first.respond_to?(:from_xml)) || (as.is_a?(Hash) && !(as.keys & [:key, :value]).empty?)
224
- ActiveSupport::Deprecation.warn "#{as.inspect} is not a valid type declaration. ROXML will raise in this case in version 3.0" unless as.nil?
216
+ raise ArgumentError, "Invalid :as argument #{as}" unless as.nil?
225
217
  end
226
218
  nil
227
219
  end
228
220
  [as, block].compact
229
221
  end
230
222
 
231
- def extract_options!(args)
232
- opts = args.extract_options!
233
- unless (opts.keys & HASH_KEYS).empty?
234
- args.push(opts)
235
- opts = {}
236
- end
237
-
238
- @default = opts.delete(:else)
239
- @to_xml = opts.delete(:to_xml)
240
- @name_explicit = opts.has_key?(:from) && opts[:from].is_a?(String)
241
- @cdata = opts.delete(:cdata)
242
- @required = opts.delete(:required)
243
- @frozen = opts.delete(:frozen)
244
- @wrapper = opts.delete(:in)
245
-
246
- @cdata ||= extract_from_as(opts, :cdata, "Please use :cdata => true")
247
-
248
- if opts[:as].is_a?(Array) && opts[:as].size > 1
249
- ActiveSupport::Deprecation.warn ":as should point to a single item. #{opts[:as].join(', ')} should be declared some other way."
250
- end
251
-
252
- opts
253
- end
254
-
255
- def extract_from_as(opts, entry, message)
256
- # remove with deprecateds...
257
- if [*opts[:as]].include?(entry)
258
- ActiveSupport::Deprecation.warn ":as => #{entry.inspect} is deprecated. #{message}"
259
- if opts[:as] == entry
260
- opts[:as] = nil
261
- else
262
- opts[:as].delete(entry)
263
- end
264
- true
265
- end
266
- end
267
-
268
- def extract_type(args, opts)
269
- types = (opts.keys & TYPE_KEYS)
270
- # type arg
271
- if args.one? && types.empty?
272
- type = args.first
273
- if type.is_a? Array
274
- ActiveSupport::Deprecation.warn "Array declarations should be passed as the :as parameter, for future release."
275
- @array = true
276
- return type.first || :text
277
- elsif type.is_a? Hash
278
- ActiveSupport::Deprecation.warn "Hash declarations should be passed as the :as parameter, for future release."
279
- return HashDefinition.new(type)
280
- elsif type == :content
281
- ActiveSupport::Deprecation.warn ":content as a type declaration is deprecated. Use :from => '.' or :from => :content instead"
282
- opts[:from] = :content
283
- return :text
284
- elsif type == :attr
285
- ActiveSupport::Deprecation.warn ":attr as a type declaration is deprecated. Use :from => '@attr_name' or :from => :attr instead"
286
- opts[:from].sub!('@', '') if opts[:from].to_s.starts_with?('@') # this is added back next line...
287
- opts[:from] = opts[:from].nil? ? :attr : "@#{opts[:from]}"
288
- return :attr
289
- else
290
- ActiveSupport::Deprecation.warn "Type declarations should be passed as the :as parameter, for future release."
291
- return type
292
- end
293
- end
294
-
295
- unless args.empty?
296
- raise ArgumentError, "too many arguments (#{(args + types).join(', ')}). Should be name, type, and " +
297
- "an options hash, with the type and options optional"
298
- end
299
-
300
- if opts[:as].is_a?(Hash)
301
- return HashDefinition.new(opts[:as])
302
- elsif opts[:as].respond_to?(:from_xml)
303
- return opts[:as]
304
- elsif opts[:as].is_a?(Array) && opts[:as].first.respond_to?(:from_xml)
223
+ def extract_type(as)
224
+ if as.is_a?(Hash)
225
+ return HashDefinition.new(as)
226
+ elsif as.respond_to?(:from_xml)
227
+ return as
228
+ elsif as.is_a?(Array) && as.first.respond_to?(:from_xml)
305
229
  @array = true
306
- return opts[:as].first
307
- end
308
-
309
- # type options
310
- if types.one?
311
- opts[:from] = opts.delete(types.first)
312
- if opts[:from] == :content
313
- opts[:from] = 'content'
314
- ActiveSupport::Deprecation.warn ":content is now a reserved as an alias for '.'. Use the string 'content' instead"
315
- end
316
- types.first
317
- elsif types.empty?
318
- :text
230
+ return as.first
319
231
  else
320
- raise ArgumentError, "more than one type option specified: #{types.join(', ')}"
232
+ :text
321
233
  end
322
234
  end
323
235
  end
@@ -1,60 +1,25 @@
1
1
  module ROXML
2
- HASH_KEYS = [:attrs, :key, :value].freeze
3
- TYPE_KEYS = [:attr, :text, :hash, :content].freeze
4
-
5
2
  class HashDefinition # :nodoc:
6
3
  attr_reader :key, :value
7
4
  attr_accessor :wrapper
8
5
 
9
6
  def initialize(opts)
10
- unless (invalid_keys = opts.keys - HASH_KEYS).empty?
11
- raise ArgumentError, "Invalid Hash description keys: #{invalid_keys.join(', ')}"
12
- end
7
+ opts.assert_valid_keys(:key, :value)
13
8
 
14
- if opts.has_key? :attrs
15
- ActiveSupport::Deprecation.warn(":as => {:attrs} is going away in 3.0. Use explicit :key and :value instead.")
16
- @key = to_hash_args(opts, :from => "@#{opts[:attrs][0]}")
17
- @value = to_hash_args(opts, :from => "@#{opts[:attrs][1]}")
18
- else
19
- @key = to_hash_args opts, fetch_element(opts, :key)
20
- @value = to_hash_args opts, fetch_element(opts, :value)
21
- end
9
+ @key = Definition.new(nil, to_definition_options(opts, :key))
10
+ @value = Definition.new(nil, to_definition_options(opts, :value))
22
11
  end
23
12
 
24
13
  private
25
- def fetch_element(opts, what)
14
+ def to_definition_options(opts, what)
26
15
  case opts[what]
27
16
  when Hash
28
- if opts[what].keys.one?
29
- ActiveSupport::Deprecation.warn(":as => {:key => {Type => 'name'} ... } is going away in 3.0. Use explicit :key => {:from => 'name', :as => Type} instead.")
30
- type = opts[what].keys.first
31
- case type
32
- when :attr
33
- {:from => "@#{opts[what][type]}"}
34
- when :text
35
- {:from => opts[what][type]}
36
- else
37
- {:as => type, :from => opts[what][type]}
38
- end
39
- else
40
- opts[what]
41
- end
17
+ opts[what]
42
18
  when String, Symbol
43
19
  {:from => opts[what]}
44
20
  else
45
21
  raise ArgumentError, "unrecognized hash parameter: #{what} => #{opts[what]}"
46
22
  end
47
23
  end
48
-
49
- def to_hash_args(args, opts)
50
- args = [args] unless args.is_a? Array
51
-
52
- if args.one? && !(args.first.keys & HASH_KEYS).empty?
53
- Definition.new(nil, opts)
54
- else
55
- opts = args.extract_options!
56
- raise opts.inspect
57
- end
58
- end
59
24
  end
60
25
  end
@@ -1,18 +1,21 @@
1
1
  module ROXML
2
2
  unless const_defined? 'XML_PARSER'
3
+ PREFERRED_PARSERS = %w[nokogiri libxml].freeze
4
+ parsers = PREFERRED_PARSERS.dup
3
5
  begin
4
- require 'libxml'
5
- XML_PARSER = 'libxml' # :nodoc:
6
+ require parsers.first
7
+ XML_PARSER = parsers.first # :nodoc:
6
8
  rescue LoadError
7
- warn <<WARNING
8
- ROXML is unable to locate libxml on your system, and so is falling back to
9
- the much slower REXML. It's best to check this out and get libxml working if possible.
10
- WARNING
11
- XML_PARSER = 'rexml' # :nodoc:
9
+ if parsers.size > 1
10
+ parsers.shift
11
+ retry
12
+ else
13
+ raise "Could not load either nokogiri or libxml"
14
+ end
12
15
  end
13
16
  end
14
17
 
15
- require File.join(File.dirname(__FILE__), 'xml/parsers', XML_PARSER)
18
+ require File.join('lib/roxml/xml/parsers', XML_PARSER)
16
19
 
17
20
  module XML
18
21
  class Node
@@ -37,4 +40,4 @@ WARNING
37
40
  end
38
41
  end
39
42
 
40
- require File.join(File.dirname(__FILE__), 'xml/references')
43
+ require 'lib/roxml/xml/references'
@@ -9,37 +9,36 @@ module ROXML
9
9
  Error = LibXML::XML::Error
10
10
 
11
11
  module NamespacedSearch
12
- def search(xpath)
13
- begin
14
- if namespaces.default && !xpath.include?(':')
15
- find(namespaced(xpath),
16
- in_default_namespace(namespaces.default.href))
17
- else
18
- find(xpath)
19
- end
20
- rescue Exception => ex
21
- raise ex, xpath
12
+ def search(xpath, roxml_namespaces = {})
13
+ if namespaces.default
14
+ roxml_namespaces = {:xmlns => namespaces.default.href}.merge(roxml_namespaces)
15
+ end
16
+ if roxml_namespaces.present?
17
+ find(xpath, roxml_namespaces.map {|prefix, href| [prefix, href].join(':') })
18
+ else
19
+ find(xpath)
22
20
  end
23
21
  end
24
22
 
25
23
  private
26
24
  def namespaced(xpath)
27
- xpath.between('/') do |component|
25
+ xpath.split('/').map do |component|
28
26
  if component =~ /\w+/ && !component.include?(':') && !component.starts_with?('@')
29
- in_default_namespace(component)
27
+ "xmlns:#{component}"
30
28
  else
31
29
  component
32
30
  end
33
- end
34
- end
35
-
36
- def in_default_namespace(name)
37
- "roxmldefaultnamespace:#{name}"
31
+ end.join('/')
38
32
  end
39
33
  end
40
34
 
41
35
  class Document
42
36
  include NamespacedSearch
37
+
38
+ def default_namespace
39
+ default = namespaces.default
40
+ default.prefix || 'xmlns' if default
41
+ end
43
42
 
44
43
  private
45
44
  delegate :namespaces, :to => :root
@@ -53,6 +52,12 @@ module ROXML
53
52
  new_without_entity_escaping(name, content && CGI.escapeHTML(content), namespace)
54
53
  end
55
54
  alias_method_chain :new, :entity_escaping
55
+
56
+ alias :create :new
57
+ end
58
+
59
+ def default_namespace
60
+ doc.default_namespace
56
61
  end
57
62
 
58
63
  def add_child(child)