happymapper-swanandp 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/License +20 -0
- data/README.rdoc +55 -0
- data/Rakefile +35 -0
- data/examples/amazon.rb +34 -0
- data/examples/current_weather.rb +21 -0
- data/examples/dashed_elements.rb +20 -0
- data/examples/multi_street_address.rb +30 -0
- data/examples/post.rb +19 -0
- data/examples/twitter.rb +37 -0
- data/lib/happymapper.rb +321 -0
- data/lib/happymapper/attribute.rb +3 -0
- data/lib/happymapper/element.rb +3 -0
- data/lib/happymapper/item.rb +179 -0
- data/lib/happymapper/version.rb +3 -0
- data/spec/fixtures/address.xml +8 -0
- data/spec/fixtures/analytics.xml +61 -0
- data/spec/fixtures/commit.xml +52 -0
- data/spec/fixtures/current_weather.xml +89 -0
- data/spec/fixtures/family_tree.xml +7 -0
- data/spec/fixtures/multi_street_address.xml +9 -0
- data/spec/fixtures/multiple_namespaces.xml +170 -0
- data/spec/fixtures/nested_namespaces.xml +17 -0
- data/spec/fixtures/notes.xml +9 -0
- data/spec/fixtures/pita.xml +133 -0
- data/spec/fixtures/posts.xml +23 -0
- data/spec/fixtures/product_default_namespace.xml +10 -0
- data/spec/fixtures/product_no_namespace.xml +10 -0
- data/spec/fixtures/product_single_namespace.xml +10 -0
- data/spec/fixtures/radar.xml +21 -0
- data/spec/fixtures/raw.xml +9 -0
- data/spec/fixtures/statuses.xml +422 -0
- data/spec/happymapper_attribute_spec.rb +17 -0
- data/spec/happymapper_element_spec.rb +17 -0
- data/spec/happymapper_item_spec.rb +115 -0
- data/spec/happymapper_spec.rb +404 -0
- data/spec/happymapper_to_xml_namespaces_spec.rb +149 -0
- data/spec/happymapper_to_xml_spec.rb +138 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/models.rb +323 -0
- metadata +121 -0
data/License
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 John Nunemaker
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
= happymapper
|
2
|
+
|
3
|
+
== DESCRIPTION:
|
4
|
+
|
5
|
+
XML to object mapping library. I have included examples to help get you going. The specs should also point you in the right direction.
|
6
|
+
|
7
|
+
== FEATURES:
|
8
|
+
|
9
|
+
* Easy to define xml attributes and elements for an object
|
10
|
+
* Fast because it uses libxml-ruby under the hood
|
11
|
+
* Automatic conversion of xml to defined objects
|
12
|
+
|
13
|
+
== EXAMPLES:
|
14
|
+
|
15
|
+
Here is a simple example that maps Twitter statuses and users.
|
16
|
+
|
17
|
+
class User
|
18
|
+
include HappyMapper
|
19
|
+
|
20
|
+
element :id, Integer
|
21
|
+
element :name, String
|
22
|
+
element :screen_name, String
|
23
|
+
element :location, String
|
24
|
+
element :description, String
|
25
|
+
element :profile_image_url, String
|
26
|
+
element :url, String
|
27
|
+
element :protected, Boolean
|
28
|
+
element :followers_count, Integer
|
29
|
+
end
|
30
|
+
|
31
|
+
class Status
|
32
|
+
include HappyMapper
|
33
|
+
|
34
|
+
element :id, Integer
|
35
|
+
element :text, String
|
36
|
+
element :created_at, Time
|
37
|
+
element :source, String
|
38
|
+
element :truncated, Boolean
|
39
|
+
element :in_reply_to_status_id, Integer
|
40
|
+
element :in_reply_to_user_id, Integer
|
41
|
+
element :favorited, Boolean
|
42
|
+
has_one :user, User
|
43
|
+
end
|
44
|
+
|
45
|
+
See examples directory in the gem for more examples.
|
46
|
+
|
47
|
+
http://github.com/jnunemaker/happymapper/tree/master/examples/
|
48
|
+
|
49
|
+
== INSTALL:
|
50
|
+
|
51
|
+
* gem install happymapper
|
52
|
+
|
53
|
+
== DOCS:
|
54
|
+
|
55
|
+
http://rdoc.info/projects/jnunemaker/happymapper
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
require File.expand_path('../lib/happymapper/version', __FILE__)
|
7
|
+
|
8
|
+
Spec::Rake::SpecTask.new do |t|
|
9
|
+
t.ruby_opts << '-rubygems'
|
10
|
+
t.verbose = true
|
11
|
+
end
|
12
|
+
task :default => :spec
|
13
|
+
|
14
|
+
desc 'Builds the gem'
|
15
|
+
task :build do
|
16
|
+
sh "gem build happymapper.gemspec"
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Builds and installs the gem'
|
20
|
+
task :install => :build do
|
21
|
+
sh "gem install happymapper-#{HappyMapper::Version}"
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Tags version, pushes to remote, and pushes gem'
|
25
|
+
task :release => :build do
|
26
|
+
sh "git tag v#{HappyMapper::Version}"
|
27
|
+
sh "git push origin master"
|
28
|
+
sh "git push origin v#{HappyMapper::Version}"
|
29
|
+
sh "gem push happymapper-#{HappyMapper::Version}.gem"
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Upload website files to rubyforge'
|
33
|
+
task :website do
|
34
|
+
sh %{rsync -av website/ jnunemaker@rubyforge.org:/var/www/gforge-projects/happymapper}
|
35
|
+
end
|
data/examples/amazon.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require File.join(dir, 'happymapper')
|
3
|
+
|
4
|
+
file_contents = File.read(dir + '/../spec/fixtures/pita.xml')
|
5
|
+
|
6
|
+
# The document `pita.xml` contains both a default namespace and the 'georss'
|
7
|
+
# namespace (for the 'point' element).
|
8
|
+
module PITA
|
9
|
+
class Item
|
10
|
+
include HappyMapper
|
11
|
+
|
12
|
+
tag 'Item' # if you put class in module you need tag
|
13
|
+
element :asin, String, :tag => 'ASIN'
|
14
|
+
element :detail_page_url, String, :tag => 'DetailPageURL'
|
15
|
+
element :manufacturer, String, :tag => 'Manufacturer', :deep => true
|
16
|
+
# this is the only element that exists in a different namespace, so it
|
17
|
+
# must be explicitly specified
|
18
|
+
element :point, String, :tag => 'point', :namespace => 'georss'
|
19
|
+
end
|
20
|
+
|
21
|
+
class Items
|
22
|
+
include HappyMapper
|
23
|
+
|
24
|
+
tag 'Items' # if you put class in module you need tag
|
25
|
+
element :total_results, Integer, :tag => 'TotalResults'
|
26
|
+
element :total_pages, Integer, :tag => 'TotalPages'
|
27
|
+
has_many :items, Item
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
item = PITA::Items.parse(file_contents, :single => true)
|
32
|
+
item.items.each do |i|
|
33
|
+
puts i.asin, i.detail_page_url, i.manufacturer, ''
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require File.join(dir, 'happymapper')
|
3
|
+
|
4
|
+
file_contents = File.read(dir + '/../spec/fixtures/current_weather.xml')
|
5
|
+
|
6
|
+
class CurrentWeather
|
7
|
+
include HappyMapper
|
8
|
+
|
9
|
+
tag 'ob'
|
10
|
+
namespace 'http://www.aws.com/aws'
|
11
|
+
element :temperature, Integer, :tag => 'temp'
|
12
|
+
element :feels_like, Integer, :tag => 'feels-like'
|
13
|
+
element :current_condition, String, :tag => 'current-condition', :attributes => {:icon => String}
|
14
|
+
end
|
15
|
+
|
16
|
+
CurrentWeather.parse(file_contents).each do |current_weather|
|
17
|
+
puts "temperature: #{current_weather.temperature}"
|
18
|
+
puts "feels_like: #{current_weather.feels_like}"
|
19
|
+
puts "current_condition: #{current_weather.current_condition}"
|
20
|
+
puts "current_condition.icon: #{current_weather.current_condition.icon}"
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require File.join(dir, 'happymapper')
|
3
|
+
|
4
|
+
file_contents = File.read(dir + '/../spec/fixtures/commit.xml')
|
5
|
+
|
6
|
+
module GitHub
|
7
|
+
class Commit
|
8
|
+
include HappyMapper
|
9
|
+
|
10
|
+
tag "commit"
|
11
|
+
element :url, String
|
12
|
+
element :tree, String
|
13
|
+
element :message, String
|
14
|
+
element :id, String
|
15
|
+
element :'committed-date', Date
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
commit = GitHub::Commit.parse(file_contents)
|
20
|
+
puts commit.committed_date, commit.url, commit.id
|
@@ -0,0 +1,30 @@
|
|
1
|
+
dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require File.join(dir, 'happymapper')
|
3
|
+
|
4
|
+
file_contents = File.read(dir + '/../spec/fixtures/multi_street_address.xml')
|
5
|
+
|
6
|
+
class MultiStreetAddress
|
7
|
+
include HappyMapper
|
8
|
+
|
9
|
+
tag 'address'
|
10
|
+
|
11
|
+
# allow primitive type to be collection
|
12
|
+
has_many :street_address, String, :tag => "streetaddress"
|
13
|
+
element :city, String
|
14
|
+
element :state_or_province, String, :tag => "stateOrProvince"
|
15
|
+
element :zip, String
|
16
|
+
element :country, String
|
17
|
+
end
|
18
|
+
|
19
|
+
multi = MultiStreetAddress.parse(file_contents)
|
20
|
+
|
21
|
+
puts "Street Address:"
|
22
|
+
|
23
|
+
multi.street_address.each do |street|
|
24
|
+
puts street
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "City: #{multi.city}"
|
28
|
+
puts "State/Province: #{multi.state_or_province}"
|
29
|
+
puts "Zip: #{multi.zip}"
|
30
|
+
puts "Country: #{multi.country}"
|
data/examples/post.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require File.join(dir, 'happymapper')
|
3
|
+
|
4
|
+
file_contents = File.read(dir + '/../spec/fixtures/posts.xml')
|
5
|
+
|
6
|
+
class Post
|
7
|
+
include HappyMapper
|
8
|
+
|
9
|
+
attribute :href, String
|
10
|
+
attribute :hash, String
|
11
|
+
attribute :description, String
|
12
|
+
attribute :tag, String
|
13
|
+
attribute :time, DateTime
|
14
|
+
attribute :others, Integer
|
15
|
+
attribute :extended, String
|
16
|
+
end
|
17
|
+
|
18
|
+
posts = Post.parse(file_contents)
|
19
|
+
posts.each { |post| puts post.description, post.href, post.extended, '' }
|
data/examples/twitter.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require File.join(dir, 'happymapper')
|
3
|
+
|
4
|
+
file_contents = File.read(dir + '/../spec/fixtures/statuses.xml')
|
5
|
+
|
6
|
+
class User
|
7
|
+
include HappyMapper
|
8
|
+
|
9
|
+
element :id, Integer
|
10
|
+
element :name, String
|
11
|
+
element :screen_name, String
|
12
|
+
element :location, String
|
13
|
+
element :description, String
|
14
|
+
element :profile_image_url, String
|
15
|
+
element :url, String
|
16
|
+
element :protected, Boolean
|
17
|
+
element :followers_count, Integer
|
18
|
+
end
|
19
|
+
|
20
|
+
class Status
|
21
|
+
include HappyMapper
|
22
|
+
|
23
|
+
element :id, Integer
|
24
|
+
element :text, String
|
25
|
+
element :created_at, Time
|
26
|
+
element :source, String
|
27
|
+
element :truncated, Boolean
|
28
|
+
element :in_reply_to_status_id, Integer
|
29
|
+
element :in_reply_to_user_id, Integer
|
30
|
+
element :favorited, Boolean
|
31
|
+
has_one :user, User
|
32
|
+
end
|
33
|
+
|
34
|
+
statuses = Status.parse(file_contents)
|
35
|
+
statuses.each do |status|
|
36
|
+
puts status.user.name, status.user.screen_name, status.text, status.source, ''
|
37
|
+
end
|
data/lib/happymapper.rb
ADDED
@@ -0,0 +1,321 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
require 'xml'
|
5
|
+
|
6
|
+
class Boolean; end
|
7
|
+
|
8
|
+
module HappyMapper
|
9
|
+
|
10
|
+
DEFAULT_NS = "happymapper"
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.instance_variable_set("@attributes", {})
|
14
|
+
base.instance_variable_set("@elements", {})
|
15
|
+
base.instance_variable_set("@registered_namespaces", {})
|
16
|
+
|
17
|
+
base.extend ClassMethods
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
def attribute(name, type, options={})
|
22
|
+
attribute = Attribute.new(name, type, options)
|
23
|
+
@attributes[to_s] ||= []
|
24
|
+
@attributes[to_s] << attribute
|
25
|
+
attr_accessor attribute.method_name.intern
|
26
|
+
end
|
27
|
+
|
28
|
+
def attributes
|
29
|
+
@attributes[to_s] || []
|
30
|
+
end
|
31
|
+
|
32
|
+
def element(name, type, options={})
|
33
|
+
element = Element.new(name, type, options)
|
34
|
+
@elements[to_s] ||= []
|
35
|
+
@elements[to_s] << element
|
36
|
+
attr_accessor element.method_name.intern
|
37
|
+
end
|
38
|
+
|
39
|
+
def content(name)
|
40
|
+
@content = name
|
41
|
+
attr_accessor name
|
42
|
+
end
|
43
|
+
|
44
|
+
def raw_content(name)
|
45
|
+
@raw_content = name
|
46
|
+
attr_accessor name
|
47
|
+
end
|
48
|
+
|
49
|
+
def after_parse_callbacks
|
50
|
+
@after_parse_callbacks ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
def after_parse(&block)
|
54
|
+
after_parse_callbacks.push(block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def elements
|
58
|
+
@elements[to_s] || []
|
59
|
+
end
|
60
|
+
|
61
|
+
def has_one(name, type, options={})
|
62
|
+
element name, type, {:single => true}.merge(options)
|
63
|
+
end
|
64
|
+
|
65
|
+
def has_many(name, type, options={})
|
66
|
+
element name, type, {:single => false}.merge(options)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Specify a namespace if a node and all its children are all namespaced
|
70
|
+
# elements. This is simpler than passing the :namespace option to each
|
71
|
+
# defined element.
|
72
|
+
def namespace(namespace = nil)
|
73
|
+
@namespace = namespace if namespace
|
74
|
+
@namespace
|
75
|
+
end
|
76
|
+
|
77
|
+
def register_namespace(namespace, ns)
|
78
|
+
@registered_namespaces.merge!(namespace => ns)
|
79
|
+
end
|
80
|
+
|
81
|
+
def tag(new_tag_name)
|
82
|
+
@tag_name = new_tag_name.to_s
|
83
|
+
end
|
84
|
+
|
85
|
+
def tag_name
|
86
|
+
@tag_name ||= to_s.split('::')[-1].downcase
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse(xml, options = {})
|
90
|
+
if xml.is_a?(XML::Node)
|
91
|
+
node = xml
|
92
|
+
else
|
93
|
+
if xml.is_a?(XML::Document)
|
94
|
+
node = xml.root
|
95
|
+
else
|
96
|
+
node = XML::Parser.string(xml).parse.root
|
97
|
+
end
|
98
|
+
|
99
|
+
root = node.name == tag_name
|
100
|
+
end
|
101
|
+
|
102
|
+
namespace = @namespace || (node.namespaces && node.namespaces.default)
|
103
|
+
namespace = "#{DEFAULT_NS}:#{namespace}" if namespace
|
104
|
+
|
105
|
+
xpath = root ? '/' : './/'
|
106
|
+
xpath += "#{DEFAULT_NS}:" if namespace
|
107
|
+
xpath += tag_name
|
108
|
+
|
109
|
+
nodes = node.find(xpath, Array(namespace))
|
110
|
+
collection = nodes.collect do |n|
|
111
|
+
obj = new
|
112
|
+
|
113
|
+
attributes.each do |attr|
|
114
|
+
obj.send("#{attr.method_name}=",
|
115
|
+
attr.from_xml_node(n, namespace))
|
116
|
+
end
|
117
|
+
|
118
|
+
elements.each do |elem|
|
119
|
+
obj.send("#{elem.method_name}=",
|
120
|
+
elem.from_xml_node(n, namespace))
|
121
|
+
end
|
122
|
+
|
123
|
+
obj.send("#{@content}=", n.content) if @content
|
124
|
+
obj.send("#{@raw_content}=", n.inner_xml) if @raw_content
|
125
|
+
|
126
|
+
obj.class.after_parse_callbacks.each { |callback| callback.call(obj) }
|
127
|
+
|
128
|
+
obj
|
129
|
+
end
|
130
|
+
|
131
|
+
# per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
|
132
|
+
nodes = nil
|
133
|
+
|
134
|
+
if options[:single] || root
|
135
|
+
collection.first
|
136
|
+
else
|
137
|
+
collection
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Create an xml representation of the specified class based on defined
|
145
|
+
# HappyMapper elements and attributes. The method is defined in a way
|
146
|
+
# that it can be called recursively by classes that are also HappyMapper
|
147
|
+
# classes, allowg for the composition of classes.
|
148
|
+
#
|
149
|
+
def to_xml(parent_node = nil, default_namespace = nil)
|
150
|
+
|
151
|
+
#
|
152
|
+
# Create a tag that uses the tag name of the class that has no contents
|
153
|
+
# but has the specified namespace or uses the default namespace
|
154
|
+
#
|
155
|
+
current_node = XML::Node.new(self.class.tag_name)
|
156
|
+
|
157
|
+
|
158
|
+
if parent_node
|
159
|
+
#
|
160
|
+
# if #to_xml has been called with a parent_node that means this method
|
161
|
+
# is being called recursively (or a special case) and we want to return
|
162
|
+
# the parent_node with the new node as a child
|
163
|
+
#
|
164
|
+
parent_node << current_node
|
165
|
+
else
|
166
|
+
#
|
167
|
+
# If #to_xml has been called without a Node (and namespace) that
|
168
|
+
# means we want to return an xml document
|
169
|
+
#
|
170
|
+
write_out_to_xml = true
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Add all the registered namespaces to the current node and the current node's
|
175
|
+
# root element. Without adding it to the root element it is not possible to
|
176
|
+
# parse or use xpath to find elements.
|
177
|
+
#
|
178
|
+
if self.class.instance_variable_get('@registered_namespaces')
|
179
|
+
|
180
|
+
# Given a node, continue moving up to parents until there are no more parents
|
181
|
+
find_root_node = lambda {|node| while node.parent? ; node = node.parent ; end ; node }
|
182
|
+
root_node = find_root_node.call(current_node)
|
183
|
+
|
184
|
+
# Add the registered namespace to the found root node only if it does not already have one defined
|
185
|
+
self.class.instance_variable_get('@registered_namespaces').each_pair do |prefix,href|
|
186
|
+
XML::Namespace.new(current_node,prefix,href)
|
187
|
+
XML::Namespace.new(root_node,prefix,href) unless root_node.namespaces.find_by_prefix(prefix)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
#
|
192
|
+
# Determine the tag namespace if one has been specified. This value takes
|
193
|
+
# precendence over one that is handed down to composed sub-classes.
|
194
|
+
#
|
195
|
+
tag_namespace = current_node.namespaces.find_by_prefix(self.class.namespace) || default_namespace
|
196
|
+
|
197
|
+
# Set the namespace of the current node to the specified namespace
|
198
|
+
current_node.namespaces.namespace = tag_namespace if tag_namespace
|
199
|
+
|
200
|
+
#
|
201
|
+
# Add all the attribute tags to the current node with their namespace, if one
|
202
|
+
# is defined, or the namespace handed down to the node.
|
203
|
+
#
|
204
|
+
self.class.attributes.each do |attribute|
|
205
|
+
attribute_namespace = current_node.namespaces.find_by_prefix(attribute.options[:namespace]) || default_namespace
|
206
|
+
|
207
|
+
value = send(attribute.method_name)
|
208
|
+
|
209
|
+
#
|
210
|
+
# If the attribute has a :on_save attribute defined that is a proc or
|
211
|
+
# a defined method, then call those with the current value.
|
212
|
+
#
|
213
|
+
if on_save_operation = attribute.options[:on_save]
|
214
|
+
if on_save_operation.is_a?(Proc)
|
215
|
+
value = on_save_operation.call(value)
|
216
|
+
elsif respond_to?(on_save_operation)
|
217
|
+
value = send(on_save_operation,value)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
current_node[ "#{attribute_namespace ? "#{attribute_namespace.prefix}:" : ""}#{attribute.tag}" ] = value
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# All all the elements defined (e.g. has_one, has_many, element) ...
|
226
|
+
#
|
227
|
+
self.class.elements.each do |element|
|
228
|
+
|
229
|
+
tag = element.tag || element.name
|
230
|
+
|
231
|
+
element_namespace = current_node.namespaces.find_by_prefix(element.options[:namespace]) || tag_namespace
|
232
|
+
|
233
|
+
value = send(element.name)
|
234
|
+
|
235
|
+
#
|
236
|
+
# If the element defines an :on_save lambda/proc then we will call that
|
237
|
+
# operation on the specified value. This allows for operations to be
|
238
|
+
# performed to convert the value to a specific value to be saved to the xml.
|
239
|
+
#
|
240
|
+
if on_save_operation = element.options[:on_save]
|
241
|
+
if on_save_operation.is_a?(Proc)
|
242
|
+
value = on_save_operation.call(value)
|
243
|
+
elsif respond_to?(on_save_operation)
|
244
|
+
value = send(on_save_operation,value)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# Normally a nil value would be ignored, however if specified then
|
250
|
+
# an empty element will be written to the xml
|
251
|
+
#
|
252
|
+
if value.nil? && element.options[:state_when_nil]
|
253
|
+
current_node << XML::Node.new(tag,nil,element_namespace)
|
254
|
+
end
|
255
|
+
|
256
|
+
#
|
257
|
+
# To allow for us to treat both groups of items and singular items
|
258
|
+
# equally we wrap the value and treat it as an array.
|
259
|
+
#
|
260
|
+
if value.nil?
|
261
|
+
values = []
|
262
|
+
elsif value.respond_to?(:to_ary) && !element.options[:single]
|
263
|
+
values = value.to_ary
|
264
|
+
else
|
265
|
+
values = [value]
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
values.each do |item|
|
270
|
+
|
271
|
+
if item.is_a?(HappyMapper)
|
272
|
+
|
273
|
+
#
|
274
|
+
# Other HappyMapper items that are convertable should not be called
|
275
|
+
# with the current node and the namespace defined for the element.
|
276
|
+
#
|
277
|
+
item.to_xml(current_node,element_namespace)
|
278
|
+
|
279
|
+
elsif item
|
280
|
+
|
281
|
+
#
|
282
|
+
# When a value exists we should append the value for the tag
|
283
|
+
#
|
284
|
+
current_node << XML::Node.new(tag,item.to_s,element_namespace)
|
285
|
+
|
286
|
+
else
|
287
|
+
|
288
|
+
#
|
289
|
+
# Normally a nil value would be ignored, however if specified then
|
290
|
+
# an empty element will be written to the xml
|
291
|
+
#
|
292
|
+
current_node << XML.Node.new(tag,nil,element_namespace) if element.options[:state_when_nil]
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
#
|
302
|
+
# Generate xml from a document if no node was passed as a parameter. Otherwise
|
303
|
+
# this method is being called recursively (or special case) and we should
|
304
|
+
# return the node with this node attached as a child.
|
305
|
+
#
|
306
|
+
if write_out_to_xml
|
307
|
+
document = XML::Document.new
|
308
|
+
document.root = current_node
|
309
|
+
document.to_s
|
310
|
+
else
|
311
|
+
parent_node
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
require File.dirname(__FILE__) + '/happymapper/item'
|
320
|
+
require File.dirname(__FILE__) + '/happymapper/attribute'
|
321
|
+
require File.dirname(__FILE__) + '/happymapper/element'
|