Empact-roxml 2.2 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -91,7 +91,8 @@ The result of the block above is stored, rather than the actual value parsed fro
91
91
  == Construction
92
92
 
93
93
  Complicated initialization may require action on multiple attributes of an object. As such, you can
94
- use xml_construct to cause your ROXML object to call its own constructor. For example:
94
+ define method xml_initialize to perform initialization after instantiation and parsing, including
95
+ causing your ROXML object to call its own constructor, as in the following:
95
96
 
96
97
  class Measurement
97
98
  include ROXML
@@ -99,15 +100,18 @@ use xml_construct to cause your ROXML object to call its own constructor. For e
99
100
  xml_reader :units, :attr
100
101
  xml_reader :value, :content
101
102
 
102
- xml_construct :value, :units
103
+ def xml_initialize
104
+ # xml attributes of self are already valid
105
+ initialize(value, units)
106
+ end
103
107
 
104
108
  def initialize(value, units)
105
109
  # translate units & value into metric, for example
106
110
  end
107
111
  end
108
112
 
109
- Will, on parse, read all listed xml attributes (units and value, in this case), then call initialize
110
- with the arguments listed after the xml_construct call.
113
+ One important use of this approach is to make ROXML object which may or may not include an xml backing,
114
+ which may be used via _new_ construction as well as _from_xml_ construction.
111
115
 
112
116
  == Selecting a parser
113
117
 
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  # Rake libraries used
2
2
  require "rubygems"
3
- require "rails_plugin_package_task"
4
3
  require "rake/rdoctask"
5
4
  require "rake/contrib/rubyforgepublisher"
6
5
  require "rake/contrib/publisher"
@@ -24,17 +23,6 @@ Rake::RDocTask.new do |rd|
24
23
  rd.options << '--main' << 'README.rdoc' << '--title' << 'ROXML Documentation'
25
24
  end
26
25
 
27
- Rake::RailsPluginPackageTask.new(spec.name, spec.version) do |p|
28
- p.package_files = FileList[
29
- "lib/**/*.rb", "*.txt", "README.rdoc", "Rakefile",
30
- "rake/**/*", "test/**/*.rb", "test/**/*.xml"]
31
- p.plugin_files = FileList["rails_plugin/**/*"]
32
- p.extra_links = {"Project page" => spec.homepage,
33
- "Author: Zak Mandhro" => 'http://rubyforge.org/users/zakmandhro/'}
34
- p.verbose = true
35
- end
36
- task :rails_plugin=>:clobber
37
-
38
26
  desc "Publish Ruby on Rails plug-in on RubyForge"
39
27
  task :release_plugin=>:rails_plugin do |task|
40
28
  pub = Rake::SshDirPublisher.new("#{RubyForgeConfig[:user_name]}@rubyforge.org",
@@ -63,6 +51,9 @@ end
63
51
  @test_files = 'test/unit/*_test.rb'
64
52
  desc "Test ROXML using the default parser selection behavior"
65
53
  task :test do
54
+ module ROXML
55
+ SILENCE_XML_NAME_WARNING = true
56
+ end
66
57
  require 'lib/roxml'
67
58
  require 'rake/runtest'
68
59
  Rake.run_tests @test_files
@@ -99,8 +90,6 @@ Rake::PackageTask.new(spec.name, spec.version) do |p|
99
90
  "rake/**/*","test/**/*.rb", "test/**/*.xml", "html/**/*"]
100
91
  end
101
92
 
102
- desc "Create the plugin package"
103
-
104
93
  task :package=>:rdoc
105
94
  task :rdoc=>:test
106
95
 
data/lib/roxml.rb CHANGED
@@ -1,20 +1,15 @@
1
- require 'rubygems'
2
- require 'extensions/enumerable'
3
- require 'extensions/array'
4
- require 'extensions/object'
5
- require 'activesupport'
6
-
7
- %w(extensions/array extensions/string options xml).each do |file|
1
+ %w(extensions/active_support extensions/deprecation extensions/array extensions/string options xml).each do |file|
8
2
  require File.join(File.dirname(__FILE__), 'roxml', file)
9
3
  end
10
4
 
11
5
  module ROXML # :nodoc:
12
6
  def self.included(base) # :nodoc:
13
- base.extend ClassMethods::Declarations
14
7
  base.extend ClassMethods::Accessors
8
+ base.extend ClassMethods::Declarations
15
9
  base.extend ClassMethods::Operations
16
10
  base.class_eval do
17
11
  include InstanceMethods::Accessors
12
+ include InstanceMethods::Construction
18
13
  include InstanceMethods::Conversions
19
14
  end
20
15
  end
@@ -33,6 +28,44 @@ module ROXML # :nodoc:
33
28
  end
34
29
  end
35
30
 
