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 +8 -4
- data/Rakefile +3 -14
- data/lib/roxml.rb +143 -46
- data/lib/roxml/extensions/active_support.rb +30 -0
- data/lib/roxml/extensions/array/conversions.rb +1 -3
- data/lib/roxml/extensions/deprecation.rb +17 -4
- data/lib/roxml/extensions/string.rb +0 -1
- data/lib/roxml/options.rb +94 -15
- data/lib/roxml/xml.rb +17 -9
- data/lib/roxml/xml/rexml.rb +2 -0
- data/roxml.gemspec +3 -2
- data/test/fixtures/book_with_depth.xml +1 -1
- data/test/mocks/mocks.rb +10 -3
- data/test/test_helper.rb +0 -1
- data/test/unit/xml_construct_test.rb +62 -4
- data/test/unit/xml_hash_test.rb +15 -0
- data/test/unit/xml_name_test.rb +74 -0
- data/test/unit/xml_namespace_test.rb +1 -0
- data/test/unit/xml_text_test.rb +11 -0
- metadata +11 -1
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
|
-
|
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
|
-
|
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
|
-
|
110
|
-
|
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
|
-
|
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,
|
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
|
-
#
|
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
|
-
|
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(
|
386
|
+
end.new(opts)
|
292
387
|
|
293
|
-
add_accessor(
|
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
|
-
#
|
401
|
+
# This method is deprecated, please use xml_initialize instead
|
307
402
|
def xml_construct(*args)
|
308
|
-
|
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. #{
|
311
|
-
|
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
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
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
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
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
|
-
|
338
|
-
|
339
|
-
|
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
|
-
#
|
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
|
384
|
-
args =
|
385
|
-
tag_refs.find {|ref| ref.
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
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(
|
54
|
+
XMLAttributeRef.new(to_hash_args(args, type, name))
|
55
55
|
when :text
|
56
|
-
XMLTextRef.new(
|
56
|
+
XMLTextRef.new(to_hash_args(args, type, name))
|
57
57
|
when Symbol
|
58
|
-
XMLTextRef.new(
|
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!(:
|
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
|
102
|
-
|
128
|
+
def variable_name
|
129
|
+
accessor.to_s.ends_with?('?') ? accessor.to_s.chomp('?') : accessor.to_s
|
103
130
|
end
|
104
131
|
|
105
|
-
def
|
106
|
-
@opts
|
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 =>
|
136
|
-
Integer =>
|
137
|
-
:float =>
|
138
|
-
Float =>
|
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
|
-
|
144
|
-
|
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
|
-
|
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(
|
24
|
-
@
|
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("@#{
|
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[
|
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(
|
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
|
|
data/lib/roxml/xml/rexml.rb
CHANGED
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.
|
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.
|
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',
|
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
|
-
|
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,19 +1,77 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'test_helper')
|
2
2
|
|
3
|
-
class
|
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 =
|
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 =
|
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 =
|
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
|
data/test/unit/xml_hash_test.rb
CHANGED
@@ -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
|
data/test/unit/xml_name_test.rb
CHANGED
@@ -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
|
|
data/test/unit/xml_text_test.rb
CHANGED
@@ -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:
|
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
|