roxml 1.2 → 2.3.2

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.
Files changed (97) hide show
  1. data/MIT-LICENSE +18 -0
  2. data/README.rdoc +126 -0
  3. data/Rakefile +100 -0
  4. data/lib/roxml.rb +479 -485
  5. data/lib/roxml/extensions/active_support.rb +32 -0
  6. data/lib/roxml/extensions/array.rb +5 -0
  7. data/lib/roxml/extensions/array/conversions.rb +25 -0
  8. data/lib/roxml/extensions/deprecation.rb +28 -0
  9. data/lib/roxml/extensions/string.rb +21 -0
  10. data/lib/roxml/extensions/string/conversions.rb +44 -0
  11. data/lib/roxml/extensions/string/iterators.rb +12 -0
  12. data/lib/roxml/options.rb +266 -0
  13. data/lib/roxml/xml.rb +223 -0
  14. data/lib/roxml/xml/libxml.rb +63 -0
  15. data/lib/roxml/xml/rexml.rb +64 -0
  16. data/roxml.gemspec +105 -0
  17. data/test/fixtures/book_text_with_attribute.xml +1 -1
  18. data/test/fixtures/book_valid.xml +2 -2
  19. data/test/fixtures/book_with_authors.xml +7 -0
  20. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  21. data/test/fixtures/book_with_default_namespace.xml +9 -0
  22. data/test/fixtures/book_with_depth.xml +6 -0
  23. data/test/fixtures/book_with_wrapped_attr.xml +3 -0
  24. data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
  25. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  26. data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
  27. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  28. data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
  29. data/test/fixtures/dictionary_of_names.xml +4 -0
  30. data/test/fixtures/dictionary_of_texts.xml +10 -0
  31. data/test/fixtures/library.xml +1 -1
  32. data/test/fixtures/library_uppercase.xml +30 -0
  33. data/test/fixtures/muffins.xml +3 -0
  34. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  35. data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
  36. data/test/fixtures/node_with_name_conflicts.xml +4 -0
  37. data/test/fixtures/numerology.xml +4 -0
  38. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  39. data/test/fixtures/person_with_mothers.xml +10 -0
  40. data/test/mocks/dictionaries.rb +56 -0
  41. data/test/mocks/mocks.rb +244 -48
  42. data/test/release/dependencies_test.rb +32 -0
  43. data/test/test_helper.rb +44 -0
  44. data/test/unit/inheritance_test.rb +19 -0
  45. data/test/unit/options_test.rb +102 -0
  46. data/test/unit/roxml_test.rb +24 -0
  47. data/test/unit/string_test.rb +11 -0
  48. data/test/unit/to_xml_test.rb +52 -0
  49. data/test/unit/xml_attribute_test.rb +39 -0
  50. data/test/unit/xml_block_test.rb +81 -0
  51. data/test/unit/xml_bool_test.rb +105 -0
  52. data/test/unit/xml_construct_test.rb +77 -0
  53. data/test/unit/xml_hash_test.rb +65 -0
  54. data/test/unit/xml_initialize_test.rb +50 -0
  55. data/test/unit/xml_name_test.rb +108 -0
  56. data/test/unit/xml_namespace_test.rb +38 -0
  57. data/test/unit/xml_object_test.rb +116 -0
  58. data/test/unit/xml_required_test.rb +93 -0
  59. data/test/unit/xml_text_test.rb +68 -0
  60. metadata +138 -87
  61. data/MIT-LICENSE.txt +0 -9
  62. data/README +0 -2
  63. data/doc/classes/ROXML.html +0 -374
  64. data/doc/classes/ROXML.src/M000003.html +0 -19
  65. data/doc/classes/ROXML.src/M000004.html +0 -25
  66. data/doc/classes/ROXML.src/M000005.html +0 -22
  67. data/doc/classes/ROXML/ROXML_Class.html +0 -429
  68. data/doc/classes/ROXML/ROXML_Class.src/M000006.html +0 -27
  69. data/doc/classes/ROXML/ROXML_Class.src/M000007.html +0 -18
  70. data/doc/classes/ROXML/ROXML_Class.src/M000008.html +0 -19
  71. data/doc/classes/ROXML/ROXML_Class.src/M000009.html +0 -25
  72. data/doc/classes/ROXML/ROXML_Class.src/M000010.html +0 -24
  73. data/doc/classes/ROXML/ROXML_Class.src/M000011.html +0 -18
  74. data/doc/classes/ROXML/ROXML_Class.src/M000012.html +0 -18
  75. data/doc/classes/ROXML/XMLAttributeRef.html +0 -175
  76. data/doc/classes/ROXML/XMLAttributeRef.src/M000015.html +0 -19
  77. data/doc/classes/ROXML/XMLAttributeRef.src/M000016.html +0 -19
  78. data/doc/classes/ROXML/XMLObjectRef.html +0 -175
  79. data/doc/classes/ROXML/XMLObjectRef.src/M000013.html +0 -26
  80. data/doc/classes/ROXML/XMLObjectRef.src/M000014.html +0 -32
  81. data/doc/classes/ROXML/XMLRef.html +0 -166
  82. data/doc/classes/ROXML/XMLRef.src/M000017.html +0 -21
  83. data/doc/classes/ROXML/XMLTextRef.html +0 -198
  84. data/doc/classes/ROXML/XMLTextRef.src/M000018.html +0 -28
  85. data/doc/classes/ROXML/XMLTextRef.src/M000019.html +0 -34
  86. data/doc/classes/String.html +0 -165
  87. data/doc/classes/String.src/M000001.html +0 -23
  88. data/doc/classes/String.src/M000002.html +0 -23
  89. data/doc/created.rid +0 -1
  90. data/doc/files/lib/roxml_rb.html +0 -234
  91. data/doc/fr_class_index.html +0 -33
  92. data/doc/fr_file_index.html +0 -27
  93. data/doc/fr_method_index.html +0 -45
  94. data/doc/index.html +0 -24
  95. data/doc/rdoc-style.css +0 -208
  96. data/test/fixture_helper.rb +0 -5
  97. data/test/test_roxml.rb +0 -105
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+ require 'active_support/core_ext/symbol'
3
+ require 'active_support/core_ext/blank'
4
+ require 'active_support/core_ext/duplicable'
5
+ require 'active_support/core_ext/array/extract_options'
6
+ require 'active_support/core_ext/hash/reverse_merge'
7
+ require 'active_support/core_ext/module/delegation'
8
+ require 'active_support/core_ext/module/aliasing'
9
+ require 'active_support/core_ext/object/misc' # returning
10
+ require 'active_support/inflector'
11
+ require 'active_support/core_ext/string/inflections'
12
+ require 'active_support/core_ext/string/starts_ends_with'
13
+
14
+ require 'extensions/enumerable'
15
+ require 'extensions/array'
16
+
17
+ class Module
18
+ include ActiveSupport::CoreExtensions::Module if ActiveSupport::CoreExtensions.const_defined? :Module
19
+ end
20
+
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
+ class Hash #:nodoc:
31
+ include ActiveSupport::CoreExtensions::Hash::ReverseMerge
32
+ end
@@ -0,0 +1,5 @@
1
+ require File.join(File.dirname(__FILE__), 'array/conversions')
2
+
3
+ class Array #:nodoc:
4
+ include ROXML::CoreExtensions::Array::Conversions
5
+ end
@@ -0,0 +1,25 @@
1
+ module ROXML
2
+ module CoreExtensions
3
+ module Array #:nodoc:
4
+ module Conversions
5
+ # Translates an array into a hash, where each element of the array is
6
+ # an array with 2 elements:
7
+ #
8
+ # >> [[:key, :value], [1, 2], ['key', 'value']].to_h
9
+ # => {:key => :value, 1 => 2, 'key' => 'value'}
10
+ #
11
+ def to_hash
12
+ inject({}) do |result, (k, v)|
13
+ result[k] = v
14
+ result
15
+ end
16
+ end
17
+
18
+ def to_h #:nodoc:
19
+ to_hash
20
+ end
21
+ deprecate :to_h => :to_hash
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require 'active_support/core_ext/kernel/reporting'
2
+ require 'active_support/core_ext/module/attribute_accessors'
3
+ require 'active_support/deprecation'
4
+ require 'active_support/version'
5
+
6
+ module ActiveSupport # :nodoc:all
7
+ module Deprecation
8
+ class << self
9
+ if VERSION::MAJOR <= 2 && VERSION::MINOR <= 1
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."
12
+ "DEPRECATION WARNING: #{message}. #{deprecation_caller_message(callstack)}"
13
+ end
14
+ end
15
+ end
16
+
17
+ module ClassMethods
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."
20
+ case message
21
+ when Symbol then "#{warning} (use #{message} instead)"
22
+ when String then "#{warning} (#{message})"
23
+ else warning
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), 'string/conversions')
2
+ require File.join(File.dirname(__FILE__), 'string/iterators')
3
+
4
+ class String #:nodoc:
5
+ include ROXML::CoreExtensions::String::Conversions
6
+ include ROXML::CoreExtensions::String::Iterators
7
+ end
8
+
9
+ class Object #:nodoc:
10
+ # Deprecated in favor of explicit #to_s.to_utf
11
+ def to_utf
12
+ ActiveSupport::Deprecation.warn "This method will be removed from Object please use String#to_utf instead via explicit #to_s"
13
+ to_s.to_utf
14
+ end
15
+
16
+ # Deprecated in favor of explicit #to_s.to_latin
17
+ def to_latin
18
+ ActiveSupport::Deprecation.warn "This method will be removed from Object please use String#to_latin instead via explicit #to_s"
19
+ to_s.to_latin
20
+ end
21
+ end
@@ -0,0 +1,44 @@
1
+ module ROXML
2
+ module CoreExtensions #:nodoc:
3
+ module String
4
+ # Extension of String class to handle conversion from/to
5
+ # UTF-8/ISO-8869-1
6
+ module Conversions
7
+ require 'iconv'
8
+
9
+ #
10
+ # Return an utf-8 representation of this string.
11
+ #
12
+ def to_utf
13
+ begin
14
+ Iconv.new("utf-8", "iso-8859-1").iconv(to_s)
15
+ rescue Iconv::IllegalSequence
16
+ STDERR << "!! Failed converting from UTF-8 -> ISO-8859-1 (#{self}). Already the right charset?"
17
+ self
18
+ end
19
+ end
20
+
21
+ #
22
+ # Convert this string to iso-8850-1
23
+ #
24
+ def to_latin
25
+ begin
26
+ Iconv.new("iso-8859-1", "utf-8").iconv(to_s)
27
+ rescue Iconv::IllegalSequence
28
+ STDERR << "!! Failed converting from ISO-8859-1 -> UTF-8 (#{self}). Already the right charset?"
29
+ self
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ class Object
38
+ end
39
+
40
+ class String
41
+ def between(separator, &block)
42
+ split(separator).collect(&block).join(separator)
43
+ end
44
+ end
@@ -0,0 +1,12 @@
1
+ module ROXML
2
+ module CoreExtensions
3
+ module String #:nodoc:
4
+ module Iterators
5
+ # Allows you to iterate over and modify the sub-strings between _separator_. Returns the joined result of the modification.
6
+ def between(separator, &block)
7
+ split(separator).collect(&block).join(separator)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,266 @@
1
+ module ROXML
2
+ HASH_KEYS = [:attrs, :key, :value].freeze
3
+ TYPE_KEYS = [:attr, :text, :hash, :content].freeze
4
+
5
+ class HashDesc # :nodoc:
6
+ attr_reader :key, :value, :wrapper
7
+
8
+ def initialize(opts, wrapper)
9
+ unless (invalid_keys = opts.keys - HASH_KEYS).empty?
10
+ raise ArgumentError, "Invalid Hash description keys: #{invalid_keys.join(', ')}"
11
+ end
12
+
13
+ @wrapper = wrapper
14
+ if opts.has_key? :attrs
15
+ @key = to_ref(opts, :attr, opts[:attrs][0])
16
+ @value = to_ref(opts, :attr, opts[:attrs][1])
17
+ else
18
+ @key = to_ref opts, *fetch_element(opts, :key)
19
+ @value = to_ref opts, *fetch_element(opts, :value)
20
+ end
21
+ end
22
+
23
+ def types
24
+ [@key.class, @value.class]
25
+ end
26
+
27
+ def names
28
+ [@key.name, @value.name]
29
+ end
30
+
31
+ private
32
+ def fetch_element(opts, what)
33
+ case opts[what]
34
+ when Hash
35
+ raise ArgumentError, "Hash #{what} is over-specified: #{opts[what].pp_s}" unless opts[what].keys.one?
36
+ type = opts[what].keys.first
37
+ [type, opts[what][type]]
38
+ when :content
39
+ [:content, opts[:name]]
40
+ when :name
41
+ [:name, '*']
42
+ when String
43
+ [:text, opts[what]]
44
+ when Symbol
45
+ [:text, opts[what]]
46
+ else
47
+ raise ArgumentError, "unrecognized hash parameter: #{what} => #{opts[what]}"
48
+ end
49
+ end
50
+
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
+ def to_hash_args(args, type, name)
65
+ args = [args] unless args.is_a? Array
66
+
67
+ if args.one? && !(args.only.keys & HASH_KEYS).empty?
68
+ opts = {type => name}
69
+ if type == :content
70
+ opts[:type] = :text
71
+ (opts[:as] ||= []) << :content
72
+ end
73
+ Opts.new(name, opts)
74
+ else
75
+ opts = args.extract_options!
76
+ raise opts.to_s
77
+ end
78
+ end
79
+ end
80
+
81
+ class Opts # :nodoc:
82
+ attr_reader :name, :type, :hash, :blocks, :default, :accessor
83
+
84
+ class << self
85
+ def silence_xml_name_warning?
86
+ @silence_xml_name_warning || (ROXML.const_defined?('SILENCE_XML_NAME_WARNING') && ROXML::SILENCE_XML_NAME_WARNING)
87
+ end
88
+
89
+ def silence_xml_name_warning!
90
+ @silence_xml_name_warning = true
91
+ end
92
+ end
93
+
94
+ def initialize(sym, *args, &block)
95
+ @accessor = sym
96
+ @opts = extract_options!(args)
97
+ @default = @opts.delete(:else)
98
+
99
+ @opts.reverse_merge!(:as => [], :in => nil)
100
+ @opts[:as] = [*@opts[:as]]
101
+
102
+ @type = extract_type(args)
103
+ @opts[:as] << :bool if @accessor.to_s.ends_with?('?')
104
+
105
+ if @type.respond_to?(:xml_name?) && @type.xml_name?
106
+ unless self.class.silence_xml_name_warning?
107
+ warn "WARNING: As of 2.3, a breaking change has been in the naming of sub-objects. " +
108
+ "ROXML now considers the xml_name of the sub-object before falling back to the accessor name of the parent. " +
109
+ "Use :from on the parent declaration to override this behavior. Set ROXML::SILENCE_XML_NAME_WARNING to avoid this message."
110
+ self.class.silence_xml_name_warning!
111
+ end
112
+ @opts[:from] ||= @type.tag_name
113
+ else
114
+ @opts[:from] ||= variable_name
115
+ end
116
+
117
+ @blocks = collect_blocks(block, @opts[:as])
118
+
119
+ @name = @opts[:from].to_s
120
+ @name = @name.singularize if hash? || array?
121
+ if hash? && (hash.key.name? || hash.value.name?)
122
+ @name = '*'
123
+ end
124
+
125
+ raise ArgumentError, "Can't specify both :else default and :required" if required? && default
126
+ end
127
+
128
+ def variable_name
129
+ accessor.to_s.ends_with?('?') ? accessor.to_s.chomp('?') : accessor.to_s
130
+ end
131
+
132
+ def hash
133
+ @hash ||= HashDesc.new(@opts.delete(:hash), name) if hash?
134
+ end
135
+
136
+ def hash?
137
+ @type == :hash
138
+ end
139
+
140
+ def content?
141
+ @type == :content
142
+ end
143
+
144
+ def array?
145
+ @opts[:as].include? :array
146
+ end
147
+
148
+ def cdata?
149
+ @opts[:as].include? :cdata
150
+ end
151
+
152
+ def wrapper
153
+ @opts[:in]
154
+ end
155
+
156
+ def required?
157
+ @opts[:required]
158
+ end
159
+
160
+ private
161
+ BLOCK_TO_FLOAT = lambda do |val|
162
+ if val.is_a? Array
163
+ val.collect do |v|
164
+ Float(v)
165
+ end
166
+ else
167
+ Float(val)
168
+ end
169
+ end
170
+
171
+ BLOCK_TO_INT = lambda do |val|
172
+ if val.is_a? Array
173
+ val.collect do |v|
174
+ Integer(v)
175
+ end
176
+ else
177
+ Integer(val)
178
+ end
179
+ end
180
+
181
+ TRUE_VALS = %w{TRUE True true 1}
182
+ FALSE_VALS = %w{FALSE False false 0}
183
+
184
+ BLOCK_SHORTHANDS = {
185
+ :integer => BLOCK_TO_INT,
186
+ Integer => BLOCK_TO_INT,
187
+ :float => BLOCK_TO_FLOAT,
188
+ Float => BLOCK_TO_FLOAT,
189
+ :bool => nil,
190
+ :bool_standalone => lambda do |val|
191
+ if TRUE_VALS.include? val
192
+ true
193
+ elsif FALSE_VALS.include? val
194
+ false
195
+ else
196
+ nil
197
+ end
198
+ end,
199
+
200
+ :bool_combined => lambda do |val|
201
+ if TRUE_VALS.include? val
202
+ true
203
+ elsif FALSE_VALS.include? val
204
+ false
205
+ else
206
+ val
207
+ end
208
+ end
209
+ }
210
+
211
+ def collect_blocks(block, as)
212
+ shorthands = as & BLOCK_SHORTHANDS.keys
213
+ if shorthands.size > 1
214
+ raise ArgumentError, "multiple block shorthands supplied #{shorthands.map(&:to_s).join(', ')}"
215
+ end
216
+
217
+ shorthand = shorthands.first
218
+ if shorthand == :bool
219
+ # if a second block is present, and we can't coerce the xml value
220
+ # to bool, we need to be able to pass it to the user-provided block
221
+ shorthand = block ? :bool_combined : :bool_standalone
222
+ end
223
+ [BLOCK_SHORTHANDS[shorthand], block].compact
224
+ end
225
+
226
+ def extract_options!(args)
227
+ opts = args.extract_options!
228
+ unless (opts.keys & HASH_KEYS).empty?
229
+ args.push(opts)
230
+ opts = {}
231
+ end
232
+ opts
233
+ end
234
+
235
+ def extract_type(args)
236
+ types = (@opts.keys & TYPE_KEYS)
237
+ # type arg
238
+ if args.one? && types.empty?
239
+ if args.only.is_a? Array
240
+ @opts[:as] << :array
241
+ return args.only.only
242
+ elsif args.only.is_a? Hash
243
+ @opts[:hash] = args.only
244
+ return :hash
245
+ else
246
+ return args.only
247
+ end
248
+ end
249
+
250
+ unless args.empty?
251
+ raise ArgumentError, "too many arguments (#{(args + types).join(', ')}). Should be name, type, and " +
252
+ "an options hash, with the type and options optional"
253
+ end
254
+
255
+ # type options
256
+ if types.one?
257
+ @opts[:from] = @opts.delete(types.only)
258
+ types.only
259
+ elsif types.empty?
260
+ :text
261
+ else
262
+ raise ArgumentError, "more than one type option specified: #{types.join(', ')}"
263
+ end
264
+ end
265
+ end
266
+ end
data/lib/roxml/xml.rb ADDED
@@ -0,0 +1,223 @@
1
+ module ROXML
2
+ unless const_defined? 'XML_PARSER'
3
+ begin
4
+ require 'libxml'
5
+ XML_PARSER = 'libxml' # :nodoc:
6
+ rescue LoadError
7
+ XML_PARSER = 'rexml' # :nodoc:
8
+ end
9
+ end
10
+ require File.join(File.dirname(__FILE__), 'xml', XML_PARSER)
11
+
12
+ class RequiredElementMissing < Exception # :nodoc:
13
+ end
14
+
15
+ #
16
+ # Internal base class that represents an XML - Class binding.
17
+ #
18
+ class XMLRef # :nodoc:
19
+ delegate :name, :required?, :array?, :wrapper, :blocks, :accessor, :variable_name, :to => :opts
20
+ alias_method :xpath_name, :name
21
+
22
+ def initialize(opts)
23
+ @opts = opts
24
+ end
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
36
+ end
37
+
38
+ def update_xml(xml, value)
39
+ returning wrap(xml) do |xml|
40
+ write_xml(xml, value)
41
+ end
42
+ end
43
+
44
+ def default
45
+ @default ||= @opts.default || (@opts.array? ? Array.new : nil)
46
+ @default.duplicable? ? @default.dup : @default
47
+ end
48
+
49
+ def value(xml)
50
+ 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)
56
+ end
57
+
58
+ private
59
+ attr_reader :opts
60
+
61
+ def apply_blocks(val)
62
+ blocks.inject(val) {|val, block| block[val] }
63
+ end
64
+
65
+ def xpath
66
+ wrapper ? "#{wrapper}/#{xpath_name}" : xpath_name.to_s
67
+ end
68
+
69
+ def wrap(xml)
70
+ (wrapper && xml.name != wrapper) ? xml.child_add(XML::Node.new_element(wrapper)) : xml
71
+ end
72
+ end
73
+
74
+ # Interal class representing an XML attribute binding
75
+ #
76
+ # In context:
77
+ # <element attribute="XMLAttributeRef">
78
+ # XMLTextRef
79
+ # </element>
80
+ class XMLAttributeRef < XMLRef # :nodoc:
81
+ private
82
+ # Updates the attribute in the given XML block to
83
+ # the value provided.
84
+ def write_xml(xml, value)
85
+ xml.attributes[name] = value.to_s.to_utf
86
+ end
87
+
88
+ def fetch_value(xml)
89
+ attr = xml.search(xpath).first
90
+ attr && attr.value
91
+ end
92
+
93
+ def xpath_name
94
+ "@#{name}"
95
+ end
96
+ end
97
+
98
+ # Interal class representing XML content text binding
99
+ #
100
+ # In context:
101
+ # <element attribute="XMLAttributeRef">
102
+ # XMLTextRef
103
+ # </element>
104
+ class XMLTextRef < XMLRef # :nodoc:
105
+ delegate :cdata?, :content?, :to => :opts
106
+
107
+ def name?
108
+ name == '*'
109
+ end
110
+
111
+ private
112
+ # Updates the text in the given _xml_ block to
113
+ # the _value_ provided.
114
+ def write_xml(xml, value)
115
+ if content?
116
+ add(xml, value)
117
+ elsif name?
118
+ xml.name = value
119
+ elsif array?
120
+ value.each do |v|
121
+ add(xml.child_add(XML::Node.new_element(name)), v)
122
+ end
123
+ else
124
+ add(xml.child_add(XML::Node.new_element(name)), value)
125
+ end
126
+ end
127
+
128
+ 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
136
+ end
137
+ else
138
+ child = xml.search(xpath).first
139
+ child.content if child
140
+ end
141
+ end
142
+
143
+ def add(dest, value)
144
+ if cdata?
145
+ dest.child_add(XML::Node.new_cdata(value.to_s.to_utf))
146
+ else
147
+ dest.content = value.to_s.to_utf
148
+ end
149
+ end
150
+ end
151
+
152
+ class XMLHashRef < XMLTextRef # :nodoc:
153
+ delegate :hash, :to => :opts
154
+
155
+ def default
156
+ result = super
157
+ result.nil? ? {} : result
158
+ end
159
+
160
+ private
161
+ # Updates the composed XML object in the given XML block to
162
+ # the value provided.
163
+ def write_xml(xml, value)
164
+ value.each_pair do |k, v|
165
+ node = xml.child_add(XML::Node.new_element(hash.wrapper))
166
+ hash.key.update_xml(node, k)
167
+ hash.value.update_xml(node, v)
168
+ end
169
+ end
170
+
171
+ def fetch_value(xml)
172
+ xml.search(xpath).collect do |e|
173
+ [hash.key.value(e), hash.value.value(e)]
174
+ end
175
+ end
176
+
177
+ def apply_blocks(vals)
178
+ unless blocks.empty?
179
+ vals.collect! do |kvp|
180
+ super(kvp)
181
+ end
182
+ end
183
+ vals.to_hash if vals
184
+ end
185
+ end
186
+
187
+ class XMLObjectRef < XMLTextRef # :nodoc:
188
+ delegate :type, :to => :opts
189
+
190
+ private
191
+ # Updates the composed XML object in the given XML block to
192
+ # the value provided.
193
+ def write_xml(xml, value)
194
+ unless array?
195
+ xml.child_add(value.to_xml(name))
196
+ else
197
+ value.each do |v|
198
+ xml.child_add(v.to_xml(name))
199
+ end
200
+ end
201
+ end
202
+
203
+ 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)
211
+ end
212
+ end
213
+ end
214
+
215
+ def instantiate(elem)
216
+ if type.respond_to? :from_xml
217
+ type.from_xml(elem)
218
+ else
219
+ type.new(elem)
220
+ end
221
+ end
222
+ end
223
+ end