nokogiri-happymapper 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/History +59 -0
  2. data/License +20 -0
  3. data/Manifest +45 -0
  4. data/README +61 -0
  5. data/Rakefile +28 -0
  6. data/TODO +0 -0
  7. data/examples/amazon.rb +34 -0
  8. data/examples/current_weather.rb +21 -0
  9. data/examples/dashed_elements.rb +20 -0
  10. data/examples/family_tree.rb +48 -0
  11. data/examples/post.rb +19 -0
  12. data/examples/twitter.rb +37 -0
  13. data/lib/happymapper.rb +157 -0
  14. data/lib/happymapper/attribute.rb +3 -0
  15. data/lib/happymapper/element.rb +3 -0
  16. data/lib/happymapper/item.rb +198 -0
  17. data/lib/happymapper/text_node.rb +3 -0
  18. data/lib/happymapper/version.rb +3 -0
  19. data/nokogiri-happymapper.gemspec +34 -0
  20. data/spec/fixtures/address.xml +8 -0
  21. data/spec/fixtures/analytics.xml +61 -0
  22. data/spec/fixtures/commit.xml +52 -0
  23. data/spec/fixtures/current_weather.xml +89 -0
  24. data/spec/fixtures/dictionary.xml +20 -0
  25. data/spec/fixtures/family_tree.xml +21 -0
  26. data/spec/fixtures/lastfm.xml +355 -0
  27. data/spec/fixtures/multiple_namespaces.xml +170 -0
  28. data/spec/fixtures/multiple_primitives.xml +5 -0
  29. data/spec/fixtures/pita.xml +133 -0
  30. data/spec/fixtures/posts.xml +23 -0
  31. data/spec/fixtures/product_default_namespace.xml +17 -0
  32. data/spec/fixtures/product_no_namespace.xml +10 -0
  33. data/spec/fixtures/product_single_namespace.xml +10 -0
  34. data/spec/fixtures/quarters.xml +19 -0
  35. data/spec/fixtures/radar.xml +21 -0
  36. data/spec/fixtures/statuses.xml +422 -0
  37. data/spec/happymapper_attribute_spec.rb +21 -0
  38. data/spec/happymapper_element_spec.rb +21 -0
  39. data/spec/happymapper_item_spec.rb +115 -0
  40. data/spec/happymapper_spec.rb +735 -0
  41. data/spec/happymapper_text_node_spec.rb +21 -0
  42. data/spec/spec.opts +1 -0
  43. data/spec/spec_helper.rb +13 -0
  44. data/website/css/common.css +47 -0
  45. data/website/index.html +98 -0
  46. metadata +120 -0
