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.
@@ -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