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.
@@ -0,0 +1,223 @@
1
+ require 'rexml/document'
2
+
3
+ module REXML
4
+ module Object
5
+
6
+ class ReflectiveElementList
7
+ include Enumerable
8
+
9
+ attr_reader :parent, :xpath
10
+
11
+ def initialize(parent, *xpath)
12
+ @parent = parent
13
+ @xpath = xpath
14
+ end
15
+
16
+ def [](i)
17
+ path = "#{self.xpath.first}[#{i+1}]"
18
+ XPath.first(self.parent, path, self.xpath.last)
19
+ end
20
+
21
+ def []=(i, element)
22
+ el = self[i]
23
+ el ? el.replace_with(element) : self<<(element)
24
+ end
25
+
26
+ def <<(element)
27
+ path = "#{self.xpath.first}[last()]"
28
+ last = XPath.first(self.parent, path, self.xpath.last)
29
+ last ? last.next_sibling = element : self.parent << element
30
+ end
31
+
32
+ def last
33
+ path = "#{self.xpath.first}[last()]"
34
+ XPath.first(self.parent, path, self.xpath.last)
35
+ end
36
+
37
+ def first
38
+ path = "#{self.xpath.first}[1]"
39
+ XPath.first(self.parent, path, self.xpath.last)
40
+ end
41
+
42
+ def delete_at(i)
43
+ path = "#{self.xpath.first}[#{i+1}]"
44
+ XPath.first(self.parent, path, self.xpath.last).remove
45
+ end
46
+
47
+ def size
48
+ path = "count(#{self.xpath.first})"
49
+ XPath.first(self.parent, path, self.xpath.last)
50
+ end
51
+
52
+ def to_a
53
+ XPath.match(self.parent, *self.xpath)
54
+ end
55
+
56
+ def clear
57
+ self.each { |el| el.remove }
58
+ end
59
+
60
+ def replace(ary)
61
+ self.clear
62
+ ary.each { |el| self<<(el) }
63
+ end
64
+
65
+ def each
66
+ XPath.each(self.parent, *self.xpath) { |el| yield el }
67
+ end
68
+ end
69
+
70
+ class ClassifiedElementList < ReflectiveElementList
71
+ attr_reader :klass
72
+
73
+ def initialize(klass, parent, *xpath)
74
+ @klass = klass
75
+ super(parent, *xpath)
76
+ end
77
+
78
+ def [](i)
79
+ self.klass.new(super(i))
80
+ end
81
+
82
+ def []=(i, obj)
83
+ el = self[i]
84
+ el ? el.node.replace_with(obj.node) : self<<(obj.node)
85
+ end
86
+
87
+ def <<(obj)
88
+ super(obj.node)
89
+ end
90
+
91
+ def last
92
+ self.klass.new(super())
93
+ end
94
+
95
+ def first
96
+ self.klass.new(super())
97
+ end
98
+
99
+ def to_a
100
+ super().collect{ |el| self.klass.new(el) }
101
+ end
102
+
103
+ def replace(ary)
104
+ self.clear
105
+ ary.each { |obj| self<<(obj) }
106
+ end
107
+
108
+ def each
109
+ XPath.each(self.parent, *self.xpath) { |el| yield self.klass.new(el) }
110
+ end
111
+
112
+ def clear
113
+ self.each { |el| el.node.remove }
114
+ end
115
+
116
+ def ==(ary)
117
+ return false if self.size != ary.size
118
+
119
+ self.each_with_index do |item, i|
120
+ return false if item != ary[i]
121
+ end
122
+
123
+ return true
124
+ end
125
+ end
126
+
127
+ class Base
128
+ attr_reader :node, :xml_element_name
129
+
130
+ def self.xsi_namespace; 'http://www.w3.org/2001/XMLSchema-instance' end
131
+ def self.xsd_namespace; 'http://www.w3.org/2001/XMLSchema' end
132
+ def self.xml_type; self.class.to_s.split('::').last end
133
+
134
+ def initialize(defn=nil)
135
+ @node = case defn
136
+ when Document then defn.root
137
+ when Element then defn
138
+ when Hash then Element.new(self.xml_element_name)
139
+ when NilClass then Element.new(self.xml_element_name)
140
+ else
141
+ Document.new(defn).root
142
+ end
143
+
144
+ initialize_members(defn) if defn.is_a?(Hash)
145
+ end
146
+
147
+ def xml_element_name; self.class.to_s.split('::').last end
148
+
149
+ def find_namespace_by_href(href)
150
+ self.node.namespaces.find{ |prefix, uri| uri == href }
151
+ end
152
+
153
+ def get_attribute(name, ns=nil)
154
+ attribute = self.node.attributes.get_attribute_ns(ns, name)
155
+ attribute ? attribute.value : nil
156
+ end
157
+
158
+ def set_attribute(name, value, ns=nil)
159
+ self.node.attributes[attribute_xpath_for(name, ns)] = value.to_s
160
+ end
161
+
162
+ def get_element(name, ns=nil)
163
+ XPath.first(self.node, *element_xpath_for(name, ns))
164
+ end
165
+
166
+ def set_element(name, value, ns)
167
+ el = get_element(name, ns)
168
+
169
+ # The element doesn't exist...
170
+ if !el
171
+ el = REXML::Element.new(name)
172
+ @node << el # so insert it
173
+ end
174
+
175
+ case value
176
+ when String then el.text = value.to_s
177
+ when REXML::Element then value.name = name; el.replace_with(value)
178
+ end
179
+
180
+ el.add_namespace(ns) if !find_namespace_by_href(ns)
181
+ end
182
+
183
+ def get_elements(name, ns=nil)
184
+ ReflectiveElementList.new(self.node, *element_xpath_for(name, ns))
185
+ end
186
+
187
+ def set_elements(name, values, ns=nil)
188
+ get_elements(name, ns).replace(values)
189
+ end
190
+
191
+ def to_s
192
+ formatter = Formatters::Default.new
193
+ xml = ''
194
+ formatter.write(self.node, xml)
195
+ xml
196
+ end
197
+
198
+ protected
199
+
200
+ def attribute_xpath_for(name, ns=nil)
201
+ if ns
202
+ namespace = find_namespace_by_href(ns)
203
+ namespace ? "#{namespace.first}:#{name}" : name
204
+ else
205
+ name
206
+ end
207
+ end
208
+
209
+ def element_xpath_for(name, ns=nil)
210
+ ns ? ["x:#{name}", {'x' => ns}] : [name]
211
+ end
212
+
213
+ private
214
+
215
+ def initialize_members(args)
216
+ args.each do |name, value|
217
+ send("#{name}=", value)
218
+ end
219
+ end
220
+ end
221
+
222
+ end
223
+ end
@@ -0,0 +1,12 @@
1
+ require 'soap/rpc/driver'
2
+ require 'ostruct'
3
+
4
+ begin
5
+ require 'rubygems'
6
+ rescue LoadError; end
7
+ require 'xml/libxml'
8
+
9
+ module VORuby
10
+ # Name resolvers (i.e. the CDS's sesame[http://cdsweb.u-strasbg.fr/cdsws/name_resolver.gml] service)
11
+ module Resolver; end
12
+ end
@@ -0,0 +1,299 @@
1
+ require 'voruby/resolver/resolver'
2
+
3
+ module VORuby
4
+ module Resolver
5
+
6
+ # Sesame[http://cdsweb.u-strasbg.fr/cdsws/name_resolver.gml] is the standard
7
+ # name resolution service of the virtual observatory. There are a couple of
8
+ # ways to use it, but typically one wants the RA and Dec of the target:
9
+ #
10
+ # ra, dec = Sesame.resolve_position('m51') # => the primary J2000 RA and Dec of M 51 in degrees
11
+ # sesame = Sesame.resolve('m51') # => a Sesame object with in-depth information about the name resolution
12
+ # puts sesame.jradeg, sesame.jdedeg
13
+ #
14
+ # It's also possible to change the location of the web service used (to a mirror, say) and to specify
15
+ # which resolvers sesame should aggregate over (by default Simbad is used).
16
+ class Sesame
17
+ # The result of the name resolution as an XML::Node.
18
+ # Not available until after #resolve has been called.
19
+ attr_reader :xml
20
+
21
+ # Whether to try to determine if the sesame service
22
+ # is available or not before each name resolution.
23
+ # The default is false.
24
+ attr_accessor :check_availability
25
+
26
+ # Resolve the <tt>target</tt>.
27
+ # The optional <tt>against</tt> may be one of <tt>:simbad</tt>, <tt>:ned</tt>, <tt>:vizier</tt> or <tt>:all</tt>.
28
+ # Simbad is the most reliable, so that's the default.
29
+ # <tt>options</tt> is a hash with the following keys:
30
+ #
31
+ # [<tt>:end_point</tt>] the sesame SOAP endpoint to use (default: http://cdsws.u-strasbg.fr/axis/services/Sesame)
32
+ # [<tt>:check_availability</tt>] whether to try to determine if the sesame service is available or not before each name resolution (default: false)
33
+ #
34
+ # Returns a Sesame object, and is exactly equivalent to:
35
+ #
36
+ # sesame = Sesame.new
37
+ # sesame.resolve
38
+ def self.resolve(target, against=:simbad, options={})
39
+ sesame = Sesame.new(options)
40
+ sesame.resolve(target, against)
41
+
42
+ sesame
43
+ end
44
+
45
+ # Resolve the <tt>target</tt>, but return only the primary position and no
46
+ # other information. The parameters are as for #resolve with the addition
47
+ # of <tt>as</tt> which may be <tt>:degrees</tt> (default) or <tt>:sexigesimal</tt>.
48
+ # In the former case, an array of floats (ra, dec) is returned while in the latter
49
+ # an array of strings.
50
+ #
51
+ # Sesame.resolve_position('m51') # => [202.4682083, 47.1946667]
52
+ # Sesame.resolve_position('m51', :sexigesimal) # => ['13:29:52.36', '+47:11:40.8']
53
+ def self.resolve_position(target, against=:simbad, as=:degrees, options={})
54
+ self.resolve(target, against, options).position(as)
55
+ end
56
+
57
+ # Create a new sesame name resolver.
58
+ # <tt>options</tt> is a hash with the following keys:
59
+ #
60
+ # [<tt>:end_point</tt>] the sesame SOAP endpoint to use (default: http://cdsws.u-strasbg.fr/axis/services/Sesame)
61
+ # [<tt>:check_availability</tt>] whether to try to determine if the sesame service is available or not before each name resolution (default: false)
62
+ #
63
+ # sesame = Sesame.new(
64
+ # :end_point => 'http://vizier.nao.ac.jp:8080/axis/services/Sesame' # maybe we're in Japan...
65
+ # :check_availability => true # and we want to always check the availability of the service
66
+ # )
67
+ def initialize(options={})
68
+ self.end_point = options[:end_point] || 'http://cdsws.u-strasbg.fr/axis/services/Sesame'
69
+ self.check_availability = options[:check_availability] || false
70
+ end
71
+
72
+ # The "end-point" or location of the name resolver's webservice.
73
+ def end_point
74
+ @end_point
75
+ end
76
+
77
+ # Set the end-point or location of the name resolver's webservice.
78
+ # sesame.end_point = 'http://vizier.nao.ac.jp:8080/axis/services/Sesame'
79
+ def end_point=(epoint)
80
+ @end_point = epoint
81
+
82
+ # We don't use the WSDL for this because ruby doesn't deal well with method overloading.
83
+ @resolver = SOAP::RPC::Driver.new(@end_point)
84
+ @resolver.add_rpc_method('sesame', 'name', 'resultType', 'all', 'service')
85
+ @resolver.add_rpc_method('getAvailability')
86
+ end
87
+
88
+ # Retrieve whatever information we can about the position of the specified <tt>target</tt>.
89
+ # The optional <tt>against</tt> may be one of <tt>:simbad</tt>, <tt>:ned</tt>, <tt>:vizier</tt> or <tt>:all</tt>.
90
+ # Simbad is the most reliable, so that's the default.
91
+ #
92
+ # sesame.resolve('m51', :all) # use all the resources at sesame's disposal
93
+ #
94
+ # This method may throw an exception if:
95
+ #
96
+ # * #end_point doesn't exist
97
+ # * you have set #check_availabilty to true and the resolver has indicated it is not available
98
+ def resolve(target, against=:simbad)
99
+ raise "Sesame at #{self.end_point} is not currently available" if self.check_availability and !self.available?
100
+
101
+ parser = XML::Parser.new
102
+ parser.string =
103
+ @resolver.sesame(target.to_s, 'x', true, service_designation(against))
104
+ @xml = parser.parse.root
105
+ end
106
+
107
+ # The target you requested name resolution on.
108
+ def target
109
+ self.xml.find_first('target').content
110
+ end
111
+
112
+ # Any information that sesame sent back with your results.
113
+ def infos
114
+ self.xml.find('INFO').collect{ |i| i.content }
115
+ end
116
+
117
+ # Any errors reported by sesame sent back with your results.
118
+ def errors
119
+ self.xml.find('ERROR').collect{ |e| e.content }
120
+ end
121
+
122
+ # Sesame actually aggregates results from a number of sources.
123
+ # Returns an array of ResolvedInfo objects, one for each resource
124
+ # polled (i.e. some combination of Simbad, VizieR and Ned).
125
+ def resolvers
126
+ self.xml.find('Resolver').collect{ |r| ResolvedInfo.new(r) }
127
+ end
128
+
129
+ # A shortcut for ResolvedInfo#position.
130
+ # Assumes the first resolver is the one you're most
131
+ # interested in.
132
+ def position(as=:degrees)
133
+ self.resolvers.first.position(as)
134
+ end
135
+
136
+ # Checks to see whether sesame is available.
137
+ # At this point it's assumed the service is present at
138
+ # the location specified by #end_point. This just checks
139
+ # to see whether sesame reports itself as available or not.
140
+ def available?
141
+ # We don't use an XML parser here because (as of 2007-01-04)
142
+ # the validTo tag isn't closed properly, rendering the XML
143
+ # malformed.
144
+ result = @resolver.getAvailability
145
+ result.match(/<available>\s*(.*)\s*<\/available>/)[1] == 'true'
146
+ end
147
+
148
+ private
149
+
150
+ def service_designation(svc)
151
+ {
152
+ :simbad => 'S',
153
+ :ned => 'N',
154
+ :vizier => 'V',
155
+ :all => 'A'
156
+ }[svc] || svc
157
+ end
158
+
159
+ # A simple object that represents a name resolution attempt.
160
+ class ResolvedInfo
161
+ attr_reader :xml
162
+
163
+ # Create a ResolvedInfo object from an appropriate
164
+ # XML::Node (one that represents a <Resolver> XML element).
165
+ def initialize(xml)
166
+ @xml = xml
167
+ end
168
+
169
+ # The name of the resolver (i.e. Simbad).
170
+ def name
171
+ self.xml['name']
172
+ end
173
+
174
+ def code
175
+ self.xml['code']
176
+ end
177
+
178
+ # Any info accompanying the name resolution.
179
+ # Returns an array of strings.
180
+ def infos
181
+ self.xml.find('INFO').collect{ |i| i.content }
182
+ end
183
+
184
+ # Any errors accompanying the name resolution.
185
+ # Returns an array of string.
186
+ def errors
187
+ self.xml.find('ERROR').collect{ |e| e.content }
188
+ end
189
+
190
+ # The classification of the object (i.e. Seyfert_2 or QSO).
191
+ def otype
192
+ self.xml.find_first('otype').content if self.xml.find_first('otype')
193
+ end
194
+
195
+ # The J2000 coordinates of the object in sexigesimal format (i.e. '13:29:52.36 +47:11:40.8')
196
+ def jpos
197
+ self.xml.find_first('jpos').content if self.xml.find_first('jpos')
198
+ end
199
+
200
+ # The J2000 right ascension of the object in decimal degrees.
201
+ def jradeg
202
+ self.xml.find_first('jradeg').content.to_f if self.xml.find_first('jradeg')
203
+ end
204
+
205
+ # The J2000 declination of the object in decimal degrees.
206
+ def jdedeg
207
+ self.xml.find_first('jdedeg').content.to_f if self.xml.find_first('jdedeg')
208
+ end
209
+
210
+ # A bibcode that corresponds to the publication in which the position
211
+ # of the object was determined.
212
+ def ref_pos
213
+ self.xml.find_first('refPos').content if self.xml.find_first('refPos')
214
+ end
215
+
216
+ # The error of the right ascension in milliarcseconds.
217
+ def err_ra_mas
218
+ self.xml.find_first('errRAmas').content.to_f if self.xml.find_first('errRAmas')
219
+ end
220
+
221
+ # The error of the declination in milliarcseconds.
222
+ def err_de_mas
223
+ self.xml.find_first('errDEmas').content.to_f if self.xml.find_first('errDEmas')
224
+ end
225
+
226
+ # The redshift of the object.
227
+ def z
228
+ self.xml.find_first('z').content.to_f if self.xml.find_first('z')
229
+ end
230
+
231
+ # The error in the redshift of the object.
232
+ def errz
233
+ self.xml.find_first('errz').content.to_i if self.xml.find_first('errz')
234
+ end
235
+
236
+ # A bibcode that corresponds to the publication in which the redshift
237
+ # of the object was determined.
238
+ def refz
239
+ self.xml.find_first('refz').content if self.xml.find_first('refz')
240
+ end
241
+
242
+ # The velocity of the object in km/s.
243
+ def vel
244
+ self.xml.find_first('Vel').content.to_f if self.xml.find_first('Vel')
245
+ end
246
+
247
+ # The error in the velocity of the object in km/s.
248
+ def err_vel
249
+ self.xml.find_first('errVel').content.to_if if self.xml.find_first('errVel')
250
+ end
251
+
252
+ # A bibcode that corresponds to the publication in which the velocity
253
+ # of the object was determined.
254
+ def ref_vel
255
+ self.xml.find_first('refVel').content if self.xml.find_first('refVel')
256
+ end
257
+
258
+ # The morphological type of the object (i.e. Sc).
259
+ def mtype
260
+ self.xml.find_first('MType').content if self.xml.find_first('MType')
261
+ end
262
+
263
+ # The spectral type of the object (i.e. A10)
264
+ def sptype
265
+ self.xml.find_first('spType').content if self.xml.find_first('spType')
266
+ end
267
+
268
+ # The canonical name of the object.
269
+ def oname
270
+ self.xml.find_first('oname').content if self.xml.find_first('oname')
271
+ end
272
+
273
+ # The number of references to this object.
274
+ def nrefs
275
+ self.xml.find_first('nrefs').content.to_i if self.xml.find_first('nrefs')
276
+ end
277
+
278
+ # A list of aliases for the object.
279
+ # Returns a list of strings.
280
+ def aliases
281
+ self.xml.find('alias').collect{ |a| a.content }
282
+ end
283
+
284
+ # The J2000 position of the object in degrees or sexigesimal format.
285
+ # info.position(:degrees) # => [202.4682083, 47.1946667]
286
+ # info.position(:sexigesimal) # => ['13:29:52.36', '+47:11:40.8']
287
+ def position(as=:degrees)
288
+ case as
289
+ when :sexigesimal then (self.jpos || '').split(/\s+/)
290
+ else
291
+ [self.jradeg, self.jdedeg]
292
+ end
293
+ end
294
+ end
295
+
296
+ end
297
+
298
+ end
299
+ end