@@ -0,0 +1,3 @@
1
+ module HappyMapper
2
+ class Attribute < Item; end
3
+ end
@@ -0,0 +1,3 @@
1
+ module HappyMapper
2
+ class Element < Item; end
3
+ end
@@ -0,0 +1,198 @@
1
+ module HappyMapper
2
+ class Item
3
+ attr_accessor :name, :type, :tag, :options, :namespace
4
+
5
+ Types = [String, Float, Time, Date, DateTime, Integer, Boolean]
6
+
7
+ # options:
8
+ # :deep => Boolean False to only parse element's children, True to include
9
+ # grandchildren and all others down the chain (// in xpath)
10
+ # :namespace => String Element's namespace if it's not the global or inherited
11
+ # default
12
+ # :parser => Symbol Class method to use for type coercion.
13
+ # :raw => Boolean Use raw node value (inc. tags) when parsing.
14
+ # :single => Boolean False if object should be collection, True for single object
15
+ # :tag => String Element name if it doesn't match the specified name.
16
+ def initialize(name, type, o={})
17
+ self.name = name.to_s
18
+ self.type = type
19
+ #self.tag = o.delete(:tag) || name.to_s
20
+ self.tag = o[:tag] || name.to_s
21
+ self.options = { :single => true }.merge(o.merge(:name => self.name))
22
+
23
+ @xml_type = self.class.to_s.split('::').last.downcase
24
+ end
25
+
26
+ def constant
27
+ @constant ||= constantize(type)
28
+ end
29
+
30
+ def from_xml_node(node, namespace, xpath_options)
31
+ if primitive?
32
+ find(node, namespace, xpath_options) do |n|
33
+ if n.respond_to?(:content)
34
+ typecast(n.content)
35
+ else
36
+ typecast(n.to_s)
37
+ end
38
+ end
39
+ elsif constant == XmlContent
40
+ find(node, namespace, xpath_options) do |n|
41
+ n = n.children if n.respond_to?(:children)
42
+ n.respond_to?(:to_xml) ? n.to_xml : n.to_s
43
+ end
44
+ else
45
+ if options[:parser]
46
+ find(node, namespace, xpath_options) do |n|
47
+ if n.respond_to?(:content) && !options[:raw]
48
+ value = n.content
49
+ else
50
+ value = n.to_s
51
+ end
52
+
53
+ begin
54
+ constant.send(options[:parser].to_sym, value)
55
+ rescue
56
+ nil
57
+ end
58
+ end
59
+ else
60
+ constant.parse(node, options.merge(:namespaces => xpath_options))
61
+ end
62
+ end
63
+ end
64
+
65
+ def xpath(namespace = self.namespace)
66
+ xpath = ''
67
+ xpath += './/' if options[:deep]
68
+ xpath += "#{namespace}:" if namespace
69
+ xpath += tag
70
+ # puts "xpath: #{xpath}"
71
+ xpath
72
+ end
73
+
74
+ def primitive?
75
+ Types.include?(constant)
76
+ end
77
+
78
+ def element?
79
+ @xml_type == 'element'
80
+ end
81
+
82
+ def attribute?
83
+ @xml_type == 'attribute'
84
+ end
85
+
86
+ def text_node?
87
+ @xml_type == 'textnode'
88
+ end
89
+
90
+ def method_name
91
+ @method_name ||= name.tr('-', '_')
92
+ end
93
+
94
+ def typecast(value)
95
+ return value if value.kind_of?(constant) || value.nil?
96
+ begin
97
+ if constant == String then value.to_s
98
+ elsif constant == Float then value.to_f
99
+ elsif constant == Time then Time.parse(value.to_s) rescue Time.at(value.to_i)
100
+ elsif constant == Date then Date.parse(value.to_s)
101
+ elsif constant == DateTime then DateTime.parse(value.to_s)
102
+ elsif constant == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
103
+ elsif constant == Integer
104
+ # ganked from datamapper
105
+ value_to_i = value.to_i
106
+ if value_to_i == 0 && value != '0'
107
+ value_to_s = value.to_s
108
+ begin
109
+ Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
110
+ rescue ArgumentError
111
+ nil
112
+ end
113
+ else
114
+ value_to_i
115
+ end
116
+ else
117
+ value
118
+ end
119
+ rescue
120
+ value
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ def constantize(type)
127
+ if type.is_a?(String)
128
+ names = type.split('::')
129
+ constant = Object
130
+ names.each do |name|
131
+ constant =
132
+ if constant.const_defined?(name)
133
+ constant.const_get(name)
134
+ else
135
+ constant.const_missing(name)
136
+ end
137
+ end
138
+ constant
139
+ else
140
+ type
141
+ end
142
+ end
143
+
144
+
145
+ def find(node, namespace, xpath_options, &block)
146
+ if self.namespace && xpath_options["xmlns:#{self.namespace}"]
147
+ # from the class definition
148
+ namespace = self.namespace
149
+ elsif options[:namespace] && xpath_options["xmlns:#{options[:namespace]}"]
150
+ namespace = options[:namespace]
151
+ end
152
+
153
+ if element?
154
+ if options[:single]
155
+ result = node.xpath(xpath(namespace), xpath_options)
156
+
157
+ if result
158
+ value = options[:single] ? yield(result.first) : result.map {|r| yield r }
159
+ handle_attributes_option(result, value, xpath_options)
160
+
161
+ value
162
+ end
163
+ else
164
+ results = node.xpath(xpath(namespace), xpath_options).collect do |result|
165
+ value = yield(result)
166
+ handle_attributes_option(result, value, xpath_options)
167
+ value
168
+ end
169
+ results
170
+ end
171
+ elsif attribute?
172
+ yield(node[tag])
173
+ else # text node
174
+ yield(node.children.detect{|c| c.text?})
175
+ end
176
+ end
177
+
178
+ def handle_attributes_option(result, value, xpath_options)
179
+ if options[:attributes].is_a?(Hash)
180
+ result = result.first if result.respond_to?(:first)
181
+
182
+ result.attribute_nodes.each do |xml_attribute|
183
+ if attribute_options = options[:attributes][xml_attribute.name.to_sym]
184
+ attribute_value = Attribute.new(xml_attribute.name.to_sym, *attribute_options).from_xml_node(result, namespace, xpath_options)
185
+
186
+ result.instance_eval <<-EOV
187
+ def value.#{xml_attribute.name}
188
+ #{attribute_value.inspect}
189
+ end
190
+ EOV
191
+ end # if attributes_options
192
+ end # attribute_nodes.each
193
+ end # if options[:attributes]
194
+ end # def handle...
195
+
196
+ # end private methods
197
+ end
198
+ end
@@ -0,0 +1,3 @@
1
+ module HappyMapper
2
+ class TextNode < Item; end
3
+ end
@@ -0,0 +1,3 @@
1
+ module HappyMapper
2
+ Version = '0.3.3'
3
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{nokogiri-happymapper}
5
+ s.version = "0.3.3"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Damien Le Berrigaud, John Nunemaker, David Bolton, Roland Swingler"]
9
+ s.date = %q{2010-03-19}
10
+ s.description = %q{object to xml mapping library, using nokogiri (fork from John Nunemaker's Happymapper)}
11
+ s.email = %q{damien@meliondesign.com}
12
+ s.extra_rdoc_files = ["README", "TODO", "lib/happymapper.rb", "lib/happymapper/attribute.rb", "lib/happymapper/element.rb", "lib/happymapper/item.rb", "lib/happymapper/text_node.rb", "lib/happymapper/version.rb"]
13
+ s.files = ["History", "License", "Manifest", "README", "Rakefile", "TODO", "examples/amazon.rb", "examples/current_weather.rb", "examples/dashed_elements.rb", "examples/family_tree.rb", "examples/post.rb", "examples/twitter.rb", "lib/happymapper.rb", "lib/happymapper/attribute.rb", "lib/happymapper/element.rb", "lib/happymapper/item.rb", "lib/happymapper/text_node.rb", "lib/happymapper/version.rb", "nokogiri-happymapper.gemspec", "spec/fixtures/address.xml", "spec/fixtures/analytics.xml", "spec/fixtures/commit.xml", "spec/fixtures/current_weather.xml", "spec/fixtures/dictionary.xml", "spec/fixtures/family_tree.xml", "spec/fixtures/lastfm.xml", "spec/fixtures/multiple_namespaces.xml", "spec/fixtures/multiple_primitives.xml", "spec/fixtures/pita.xml", "spec/fixtures/posts.xml", "spec/fixtures/product_default_namespace.xml", "spec/fixtures/product_no_namespace.xml", "spec/fixtures/product_single_namespace.xml", "spec/fixtures/quarters.xml", "spec/fixtures/radar.xml", "spec/fixtures/statuses.xml", "spec/happymapper_attribute_spec.rb", "spec/happymapper_element_spec.rb", "spec/happymapper_item_spec.rb", "spec/happymapper_spec.rb", "spec/happymapper_text_node_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "website/css/common.css", "website/index.html"]
14
+ s.homepage = %q{http://github.com/dam5s/happymapper}
15
+ s.post_install_message = %q{May you have many happy mappings!}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Nokogiri-happymapper", "--main", "README"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{nokogiri-happymapper}
19
+ s.rubygems_version = %q{1.3.5}
20
+ s.summary = %q{object to xml mapping library, using nokogiri (fork from John Nunemaker's Happymapper)}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.4.0"])
28
+ else
29
+ s.add_dependency(%q<nokogiri>, [">= 1.4.0"])
30
+ end
31
+ else
32
+ s.add_dependency(%q<nokogiri>, [">= 1.4.0"])
33
+ end
34
+ end
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <address>
3
+ <street>Milchstrasse</street>
4
+ <housenumber>23</housenumber>
5
+ <postcode>26131</postcode>
6
+ <city>Oldenburg</city>
7
+ <country code="de">Germany</country>
8
+ </address>
@@ -0,0 +1,61 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <feed xmlns="http://www.w3.org/2005/Atom"
3
+ xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/"
4
+ xmlns:dxp="http://schemas.google.com/analytics/2009">
5
+ <id>http://www.google.com/analytics/feeds/accounts/nunemaker@gmail.com</id>
6
+ <updated>2009-04-22T23:21:23.000-07:00</updated>
7
+ <title type="text">Profile list for nunemaker@gmail.com</title>
8
+ <link rel="self" type="application/atom+xml"
9
+ href="http://www.google.com/analytics/feeds/accounts/default"/>
10
+ <author>
11
+ <name>Google Analytics</name>
12
+ </author>
13
+ <generator version="1.0">Google Analytics</generator>
14
+ <openSearch:totalResults>4</openSearch:totalResults>
15
+ <openSearch:startIndex>1</openSearch:startIndex>
16
+ <openSearch:itemsPerPage>4</openSearch:itemsPerPage>
17
+ <entry>
18
+ <id>http://www.google.com/analytics/feeds/accounts/ga:47912</id>
19
+ <updated>2008-11-24T12:11:32.000-08:00</updated>
20
+ <title type="text">addictedtonew.com</title>
21
+ <link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
22
+ <dxp:tableId>ga:47912</dxp:tableId>
23
+ <dxp:property name="ga:accountId" value="85301"/>
24
+ <dxp:property name="ga:accountName" value="addictedtonew.com"/>
25
+ <dxp:property name="ga:profileId" value="47912"/>
26
+ <dxp:property name="ga:webPropertyId" value="UA-85301-1"/>
27
+ </entry>
28
+ <entry>
29
+ <id>http://www.google.com/analytics/feeds/accounts/ga:1897579</id>
30
+ <updated>2008-11-24T12:11:32.000-08:00</updated>
31
+ <title type="text">railstips.org</title>
32
+ <link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
33
+ <dxp:tableId>ga:1897579</dxp:tableId>
34
+ <dxp:property name="ga:accountId" value="85301"/>
35
+ <dxp:property name="ga:accountName" value="addictedtonew.com"/>
36
+ <dxp:property name="ga:profileId" value="1897579"/>
37
+ <dxp:property name="ga:webPropertyId" value="UA-85301-7"/>
38
+ </entry>
39
+ <entry>
40
+ <id>http://www.google.com/analytics/feeds/accounts/ga:17132454</id>
41
+ <updated>2009-04-22T23:21:23.000-07:00</updated>
42
+ <title type="text">johnnunemaker.com</title>
43
+ <link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
44
+ <dxp:tableId>ga:17132454</dxp:tableId>
45
+ <dxp:property name="ga:accountId" value="85301"/>
46
+ <dxp:property name="ga:accountName" value="addictedtonew.com"/>
47
+ <dxp:property name="ga:profileId" value="17132454"/>
48
+ <dxp:property name="ga:webPropertyId" value="UA-85301-25"/>
49
+ </entry>
50
+ <entry>
51
+ <id>http://www.google.com/analytics/feeds/accounts/ga:17132478</id>
52
+ <updated>2009-04-22T23:21:23.000-07:00</updated>
53
+ <title type="text">harmonyapp.com</title>
54
+ <link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
55
+ <dxp:tableId>ga:17132478</dxp:tableId>
56
+ <dxp:property name="ga:accountId" value="85301"/>
57
+ <dxp:property name="ga:accountName" value="addictedtonew.com"/>
58
+ <dxp:property name="ga:profileId" value="17132478"/>
59
+ <dxp:property name="ga:webPropertyId" value="UA-85301-26"/>
60
+ </entry>
61
+ </feed>
@@ -0,0 +1,52 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <commit>
3
+ <removed type="array">
4
+ <removed>
5
+ <filename>commands.rb</filename>
6
+ </removed>
7
+ <removed>
8
+ <filename>helpers.rb</filename>
9
+ </removed>
10
+ </removed>
11
+ <added type="array">
12
+ <added>
13
+ <filename>commands/commands.rb</filename>
14
+ </added>
15
+ <added>
16
+ <filename>commands/helpers.rb</filename>
17
+ </added>
18
+ </added>
19
+ <message>move commands.rb and helpers.rb into commands/ dir</message>
20
+ <modified type="array">
21
+ <modified>
22
+ <diff>@@ -56,7 +56,7 @@ module GitHub
23
+ end
24
+
25
+ def load(file)
26
+ - file[0] == ?/ ? super : super(BasePath + "/#{file}")
27
+ + file[0] == ?/ ? super : super(BasePath + "/commands/#{file}")
28
+ end
29
+
30
+ def debug(*messages)</diff>
31
+ <filename>lib/github.rb</filename>
32
+ </modified>
33
+ </modified>
34
+ <parents type="array">
35
+ <parent>
36
+ <id>d462d2a2e60438ded3dd9e8e6593ca4146c5a0ba</id>
37
+ </parent>
38
+ </parents>
39
+ <url>http://github.com/defunkt/github-gem/commit/c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b</url>
40
+ <author>
41
+ <name>Chris Wanstrath</name>
42
+ <email>chris@ozmm.org</email>
43
+ </author>
44
+ <id>c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b</id>
45
+ <committed-date>2008-03-02T16:45:41-08:00</committed-date>
46
+ <authored-date>2008-03-02T16:45:41-08:00</authored-date>
47
+ <tree>28a1a1ca3e663d35ba8bf07d3f1781af71359b76</tree>
48
+ <committer>
49
+ <name>Chris Wanstrath</name>
50
+ <email>chris@ozmm.org</email>
51
+ </committer>
52
+ </commit>
@@ -0,0 +1,89 @@
1
+ <aws:weather xmlns:aws="http://www.aws.com/aws">
2
+ <aws:api version="2.0"/>
3
+ <aws:WebURL>http://weather.weatherbug.com/IN/Carmel-weather.html?ZCode=Z5546&amp;Units=0&amp;stat=MOCAR</aws:WebURL>
4
+ <aws:ob>
5
+ <aws:ob-date>
6
+ <aws:year number="2008"/>
7
+ <aws:month number="12" text="December" abbrv="Dec"/>
8
+ <aws:day number="30" text="Tuesday" abbrv="Tue"/>
9
+ <aws:hour number="4" hour-24="16"/>
10
+ <aws:minute number="18"/>
11
+ <aws:second number="01"/>
12
+ <aws:am-pm abbrv="PM"/>
13
+ <aws:time-zone offset="-5" text="Eastern Standard Time" abbrv="EST"/>
14
+ </aws:ob-date>
15
+ <aws:requested-station-id>mocar</aws:requested-station-id>
16
+ <aws:station-id>MOCAR</aws:station-id>
17
+ <aws:station>Mohawk Trail ES</aws:station>
18
+ <aws:city-state zipcode="46033">Carmel, IN</aws:city-state>
19
+ <aws:country>USA</aws:country>
20
+ <aws:latitude>39.9711111111111</aws:latitude>
21
+ <aws:longitude>-86.0938888888889</aws:longitude>
22
+ <aws:site-url>http://www1.ccs.k12.in.us/mte/home</aws:site-url>
23
+ <aws:aux-temp units="&amp;deg;F">74</aws:aux-temp>
24
+ <aws:aux-temp-rate units="&amp;deg;F">+0.0</aws:aux-temp-rate>
25
+ <aws:current-condition icon="http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif">Sunny</aws:current-condition>
26
+ <aws:dew-point units="&amp;deg;F">35</aws:dew-point>
27
+ <aws:elevation units="ft">817</aws:elevation>
28
+ <aws:feels-like units="&amp;deg;F">51</aws:feels-like>
29
+ <aws:gust-time>
30
+ <aws:year number="0001"/>
31
+ <aws:month number="1" text="January" abbrv="Jan"/>
32
+ <aws:day number="1" text="Monday" abbrv="Mon"/>
33
+ <aws:hour number="12" hour-24="00"/>
34
+ <aws:minute number="00"/>
35
+ <aws:second number="00"/>
36
+ <aws:am-pm abbrv="AM"/>
37
+ <aws:time-zone offset="-5" text="Eastern Standard Time" abbrv="EST"/>
38
+ </aws:gust-time>
39
+ <aws:gust-direction>W</aws:gust-direction>
40
+ <aws:gust-speed units="mph">25</aws:gust-speed>
41
+ <aws:humidity units="%">53</aws:humidity>
42
+ <aws:humidity-high units="%">100.0</aws:humidity-high>
43
+ <aws:humidity-low units="%">42.5</aws:humidity-low>
44
+ <aws:humidity-rate>-5.0</aws:humidity-rate>
45
+ <aws:indoor-temp units="&amp;deg;F">75</aws:indoor-temp>
46
+ <aws:indoor-temp-rate units="&amp;deg;F">+0.0</aws:indoor-temp-rate>
47
+ <aws:light>28</aws:light>
48
+ <aws:light-rate>-1.5</aws:light-rate>
49
+ <aws:moon-phase moon-phase-img="http://api.wxbug.net/images/moonphase/mphase02.gif">-10</aws:moon-phase>
50
+ <aws:pressure units="&quot;">29.71</aws:pressure>
51
+ <aws:pressure-high units="&quot;">30.18</aws:pressure-high>
52
+ <aws:pressure-low units="&quot;">29.71</aws:pressure-low>
53
+ <aws:pressure-rate units="&quot;/h">-0.04</aws:pressure-rate>
54
+ <aws:rain-month units="&quot;">6.64</aws:rain-month>
55
+ <aws:rain-rate units="&quot;/h">0.00</aws:rain-rate>
56
+ <aws:rain-rate-max units="&quot;/h">0.00</aws:rain-rate-max>
57
+ <aws:rain-today units="&quot;">0.00</aws:rain-today>
58
+ <aws:rain-year units="&quot;">53.83</aws:rain-year>
59
+ <aws:temp units="&amp;deg;F">51.8</aws:temp>
60
+ <aws:temp-high units="&amp;deg;F">52</aws:temp-high>
61
+ <aws:temp-low units="&amp;deg;F">29</aws:temp-low>
62
+ <aws:temp-rate units="&amp;deg;F/h">+2.5</aws:temp-rate>
63
+ <aws:sunrise>
64
+ <aws:year number="2008"/>
65
+ <aws:month number="12" text="December" abbrv="Dec"/>
66
+ <aws:day number="30" text="Tuesday" abbrv="Tue"/>
67
+ <aws:hour number="8" hour-24="08"/>
68
+ <aws:minute number="06"/>
69
+ <aws:second number="02"/>
70
+ <aws:am-pm abbrv="AM"/>
71
+ <aws:time-zone offset="-5" text="Eastern Standard Time" abbrv="EST"/>
72
+ </aws:sunrise>
73
+ <aws:sunset>
74
+ <aws:year number="2008"/>
75
+ <aws:month number="12" text="December" abbrv="Dec"/>
76
+ <aws:day number="30" text="Tuesday" abbrv="Tue"/>
77
+ <aws:hour number="5" hour-24="17"/>
78
+ <aws:minute number="28"/>
79
+ <aws:second number="53"/>
80
+ <aws:am-pm abbrv="PM"/>
81
+ <aws:time-zone offset="-5" text="Eastern Standard Time" abbrv="EST"/>
82
+ </aws:sunset>
83
+ <aws:wet-bulb units="&amp;deg;F">44.24</aws:wet-bulb>
84
+ <aws:wind-speed units="mph">4</aws:wind-speed>
85
+ <aws:wind-speed-avg units="mph">7</aws:wind-speed-avg>
86
+ <aws:wind-direction>SSW</aws:wind-direction>
87
+ <aws:wind-direction-avg>SW</aws:wind-direction-avg>
88
+ </aws:ob>
89
+ </aws:weather>