rcap-rails-generators 1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/CHANGELOG.rdoc +25 -0
  2. data/README.rdoc +248 -0
  3. data/lib/generators/rcap/migrations/migrations_generator.rb +38 -0
  4. data/lib/generators/rcap/migrations/templates/alerts_migration.rb +27 -0
  5. data/lib/generators/rcap/migrations/templates/areas_migration.rb +14 -0
  6. data/lib/generators/rcap/migrations/templates/infos_migration.rb +33 -0
  7. data/lib/generators/rcap/migrations/templates/resources_migration.rb +14 -0
  8. data/lib/generators/rcap/models/models_generator.rb +33 -0
  9. data/lib/generators/rcap/models/templates/models/alert.rb +365 -0
  10. data/lib/generators/rcap/models/templates/models/area.rb +156 -0
  11. data/lib/generators/rcap/models/templates/models/circle.rb +76 -0
  12. data/lib/generators/rcap/models/templates/models/event_code.rb +20 -0
  13. data/lib/generators/rcap/models/templates/models/geocode.rb +20 -0
  14. data/lib/generators/rcap/models/templates/models/info.rb +452 -0
  15. data/lib/generators/rcap/models/templates/models/parameter.rb +64 -0
  16. data/lib/generators/rcap/models/templates/models/point.rb +51 -0
  17. data/lib/generators/rcap/models/templates/models/polygon.rb +75 -0
  18. data/lib/generators/rcap/models/templates/models/resource.rb +143 -0
  19. data/lib/generators/rcap/models/templates/modules/rcap.rb +5 -0
  20. data/lib/generators/rcap/models/templates/modules/validations.rb +116 -0
  21. data/spec/alert_spec.rb +195 -0
  22. data/spec/area_spec.rb +179 -0
  23. data/spec/circle_spec.rb +88 -0
  24. data/spec/geocode_spec.rb +38 -0
  25. data/spec/info_spec.rb +270 -0
  26. data/spec/point_spec.rb +46 -0
  27. data/spec/polygon_spec.rb +68 -0
  28. data/spec/resource_spec.rb +156 -0
  29. data/spec/spec_helper.rb +8 -0
  30. data/spec/utilities_spec.rb +57 -0
  31. data/spec/validations_spec.rb +95 -0
  32. metadata +155 -0