31
+ module Construction
32
+ # xml_initialize is called at the end of the #from_xml operation on objects
33
+ # where xml_construct is not in place. Override xml_initialize in order to establish
34
+ # post-import behavior. For example, you can use xml_initialize to map xml attribute
35
+ # values into the object standard initialize function, thus enabling a ROXML object
36
+ # to freely be either xml-backed or instantiated directly via #new.
37
+ # An example of this follows:
38
+ #
39
+ # class Measurement
40
+ # include ROXML
41
+ #
42
+ # xml_reader :units, :attr
43
+ # xml_reader :value, :content
44
+ #
45
+ # def xml_initialize
46
+ # # the object is instantiated, and all xml attributes are imported
47
+ # # and available, i.e., value and units below are the same value and units
48
+ # # found in the xml via the xml_reader declarations above.
49
+ # initialize(value, units)
50
+ # end
51
+ #
52
+ # def initialize(value, units = 'pixels')
53
+ # @value = Float(value)
54
+ # @units = units.to_s
55
+ # if @units.starts_with? 'hundredths-'
56
+ # @value /= 100
57
+ # @units = @units.split('hundredths-')[1]
58
+ # end
59
+ # end
60
+ # end
61
+ #
62
+ # #xml_initialize may be written to take arguments, in which case extra arguments
63
+ # from from_xml will be passed into the function.
64
+ #
65
+ def xml_initialize
66
+ end
67
+ end
68
+
36
69
  module Conversions
37
70
  # Returns a LibXML::XML::Node or a REXML::Element representing this object
38
71
  def to_xml(name = nil)
@@ -50,11 +83,16 @@ module ROXML # :nodoc:
50
83
  # This class defines the annotation methods that are mixed into your
51
84
  # Ruby classes for XML mapping information and behavior.
52
85
  #
53
- # See xml_name, xml_construct, xml, xml_reader and xml_accessor for
86
+ # See xml_name, xml_initialize, xml, xml_reader and xml_accessor for
54
87
  # available annotations.
55
88
  #
56
89
  module ClassMethods # :nodoc:
57
90
  module Declarations
91
+ # A helper which enables us to detect when the xml_name has been explicitly set
92
+ def xml_name? #:nodoc:
93
+ @xml_name
94
+ end
95
+
58
96
  # Sets the name of the XML element that represents this class. Use this
59
97
  # to override the default lowercase class name.
60
98
  #
@@ -66,13 +104,68 @@ module ROXML # :nodoc:
66
104
  # Without the xml_name annotation, the XML mapped tag would have been "bookwithpublisher".
67
105
  #
68
106
  def xml_name(name)
107
+ @xml_name = true
69
108
  @tag_name = name
70
109
  end
71
110
 
72
111
  # Declares an accesser to a certain xml element, whether an attribute, a node,
73
- # or a typed collection of nodes
112
+ # or a typed collection of nodes. Typically you should call xml_reader or xml_accessor
113
+ # rather than calling this method directly, but the instructions below apply to both.
114
+ #
115
+ # == Sym Option
116
+ # [sym] Symbol representing the name of the accessor.
117
+ #
118
+ # === Default naming
119
+ # This name will be the default node or attribute name searched for,
120
+ # if no other is declared. For example,
121
+ #
122
+ # xml_reader :bob, :from => 'bob'
123
+ # xml_accessor :pony, :attr => 'pony'
124
+ #
125
+ # are equivalent to:
126
+ #
127
+ # xml_reader :bob
128
+ # xml_accessor :pony, :attr
129
+ #
130
+ # === Boolean attributes
131
+ # If the name ends in a ?, ROXML will attempt to coerce the value to true or false,
132
+ # with True, TRUE, true and 1 mapping to true and False, FALSE, false and 0 mapping
133
+ # to false, as shown below:
134
+ #
135
+ # xml_reader :desirable?
136
+ # xml_reader :bizzare?, :attr => 'BIZZARE'
137
+ #
138
+ # x = #from_xml(%{
139
+ # <object BIZZARE="1">
140
+ # <desirable>False</desirable>
141
+ # </object>
142
+ # })
143
+ # x.desirable?
144
+ # => false
145
+ # x.bizzare?
146
+ # => true
147
+ #
148
+ # If an unexpected value is encountered, the attribute will be set to nil,
149
+ # unless you provide a block, in which case the block will recived
150
+ # the actual unexpected value.
151
+ #
152
+ # #from_xml(%{
153
+ # <object>
154
+ # <desirable>Dunno</desirable>
155
+ # </object>
156
+ # }).desirable?
157
+ # => nil
158
+ #
159
+ # xml_reader :strange? do |val|
160
+ # val.upcase
161
+ # end
74
162
  #
75
- # [sym] Symbol representing the name of the accessor
163
+ # #from_xml(%{
164
+ # <object>
165
+ # <strange>Dunno</strange>
166
+ # </object>
167
+ # }).strange?
168
+ # => DUNNO
76
169
  #
77
170
  # == Type options
78
171
  # All type arguments may be used as the type argument to indicate just type,
@@ -258,6 +351,8 @@ module ROXML # :nodoc:
258
351
  # For hash types, the block recieves the key and value as arguments, and they should
259
352
  # be returned as an array of [key, value]
260
353
  #
354
+ # For array types, the entire array is passed in, and must be returned in the same fashion.
355
+ #
261
356
  # === Block Shorthands
262
357
  #
263
358
  # Alternatively, you may use block shorthands to specify common coercions, such that:
