roxml 2.3.2 → 2.4.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.
- data/History.txt +145 -0
- data/Manifest.txt +81 -0
- data/Rakefile +85 -95
- data/TODO +42 -0
- data/html/index.html +278 -0
- data/html/style.css +79 -0
- data/lib/roxml.rb +112 -49
- data/lib/roxml/extensions/active_support.rb +37 -12
- data/lib/roxml/extensions/array/conversions.rb +12 -2
- data/lib/roxml/extensions/deprecation.rb +4 -4
- data/lib/roxml/options.rb +39 -30
- data/lib/roxml/xml.rb +119 -54
- data/lib/roxml/xml/rexml.rb +2 -2
- data/roxml.gemspec +40 -101
- data/tasks/test.rake +42 -0
- data/test/bugs/rexml_bugs.rb +15 -0
- data/test/fixtures/book_with_octal_pages.xml +4 -0
- data/test/mocks/mocks.rb +0 -4
- data/test/test_helper.rb +1 -1
- data/test/unit/array_test.rb +16 -0
- data/test/unit/freeze_test.rb +71 -0
- data/test/unit/inheritance_test.rb +26 -3
- data/test/unit/options_test.rb +20 -19
- data/test/unit/overriden_output_test.rb +33 -0
- data/test/unit/roxml_test.rb +7 -0
- data/test/unit/xml_bool_test.rb +16 -11
- data/test/unit/xml_convention_test.rb +150 -0
- data/test/unit/xml_hash_test.rb +41 -0
- data/test/unit/xml_name_test.rb +29 -6
- data/test/unit/xml_object_test.rb +30 -0
- data/vendor/override_rake_task/README +30 -0
- data/vendor/override_rake_task/init.rb +1 -0
- data/vendor/override_rake_task/install.rb +46 -0
- data/vendor/override_rake_task/lib/override_rake_task.rb +16 -0
- metadata +77 -34
@@ -3,30 +3,55 @@ require 'active_support/core_ext/symbol'
|
|
3
3
|
require 'active_support/core_ext/blank'
|
4
4
|
require 'active_support/core_ext/duplicable'
|
5
5
|
require 'active_support/core_ext/array/extract_options'
|
6
|
+
|
7
|
+
class Array #:nodoc:
|
8
|
+
include ActiveSupport::CoreExtensions::Array::ExtractOptions
|
9
|
+
end
|
10
|
+
|
6
11
|
require 'active_support/core_ext/hash/reverse_merge'
|
7
12
|
require 'active_support/core_ext/module/delegation'
|
8
13
|
require 'active_support/core_ext/module/aliasing'
|
14
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
9
15
|
require 'active_support/core_ext/object/misc' # returning
|
10
16
|
require 'active_support/inflector'
|
11
|
-
require 'active_support/
|
12
|
-
require 'active_support/core_ext/string
|
17
|
+
require 'active_support/multibyte'
|
18
|
+
require 'active_support/core_ext/string'
|
19
|
+
class String
|
20
|
+
# This conflicts with builder, unless builder is required first, which we don't want to force on people
|
21
|
+
undef_method :to_xs if method_defined?(:to_xs)
|
22
|
+
end
|
13
23
|
|
14
24
|
require 'extensions/enumerable'
|
15
25
|
require 'extensions/array'
|
16
26
|
|
17
|
-
class Module
|
27
|
+
class Module #:nodoc:
|
18
28
|
include ActiveSupport::CoreExtensions::Module if ActiveSupport::CoreExtensions.const_defined? :Module
|
19
29
|
end
|
20
30
|
|
21
|
-
class String #:nodoc:
|
22
|
-
include ActiveSupport::CoreExtensions::String::Inflections
|
23
|
-
include ActiveSupport::CoreExtensions::String::StartsEndsWith
|
24
|
-
end
|
25
|
-
|
26
|
-
class Array #:nodoc:
|
27
|
-
include ActiveSupport::CoreExtensions::Array::ExtractOptions
|
28
|
-
end
|
29
|
-
|
30
31
|
class Hash #:nodoc:
|
31
32
|
include ActiveSupport::CoreExtensions::Hash::ReverseMerge
|
33
|
+
end
|
34
|
+
|
35
|
+
class Object #:nodoc:
|
36
|
+
unless method_defined?(:try)
|
37
|
+
# Taken from the upcoming ActiveSupport 2.3
|
38
|
+
#
|
39
|
+
# Tries to send the method only if object responds to it. Return +nil+ otherwise.
|
40
|
+
# It will also forward any arguments and/or block like Object#send does.
|
41
|
+
#
|
42
|
+
# ==== Example :
|
43
|
+
#
|
44
|
+
# # Without try
|
45
|
+
# @person ? @person.name : nil
|
46
|
+
#
|
47
|
+
# With try
|
48
|
+
# @person.try(:name)
|
49
|
+
#
|
50
|
+
# # try also accepts arguments/blocks for the method it is trying
|
51
|
+
# Person.try(:find, 1)
|
52
|
+
# @people.try(:map) {|p| p.name}
|
53
|
+
def try(method, *args, &block)
|
54
|
+
send(method, *args, &block) if respond_to?(method, true)
|
55
|
+
end
|
56
|
+
end
|
32
57
|
end
|
@@ -9,16 +9,26 @@ module ROXML
|
|
9
9
|
# => {:key => :value, 1 => 2, 'key' => 'value'}
|
10
10
|
#
|
11
11
|
def to_hash
|
12
|
-
inject({}) do |result, (k, v)|
|
13
|
-
result[k]
|
12
|
+
hash = inject({}) do |result, (k, v)|
|
13
|
+
result[k] ||= []
|
14
|
+
result[k] << v
|
14
15
|
result
|
15
16
|
end
|
17
|
+
hash.each_pair do |k, v|
|
18
|
+
hash[k] = v.only if v.one?
|
19
|
+
end
|
20
|
+
hash
|
16
21
|
end
|
17
22
|
|
18
23
|
def to_h #:nodoc:
|
19
24
|
to_hash
|
20
25
|
end
|
21
26
|
deprecate :to_h => :to_hash
|
27
|
+
|
28
|
+
def apply_to(val)
|
29
|
+
# Only makes sense for arrays of blocks... maybe better outside Array...
|
30
|
+
inject(val) {|val, block| block.call(val) }
|
31
|
+
end
|
22
32
|
end
|
23
33
|
end
|
24
34
|
end
|
@@ -8,7 +8,7 @@ module ActiveSupport # :nodoc:all
|
|
8
8
|
class << self
|
9
9
|
if VERSION::MAJOR <= 2 && VERSION::MINOR <= 1
|
10
10
|
def deprecation_message(callstack, message = nil)
|
11
|
-
message ||= "You are using deprecated behavior which will be removed from the next major or minor release
|
11
|
+
message ||= "You are using deprecated behavior which will be removed from the next major or minor release"
|
12
12
|
"DEPRECATION WARNING: #{message}. #{deprecation_caller_message(callstack)}"
|
13
13
|
end
|
14
14
|
end
|
@@ -16,10 +16,10 @@ module ActiveSupport # :nodoc:all
|
|
16
16
|
|
17
17
|
module ClassMethods
|
18
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
|
19
|
+
warning = "#{method_name} is deprecated and will be removed from the next major or minor release"
|
20
20
|
case message
|
21
|
-
when Symbol then "#{warning} (use #{message} instead)"
|
22
|
-
when String then "#{warning} (#{message})"
|
21
|
+
when Symbol then "#{warning}. (use #{message} instead)"
|
22
|
+
when String then "#{warning}. (#{message})"
|
23
23
|
else warning
|
24
24
|
end
|
25
25
|
end
|
data/lib/roxml/options.rb
CHANGED
@@ -12,27 +12,19 @@ module ROXML
|
|
12
12
|
|
13
13
|
@wrapper = wrapper
|
14
14
|
if opts.has_key? :attrs
|
15
|
-
@key =
|
16
|
-
@value =
|
15
|
+
@key = to_hash_args(opts, :attr, opts[:attrs][0])
|
16
|
+
@value = to_hash_args(opts, :attr, opts[:attrs][1])
|
17
17
|
else
|
18
|
-
@key =
|
19
|
-
@value =
|
18
|
+
@key = to_hash_args opts, *fetch_element(opts, :key)
|
19
|
+
@value = to_hash_args opts, *fetch_element(opts, :value)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def types
|
24
|
-
[@key.class, @value.class]
|
25
|
-
end
|
26
|
-
|
27
|
-
def names
|
28
|
-
[@key.name, @value.name]
|
29
|
-
end
|
30
|
-
|
31
23
|
private
|
32
24
|
def fetch_element(opts, what)
|
33
25
|
case opts[what]
|
34
26
|
when Hash
|
35
|
-
raise ArgumentError, "Hash #{what} is over-specified: #{opts[what].
|
27
|
+
raise ArgumentError, "Hash #{what} is over-specified: #{opts[what].inspect}" unless opts[what].keys.one?
|
36
28
|
type = opts[what].keys.first
|
37
29
|
[type, opts[what][type]]
|
38
30
|
when :content
|
@@ -48,19 +40,6 @@ module ROXML
|
|
48
40
|
end
|
49
41
|
end
|
50
42
|
|
51
|
-
def to_ref(args, type, name)
|
52
|
-
case type
|
53
|
-
when :attr
|
54
|
-
XMLAttributeRef.new(to_hash_args(args, type, name))
|
55
|
-
when :text
|
56
|
-
XMLTextRef.new(to_hash_args(args, type, name))
|
57
|
-
when Symbol
|
58
|
-
XMLTextRef.new(to_hash_args(args, type, name))
|
59
|
-
else
|
60
|
-
raise ArgumentError, "Missing key description #{{:type => type, :name => name}.pp_s}"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
43
|
def to_hash_args(args, type, name)
|
65
44
|
args = [args] unless args.is_a? Array
|
66
45
|
|
@@ -73,13 +52,13 @@ module ROXML
|
|
73
52
|
Opts.new(name, opts)
|
74
53
|
else
|
75
54
|
opts = args.extract_options!
|
76
|
-
raise opts.
|
55
|
+
raise opts.inspect
|
77
56
|
end
|
78
57
|
end
|
79
58
|
end
|
80
59
|
|
81
60
|
class Opts # :nodoc:
|
82
|
-
attr_reader :name, :type, :hash, :blocks, :
|
61
|
+
attr_reader :name, :type, :hash, :blocks, :accessor, :to_xml
|
83
62
|
|
84
63
|
class << self
|
85
64
|
def silence_xml_name_warning?
|
@@ -95,6 +74,11 @@ module ROXML
|
|
95
74
|
@accessor = sym
|
96
75
|
@opts = extract_options!(args)
|
97
76
|
@default = @opts.delete(:else)
|
77
|
+
@to_xml = @opts.delete(:to_xml)
|
78
|
+
|
79
|
+
if @opts.has_key?(:readonly)
|
80
|
+
raise ArgumentError, "There is no 'readonly' option. You probably mean to use :frozen => true"
|
81
|
+
end
|
98
82
|
|
99
83
|
@opts.reverse_merge!(:as => [], :in => nil)
|
100
84
|
@opts[:as] = [*@opts[:as]]
|
@@ -102,7 +86,7 @@ module ROXML
|
|
102
86
|
@type = extract_type(args)
|
103
87
|
@opts[:as] << :bool if @accessor.to_s.ends_with?('?')
|
104
88
|
|
105
|
-
if @type.
|
89
|
+
if @type.try(:xml_name_without_deprecation?)
|
106
90
|
unless self.class.silence_xml_name_warning?
|
107
91
|
warn "WARNING: As of 2.3, a breaking change has been in the naming of sub-objects. " +
|
108
92
|
"ROXML now considers the xml_name of the sub-object before falling back to the accessor name of the parent. " +
|
@@ -122,7 +106,7 @@ module ROXML
|
|
122
106
|
@name = '*'
|
123
107
|
end
|
124
108
|
|
125
|
-
raise ArgumentError, "Can't specify both :else default and :required" if required? && default
|
109
|
+
raise ArgumentError, "Can't specify both :else default and :required" if required? && @default
|
126
110
|
end
|
127
111
|
|
128
112
|
def variable_name
|
@@ -137,6 +121,10 @@ module ROXML
|
|
137
121
|
@type == :hash
|
138
122
|
end
|
139
123
|
|
124
|
+
def name?
|
125
|
+
@name == '*'
|
126
|
+
end
|
127
|
+
|
140
128
|
def content?
|
141
129
|
@type == :content
|
142
130
|
end
|
@@ -157,6 +145,27 @@ module ROXML
|
|
157
145
|
@opts[:required]
|
158
146
|
end
|
159
147
|
|
148
|
+
def freeze?
|
149
|
+
@opts[:frozen]
|
150
|
+
end
|
151
|
+
|
152
|
+
def default
|
153
|
+
@default ||= [] if array?
|
154
|
+
@default ||= {} if hash?
|
155
|
+
@default.duplicable? ? @default.dup : @default
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_ref(inst)
|
159
|
+
case type
|
160
|
+
when :attr then XMLAttributeRef
|
161
|
+
when :content then XMLTextRef
|
162
|
+
when :text then XMLTextRef
|
163
|
+
when :hash then XMLHashRef
|
164
|
+
when Symbol then raise ArgumentError, "Invalid type argument #{opts.type}"
|
165
|
+
else XMLObjectRef
|
166
|
+
end.new(self, inst)
|
167
|
+
end
|
168
|
+
|
160
169
|
private
|
161
170
|
BLOCK_TO_FLOAT = lambda do |val|
|
162
171
|
if val.is_a? Array
|
data/lib/roxml/xml.rb
CHANGED
@@ -16,23 +16,16 @@ module ROXML
|
|
16
16
|
# Internal base class that represents an XML - Class binding.
|
17
17
|
#
|
18
18
|
class XMLRef # :nodoc:
|
19
|
-
delegate :
|
20
|
-
alias_method :xpath_name, :name
|
19
|
+
delegate :required?, :array?, :blocks, :accessor, :variable_name, :default, :to => :opts
|
21
20
|
|
22
|
-
def initialize(opts)
|
21
|
+
def initialize(opts, instance)
|
23
22
|
@opts = opts
|
23
|
+
@instance = instance
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
data = value(xml)
|
30
|
-
instance.instance_variable_set("@#{variable_name}", data)
|
31
|
-
instance
|
32
|
-
end
|
33
|
-
|
34
|
-
def name?
|
35
|
-
false
|
26
|
+
def to_xml
|
27
|
+
val = @instance.__send__(accessor)
|
28
|
+
opts.to_xml.respond_to?(:call) ? opts.to_xml.call(val) : val
|
36
29
|
end
|
37
30
|
|
38
31
|
def update_xml(xml, value)
|
@@ -41,33 +34,79 @@ module ROXML
|
|
41
34
|
end
|
42
35
|
end
|
43
36
|
|
37
|
+
def name
|
38
|
+
conventionize(opts.name)
|
39
|
+
end
|
40
|
+
alias_method :xpath_name, :name
|
41
|
+
|
44
42
|
def default
|
45
43
|
@default ||= @opts.default || (@opts.array? ? Array.new : nil)
|
46
44
|
@default.duplicable? ? @default.dup : @default
|
47
45
|
end
|
48
46
|
|
49
|
-
def
|
47
|
+
def value_in(xml)
|
50
48
|
value = fetch_value(xml)
|
51
|
-
|
52
|
-
raise RequiredElementMissing, accessor.to_s if required?
|
53
|
-
value = default
|
54
|
-
end
|
55
|
-
apply_blocks(value)
|
49
|
+
freeze(apply_blocks(value))
|
56
50
|
end
|
57
51
|
|
58
52
|
private
|
59
53
|
attr_reader :opts
|
60
54
|
|
55
|
+
def conventionize(what)
|
56
|
+
if !what.blank? && @instance.try(:class).try(:roxml_naming_convention).respond_to?(:call)
|
57
|
+
@instance.class.roxml_naming_convention.call(what)
|
58
|
+
else
|
59
|
+
what
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def wrapper
|
64
|
+
conventionize(opts.wrapper)
|
65
|
+
end
|
66
|
+
|
61
67
|
def apply_blocks(val)
|
62
|
-
blocks.
|
68
|
+
blocks.apply_to(val)
|
69
|
+
end
|
70
|
+
|
71
|
+
def freeze(val)
|
72
|
+
if opts.freeze?
|
73
|
+
val.each(&:freeze) if val.is_a?(Enumerable)
|
74
|
+
val.freeze
|
75
|
+
else
|
76
|
+
val
|
77
|
+
end
|
63
78
|
end
|
64
79
|
|
65
80
|
def xpath
|
66
81
|
wrapper ? "#{wrapper}/#{xpath_name}" : xpath_name.to_s
|
67
82
|
end
|
68
83
|
|
84
|
+
def auto_xpath
|
85
|
+
"#{conventionize(opts.name.pluralize)}/#{xpath_name}" if array?
|
86
|
+
end
|
87
|
+
|
69
88
|
def wrap(xml)
|
70
|
-
|
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.child_add(XML::Node.new_element(wrapper))
|
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
|
71
110
|
end
|
72
111
|
end
|
73
112
|
|
@@ -86,8 +125,9 @@ module ROXML
|
|
86
125
|
end
|
87
126
|
|
88
127
|
def fetch_value(xml)
|
89
|
-
|
90
|
-
|
128
|
+
nodes_in(xml) do |nodes|
|
129
|
+
nodes.first.value
|
130
|
+
end
|
91
131
|
end
|
92
132
|
|
93
133
|
def xpath_name
|
@@ -102,11 +142,7 @@ module ROXML
|
|
102
142
|
# XMLTextRef
|
103
143
|
# </element>
|
104
144
|
class XMLTextRef < XMLRef # :nodoc:
|
105
|
-
delegate :cdata?, :content?, :to => :opts
|
106
|
-
|
107
|
-
def name?
|
108
|
-
name == '*'
|
109
|
-
end
|
145
|
+
delegate :cdata?, :content?, :name?, :to => :opts
|
110
146
|
|
111
147
|
private
|
112
148
|
# Updates the text in the given _xml_ block to
|
@@ -126,17 +162,30 @@ module ROXML
|
|
126
162
|
end
|
127
163
|
|
128
164
|
def fetch_value(xml)
|
129
|
-
if content?
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
136
178
|
end
|
137
179
|
else
|
138
|
-
|
139
|
-
|
180
|
+
nodes_in(xml) do |nodes|
|
181
|
+
if array?
|
182
|
+
nodes.collect do |e|
|
183
|
+
e.content.strip.to_latin
|
184
|
+
end
|
185
|
+
else
|
186
|
+
nodes.first.content
|
187
|
+
end
|
188
|
+
end
|
140
189
|
end
|
141
190
|
end
|
142
191
|
|
@@ -152,9 +201,10 @@ module ROXML
|
|
152
201
|
class XMLHashRef < XMLTextRef # :nodoc:
|
153
202
|
delegate :hash, :to => :opts
|
154
203
|
|
155
|
-
def
|
156
|
-
|
157
|
-
|
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)
|
158
208
|
end
|
159
209
|
|
160
210
|
private
|
@@ -163,14 +213,16 @@ module ROXML
|
|
163
213
|
def write_xml(xml, value)
|
164
214
|
value.each_pair do |k, v|
|
165
215
|
node = xml.child_add(XML::Node.new_element(hash.wrapper))
|
166
|
-
|
167
|
-
|
216
|
+
@key.update_xml(node, k)
|
217
|
+
@value.update_xml(node, v)
|
168
218
|
end
|
169
219
|
end
|
170
220
|
|
171
221
|
def fetch_value(xml)
|
172
|
-
xml
|
173
|
-
|
222
|
+
nodes_in(xml) do |nodes|
|
223
|
+
nodes.collect do |e|
|
224
|
+
[@key.value_in(e), @value.value_in(e)]
|
225
|
+
end
|
174
226
|
end
|
175
227
|
end
|
176
228
|
|
@@ -182,6 +234,15 @@ module ROXML
|
|
182
234
|
end
|
183
235
|
vals.to_hash if vals
|
184
236
|
end
|
237
|
+
|
238
|
+
def freeze(vals)
|
239
|
+
if opts.freeze?
|
240
|
+
vals.each_pair{|k, v| k.freeze; v.freeze }
|
241
|
+
vals.freeze
|
242
|
+
else
|
243
|
+
vals
|
244
|
+
end
|
245
|
+
end
|
185
246
|
end
|
186
247
|
|
187
248
|
class XMLObjectRef < XMLTextRef # :nodoc:
|
@@ -191,23 +252,27 @@ module ROXML
|
|
191
252
|
# Updates the composed XML object in the given XML block to
|
192
253
|
# the value provided.
|
193
254
|
def write_xml(xml, value)
|
194
|
-
|
195
|
-
xml.child_add(value.to_xml(name))
|
196
|
-
else
|
255
|
+
if array?
|
197
256
|
value.each do |v|
|
198
257
|
xml.child_add(v.to_xml(name))
|
199
258
|
end
|
259
|
+
elsif value.is_a?(ROXML)
|
260
|
+
xml.child_add(value.to_xml(name))
|
261
|
+
else
|
262
|
+
node = XML::Node.new_element(name)
|
263
|
+
node.content = value.to_xml
|
264
|
+
xml.child_add(node)
|
200
265
|
end
|
201
266
|
end
|
202
267
|
|
203
268
|
def fetch_value(xml)
|
204
|
-
|
205
|
-
|
206
|
-
instantiate(
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
269
|
+
nodes_in(xml) do |nodes|
|
270
|
+
unless array?
|
271
|
+
instantiate(nodes.first)
|
272
|
+
else
|
273
|
+
nodes.collect do |e|
|
274
|
+
instantiate(e)
|
275
|
+
end
|
211
276
|
end
|
212
277
|
end
|
213
278
|
end
|