voruby2-preview 1.0.0

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