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.
@@ -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/core_ext/string/inflections'
12
- require 'active_support/core_ext/string/starts_ends_with'
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] = v
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
@@ -12,27 +12,19 @@ module ROXML
12
12
 
13
13
  @wrapper = wrapper
14
14
  if opts.has_key? :attrs
15
- @key = to_ref(opts, :attr, opts[:attrs][0])
16
- @value = to_ref(opts, :attr, opts[:attrs][1])
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 = to_ref opts, *fetch_element(opts, :key)
19
- @value = to_ref opts, *fetch_element(opts, :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].pp_s}" unless opts[what].keys.one?
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.to_s
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, :default, :accessor
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.respond_to?(:xml_name?) && @type.xml_name?
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
@@ -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 :name, :required?, :array?, :wrapper, :blocks, :accessor, :variable_name, :to => :opts
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
- # Reads data from the XML element and populates the instance
27
- # accordingly.
28
- def populate(xml, instance)
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 value(xml)
47
+ def value_in(xml)
50
48
  value = fetch_value(xml)
51
- if value.blank?
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.inject(val) {|val, block| block[val] }
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
- (wrapper && xml.name != wrapper) ? xml.child_add(XML::Node.new_element(wrapper)) : xml
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
- attr = xml.search(xpath).first
90
- attr && attr.value
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
- xml.content.strip
131
- elsif name?
132
- xml.name
133
- elsif array?
134
- xml.search(xpath).collect do |e|
135
- e.content.strip.to_latin if e.content
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
- child = xml.search(xpath).first
139
- child.content if child
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 default
156
- result = super
157
- result.nil? ? {} : result
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
- hash.key.update_xml(node, k)
167
- hash.value.update_xml(node, v)
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.search(xpath).collect do |e|
173
- [hash.key.value(e), hash.value.value(e)]
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
- unless array?
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
- unless array?
205
- if child = xml.search(xpath).first
206
- instantiate(child)
207
- end
208
- else
209
- xml.search(xpath).collect do |e|
210
- instantiate(e)
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