Skip to content

Commit

Permalink
Reworking to use nokogiri.
Browse files Browse the repository at this point in the history
- upped library to version 1.2.0 to reflect changes.

- use new Yahoo Weather API url at
  http://weather.yahooapis.com/forecastrss

- shift xml parsing to use nokogiri rather than xml-simple.

- shift from Response.image_url to Response.image which
  contains the new YahooWeather::Image record with the old image url
  plus more detail as now provided in the API response.
  • Loading branch information
Walter Korman committed Dec 25, 2009
1 parent 3d2b9ad commit 8eb3560
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 46 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.rdoc
@@ -1,3 +1,14 @@
* [shaper] upped library to version 1.2.0 to reflect changes.

* [shaper] use new Yahoo Weather API at
http://weather.yahooapis.com/forecastrss

* [shaper] shift xml parsing to use nokogiri rather than xml-simple.

* [shaper] shift from Response.image_url to Response.image which
contains the new YahooWeather::Image record with the old image url
plus more detail as now provided in the API response.

* [shaper] refactor for single class per file.

* [shaper] fix km/h unit speed in test.
Expand Down
2 changes: 1 addition & 1 deletion README.rdoc
Expand Up @@ -48,7 +48,7 @@ A simple example program:
# sample html output
print <<edoc
<div>
<img src="#{response.image_url}"><br/>
<img src="#{response.image.url}"><br/>
#{response.condition.temp} degrees #{response.units.temperature}<br/>
#{response.condition.text}<br>
Forecast:<br/>
Expand Down
3 changes: 1 addition & 2 deletions Rakefile
Expand Up @@ -8,7 +8,7 @@ Hoe.spec('yahoo-weather') do |p|
self.rubyforge_name = 'yahoo-weather'
self.author = 'Walter Korman'
self.email = 'shaper@fatgoose.com'
self.extra_deps << [ 'xml-simple', '>= 1.0.9' ]
self.extra_deps << [ 'nokogiri', '>= 1.4.1' ]
self.summary = 'A Ruby object-oriented interface to the Yahoo! Weather service.'
self.description = <<EDOC
The yahoo-weather rubygem provides a Ruby object-oriented interface to the
Expand All @@ -17,7 +17,6 @@ Yahoo! Weather service described in detail at:
http://developer.yahoo.com/weather
EDOC
self.url = 'http://rubyforge.org/projects/yahoo-weather'
self.changes = p.paragraphs_of('CHANGELOG.rdoc', 0..1).join("\n\n")
self.remote_rdoc_dir = '' # Release to root
self.readme_file = 'README.rdoc'
self.history_file = 'CHANGELOG.rdoc'
Expand Down
2 changes: 1 addition & 1 deletion examples/example.rb
Expand Up @@ -16,7 +16,7 @@
# html output
print <<edoc
<div>
<img src="#{response.image_url}"><br/>
<img src="#{response.image.url}"><br/>
#{response.condition.temp} degrees #{response.units.temperature}<br/>
#{response.condition.text}<br>
Forecast:<br/>
Expand Down
7 changes: 3 additions & 4 deletions lib/yahoo-weather.rb
Expand Up @@ -17,13 +17,11 @@

require 'net/http'
require 'time'
require 'xmlsimple'
require 'nokogiri'

class YahooWeather
VERSION = '1.2.0'

VERSION = '1.1.0'

private
def self._parse_time (text)
(text) ? Time.parse(text) : nil
end
Expand All @@ -34,6 +32,7 @@ def self._parse_time (text)
require 'yahoo-weather/client'
require 'yahoo-weather/condition'
require 'yahoo-weather/forecast'
require 'yahoo-weather/image'
require 'yahoo-weather/location'
require 'yahoo-weather/response'
require 'yahoo-weather/units'
Expand Down
15 changes: 10 additions & 5 deletions lib/yahoo-weather/client.rb
@@ -1,7 +1,7 @@
# The main client object through which the Yahoo! Weather service may be accessed.
class YahooWeather::Client
# the url with which we obtain weather information from yahoo
@@API_URL = "http://xml.weather.yahoo.com/forecastrss"
@@API_URL = "http://weather.yahooapis.com/forecastrss"

