representable 0.0.1.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|