@@ -281,16 +376,16 @@ module ROXML # :nodoc:
281
376
  def xml(sym, writable = false, type_and_or_opts = :text, opts = nil, &block)
282
377
  opts = Opts.new(sym, *[type_and_or_opts, opts].compact, &block)
283
378
 
284
- tag_refs << case opts.type
379
+ ref = case opts.type
285
380
  when :attr then XMLAttributeRef
286
381
  when :content then XMLTextRef
287
382
  when :text then XMLTextRef
288
383
  when :hash then XMLHashRef
289
384
  when Symbol then raise ArgumentError, "Invalid type argument #{opts.type}"
290
385
  else XMLObjectRef
291
- end.new(sym, opts)
386
+ end.new(opts)
292
387
 
293
- add_accessor(sym, writable, opts.array?, opts.default)
388
+ add_accessor(ref, writable)
294
389
  end
295
390
 
296
391
  # Declares a read-only xml reference. See xml for details.
@@ -303,40 +398,38 @@ module ROXML # :nodoc:
303
398
  xml sym, true, type_and_or_opts, opts, &block
304
399
  end
305
400
 
306
- # On parse, call the target object's initialize function with the listed arguments
401
+ # This method is deprecated, please use xml_initialize instead
307
402
  def xml_construct(*args)
308
- if missing_tag = args.detect {|arg| !tag_refs.map(&:name).include?(arg.to_s) }
403
+ present_tags = tag_refs.map(&:accessor)
404
+ missing_tags = args - present_tags
405
+ unless missing_tags.empty?
309
406
  raise ArgumentError, "All construction tags must be declared first using xml, " +
310
- "xml_reader, or xml_accessor. #{missing_tag} is missing. " +
311
- tag_refs.map(&:name).join(', ') + ' are declared.'
407
+ "xml_reader, or xml_accessor. #{missing_tags.join(', ')} is missing. " +
408
+ "#{present_tags.join(', ')} are declared."
312
409
  end
313
410
  @xml_construction_args = args
314
411
  end
412
+ deprecate :xml_construct => :xml_initialize
315
413
 
316
414
  private
317
- def assert_accessor(name)
318
- @tag_accessors ||= []
319
- raise "Accessor #{name} is already defined as XML accessor in class #{self}" if @tag_accessors.include?(name)
320
- @tag_accessors << name
321
- end
322
-
323
- def add_accessor(name, writable, as_array, default = nil)
324
- assert_accessor(name)
325
- unless instance_methods.include?(name)
326
- default ||= Array.new if as_array
415
+ def add_accessor(ref, writable)
416
+ if tag_refs.map(&:accessor).include? ref.accessor
417
+ raise "Accessor #{ref.accessor} is already defined as XML accessor in class #{self.name}"
418
+ end
419
+ tag_refs << ref
327
420
 
328
- define_method(name) do
329
- val = instance_variable_get("@#{name}")
330
- if val.nil?
331
- val = default.duplicable? ? default.dup : default
332
- instance_variable_set("@#{name}", val)
333
- end
334
- val
421
+ define_method(ref.accessor) do
422
+ result = instance_variable_get("@#{ref.variable_name}")
423
+ if result.nil?
424
+ result = ref.default
425
+ instance_variable_set("@#{ref.variable_name}", result)
335
426
  end
427
+ result
336
428
  end
337
- if writable && !instance_methods.include?("#{name}=")
338
- define_method("#{name}=") do |v|
339
- instance_variable_set("@#{name}", v)
429
+
430
+ if writable && !instance_methods.include?("#{ref.accessor}=")
431
+ define_method("#{ref.accessor}=") do |v|
432
+ instance_variable_set("@#{ref.accessor}", v)
340
433
  end
341
434
  end
342
435
  end
@@ -346,7 +439,7 @@ module ROXML # :nodoc:
346
439
  def xml_construction_args # :nodoc:
347
440
  @xml_construction_args ||= []
348
441
  end
349
-
442
+ deprecate :xml_construction_args
350
443
 
351
444
  # Returns the tag name (also known as xml_name) of the class.
352
445
  # If no tag name is set with xml_name method, returns default class name
@@ -358,7 +451,7 @@ module ROXML # :nodoc:
358
451
  # Returns array of internal reference objects, such as attributes
359
452
  # and composed XML objects
360
453
  def tag_refs
361
- @xml_refs ||= []
454
+ @xml_refs ||= superclass.respond_to?(:tag_refs) ? superclass.tag_refs.clone : []
362
455
  end
363
456
  end
364
457
 
@@ -375,14 +468,17 @@ module ROXML # :nodoc:
375
468
  # or
376
469
  # book = Book.from_xml("<book><name>Beyond Java</name></book>")
377
470
  #
378
- # See also: xml_construct
471
+ # _initialization_args_ passed into from_xml will be passed into
472
+ # the object #xml_initialize method.
473
+ #
474
+ # See also: xml_initialize
379
475
  #
380
- def from_xml(data)
476
+ def from_xml(data, *initialization_args)
381
477
  xml = (data.kind_of?(XML::Node) ? data : XML::Parser.parse(data).root)
382
478
 
