xmlmapper 0.5.9 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -4
- data/README.md +35 -35
- data/lib/xmlmapper.rb +776 -1
- data/lib/{happymapper → xmlmapper}/anonymous_mapper.rb +23 -23
- data/lib/{happymapper → xmlmapper}/attribute.rb +1 -1
- data/lib/{happymapper → xmlmapper}/element.rb +1 -1
- data/lib/{happymapper → xmlmapper}/item.rb +1 -1
- data/lib/{happymapper → xmlmapper}/supported_types.rb +2 -2
- data/lib/{happymapper → xmlmapper}/text_node.rb +1 -1
- data/lib/xmlmapper/version.rb +3 -0
- data/spec/attribute_default_value_spec.rb +1 -1
- data/spec/attributes_spec.rb +2 -2
- data/spec/fixtures/unformatted_address.xml +1 -0
- data/spec/has_many_empty_array_spec.rb +2 -2
- data/spec/ignay_spec.rb +5 -5
- data/spec/inheritance_spec.rb +6 -6
- data/spec/mixed_namespaces_spec.rb +2 -2
- data/spec/parse_with_object_to_update_spec.rb +4 -4
- data/spec/spec_helper.rb +1 -1
- data/spec/to_xml_spec.rb +5 -5
- data/spec/to_xml_with_namespaces_spec.rb +6 -6
- data/spec/wilcard_tag_name_spec.rb +8 -8
- data/spec/wrap_spec.rb +5 -5
- data/spec/{happymapper → xmlmapper}/attribute_spec.rb +1 -1
- data/spec/{happymapper → xmlmapper}/element_spec.rb +2 -2
- data/spec/{happymapper → xmlmapper}/item_spec.rb +16 -16
- data/spec/xmlmapper/text_node_spec.rb +9 -0
- data/spec/{happymapper_parse_spec.rb → xmlmapper_parse_spec.rb} +3 -3
- data/spec/{happymapper_spec.rb → xmlmapper_spec.rb} +87 -66
- data/spec/xpath_spec.rb +5 -5
- metadata +22 -21
- data/lib/happymapper.rb +0 -776
- data/lib/happymapper/version.rb +0 -3
- data/spec/happymapper/text_node_spec.rb +0 -9
data/spec/xpath_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe "Specifying elements and attributes with an xpath" do
|
4
4
|
|
5
5
|
class Item
|
6
|
-
include
|
6
|
+
include XmlMapper
|
7
7
|
|
8
8
|
tag 'item'
|
9
9
|
namespace 'amazing'
|
@@ -21,7 +21,7 @@ describe "Specifying elements and attributes with an xpath" do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
class Baby
|
24
|
-
include
|
24
|
+
include XmlMapper
|
25
25
|
|
26
26
|
has_one :name, String
|
27
27
|
end
|
@@ -65,19 +65,19 @@ describe "Specifying elements and attributes with an xpath" do
|
|
65
65
|
end
|
66
66
|
|
67
67
|
it "should find the subitems based on the xpath" do
|
68
|
-
expect(subject.more_details_text).to
|
68
|
+
expect(subject.more_details_text.size).to eq 2
|
69
69
|
expect(subject.more_details_text.first).to eq "more 1"
|
70
70
|
expect(subject.more_details_text.last).to eq "more 2"
|
71
71
|
end
|
72
72
|
|
73
73
|
it "should find the subitems based on the xpath" do
|
74
|
-
expect(subject.more_details).to
|
74
|
+
expect(subject.more_details.size).to eq 2
|
75
75
|
expect(subject.more_details.first).to eq "this one"
|
76
76
|
expect(subject.more_details.last).to eq "another one"
|
77
77
|
end
|
78
78
|
|
79
79
|
it "should find the subitems based on the xpath" do
|
80
|
-
expect(subject.more_details_alternative).to
|
80
|
+
expect(subject.more_details_alternative.size).to eq 2
|
81
81
|
expect(subject.more_details_alternative.first).to eq "this one"
|
82
82
|
expect(subject.more_details_alternative.last).to eq "another one"
|
83
83
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xmlmapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Damien Le Berrigaud
|
@@ -54,15 +54,14 @@ extra_rdoc_files:
|
|
54
54
|
files:
|
55
55
|
- CHANGELOG.md
|
56
56
|
- README.md
|
57
|
-
- lib/happymapper.rb
|
58
|
-
- lib/happymapper/anonymous_mapper.rb
|
59
|
-
- lib/happymapper/attribute.rb
|
60
|
-
- lib/happymapper/element.rb
|
61
|
-
- lib/happymapper/item.rb
|
62
|
-
- lib/happymapper/supported_types.rb
|
63
|
-
- lib/happymapper/text_node.rb
|
64
|
-
- lib/happymapper/version.rb
|
65
57
|
- lib/xmlmapper.rb
|
58
|
+
- lib/xmlmapper/anonymous_mapper.rb
|
59
|
+
- lib/xmlmapper/attribute.rb
|
60
|
+
- lib/xmlmapper/element.rb
|
61
|
+
- lib/xmlmapper/item.rb
|
62
|
+
- lib/xmlmapper/supported_types.rb
|
63
|
+
- lib/xmlmapper/text_node.rb
|
64
|
+
- lib/xmlmapper/version.rb
|
66
65
|
- spec/attribute_default_value_spec.rb
|
67
66
|
- spec/attributes_spec.rb
|
68
67
|
- spec/fixtures/address.xml
|
@@ -91,13 +90,8 @@ files:
|
|
91
90
|
- spec/fixtures/set_config_options.xml
|
92
91
|
- spec/fixtures/statuses.xml
|
93
92
|
- spec/fixtures/subclass_namespace.xml
|
93
|
+
- spec/fixtures/unformatted_address.xml
|
94
94
|
- spec/fixtures/wrapper.xml
|
95
|
-
- spec/happymapper/attribute_spec.rb
|
96
|
-
- spec/happymapper/element_spec.rb
|
97
|
-
- spec/happymapper/item_spec.rb
|
98
|
-
- spec/happymapper/text_node_spec.rb
|
99
|
-
- spec/happymapper_parse_spec.rb
|
100
|
-
- spec/happymapper_spec.rb
|
101
95
|
- spec/has_many_empty_array_spec.rb
|
102
96
|
- spec/ignay_spec.rb
|
103
97
|
- spec/inheritance_spec.rb
|
@@ -108,6 +102,12 @@ files:
|
|
108
102
|
- spec/to_xml_with_namespaces_spec.rb
|
109
103
|
- spec/wilcard_tag_name_spec.rb
|
110
104
|
- spec/wrap_spec.rb
|
105
|
+
- spec/xmlmapper/attribute_spec.rb
|
106
|
+
- spec/xmlmapper/element_spec.rb
|
107
|
+
- spec/xmlmapper/item_spec.rb
|
108
|
+
- spec/xmlmapper/text_node_spec.rb
|
109
|
+
- spec/xmlmapper_parse_spec.rb
|
110
|
+
- spec/xmlmapper_spec.rb
|
111
111
|
- spec/xpath_spec.rb
|
112
112
|
homepage: http://github.com/digidentity/xmlmapper
|
113
113
|
licenses:
|
@@ -162,13 +162,8 @@ test_files:
|
|
162
162
|
- spec/fixtures/set_config_options.xml
|
163
163
|
- spec/fixtures/statuses.xml
|
164
164
|
- spec/fixtures/subclass_namespace.xml
|
165
|
+
- spec/fixtures/unformatted_address.xml
|
165
166
|
- spec/fixtures/wrapper.xml
|
166
|
-
- spec/happymapper/attribute_spec.rb
|
167
|
-
- spec/happymapper/element_spec.rb
|
168
|
-
- spec/happymapper/item_spec.rb
|
169
|
-
- spec/happymapper/text_node_spec.rb
|
170
|
-
- spec/happymapper_parse_spec.rb
|
171
|
-
- spec/happymapper_spec.rb
|
172
167
|
- spec/has_many_empty_array_spec.rb
|
173
168
|
- spec/ignay_spec.rb
|
174
169
|
- spec/inheritance_spec.rb
|
@@ -179,4 +174,10 @@ test_files:
|
|
179
174
|
- spec/to_xml_with_namespaces_spec.rb
|
180
175
|
- spec/wilcard_tag_name_spec.rb
|
181
176
|
- spec/wrap_spec.rb
|
177
|
+
- spec/xmlmapper/attribute_spec.rb
|
178
|
+
- spec/xmlmapper/element_spec.rb
|
179
|
+
- spec/xmlmapper/item_spec.rb
|
180
|
+
- spec/xmlmapper/text_node_spec.rb
|
181
|
+
- spec/xmlmapper_parse_spec.rb
|
182
|
+
- spec/xmlmapper_spec.rb
|
182
183
|
- spec/xpath_spec.rb
|
data/lib/happymapper.rb
DELETED
@@ -1,776 +0,0 @@
|
|
1
|
-
require 'nokogiri'
|
2
|
-
require 'date'
|
3
|
-
require 'time'
|
4
|
-
require 'happymapper/anonymous_mapper'
|
5
|
-
|
6
|
-
module HappyMapper
|
7
|
-
class Boolean; end
|
8
|
-
class XmlContent; end
|
9
|
-
|
10
|
-
extend AnonymousMapper
|
11
|
-
|
12
|
-
DEFAULT_NS = "happymapper"
|
13
|
-
|
14
|
-
def self.included(base)
|
15
|
-
if !(base.superclass <= HappyMapper)
|
16
|
-
base.instance_eval do
|
17
|
-
@attributes = {}
|
18
|
-
@elements = {}
|
19
|
-
@registered_namespaces = {}
|
20
|
-
@wrapper_anonymous_classes = {}
|
21
|
-
end
|
22
|
-
else
|
23
|
-
base.instance_eval do
|
24
|
-
@attributes =
|
25
|
-
superclass.instance_variable_get(:@attributes).dup
|
26
|
-
@elements =
|
27
|
-
superclass.instance_variable_get(:@elements).dup
|
28
|
-
@registered_namespaces =
|
29
|
-
superclass.instance_variable_get(:@registered_namespaces).dup
|
30
|
-
@wrapper_anonymous_classes =
|
31
|
-
superclass.instance_variable_get(:@wrapper_anonymous_classes).dup
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
base.extend ClassMethods
|
36
|
-
end
|
37
|
-
|
38
|
-
module ClassMethods
|
39
|
-
|
40
|
-
#
|
41
|
-
# The xml has the following attributes defined.
|
42
|
-
#
|
43
|
-
# @example
|
44
|
-
#
|
45
|
-
# "<country code='de'>Germany</country>"
|
46
|
-
#
|
47
|
-
# # definition of the 'code' attribute within the class
|
48
|
-
# attribute :code, String
|
49
|
-
#
|
50
|
-
# @param [Symbol] name the name of the accessor that is created
|
51
|
-
# @param [String,Class] type the class name of the name of the class whcih
|
52
|
-
# the object will be converted upon parsing
|
53
|
-
# @param [Hash] options additional parameters to send to the relationship
|
54
|
-
#
|
55
|
-
def attribute(name, type, options={})
|
56
|
-
attribute = Attribute.new(name, type, options)
|
57
|
-
@attributes[name] = attribute
|
58
|
-
attr_accessor attribute.method_name.intern
|
59
|
-
end
|
60
|
-
|
61
|
-
#
|
62
|
-
# The elements defined through {#attribute}.
|
63
|
-
#
|
64
|
-
# @return [Array<Attribute>] a list of the attributes defined for this class;
|
65
|
-
# an empty array is returned when there have been no attributes defined.
|
66
|
-
#
|
67
|
-
def attributes
|
68
|
-
@attributes.values
|
69
|
-
end
|
70
|
-
|
71
|
-
#
|
72
|
-
# Register a namespace that is used to persist the object namespace back to
|
73
|
-
# XML.
|
74
|
-
#
|
75
|
-
# @example
|
76
|
-
#
|
77
|
-
# register_namespace 'prefix', 'http://www.unicornland.com/prefix'
|
78
|
-
#
|
79
|
-
# # the output will contain the namespace defined
|
80
|
-
#
|
81
|
-
# "<outputXML xmlns:prefix="http://www.unicornland.com/prefix">
|
82
|
-
# ...
|
83
|
-
# </outputXML>"
|
84
|
-
#
|
85
|
-
# @param [String] namespace the xml prefix
|
86
|
-
# @param [String] ns url for the xml namespace
|
87
|
-
#
|
88
|
-
def register_namespace(namespace, ns)
|
89
|
-
@registered_namespaces.merge!({namespace => ns})
|
90
|
-
end
|
91
|
-
|
92
|
-
#
|
93
|
-
# An element defined in the XML that is parsed.
|
94
|
-
#
|
95
|
-
# @example
|
96
|
-
#
|
97
|
-
# "<address location='home'>
|
98
|
-
# <city>Oldenburg</city>
|
99
|
-
# </address>"
|
100
|
-
#
|
101
|
-
# # definition of the 'city' element within the class
|
102
|
-
#
|
103
|
-
# element :city, String
|
104
|
-
#
|
105
|
-
# @param [Symbol] name the name of the accessor that is created
|
106
|
-
# @param [String,Class] type the class name of the name of the class whcih
|
107
|
-
# the object will be converted upon parsing
|
108
|
-
# @param [Hash] options additional parameters to send to the relationship
|
109
|
-
#
|
110
|
-
def element(name, type, options={})
|
111
|
-
element = Element.new(name, type, options)
|
112
|
-
@elements[name] = element
|
113
|
-
attr_accessor element.method_name.intern
|
114
|
-
end
|
115
|
-
|
116
|
-
#
|
117
|
-
# The elements defined through {#element}, {#has_one}, and {#has_many}.
|
118
|
-
#
|
119
|
-
# @return [Array<Element>] a list of the elements contained defined for this
|
120
|
-
# class; an empty array is returned when there have been no elements
|
121
|
-
# defined.
|
122
|
-
#
|
123
|
-
def elements
|
124
|
-
@elements.values
|
125
|
-
end
|
126
|
-
|
127
|
-
#
|
128
|
-
# The value stored in the text node of the current element.
|
129
|
-
#
|
130
|
-
# @example
|
131
|
-
#
|
132
|
-
# "<firstName>Michael Jackson</firstName>"
|
133
|
-
#
|
134
|
-
# # definition of the 'firstName' text node within the class
|
135
|
-
#
|
136
|
-
# content :first_name, String
|
137
|
-
#
|
138
|
-
# @param [Symbol] name the name of the accessor that is created
|
139
|
-
# @param [String,Class] type the class name of the name of the class whcih
|
140
|
-
# the object will be converted upon parsing. By Default String class will be taken.
|
141
|
-
# @param [Hash] options additional parameters to send to the relationship
|
142
|
-
#
|
143
|
-
def content(name, type=String, options={})
|
144
|
-
@content = TextNode.new(name, type, options)
|
145
|
-
attr_accessor @content.method_name.intern
|
146
|
-
end
|
147
|
-
|
148
|
-
#
|
149
|
-
# Sets the object to have xml content, this will assign the XML contents
|
150
|
-
# that are parsed to the attribute accessor xml_content. The object will
|
151
|
-
# respond to the method #xml_content and will return the XML data that
|
152
|
-
# it has parsed.
|
153
|
-
#
|
154
|
-
def has_xml_content
|
155
|
-
attr_accessor :xml_content
|
156
|
-
end
|
157
|
-
|
158
|
-
#
|
159
|
-
# The object has one of these elements in the XML. If there are multiple,
|
160
|
-
# the last one will be set to this value.
|
161
|
-
#
|
162
|
-
# @param [Symbol] name the name of the accessor that is created
|
163
|
-
# @param [String,Class] type the class name of the name of the class whcih
|
164
|
-
# the object will be converted upon parsing
|
165
|
-
# @param [Hash] options additional parameters to send to the relationship
|
166
|
-
#
|
167
|
-
# @see #element
|
168
|
-
#
|
169
|
-
def has_one(name, type, options={})
|
170
|
-
element name, type, {:single => true}.merge(options)
|
171
|
-
end
|
172
|
-
|
173
|
-
#
|
174
|
-
# The object has many of these elements in the XML.
|
175
|
-
#
|
176
|
-
# @param [Symbol] name the name of accessor that is created
|
177
|
-
# @param [String,Class] type the class name or the name of the class which
|
178
|
-
# the object will be converted upon parsing.
|
179
|
-
# @param [Hash] options additional parameters to send to the relationship
|
180
|
-
#
|
181
|
-
# @see #element
|
182
|
-
#
|
183
|
-
def has_many(name, type, options={})
|
184
|
-
element name, type, {:single => false}.merge(options)
|
185
|
-
end
|
186
|
-
|
187
|
-
#
|
188
|
-
# The list of registered after_parse callbacks.
|
189
|
-
#
|
190
|
-
def after_parse_callbacks
|
191
|
-
@after_parse_callbacks ||= []
|
192
|
-
end
|
193
|
-
|
194
|
-
#
|
195
|
-
# Register a new after_parse callback, given as a block.
|
196
|
-
#
|
197
|
-
# @yield [object] Yields the newly-parsed object to the block after parsing.
|
198
|
-
# Sub-objects will be already populated.
|
199
|
-
def after_parse(&block)
|
200
|
-
after_parse_callbacks.push(block)
|
201
|
-
end
|
202
|
-
|
203
|
-
#
|
204
|
-
# Specify a namespace if a node and all its children are all namespaced
|
205
|
-
# elements. This is simpler than passing the :namespace option to each
|
206
|
-
# defined element.
|
207
|
-
#
|
208
|
-
# @param [String] namespace the namespace to set as default for the class
|
209
|
-
# element.
|
210
|
-
#
|
211
|
-
def namespace(namespace = nil)
|
212
|
-
@namespace = namespace if namespace
|
213
|
-
@namespace
|
214
|
-
end
|
215
|
-
|
216
|
-
#
|
217
|
-
# @param [String] new_tag_name the name for the tag
|
218
|
-
#
|
219
|
-
def tag(new_tag_name)
|
220
|
-
@tag_name = new_tag_name.to_s unless new_tag_name.nil? || new_tag_name.to_s.empty?
|
221
|
-
end
|
222
|
-
|
223
|
-
#
|
224
|
-
# The name of the tag
|
225
|
-
#
|
226
|
-
# @return [String] the name of the tag as a string, downcased
|
227
|
-
#
|
228
|
-
def tag_name
|
229
|
-
@tag_name ||= to_s.split('::')[-1].downcase
|
230
|
-
end
|
231
|
-
|
232
|
-
# There is an XML tag that needs to be known for parsing and should be generated
|
233
|
-
# during a to_xml. But it doesn't need to be a class and the contained elements should
|
234
|
-
# be made available on the parent class
|
235
|
-
#
|
236
|
-
# @param [String] name the name of the element that is just a place holder
|
237
|
-
# @param [Proc] blk the element definitions inside the place holder tag
|
238
|
-
#
|
239
|
-
def wrap(name, &blk)
|
240
|
-
# Get an anonymous HappyMapper that has 'name' as its tag and defined
|
241
|
-
# in '&blk'. Then save that to a class instance variable for later use
|
242
|
-
wrapper = AnonymousWrapperClassFactory.get(name, &blk)
|
243
|
-
@wrapper_anonymous_classes[wrapper.inspect] = wrapper
|
244
|
-
|
245
|
-
# Create getter/setter for each element and attribute defined on the anonymous HappyMapper
|
246
|
-
# onto this class. They get/set the value by passing thru to the anonymous class.
|
247
|
-
passthrus = wrapper.attributes + wrapper.elements
|
248
|
-
passthrus.each do |item|
|
249
|
-
class_eval %{
|
250
|
-
def #{item.method_name}
|
251
|
-
@#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper.inspect}'].new
|
252
|
-
@#{name}.#{item.method_name}
|
253
|
-
end
|
254
|
-
def #{item.method_name}=(value)
|
255
|
-
@#{name} ||= self.class.instance_variable_get('@wrapper_anonymous_classes')['#{wrapper.inspect}'].new
|
256
|
-
@#{name}.#{item.method_name} = value
|
257
|
-
end
|
258
|
-
}
|
259
|
-
end
|
260
|
-
|
261
|
-
has_one name, wrapper
|
262
|
-
end
|
263
|
-
|
264
|
-
# The callback defined through {.with_nokogiri_config}.
|
265
|
-
#
|
266
|
-
# @return [Proc] the proc to pass to Nokogiri to setup parse options. nil if empty.
|
267
|
-
#
|
268
|
-
def nokogiri_config_callback
|
269
|
-
@nokogiri_config_callback
|
270
|
-
end
|
271
|
-
|
272
|
-
# Register a config callback according to the block Nokogori expects when calling Nokogiri::XML::Document.parse().
|
273
|
-
# See http://nokogiri.org/Nokogiri/XML/Document.html#method-c-parse
|
274
|
-
#
|
275
|
-
# @param [Proc] the proc to pass to Nokogiri to setup parse options
|
276
|
-
#
|
277
|
-
def with_nokogiri_config(&blk)
|
278
|
-
@nokogiri_config_callback = blk
|
279
|
-
end
|
280
|
-
|
281
|
-
#
|
282
|
-
# @param [Nokogiri::XML::Node,Nokogiri:XML::Document,String] xml the XML
|
283
|
-
# contents to convert into Object.
|
284
|
-
# @param [Hash] options additional information for parsing. :single => true
|
285
|
-
# if requesting a single object, otherwise it defaults to retuning an
|
286
|
-
# array of multiple items. :xpath information where to start the parsing
|
287
|
-
# :namespace is the namespace to use for additional information.
|
288
|
-
#
|
289
|
-
def parse(xml, options = {})
|
290
|
-
|
291
|
-
# create a local copy of the objects namespace value for this parse execution
|
292
|
-
namespace = @namespace
|
293
|
-
|
294
|
-
# If the XML specified is an Node then we have what we need.
|
295
|
-
if xml.is_a?(Nokogiri::XML::Node) && !xml.is_a?(Nokogiri::XML::Document)
|
296
|
-
node = xml
|
297
|
-
else
|
298
|
-
|
299
|
-
# If xml is an XML document select the root node of the document
|
300
|
-
if xml.is_a?(Nokogiri::XML::Document)
|
301
|
-
node = xml.root
|
302
|
-
else
|
303
|
-
|
304
|
-
# Attempt to parse the xml value with Nokogiri XML as a document
|
305
|
-
# and select the root element
|
306
|
-
xml = Nokogiri::XML(
|
307
|
-
xml, nil, nil,
|
308
|
-
Nokogiri::XML::ParseOptions::STRICT,
|
309
|
-
&nokogiri_config_callback
|
310
|
-
)
|
311
|
-
node = xml.root
|
312
|
-
end
|
313
|
-
|
314
|
-
# if the node name is equal to the tag name then the we are parsing the
|
315
|
-
# root element and that is important to record so that we can apply
|
316
|
-
# the correct xpath on the elements of this document.
|
317
|
-
|
318
|
-
root = node.name == tag_name
|
319
|
-
end
|
320
|
-
|
321
|
-
# if any namespaces have been provied then we should capture those and then
|
322
|
-
# merge them with any namespaces found on the xml node and merge all that
|
323
|
-
# with any namespaces that have been registered on the object
|
324
|
-
|
325
|
-
namespaces = options[:namespaces] || {}
|
326
|
-
namespaces = namespaces.merge(xml.collect_namespaces) if xml.respond_to?(:collect_namespaces)
|
327
|
-
namespaces = namespaces.merge(@registered_namespaces)
|
328
|
-
|
329
|
-
# if a namespace has been provided then set the current namespace to it
|
330
|
-
# or set the default namespace to the one defined under 'xmlns'
|
331
|
-
# or set the default namespace to the namespace that matches 'happymapper's
|
332
|
-
|
333
|
-
if options[:namespace]
|
334
|
-
namespace = options[:namespace]
|
335
|
-
elsif namespaces.has_key?("xmlns")
|
336
|
-
namespace ||= DEFAULT_NS
|
337
|
-
namespaces[DEFAULT_NS] = namespaces.delete("xmlns")
|
338
|
-
elsif namespaces.has_key?(DEFAULT_NS)
|
339
|
-
namespace ||= DEFAULT_NS
|
340
|
-
end
|
341
|
-
|
342
|
-
# from the options grab any nodes present and if none are present then
|
343
|
-
# perform the following to find the nodes for the given class
|
344
|
-
|
345
|
-
nodes = options.fetch(:nodes) do
|
346
|
-
|
347
|
-
# when at the root use the xpath '/' otherwise use a more gready './/'
|
348
|
-
# unless an xpath has been specified, which should overwrite default
|
349
|
-
# and finally attach the current namespace if one has been defined
|
350
|
-
#
|
351
|
-
|
352
|
-
xpath = (root ? '/' : './/')
|
353
|
-
xpath = options[:xpath].to_s.sub(/([^\/])$/, '\1/') if options[:xpath]
|
354
|
-
xpath += "#{namespace}:" if namespace
|
355
|
-
|
356
|
-
nodes = []
|
357
|
-
|
358
|
-
# when finding nodes, do it in this order:
|
359
|
-
# 1. specified tag if one has been provided
|
360
|
-
# 2. name of element
|
361
|
-
# 3. tag_name (derived from class name by default)
|
362
|
-
|
363
|
-
# If a tag has been provided we need to search for it.
|
364
|
-
|
365
|
-
if options.key?(:tag)
|
366
|
-
begin
|
367
|
-
nodes = node.xpath(xpath + options[:tag].to_s, namespaces)
|
368
|
-
rescue
|
369
|
-
# This exception takes place when the namespace is often not found
|
370
|
-
# and we should continue on with the empty array of nodes.
|
371
|
-
end
|
372
|
-
else
|
373
|
-
|
374
|
-
# This is the default case when no tag value is provided.
|
375
|
-
# First we use the name of the element `items` in `has_many items`
|
376
|
-
# Second we use the tag name which is the name of the class cleaned up
|
377
|
-
|
378
|
-
[options[:name], tag_name].compact.each do |xpath_ext|
|
379
|
-
begin
|
380
|
-
nodes = node.xpath(xpath + xpath_ext.to_s, namespaces)
|
381
|
-
rescue
|
382
|
-
break
|
383
|
-
# This exception takes place when the namespace is often not found
|
384
|
-
# and we should continue with the empty array of nodes or keep looking
|
385
|
-
end
|
386
|
-
break if nodes && !nodes.empty?
|
387
|
-
end
|
388
|
-
|
389
|
-
end
|
390
|
-
|
391
|
-
nodes
|
392
|
-
end
|
393
|
-
|
394
|
-
# Nothing matching found, we can go ahead and return
|
395
|
-
return ( ( options[:single] || root ) ? nil : [] ) if nodes.size == 0
|
396
|
-
|
397
|
-
# If the :limit option has been specified then we are going to slice
|
398
|
-
# our node results by that amount to allow us the ability to deal with
|
399
|
-
# a large result set of data.
|
400
|
-
|
401
|
-
limit = options[:in_groups_of] || nodes.size
|
402
|
-
|
403
|
-
# If the limit of 0 has been specified then the user obviously wants
|
404
|
-
# none of the nodes that we are serving within this batch of nodes.
|
405
|
-
|
406
|
-
return [] if limit == 0
|
407
|
-
|
408
|
-
collection = []
|
409
|
-
|
410
|
-
nodes.each_slice(limit) do |slice|
|
411
|
-
|
412
|
-
part = slice.map do |n|
|
413
|
-
|
414
|
-
# If an existing HappyMapper object is provided, update it with the
|
415
|
-
# values from the xml being parsed. Otherwise, create a new object
|
416
|
-
|
417
|
-
obj = options[:update] ? options[:update] : new
|
418
|
-
|
419
|
-
attributes.each do |attr|
|
420
|
-
value = attr.from_xml_node(n, namespace, namespaces)
|
421
|
-
value = attr.default if value.nil?
|
422
|
-
obj.send("#{attr.method_name}=", value)
|
423
|
-
end
|
424
|
-
|
425
|
-
elements.each do |elem|
|
426
|
-
obj.send("#{elem.method_name}=",elem.from_xml_node(n, namespace, namespaces))
|
427
|
-
end
|
428
|
-
|
429
|
-
if @content
|
430
|
-
obj.send("#{@content.method_name}=",@content.from_xml_node(n, namespace, namespaces))
|
431
|
-
end
|
432
|
-
|
433
|
-
# If the HappyMapper class has the method #xml_value=,
|
434
|
-
# attr_writer :xml_value, or attr_accessor :xml_value then we want to
|
435
|
-
# assign the current xml that we just parsed to the xml_value
|
436
|
-
|
437
|
-
if obj.respond_to?('xml_value=')
|
438
|
-
n.namespaces.each {|name,path| n[name] = path }
|
439
|
-
obj.xml_value = n.to_xml
|
440
|
-
end
|
441
|
-
|
442
|
-
# If the HappyMapper class has the method #xml_content=,
|
443
|
-
# attr_write :xml_content, or attr_accessor :xml_content then we want to
|
444
|
-
# assign the child xml that we just parsed to the xml_content
|
445
|
-
|
446
|
-
if obj.respond_to?('xml_content=')
|
447
|
-
n = n.children if n.respond_to?(:children)
|
448
|
-
obj.xml_content = n.to_xml
|
449
|
-
end
|
450
|
-
|
451
|
-
# Call any registered after_parse callbacks for the object's class
|
452
|
-
|
453
|
-
obj.class.after_parse_callbacks.each { |callback| callback.call(obj) }
|
454
|
-
|
455
|
-
# collect the object that we have created
|
456
|
-
|
457
|
-
obj
|
458
|
-
end
|
459
|
-
|
460
|
-
# If a block has been provided and the user has requested that the objects
|
461
|
-
# be handled in groups then we should yield the slice of the objects to them
|
462
|
-
# otherwise continue to lump them together
|
463
|
-
|
464
|
-
if block_given? and options[:in_groups_of]
|
465
|
-
yield part
|
466
|
-
else
|
467
|
-
collection += part
|
468
|
-
end
|
469
|
-
|
470
|
-
end
|
471
|
-
|
472
|
-
# per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
|
473
|
-
nodes = nil
|
474
|
-
|
475
|
-
# If the :single option has been specified or we are at the root element
|
476
|
-
# then we are going to return the first item in the collection. Otherwise
|
477
|
-
# the return response is going to be an entire array of items.
|
478
|
-
|
479
|
-
if options[:single] or root
|
480
|
-
collection.first
|
481
|
-
else
|
482
|
-
collection
|
483
|
-
end
|
484
|
-
end
|
485
|
-
end
|
486
|
-
|
487
|
-
# Set all attributes with a default to their default values
|
488
|
-
def initialize
|
489
|
-
super
|
490
|
-
self.class.attributes.reject {|attr| attr.default.nil?}.each do |attr|
|
491
|
-
send("#{attr.method_name}=", attr.default)
|
492
|
-
end
|
493
|
-
end
|
494
|
-
|
495
|
-
#
|
496
|
-
# Create an xml representation of the specified class based on defined
|
497
|
-
# HappyMapper elements and attributes. The method is defined in a way
|
498
|
-
# that it can be called recursively by classes that are also HappyMapper
|
499
|
-
# classes, allowg for the composition of classes.
|
500
|
-
#
|
501
|
-
# @param [Nokogiri::XML::Builder] builder an instance of the XML builder which
|
502
|
-
# is being used when called recursively.
|
503
|
-
# @param [String] default_namespace The name of the namespace which is the
|
504
|
-
# default for the xml being produced; this is the namespace of the
|
505
|
-
# parent
|
506
|
-
# @param [String] namespace_override The namespace specified with the element
|
507
|
-
# declaration in the parent. Overrides the namespace declaration in the
|
508
|
-
# element class itself when calling #to_xml recursively.
|
509
|
-
# @param [String] tag_from_parent The xml tag to use on the element when being
|
510
|
-
# called recursively. This lets the parent doc define its own structure.
|
511
|
-
# Otherwise the element uses the tag it has defined for itself. Should only
|
512
|
-
# apply when calling a child HappyMapper element.
|
513
|
-
#
|
514
|
-
# @return [String,Nokogiri::XML::Builder] return XML representation of the
|
515
|
-
# HappyMapper object; when called recursively this is going to return
|
516
|
-
# and Nokogiri::XML::Builder object.
|
517
|
-
#
|
518
|
-
def to_xml(builder = nil, default_namespace = nil, namespace_override = nil,
|
519
|
-
tag_from_parent = nil)
|
520
|
-
|
521
|
-
#
|
522
|
-
# If to_xml has been called without a passed in builder instance that
|
523
|
-
# means we are going to return xml output. When it has been called with
|
524
|
-
# a builder instance that means we most likely being called recursively
|
525
|
-
# and will return the end product as a builder instance.
|
526
|
-
#
|
527
|
-
unless builder
|
528
|
-
write_out_to_xml = true
|
529
|
-
builder = Nokogiri::XML::Builder.new
|
530
|
-
end
|
531
|
-
|
532
|
-
#
|
533
|
-
# Find the attributes for the class and collect them into an array
|
534
|
-
# that will be placed into a Hash structure
|
535
|
-
#
|
536
|
-
attributes = self.class.attributes.collect do |attribute|
|
537
|
-
|
538
|
-
#
|
539
|
-
# If an attribute is marked as read_only then we want to ignore the attribute
|
540
|
-
# when it comes to saving the xml document; so we wiill not go into any of
|
541
|
-
# the below process
|
542
|
-
#
|
543
|
-
unless attribute.options[:read_only]
|
544
|
-
|
545
|
-
value = send(attribute.method_name)
|
546
|
-
value = nil if value == attribute.default
|
547
|
-
|
548
|
-
#
|
549
|
-
# If the attribute defines an on_save lambda/proc or value that maps to
|
550
|
-
# a method that the class has defined, then call it with the value as a
|
551
|
-
# parameter.
|
552
|
-
#
|
553
|
-
if on_save_action = attribute.options[:on_save]
|
554
|
-
if on_save_action.is_a?(Proc)
|
555
|
-
value = on_save_action.call(value)
|
556
|
-
elsif respond_to?(on_save_action)
|
557
|
-
value = send(on_save_action,value)
|
558
|
-
end
|
559
|
-
end
|
560
|
-
|
561
|
-
#
|
562
|
-
# Attributes that have a nil value should be ignored unless they explicitly
|
563
|
-
# state that they should be expressed in the output.
|
564
|
-
#
|
565
|
-
if not value.nil? || attribute.options[:state_when_nil]
|
566
|
-
attribute_namespace = attribute.options[:namespace]
|
567
|
-
[ "#{attribute_namespace ? "#{attribute_namespace}:" : ""}#{attribute.tag}", value ]
|
568
|
-
else
|
569
|
-
[]
|
570
|
-
end
|
571
|
-
|
572
|
-
else
|
573
|
-
[]
|
574
|
-
end
|
575
|
-
|
576
|
-
end.flatten
|
577
|
-
|
578
|
-
attributes = Hash[ *attributes ]
|
579
|
-
|
580
|
-
#
|
581
|
-
# Create a tag in the builder that matches the class's tag name unless a tag was passed
|
582
|
-
# in a recursive call from the parent doc. Then append
|
583
|
-
# any attributes to the element that were defined above.
|
584
|
-
#
|
585
|
-
builder.send("#{tag_from_parent || self.class.tag_name}_",attributes) do |xml|
|
586
|
-
|
587
|
-
#
|
588
|
-
# Add all the registered namespaces to the root element.
|
589
|
-
# When this is called recurisvely by composed classes the namespaces
|
590
|
-
# are still added to the root element
|
591
|
-
#
|
592
|
-
# However, we do not want to add the namespace if the namespace is 'xmlns'
|
593
|
-
# which means that it is the default namesapce of the code.
|
594
|
-
#
|
595
|
-
if self.class.instance_variable_get('@registered_namespaces') && builder.doc.root
|
596
|
-
self.class.instance_variable_get('@registered_namespaces').each_pair do |name,href|
|
597
|
-
name = nil if name == "xmlns"
|
598
|
-
builder.doc.root.add_namespace(name,href)
|
599
|
-
end
|
600
|
-
end
|
601
|
-
|
602
|
-
#
|
603
|
-
# If the object we are serializing has a namespace declaration we will want
|
604
|
-
# to use that namespace or we will use the default namespace.
|
605
|
-
# When neither are specifed we are simply using whatever is default to the
|
606
|
-
# builder
|
607
|
-
#
|
608
|
-
namespace_for_parent = namespace_override
|
609
|
-
if self.class.respond_to?(:namespace) && self.class.namespace
|
610
|
-
namespace_for_parent ||= self.class.namespace
|
611
|
-
end
|
612
|
-
namespace_for_parent ||= default_namespace
|
613
|
-
|
614
|
-
xml.parent.namespace =
|
615
|
-
builder.doc.root.namespace_definitions.find { |x| x.prefix == namespace_for_parent }
|
616
|
-
|
617
|
-
|
618
|
-
#
|
619
|
-
# When a content has been defined we add the resulting value
|
620
|
-
# the output xml
|
621
|
-
#
|
622
|
-
if content = self.class.instance_variable_get('@content')
|
623
|
-
|
624
|
-
unless content.options[:read_only]
|
625
|
-
text_accessor = content.tag || content.name
|
626
|
-
value = send(text_accessor)
|
627
|
-
|
628
|
-
if on_save_action = content.options[:on_save]
|
629
|
-
if on_save_action.is_a?(Proc)
|
630
|
-
value = on_save_action.call(value)
|
631
|
-
elsif respond_to?(on_save_action)
|
632
|
-
value = send(on_save_action,value)
|
633
|
-
end
|
634
|
-
end
|
635
|
-
|
636
|
-
builder.text(value)
|
637
|
-
end
|
638
|
-
|
639
|
-
end
|
640
|
-
|
641
|
-
#
|
642
|
-
# for every define element (i.e. has_one, has_many, element) we are
|
643
|
-
# going to persist each one
|
644
|
-
#
|
645
|
-
self.class.elements.each do |element|
|
646
|
-
|
647
|
-
#
|
648
|
-
# If an element is marked as read only do not consider at all when
|
649
|
-
# saving to XML.
|
650
|
-
#
|
651
|
-
unless element.options[:read_only]
|
652
|
-
|
653
|
-
tag = element.tag || element.name
|
654
|
-
|
655
|
-
#
|
656
|
-
# The value to store is the result of the method call to the element,
|
657
|
-
# by default this is simply utilizing the attr_accessor defined. However,
|
658
|
-
# this allows for this method to be overridden
|
659
|
-
#
|
660
|
-
value = send(element.name)
|
661
|
-
|
662
|
-
#
|
663
|
-
# If the element defines an on_save lambda/proc then we will call that
|
664
|
-
# operation on the specified value. This allows for operations to be
|
665
|
-
# performed to convert the value to a specific value to be saved to the xml.
|
666
|
-
#
|
667
|
-
if on_save_action = element.options[:on_save]
|
668
|
-
if on_save_action.is_a?(Proc)
|
669
|
-
value = on_save_action.call(value)
|
670
|
-
elsif respond_to?(on_save_action)
|
671
|
-
value = send(on_save_action,value)
|
672
|
-
end
|
673
|
-
end
|
674
|
-
|
675
|
-
#
|
676
|
-
# Normally a nil value would be ignored, however if specified then
|
677
|
-
# an empty element will be written to the xml
|
678
|
-
#
|
679
|
-
if value.nil? && element.options[:single] && element.options[:state_when_nil]
|
680
|
-
xml.send("#{tag}_","")
|
681
|
-
end
|
682
|
-
|
683
|
-
#
|
684
|
-
# To allow for us to treat both groups of items and singular items
|
685
|
-
# equally we wrap the value and treat it as an array.
|
686
|
-
#
|
687
|
-
if value.nil?
|
688
|
-
values = []
|
689
|
-
elsif value.respond_to?(:to_ary) && !element.options[:single]
|
690
|
-
values = value.to_ary
|
691
|
-
else
|
692
|
-
values = [value]
|
693
|
-
end
|
694
|
-
|
695
|
-
values.each do |item|
|
696
|
-
|
697
|
-
if item.is_a?(HappyMapper)
|
698
|
-
|
699
|
-
#
|
700
|
-
# Other items are convertable to xml through the xml builder
|
701
|
-
# process should have their contents retrieved and attached
|
702
|
-
# to the builder structure
|
703
|
-
#
|
704
|
-
item.to_xml(xml, self.class.namespace || default_namespace,
|
705
|
-
element.options[:namespace],
|
706
|
-
element.options[:tag] || nil)
|
707
|
-
|
708
|
-
elsif !item.nil?
|
709
|
-
|
710
|
-
item_namespace = element.options[:namespace] || self.class.namespace || default_namespace
|
711
|
-
|
712
|
-
#
|
713
|
-
# When a value exists we should append the value for the tag
|
714
|
-
#
|
715
|
-
if item_namespace
|
716
|
-
xml[item_namespace].send("#{tag}_",item.to_s)
|
717
|
-
else
|
718
|
-
xml.send("#{tag}_",item.to_s)
|
719
|
-
end
|
720
|
-
|
721
|
-
else
|
722
|
-
|
723
|
-
#
|
724
|
-
# Normally a nil value would be ignored, however if specified then
|
725
|
-
# an empty element will be written to the xml
|
726
|
-
#
|
727
|
-
xml.send("#{tag}_","") if element.options[:state_when_nil]
|
728
|
-
|
729
|
-
end
|
730
|
-
|
731
|
-
end
|
732
|
-
|
733
|
-
end
|
734
|
-
end
|
735
|
-
|
736
|
-
end
|
737
|
-
|
738
|
-
# Write out to XML, this value was set above, based on whether or not an XML
|
739
|
-
# builder object was passed to it as a parameter. When there was no parameter
|
740
|
-
# we assume we are at the root level of the #to_xml call and want the actual
|
741
|
-
# xml generated from the object. If an XML builder instance was specified
|
742
|
-
# then we assume that has been called recursively to generate a larger
|
743
|
-
# XML document.
|
744
|
-
write_out_to_xml ? builder.to_xml : builder
|
745
|
-
|
746
|
-
end
|
747
|
-
|
748
|
-
# Parse the xml and update this instance. This does not update instances
|
749
|
-
# of HappyMappers that are children of this object. New instances will be
|
750
|
-
# created for any HappyMapper children of this object.
|
751
|
-
#
|
752
|
-
# Params and return are the same as the class parse() method above.
|
753
|
-
def parse(xml, options = {})
|
754
|
-
self.class.parse(xml, options.merge!(:update => self))
|
755
|
-
end
|
756
|
-
|
757
|
-
private
|
758
|
-
|
759
|
-
# Factory for creating anonmyous HappyMappers
|
760
|
-
class AnonymousWrapperClassFactory
|
761
|
-
def self.get(name, &blk)
|
762
|
-
Class.new do
|
763
|
-
include HappyMapper
|
764
|
-
tag name
|
765
|
-
instance_eval &blk
|
766
|
-
end
|
767
|
-
end
|
768
|
-
end
|
769
|
-
|
770
|
-
end
|
771
|
-
|
772
|
-
require 'happymapper/supported_types'
|
773
|
-
require 'happymapper/item'
|
774
|
-
require 'happymapper/attribute'
|
775
|
-
require 'happymapper/element'
|
776
|
-
require 'happymapper/text_node'
|