representable 0.0.1.alpha1
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/.gitignore +7 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/History.txt +354 -0
- data/LICENSE +20 -0
- data/README.rdoc +186 -0
- data/Rakefile +10 -0
- data/TODO +37 -0
- data/VERSION +1 -0
- data/examples/amazon.rb +35 -0
- data/examples/current_weather.rb +27 -0
- data/examples/dashed_elements.rb +20 -0
- data/examples/library.rb +40 -0
- data/examples/posts.rb +27 -0
- data/examples/rails.rb +70 -0
- data/examples/twitter.rb +37 -0
- data/examples/xml/active_record.xml +70 -0
- data/examples/xml/amazon.xml +133 -0
- data/examples/xml/current_weather.xml +89 -0
- data/examples/xml/dashed_elements.xml +52 -0
- data/examples/xml/posts.xml +23 -0
- data/examples/xml/twitter.xml +422 -0
- data/lib/representable.rb +257 -0
- data/lib/representable/definition.rb +109 -0
- data/lib/representable/nokogiri_extensions.rb +19 -0
- data/lib/representable/references.rb +153 -0
- data/lib/representable/version.rb +3 -0
- data/lib/representable/xml.rb +79 -0
- data/representable.gemspec +29 -0
- data/spec/definition_spec.rb +495 -0
- data/spec/examples/active_record_spec.rb +41 -0
- data/spec/examples/amazon_spec.rb +54 -0
- data/spec/examples/current_weather_spec.rb +37 -0
- data/spec/examples/dashed_elements_spec.rb +20 -0
- data/spec/examples/library_spec.rb +46 -0
- data/spec/examples/post_spec.rb +24 -0
- data/spec/examples/twitter_spec.rb +32 -0
- data/spec/roxml_integration_test.rb +289 -0
- data/spec/roxml_spec.rb +372 -0
- data/spec/shared_specs.rb +15 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support/libxml.rb +3 -0
- data/spec/support/nokogiri.rb +3 -0
- data/spec/xml/array_spec.rb +36 -0
- data/spec/xml/attributes_spec.rb +71 -0
- data/spec/xml/encoding_spec.rb +53 -0
- data/spec/xml/namespace_spec.rb +270 -0
- data/spec/xml/namespaces_spec.rb +67 -0
- data/spec/xml/object_spec.rb +82 -0
- data/spec/xml/parser_spec.rb +21 -0
- data/spec/xml/text_spec.rb +71 -0
- data/test/fixtures/book_malformed.xml +5 -0
- data/test/fixtures/book_pair.xml +8 -0
- data/test/fixtures/book_text_with_attribute.xml +5 -0
- data/test/fixtures/book_valid.xml +5 -0
- data/test/fixtures/book_with_authors.xml +7 -0
- data/test/fixtures/book_with_contributions.xml +9 -0
- data/test/fixtures/book_with_contributors.xml +7 -0
- data/test/fixtures/book_with_contributors_attrs.xml +7 -0
- data/test/fixtures/book_with_default_namespace.xml +9 -0
- data/test/fixtures/book_with_depth.xml +6 -0
- data/test/fixtures/book_with_octal_pages.xml +4 -0
- data/test/fixtures/book_with_publisher.xml +7 -0
- data/test/fixtures/book_with_wrapped_attr.xml +3 -0
- data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
- data/test/fixtures/dictionary_of_attrs.xml +6 -0
- data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
- data/test/fixtures/dictionary_of_mixeds.xml +4 -0
- data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
- data/test/fixtures/dictionary_of_names.xml +4 -0
- data/test/fixtures/dictionary_of_texts.xml +10 -0
- data/test/fixtures/library.xml +30 -0
- data/test/fixtures/library_uppercase.xml +30 -0
- data/test/fixtures/muffins.xml +3 -0
- data/test/fixtures/nameless_ageless_youth.xml +2 -0
- data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
- data/test/fixtures/node_with_name_conflicts.xml +4 -0
- data/test/fixtures/numerology.xml +4 -0
- data/test/fixtures/person.xml +1 -0
- data/test/fixtures/person_with_guarded_mothers.xml +13 -0
- data/test/fixtures/person_with_mothers.xml +10 -0
- data/test/mocks/dictionaries.rb +57 -0
- data/test/mocks/mocks.rb +279 -0
- data/test/roxml_test.rb +58 -0
- data/test/support/fixtures.rb +11 -0
- data/test/test_helper.rb +6 -0
- data/test/unit/definition_test.rb +235 -0
- data/test/unit/deprecations_test.rb +24 -0
- data/test/unit/to_xml_test.rb +81 -0
- data/test/unit/xml_attribute_test.rb +39 -0
- data/test/unit/xml_block_test.rb +81 -0
- data/test/unit/xml_bool_test.rb +122 -0
- data/test/unit/xml_convention_test.rb +150 -0
- data/test/unit/xml_hash_test.rb +115 -0
- data/test/unit/xml_initialize_test.rb +49 -0
- data/test/unit/xml_name_test.rb +141 -0
- data/test/unit/xml_namespace_test.rb +31 -0
- data/test/unit/xml_object_test.rb +206 -0
- data/test/unit/xml_required_test.rb +94 -0
- data/test/unit/xml_text_test.rb +71 -0
- data/website/index.html +98 -0
- metadata +248 -0
@@ -0,0 +1,257 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
require 'active_support/core_ext/array/extract_options'
|
4
|
+
require 'active_support/core_ext/string/starts_ends_with'
|
5
|
+
require 'active_support/core_ext/string/inflections.rb'
|
6
|
+
require 'active_support/core_ext/hash/reverse_merge.rb'
|
7
|
+
|
8
|
+
require 'hooks/inheritable_attribute'
|
9
|
+
|
10
|
+
|
11
|
+
require 'representable/definition'
|
12
|
+
require 'representable/nokogiri_extensions'
|
13
|
+
require 'representable/references'
|
14
|
+
|
15
|
+
require 'representable/xml' # TODO: do that dynamically.
|
16
|
+
|
17
|
+
module Representable
|
18
|
+
VERSION = '3.1.5'
|
19
|
+
|
20
|
+
|
21
|
+
def self.included(base)
|
22
|
+
base.class_eval do
|
23
|
+
extend ClassMethods::Accessors, ClassMethods::Declarations
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
attr_accessor :roxml_references
|
28
|
+
|
29
|
+
extend Hooks::InheritableAttribute
|
30
|
+
inheritable_attr :representable_attrs
|
31
|
+
self.representable_attrs = []
|
32
|
+
|
33
|
+
inheritable_attr :explicit_representation_name # FIXME: move to Accessors.
|
34
|
+
|
35
|
+
|
36
|
+
extend Xml::Declarations # DISCUSS: do that dynamically?
|
37
|
+
extend Xml::ClassMethods # DISCUSS: do that dynamically?
|
38
|
+
include Xml::InstanceMethods # DISCUSS: do that dynamically?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods # :nodoc:
|
43
|
+
module Declarations
|
44
|
+
def definition_class
|
45
|
+
Definition
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Declares a reference to a certain xml element, whether an attribute, a node,
|
50
|
+
# or a typed collection of nodes. This method does not add a corresponding accessor
|
51
|
+
# to the object. For that behavior see the similar methods: .xml_reader and .xml_accessor.
|
52
|
+
#
|
53
|
+
# == Sym Option
|
54
|
+
# [sym] Symbol representing the name of the accessor.
|
55
|
+
#
|
56
|
+
# === Default naming
|
57
|
+
# This name will be the default node or attribute name searched for,
|
58
|
+
# if no other is declared. For example,
|
59
|
+
#
|
60
|
+
# xml_reader :bob
|
61
|
+
# xml_accessor :pony, :from => :attr
|
62
|
+
#
|
63
|
+
# are equivalent to:
|
64
|
+
#
|
65
|
+
# xml_reader :bob, :from => 'bob'
|
66
|
+
# xml_accessor :pony, :from => '@pony'
|
67
|
+
#
|
68
|
+
# == Options
|
69
|
+
# === :as
|
70
|
+
# ==== Basic Types
|
71
|
+
# Allows you to specify one of several basic types to return the value as. For example
|
72
|
+
#
|
73
|
+
# xml_reader :count, :as => Integer
|
74
|
+
#
|
75
|
+
# is equivalent to:
|
76
|
+
#
|
77
|
+
# xml_reader(:count) {|val| Integer(val) unless val.empty? }
|
78
|
+
#
|
79
|
+
# Such block shorthands for Integer, Float, Fixnum, BigDecimal, Date, Time, and DateTime
|
80
|
+
# are currently available, but only for non-Hash declarations.
|
81
|
+
#
|
82
|
+
# To reference many elements, put the desired type in a literal array. e.g.:
|
83
|
+
#
|
84
|
+
# xml_reader :counts, :as => [Integer]
|
85
|
+
#
|
86
|
+
# Even an array of text nodes can be specified with :as => []
|
87
|
+
#
|
88
|
+
# xml_reader :quotes, :as => []
|
89
|
+
#
|
90
|
+
# === Other ROXML Class
|
91
|
+
# Declares an accessor that represents another ROXML class as child XML element
|
92
|
+
# (one-to-one or composition) or array of child elements (one-to-many or
|
93
|
+
# aggregation) of this type. Default is one-to-one. For one-to-many, simply pass the class
|
94
|
+
# as the only element in an array.
|
95
|
+
#
|
96
|
+
# Composition example:
|
97
|
+
# <book>
|
98
|
+
# <publisher>
|
99
|
+
# <name>Pragmatic Bookshelf</name>
|
100
|
+
# </publisher>
|
101
|
+
# </book>
|
102
|
+
#
|
103
|
+
# Can be mapped using the following code:
|
104
|
+
# class Book
|
105
|
+
# xml_reader :publisher, :as => Publisher
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# Aggregation example:
|
109
|
+
# <library>
|
110
|
+
# <books>
|
111
|
+
# <book/>
|
112
|
+
# <book/>
|
113
|
+
# </books>
|
114
|
+
# </library>
|
115
|
+
#
|
116
|
+
# Can be mapped using the following code:
|
117
|
+
# class Library
|
118
|
+
# xml_reader :books, :as => [Book], :in => "books"
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# If you don't have the <books> tag to wrap around the list of <book> tags:
|
122
|
+
# <library>
|
123
|
+
# <name>Ruby books</name>
|
124
|
+
# <book/>
|
125
|
+
# <book/>
|
126
|
+
# </library>
|
127
|
+
#
|
128
|
+
# You can skip the wrapper argument:
|
129
|
+
# xml_reader :books, :as => [Book]
|
130
|
+
#
|
131
|
+
# === :from
|
132
|
+
# The name by which the xml value will be found, either an attribute or tag name in XML.
|
133
|
+
# Default is sym, or the singular form of sym, in the case of arrays and hashes.
|
134
|
+
#
|
135
|
+
# This value may also include XPath notation.
|
136
|
+
#
|
137
|
+
# ==== :from => :content
|
138
|
+
# When :from is set to :content, this refers to the content of the current node,
|
139
|
+
# rather than a sub-node. It is equivalent to :from => '.'
|
140
|
+
#
|
141
|
+
# Example:
|
142
|
+
# class Contributor
|
143
|
+
# xml_reader :name, :from => :content
|
144
|
+
# xml_reader :role, :from => :attr
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# To map:
|
148
|
+
# <contributor role="editor">James Wick</contributor>
|
149
|
+
#
|
150
|
+
# ==== :from => :attr
|
151
|
+
# When :from is set to :attr, this refers to the content of an attribute,
|
152
|
+
# rather than a sub-node. It is equivalent to :from => '@attribute_name'
|
153
|
+
#
|
154
|
+
# Example:
|
155
|
+
# class Book
|
156
|
+
# xml_reader :isbn, :from => "@ISBN"
|
157
|
+
# xml_accessor :title, :from => :attr # :from defaults to '@title'
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# To map:
|
161
|
+
# <book ISBN="0974514055" title="Programming Ruby: the pragmatic programmers' guide" />
|
162
|
+
#
|
163
|
+
# ==== :from => :text
|
164
|
+
# The default source, if none is specified, this means the accessor
|
165
|
+
# represents a text node from XML. This is documented for completeness
|
166
|
+
# only. You should just leave this option off when you want the default behavior,
|
167
|
+
# as in the examples below.
|
168
|
+
#
|
169
|
+
# :text is equivalent to :from => accessor_name, and you should specify the
|
170
|
+
# actual node name (and, optionally, a namespace) if it differs, as in the case of :author below.
|
171
|
+
#
|
172
|
+
# Example:
|
173
|
+
# class Book
|
174
|
+
# xml_reader :author, :from => 'Author'
|
175
|
+
# xml_accessor :description, :cdata => true
|
176
|
+
# xml_reader :title
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# To map:
|
180
|
+
# <book>
|
181
|
+
# <title>Programming Ruby: the pragmatic programmers' guide</title>
|
182
|
+
# <description><![CDATA[Probably the best Ruby book out there]]></description>
|
183
|
+
# <Author>David Thomas</Author>
|
184
|
+
# </book>
|
185
|
+
#
|
186
|
+
# Likewise, a number of :text node values can be collected in an array like so:
|
187
|
+
#
|
188
|
+
# Example:
|
189
|
+
# class Library
|
190
|
+
# xml_reader :books, :as => []
|
191
|
+
# end
|
192
|
+
#
|
193
|
+
# To map:
|
194
|
+
# <library>
|
195
|
+
# <book>To kill a mockingbird</book>
|
196
|
+
# <book>House of Leaves</book>
|
197
|
+
# <book>Gödel, Escher, Bach</book>
|
198
|
+
# </library>
|
199
|
+
#
|
200
|
+
# === Other Options
|
201
|
+
# [:in] An optional name of a wrapping tag for this XML accessor.
|
202
|
+
# This can include other xpath values, which will be joined with :from with a '/'
|
203
|
+
# [:required] If true, throws RequiredElementMissing when the element isn't present
|
204
|
+
# [:cdata] true for values which should be input from or output as cdata elements
|
205
|
+
# [:to_xml] this proc is applied to the attributes value outputting the instance via #to_xml
|
206
|
+
#
|
207
|
+
def representable_attr(*syms, &block)
|
208
|
+
opts = syms.extract_options!
|
209
|
+
syms.map do |sym|
|
210
|
+
definition_class.new(sym, opts, &block).tap do |attr|
|
211
|
+
representable_attrs << attr
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Declares a read-only xml reference. See xml_attr for details.
|
217
|
+
#
|
218
|
+
# Note that while xml_reader does not create a setter for this attribute,
|
219
|
+
# its value can be modified indirectly via methods. For more complete
|
220
|
+
# protection, consider the :frozen option.
|
221
|
+
def representable_reader(*syms, &block)
|
222
|
+
representable_attr(*syms, &block).each do |attr|
|
223
|
+
add_reader(attr)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Declares a writable xml reference. See xml_attr for details.
|
228
|
+
#
|
229
|
+
# Note that while xml_accessor does create a setter for this attribute,
|
230
|
+
# you can use the :frozen option to prevent its value from being
|
231
|
+
# modified indirectly via methods.
|
232
|
+
def representable_accessor(*syms, &block)
|
233
|
+
representable_attr(*syms, &block).each do |attr|
|
234
|
+
add_reader(attr)
|
235
|
+
attr_writer(attr.accessor)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
def add_reader(attr)
|
241
|
+
define_method(attr.accessor) do
|
242
|
+
instance_variable_get(attr.instance_variable_name)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
module Accessors
|
248
|
+
def representation_name=(name)
|
249
|
+
self.explicit_representation_name = name
|
250
|
+
end
|
251
|
+
|
252
|
+
def representation_name
|
253
|
+
explicit_representation_name or name.split('::').last.underscore
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
class Module
|
2
|
+
def bool_attr_reader(*attrs)
|
3
|
+
attrs.each do |attr|
|
4
|
+
define_method :"#{attr}?" do
|
5
|
+
instance_variable_get(:"@#{attr}") || false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Representable
|
12
|
+
class ContradictoryNamespaces < StandardError
|
13
|
+
end
|
14
|
+
class Definition # :nodoc:
|
15
|
+
attr_reader :name, :sought_type, :wrapper, :accessor, :namespace
|
16
|
+
bool_attr_reader :name_explicit, :array, :cdata
|
17
|
+
|
18
|
+
def initialize(sym, opts={})
|
19
|
+
@accessor = sym.to_s
|
20
|
+
@namespace = opts.delete(:namespace)
|
21
|
+
|
22
|
+
|
23
|
+
if opts[:as].is_a?(Array) # DISCUSS: move to ArrayDefinition.
|
24
|
+
@array = true
|
25
|
+
@name = (opts[:tag] || @accessor).to_s
|
26
|
+
else
|
27
|
+
@name = accessor
|
28
|
+
@name = (opts[:from] || @name).to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
@sought_type = extract_type(opts[:as])
|
32
|
+
if @sought_type.respond_to?(:roxml_tag_name)
|
33
|
+
opts[:from] ||= @sought_type.roxml_tag_name
|
34
|
+
end
|
35
|
+
|
36
|
+
if opts[:from] == :content
|
37
|
+
opts[:from] = '.'
|
38
|
+
elsif opts[:from] == :name
|
39
|
+
opts[:from] = '*'
|
40
|
+
elsif opts[:from] == :attr
|
41
|
+
@sought_type = :attr
|
42
|
+
opts[:from] = nil
|
43
|
+
elsif opts[:from] == :namespace
|
44
|
+
opts[:from] = '*'
|
45
|
+
@sought_type = :namespace
|
46
|
+
elsif opts[:from].to_s.starts_with?('@')
|
47
|
+
@sought_type = :attr
|
48
|
+
opts[:from].sub!('@', '')
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
#raise ContradictoryNamespaces if @name.include?(':') && (@namespace.present? || @namespace == false)
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def instance_variable_name
|
57
|
+
:"@#{accessor}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def setter
|
61
|
+
:"#{accessor}="
|
62
|
+
end
|
63
|
+
|
64
|
+
def typed?
|
65
|
+
sought_type.is_a?(Class)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def name?
|
70
|
+
@name == '*'
|
71
|
+
end
|
72
|
+
|
73
|
+
def content?
|
74
|
+
@name == '.'
|
75
|
+
end
|
76
|
+
|
77
|
+
# Applies the block to +value+ which might also be a collection.
|
78
|
+
def apply(value)
|
79
|
+
return value unless value # DISCUSS: is that ok here?
|
80
|
+
|
81
|
+
if array?
|
82
|
+
value = value.collect do |item|
|
83
|
+
yield item
|
84
|
+
end
|
85
|
+
else
|
86
|
+
value = yield value
|
87
|
+
end
|
88
|
+
|
89
|
+
value
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_ref
|
93
|
+
case sought_type
|
94
|
+
when :attr then XMLAttributeRef
|
95
|
+
when :text then XMLTextRef
|
96
|
+
when :namespace then XMLNameSpaceRef
|
97
|
+
when Symbol then raise ArgumentError, "Invalid type argument #{sought_type}"
|
98
|
+
else XMLObjectRef
|
99
|
+
end.new(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
def extract_type(as)
|
104
|
+
as = as.first if as.is_a?(Array) # TODO: move to ArrayDefinition.
|
105
|
+
|
106
|
+
as || :text
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
Nokogiri::XML::Node.class_eval do
|
4
|
+
def add_node(name)
|
5
|
+
add_child Nokogiri::XML::Node.new(name, document)
|
6
|
+
end
|
7
|
+
|
8
|
+
# FIXME: remove switch. where is #from used with nodes?
|
9
|
+
def self.from(data)
|
10
|
+
case data
|
11
|
+
when Nokogiri::XML::Node
|
12
|
+
data
|
13
|
+
when Nokogiri::XML::Document
|
14
|
+
data.root
|
15
|
+
else
|
16
|
+
Nokogiri::XML(data).root
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module Representable
|
2
|
+
class RequiredElementMissing < Exception # :nodoc:
|
3
|
+
end
|
4
|
+
|
5
|
+
# Internal base class that represents an XML - Class binding.
|
6
|
+
class XMLRef
|
7
|
+
attr_reader :definition
|
8
|
+
delegate :required?, :array?, :accessor, :wrapper, :name, :to => :definition
|
9
|
+
|
10
|
+
def initialize(definition)
|
11
|
+
@definition = definition
|
12
|
+
end
|
13
|
+
|
14
|
+
def value_in(xml)
|
15
|
+
xml = Nokogiri::XML::Node.from(xml) or return default
|
16
|
+
|
17
|
+
value_from_node(xml) or default
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def default
|
22
|
+
""
|
23
|
+
end
|
24
|
+
|
25
|
+
def xpath
|
26
|
+
name
|
27
|
+
end
|
28
|
+
|
29
|
+
def wrap(xml, opts = {:always_create => false})
|
30
|
+
wrap_with = @auto_vals ? auto_wrapper : wrapper
|
31
|
+
|
32
|
+
return xml if !wrap_with || xml.name == wrap_with
|
33
|
+
if !opts[:always_create] && (child = xml.children.find {|c| c.name == wrap_with })
|
34
|
+
return child
|
35
|
+
end
|
36
|
+
xml.add_node(wrap_with.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
def collect_for(xml)
|
40
|
+
nodes = xml.search("./#{xpath}")
|
41
|
+
vals = nodes.collect { |node| yield node }
|
42
|
+
|
43
|
+
array? ? vals : vals.first
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Represents a tag attribute.
|
48
|
+
class XMLAttributeRef < XMLRef
|
49
|
+
# Updates the attribute in the given XML block to
|
50
|
+
# the value provided.
|
51
|
+
def update_xml(xml, values)
|
52
|
+
wrap(xml).tap do |xml|
|
53
|
+
xml[name] = values.to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def value_from_node(xml)
|
59
|
+
xml[name]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Represents text content in a tag.
|
64
|
+
class XMLTextRef < XMLRef
|
65
|
+
delegate :cdata?, :content?, :name?, :to => :definition
|
66
|
+
|
67
|
+
# Updates the text in the given _xml_ block to
|
68
|
+
# the _value_ provided.
|
69
|
+
def update_xml(xml, value)
|
70
|
+
wrap(xml).tap do |xml|
|
71
|
+
if content?
|
72
|
+
add(xml, value)
|
73
|
+
elsif name?
|
74
|
+
xml.name = value
|
75
|
+
elsif array?
|
76
|
+
value.each do |v|
|
77
|
+
add(xml.add_node(name), v)
|
78
|
+
end
|
79
|
+
else
|
80
|
+
add(xml.add_node(name), value)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def value_from_node(xml)
|
87
|
+
collect_for(xml) do |node|
|
88
|
+
node.content
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def add(dest, value)
|
93
|
+
if cdata?
|
94
|
+
dest.add_child(Nokogiri::XML::CDATA.new(dest.document, content))
|
95
|
+
else
|
96
|
+
dest.content = value.to_s
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class XMLNameSpaceRef < XMLRef
|
102
|
+
private
|
103
|
+
def value_from_node(xml)
|
104
|
+
xml.namespace.prefix
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Represents a tag with object binding.
|
109
|
+
class XMLObjectRef < XMLTextRef
|
110
|
+
delegate :sought_type, :to => :definition
|
111
|
+
|
112
|
+
# Adds the ref's markup to +xml+.
|
113
|
+
def update_xml(xml, value)
|
114
|
+
wrap(xml).tap do |xml|
|
115
|
+
if array?
|
116
|
+
update_xml_for_collection(xml, value)
|
117
|
+
else
|
118
|
+
update_xml_for_entity(xml, value)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
def default
|
125
|
+
[]
|
126
|
+
end
|
127
|
+
|
128
|
+
def serialize(object)
|
129
|
+
object.to_xml
|
130
|
+
end
|
131
|
+
|
132
|
+
def deserialize(node_class, xml)
|
133
|
+
node_class.from_xml(xml)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Deserializes the ref's element from +xml+.
|
137
|
+
def value_from_node(xml)
|
138
|
+
collect_for(xml) do |node|
|
139
|
+
deserialize(sought_type, node)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def update_xml_for_collection(xml, collection)
|
144
|
+
collection.each do |item|
|
145
|
+
update_xml_for_entity(xml, item)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def update_xml_for_entity(xml, entity)
|
150
|
+
xml.add_child(serialize(entity))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|