383
- unless xml_construction_args.empty?
384
- args = xml_construction_args.map do |arg|
385
- tag_refs.find {|ref| ref.name == arg.to_s }
479
+ unless xml_construction_args_without_deprecation.empty?
480
+ args = xml_construction_args_without_deprecation.map do |arg|
481
+ tag_refs.find {|ref| ref.accessor == arg }
386
482
  end.map {|ref| ref.value(xml) }
387
483
  new(*args)
388
484
  else
@@ -390,15 +486,16 @@ module ROXML # :nodoc:
390
486
  tag_refs.each do |ref|
391
487
  ref.populate(xml, inst)
392
488
  end
489
+ inst.send(:xml_initialize, *initialization_args)
393
490
  end
394
491
  end
395
492
  end
396
493
 
397
494
  # Deprecated in favor of #from_xml
398
495
  def parse(data)
399
- ActiveSupport::Deprecation.warn '#parse has been deprecated, please use #from_xml instead'
400
496
  from_xml(data)
401
497
  end
498
+ deprecate :parse => :from_xml
402
499
  end
403
500
  end
404
501
  end
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'active_support/core_ext/blank'
3
+ require 'active_support/core_ext/duplicable'
4
+ require 'active_support/core_ext/array/extract_options'
5
+ require 'active_support/core_ext/hash/reverse_merge'
6
+ require 'active_support/core_ext/module/delegation'
7
+ require 'active_support/core_ext/module/aliasing'
8
+ require 'active_support/core_ext/object/misc' # returning
9
+ require 'active_support/inflector'
10
+ require 'active_support/core_ext/string/inflections'
11
+ require 'active_support/core_ext/string/starts_ends_with'
12
+
13
+ require 'extensions/array'
14
+
15
+ class Module
16
+ include ActiveSupport::CoreExtensions::Module if ActiveSupport::CoreExtensions.const_defined? :Module
17
+ end
18
+
19
+ class String #:nodoc:
20
+ include ActiveSupport::CoreExtensions::String::Inflections
21
+ include ActiveSupport::CoreExtensions::String::StartsEndsWith
22
+ end
23
+
24
+ class Array #:nodoc:
25
+ include ActiveSupport::CoreExtensions::Array::ExtractOptions
26
+ end
27
+
28
+ class Hash #:nodoc:
29
+ include ActiveSupport::CoreExtensions::Hash::ReverseMerge
30
+ end
@@ -1,5 +1,3 @@
1
- require File.join(File.dirname(__FILE__), '../deprecation')
2
-
3
1
  module ROXML
4
2
  module CoreExtensions
5
3
  module Array #:nodoc:
@@ -18,9 +16,9 @@ module ROXML
18
16
  end
19
17
 
20
18
  def to_h #:nodoc:
21
- ActiveSupport::Deprecation.warn "Please use #to_hash instead"
22
19
  to_hash
23
20
  end
21
+ deprecate :to_h => :to_hash
24
22
  end
25
23
  end
26
24
  end
@@ -1,15 +1,28 @@
1
- require 'active_support'
1
+ require 'active_support/core_ext/kernel/reporting'
2
+ require 'active_support/core_ext/module/attribute_accessors'
3
+ require 'active_support/deprecation'
2
4
  require 'active_support/version'
3
5
 
4
6
  module ActiveSupport # :nodoc:all
5
- if VERSION::MAJOR <= 2 && VERSION::MINOR <= 1
6
- module Deprecation
7
- class << self
7
+ module Deprecation
8
+ class << self
9
+ if VERSION::MAJOR <= 2 && VERSION::MINOR <= 1
8
10
  def deprecation_message(callstack, message = nil)
9
11
  message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
10
12
  "DEPRECATION WARNING: #{message}. #{deprecation_caller_message(callstack)}"
11
13
  end
12
14
  end
13
15
  end
16
+
17
+ module ClassMethods
18
+ def deprecated_method_warning(method_name, message=nil)
19
+ warning = "#{method_name} is deprecated and will be removed from the next major or minor release."
20
+ case message
21
+ when Symbol then "#{warning} (use #{message} instead)"
22
+ when String then "#{warning} (#{message})"
23
+ else warning
24
+ end
25
+ end
26
+ end
14
27
  end
15
28
  end
@@ -6,7 +6,6 @@ class String #:nodoc:
6
6
  include ROXML::CoreExtensions::String::Iterators
7
7
  end
8
8
 
9
- require File.join(File.dirname(__FILE__), 'deprecation')
10
9
  class Object #:nodoc:
11
10
  # Deprecated in favor of explicit #to_s.to_utf
12
11
  def to_utf
data/lib/roxml/options.rb CHANGED
@@ -51,11 +51,11 @@ module ROXML
51
51
  def to_ref(args, type, name)
52
52
  case type
53
53
  when :attr
54
- XMLAttributeRef.new(nil, to_hash_args(args, type, name))
54
+ XMLAttributeRef.new(to_hash_args(args, type, name))
55
55
  when :text
56
- XMLTextRef.new(nil, to_hash_args(args, type, name))
56
+ XMLTextRef.new(to_hash_args(args, type, name))
57
57
  when Symbol
58
- XMLTextRef.new(nil, to_hash_args(args, type, name))
58
+ XMLTextRef.new(to_hash_args(args, type, name))
59
59
  else
