rubyweather 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +504 -0
- data/README +113 -0
- data/lib/forecast.rb +318 -0
- data/lib/service.rb +84 -0
- data/lib/util.rb +17 -0
- data/test/forecast_test.rb +77 -0
- data/test/service_test.rb +62 -0
- data/test/test_weather.xml +215 -0
- data/weather.rb +3 -0
- metadata +60 -0
data/README
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
= RubyWeather
|
2
|
+
|
3
|
+
Author:: Matt Zukowski (http://blog.roughest.net)
|
4
|
+
Copyright:: Copyright (c) 2006 Urbacon Ltd.
|
5
|
+
License:: GNU Lesser General Public License v2.1 (LGPL 2.1)
|
6
|
+
|
7
|
+
<b>RubyWeather is a Ruby[http://ruby-lang.org] library for fetching weather-related data from weather.com[http://www.weather.com/services/xmloap.html].</b>
|
8
|
+
|
9
|
+
Simple usage example:
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require_gem 'rubyweather'
|
13
|
+
|
14
|
+
require 'weather'
|
15
|
+
|
16
|
+
service = Weather::Service.new
|
17
|
+
service.partner_id = <b>your partner id</b>
|
18
|
+
service.license_key = <b>your license key</b>
|
19
|
+
|
20
|
+
locations = service.find_location('Toronto')
|
21
|
+
puts "Matching Locations: " + locations.inspect
|
22
|
+
|
23
|
+
The above will print out a list of available locations and their codes. We can
|
24
|
+
now use these codes to fetch the weather data for our city:
|
25
|
+
|
26
|
+
forecast = service.fetch_forecast("CAXX0504", 5)
|
27
|
+
|
28
|
+
puts "Location: %s" % forecast.location_name
|
29
|
+
|
30
|
+
puts "Current Temperature: %s" % forecast.current.temperature
|
31
|
+
puts "Current Windspeed: %s" % forecast.current.wind.speed
|
32
|
+
|
33
|
+
puts "Tomorrow's High: %s" % forecast.tomorrow.high
|
34
|
+
puts "Tomorrow's Outlook: %s" % forecast.tomorrow.outlook
|
35
|
+
puts "Tomorrow's Wind Direction: %s" % forecast.tomorrow.wind.direction
|
36
|
+
|
37
|
+
Forecasts for days in the future are accessed via <tt>forecast.day(#)</tt> where <tt>#</tt> is the number of day sinto the future
|
38
|
+
(assuming that you've fetched data for as many days in your <tt>service.fetch_forecast</tt> request):
|
39
|
+
|
40
|
+
puts "High 3 days from now: %s" % forecast.day(3).high
|
41
|
+
puts "Probability of precipitation 4 days from now: %s" % forecast.day(4).pop
|
42
|
+
|
43
|
+
There are a lot of attributes you can fetch for a forecast day. Here are just a few:
|
44
|
+
|
45
|
+
<tt>temp</tt>, <tt>temperature</tt>, <tt>temp</tt>::
|
46
|
+
The temperature. For future days this is equivalent to the low for nighttime and high for daytime.
|
47
|
+
<tt>icon</tt>::
|
48
|
+
The number of the icon gif file from the weather.com SDK[http://www.weather.com/services/xmloap.html] that
|
49
|
+
identifies the conditions (e.g. a little icon of a cloud with rain, or sun, or whatever).
|
50
|
+
<tt>outlook</tt>, <tt>outlook_brief</tt>::
|
51
|
+
Brief text describing the conditions (e.g. "Mostly Cloudy", "Rain", "Scattered T-Storms"). An abbreviated
|
52
|
+
version is available in <tt>outlook_brief</tt> (e.g. "M Cloudy", "Scat T-Storms").
|
53
|
+
<tt>low</tt>, <tt>lo</tt>::
|
54
|
+
The forecasted low temperature. (Not available for current conditions)
|
55
|
+
<tt>high</tt>, <tt>hi</tt>::
|
56
|
+
The forecasted high temperature. (Not available for current conditions)
|
57
|
+
<tt>wind</tt>, <tt>wind.direction</tt>, <tt>wind.speed</tt>, <tt>wind.heading</tt>::
|
58
|
+
Wind conditions. The <tt>wind</tt> attribute returns an object with sub-attributes.
|
59
|
+
<tt>pop</tt>, <tt>ppcp</tt>::
|
60
|
+
Probability of precipitation.
|
61
|
+
<tt>date</tt>::
|
62
|
+
The date that this forecast is for, returned as a ruby Time object.
|
63
|
+
<tt>sunrise</tt>::
|
64
|
+
The time of sunrise on the day of the forecast.
|
65
|
+
<tt>sunset</tt>::
|
66
|
+
The time of sunset on the day of the forecast.
|
67
|
+
|
68
|
+
Additionally, all (or at least most) of the attributes for a given day in the raw weather.com xml data are
|
69
|
+
also available. For example, you can call forecast.tomorrow.dewp to get the dewpoint, because the xml file
|
70
|
+
contains a <tt>dewp</tt> element for that day. Have a look at #test/test_weather.xml to see what data is
|
71
|
+
available in the xml file. Note though that raw xml elements will be returned as a string, without any nice
|
72
|
+
class casting or unit conversion.
|
73
|
+
|
74
|
+
I encourage other programmers to add more functionality to the lib/forecast.rb module to provide better
|
75
|
+
accessor methods for the underlying xml data.
|
76
|
+
|
77
|
+
== Download
|
78
|
+
|
79
|
+
You can download the latest stable version of RubyWeather from http://rubyforge.org/projects/rubyweather/.
|
80
|
+
Alternatively, the files <em>may</em> be available from http://roughest.net/rubyweather (but no promises -- the
|
81
|
+
rubyforge site is a better bet).
|
82
|
+
|
83
|
+
You can also check out the latest copy via subversion from svn://rubyforge.org//var/svn/rubyweather/trunk. If you would
|
84
|
+
like to contribute back your changes to the code, please contact me via the rubyforge project site to obtain a subversion
|
85
|
+
account.
|
86
|
+
|
87
|
+
== Note
|
88
|
+
|
89
|
+
To use this library you will need a partner id and license key from
|
90
|
+
weather.com. The service is completely free, but requires that you agree
|
91
|
+
to weather.com's legal stuff, which, among other things, requires your software
|
92
|
+
to include a link back to the weather.com website (although this is not actually
|
93
|
+
enforced in any way by the service).
|
94
|
+
|
95
|
+
To obtain the free license visit http://www.weather.com/services/xmloap.html.
|
96
|
+
This will also allow you to download an SDK that includes nice weather icons for
|
97
|
+
use with the data.
|
98
|
+
|
99
|
+
== License
|
100
|
+
|
101
|
+
This program is free software; you can redistribute it and/or modify
|
102
|
+
it under the terms of the GNU General Public License as published by
|
103
|
+
the Free Software Foundation; either version 2 of the License, or
|
104
|
+
(at your option) any later version.
|
105
|
+
|
106
|
+
This program is distributed in the hope that it will be useful,
|
107
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
108
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
109
|
+
GNU General Public License for more details.
|
110
|
+
|
111
|
+
You should have received a copy of the GNU General Public License
|
112
|
+
along with this program (see the file called LICENSE); if not, write to the
|
113
|
+
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
data/lib/forecast.rb
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Zukowski (http://blog.roughest.net)
|
3
|
+
# Copyright:: Copyright (c) 2006 Urbacon Ltd.
|
4
|
+
# License:: GNU Lesser General Public License v2.1 (LGPL 2.1)
|
5
|
+
#
|
6
|
+
|
7
|
+
require File.dirname(__FILE__) + '/util'
|
8
|
+
|
9
|
+
module Weather
|
10
|
+
|
11
|
+
# Namespace module for weather data objects.
|
12
|
+
module Forecast
|
13
|
+
|
14
|
+
# Contains several days of weather data.
|
15
|
+
# The forecast object includes the Enumerable mixin, so that you can iterate
|
16
|
+
# over all of the days in the forecast using the standard ruby mechanisms
|
17
|
+
# (i.e. <tt>myforecast.each { |d| print d.outlook }</tt>).
|
18
|
+
class Forecast
|
19
|
+
include Enumerable
|
20
|
+
|
21
|
+
attr_reader :xml
|
22
|
+
|
23
|
+
# Instantiate a Forecast object from the specified Weather.com REXML::Document.
|
24
|
+
def initialize(weather_xmldoc)
|
25
|
+
if (not weather_xmldoc.kind_of? REXML::Document)
|
26
|
+
raise "The xml document given to the Forecast constructor must be a valid REXML::Document"
|
27
|
+
end
|
28
|
+
|
29
|
+
@xml = weather_xmldoc
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the current conditions as a Conditions object.
|
33
|
+
def current
|
34
|
+
CurrentConditions.new(xml.root.elements['cc'])
|
35
|
+
end
|
36
|
+
|
37
|
+
# Alias for day(1)
|
38
|
+
def tomorrow
|
39
|
+
day(1)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the conditions for the given day (0 = today, 1 = tomorrow, etc.)
|
43
|
+
# The maximum day number depends on the data available in the xml that was used to create this Forecast.
|
44
|
+
def day(num)
|
45
|
+
element = xml.root.elements['dayf'].elements["day[@d='#{num.to_s}']"]
|
46
|
+
if not element
|
47
|
+
case num
|
48
|
+
when 0 then daydisplay = "today"
|
49
|
+
when 1 then daydisplay = "tomorrow"
|
50
|
+
else daydisplay = "#{num} days from now"
|
51
|
+
end
|
52
|
+
raise "Sorry, there is no data available for #{daydisplay}"
|
53
|
+
else
|
54
|
+
Day.new(element)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the conditions for the given night (0 = tonight, 1 = tomorrow night, etc.)
|
59
|
+
# The maximum day number depends on the data available in the xml that was used to create this Forecast.
|
60
|
+
def night(num)
|
61
|
+
element = xml.root.elements['dayf'].elements["day[@d='#{num.to_s}']"]
|
62
|
+
Night.new(element)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Iterates over all of the days in this Forecast.
|
66
|
+
def each(&block)
|
67
|
+
first = true
|
68
|
+
REXML::XPath.match(xml, "//dayf/day").each do |dxml|
|
69
|
+
d = Day.new(dxml)
|
70
|
+
|
71
|
+
# if it is after 3 PM, use current conditions
|
72
|
+
if Time.now > Time.local(d.date.year, d.date.month, d.date.day, 15)
|
73
|
+
d = current
|
74
|
+
end
|
75
|
+
|
76
|
+
yield d
|
77
|
+
|
78
|
+
first = false if first
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the full human-readable name of the place that this Forecast is for.
|
83
|
+
def location_name
|
84
|
+
xml.root.elements['loc'].elements['dnam'].text
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the name of the city that this Forecast is for.
|
88
|
+
def location_city
|
89
|
+
xml.root.elements['loc'].elements['dnam'].text.split(",").first
|
90
|
+
end
|
91
|
+
|
92
|
+
# The location code of the weather station that this Forecast is for.
|
93
|
+
def location_code
|
94
|
+
xml.root.elements['loc'].attributes['id']
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns true if the units returned by this Forecast will be in the metric system (i.e. Celcius).
|
98
|
+
def metric?
|
99
|
+
xml.root.elements['head'].elements['ut'].text == "C"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Abstract class that all Forecast entities (roughly "days") are based on.
|
104
|
+
class Conditions
|
105
|
+
|
106
|
+
# For elements in the forecast that we have not defined an explicit accessor,
|
107
|
+
# this allows accessing the raw underlying data in the forecast xml.
|
108
|
+
def method_missing(symbol)
|
109
|
+
begin
|
110
|
+
return @xml.elements[symbol.to_s].text
|
111
|
+
rescue NoMethodError
|
112
|
+
return "N/A"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the wind conditions as an anonymous object (i.e. wind.d for wind
|
117
|
+
# direction, wind.s for wind speed, etc.) See the <wind> element in the
|
118
|
+
# weather.com XML data spec for more info.
|
119
|
+
def wind
|
120
|
+
fix_wind(complex_attribute(@xml.elements['wind']))
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
# Returns the element specified by name as a cleaned-up temperature value.
|
125
|
+
# That is, if the temperature is "N/A", then nil is returned; otherwise
|
126
|
+
# the value is converted to an integer.
|
127
|
+
def clean_temp(name)
|
128
|
+
temp = @xml.elements[name].text
|
129
|
+
|
130
|
+
if (temp == "N/A")
|
131
|
+
return nil
|
132
|
+
else
|
133
|
+
return temp.to_i
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return the given xml element as an anonymous object, with the text nodes of the element's
|
138
|
+
# immediate children available as accessor methods.
|
139
|
+
# This allows for accessing attributes that have child elements (i.e. wind, bar, etc.)
|
140
|
+
# as anonymous objects (i.e. wind.d for wind direction, wind.s for wind speed, etc.)
|
141
|
+
def complex_attribute(objxml)
|
142
|
+
obj = {}
|
143
|
+
class << obj
|
144
|
+
def method_missing(name)
|
145
|
+
return self[name.to_s]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
objxml.elements.each do |element|
|
150
|
+
obj[element.name] = element.text
|
151
|
+
end
|
152
|
+
|
153
|
+
return obj
|
154
|
+
end
|
155
|
+
|
156
|
+
def fix_wind(obj)
|
157
|
+
obj['heading'] = obj.t
|
158
|
+
obj['direction'] = obj.d.to_i
|
159
|
+
obj['speed'] = obj.s.to_i
|
160
|
+
|
161
|
+
return obj
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Represents the current weather conditions.
|
166
|
+
class CurrentConditions < Conditions
|
167
|
+
attr_reader :xml
|
168
|
+
|
169
|
+
@xml
|
170
|
+
|
171
|
+
def initialize(element)
|
172
|
+
if (not element.kind_of? REXML::Element)
|
173
|
+
raise "The xml element given to the Day/Night constructor must be a valid REXML::Element"
|
174
|
+
end
|
175
|
+
@xml = element
|
176
|
+
end
|
177
|
+
|
178
|
+
def icon
|
179
|
+
xml.elements['icon'].text.to_i
|
180
|
+
end
|
181
|
+
|
182
|
+
def temp
|
183
|
+
clean_temp('tmp')
|
184
|
+
end
|
185
|
+
alias_method :tmp, :temp
|
186
|
+
alias_method :temperature, :temp
|
187
|
+
|
188
|
+
def outlook
|
189
|
+
xml.elements['t'].text
|
190
|
+
end
|
191
|
+
|
192
|
+
def outlook_brief
|
193
|
+
xml.elements['bt'].text
|
194
|
+
end
|
195
|
+
|
196
|
+
def pop
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
alias_method :ppcp, :pop
|
200
|
+
|
201
|
+
def date
|
202
|
+
Time.now
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Abstract class representing either a day or night in the daily forecast (note that "future" can include today).
|
207
|
+
class FutureConditions < Conditions
|
208
|
+
attr_reader :xml
|
209
|
+
|
210
|
+
@xml
|
211
|
+
|
212
|
+
def initialize(element)
|
213
|
+
if (not element.kind_of? REXML::Element)
|
214
|
+
raise "The xml element given to the Day/Night constructor must be a valid REXML::Element"
|
215
|
+
end
|
216
|
+
@xml = element
|
217
|
+
end
|
218
|
+
|
219
|
+
def method_missing(name)
|
220
|
+
begin
|
221
|
+
return mypart.elements[name.to_s].text
|
222
|
+
rescue NoMethodError
|
223
|
+
return "N/A"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def wind
|
228
|
+
fix_wind(complex_attribute(mypart.elements['wind']))
|
229
|
+
end
|
230
|
+
|
231
|
+
def date
|
232
|
+
# FIXME: this will break if rolling over to next year (i.e. fetched 5 days into the future on Dec 30), since today's year is assumed
|
233
|
+
mon, day = @xml.attributes['dt'].split(" ")
|
234
|
+
Time.local(Time.now.year, mon, day)
|
235
|
+
end
|
236
|
+
|
237
|
+
def icon
|
238
|
+
mypart.elements['icon'].text.to_i
|
239
|
+
end
|
240
|
+
|
241
|
+
def high
|
242
|
+
clean_temp('hi')
|
243
|
+
end
|
244
|
+
alias_method :hi, :high
|
245
|
+
|
246
|
+
def low
|
247
|
+
clean_temp('low')
|
248
|
+
end
|
249
|
+
alias_method :lo, :low
|
250
|
+
|
251
|
+
|
252
|
+
def outlook
|
253
|
+
mypart.elements['t'].text
|
254
|
+
end
|
255
|
+
|
256
|
+
def outlook_brief
|
257
|
+
mypart.elements['bt'].text
|
258
|
+
end
|
259
|
+
|
260
|
+
def pop
|
261
|
+
mypart.elements['ppcp'].text.to_i
|
262
|
+
end
|
263
|
+
alias_method :ppcp, :pop
|
264
|
+
|
265
|
+
def sunrise
|
266
|
+
hour,minute = @xml.elements['sunr'].text.split(" ").first.split(":")
|
267
|
+
hour = hour.to_i
|
268
|
+
minute = minute.to_i
|
269
|
+
Time.local(date.year, date.month, date.day, hour, minute)
|
270
|
+
end
|
271
|
+
|
272
|
+
def sunset
|
273
|
+
hour,minute = @xml.elements['suns'].text.split(" ").first.split(":")
|
274
|
+
hour = hour.to_i + 12 # add 12 since we need 24 hour clock and sunset is always (?) in the PM
|
275
|
+
minute = minute.to_i
|
276
|
+
Time.local(date.year, date.month, date.day, hour, minute)
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
# The daytime part of the forecast for a given day.
|
282
|
+
class Day < FutureConditions
|
283
|
+
def initialize(element)
|
284
|
+
super(element)
|
285
|
+
end
|
286
|
+
|
287
|
+
def temp
|
288
|
+
high
|
289
|
+
end
|
290
|
+
alias_method :tmp, :temp
|
291
|
+
alias_method :temperature, :temp
|
292
|
+
|
293
|
+
private
|
294
|
+
def mypart
|
295
|
+
@xml.elements['part[@p="d"]']
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# The nighttime part of a forecast for a given day.
|
300
|
+
class Night < FutureConditions
|
301
|
+
def initialize(element)
|
302
|
+
super(element)
|
303
|
+
end
|
304
|
+
|
305
|
+
def temp
|
306
|
+
low
|
307
|
+
end
|
308
|
+
alias_method :tmp, :temp
|
309
|
+
alias_method :temperature, :temp
|
310
|
+
|
311
|
+
private
|
312
|
+
def mypart
|
313
|
+
@xml.elements['part[@p="n"]']
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
data/lib/service.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Zukowski (http://blog.roughest.net)
|
3
|
+
# Copyright:: Copyright (c) 2006 Urbacon Ltd.
|
4
|
+
# License:: GNU Lesser General Public License v2.1 (LGPL 2.1)
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'net/http'
|
8
|
+
require 'rexml/document'
|
9
|
+
|
10
|
+
require File.dirname(__FILE__) + '/forecast'
|
11
|
+
|
12
|
+
module Weather
|
13
|
+
|
14
|
+
# Interface for interacting with the weather.com service.
|
15
|
+
class Service
|
16
|
+
attr_writer :partner_id, :license_key, :imperial
|
17
|
+
attr_reader :partner_id, :license_key, :imperial
|
18
|
+
|
19
|
+
# Returns the forecast data fetched from the weather.com xoap service for the given location and number of days.
|
20
|
+
def fetch_forecast(location_id, days = 5)
|
21
|
+
|
22
|
+
# try to pull the partner_id and license_key from the environment if not already set
|
23
|
+
partner_id = ENV['WEATHER_COM_PARTNER_ID'] unless partner_id
|
24
|
+
license_key = ENV['WEATHER_COM_LICENSE_KEY'] unless license_key
|
25
|
+
|
26
|
+
if imperial or (ENV.has_key? 'USE_IMPERIAL_UNITS' and ENV['USE_IMPERIAL_UNITS'])
|
27
|
+
imperial = true
|
28
|
+
else
|
29
|
+
imperial = false
|
30
|
+
end
|
31
|
+
|
32
|
+
# NOTE: Strangely enough, weather.com doesn't seem to be enforcing the partner_id/license_key stuff. You can specify blank values for both
|
33
|
+
# and the service will return the data just fine (actually, it will accept any value as valid). I'm commenting out these checks
|
34
|
+
# for now, but we may need to re-enable these once weather.com figures out what's going on.
|
35
|
+
#if not partner_id
|
36
|
+
# puts "WARNING: No partner ID has been set. Please obtain a partner ID from weather.com before attempting to fetch a forecast, otherwise the data you requested may not be available."
|
37
|
+
#end
|
38
|
+
#
|
39
|
+
#if not license_key
|
40
|
+
# puts "WARNING: No license key has been set. Please obtain a license key from weather.com before attempting to fetch a forecast, otherwise the data you requested may not be available"
|
41
|
+
#end
|
42
|
+
|
43
|
+
# default to metric (degrees fahrenheit are just silly :)
|
44
|
+
unit = imperial ? "s" : "m"
|
45
|
+
|
46
|
+
host = "xoap.weather.com"
|
47
|
+
url = "/weather/local/#{location_id}?cc=*&dayf=#{days}&prod=xoap&par=#{partner_id}&key=#{license_key}&unit=#{unit}"
|
48
|
+
|
49
|
+
# puts "Using url: "+url
|
50
|
+
|
51
|
+
xml = Net::HTTP.get(host, url);
|
52
|
+
doc = REXML::Document.new(xml)
|
53
|
+
|
54
|
+
Forecast::Forecast.new(doc)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the forecast data loaded from a file. This is useful for testing.
|
58
|
+
def load_forecast(filename)
|
59
|
+
file = File.new(filename)
|
60
|
+
doc = REXML::Document.new(file)
|
61
|
+
|
62
|
+
Forecast::Forecast.new(doc)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a hash containing location_code => location_name key-value pairs for the given location search string.
|
66
|
+
# In other words, you can use this to find a location code based on a city name, ZIP code, etc.
|
67
|
+
def find_location(search_string)
|
68
|
+
host = "xoap.weather.com"
|
69
|
+
# FIXME: need to do url encoding of the search string!
|
70
|
+
url = "/weather/search/search?where=#{search_string}"
|
71
|
+
|
72
|
+
xml = Net::HTTP.get(host, url);
|
73
|
+
doc = REXML::Document.new(xml)
|
74
|
+
|
75
|
+
locations = {}
|
76
|
+
|
77
|
+
REXML::XPath.match(doc.root, "//loc").each do |loc|
|
78
|
+
locations[loc.attributes['id']] = loc.text
|
79
|
+
end
|
80
|
+
|
81
|
+
return locations
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|