def initialize (api_url = @@API_URL)
@api_url = api_url
Expand All @@ -17,25 +17,30 @@ def initialize (api_url = @@API_URL)
# 'USWA0395.html', so the location code for Seattle is 'USWA0395'.
#
# +units+ allows specifying whether to retrieve information in
# +Fahrenheit+ as +f+, or +Celsius+ as +c+, and defaults to +f+.
# +Fahrenheit+ as +YahooWeather::Units::FAHRENHEIT+, or +Celsius+ as
# +YahooWeather::Units::CELSIUS+, and defaults to fahrenheit.
#
def lookup_location (location, units = 'f')
# query the service to grab the xml data
url = _request_url(location, units)
begin
response = Net::HTTP.get_response(URI.parse(url)).body.to_s
response = Net::HTTP.get_response(URI.parse(url)).body.to_s

rescue
raise "failed to get weather via '#{url}': " + $!
end

# create the response object
response = XmlSimple.xml_in(response)
YahooWeather::Response.new(location, url, response)
doc = Nokogiri::XML.parse(response)
YahooWeather::Response.new(location, url, doc)
end

private

def _request_url (location, units)
@api_url + '?p=' + URI.encode(location) + '&u=' + URI.encode(units)
# TODO: for WOEID lookup, use 'w' rather than 'p' for query param
# @api_url + '?w=' + URI.encode( location ) + '&u=' + URI.encode( units )
end

end
25 changes: 25 additions & 0 deletions lib/yahoo-weather/image.rb
@@ -0,0 +1,25 @@
class YahooWeather::Image
# the height of the image in pixels.
attr_reader :height

# the url intended to be used as a link wrapping the image, for
# instance, to send the user to the main Yahoo Weather home page.
attr_reader :link

# the title of hte image.
attr_reader :title

# the full url to the image.
attr_reader :url

# the width of the image in pixels.
attr_reader :width

def initialize (payload)
@title = payload.xpath('title').first.content
@link = payload.xpath('link').first.content
@url = payload.xpath('url').first.content
@height = payload.xpath('height').first.content.to_i
@width = payload.xpath('width').first.content.to_i
end
end
49 changes: 24 additions & 25 deletions lib/yahoo-weather/response.rb
Expand Up @@ -32,9 +32,9 @@ class YahooWeather::Response
# weather conditions for the requested location.
attr_reader :description

# a link to the Yahoo! Weather image icon representing the current weather
# conditions visually.
attr_reader :image_url
# a YahooWeather::Image record describing an image icon
# representing the current weather.
attr_reader :image

# the latitude of the location for which weather is detailed.
attr_reader :latitude
Expand All @@ -55,32 +55,31 @@ class YahooWeather::Response
# the prose descriptive title of the weather information.
attr_reader :title

# regular expression used to pull the weather image icon from full
# description text.
@@REGEXP_IMAGE = Regexp.new(/img src="([^"]+)"/)

def initialize (request_location, request_url, payload)
def initialize (request_location, request_url, doc)
# save off the request params
@request_location = request_location
@request_url = request_url

root = payload['channel'].first
@astronomy = YahooWeather::Astronomy.new root['astronomy'].first
@location = YahooWeather::Location.new root['location'].first
@units = YahooWeather::Units.new root['units'].first
@wind = YahooWeather::Wind.new root['wind'].first
@atmosphere = YahooWeather::Atmosphere.new root['atmosphere'].first
# parse the nokogiri xml document to gather response data
root = doc.xpath('/rss/channel').first