60
60
  raise ArgumentError, "Missing key description #{{:type => type, :name => name}.pp_s}"
61
61
  end
@@ -79,14 +79,41 @@ module ROXML
79
79
  end
80
80
 
81
81
  class Opts # :nodoc:
82
- attr_reader :name, :type, :hash, :blocks
82
+ attr_reader :name, :type, :hash, :blocks, :default, :accessor
83
+
84
+ class << self
85
+ def silence_xml_name_warning?
86
+ @silence_xml_name_warning || (ROXML.const_defined?('SILENCE_XML_NAME_WARNING') && ROXML::SILENCE_XML_NAME_WARNING)
87
+ end
88
+
89
+ def silence_xml_name_warning!
90
+ @silence_xml_name_warning = true
91
+ end
92
+ end
83
93
 
84
94
  def initialize(sym, *args, &block)
95
+ @accessor = sym
85
96
  @opts = extract_options!(args)
97
+ @default = @opts.delete(:else)
86
98
 
87
- @opts.reverse_merge!(:from => sym.to_s, :as => [], :else => nil, :in => nil)
99
+ @opts.reverse_merge!(:as => [], :in => nil)
88
100
  @opts[:as] = [*@opts[:as]]
101
+
89
102
  @type = extract_type(args)
103
+ @opts[:as] << :bool if @accessor.to_s.ends_with?('?')
104
+
105
+ if @type.respond_to?(:xml_name?) && @type.xml_name?
106
+ unless self.class.silence_xml_name_warning?
107
+ warn "WARNING: As of 2.3, a breaking change has been in the naming of sub-objects. " +
108
+ "ROXML now considers the xml_name of the sub-object before falling back to the accessor name of the parent. " +
109
+ "Use :from on the parent declaration to override this behavior. Set ROXML::SILENCE_XML_NAME_WARNING to avoid this message."
110
+ self.class.silence_xml_name_warning!
111
+ end
112
+ @opts[:from] ||= @type.tag_name
113
+ else
114
+ @opts[:from] ||= variable_name
115
+ end
116
+
90
117
  @blocks = collect_blocks(block, @opts[:as])
91
118
 
92
119
  @name = @opts[:from].to_s
@@ -98,12 +125,12 @@ module ROXML
98
125
  raise ArgumentError, "Can't specify both :else default and :required" if required? && default
99
126
  end
100
127
 
101
- def hash
102
- @hash ||= HashDesc.new(@opts.delete(:hash), name) if hash?
128
+ def variable_name
129
+ accessor.to_s.ends_with?('?') ? accessor.to_s.chomp('?') : accessor.to_s
103
130
  end
104
131
 
105
- def default
106
- @opts[:else]
132
+ def hash
133
+ @hash ||= HashDesc.new(@opts.delete(:hash), name) if hash?
107
134
  end
108
135
 
109
136
  def hash?
@@ -131,17 +158,69 @@ module ROXML
131
158
  end
132
159
 
133
160
  private
161
+ BLOCK_TO_FLOAT = lambda do |val|
162
+ if val.is_a? Array
163
+ val.collect do |v|
164
+ Float(v)
165
+ end
166
+ else
167
+ Float(val)
168
+ end
169
+ end
170
+
171
+ BLOCK_TO_INT = lambda do |val|
172
+ if val.is_a? Array
173
+ val.collect do |v|
174
+ Integer(v)
175
+ end
176
+ else
177
+ Integer(val)
178
+ end
179
+ end
180
+
181
+ TRUE_VALS = %w{TRUE True true 1}
182
+ FALSE_VALS = %w{FALSE False false 0}
183
+
134
184
  BLOCK_SHORTHANDS = {
135
- :integer => lambda {|val| Integer(val) },
136
- Integer => lambda {|val| Integer(val) },
137
- :float => lambda {|val| Float(val) },
138
- Float => lambda {|val| Float(val) }
185
+ :integer => BLOCK_TO_INT,
186
+ Integer => BLOCK_TO_INT,
187
+ :float => BLOCK_TO_FLOAT,
188
+ Float => BLOCK_TO_FLOAT,
189
+ :bool => nil,
190
+ :bool_standalone => lambda do |val|
191
+ if TRUE_VALS.include? val
192
+ true
193
+ elsif FALSE_VALS.include? val
194
+ false
195
+ else
196
+ nil
197
+ end
198
+ end,
199
+
200
+ :bool_combined => lambda do |val|
201
+ if TRUE_VALS.include? val
202
+ true
203
+ elsif FALSE_VALS.include? val
204
+ false
205
+ else
206
+ val
207
+ end
208
+ end
139
209
  }
140
210
 
141
211
  def collect_blocks(block, as)
142
212
  shorthands = as & BLOCK_SHORTHANDS.keys
