Empact-roxml 2.3.1 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +113 -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 -55
- data/lib/roxml/xml/rexml.rb +2 -2
- data/roxml.gemspec +40 -101
- 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 +29 -0
- 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/to_xml_test.rb +0 -25
- data/test/unit/xml_bool_test.rb +51 -45
- 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
- metadata +69 -32
@@ -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,34 +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 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.
|
63
|
-
|
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
|
64
78
|
end
|
65
79
|
|
66
80
|
def xpath
|
67
81
|
wrapper ? "#{wrapper}/#{xpath_name}" : xpath_name.to_s
|
68
82
|
end
|
69
83
|
|
84
|
+
def auto_xpath
|
85
|
+
"#{conventionize(opts.name.pluralize)}/#{xpath_name}" if array?
|
86
|
+
end
|
87
|
+
|
70
88
|
def wrap(xml)
|
71
|
-
|
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
|
72
110
|
end
|
73
111
|
end
|
74
112
|
|
@@ -87,8 +125,9 @@ module ROXML
|
|
87
125
|
end
|
88
126
|
|
89
127
|
def fetch_value(xml)
|
90
|
-
|
91
|
-
|
128
|
+
nodes_in(xml) do |nodes|
|
129
|
+
nodes.first.value
|
130
|
+
end
|
92
131
|
end
|
93
132
|
|
94
133
|
def xpath_name
|
@@ -103,11 +142,7 @@ module ROXML
|
|
103
142
|
# XMLTextRef
|
104
143
|
# </element>
|
105
144
|
class XMLTextRef < XMLRef # :nodoc:
|
106
|
-
delegate :cdata?, :content?, :to => :opts
|
107
|
-
|
108
|
-
def name?
|
109
|
-
name == '*'
|
110
|
-
end
|
145
|
+
delegate :cdata?, :content?, :name?, :to => :opts
|
111
146
|
|
112
147
|
private
|
113
148
|
# Updates the text in the given _xml_ block to
|
@@ -127,17 +162,30 @@ module ROXML
|
|
127
162
|
end
|
128
163
|
|
129
164
|
def fetch_value(xml)
|
130
|
-
if content?
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
137
178
|
end
|
138
179
|
else
|
139
|
-
|
140
|
-
|
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
|
141
189
|
end
|
142
190
|
end
|
143
191
|
|
@@ -153,9 +201,10 @@ module ROXML
|
|
153
201
|
class XMLHashRef < XMLTextRef # :nodoc:
|
154
202
|
delegate :hash, :to => :opts
|
155
203
|
|
156
|
-
def
|
157
|
-
|
158
|
-
|
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)
|
159
208
|
end
|
160
209
|
|
161
210
|
private
|
@@ -164,14 +213,16 @@ module ROXML
|
|
164
213
|
def write_xml(xml, value)
|
165
214
|
value.each_pair do |k, v|
|
166
215
|
node = xml.child_add(XML::Node.new_element(hash.wrapper))
|
167
|
-
|
168
|
-
|
216
|
+
@key.update_xml(node, k)
|
217
|
+
@value.update_xml(node, v)
|
169
218
|
end
|
170
219
|
end
|
171
220
|
|
172
221
|
def fetch_value(xml)
|
173
|
-
xml
|
174
|
-
|
222
|
+
nodes_in(xml) do |nodes|
|
223
|
+
nodes.collect do |e|
|
224
|
+
[@key.value_in(e), @value.value_in(e)]
|
225
|
+
end
|
175
226
|
end
|
176
227
|
end
|
177
228
|
|
@@ -183,6 +234,15 @@ module ROXML
|
|
183
234
|
end
|
184
235
|
vals.to_hash if vals
|
185
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
|
186
246
|
end
|
187
247
|
|
188
248
|
class XMLObjectRef < XMLTextRef # :nodoc:
|
@@ -192,23 +252,27 @@ module ROXML
|
|
192
252
|
# Updates the composed XML object in the given XML block to
|
193
253
|
# the value provided.
|
194
254
|
def write_xml(xml, value)
|
195
|
-
|
196
|
-
xml.child_add(value.to_xml(name))
|
197
|
-
else
|
255
|
+
if array?
|
198
256
|
value.each do |v|
|
199
257
|
xml.child_add(v.to_xml(name))
|
200
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)
|
201
265
|
end
|
202
266
|
end
|
203
267
|
|
204
268
|
def fetch_value(xml)
|
205
|
-
|
206
|
-
|
207
|
-
instantiate(
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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
|
212
276
|
end
|
213
277
|
end
|
214
278
|
end
|