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.
@@ -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
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 = 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
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 :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,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 value(xml)
47
+ def value_in(xml)
50
48
  value = fetch_value(xml)
51
- if value.blank?
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.each {|block| val = block[val] }
63
- 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
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
- (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
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
- attr = xml.search(xpath).first
91
- attr && attr.value
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
- xml.content.strip
132
- elsif name?
133
- xml.name
134
- elsif array?
135
- xml.search(xpath).collect do |e|
136
- 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
137
178
  end
138
179
  else
139
- child = xml.search(xpath).first
140
- 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
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 default
157
- result = super
158
- 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)
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
- hash.key.update_xml(node, k)
168
- hash.value.update_xml(node, v)
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.search(xpath).collect do |e|
174
- [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
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
- unless array?
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
- unless array?
206
- if child = xml.search(xpath).first
207
- instantiate(child)
208
- end
209
- else
210
- xml.search(xpath).collect do |e|
211
- 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
212
276
  end
213
277
  end
214
278
  end