143
- raise ArgumentError, "multiple block shorthands supplied #{shorthands.map(&:to_s).join(', ')}" if shorthands.size > 1
144
- [BLOCK_SHORTHANDS[shorthands.first], block].compact
213
+ if shorthands.size > 1
214
+ raise ArgumentError, "multiple block shorthands supplied #{shorthands.map(&:to_s).join(', ')}"
215
+ end
216
+
217
+ shorthand = shorthands.first
218
+ if shorthand == :bool
219
+ # if a second block is present, and we can't coerce the xml value
220
+ # to bool, we need to be able to pass it to the user-provided block
221
+ shorthand = block ? :bool_combined : :bool_standalone
222
+ end
223
+ [BLOCK_SHORTHANDS[shorthand], block].compact
145
224
  end
146
225
 
147
226
  def extract_options!(args)
data/lib/roxml/xml.rb CHANGED
@@ -16,20 +16,18 @@ module ROXML
16
16
  # Internal base class that represents an XML - Class binding.
17
17
  #
18
18
  class XMLRef # :nodoc:
19
- attr_reader :accessor
20
- delegate :name, :required?, :array?, :default, :wrapper, :blocks, :to => :opts
19
+ delegate :name, :required?, :array?, :wrapper, :blocks, :accessor, :variable_name, :to => :opts
21
20
  alias_method :xpath_name, :name
22
21
 
23
- def initialize(accessor, args)
24
- @accessor = accessor
25
- @opts = args
22
+ def initialize(opts)
23
+ @opts = opts
26
24
  end
27
25
 
28
26
  # Reads data from the XML element and populates the instance
29
27
  # accordingly.
30
28
  def populate(xml, instance)
31
29
  data = value(xml)
32
- instance.instance_variable_set("@#{accessor}", data) if data
30
+ instance.instance_variable_set("@#{variable_name}", data)
33
31
  instance
34
32
  end
35
33
 
@@ -43,6 +41,11 @@ module ROXML
43
41
  end
44
42
  end
45
43
 
44
+ def default
45
+ @default ||= @opts.default || (@opts.array? ? Array.new : nil)
46
+ @default.duplicable? ? @default.dup : @default
47
+ end
48
+
46
49
  def value(xml)
47
50
  value = fetch_value(xml)
48
51
  if value.blank?
@@ -56,7 +59,7 @@ module ROXML
56
59
  attr_reader :opts
57
60
 
58
61
  def apply_blocks(val)
59
- blocks.each {|block| val = block[*val] } unless blocks.empty?
62
+ blocks.each {|block| val = block[val] }
60
63
  val
61
64
  end
62
65
 
@@ -133,7 +136,7 @@ module ROXML
133
136
  e.content.strip.to_latin if e.content
134
137
  end
135
138
  else
136
- child = xml.search(name).first
139
+ child = xml.search(xpath).first
137
140
  child.content if child
138
141
  end
139
142
  end
@@ -150,6 +153,11 @@ module ROXML
150
153
  class XMLHashRef < XMLTextRef # :nodoc:
151
154
  delegate :hash, :to => :opts
152
155
 
156
+ def default
157
+ result = super
158
+ result.nil? ? {} : result
159
+ end
160
+
153
161
  private
154
162
  # Updates the composed XML object in the given XML block to
155
163
  # the value provided.
@@ -173,7 +181,7 @@ module ROXML
173
181
  super(kvp)
174
182
  end
175
183
  end
176
- vals.to_hash
184
+ vals.to_hash if vals
177
185
  end
178
186
  end
179
187
 
@@ -1,3 +1,5 @@
1
+ require 'rexml/document'
2
+
1
3
  module ROXML
2
4
  module XML # :nodoc:all
3
5
  Document = REXML::Document
data/roxml.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "roxml"
3
3
  s.summary = "Ruby Object to XML mapping library"
4
- s.version = "2.2"
4
+ s.version = "2.3.0"
5
5
  s.homepage = "http://roxml.rubyforge.org"
6
6
  s.platform = Gem::Platform::RUBY
7
7
  s.authors = ["Ben Woosley", "Zak Mandhro", "Anders Engstrom", "Russ Olsen"]
@@ -13,6 +13,7 @@ Gem::Specification.new do |s|
13
13
  'Rakefile',
14
14
  'roxml.gemspec',
15
15
  'lib/roxml.rb',
16
+ 'lib/roxml/extensions/active_support.rb',
16
17
  'lib/roxml/extensions/array.rb',
17
18
  'lib/roxml/extensions/array/conversions.rb',
18
19
  'lib/roxml/extensions/string.rb',
@@ -66,7 +67,7 @@ Gem::Specification.new do |s|
66
67
  'test/unit/xml_text_test.rb']
67
68
  s.requirements << 'none'
68
69
  s.add_dependency 'extensions', '>= 0.6.0'
69
- s.add_development_dependency('Empact-rails-plugin-package-task', '>= 0.1') if s.respond_to?(:add_development_dependency)
70
+ s.add_dependency 'activesupport', '>= 2.0.0'
70
71
  s.require_path = 'lib'