@astronomy = YahooWeather::Astronomy.new(root.xpath('yweather:astronomy').first)
@location = YahooWeather::Location.new(root.xpath('yweather:location').first)
@units = YahooWeather::Units.new(root.xpath('yweather:units').first)
@wind = YahooWeather::Wind.new(root.xpath('yweather:wind').first)
@atmosphere = YahooWeather::Atmosphere.new(root.xpath('yweather:atmosphere').first)
@image = YahooWeather::Image.new(root.xpath('image').first)

item = root['item'].first
@condition = YahooWeather::Condition.new item['condition'].first
item = root.xpath('item').first
@condition = YahooWeather::Condition.
new(item.xpath('yweather:condition').first)
@forecasts = []
item['forecast'].each { |forecast| @forecasts << YahooWeather::Forecast.new(forecast) }
@latitude = item['lat'].first.to_f
@longitude = item['long'].first.to_f
@page_url = item['link'].first
@title = item['title'].first
@description = item['description'].first

match_data = @@REGEXP_IMAGE.match(description)
@image_url = (match_data) ? match_data[1] : nil
item.xpath('yweather:forecast').each { |forecast|
@forecasts << YahooWeather::Forecast.new(forecast) }
@latitude = item.xpath('geo:lat').first.content.to_f
@longitude = item.xpath('geo:long').first.content.to_f
@page_url = item.xpath('link').first.content
@title = item.xpath('title').first.content
@description = item.xpath('description').first.content
end
end
3 changes: 3 additions & 0 deletions lib/yahoo-weather/units.rb
@@ -1,5 +1,8 @@
# Describes the units of measure with which weather information is provided.
class YahooWeather::Units
FAHRENHEIT = 'f'
CELSIUS = 'c'

# the units in which temperature is measured, e.g. +F+ for +Fahrenheit+ or +C+ for +Celsius+.
attr_reader :temperature

Expand Down
17 changes: 9 additions & 8 deletions test/test_api.rb
Expand Up @@ -42,9 +42,10 @@ def test_units
def _assert_valid_response (response, request_location, units, city, region, country)
assert_not_nil response

# check the request location and url explicitly against what we know they should be
# check the request location and url explicitly against what we
# know they should be
assert_equal response.request_location, request_location
assert_equal response.request_url, "http://xml.weather.yahoo.com/forecastrss?p=#{request_location}&u=#{units}"
assert_equal response.request_url, "http://weather.yahooapis.com/forecastrss?p=#{request_location}&u=#{units}"

# check the astronomy info
assert_instance_of YahooWeather::Astronomy, response.astronomy
Expand All @@ -66,7 +67,7 @@ def _assert_valid_response (response, request_location, units, city, region, cou
assert(response.wind.chill <= response.condition.temp)
assert_kind_of Numeric, response.wind.direction
assert_kind_of Numeric, response.wind.speed

# check the atmosphere info
assert_instance_of YahooWeather::Atmosphere, response.atmosphere
assert_kind_of Numeric, response.atmosphere.humidity
Expand Down Expand Up @@ -95,21 +96,21 @@ def _assert_valid_response (response, request_location, units, city, region, cou
assert(forecast.text && forecast.text.length > 0)
_assert_valid_weather_code forecast.code
end

# check the basic attributes
assert(response.description && response.description.length > 0)
assert(response.image_url =~ /yimg\.com/)
assert(response.image.url =~ /yimg\.com/)
assert_kind_of Numeric, response.latitude
assert_kind_of Numeric, response.longitude
assert(response.page_url =~ /\.html$/)
assert(response.title && response.title.length > 0)
assert_not_nil (response.title.index("#{response.location.city}, #{response.location.region}"))
assert_not_nil(response.title.index("#{response.location.city}, #{response.location.region}"))
end

def _assert_valid_weather_code (code)
(((code >= 0) && (code <= 47)) || (code == 3200))
end

def _assert_valid_units (units, fahrenheit_based)
assert_instance_of YahooWeather::Units, units
if fahrenheit_based
Expand Down

0 comments on commit 8eb3560

Please sign in to comment.