@@ -0,0 +1,156 @@
1
+ # An Area object is valid if
2
+ # * it has an area description
3
+ # * all Circle objects contained in circles are valid
4
+ # * all Geocode objects contained in geocodes are valid
5
+ # * all Polygon objects contained in polygons are valid
6
+ # * altitude has a value if ceiling is set
7
+ class Area < ActiveRecord::Base
8
+ include Validation
9
+
10
+ belongs_to :item
11
+
12
+ # Area Description - Textual description of area.
13
+ attr_accessor( :area_desc )
14
+ # Expressed in feet above sea level
15
+ attr_accessor( :altitude )
16
+ # Expressed in feet above sea level.
17
+ attr_accessor( :ceiling )
18
+ # Collection of Circle objects
19
+ attr_reader( :circles )
20
+ # Collection of Geocode objects
21
+ attr_reader( :geocodes )
22
+ # Collection of Polygon objects
23
+ attr_reader( :polygons )
24
+
25
+ validates_presence_of( :area_desc )
26
+ validates_collection_of( :circles, :geocodes, :polygons )
27
+ validates_dependency_of( :ceiling, :on => :altitude )
28
+
29
+ XML_ELEMENT_NAME = 'area' # :nodoc:
30
+ AREA_DESC_ELEMENT_NAME = 'areaDesc' # :nodoc:
31
+ ALTITUDE_ELEMENT_NAME = 'altitude' # :nodoc:
32
+ CEILING_ELEMENT_NAME = 'ceiling' # :nodoc:
33
+
34
+ XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
35
+ AREA_DESC_XPATH = "cap:#{ AREA_DESC_ELEMENT_NAME }" # :nodoc:
36
+ ALTITUDE_XPATH = "cap:#{ ALTITUDE_ELEMENT_NAME }" # :nodoc:
37
+ CEILING_XPATH = "cap:#{ CEILING_ELEMENT_NAME }" # :nodoc:
38
+
39
+ def initialize( attributes = {})
40
+ @area_desc = attributes[ :area_desc ]
41
+ @altitude = attributes[ :altitude ]
42
+ @ceiling = attributes[ :ceiling ]
43
+ @circles = Array( attributes[ :circles ])
44
+ @geocodes = Array( attributes[ :geocodes ])
45
+ @polygons = Array( attributes[ :polygons ])
46
+ end
47
+
48
+ def to_xml_element # :nodoc:
49
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
50
+ xml_element.add_element( AREA_DESC_ELEMENT_NAME ).add_text( @area_desc.to_s )
51
+ add_to_xml_element = lambda do |element, object|
52
+ element.add_element( object.to_xml_element )
53
+ element
54
+ end
55
+ @polygons.inject( xml_element, &add_to_xml_element )
56
+ @circles.inject( xml_element, &add_to_xml_element )
57
+ @geocodes.inject( xml_element, &add_to_xml_element )
58
+ xml_element.add_element( ALTITUDE_ELEMENT_NAME ).add_text( @altitude.to_s ) unless self.altitude.blank?
59
+ xml_element.add_element( CEILING_ELEMENT_NAME ).add_text( @ceiling.to_s ) unless self.altitude.blank?
60
+ xml_element
61
+ end
62
+
63
+ def to_xml # :nodoc:
64
+ self.to_xml_element.to_s
65
+ end
66
+
67
+ # Implements an equality operator for the Area object. Two Area objects are equal if all their attributes are equal.
68
+ def ==( other )
69
+ comparison_attributes = lambda{ |area| [ area.area_desc, area.altitude, area.ceiling, area.circles, area.geocodes, area.polygons ]}
70
+ comparison_attributes.call( self ) == comparison_attributes.call( other )
71
+ end
72
+
73
+ def inspect # :nodoc:
74
+ area_inspect = <<EOF
75
+ Area Description: #{ self.area_desc }
76
+ Polygons:
77
+ #{ self.polygons.map{ |polygon| " " + polygon.inspect }.join("\n" )}
78
+ Circles: #{ self.circles.inspect }
79
+ Geocodes: #{ self.geocodes.inspect }
80
+ EOF
81
+ RCAP.format_lines_for_inspect( 'AREA', area_inspect )
82
+ end
83
+
84
+ # Returns a string representation of the area of the form
85
+ # area_desc
86
+ def to_s
87
+ self.area_desc
88
+ end
89
+
90
+ def self.from_xml_element( area_xml_element ) # :nodoc:
91
+ RCAP::Area.new( :area_desc => RCAP.xpath_text( area_xml_element, AREA_DESC_XPATH ),
92
+ :altitude => (( alt = RCAP.xpath_text( area_xml_element, ALTITUDE_XPATH )) ? alt.to_f : nil ),
93
+ :ceiling => (( ceil = RCAP.xpath_text( area_xml_element, CEILING_XPATH )) ? ceil.to_f : nil ),
94
+ :circles => RCAP.xpath_match( area_xml_element, RCAP::Circle::XPATH ).map{ |circle_element| RCAP::Circle.from_xml_element( circle_element )},
95
+ :geocodes => RCAP.xpath_match( area_xml_element, RCAP::Geocode::XPATH ).map{ |geocode_element| RCAP::Geocode.from_xml_element( geocode_element )},
96
+ :polygons => RCAP.xpath_match( area_xml_element, RCAP::Polygon::XPATH ).map{ |polygon_element| RCAP::Polygon.from_xml_element( polygon_element )})
97
+ end
98
+
99
+ AREA_DESC_YAML = 'Area Description' # :nodoc:
100
+ ALTITUDE_YAML = 'Altitude' # :nodoc:
101
+ CEILING_YAML = 'Ceiling' # :nodoc:
102
+ CIRCLES_YAML = 'Circles' # :nodoc:
103
+ GEOCODES_YAML = 'Geocodes' # :nodoc:
104
+ POLYGONS_YAML = 'Polygons' # :nodoc:
105
+
106
+ def to_yaml( options = {} ) # :nodoc:
107
+ circles_yaml = self.circles.map{ |circle| [ circle.lattitude, circle.longitude, circle.radius ]}
108
+ def circles_yaml.to_yaml_style; :inline; end
109
+
110
+ RCAP.attribute_values_to_hash(
111
+ [ AREA_DESC_YAML, self.area_desc ],
112
+ [ ALTITUDE_YAML, self.altitude ],
113
+ [ CEILING_YAML, self.ceiling ],
114
+ [ CIRCLES_YAML, circles_yaml ],
115
+ [ GEOCODES_YAML, self.geocodes.inject({}){|h,geocode| h.merge( geocode.name => geocode.value )}],
116
+ [ POLYGONS_YAML, self.polygons ]
117
+ ).to_yaml( options )
118
+ end
119
+
120
+ def from_yaml_data( area_yaml_data ) # :nodoc:
121
+ Area.new(
122
+ :area_desc => area_yaml_data[ AREA_DESC_YAML ],
123
+ :altitude => area_yaml_data[ ALTITUDE_YAML ],
124
+ :ceiling => area_yaml_data[ CEILING_YAML ],
125
+ :circles => Array( area_yaml_data[ CIRCLES_YAML ]).map{ |circle_yaml_data| RCAP::Circle.from_yaml_data( circle_yaml_data )},
126
+ :geocodes => Array( area_yaml_data[ GEOCODES_YAML ]).map{ |name, value| RCAP::Geocode.new( :name => name, :value => value )},
127
+ :polygons => Array( area_yaml_data[ POLYGONS_YAML ]).map{ |polyon_yaml_data| RCAP::Polygon.from_yaml_data( polyon_yaml_data )}
128
+ )
129
+ end
130
+
131
+ AREA_DESC_KEY = 'area_desc' # :nodoc:
132
+ ALTITUDE_KEY = 'altitude' # :nodoc:
133
+ CEILING_KEY = 'ceiling' # :nodoc:
134
+ CIRCLES_KEY = 'circles' # :nodoc:
135
+ GEOCODES_KEY = 'geocodes' # :nodoc:
136
+ POLYGONS_KEY = 'polygons' # :nodoc:
137
+
138
+ def to_h # :nodoc:
139
+ { AREA_DESC_KEY => self.area_desc,
140
+ ALTITUDE_KEY => self.altitude,
141
+ CEILING_KEY => self.ceiling,
142
+ CIRCLES_KEY => self.circles.map{ |circle| circle.to_h },
143
+ GEOCODES_KEY => self.geocodes.map{ |geocode| geocode.to_h },
144
+ POLYGONS_KEY => self.polygons.map{ |polygon| polygon.to_h }}
145
+ end
146
+
147
+ def self.from_h( area_hash ) # :nodoc:
148
+ self.new(
149
+ :area_desc => area_hash[ AREA_DESC_KEY ],
150
+ :altitude => area_hash[ ALTITUDE_KEY ],
151
+ :ceiling => area_hash[ CEILING_KEY ],
152
+ :circles => area_hash[ CIRCLES_KEY ].map{ |circle_hash| RCAP::Circle.from_h( circle_hash )},
153
+ :geocodes => area_hash[ GEOCODES_KEY ].map{ |geocode_hash| RCAP::Geocode.from_h( geocode_hash )},
154
+ :polygons => area_hash[ POLYGONS_KEY ].map{ |polygon_hash| RCAP::Polygon.from_h( polygon_hash )})
155
+ end
156
+ end
@@ -0,0 +1,76 @@
1
+ # A Circle object is valid if
2
+ # * it has a valid lattitude and longitude
3
+ # * it has a radius with a value greater than zero
4
+ class Circle < Point
5
+ include Validation
6
+
7
+ # Expresed in kilometers
8
+ attr_accessor( :radius )
9
+
10
+ validates_presence_of( :radius )
11
+ validates_numericality_of( :radius , :greater_than => 0 )
12
+
13
+ XML_ELEMENT_NAME = 'circle' # :nodoc:
14
+
15
+ XPATH = 'cap:circle' # :nodoc:
16
+
17
+ def initialize( attributes = {} )
18
+ super( attributes )
19
+ @radius = attributes[ :radius ]
20
+ end
21
+
22
+ # Returns a string representation of the circle of the form
23
+ # lattitude,longitude,radius
24
+ def to_s # :nodoc:
25
+ "#{ self.lattitude },#{ self.longitude },#{ self.radius }"
26
+ end
27
+
28
+ def inspect # :nodoc:
29
+ "(#{ self.lattitude},#{ self.longitude },#{ self.radius })"
30
+ end
31
+
32
+ def to_xml_element # :nodoc:
33
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
34
+ xml_element.add_text( self.to_s )
35
+ xml_element
36
+ end
37
+
38
+ def to_xml # :nodoc:
39
+ self.to_xml_element.to_s
40
+ end
41
+
42
+ def self.parse_circle_string( circle_string ) # :nodoc:
43
+ lattitude, longitude, radius = circle_string.split( ',' )
44
+ [ lattitude, longitude, radius ].map{ |e| e.to_f }
45
+ end
46
+
47
+ def self.from_xml_element( circle_xml_element ) # :nodoc:
48
+ lattitude, longitude, radius = self.parse_circle_string( circle_xml_element.text )
49
+ circle = self.new( :lattitude => lattitude,
50
+ :longitude => longitude,
51
+ :radius => radius )
52
+ end
53
+
54
+ # Two circles are equivalent if their lattitude, longitude and radius are equal.
55
+ def ==( other )
56
+ [ self.lattitude, self.longitude, self.radius ] == [ other.lattitude, other.longitude, other.radius ]
57
+ end
58
+
59
+ def self.from_yaml_data( circle_yaml_data ) # :nodoc:
60
+ lattitude, longitude,radius = circle_yaml_data
61
+ self.new( :lattitude => lattitude, :longitude => longitude, :radius => radius )
62
+ end
63
+
64
+ RADIUS_KEY = 'radius' # :nodoc:
65
+ LATTITUDE_KEY = 'lattitude' # :nodoc:
66
+ LONGITUDE_KEY = 'longitude' # :nodoc:
67
+ def to_h # :nodoc:
68
+ RCAP.attribute_values_to_hash( [ RADIUS_KEY, self.radius ],
69
+ [ LATTITUDE_KEY, self.lattitude ],
70
+ [ LONGITUDE_KEY, self.longitude ])
71
+ end
72
+
73
+ def self.from_h( circle_hash ) # :nodoc:
74
+ self.new( :radius => circle_hash[ RADIUS_KEY ], :lattitude => circle_hash[ LATTITUDE_KEY ], :longitude => circle_hash[ LONGITUDE_KEY ])
75
+ end
76
+ end
@@ -0,0 +1,20 @@
1
+ module RCAP
2
+ class EventCode < Parameter
3
+
4
+ XML_ELEMENT_NAME = 'eventCode' # :nodoc:
5
+
6
+ XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
7
+
8
+ def to_xml_element # :nodoc:
9
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
10
+ xml_element.add_element( NAME_ELEMENT_NAME ).add_text( self.name )
11
+ xml_element.add_element( VALUE_ELEMENT_NAME ).add_text( self.value )
12
+ xml_element
13
+ end
14
+
15
+ def self.from_xml_element( event_code_xml_element ) # :nodoc:
16
+ EventCode.new( :name => RCAP.xpath_text( event_code_xml_element, NAME_XPATH ),
17
+ :value => RCAP.xpath_text( event_code_xml_element, VALUE_XPATH ))
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module RCAP
2
+ class Geocode < Parameter
3
+
4
+ XML_ELEMENT_NAME = 'geocode' # :nodoc:
5
+
6
+ XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
7
+
8
+ def to_xml_element # :nodoc:
9
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
10
+ xml_element.add_element( NAME_ELEMENT_NAME ).add_text( @name )
11
+ xml_element.add_element( VALUE_ELEMENT_NAME ).add_text( @value )
12
+ xml_element
13
+ end
14
+
15
+ def self.from_xml_element( geocode_xml_element ) # :nodoc:
16
+ self.new( :name => RCAP.xpath_text( geocode_xml_element, NAME_XPATH ),
17
+ :value => RCAP.xpath_text( geocode_xml_element, VALUE_XPATH ))
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,452 @@
1
+ # In Info object is valid if
2
+ # * it has an event
3
+ # * it has an urgency with a valid value
4
+ # * it has a severity with a valid value
5
+ # * it has a certainty with a valid value
6
+ # * all categories are valid and categories has at minimum 1 entry
7
+ # * all Resource objects in the resources collection are valid
8
+ # * all Area objects in the areas collection are valid
9
+ #
10
+ class Info < ActiveRecord::Base
11
+ include Validation
12
+
13
+ belongs_to :alert
14
+ has_many :areas
15
+ has_many :resources
16
+
17
+ serialize :categories
18
+ serialize :response_types
19
+ serialize :event_codes
20
+ serialize :parameters
21
+
22
+ before_create :set_language
23
+
24
+ CATEGORY_GEO = "Geo" # :nodoc:
25
+ CATEGORY_MET = "Met" # :nodoc:
26
+ CATEGORY_SAFETY = "Safety" # :nodoc:
27
+ CATEGORY_SECURITY = "Security" # :nodoc:
28
+ CATEGORY_RESCUE = "Rescue" # :nodoc:
29
+ CATEGORY_FIRE = "Fire" # :nodoc:
30
+ CATEGORY_HEALTH = "Health" # :nodoc:
31
+ CATEGORY_ENV = "Env" # :nodoc:
32
+ CATEGORY_TRANSPORT = "Transport" # :nodoc:
33
+ CATEGORY_INFRA = "Infra" # :nodoc:
34
+ CATEGORY_CBRNE = "CBRNE" # :nodoc:
35
+ CATEGORY_OTHER = "Other" # :nodoc:
36
+ # Valid values for categories
37
+ VALID_CATEGORIES = [ CATEGORY_GEO, CATEGORY_MET, CATEGORY_SAFETY,
38
+ CATEGORY_SECURITY, CATEGORY_RESCUE, CATEGORY_FIRE, CATEGORY_HEALTH,
39
+ CATEGORY_ENV, CATEGORY_TRANSPORT, CATEGORY_INFRA, CATEGORY_CBRNE,
40
+ CATEGORY_OTHER ]
41
+
42
+ RESPONSE_TYPE_SHELTER = "Shelter" # :nodoc:
43
+ RESPONSE_TYPE_EVACUATE = "Evacuate" # :nodoc:
44
+ RESPONSE_TYPE_PREPARE = "Prepare" # :nodoc:
45
+ RESPONSE_TYPE_EXECUTE = "Execute" # :nodoc:
46
+ RESPONSE_TYPE_MONITOR = "Monitor" # :nodoc:
47
+ RESPONSE_TYPE_ASSESS = "Assess" # :nodoc:
48
+ RESPONSE_TYPE_NONE = "None" # :nodoc:
49
+ # Valid values for response_type
50
+ VALID_RESPONSE_TYPES = [ RESPONSE_TYPE_SHELTER, RESPONSE_TYPE_EVACUATE,
51
+ RESPONSE_TYPE_PREPARE, RESPONSE_TYPE_EXECUTE, RESPONSE_TYPE_MONITOR,
52
+ RESPONSE_TYPE_ASSESS, RESPONSE_TYPE_NONE ]
53
+
54
+ URGENCY_IMMEDIATE = "Immediate" # :nodoc:
55
+ URGENCY_EXPECTED = "Expected" # :nodoc:
56
+ URGENCY_FUTURE = "Future" # :nodoc:
57
+ URGENCY_PAST = "Past" # :nodoc:
58
+ URGENCY_UNKNOWN = "Unknown" # :nodoc:
59
+ # Valid values for urgency
60
+ VALID_URGENCIES = [ URGENCY_IMMEDIATE, URGENCY_EXPECTED, URGENCY_FUTURE,
61
+ URGENCY_PAST, URGENCY_UNKNOWN ]
62
+
63
+ SEVERITY_EXTREME = "Extreme" # :nodoc:
64
+ SEVERITY_SEVERE = "Severe" # :nodoc:
65
+ SEVERITY_MODERATE = "Moderate" # :nodoc:
66
+ SEVERITY_MINOR = "Minor" # :nodoc:
67
+ SEVERITY_UNKNOWN = "Unknown" # :nodoc:
68
+ # Valid values for severity
69
+ VALID_SEVERITIES = [ SEVERITY_EXTREME, SEVERITY_SEVERE, SEVERITY_MODERATE,
70
+ SEVERITY_MINOR, SEVERITY_UNKNOWN ]
71
+
72
+ CERTAINTY_OBSERVED = "Observed" # :nodoc:
73
+ CERTAINTY_LIKELY = "Likely" # :nodoc:
74
+ CERTAINTY_POSSIBLE = "Possible" # :nodoc:
75
+ CERTAINTY_UNLIKELY = "Unlikely" # :nodoc:
76
+ CERTAINTY_UNKNOWN = "Unknown" # :nodoc:
77
+ # Valid valies for certainty
78
+ VALID_CERTAINTIES = [ CERTAINTY_OBSERVED, CERTAINTY_LIKELY,
79
+ CERTAINTY_POSSIBLE, CERTAINTY_UNLIKELY, CERTAINTY_UNKNOWN ]
80
+
81
+ XML_ELEMENT_NAME = 'info' # :nodoc:
82
+ LANGUAGE_ELEMENT_NAME = 'language' # :nodoc:
83
+ CATEGORY_ELEMENT_NAME = 'category' # :nodoc:
84
+ EVENT_ELEMENT_NAME = 'event' # :nodoc:
85
+ RESPONSE_TYPE_ELEMENT_NAME = 'responseType' # :nodoc:
86
+ URGENCY_ELEMENT_NAME = 'urgency' # :nodoc:
87
+ SEVERITY_ELEMENT_NAME = 'severity' # :nodoc:
88
+ CERTAINTY_ELEMENT_NAME = 'certainty' # :nodoc:
89
+ AUDIENCE_ELEMENT_NAME = 'audience' # :nodoc:
90
+ EVENT_CODE_ELEMENT_NAME = 'eventCode' # :nodoc:
91
+ EFFECTIVE_ELEMENT_NAME = 'effective' # :nodoc:
92
+ ONSET_ELEMENT_NAME = 'onset' # :nodoc:
93
+ EXPIRES_ELEMENT_NAME = 'expires' # :nodoc:
94
+ SENDER_NAME_ELEMENT_NAME = 'sernderName' # :nodoc:
95
+ HEADLINE_ELEMENT_NAME = 'headline' # :nodoc:
96
+ DESCRIPTION_ELEMENT_NAME = 'description' # :nodoc:
97
+ INSTRUCTION_ELEMENT_NAME = 'instruction' # :nodoc:
98
+ WEB_ELEMENT_NAME = 'web' # :nodoc:
99
+ CONTACT_ELEMENT_NAME = 'contact' # :nodoc:
100
+
101
+ XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
102
+ LANGUAGE_XPATH = "cap:#{ LANGUAGE_ELEMENT_NAME }" # :nodoc:
103
+ EVENT_XPATH = "cap:#{ EVENT_ELEMENT_NAME }" # :nodoc:
104
+ URGENCY_XPATH = "cap:#{ URGENCY_ELEMENT_NAME }" # :nodoc:
105
+ RESPONSE_TYPE_XPATH = "cap:#{ RESPONSE_TYPE_ELEMENT_NAME }" # :nodoc:
106
+ CATEGORY_XPATH = "cap:#{ CATEGORY_ELEMENT_NAME }" # :nodoc:
107
+ SEVERITY_XPATH = "cap:#{ SEVERITY_ELEMENT_NAME }" # :nodoc:
108
+ CERTAINTY_XPATH = "cap:#{ CERTAINTY_ELEMENT_NAME }" # :nodoc:
109
+ AUDIENCE_XPATH = "cap:#{ AUDIENCE_ELEMENT_NAME }" # :nodoc:
110
+ EVENT_CODE_XPATH = "cap:#{ EVENT_CODE_ELEMENT_NAME }" # :nodoc:
111
+ EFFECTIVE_XPATH = "cap:#{ EFFECTIVE_ELEMENT_NAME }" # :nodoc:
112
+ ONSET_XPATH = "cap:#{ ONSET_ELEMENT_NAME }" # :nodoc:
113
+ EXPIRES_XPATH = "cap:#{ EXPIRES_ELEMENT_NAME }" # :nodoc:
114
+ SENDER_NAME_XPATH = "cap:#{ SENDER_NAME_ELEMENT_NAME }" # :nodoc:
115
+ HEADLINE_XPATH = "cap:#{ HEADLINE_ELEMENT_NAME }" # :nodoc:
116
+ DESCRIPTION_XPATH = "cap:#{ DESCRIPTION_ELEMENT_NAME }" # :nodoc:
117
+ INSTRUCTION_XPATH = "cap:#{ INSTRUCTION_ELEMENT_NAME }" # :nodoc:
118
+ WEB_XPATH = "cap:#{ WEB_ELEMENT_NAME }" # :nodoc:
119
+ CONTACT_XPATH = "cap:#{ CONTACT_ELEMENT_NAME }" # :nodoc:
120
+
121
+ DEFAULT_LANGUAGE = 'en-US'
122
+
123
+ validates_presence_of( :event, :urgency, :severity, :certainty )
124
+ validates_length_of( :categories, :minimum => 1 )
125
+ validates_inclusion_of( :certainty, :allow_nil => true, :in => VALID_CERTAINTIES, :message => "can only be assigned the following values: #{ VALID_CERTAINTIES.join(', ') }")
126
+ validates_inclusion_of( :severity, :allow_nil => true, :in => VALID_SEVERITIES, :message => "can only be assigned the following values: #{ VALID_SEVERITIES.join(', ') }" )
127
+ validates_inclusion_of( :urgency, :allow_nil => true, :in => VALID_URGENCIES, :message => "can only be assigned the following values: #{ VALID_URGENCIES.join(', ') }" )
128
+ validates_inclusion_of_members_of( :response_types, :in => VALID_RESPONSE_TYPES, :allow_blank => true )
129
+ validates_inclusion_of_members_of( :categories, :in => VALID_CATEGORIES, :allow_blank => true )
130
+ validates_collection_of( :resources, :areas )
131
+
132
+ # attr_accessor( :event )
133
+ # # Value can only be one of VALID_URGENCIES
134
+ # attr_accessor( :urgency )
135
+ # # Value can only be one of VALID_SEVERITIES
136
+ # attr_accessor( :severity )
137
+ # # Value can only be one of VALID_CERTAINTIES
138
+ # attr_accessor( :certainty )
139
+ # attr_accessor( :language )
140
+ # attr_accessor( :audience )
141
+ # # Effective start time of information
142
+ # attr_accessor( :effective )
143
+ # # Expected start of event
144
+ # attr_accessor( :onset )
145
+ # # Effective expiry time of information
146
+ # attr_accessor( :expires )
147
+ # attr_accessor( :sender_name )
148
+ # attr_accessor( :headline )
149
+ # attr_accessor( :description )
150
+ # attr_accessor( :instruction )
151
+ # attr_accessor( :web )
152
+ # attr_accessor( :contact )
153
+ #
154
+ # # Collection of textual categories; elements can be one of VALID_CATEGORIES
155
+ # attr_reader( :categories )
156
+ # # Collection of textual response types
157
+ # attr_reader( :response_types )
158
+ # # Collectoin of EventCode objects
159
+ # attr_reader( :event_codes )
160
+ # # Collection of Parameter objects
161
+ # attr_reader( :parameters )
162
+ # # Collection of Resource objects
163
+ # attr_reader( :resources )
164
+ # # Collection of Area objects
165
+ # attr_reader( :areas )
166
+
167
+ # def initialize( attributes = {} )
168
+ # @language = attributes[ :language ] || DEFAULT_LANGUAGE
169
+ # @categories = Array( attributes[ :categories ])
170
+ # @event = attributes [ :event ]
171
+ # @response_types = Array( attributes[ :response_types ])
172
+ # @urgency = attributes[ :urgency ]
173
+ # @severity = attributes[ :severity ]
174
+ # @certainty = attributes[ :certainty ]
175
+ # @effective = attributes[ :effective ]
176
+ # @onset = attributes[ :onset ]
177
+ # @expires = attributes[ :expires ]
178
+ # @event_codes = Array( attributes[ :event_codes ])
179
+ # @sender_name = attributes[ :sender_name ]
180
+ # @headline = attributes[ :headline ]
181
+ # @description = attributes[ :description ]
182
+ # @instruction = attributes[ :instruction ]
183
+ # @web = attributes[ :web ]
184
+ # @contact = attributes[ :contact ]
185
+ # @parameters = Array( attributes[ :parameters ])
186
+ # @resources = Array( attributes[ :resources ])
187
+ # @areas = Array( attributes[ :areas ])
188
+ # end
189
+
190
+ def to_xml_element # :nodoc:
191
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
192
+ xml_element.add_element( LANGUAGE_ELEMENT_NAME ).add_text( self.language ) if self.language
193
+ @categories.each do |category|
194
+ xml_element.add_element( CATEGORY_ELEMENT_NAME ).add_text( category )
195
+ end
196
+ xml_element.add_element( EVENT_ELEMENT_NAME ).add_text( self.event )
197
+ @response_types.each do |response_type|
198
+ xml_element.add_element( RESPONSE_TYPE_ELEMENT_NAME ).add_text( response_type )
199
+ end
200
+ xml_element.add_element( URGENCY_ELEMENT_NAME ).add_text( self.urgency )
201
+ xml_element.add_element( SEVERITY_ELEMENT_NAME ).add_text( self.severity )
202
+ xml_element.add_element( CERTAINTY_ELEMENT_NAME ).add_text( self.certainty )
203
+ xml_element.add_element( AUDIENCE_ELEMENT_NAME ).add_text( self.audience ) if self.audience
204
+ @event_codes.each do |event_code|
205
+ xml_element.add_element( event_code.to_xml_element )
206
+ end
207
+ xml_element.add_element( EFFECTIVE_ELEMENT_NAME ).add_text( self.effective_at.to_s_for_cap ) if self.effective_at
208
+ xml_element.add_element( ONSET_ELEMENT_NAME ).add_text( self.onset_at.to_s_for_cap ) if self.onset_at
209
+ xml_element.add_element( EXPIRES_ELEMENT_NAME ).add_text( self.expires_at.to_s_for_cap ) if self.expires_at
210
+ xml_element.add_element( SENDER_NAME_ELEMENT_NAME ).add_text( self.sender_name ) if self.sender_name
211
+ xml_element.add_element( HEADLINE_ELEMENT_NAME ).add_text( self.headline ) if self.headline
212
+ xml_element.add_element( DESCRIPTION_ELEMENT_NAME ).add_text( self.description ) if self.description
213
+ xml_element.add_element( INSTRUCTION_ELEMENT_NAME ).add_text( self.instruction ) if self.instruction
214
+ xml_element.add_element( WEB_ELEMENT_NAME ).add_text( self.web ) if self.web
215
+ xml_element.add_element( CONTACT_ELEMENT_NAME ).add_text( self.contact ) if self.contact
216
+ @parameters.each do |parameter|
217
+ xml_element.add_element( parameter.to_xml_element )
218
+ end
219
+ @resources.each do |resource|
220
+ xml_element.add_element( resource.to_xml_element )
221
+ end
222
+ @areas.each do |area|
223
+ xml_element.add_element( area.to_xml_element )
224
+ end
225
+ xml_element
226
+ end
227
+
228
+ def to_xml # :nodoc:
229
+ self.to_xml_element.to_s
230
+ end
231
+
232
+ def inspect # :nodoc:
233
+ info_inspect = <<EOF
234
+ Language: #{ self.language }
235
+ Categories: #{ self.categories.to_s_for_cap }
236
+ Event: #{ self.event }
237
+ Response Types: #{ self.response_types.to_s_for_cap }
238
+ Urgency: #{ self.urgency }
239
+ Severity: #{ self.severity }
240
+ Certainty: #{ self.certainty }
241
+ Audience: #{ self.audience }
242
+ Event Codes: #{ self.event_codes.inspect }
243
+ Effective: #{ self.effective_at }
244
+ Onset: #{ self.onset_at }
245
+ Expires: #{ self.expires_at }
246
+ Sender Name: #{ self.sender_name }
247
+ Headline: #{ self.headline }
248
+ Description:
249
+ #{ self.description.to_s.lines.map{ |line| " " + line }.join }
250
+ Instruction: #{ self.instruction }
251
+ Web: #{ self.web }
252
+ Contact: #{ self.contact }
253
+ Parameters:
254
+ #{ self.parameters.map{ |parameter| parameter.inspect }.join( "\n" )}
255
+ Resources:
256
+ #{ self.resources.map{ |resource| " " + resource.inspect }.join( "\n" )}
257
+ Area:
258
+ #{ self.areas.map{ |area| " #{ area }" }.join( "\n" )}
259
+ EOF
260
+ RCAP.format_lines_for_inspect( 'INFO', info_inspect )
261
+ end
262
+
263
+ # Returns a string representation of the event of the form
264
+ # event(urgency/severity/certainty)
265
+ def to_s
266
+ "#{ self.event }(#{ self.urgency }/#{ self.severity }/#{ self.certainty })"
267
+ end
268
+
269
+ def self.from_xml_element( info_xml_element ) # :nodoc:
270
+ self.new(
271
+ :language => RCAP.xpath_text( info_xml_element, LANGUAGE_XPATH ) || DEFAULT_LANGUAGE,
272
+ :categories => RCAP.xpath_match( info_xml_element, CATEGORY_XPATH ).map{ |element| element.text },
273
+ :event => RCAP.xpath_text( info_xml_element, EVENT_XPATH ),
274
+ :response_types => RCAP.xpath_match( info_xml_element, RESPONSE_TYPE_XPATH ).map{ |element| element.text },
275
+ :urgency => RCAP.xpath_text( info_xml_element, URGENCY_XPATH ),
276
+ :severity => RCAP.xpath_text( info_xml_element, SEVERITY_XPATH ),
277
+ :certainty => RCAP.xpath_text( info_xml_element, CERTAINTY_XPATH ),
278
+ :audience => RCAP.xpath_text( info_xml_element, AUDIENCE_XPATH ),
279
+ :effective_at => (( effective = RCAP.xpath_first( info_xml_element, EFFECTIVE_XPATH )) ? DateTime.parse( effective.text ) : nil ),
280
+ :onset_at => (( onset = RCAP.xpath_first( info_xml_element, ONSET_XPATH )) ? DateTime.parse( onset.text ) : nil ),
281
+ :expires_at => (( expires = RCAP.xpath_first( info_xml_element, EXPIRES_XPATH )) ? DateTime.parse( expires.text ) : nil ),
282
+ :sender_name => RCAP.xpath_text( info_xml_element, SENDER_NAME_XPATH ),
283
+ :headline => RCAP.xpath_text( info_xml_element, HEADLINE_XPATH ),
284
+ :description => RCAP.xpath_text( info_xml_element, DESCRIPTION_XPATH ),
285
+ :instruction => RCAP.xpath_text( info_xml_element, INSTRUCTION_XPATH ),
286
+ :web => RCAP.xpath_text( info_xml_element, WEB_XPATH ),
287
+ :contact => RCAP.xpath_text( info_xml_element, CONTACT_XPATH ),
288
+ :event_codes => RCAP.xpath_match( info_xml_element, RCAP::EventCode::XPATH ).map{ |element| RCAP::EventCode.from_xml_element( element )},
289
+ :parameters => RCAP.xpath_match( info_xml_element, RCAP::Parameter::XPATH ).map{ |element| RCAP::Parameter.from_xml_element( element )},
290
+ :resources => RCAP.xpath_match( info_xml_element, RCAP::Resource::XPATH ).map{ |element| RCAP::Resource.from_xml_element( element )},
291
+ :areas => RCAP.xpath_match( info_xml_element, RCAP::Area::XPATH ).map{ |element| RCAP::Area.from_xml_element( element )}
292
+ )
293
+ end
294
+
295
+ LANGUAGE_YAML = 'Language' # :nodoc:
296
+ CATEGORIES_YAML = 'Categories' # :nodoc:
297
+ EVENT_YAML = 'Event' # :nodoc:
298
+ RESPONSE_TYPES_YAML = 'Response Types' # :nodoc:
299
+ URGENCY_YAML = 'Urgency' # :nodoc:
300
+ SEVERITY_YAML = 'Severity' # :nodoc:
301
+ CERTAINTY_YAML = 'Certainty' # :nodoc:
302
+ AUDIENCE_YAML = 'Audience' # :nodoc:
303
+ EFFECTIVE_YAML = 'Effective' # :nodoc:
304
+ ONSET_YAML = 'Onset' # :nodoc:
305
+ EXPIRES_YAML = 'Expires' # :nodoc:
306
+ SENDER_NAME_YAML = 'Sender Name' # :nodoc:
307
+ HEADLINE_YAML = 'Headline' # :nodoc:
308
+ DESCRIPTION_YAML = 'Description' # :nodoc:
309
+ INSTRUCTION_YAML = 'Instruction' # :nodoc:
310
+ WEB_YAML = 'Web' # :nodoc:
311
+ CONTACT_YAML = 'Contact' # :nodoc:
312
+ EVENT_CODES_YAML = 'Event Codes' # :nodoc:
313
+ PARAMETERS_YAML = 'Parameters' # :nodoc:
314
+ RESOURCES_YAML = 'Resources' # :nodoc:
315
+ AREAS_YAML = 'Areas' # :nodoc:
316
+
317
+ def to_yaml( options = {} ) # :nodoc:
318
+ response_types_yaml = self.response_types
319
+ def response_types_yaml.to_yaml_style; :inline; end
320
+
321
+ categories_yaml = self.categories
322
+ def categories_yaml.to_yaml_style; :inline; end
323
+
324
+ parameter_to_hash = lambda{ |hash, parameter| hash.merge( parameter.name => parameter.value )}
325
+
326
+ RCAP.attribute_values_to_hash(
327
+ [ LANGUAGE_YAML, self.language ],
328
+ [ CATEGORIES_YAML, categories_yaml ],
329
+ [ EVENT_YAML, self.event ],
330
+ [ RESPONSE_TYPES_YAML, response_types_yaml ],
331
+ [ URGENCY_YAML, self.urgency ],
332
+ [ SEVERITY_YAML, self.severity ],
333
+ [ CERTAINTY_YAML, self.certainty ],
334
+ [ AUDIENCE_YAML, self.audience ],
335
+ [ EFFECTIVE_YAML, self.effective_at ],
336
+ [ ONSET_YAML, self.onset_at ],
337
+ [ EXPIRES_YAML, self.expires_at ],
338
+ [ SENDER_NAME_YAML, self.sender_name ],
339
+ [ HEADLINE_YAML, self.headline ],
340
+ [ DESCRIPTION_YAML, self.description ],
341
+ [ INSTRUCTION_YAML, self.instruction ],
342
+ [ WEB_YAML, self.web ],
343
+ [ CONTACT_YAML, self.contact ],
344
+ [ EVENT_CODES_YAML, self.event_codes.inject({}, &parameter_to_hash )],
345
+ [ PARAMETERS_YAML, self.parameters.inject({}, &parameter_to_hash )],
346
+ [ RESOURCES_YAML, self.resources ],
347
+ [ AREAS_YAML, self.areas ]
348
+ ).to_yaml( options )
349
+ end
350
+
351
+ def self.from_yaml_data( info_yaml_data ) # :nodoc:
352
+ self.new(
353
+ :language => info_yaml_data [ LANGUAGE_YAML ],
354
+ :categories => info_yaml_data [ CATEGORIES_YAML ],
355
+ :event => info_yaml_data [ EVENT_YAML ],
356
+ :response_types => info_yaml_data [ RESPONSE_TYPES_YAML ],
357
+ :urgency => info_yaml_data [ URGENCY_YAML ],
358
+ :severity => info_yaml_data [ SEVERITY_YAML ],
359
+ :certainty => info_yaml_data [ CERTAINTY_YAML ],
360
+ :audience => info_yaml_data [ AUDIENCE_YAML ],
361
+ :effective_at => ( effective = info_yaml_data[ EFFECTIVE_YAML ]).blank? ? nil : DateTime.parse( effective.to_s ),
362
+ :onset_at => ( onset = info_yaml_data[ ONSET_YAML ]).blank? ? nil : DateTime.parse( onset.to_s ),
363
+ :expires_at => ( expires = info_yaml_data[ EXPIRES_YAML ]).blank? ? nil : DateTime.parse( expires.to_s ),
364
+ :sender_name => info_yaml_data [ SENDER_NAME_YAML ],
365
+ :headline => info_yaml_data [ HEADLINE_YAML ],
366
+ :description => info_yaml_data [ DESCRIPTION_YAML ],
367
+ :instruction => info_yaml_data [ INSTRUCTION_YAML ],
368
+ :web => info_yaml_data [ WEB_YAML ],
369
+ :contact => info_yaml_data [ CONTACT_YAML ],
370
+ :event_codes => Array( info_yaml_data [ EVENT_CODES_YAML ]).map{ |name,value| RCAP::EventCode.new( :name => name, :value => value )},
371
+ :parameters => Array( info_yaml_data [ PARAMETERS_YAML ]).map{ |parameter_yaml_data| RCAP::Parameter.new( :name => name, :value => value )},
372
+ :resources => Array( info_yaml_data [ RESOURCES_YAML ]).map{ |resource_yaml_data| RCAP::Resource.from_yaml_data( resource_yaml_data )},
373
+ :areas => Array( info_yaml_data [ AREAS_YAML ]).map{ |area_yaml_data| RCAP::Area.from_yaml_data( area_yaml_data )}
374
+ )
375
+ end
376
+
377
+ LANGUAGE_KEY = 'language' # :nodoc:
378
+ CATEGORIES_KEY = 'categories' # :nodoc:
379
+ EVENT_KEY = 'event' # :nodoc:
380
+ RESPONSE_TYPES_KEY = 'response_types' # :nodoc:
381
+ URGENCY_KEY = 'urgency' # :nodoc:
382
+ SEVERITY_KEY = 'severity' # :nodoc:
383
+ CERTAINTY_KEY = 'certainty' # :nodoc:
384
+ AUDIENCE_KEY = 'audience' # :nodoc:
385
+ EFFECTIVE_KEY = 'effective' # :nodoc:
386
+ ONSET_KEY = 'onset' # :nodoc:
387
+ EXPIRES_KEY = 'expires' # :nodoc:
388
+ SENDER_NAME_KEY = 'sender_name' # :nodoc:
389
+ HEADLINE_KEY = 'headline' # :nodoc:
390
+ DESCRIPTION_KEY = 'description' # :nodoc:
391
+ INSTRUCTION_KEY = 'instruction' # :nodoc:
392
+ WEB_KEY = 'web' # :nodoc:
393
+ CONTACT_KEY = 'contact' # :nodoc:
394
+ RESOURCES_KEY = 'resources' # :nodoc:
395
+ EVENT_CODES_KEY = 'event_codes' # :nodoc:
396
+ PARAMETERS_KEY = 'parameters' # :nodoc:
397
+ AREAS_KEY = 'areas' # :nodoc:
398
+
399
+ def to_h # :nodoc:
400
+ RCAP.attribute_values_to_hash( [ LANGUAGE_KEY, self.language ],
401
+ [ CATEGORIES_KEY, self.categories ],
402
+ [ EVENT_KEY, self.event ],
403
+ [ RESPONSE_TYPES_KEY, self.response_types ],
404
+ [ URGENCY_KEY, self.urgency ],
405
+ [ SEVERITY_KEY, self.severity ],
406
+ [ CERTAINTY_KEY, self.certainty ],
407
+ [ AUDIENCE_KEY, self.audience ],
408
+ [ EFFECTIVE_KEY, RCAP.to_s_for_cap( self.effective_at )],
409
+ [ ONSET_KEY, RCAP.to_s_for_cap( self.onset_at )],
410
+ [ EXPIRES_KEY, RCAP.to_s_for_cap( self.expires_at )],
411
+ [ SENDER_NAME_KEY, self.sender_name ],
412
+ [ HEADLINE_KEY, self.headline ],
413
+ [ DESCRIPTION_KEY, self.description ],
414
+ [ INSTRUCTION_KEY, self.instruction ],
415
+ [ WEB_KEY, self.web ],
416
+ [ CONTACT_KEY, self.contact ],
417
+ [ RESOURCES_KEY, self.resources.map{ |resource| resource.to_h } ],
418
+ [ EVENT_CODES_KEY, self.event_codes.map{ |event_code| event_code.to_h } ],
419
+ [ PARAMETERS_KEY, self.parameters.map{ |parameter| parameter.to_h } ],
420
+ [ AREAS_KEY, self.areas.map{ |area| area.to_h }])
421
+ end
422
+
423
+ def self.from_h( info_hash ) # :nodoc:
424
+ self.new( :language => info_hash[ LANGUAGE_KEY ],
425
+ :categories => info_hash[ CATEGORIES_KEY ],
426
+ :event => info_hash[ EVENT_KEY ],
427
+ :response_types => info_hash[ RESPONSE_TYPES_KEY ],
428
+ :urgency => info_hash[ URGENCY_KEY ],
429
+ :severity => info_hash[ SEVERITY_KEY ],
430
+ :certainty => info_hash[ CERTAINTY_KEY ],
431
+ :audience => info_hash[ AUDIENCE_KEY ],
432
+ :effective_at => RCAP.parse_datetime( info_hash[ EFFECTIVE_KEY ]),
433
+ :onset_at => RCAP.parse_datetime( info_hash[ ONSET_KEY ]),
434
+ :expires_at => RCAP.parse_datetime( info_hash[ EXPIRES_KEY ]),
435
+ :sender_name => info_hash[ SENDER_NAME_KEY ],
436
+ :headline => info_hash[ HEADLINE_KEY ],
437
+ :description => info_hash[ DESCRIPTION_KEY ],
438
+ :instruction => info_hash[ INSTRUCTION_KEY ],
439
+ :web => info_hash[ WEB_KEY ],
440
+ :contact => info_hash[ CONTACT_KEY ],
441
+ :resources => Array( info_hash[ RESOURCES_KEY ]).map{ |resource_hash| RCAP::Resource.from_h( resource_hash ) },
442
+ :event_codes => Array( info_hash[ EVENT_CODES_KEY ]).map{ |event_code_hash| RCAP::EventCode.from_h( event_code_hash )},
443
+ :parameters => Array( info_hash[ PARAMETERS_KEY ]).map{ |parameter_hash| RCAP::Parameter.from_h( parameter_hash )},
444
+ :areas => Array( info_hash[ AREAS_KEY ]).map{ |area_hash| RCAP::Area.from_h( area_hash )})
445
+ end
446
+
447
+ private
448
+
449
+ def set_language
450
+ language = DEFAULT_LANGUAGE unless language
451
+ end
452
+ end