71
72
  s.test_files = [
72
73
  'test/unit/options_test.rb',
@@ -1,4 +1,4 @@
1
- <book ISDN="0201710897">
1
+ <book ISBN="0201710897">
2
2
  <title>The PickAxe</title>
3
3
  <description><![CDATA[Probably the best Ruby book out there]]></description>
4
4
  <author>David Thomas, Andrew Hunt, Dave Thomas</author>
data/test/mocks/mocks.rb CHANGED
@@ -72,7 +72,10 @@ class Measurement
72
72
 
73
73
  xml_reader :units, :attr
74
74
  xml_reader :value, :content
75
- xml_construct :value, :units
75
+
76
+ def xml_initialize
77
+ initialize(value, units)
78
+ end
76
79
 
77
80
  def initialize(value, units = 'pixels')
78
81
  @value = Float(value)
@@ -83,6 +86,10 @@ class Measurement
83
86
  end
84
87
  end
85
88
 
89
+ def to_s
90
+ "#{value} #{units}"
91
+ end
92
+
86
93
  def ==(other)
87
94
  other.units == @units && other.value == @value
88
95
  end
@@ -244,7 +251,7 @@ class PersonWithMother
244
251
 
245
252
  xml_name :person
246
253
  xml_reader :name
247
- xml_reader :mother, PersonWithMother
254
+ xml_reader :mother, PersonWithMother, :from => 'mother'
248
255
  end
249
256
 
250
257
  class PersonWithGuardedMother
@@ -261,4 +268,4 @@ class PersonWithMotherOrMissing
261
268
  xml_reader :age, :attr, :else => 21
262
269
  xml_reader :name, :else => 'Anonymous'
263
270
  xml_reader :mother, PersonWithMotherOrMissing, :else => Person.new
264
- end
271
+ end
data/test/test_helper.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require "lib/roxml"
2
- require "test/unit"
3
2
  require 'test/mocks/mocks'
4
3
  require 'test/mocks/dictionaries'
5
4
 
@@ -1,19 +1,77 @@
1
1
  require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
2
 
3
- class TestXMLAttribute < Test::Unit::TestCase
3
+ class MeasurementWithXmlConstruct
4
+ include ROXML
5
+
6
+ xml_reader :units, :attr
7
+ xml_reader :value, :content
8
+
9
+ xml_construct_without_deprecation :value, :units
10
+
11
+ def initialize(value, units = 'pixels')
12
+ @value = Float(value)
13
+ @units = units.to_s
14
+ if @units.starts_with? 'hundredths-'
15
+ @value /= 100
16
+ @units = @units.split('hundredths-')[1]
17
+ end
18
+ end
19
+
20
+ def ==(other)
21
+ other.units == @units && other.value == @value
22
+ end
23
+ end
24
+
25
+ class BookWithDepthWithXmlConstruct
26
+ include ROXML
27
+
28
+ xml_reader :isbn, :attr => 'ISBN'
29
+ xml_reader :title
30
+ xml_reader :description, :as => :cdata
31
+ xml_reader :author
32
+ xml_reader :depth, MeasurementWithXmlConstruct
33
+ end
34
+
35
+ class InheritedBookWithDepthWithXmlConstruct < Book
36
+ xml_reader :depth, MeasurementWithXmlConstruct
37
+ end
38
+
39
+ class TestXMLConstruct < Test::Unit::TestCase
40
+ def test_is_deprecated
41
+ assert_deprecated do
42
+ MeasurementWithXmlConstruct.xml_construction_args
43
+ end
44
+ end
45
+
4
46
  def test_initialize_is_run
5
- m = Measurement.from_xml('<measurement units="hundredths-meters">1130</measurement>')
47
+ m = MeasurementWithXmlConstruct.from_xml('<measurement units="hundredths-meters">1130</measurement>')
6
48
  assert_equal 11.3, m.value
7
49
  assert_equal 'meters', m.units
8
50
  end
9
51
 
10
52
  def test_initialize_is_run_for_nested_type
11
- b = BookWithDepth.from_xml(fixture(:book_with_depth))
53
+ b = BookWithDepthWithXmlConstruct.from_xml(fixture(:book_with_depth))
12
54
  assert_equal Measurement.new(11.3, 'meters'), b.depth
13
55
  end
14
56
 
15
57
  def test_initialize_is_run_for_nested_type_with_inheritance
16
- b = InheritedBookWithDepth.from_xml(fixture(:book_with_depth))
58
+ b = InheritedBookWithDepthWithXmlConstruct.from_xml(fixture(:book_with_depth))
17
59
  assert_equal Measurement.new(11.3, 'meters'), b.depth
18
60
  end
61
+
62
+ def test_xml_name_uses_accessor_not_name
63
+ assert_nothing_raised do
64
+ Class.new do
65
+ include ROXML
66
+
67
+ xml_reader :bar, :attr => 'Foo'
68
+ xml_reader :foo, :text => 'Foo'
69
+ xml_reader :baz, :attr => 'Bar'
70
+
71
+ xml_construct_without_deprecation :baz, :bar, :foo
72
+ def initialize(baz, bar, foo)
73
+ end
74
+ end
75
+ end
76
+ end
19
77
  end
@@ -47,4 +47,19 @@ class TestXMLHash < Test::Unit::TestCase
47
47
  assert_equal Hash, dict.definitions.class
48
48
  assert_equal @contents, dict.definitions
49
49
  end
50
+
51
+ def test_it_should_gracefully_handle_empty_hash
52
+ dict = Class.new do
53
+ include ROXML
54
+
55
+ xml_reader :missing_hash, {:key => :name, :value => :content}, :in => 'EmptyDictionary'
56
+ end
57
+
58
+ assert_equal({}, dict.from_xml(%{
59
+ <dict>
60
+ <EmptyDictionary>
61
+ </EmptyDictionary>
62
+ </dict>
63
+ }).missing_hash)
64
+ end
50
65
  end
@@ -1,6 +1,80 @@
1
1
  require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
2
 
3
+ # Parent | Child
4
+ # :from | no :from |
5
+ # -------------------|--------------
6
+ # :from | xml_name | xml_name-d
7
+ # value | value |
8
+ # -------------------|--------------
9
+ # :from | parent's |
10
+ # value | accessor | un-xml_name-d
11
+ # | name |
12
+
13
+ class Child
14
+ include ROXML
15
+ end
16
+
17
+ class NamedChild
18
+ include ROXML
19
+
20
+ xml_name :xml_name_of_child
21
+ end
22
+
23
+ class ParentOfNamedChild
24
+ include ROXML
25
+
26
+ xml_name :parent
27
+ xml_accessor :child_accessor_name, NamedChild
28
+ end
29
+
30
+ class ParentOfNamedChildWithFrom
31
+ include ROXML
32
+
33
+ xml_name :parent
34
+ xml_accessor :child_accessor_name, NamedChild, :from => 'child_from_name'
35
+ end
36
+
37
+ class ParentOfUnnamedChild
38
+ include ROXML
39
+
40
+ xml_name :parent
41
+ xml_accessor :child_accessor_name, Child
42
+ end
43
+
44
+ class ParentOfUnnamedChildWithFrom
45
+ include ROXML
46
+
47
+ xml_name :parent
48
+ xml_accessor :child_accessor_name, Child, :from => 'child_from_name'
49
+ end
50
+
3
51
  class TestXMLName < Test::Unit::TestCase
52
+ def test_from_always_dominates_attribute_name_xml_name_or_not
53
+ parent = ParentOfNamedChildWithFrom.new
54
+ parent.child_accessor_name = Child.new
55
+
56
+ assert_equal "<parent><child_from_name/></parent>", parent.to_xml.to_s.gsub(/[\n ]/, '')
57
+
58
+ parent = ParentOfUnnamedChildWithFrom.new
59
+ parent.child_accessor_name = Child.new
60
+
61
+ assert_equal "<parent><child_from_name/></parent>", parent.to_xml.to_s.gsub(/[\n ]/, '')
62
+ end
63
+
64
+ def test_attribute_name_comes_from_the_xml_name_value_if_present
65
+ parent = ParentOfNamedChild.new
66
+ parent.child_accessor_name = Child.new
67
+
68
+ assert_equal "<parent><xml_name_of_child/></parent>", parent.to_xml.to_s.gsub(/[\n ]/, '')
69
+ end
70
+
71
+ def test_attribute_name_comes_from_parent_accessor_by_default
72
+ parent = ParentOfUnnamedChild.new
73
+ parent.child_accessor_name = Child.new
74
+
75
+ assert_equal "<parent><child_accessor_name/></parent>", parent.to_xml.to_s.gsub(/[\n ]/, '')
76
+ end
77
+
4
78
  def test_named_books_picked_up
5
79
  named = Library.from_xml(fixture(:library))
6
80
  assert named.books
@@ -18,6 +18,7 @@ class TestXMLNamespaces < Test::Unit::TestCase
18
18
  end
19
19
 
20
20
  def test_that_rexml_follows_nameless_default_namespace
21
+ require 'rexml/document'
21
22
  xml = REXML::Document.new(
22
23
  '<container xmlns="http://fakenamespace.org"><node>Yeah, content</node></container>')
23
24
 
@@ -21,6 +21,17 @@ class TestXMLText < Test::Unit::TestCase
21
21
  BookWithAuthors.from_xml(fixture(:book_with_authors)).authors.sort
22
22
  end
23
23
 
24
+ def test_empty_array_result_returned_properly
25
+ empty_array = Class.new do
26
+ include ROXML
27
+
28
+ xml_reader :missing_array, [:text], :from => 'missing'
29
+ end
30
+
31
+ obj = empty_array.from_xml('<empty_array></empty_array>')
32
+ assert_equal [], obj.missing_array
33
+ end
34
+
24
35
  def test_text_modification
25
36
  person = Person.from_xml(fixture(:person))
26
37
  assert_equal("Ben Franklin", person.name)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: Empact-roxml
3
3
  version: !ruby/object:Gem::Version
4
- version: "2.2"
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Woosley
@@ -24,6 +24,15 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: 0.6.0
26
26
  version:
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 2.0.0
35
+ version:
27
36
  description: 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.
28
37
  email: ben.woosley@gmail.com
29
38
  executables: []
@@ -38,6 +47,7 @@ files:
38
47
  - Rakefile
39
48
  - roxml.gemspec
40
49
  - lib/roxml.rb
50
+ - lib/roxml/extensions/active_support.rb
41
51
  - lib/roxml/extensions/array.rb
42
52
  - lib/roxml/extensions/array/conversions.rb
43
53
  - lib/roxml/extensions/string.rb