yweather 0.0.1
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/.gitignore +6 -0
- data/CHANGELOG +3 -0
- data/Gemfile +2 -0
- data/Guardfile +9 -0
- data/LICENSE +504 -0
- data/README.md +109 -0
- data/Rakefile +14 -0
- data/bin/yweather +30 -0
- data/lib/yweather.rb +22 -0
- data/lib/yweather/astronomy.rb +12 -0
- data/lib/yweather/atmosphere.rb +34 -0
- data/lib/yweather/client.rb +80 -0
- data/lib/yweather/condition.rb +71 -0
- data/lib/yweather/forecast.rb +22 -0
- data/lib/yweather/image.rb +19 -0
- data/lib/yweather/location.rb +15 -0
- data/lib/yweather/response.rb +87 -0
- data/lib/yweather/units.rb +20 -0
- data/lib/yweather/utils.rb +7 -0
- data/lib/yweather/version.rb +3 -0
- data/lib/yweather/wind.rb +15 -0
- data/spec/bin/yweather_spec.rb +7 -0
- data/spec/cassettes/invalid_weather_transaction.yml +44 -0
- data/spec/cassettes/invalid_woeid_conversion_transaction.yml +38 -0
- data/spec/cassettes/valid_weather_transaction.yml +66 -0
- data/spec/cassettes/valid_weather_transaction_with_zipcode_conversion.yml +101 -0
- data/spec/cassettes/valid_woeid_conversion_transaction.yml +38 -0
- data/spec/lib/yweather/astronomy_spec.rb +27 -0
- data/spec/lib/yweather/atmosphere_spec.rb +25 -0
- data/spec/lib/yweather/client_spec.rb +58 -0
- data/spec/lib/yweather/condition_spec.rb +25 -0
- data/spec/lib/yweather/forecast_spec.rb +25 -0
- data/spec/lib/yweather/image_spec.rb +25 -0
- data/spec/lib/yweather/location_spec.rb +25 -0
- data/spec/lib/yweather/response_spec.rb +49 -0
- data/spec/lib/yweather/units_spec.rb +25 -0
- data/spec/lib/yweather/wind_spec.rb +25 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/helpers/spec_parse_helpers.rb +25 -0
- data/spec/support/helpers/spec_yweather_helpers.rb +16 -0
- data/yweather.gemspec +39 -0
- metadata +283 -0
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# Yweather
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
This is a rewrite of Walter Korman's <shaper@fatgoose.com>
|
6
|
+
[yahoo-weather](http://rubyforge.org/projects/yahoo-weather) gem.
|
7
|
+
|
8
|
+
The aim of this port/rewrite is to add proper specs, better test-coverage and active
|
9
|
+
maintenance. I have ported much of Walter's original intent and LICENSE, and thank him for
|
10
|
+
his contribution. My initial motivation is to send Prowl notifications to my iPad, iPhone,
|
11
|
+
iPod whenever hazardous weather is coming.
|
12
|
+
|
13
|
+
Yweather provides an object-oriented Ruby interface to the Yahoo! Weather XML RSS feed
|
14
|
+
detailed at [http://developer.yahoo.com/weather](http://developer.yahoo.com/weather).
|
15
|
+
|
16
|
+
People care a lot about the weather. This may seem ironic given they can just glance out
|
17
|
+
the window. However, we can all understand a fascination with details and forecasting.
|
18
|
+
|
19
|
+
Log the weather information to your database! Graph it to your heart's content! Write a
|
20
|
+
widget that emails the weather to your cell phone every five minutes with a link to your
|
21
|
+
friend's PayPal account to deposit money if the weather's sunny and you both bet that it
|
22
|
+
would be rainy. And the fun doesn't have to stop there.
|
23
|
+
|
24
|
+
Source code is at [http://github.com/midwire/yweather](http://github.com/midwire/yweather).
|
25
|
+
|
26
|
+
NOTE: This library was updated as of December 2009 to use a new WOEID-based lookup
|
27
|
+
interface. Yahoo has deprecated the older non-WOEID-based lookup API. The archived page
|
28
|
+
with the deprecated API details is at:
|
29
|
+
|
30
|
+
[http://developer.yahoo.com/weather/archive.html](http://developer.yahoo.com/weather/archive.html)
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
gem install yweather
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
A simple example program:
|
39
|
+
|
40
|
+
#!/usr/bin/env ruby
|
41
|
+
require "yweather"
|
42
|
+
require "colorize"
|
43
|
+
|
44
|
+
zipcode = ARGV.shift
|
45
|
+
if zipcode.nil?
|
46
|
+
puts ">>> Usage: yweather zipcode"
|
47
|
+
exit
|
48
|
+
end
|
49
|
+
|
50
|
+
client = Yweather::Client.new
|
51
|
+
response = client.get_response_for_zipcode(zipcode)
|
52
|
+
|
53
|
+
# TODO: Use a ~/.yweather YAML file to determine format of output
|
54
|
+
|
55
|
+
print <<edoc
|
56
|
+
#{response.title.yellow}
|
57
|
+
#{response.condition.temp} degrees
|
58
|
+
#{response.condition.text}
|
59
|
+
edoc
|
60
|
+
|
61
|
+
puts "Wind:".yellow
|
62
|
+
response.wind_conditions.each_pair do |key, value|
|
63
|
+
puts " #{key}: #{value}"
|
64
|
+
end
|
65
|
+
|
66
|
+
puts "Atmosphere:".yellow
|
67
|
+
response.atmospheric_conditions.each_pair do |key, value|
|
68
|
+
puts " #{key}: #{value}"
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
Produces output as:
|
73
|
+
|
74
|
+
Conditions for Beverly Hills, CA at 10:51 am PST
|
75
|
+
61 degrees
|
76
|
+
Fair
|
77
|
+
Wind:
|
78
|
+
chill: 61°
|
79
|
+
direction: 0°
|
80
|
+
speed: 0 mph
|
81
|
+
Atmosphere:
|
82
|
+
humidity: 23%
|
83
|
+
visibility: 10 mi
|
84
|
+
pressure: steady at 30.18in
|
85
|
+
|
86
|
+
There is a variety of detailed weather information in other attributes of the Yweather::Response object.
|
87
|
+
|
88
|
+
## License
|
89
|
+
|
90
|
+
This library is provided via the GNU LGPL license at http://www.gnu.org/licenses/lgpl.html.
|
91
|
+
|
92
|
+
## Author
|
93
|
+
|
94
|
+
Copyright 2006 - 2009, Walter Korman <shaper@fatgoose.com>,
|
95
|
+
http://lemurware.blogspot.com.
|
96
|
+
|
97
|
+
Copyright 2012, Chris Blackburn <chris you-know-what-goes-here m and-here blackburn@gmail and-here com>,
|
98
|
+
http://midwire.github.com
|
99
|
+
|
100
|
+
Thanks to Walter Korman for the initial hack.
|
101
|
+
|
102
|
+
## Notes
|
103
|
+
|
104
|
+
Pull requests are appreciated. Please use feature/branches or hotfix/branches and add specs for your changes.
|
105
|
+
|
106
|
+
## ToDo
|
107
|
+
|
108
|
+
* Use a ~/.yweather YAML file, or some other templating scheme, to determine format of output when running the binary.
|
109
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
spec_tasks = Dir['spec/*/'].map { |d| File.basename(d) }
|
5
|
+
|
6
|
+
spec_tasks.each do |folder|
|
7
|
+
RSpec::Core::RakeTask.new("spec:#{folder}") do |t|
|
8
|
+
t.pattern = "./spec/#{folder}/**/*_spec.rb"
|
9
|
+
t.rspec_opts = %w(-fs --color)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Run complete application spec suite"
|
14
|
+
task 'spec' => spec_tasks.map { |f| "spec:#{f}" }
|
data/bin/yweather
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "yweather"
|
3
|
+
require "colorize"
|
4
|
+
|
5
|
+
zipcode = ARGV.shift
|
6
|
+
if zipcode.nil?
|
7
|
+
puts ">>> Usage: yweather zipcode"
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
|
11
|
+
client = Yweather::Client.new
|
12
|
+
response = client.get_response_for_zipcode(zipcode)
|
13
|
+
|
14
|
+
# TODO: Use a ~/.yweather YAML file to determine format of output
|
15
|
+
|
16
|
+
print <<edoc
|
17
|
+
#{response.title.yellow}
|
18
|
+
#{response.condition.temp} degrees
|
19
|
+
#{response.condition.text}
|
20
|
+
edoc
|
21
|
+
|
22
|
+
puts "Wind:".yellow
|
23
|
+
response.wind_conditions.each_pair do |key, value|
|
24
|
+
puts " #{key}: #{value}"
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "Atmosphere:".yellow
|
28
|
+
response.atmospheric_conditions.each_pair do |key, value|
|
29
|
+
puts " #{key}: #{value}"
|
30
|
+
end
|
data/lib/yweather.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'cgi'
|
3
|
+
require 'rest-client'
|
4
|
+
require 'yql'
|
5
|
+
require 'active_support/core_ext'
|
6
|
+
require 'json'
|
7
|
+
require "yweather/utils"
|
8
|
+
require "yweather/version"
|
9
|
+
require "yweather/client"
|
10
|
+
require "yweather/response"
|
11
|
+
require "yweather/astronomy"
|
12
|
+
require "yweather/atmosphere"
|
13
|
+
require "yweather/location"
|
14
|
+
require "yweather/wind"
|
15
|
+
require "yweather/units"
|
16
|
+
require "yweather/image"
|
17
|
+
require "yweather/condition"
|
18
|
+
require "yweather/forecast"
|
19
|
+
|
20
|
+
module Yweather
|
21
|
+
class YweatherException < RuntimeError; end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Yweather
|
2
|
+
class Atmosphere
|
3
|
+
|
4
|
+
class Barometer
|
5
|
+
STEADY = 'steady'
|
6
|
+
RISING = 'rising'
|
7
|
+
FALLING = 'falling'
|
8
|
+
|
9
|
+
# lists all possible barometer constants
|
10
|
+
ALL = [ STEADY, RISING, FALLING ]
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :humidity
|
14
|
+
attr_reader :visibility
|
15
|
+
attr_reader :pressure
|
16
|
+
attr_reader :barometer
|
17
|
+
|
18
|
+
def initialize(data)
|
19
|
+
@humidity = data[:humidity].to_i
|
20
|
+
@visibility = data[:visibility].to_i
|
21
|
+
@pressure = data[:pressure].to_f
|
22
|
+
@barometer = nil
|
23
|
+
case data[:rising].to_i
|
24
|
+
when 0
|
25
|
+
@barometer = Barometer::STEADY
|
26
|
+
when 1
|
27
|
+
@barometer = Barometer::RISING
|
28
|
+
when 2
|
29
|
+
@barometer = Barometer::FALLING
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Yweather
|
2
|
+
class Client
|
3
|
+
@@BASE_URL = "http://weather.yahooapis.com/forecastrss"
|
4
|
+
attr_reader :response
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@response = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_feed_for_zipcode(zipcode, options = {})
|
11
|
+
woeid = woeid_from_zipcode(zipcode)
|
12
|
+
get_feed_for_woeid(woeid, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_feed_for_woeid(woeid, options = {})
|
16
|
+
units = options[:units] || 'f'
|
17
|
+
format = options[:format] || :json
|
18
|
+
url = build_url(woeid, units)
|
19
|
+
get(url, format)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_response_for_woeid(woeid, options = {})
|
23
|
+
units = options[:units] || 'f'
|
24
|
+
format = options[:format] || :hash
|
25
|
+
url = build_url(woeid, units)
|
26
|
+
hash = get(url, format)
|
27
|
+
@response = Response.new(woeid, nil, url, hash)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_response_for_zipcode(zipcode, options = {})
|
31
|
+
units = options[:units] || 'f'
|
32
|
+
format = options[:format] || :hash
|
33
|
+
woeid = woeid_from_zipcode(zipcode)
|
34
|
+
url = build_url(woeid, units)
|
35
|
+
hash = get(url, format)
|
36
|
+
@response = Response.new(woeid, zipcode, url, hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
##################################################
|
40
|
+
private
|
41
|
+
|
42
|
+
def build_url(woeid, units)
|
43
|
+
"#{@@BASE_URL}?w=#{CGI.escape(woeid.to_s)}&u=#{CGI.escape(units)}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def woeid_from_zipcode(zipcode)
|
47
|
+
yql = Yql::Client.new
|
48
|
+
query = Yql::QueryBuilder.new 'geo.places'
|
49
|
+
query.select = 'woeid'
|
50
|
+
query.conditions = "text = '#{zipcode}, usa'"
|
51
|
+
yql.query = query
|
52
|
+
yql.format = 'json'
|
53
|
+
response = yql.get
|
54
|
+
h = ::HashWithIndifferentAccess.new(::JSON::parse(response.body))
|
55
|
+
if h[:query][:results][:place].is_a?(Array)
|
56
|
+
raise YweatherException, "Cannot determine WOEID from the ZIPCODE [#{zipcode}]"
|
57
|
+
return
|
58
|
+
end
|
59
|
+
h[:query][:results][:place][:woeid]
|
60
|
+
end
|
61
|
+
|
62
|
+
def get(url, format)
|
63
|
+
response = RestClient.get(url)
|
64
|
+
if response.body =~ /Error/
|
65
|
+
hash = ::HashWithIndifferentAccess.new(Hash.from_xml(response.body))
|
66
|
+
item = hash[:rss][:channel][:item]
|
67
|
+
raise YweatherException, "#{hash[:rss][:channel][:title]} :: #{item[:title]} :: #{item[:description]}"
|
68
|
+
return
|
69
|
+
end
|
70
|
+
if format == :xml
|
71
|
+
response.body
|
72
|
+
elsif format == :json
|
73
|
+
Hash.from_xml(response.body).to_json
|
74
|
+
elsif format == :hash
|
75
|
+
::HashWithIndifferentAccess.new(Hash.from_xml(response.body))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Yweather
|
2
|
+
class Condition
|
3
|
+
include Yweather::Utils
|
4
|
+
|
5
|
+
CODES = {
|
6
|
+
0 => "tornado",
|
7
|
+
1 => "tropical storm",
|
8
|
+
2 => "hurricane",
|
9
|
+
3 => "severe thunderstorms",
|
10
|
+
4 => "thunderstorms",
|
11
|
+
5 => "mixed rain and snow",
|
12
|
+
6 => "mixed rain and sleet",
|
13
|
+
7 => "mixed snow and sleet",
|
14
|
+
8 => "freezing drizzle",
|
15
|
+
9 => "drizzle",
|
16
|
+
10 => "freezing rain",
|
17
|
+
11 => "showers",
|
18
|
+
12 => "showers",
|
19
|
+
13 => "snow flurries",
|
20
|
+
14 => "light snow showers",
|
21
|
+
15 => "blowing snow",
|
22
|
+
16 => "snow",
|
23
|
+
17 => "hail",
|
24
|
+
18 => "sleet",
|
25
|
+
19 => "dust",
|
26
|
+
20 => "foggy",
|
27
|
+
21 => "haze",
|
28
|
+
22 => "smoky",
|
29
|
+
23 => "blustery",
|
30
|
+
24 => "windy",
|
31
|
+
25 => "cold",
|
32
|
+
26 => "cloudy",
|
33
|
+
27 => "mostly cloudy (night)",
|
34
|
+
28 => "mostly cloudy (day)",
|
35
|
+
29 => "partly cloudy (night)",
|
36
|
+
30 => "partly cloudy (day)",
|
37
|
+
31 => "clear (night)",
|
38
|
+
32 => "sunny",
|
39
|
+
33 => "fair (night)",
|
40
|
+
34 => "fair (day)",
|
41
|
+
35 => "mixed rain and hail",
|
42
|
+
36 => "hot",
|
43
|
+
37 => "isolated thunderstorms",
|
44
|
+
38 => "scattered thunderstorms",
|
45
|
+
39 => "scattered thunderstorms",
|
46
|
+
40 => "scattered showers",
|
47
|
+
41 => "heavy snow",
|
48
|
+
42 => "scattered snow showers",
|
49
|
+
43 => "heavy snow",
|
50
|
+
44 => "partly cloudy",
|
51
|
+
45 => "thundershowers",
|
52
|
+
46 => "snow showers",
|
53
|
+
47 => "isolated thundershowers",
|
54
|
+
3200 => "not available"
|
55
|
+
}
|
56
|
+
|
57
|
+
# the Yahoo! Weather condition code, as detailed at http://developer.yahoo.com/weather.
|
58
|
+
attr_reader :code
|
59
|
+
attr_reader :date
|
60
|
+
attr_reader :temp
|
61
|
+
attr_reader :text
|
62
|
+
|
63
|
+
def initialize(data)
|
64
|
+
@code = data[:code].to_i
|
65
|
+
@date = parse_time(data[:date])
|
66
|
+
@temp = data[:temp].to_i
|
67
|
+
@text = data[:text]
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Yweather
|
2
|
+
class Forecast
|
3
|
+
include Yweather::Utils
|
4
|
+
|
5
|
+
attr_reader :day
|
6
|
+
attr_reader :date
|
7
|
+
attr_reader :low
|
8
|
+
attr_reader :high
|
9
|
+
attr_reader :text
|
10
|
+
attr_reader :code
|
11
|
+
|
12
|
+
def initialize (data)
|
13
|
+
@day = data[:day]
|
14
|
+
@date = parse_time(data[:date])
|
15
|
+
@low = data[:low].to_i
|
16
|
+
@high = data[:high].to_i
|
17
|
+
@text = data[:text]
|
18
|
+
@code = data[:code].to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Yweather
|
2
|
+
class Image
|
3
|
+
|
4
|
+
attr_reader :height
|
5
|
+
attr_reader :link
|
6
|
+
attr_reader :title
|
7
|
+
attr_reader :url
|
8
|
+
attr_reader :width
|
9
|
+
|
10
|
+
def initialize (data)
|
11
|
+
@title = data[:title]
|
12
|
+
@link = data[:link]
|
13
|
+
@url = data[:url]
|
14
|
+
@height = data[:height]
|
15
|
+
@width = data[:width]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
module Yweather
|
3
|
+
class Response
|
4
|
+
attr_accessor :astronomy
|
5
|
+
attr_accessor :atmosphere
|
6
|
+
attr_accessor :condition
|
7
|
+
attr_accessor :description
|
8
|
+
attr_accessor :forecasts
|
9
|
+
attr_accessor :image
|
10
|
+
attr_accessor :latitude
|
11
|
+
attr_accessor :location
|
12
|
+
attr_accessor :longitude
|
13
|
+
attr_accessor :page_url
|
14
|
+
attr_accessor :request_location
|
15
|
+
attr_accessor :request_url
|
16
|
+
attr_accessor :title
|
17
|
+
attr_accessor :units
|
18
|
+
attr_accessor :wind
|
19
|
+
|
20
|
+
def initialize(woeid, zipcode, url, hash)
|
21
|
+
if hash[:rss][:channel][:title] =~ /Error/
|
22
|
+
item = hash[:rss][:channel][:item]
|
23
|
+
raise YweatherException, "#{hash[:rss][:channel][:title]} :: #{item[:title]} :: #{item[:description]}"
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
@astronomy = Astronomy.new(hash[:rss][:channel][:astronomy])
|
28
|
+
@atmosphere = Atmosphere.new(hash[:rss][:channel][:atmosphere])
|
29
|
+
@location = Location.new(hash[:rss][:channel][:location])
|
30
|
+
@wind = Wind.new(hash[:rss][:channel][:wind])
|
31
|
+
@units = Units.new(hash[:rss][:channel][:units])
|
32
|
+
@image = Image.new(hash[:rss][:channel][:image])
|
33
|
+
|
34
|
+
item = hash[:rss][:channel][:item]
|
35
|
+
@condition = Condition.new(item[:condition])
|
36
|
+
@forecasts = []
|
37
|
+
item[:forecast].each do |forecast|
|
38
|
+
@forecasts << Forecast.new(forecast)
|
39
|
+
end
|
40
|
+
@latitude = item[:lat]
|
41
|
+
@longitude = item[:long]
|
42
|
+
@page_url = item[:link]
|
43
|
+
@title = item[:title]
|
44
|
+
@description = item[:description]
|
45
|
+
end
|
46
|
+
|
47
|
+
def atmospheric_conditions(options = {})
|
48
|
+
pressure = (@atmosphere.barometer == "steady") ?
|
49
|
+
"#{@atmosphere.barometer} at #{@atmosphere.pressure}#{@units.pressure}" :
|
50
|
+
"#{@atmosphere.pressure}#{@units.pressure} and #{@atmosphere.barometer}"
|
51
|
+
|
52
|
+
h = ::HashWithIndifferentAccess.new({
|
53
|
+
:humidity => "#{@atmosphere.humidity}%",
|
54
|
+
:visibility => "#{@atmosphere.visibility} #{@units.distance}",
|
55
|
+
:pressure => "#{pressure}"
|
56
|
+
})
|
57
|
+
|
58
|
+
return_content(h, options[:format])
|
59
|
+
end
|
60
|
+
|
61
|
+
def wind_conditions(options = {})
|
62
|
+
# chill: wind chill in degrees (integer)
|
63
|
+
# direction: wind direction, in degrees (integer)
|
64
|
+
# speed: wind speed, in the units specified in the speed attribute of the yweather:units element (mph or kph). (integer)
|
65
|
+
h = ::HashWithIndifferentAccess.new({
|
66
|
+
:chill => "#{@wind.chill}°",
|
67
|
+
:direction => "#{@wind.direction}°",
|
68
|
+
:speed => "#{@wind.speed} #{@units.speed}"
|
69
|
+
})
|
70
|
+
return_content(h, options[:format])
|
71
|
+
end
|
72
|
+
|
73
|
+
##################################################
|
74
|
+
private
|
75
|
+
|
76
|
+
def return_content(hash, format)
|
77
|
+
if format == :json
|
78
|
+
hash.to_json
|
79
|
+
elsif format == :xml
|
80
|
+
hash.to_xml
|
81
|
+
else
|
82
|
+
hash
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|