voruby2-preview 1.0.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 +339 -0
- data/Rakefile.rb +113 -0
- data/lib/symphony.rb +1 -0
- data/lib/symphony/symphony.rb +247 -0
- data/lib/voruby.rb +186 -0
- data/lib/voruby/misc/libxml_ext.rb +121 -0
- data/lib/voruby/misc/rexml_ext.rb +223 -0
- data/lib/voruby/resolver/resolver.rb +12 -0
- data/lib/voruby/resolver/sesame.rb +299 -0
- data/lib/voruby/votable/1.0/votable.rb +1807 -0
- data/lib/voruby/votable/1.1/votable.rb +2100 -0
- data/lib/voruby/votable/votable.rb +308 -0
- data/lib/voruby/wesix/wesix.rb +491 -0
- data/test/voruby/resolver/sesame/test.rb +56 -0
- data/test/voruby/votable/1.0/test.rb +714 -0
- data/test/voruby/votable/1.0/votable.basic.xml +660 -0
- data/test/voruby/votable/1.0/votable.html +86 -0
- data/test/voruby/votable/1.0/votable.ns.xml +56 -0
- data/test/voruby/votable/1.1/test.rb +785 -0
- data/test/voruby/votable/1.1/votable.basic.xml +38 -0
- data/test/voruby/votable/1.1/votable.html +86 -0
- data/test/voruby/votable/1.1/votable.large.xml +168492 -0
- data/test/voruby/votable/1.1/votable.ns.xml +56 -0
- data/test/voruby/votable/test.rb +15 -0
- data/test/voruby/wesix/test.rb +268 -0
- data/test/voruby/wesix/testr.fits +28 -0
- metadata +119 -0
data/lib/symphony.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'symphony/symphony'
|
@@ -0,0 +1,247 @@
|
|
1
|
+
class HomogeneousArray < Array
|
2
|
+
attr_reader :klass
|
3
|
+
|
4
|
+
def initialize(of_class, *spec)
|
5
|
+
of_class = [of_class] if !of_class.is_a?(Array)
|
6
|
+
@klass = of_class
|
7
|
+
|
8
|
+
spec.flatten.each { |obj| raise_type_error(obj) if !self.acceptable_type?(obj) }
|
9
|
+
|
10
|
+
super(*spec)
|
11
|
+
end
|
12
|
+
|
13
|
+
def raise_type_error(obj)
|
14
|
+
raise TypeError, "wrong argument type of #{obj.class} #{obj.inspect} (expected #{self.klass})"
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(*assign_spec)
|
18
|
+
raise_type_error(assign_spec.last) if !self.acceptable_type?(assign_spec.last)
|
19
|
+
super(*assign_spec)
|
20
|
+
end
|
21
|
+
|
22
|
+
def <<(obj)
|
23
|
+
raise_type_error(obj) if !self.acceptable_type?(obj)
|
24
|
+
super(obj)
|
25
|
+
end
|
26
|
+
|
27
|
+
def replace(array_of_obj)
|
28
|
+
array_of_obj.each { |obj| raise_type_error(obj) if !self.acceptable_type?(obj) }
|
29
|
+
super(array_of_obj)
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(ary)
|
33
|
+
return false if !ary.respond_to?(:size)
|
34
|
+
return false if self.size != ary.size
|
35
|
+
|
36
|
+
self.each_with_index do |el, i|
|
37
|
+
return false if !(el == ary[i])
|
38
|
+
end
|
39
|
+
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
def acceptable_type?(obj)
|
45
|
+
self.klass.find_all{ |k| obj.is_a?(k) }.size > 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class MinimumSizedHomogeneousArray < HomogeneousArray
|
50
|
+
attr_reader :min_size
|
51
|
+
|
52
|
+
def initialize(min_size, of_class, *spec)
|
53
|
+
raise_size_error(spec.last.size, min_size) if spec.last.size < min_size
|
54
|
+
|
55
|
+
super(of_class, *spec)
|
56
|
+
@min_size = min_size
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete(obj)
|
60
|
+
raise_size_error(self.size - 1, self.min_size) if self.size - 1 < self.min_size
|
61
|
+
super(obj)
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete_at(index)
|
65
|
+
raise_size_error(self.size - 1, self.min_size) if self.size - 1 < self.min_size
|
66
|
+
super(index)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def raise_size_error(size, min_size=nil)
|
71
|
+
raise ArgumentError, "array of size #{size} can not shrink below a capacity of #{min_size || self.min_size}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class AtLeastTwoArray < MinimumSizedHomogeneousArray
|
76
|
+
def initialize(of_class, *spec)
|
77
|
+
super(2, of_class, *spec)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class FixedSizedHomogeneousArray < HomogeneousArray
|
82
|
+
attr_reader :capacity
|
83
|
+
|
84
|
+
def initialize(capacity, of_class, *spec)
|
85
|
+
raise_capacity_error(spec.last.size, capacity) if spec.last.size != capacity
|
86
|
+
|
87
|
+
super(of_class, *spec)
|
88
|
+
@capacity = capacity
|
89
|
+
end
|
90
|
+
|
91
|
+
def []=(*assign_spec)
|
92
|
+
super(*assign_spec)
|
93
|
+
raise_capacity_error(self.size) if self.size != self.capacity
|
94
|
+
end
|
95
|
+
|
96
|
+
def <<(obj)
|
97
|
+
raise_capacity_error(self.size + 1) if self.size + 1 != self.capacity
|
98
|
+
super(obj)
|
99
|
+
end
|
100
|
+
|
101
|
+
def replace(array_of_obj)
|
102
|
+
aise_capacity_error(self.size + array_of_obj.size) if self.size + array_of_obj.size != self.capacity
|
103
|
+
super(array_of_obj)
|
104
|
+
end
|
105
|
+
|
106
|
+
def delete(obj); end
|
107
|
+
def delete_at(index); end
|
108
|
+
|
109
|
+
private
|
110
|
+
def raise_capacity_error(size, capacity=nil)
|
111
|
+
raise ArgumentError, "array incorrect size of #{size} (expected #{capacity || self.capacity})"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class Array
|
116
|
+
def to_homogeneous(of_type)
|
117
|
+
harray = HomogeneousArray.new(of_type)
|
118
|
+
self.each{ |item| harray << item }
|
119
|
+
harray
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
module Symphony
|
124
|
+
module ClassMethods
|
125
|
+
DEFAULT_OPTIONS = {
|
126
|
+
:of => Object,
|
127
|
+
:optional => false,
|
128
|
+
:nillable => false
|
129
|
+
}
|
130
|
+
|
131
|
+
attr_reader :symphony_definitions
|
132
|
+
|
133
|
+
def initialize_symphony_definitions
|
134
|
+
@symphony_definitions ||= []
|
135
|
+
end
|
136
|
+
|
137
|
+
def has(multiplicity, name, options={})
|
138
|
+
initialize_symphony_definitions()
|
139
|
+
|
140
|
+
raise ArgumentError, "multiplicity must be an integer or a symbol (i.e. :many)" if !multiplicity.is_a?(Integer) and !multiplicity.is_a?(Symbol)
|
141
|
+
|
142
|
+
options = DEFAULT_OPTIONS.merge(options)
|
143
|
+
defn = {:multiplicity => multiplicity, :method_name => name.to_sym, :options => options}
|
144
|
+
@symphony_definitions << defn
|
145
|
+
|
146
|
+
create_methods(defn)
|
147
|
+
end
|
148
|
+
|
149
|
+
def has_one(name, options)
|
150
|
+
has 1, name, options
|
151
|
+
end
|
152
|
+
|
153
|
+
def has_many(name, options)
|
154
|
+
has :many, name, options
|
155
|
+
end
|
156
|
+
|
157
|
+
def create_methods(defn)
|
158
|
+
# reader
|
159
|
+
class_eval "
|
160
|
+
def #{defn[:method_name]}
|
161
|
+
puts '#{defn[:method_name]}:' + self.class.symphony_definitions.find{ |d| d[:method_name] == :#{defn[:method_name]} }.inspect
|
162
|
+
@#{defn[:method_name]} || self.class.symphony_definitions.find{ |d| d[:method_name] == :#{defn[:method_name]} }[:options][:default]
|
163
|
+
end
|
164
|
+
"
|
165
|
+
|
166
|
+
# writer
|
167
|
+
check_method = (defn[:multiplicity] == :many or defn[:multiplicity] > 1) ? 'check_made_of_only' : 'check_type'
|
168
|
+
class_eval "
|
169
|
+
def #{defn[:method_name]}=(obj)
|
170
|
+
raise TypeError, \"#{defn[:method_name]} is not nillable\" if !#{defn[:options][:nillable]} and obj == nil
|
171
|
+
#{check_method}(#{defn[:options][:of]}, obj) if obj != nil or !#{defn[:options][:nillable]}
|
172
|
+
|
173
|
+
@#{defn[:method_name]} = obj
|
174
|
+
end
|
175
|
+
"
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
class << self
|
181
|
+
def included(klass)
|
182
|
+
super
|
183
|
+
klass.__send__(:extend, ClassMethods)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Symphony::Base
|
189
|
+
include Symphony
|
190
|
+
|
191
|
+
def initialize(args={})
|
192
|
+
self.class.initialize_symphony_definitions()
|
193
|
+
validate(args)
|
194
|
+
assign_initial_values(args)
|
195
|
+
end
|
196
|
+
|
197
|
+
def ==(obj)
|
198
|
+
return false if !obj.is_a?(self.class)
|
199
|
+
|
200
|
+
self.class.symphony_definitions.each do |defn|
|
201
|
+
return false if self.send(defn[:method_name]) != obj.send(defn[:method_name])
|
202
|
+
end
|
203
|
+
|
204
|
+
return true
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def check_type(klass, o)
|
210
|
+
raise TypeError, "wrong argument type of #{o.class} #{o.inspect} (expected #{klass})" if !o.is_a?(klass)
|
211
|
+
end
|
212
|
+
|
213
|
+
def check_made_of_only(klass, list)
|
214
|
+
list.each { |o| check_type(klass, o) }
|
215
|
+
end
|
216
|
+
|
217
|
+
def validate(args)
|
218
|
+
self.class.symphony_definitions.each do |defn|
|
219
|
+
raise ArgumentError, "#{defn[:method_name]} is missing but is required" if !defn[:options][:optional] and !args.has_key?(defn[:method_name])
|
220
|
+
raise TypeError, "#{defn[:method_name]} is not nillable" if args.has_key?(defn[:method_name]) and args[defn[:method_name]] == nil and !defn[:options][:nillable]
|
221
|
+
|
222
|
+
values = args[defn[:method_name]]
|
223
|
+
values = values.is_a?(Array) ? values : [values]
|
224
|
+
|
225
|
+
# if defn[:multiplicity].is_a?(Integer)
|
226
|
+
# raise ArgumentError, "wrong number of arguments for #{defn[:method_name]} (#{values.size} for #{defn[:multiplicity]})" if values.size != defn[:multiplicity]
|
227
|
+
# elsif defn[:multiplicity].is_a?(Symbol)
|
228
|
+
# raise ArgumentError, "don't know what a multiplicity of #{defn[:multiplicity]} means" if defn[:multiplicity] != :many
|
229
|
+
# end
|
230
|
+
|
231
|
+
raise ArgumentError, "don't know what a multiplicity of #{defn[:multiplicity]} means" if !defn[:multiplicity].is_a?(Integer) and defn[:multiplicity] != :many
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def assign_initial_values(args)
|
236
|
+
self.class.symphony_definitions.each do |defn|
|
237
|
+
values = if !args[defn[:method_name]].is_a?(HomogeneousArray) and args[defn[:method_name]].is_a?(Array)
|
238
|
+
args[defn[:method_name]].to_homogeneous(defn[:options][:of])
|
239
|
+
else
|
240
|
+
check_type(defn[:options][:of], args[defn[:method_name]]) if !defn[:options][:optional] and (!defn[:options][:nillable] and args[defn[:method_name]] != nil)
|
241
|
+
args[defn[:method_name]]
|
242
|
+
end
|
243
|
+
|
244
|
+
send("#{defn[:method_name]}=", values)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
data/lib/voruby.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
module VORuby
|
2
|
+
begin
|
3
|
+
require 'rfits'
|
4
|
+
rescue LoadError
|
5
|
+
@@rfits_available = false
|
6
|
+
else
|
7
|
+
@@rfits_available = true
|
8
|
+
end
|
9
|
+
|
10
|
+
# Reports whether the RFits[http://rubyforge.org/projects/rfits]
|
11
|
+
# FITS reading/writing package is installed or not.
|
12
|
+
def self.rfits?
|
13
|
+
@@rfits_available
|
14
|
+
end
|
15
|
+
|
16
|
+
# An array-like class that uses an XML::Node to back itself.
|
17
|
+
# Designed to work in concert with a VOTable::Base object
|
18
|
+
# or at least something with a _node_ method and a _new_
|
19
|
+
# method that takes a XML::Node object.
|
20
|
+
# Mixes in Enumerable for added functionality.
|
21
|
+
class HomogeneousNodeList
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
# The XPath expression to the child node(s) of the list.
|
25
|
+
attr_reader :xpath
|
26
|
+
|
27
|
+
# The class of the sub-objects allowed in the list.
|
28
|
+
attr_reader :klass
|
29
|
+
|
30
|
+
# Create a new node list.
|
31
|
+
# _node_ is the XML::Node that all operation will be mirrored on.
|
32
|
+
# _xpath_ is the the XPath expression used to retrieve the
|
33
|
+
# children of _node_ that make up the list.
|
34
|
+
# _klass_ is the class of those children.
|
35
|
+
#
|
36
|
+
# vot = VOTable.new({
|
37
|
+
# :version => '1.1',
|
38
|
+
# :id => 'my_test_votable',
|
39
|
+
# :description => Description.new,
|
40
|
+
# :definitions => Definitions.new,
|
41
|
+
# :coordinate_systems => [Coosys.new],
|
42
|
+
# :params => [Param.new],
|
43
|
+
# :infos => [Info.new],
|
44
|
+
# :resources => [Resource.new]
|
45
|
+
# })
|
46
|
+
# node_list = HomogeneousNodeList.new(vot, 'COOSYS', VOTable::V_1::Coosys)
|
47
|
+
def initialize(node, xpath, klass)
|
48
|
+
@node = node.is_a?(XML::Node) ? node : node.node
|
49
|
+
@xpath = xpath
|
50
|
+
@klass = klass
|
51
|
+
end
|
52
|
+
|
53
|
+
# Iterate through each member of the list.
|
54
|
+
# node_list.each do |obj|
|
55
|
+
# puts obj
|
56
|
+
# end
|
57
|
+
def each
|
58
|
+
@node.find(*self.xpath).each do |e|
|
59
|
+
yield klass.new(e)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Replace the current member of the list with
|
64
|
+
# those specified.
|
65
|
+
# node_list.replace(Coosys.new, Coosys.new, Coosys.new)
|
66
|
+
def replace(*args)
|
67
|
+
self.clear
|
68
|
+
args.flatten.each do |a|
|
69
|
+
self << a
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Append a new member to the end of the list.
|
74
|
+
# node_list << Coosys.new
|
75
|
+
def <<(obj)
|
76
|
+
raise "Expected item of type #{@klass} but was #{obj.class}" if !obj.is_a?(@klass)
|
77
|
+
|
78
|
+
obj.node.name = self.xpath.first.split(':').last if self.xpath.is_a?(Array)
|
79
|
+
|
80
|
+
last_in_list = @node.find_first("*[local-name()='#{obj.node.name}'][last()]")
|
81
|
+
last_in_list ? last_in_list.next = obj.node : @node.child_add(obj.node)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Prepend a new member to the beginning of the list.
|
85
|
+
# node_list.prepend(Coosys.new)
|
86
|
+
def prepend(obj)
|
87
|
+
raise "Expected item of type #{@klass} but was #{obj.class}" if !obj.is_a?(@klass)
|
88
|
+
|
89
|
+
obj.node.name = self.xpath.first.split(':').last if self.xpath.is_a?(Array)
|
90
|
+
|
91
|
+
first_in_list = @node.find_first("*[local-name()='#{obj.node.name}'][1]")
|
92
|
+
first_in_list ? first_in_list.prev = obj.node : @node.child_add(obj.node)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Retrieve the ith member of the list. i may be less than one.
|
96
|
+
# coosys2 = node_list[1]
|
97
|
+
def [](i)
|
98
|
+
i = self.length + i if i < 0 # support negative indices
|
99
|
+
|
100
|
+
expr = self.xpath.is_a?(Array) ?
|
101
|
+
["#{self.xpath.first}[#{i+1}]", self.xpath.last] :
|
102
|
+
["#{self.xpath}[#{i+1}]"]
|
103
|
+
|
104
|
+
klass.new(@node.find_first(*expr))
|
105
|
+
end
|
106
|
+
|
107
|
+
# Set the ith member of the list. Setting a member to a location
|
108
|
+
# longer than the current list, appends that member to the end
|
109
|
+
# of the list. In other words, there are no nil members allowed.
|
110
|
+
# node_list[1] = Coosys.new
|
111
|
+
def []=(i, obj)
|
112
|
+
raise "Expected item of type #{@klass} but was #{obj.class}" if !obj.is_a?(@klass)
|
113
|
+
|
114
|
+
i = self.length + i if i < 0 # support negative indices
|
115
|
+
|
116
|
+
expr = self.xpath.is_a?(Array) ?
|
117
|
+
["#{self.xpath.first}[#{i+1}]", self.xpath.last] :
|
118
|
+
["#{self.xpath}[#{i+1}]"]
|
119
|
+
|
120
|
+
obj.node.name = self.xpath.first.split(':').last if self.xpath.is_a?(Array)
|
121
|
+
|
122
|
+
this_node = @node.find_first(*expr)
|
123
|
+
this_node ? this_node.replace_with(obj.node) : self << obj
|
124
|
+
end
|
125
|
+
|
126
|
+
# Delete the ith member in the list.
|
127
|
+
# node_list.delete_at(1) # delete the second coordinate system
|
128
|
+
def delete_at(i)
|
129
|
+
expr = self.xpath.is_a?(Array) ?
|
130
|
+
["#{self.xpath.first}[#{i+1}]", self.xpath.last] :
|
131
|
+
["#{self.xpath}[#{i+1}]"]
|
132
|
+
@node.find_first(*expr).remove!
|
133
|
+
end
|
134
|
+
|
135
|
+
# Clear the list of all its members.
|
136
|
+
# node_list.clear
|
137
|
+
def clear
|
138
|
+
@node.find(*self.xpath).each do |el|
|
139
|
+
el.remove!
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Pick the first member of the list.
|
144
|
+
# coosys1 = node_list.first
|
145
|
+
def first
|
146
|
+
expr = self.xpath.is_a?(Array) ?
|
147
|
+
["#{self.xpath.first}[1]", self.xpath.last] :
|
148
|
+
["#{self.xpath}[1]"]
|
149
|
+
klass.new(@node.find_first(*expr))
|
150
|
+
end
|
151
|
+
|
152
|
+
# Pick the last member of the list
|
153
|
+
# coosys_last = node_list.last
|
154
|
+
def last
|
155
|
+
expr = self.xpath.is_a?(Array) ?
|
156
|
+
["#{self.xpath.first}[last()]", self.xpath.last] :
|
157
|
+
["#{self.xpath}[last()]"]
|
158
|
+
klass.new(@node.find_first(*expr))
|
159
|
+
end
|
160
|
+
|
161
|
+
# Find the number of members in the list.
|
162
|
+
# node_list.length # => 4
|
163
|
+
def length
|
164
|
+
@node.find(*self.xpath).length
|
165
|
+
end
|
166
|
+
|
167
|
+
# Alias for #length.
|
168
|
+
def size
|
169
|
+
length
|
170
|
+
end
|
171
|
+
|
172
|
+
# Two lists are equivalent if their lengths are equal
|
173
|
+
# and the members of the lists are equal to each other
|
174
|
+
# (and in the same order).
|
175
|
+
def ==(obj)
|
176
|
+
return false if self.length != obj.length
|
177
|
+
|
178
|
+
self.each_with_index do |item, i|
|
179
|
+
return false if obj[i] != item
|
180
|
+
end
|
181
|
+
|
182
|
+
true
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
class XML::Node
|
2
|
+
def replace_with(other)
|
3
|
+
self.next = other
|
4
|
+
remove!
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module XML
|
9
|
+
module Object
|
10
|
+
# A (essentially abstract) class that can be used in the
|
11
|
+
# creation of domain objects backed by XML documents.
|
12
|
+
class Base
|
13
|
+
# The XML::Node used to store the state of the domain object.
|
14
|
+
attr_reader :node
|
15
|
+
|
16
|
+
# Create a domain object from an XML file on disk.
|
17
|
+
# _filename_:: the name of the file to parse
|
18
|
+
#
|
19
|
+
# votable = VOTable::V1_1::VOTable.from_file('myvotable.xml')
|
20
|
+
def self.from_file(filename)
|
21
|
+
self.new(XML::Document.file(filename))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get the name of the element when serialized
|
25
|
+
# to XML that the object in question will take on.
|
26
|
+
# By default, the name of the class.
|
27
|
+
def self.element_name
|
28
|
+
self.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create a new domain object from a number of source formats.
|
32
|
+
# _xml_ may be one of
|
33
|
+
# * a string
|
34
|
+
# * an IO object (i.e. a File object)
|
35
|
+
# * a LibXML Document (i.e. XML::Document)
|
36
|
+
# * a LibXML Node (i.e. XML::Node)
|
37
|
+
# * nothing (to create a blank object)
|
38
|
+
# * a hash where the keys of the hash represent the method to call
|
39
|
+
#
|
40
|
+
# Assuming VOTable::V1_1::VOTable is a subclass of Base...
|
41
|
+
#
|
42
|
+
# votable = VOTable::V1_1::VOTable.new('<VOTABLE version="1.1"/>')
|
43
|
+
# votable = VOTable::V1_1::VOTable.new(File.new('myvotable.xml')) # you could also use #from_file
|
44
|
+
# votable = VOTable::V1_1::VOTable.new(XML::Document.file('myvotable.xml'))
|
45
|
+
# votable = VOTable::V1_1::VOTable.new(XML::Node.new('VOTABLE'))
|
46
|
+
# votable = VOTable::V1_1::VOTable.new()
|
47
|
+
# votable = VOTable::V1_1::VOTable.new(:version => '1.1')
|
48
|
+
def initialize(xml=nil)
|
49
|
+
@node = case xml
|
50
|
+
when String
|
51
|
+
parser = XML::Parser.new
|
52
|
+
parser.string = xml
|
53
|
+
parser.parse.root
|
54
|
+
when IO
|
55
|
+
parser = XML::Parser.new
|
56
|
+
parser.io = xml
|
57
|
+
parser.parse.root
|
58
|
+
when XML::Document
|
59
|
+
xml.root
|
60
|
+
when XML::Node
|
61
|
+
xml
|
62
|
+
when NilClass
|
63
|
+
doc = XML::Document.new
|
64
|
+
doc.root = XML::Node.new(self.class.element_name)
|
65
|
+
doc.root
|
66
|
+
when Hash
|
67
|
+
doc = XML::Document.new
|
68
|
+
doc.root = XML::Node.new(self.class.element_name)
|
69
|
+
doc.root
|
70
|
+
else
|
71
|
+
raise "XML must be a XML::Document, XML::Node, String or IO object"
|
72
|
+
end
|
73
|
+
|
74
|
+
initialize_members(xml) if xml.is_a?(Hash)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Serialize the domain object to XML.
|
78
|
+
def to_s
|
79
|
+
self.node.to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
# Equality among domain objects.
|
83
|
+
# Always returns false, override to get less brain-dead result.
|
84
|
+
def ==(obj)
|
85
|
+
false
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
# Retrieve the domain object that corresponds to
|
91
|
+
# the specified class.
|
92
|
+
def get_element(klass)
|
93
|
+
subnode = self.node.find_first(self.xpath_for(klass))
|
94
|
+
subnode ? klass.new(subnode) : nil
|
95
|
+
end
|
96
|
+
|
97
|
+
# Set the specified domain object that corresponds to
|
98
|
+
# the specified class.
|
99
|
+
def set_element(klass, e)
|
100
|
+
descr_node = self.node.find_first(xpath_for(klass))
|
101
|
+
descr_node ? descr_node.replace_with(e.node) : @node.child_add(e.node)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Determine the xpath corresponding to the specified class.
|
105
|
+
# By default, this is just the name of the class.
|
106
|
+
# obj.xpath_for(Field) # => 'Field'
|
107
|
+
def xpath_for(klass)
|
108
|
+
klass.to_s
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def initialize_members(args)
|
114
|
+
args.each do |name, value|
|
115
|
+
send("#{name}=", value)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|