Empact-roxml 2.3.1 → 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 +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
|