rcap 0.4 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +7 -0
  2. data/CHANGELOG.rdoc +6 -0
  3. data/Gemfile +4 -0
  4. data/README.rdoc +104 -85
  5. data/Rakefile +35 -0
  6. data/lib/rcap.rb +21 -14
  7. data/lib/rcap/alert.rb +26 -325
  8. data/lib/rcap/cap_1_1/alert.rb +363 -0
  9. data/lib/rcap/cap_1_1/area.rb +180 -0
  10. data/lib/rcap/cap_1_1/circle.rb +81 -0
  11. data/lib/rcap/cap_1_1/event_code.rb +22 -0
  12. data/lib/rcap/cap_1_1/geocode.rb +22 -0
  13. data/lib/rcap/cap_1_1/info.rb +470 -0
  14. data/lib/rcap/cap_1_1/parameter.rb +68 -0
  15. data/lib/rcap/cap_1_1/point.rb +55 -0
  16. data/lib/rcap/cap_1_1/polygon.rb +89 -0
  17. data/lib/rcap/cap_1_1/resource.rb +145 -0
  18. data/lib/rcap/cap_1_2/alert.rb +363 -0
  19. data/lib/rcap/cap_1_2/area.rb +180 -0
  20. data/lib/rcap/cap_1_2/circle.rb +81 -0
  21. data/lib/rcap/cap_1_2/event_code.rb +22 -0
  22. data/lib/rcap/cap_1_2/geocode.rb +22 -0
  23. data/lib/rcap/cap_1_2/info.rb +472 -0
  24. data/lib/rcap/cap_1_2/parameter.rb +68 -0
  25. data/lib/rcap/cap_1_2/point.rb +55 -0
  26. data/lib/rcap/cap_1_2/polygon.rb +90 -0
  27. data/lib/rcap/cap_1_2/resource.rb +147 -0
  28. data/lib/rcap/utilities.rb +14 -9
  29. data/lib/rcap/validations.rb +39 -7
  30. data/lib/rcap/version.rb +3 -0
  31. data/rcap.gemspec +30 -0
  32. data/spec/alert_spec.rb +109 -172
  33. data/spec/cap_1_1/alert_spec.rb +222 -0
  34. data/spec/cap_1_1/area_spec.rb +247 -0
  35. data/spec/cap_1_1/circle_spec.rb +88 -0
  36. data/spec/{geocode_spec.rb → cap_1_1/geocode_spec.rb} +8 -8
  37. data/spec/{info_spec.rb → cap_1_1/info_spec.rb} +143 -75
  38. data/spec/{point_spec.rb → cap_1_1/point_spec.rb} +8 -8
  39. data/spec/cap_1_1/polygon_spec.rb +97 -0
  40. data/spec/{resource_spec.rb → cap_1_1/resource_spec.rb} +24 -24
  41. data/spec/cap_1_2/alert_spec.rb +233 -0
  42. data/spec/cap_1_2/area_spec.rb +248 -0
  43. data/spec/cap_1_2/circle_spec.rb +95 -0
  44. data/spec/cap_1_2/geocode_spec.rb +38 -0
  45. data/spec/cap_1_2/info_spec.rb +338 -0
  46. data/spec/cap_1_2/point_spec.rb +46 -0
  47. data/spec/cap_1_2/polygon_spec.rb +102 -0
  48. data/spec/cap_1_2/resource_spec.rb +161 -0
  49. data/spec/spec.opts +2 -0
  50. data/spec/validations_spec.rb +80 -7
  51. metadata +122 -37
  52. data/lib/rcap/area.rb +0 -156
  53. data/lib/rcap/circle.rb +0 -78
  54. data/lib/rcap/event_code.rb +0 -20
  55. data/lib/rcap/geocode.rb +0 -20
  56. data/lib/rcap/info.rb +0 -437
  57. data/lib/rcap/parameter.rb +0 -66
  58. data/lib/rcap/point.rb +0 -53
  59. data/lib/rcap/polygon.rb +0 -77
  60. data/lib/rcap/resource.rb +0 -143
  61. data/spec/area_spec.rb +0 -179
  62. data/spec/circle_spec.rb +0 -88
  63. data/spec/polygon_spec.rb +0 -68
@@ -0,0 +1,363 @@
1
+ module RCAP
2
+ module CAP_1_1
3
+ # An Alert object is valid if
4
+ # * it has an identifier
5
+ # * it has a sender
6
+ # * it has a sent time
7
+ # * it has a valid status value
8
+ # * it has a valid messge type value
9
+ # * it has a valid scope value
10
+ # * all Info objects contained in infos are valid
11
+ class Alert
12
+ include Validation
13
+
14
+ XMLNS = "urn:oasis:names:tc:emergency:cap:1.1"
15
+ CAP_VERSION = "1.1"
16
+
17
+ STATUS_ACTUAL = "Actual" # :nodoc:
18
+ STATUS_EXERCISE = "Exercise" # :nodoc:
19
+ STATUS_SYSTEM = "System" # :nodoc:
20
+ STATUS_TEST = "Test" # :nodoc:
21
+ STATUS_DRAFT = "Draft" # :nodoc:
22
+ # Valid values for status
23
+ VALID_STATUSES = [ STATUS_ACTUAL, STATUS_EXERCISE, STATUS_SYSTEM, STATUS_TEST, STATUS_DRAFT ]
24
+
25
+ MSG_TYPE_ALERT = "Alert" # :nodoc:
26
+ MSG_TYPE_UPDATE = "Update" # :nodoc:
27
+ MSG_TYPE_CANCEL = "Cancel" # :nodoc:
28
+ MSG_TYPE_ACK = "Ack" # :nodoc:
29
+ MSG_TYPE_ERROR = "Error" # :nodoc:
30
+ # Valid values for msg_type
31
+ VALID_MSG_TYPES = [ MSG_TYPE_ALERT, MSG_TYPE_UPDATE, MSG_TYPE_CANCEL, MSG_TYPE_ACK, MSG_TYPE_ERROR ]
32
+
33
+ SCOPE_PUBLIC = "Public" # :nodoc:
34
+ SCOPE_RESTRICTED = "Restricted" # :nodoc:
35
+ SCOPE_PRIVATE = "Private" # :nodoc:
36
+ # Valid values for scope
37
+ VALID_SCOPES = [ SCOPE_PUBLIC, SCOPE_PRIVATE, SCOPE_RESTRICTED ]
38
+
39
+ XML_ELEMENT_NAME = 'alert' # :nodoc:
40
+ IDENTIFIER_ELEMENT_NAME = 'identifier' # :nodoc:
41
+ SENDER_ELEMENT_NAME = 'sender' # :nodoc:
42
+ SENT_ELEMENT_NAME = 'sent' # :nodoc:
43
+ STATUS_ELEMENT_NAME = 'status' # :nodoc:
44
+ MSG_TYPE_ELEMENT_NAME = 'msgType' # :nodoc:
45
+ SOURCE_ELEMENT_NAME = 'source' # :nodoc:
46
+ SCOPE_ELEMENT_NAME = 'scope' # :nodoc:
47
+ RESTRICTION_ELEMENT_NAME = 'restriction' # :nodoc:
48
+ ADDRESSES_ELEMENT_NAME = 'addresses' # :nodoc:
49
+ CODE_ELEMENT_NAME = 'code' # :nodoc:
50
+ NOTE_ELEMENT_NAME = 'note' # :nodoc:
51
+ REFERENCES_ELEMENT_NAME = 'references' # :nodoc:
52
+ INCIDENTS_ELEMENT_NAME = 'incidents' # :nodoc:
53
+
54
+ XPATH = 'cap:alert' # :nodoc:
55
+ IDENTIFIER_XPATH = "cap:#{ IDENTIFIER_ELEMENT_NAME }" # :nodoc:
56
+ SENDER_XPATH = "cap:#{ SENDER_ELEMENT_NAME }" # :nodoc:
57
+ SENT_XPATH = "cap:#{ SENT_ELEMENT_NAME }" # :nodoc:
58
+ STATUS_XPATH = "cap:#{ STATUS_ELEMENT_NAME }" # :nodoc:
59
+ MSG_TYPE_XPATH = "cap:#{ MSG_TYPE_ELEMENT_NAME }" # :nodoc:
60
+ SOURCE_XPATH = "cap:#{ SOURCE_ELEMENT_NAME }" # :nodoc:
61
+ SCOPE_XPATH = "cap:#{ SCOPE_ELEMENT_NAME }" # :nodoc:
62
+ RESTRICTION_XPATH = "cap:#{ RESTRICTION_ELEMENT_NAME }" # :nodoc:
63
+ ADDRESSES_XPATH = "cap:#{ ADDRESSES_ELEMENT_NAME }" # :nodoc:
64
+ CODE_XPATH = "cap:#{ CODE_ELEMENT_NAME }" # :nodoc:
65
+ NOTE_XPATH = "cap:#{ NOTE_ELEMENT_NAME }" # :nodoc:
66
+ REFERENCES_XPATH = "cap:#{ REFERENCES_ELEMENT_NAME }" # :nodoc:
67
+ INCIDENTS_XPATH = "cap:#{ INCIDENTS_ELEMENT_NAME }" # :nodoc:
68
+
69
+ # If not set a UUID will be set by default
70
+ attr_accessor( :identifier)
71
+ attr_accessor( :sender )
72
+ # Sent Time - If not set will value will be time of creation.
73
+ attr_accessor( :sent )
74
+ # Value can only be one of VALID_STATUSES
75
+ attr_accessor( :status )
76
+ # Value can only be one of VALID_MSG_TYPES
77
+ attr_accessor( :msg_type )
78
+ # Value can only be one of VALID_SCOPES
79
+ attr_accessor( :scope )
80
+ attr_accessor( :source )
81
+ # Depends on scope being SCOPE_RESTRICTED.
82
+ attr_accessor( :restriction )
83
+ attr_accessor( :note )
84
+
85
+ # Collection of address strings. Depends on scope being SCOPE_PRIVATE.
86
+ attr_reader( :addresses )
87
+ attr_reader( :codes )
88
+ # Collection of reference strings - see Alert#to_reference
89
+ attr_reader( :references)
90
+ # Collection of incident strings
91
+ attr_reader( :incidents )
92
+ # Collection of Info objects
93
+ attr_reader( :infos )
94
+
95
+ validates_presence_of( :identifier, :sender, :sent, :status, :msg_type, :scope )
96
+
97
+ validates_inclusion_of( :status, :in => VALID_STATUSES )
98
+ validates_inclusion_of( :msg_type, :in => VALID_MSG_TYPES )
99
+ validates_inclusion_of( :scope, :in => VALID_SCOPES )
100
+
101
+ validates_format_of( :identifier, :with => ALLOWED_CHARACTERS )
102
+ validates_format_of( :sender , :with => ALLOWED_CHARACTERS )
103
+
104
+ validates_dependency_of( :addresses, :on => :scope, :with_value => SCOPE_PRIVATE )
105
+ validates_dependency_of( :restriction, :on => :scope, :with_value => SCOPE_RESTRICTED )
106
+
107
+ validates_collection_of( :infos )
108
+
109
+ def initialize( attributes = {})
110
+ @identifier = attributes[ :identifier ]
111
+ @sender = attributes[ :sender ]
112
+ @sent = attributes[ :sent ]
113
+ @status = attributes[ :status ]
114
+ @msg_type = attributes[ :msg_type ]
115
+ @scope = attributes[ :scope ]
116
+ @source = attributes[ :source ]
117
+ @restriction = attributes[ :restriction ]
118
+ @addresses = Array( attributes[ :addresses ])
119
+ @codes = Array( attributes[ :codes ])
120
+ @references = Array( attributes[ :references ])
121
+ @incidents = Array( attributes[ :incidents ])
122
+ @infos = Array( attributes[ :infos ])
123
+ end
124
+
125
+ # Creates a new Info object and adds it to the infos array. The
126
+ # info_attributes are passed as a parameter to Info.new.
127
+ def add_info( info_attributes = {})
128
+ info = Info.new( info_attributes )
129
+ self.infos << info
130
+ info
131
+ end
132
+
133
+ def to_xml_element #:nodoc:
134
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
135
+ xml_element.add_namespace( XMLNS )
136
+ xml_element.add_element( IDENTIFIER_ELEMENT_NAME ).add_text( self.identifier ) if self.identifier
137
+ xml_element.add_element( SENDER_ELEMENT_NAME ).add_text( self.sender ) if self.sender
138
+ xml_element.add_element( SENT_ELEMENT_NAME ).add_text( self.sent.to_s_for_cap ) if self.sent
139
+ xml_element.add_element( STATUS_ELEMENT_NAME ).add_text( self.status ) if self.status
140
+ xml_element.add_element( MSG_TYPE_ELEMENT_NAME ).add_text( self.msg_type ) if self.msg_type
141
+ xml_element.add_element( SOURCE_ELEMENT_NAME ).add_text( self.source ) if self.source
142
+ xml_element.add_element( SCOPE_ELEMENT_NAME ).add_text( self.scope ) if self.scope
143
+ xml_element.add_element( RESTRICTION_ELEMENT_NAME ).add_text( self.restriction ) if self.restriction
144
+ unless self.addresses.empty?
145
+ xml_element.add_element( ADDRESSES_ELEMENT_NAME ).add_text( self.addresses.to_s_for_cap )
146
+ end
147
+ self.codes.each do |code|
148
+ xml_element.add_element( CODE_ELEMENT_NAME ).add_text( code )
149
+ end
150
+ xml_element.add_element( NOTE_ELEMENT_NAME ).add_text( self.note ) if self.note
151
+ unless self.references.empty?
152
+ xml_element.add_element( REFERENCES_ELEMENT_NAME ).add_text( self.references.join( ' ' ))
153
+ end
154
+ unless self.incidents.empty?
155
+ xml_element.add_element( INCIDENTS_ELEMENT_NAME ).add_text( self.incidents.join( ' ' ))
156
+ end
157
+ self.infos.each do |info|
158
+ xml_element.add_element( info.to_xml_element )
159
+ end
160
+ xml_element
161
+ end
162
+
163
+ def to_xml_document #:nodoc:
164
+ xml_document = REXML::Document.new
165
+ xml_document.add( REXML::XMLDecl.new )
166
+ xml_document.add( self.to_xml_element )
167
+ xml_document
168
+ end
169
+
170
+ # Returns a string containing the XML representation of the alert.
171
+ def to_xml
172
+ self.to_xml_document.to_s
173
+ end
174
+
175
+ # Returns a string representation of the alert suitable for usage as a reference in a CAP message of the form
176
+ # sender,identifier,sent
177
+ def to_reference
178
+ "#{ self.sender },#{ self.identifier },#{ self.sent }"
179
+ end
180
+
181
+ def inspect # :nodoc:
182
+ alert_inspect = <<EOF
183
+ CAP Version: #{ CAP_VERSION }
184
+ Identifier: #{ self.identifier }
185
+ Sender: #{ self.sender }
186
+ Sent: #{ self.sent }
187
+ Status: #{ self.status }
188
+ Message Type: #{ self.msg_type }
189
+ Source: #{ self.source }
190
+ Scope: #{ self.scope }
191
+ Restriction: #{ self.restriction }
192
+ Addresses: #{ self.addresses.to_s_for_cap }
193
+ Codes:
194
+ #{ self.codes.map{ |code| " #{ code }" }.join("\n")}
195
+ Note: #{ self.note }
196
+ References: #{ self.references.join( ' ' )}
197
+ Incidents: #{ self.incidents.join( ' ')}
198
+ Information:
199
+ #{ self.infos.map{ |info| " " + info.to_s }.join( "\n" )}
200
+ EOF
201
+ RCAP.format_lines_for_inspect( 'ALERT', alert_inspect )
202
+ end
203
+
204
+ # Returns a string representation of the alert of the form
205
+ # sender/identifier/sent
206
+ # See Alert#to_reference for another string representation suitable as a CAP reference.
207
+ def to_s
208
+ "#{ self.sender }/#{ self.identifier }/#{ self.sent }"
209
+ end
210
+
211
+ def self.from_xml_element( alert_xml_element ) # :nodoc:
212
+ self.new( :identifier => RCAP.xpath_text( alert_xml_element, IDENTIFIER_XPATH, Alert::XMLNS ),
213
+ :sender => RCAP.xpath_text( alert_xml_element, SENDER_XPATH, Alert::XMLNS ),
214
+ :sent => (( sent = RCAP.xpath_first( alert_xml_element, SENT_XPATH, Alert::XMLNS )) ? DateTime.parse( sent.text ) : nil ),
215
+ :status => RCAP.xpath_text( alert_xml_element, STATUS_XPATH, Alert::XMLNS ),
216
+ :msg_type => RCAP.xpath_text( alert_xml_element, MSG_TYPE_XPATH, Alert::XMLNS ),
217
+ :source => RCAP.xpath_text( alert_xml_element, SOURCE_XPATH, Alert::XMLNS ),
218
+ :scope => RCAP.xpath_text( alert_xml_element, SCOPE_XPATH, Alert::XMLNS ),
219
+ :restriction => RCAP.xpath_text( alert_xml_element, RESTRICTION_XPATH, Alert::XMLNS ),
220
+ :addresses => (( address = RCAP.xpath_text( alert_xml_element, ADDRESSES_XPATH, Alert::XMLNS )) ? address.unpack_cap_list : nil ),
221
+ :codes => RCAP.xpath_match( alert_xml_element, CODE_XPATH, Alert::XMLNS ).map{ |element| element.text },
222
+ :note => RCAP.xpath_text( alert_xml_element, NOTE_XPATH, Alert::XMLNS ),
223
+ :references => (( references = RCAP.xpath_text( alert_xml_element, REFERENCES_XPATH, Alert::XMLNS )) ? references.split( ' ' ) : nil ),
224
+ :incidents => (( incidents = RCAP.xpath_text( alert_xml_element, INCIDENTS_XPATH, Alert::XMLNS )) ? incidents.split( ' ' ) : nil ),
225
+ :infos => RCAP.xpath_match( alert_xml_element, Info::XPATH, Alert::XMLNS ).map{ |element| Info.from_xml_element( element )})
226
+ end
227
+
228
+ def self.from_xml_document( xml_document ) # :nodoc:
229
+ self.from_xml_element( xml_document.root )
230
+ end
231
+
232
+ # Initialise an Alert object from an XML string. Any object that is a subclass of IO (e.g. File) can be passed in.
233
+ def self.from_xml( xml )
234
+ self.from_xml_document( REXML::Document.new( xml ))
235
+ end
236
+
237
+ CAP_VERSION_YAML = "CAP Version" # :nodoc:
238
+ IDENTIFIER_YAML = "Identifier" # :nodoc:
239
+ SENDER_YAML = "Sender" # :nodoc:
240
+ SENT_YAML = "Sent" # :nodoc:
241
+ STATUS_YAML = "Status" # :nodoc:
242
+ MSG_TYPE_YAML = "Message Type" # :nodoc:
243
+ SOURCE_YAML = "Source" # :nodoc:
244
+ SCOPE_YAML = "Scope" # :nodoc:
245
+ RESTRICTION_YAML = "Restriction" # :nodoc:
246
+ ADDRESSES_YAML = "Addresses" # :nodoc:
247
+ CODES_YAML = "Codes" # :nodoc:
248
+ NOTE_YAML = "Note" # :nodoc:
249
+ REFERENCES_YAML = "References" # :nodoc:
250
+ INCIDENTS_YAML = "Incidents" # :nodoc:
251
+ INFOS_YAML = "Information" # :nodoc:
252
+
253
+ # Returns a string containing the YAML representation of the alert.
254
+ def to_yaml( options = {} )
255
+ RCAP.attribute_values_to_hash(
256
+ [ CAP_VERSION_YAML, CAP_VERSION ],
257
+ [ IDENTIFIER_YAML, self.identifier ],
258
+ [ SENDER_YAML, self.sender ],
259
+ [ SENT_YAML, self.sent ],
260
+ [ STATUS_YAML, self.status ],
261
+ [ MSG_TYPE_YAML, self.msg_type ],
262
+ [ SOURCE_YAML, self.source ],
263
+ [ SCOPE_YAML, self.scope ],
264
+ [ RESTRICTION_YAML, self.restriction ],
265
+ [ ADDRESSES_YAML, self.addresses ],
266
+ [ CODES_YAML, self.codes ],
267
+ [ NOTE_YAML, self.note ],
268
+ [ REFERENCES_YAML, self.references ],
269
+ [ INCIDENTS_YAML, self.incidents ],
270
+ [ INFOS_YAML, self.infos ]
271
+ ).to_yaml( options )
272
+ end
273
+
274
+ # Initialise an Alert object from a YAML string. Any object that is a subclass of IO (e.g. File) can be passed in.
275
+ def self.from_yaml( yaml )
276
+ self.from_yaml_data( YAML.load( yaml ))
277
+ end
278
+
279
+ def self.from_yaml_data( alert_yaml_data ) # :nodoc:
280
+ Alert.new(
281
+ :identifier => alert_yaml_data[ IDENTIFIER_YAML ],
282
+ :sender => alert_yaml_data[ SENDER_YAML ],
283
+ :sent => ( sent = alert_yaml_data[ SENT_YAML ]).blank? ? nil : DateTime.parse( sent.to_s ),
284
+ :status => alert_yaml_data[ STATUS_YAML ],
285
+ :msg_type => alert_yaml_data[ MSG_TYPE_YAML ],
286
+ :source => alert_yaml_data[ SOURCE_YAML ],
287
+ :scope => alert_yaml_data[ SCOPE_YAML ],
288
+ :restriction => alert_yaml_data[ RESTRICTION_YAML ],
289
+ :addresses => alert_yaml_data[ ADDRESSES_YAML ],
290
+ :codes => alert_yaml_data[ CODES_YAML ],
291
+ :note => alert_yaml_data[ NOTE_YAML ],
292
+ :references => alert_yaml_data[ REFERENCES_YAML ],
293
+ :incidents => alert_yaml_data[ INCIDENTS_YAML ],
294
+ :infos => Array( alert_yaml_data[ INFOS_YAML ]).map{ |info_yaml_data| Info.from_yaml_data( info_yaml_data )}
295
+ )
296
+ end
297
+
298
+ CAP_VERSION_KEY = 'cap_version' # :nodoc:
299
+ IDENTIFIER_KEY = 'identifier' # :nodoc:
300
+ SENDER_KEY = 'sender' # :nodoc:
301
+ SENT_KEY = 'sent' # :nodoc:
302
+ STATUS_KEY = 'status' # :nodoc:
303
+ MSG_TYPE_KEY = 'msg_type' # :nodoc:
304
+ SOURCE_KEY = 'source' # :nodoc:
305
+ SCOPE_KEY = 'scope' # :nodoc:
306
+ RESTRICTION_KEY = 'restriction' # :nodoc:
307
+ ADDRESSES_KEY = 'addresses' # :nodoc:
308
+ CODES_KEY = 'codes' # :nodoc:
309
+ NOTE_KEY = 'note' # :nodoc:
310
+ REFERENCES_KEY = 'references' # :nodoc:
311
+ INCIDENTS_KEY = 'incidents' # :nodoc:
312
+ INFOS_KEY = 'infos' # :nodoc:
313
+
314
+ # Returns a Hash representation of an Alert object
315
+ def to_h
316
+ RCAP.attribute_values_to_hash( [ CAP_VERSION_KEY, CAP_VERSION ],
317
+ [ IDENTIFIER_KEY, self.identifier ],
318
+ [ SENDER_KEY, self.sender ],
319
+ [ SENT_KEY, RCAP.to_s_for_cap( self.sent )],
320
+ [ STATUS_KEY, self.status ],
321
+ [ MSG_TYPE_KEY, self.msg_type ],
322
+ [ SOURCE_KEY, self.source ],
323
+ [ SCOPE_KEY, self.scope ],
324
+ [ RESTRICTION_KEY, self.restriction ],
325
+ [ ADDRESSES_KEY, self.addresses ],
326
+ [ CODES_KEY, self.codes ],
327
+ [ NOTE_KEY, self.note ],
328
+ [ REFERENCES_KEY, self.references ],
329
+ [ INCIDENTS_KEY, self.incidents ],
330
+ [ INFOS_KEY, self.infos.map{ |info| info.to_h }])
331
+ end
332
+
333
+ # Initialises an Alert object from a Hash produced by Alert#to_h
334
+ def self.from_h( alert_hash )
335
+ self.new(
336
+ :identifier => alert_hash[ IDENTIFIER_KEY ],
337
+ :sender => alert_hash[ SENDER_KEY ],
338
+ :sent => RCAP.parse_datetime( alert_hash[ SENT_KEY ]),
339
+ :status => alert_hash[ STATUS_KEY ],
340
+ :msg_type => alert_hash[ MSG_TYPE_KEY ],
341
+ :source => alert_hash[ SOURCE_KEY ],
342
+ :scope => alert_hash[ SCOPE_KEY ],
343
+ :restriction => alert_hash[ RESTRICTION_KEY ],
344
+ :addresses => alert_hash[ ADDRESSES_KEY ],
345
+ :codes => alert_hash[ CODES_KEY ],
346
+ :note => alert_hash[ NOTE_KEY ],
347
+ :references => alert_hash[ REFERENCES_KEY ],
348
+ :incidents => alert_hash[ INCIDENTS_KEY ],
349
+ :infos => Array( alert_hash[ INFOS_KEY ]).map{ |info_hash| Info.from_h( info_hash )})
350
+ end
351
+
352
+ # Returns a JSON string representation of an Alert object
353
+ def to_json
354
+ self.to_h.to_json
355
+ end
356
+
357
+ # Initiialises an Alert object from a JSON string produced by Alert#to_json
358
+ def self.from_json( json_string )
359
+ self.from_h( JSON.parse( json_string ))
360
+ end
361
+ end
362
+ end
363
+ end
@@ -0,0 +1,180 @@
1
+ module RCAP
2
+ module CAP_1_1
3
+ # An Area object is valid if
4
+ # * it has an area description
5
+ # * all Circle objects contained in circles are valid
6
+ # * all Geocode objects contained in geocodes are valid
7
+ # * all Polygon objects contained in polygons are valid
8
+ # * altitude has a value if ceiling is set
9
+ class Area
10
+ include Validation
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
+ # Creates a new Polygon object and adds it to the polygons array. The
49
+ # polygon_attributes are passed as a parameter to Polygon.new.
50
+ def add_polygon( polygon_attributes = {})
51
+ polygon = Polygon.new( polygon_attributes )
52
+ self.polygons << polygon
53
+ polygon
54
+ end
55
+
56
+ # Creates a new Circle object and adds it to the circles array. The
57
+ # circle_attributes are passed as a parameter to Circle.new.
58
+ def add_circle( circle_attributes = {})
59
+ circle = Circle.new( circle_attributes )
60
+ self.circles << circle
61
+ circle
62
+ end
63
+
64
+ # Creates a new Geocode object and adds it to the geocodes array. The
65
+ # geocode_attributes are passed as a parameter to Geocode.new.
66
+ def add_geocode( geocode_attributes = {})
67
+ geocode = Geocode.new( geocode_attributes )
68
+ self.geocodes << geocode
69
+ geocode
70
+ end
71
+
72
+ def to_xml_element # :nodoc:
73
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
74
+ xml_element.add_element( AREA_DESC_ELEMENT_NAME ).add_text( @area_desc.to_s )
75
+ add_to_xml_element = lambda do |element, object|
76
+ element.add_element( object.to_xml_element )
77
+ element
78
+ end
79
+ @polygons.inject( xml_element, &add_to_xml_element )
80
+ @circles.inject( xml_element, &add_to_xml_element )
81
+ @geocodes.inject( xml_element, &add_to_xml_element )
82
+ xml_element.add_element( ALTITUDE_ELEMENT_NAME ).add_text( @altitude.to_s ) unless self.altitude.blank?
83
+ xml_element.add_element( CEILING_ELEMENT_NAME ).add_text( @ceiling.to_s ) unless self.altitude.blank?
84
+ xml_element
85
+ end
86
+
87
+ def to_xml # :nodoc:
88
+ self.to_xml_element.to_s
89
+ end
90
+
91
+ # Implements an equality operator for the Area object. Two Area objects are equal if all their attributes are equal.
92
+ def ==( other )
93
+ comparison_attributes = lambda{ |area| [ area.area_desc, area.altitude, area.ceiling, area.circles, area.geocodes, area.polygons ]}
94
+ comparison_attributes.call( self ) == comparison_attributes.call( other )
95
+ end
96
+
97
+ def inspect # :nodoc:
98
+ area_inspect = <<EOF
99
+ Area Description: #{ self.area_desc }
100
+ Polygons:
101
+ #{ self.polygons.map{ |polygon| " " + polygon.inspect }.join("\n" )}
102
+ Circles: #{ self.circles.inspect }
103
+ Geocodes: #{ self.geocodes.inspect }
104
+ EOF
105
+ RCAP.format_lines_for_inspect( 'AREA', area_inspect )
106
+ end
107
+
108
+ # Returns a string representation of the area of the form
109
+ # area_desc
110
+ def to_s
111
+ self.area_desc
112
+ end
113
+
114
+ def self.from_xml_element( area_xml_element ) # :nodoc:
115
+ self.new( :area_desc => RCAP.xpath_text( area_xml_element, AREA_DESC_XPATH, Alert::XMLNS ),
116
+ :altitude => (( alt = RCAP.xpath_text( area_xml_element, ALTITUDE_XPATH, Alert::XMLNS )) ? alt.to_f : nil ),
117
+ :ceiling => (( ceil = RCAP.xpath_text( area_xml_element, CEILING_XPATH, Alert::XMLNS )) ? ceil.to_f : nil ),
118
+ :circles => RCAP.xpath_match( area_xml_element, Circle::XPATH, Alert::XMLNS ).map{ |circle_element| Circle.from_xml_element( circle_element )},
119
+ :geocodes => RCAP.xpath_match( area_xml_element, Geocode::XPATH, Alert::XMLNS ).map{ |geocode_element| Geocode.from_xml_element( geocode_element )},
120
+ :polygons => RCAP.xpath_match( area_xml_element, Polygon::XPATH, Alert::XMLNS ).map{ |polygon_element| Polygon.from_xml_element( polygon_element )})
121
+ end
122
+
123
+ AREA_DESC_YAML = 'Area Description' # :nodoc:
124
+ ALTITUDE_YAML = 'Altitude' # :nodoc:
125
+ CEILING_YAML = 'Ceiling' # :nodoc:
126
+ CIRCLES_YAML = 'Circles' # :nodoc:
127
+ GEOCODES_YAML = 'Geocodes' # :nodoc:
128
+ POLYGONS_YAML = 'Polygons' # :nodoc:
129
+
130
+ def to_yaml( options = {} ) # :nodoc:
131
+ circles_yaml = self.circles.map{ |circle| [ circle.lattitude, circle.longitude, circle.radius ]}
132
+ def circles_yaml.to_yaml_style; :inline; end
133
+
134
+ RCAP.attribute_values_to_hash(
135
+ [ AREA_DESC_YAML, self.area_desc ],
136
+ [ ALTITUDE_YAML, self.altitude ],
137
+ [ CEILING_YAML, self.ceiling ],
138
+ [ CIRCLES_YAML, circles_yaml ],
139
+ [ GEOCODES_YAML, self.geocodes.inject({}){|h,geocode| h.merge( geocode.name => geocode.value )}],
140
+ [ POLYGONS_YAML, self.polygons ]
141
+ ).to_yaml( options )
142
+ end
143
+
144
+ def self.from_yaml_data( area_yaml_data ) # :nodoc:
145
+ self.new( :area_desc => area_yaml_data[ AREA_DESC_YAML ],
146
+ :altitude => area_yaml_data[ ALTITUDE_YAML ],
147
+ :ceiling => area_yaml_data[ CEILING_YAML ],
148
+ :circles => Array( area_yaml_data[ CIRCLES_YAML ]).map{ |circle_yaml_data| Circle.from_yaml_data( circle_yaml_data )},
149
+ :geocodes => Array( area_yaml_data[ GEOCODES_YAML ]).map{ |name, value| Geocode.new( :name => name, :value => value )},
150
+ :polygons => Array( area_yaml_data[ POLYGONS_YAML ]).map{ |polyon_yaml_data| Polygon.from_yaml_data( polyon_yaml_data )})
151
+ end
152
+
153
+ AREA_DESC_KEY = 'area_desc' # :nodoc:
154
+ ALTITUDE_KEY = 'altitude' # :nodoc:
155
+ CEILING_KEY = 'ceiling' # :nodoc:
156
+ CIRCLES_KEY = 'circles' # :nodoc:
157
+ GEOCODES_KEY = 'geocodes' # :nodoc:
158
+ POLYGONS_KEY = 'polygons' # :nodoc:
159
+
160
+ def to_h # :nodoc:
161
+ { AREA_DESC_KEY => self.area_desc,
162
+ ALTITUDE_KEY => self.altitude,
163
+ CEILING_KEY => self.ceiling,
164
+ CIRCLES_KEY => self.circles.map{ |circle| circle.to_h },
165
+ GEOCODES_KEY => self.geocodes.map{ |geocode| geocode.to_h },
166
+ POLYGONS_KEY => self.polygons.map{ |polygon| polygon.to_h }}
167
+ end
168
+
169
+ def self.from_h( area_hash ) # :nodoc:
170
+ self.new(
171
+ :area_desc => area_hash[ AREA_DESC_KEY ],
172
+ :altitude => area_hash[ ALTITUDE_KEY ],
173
+ :ceiling => area_hash[ CEILING_KEY ],
174
+ :circles => area_hash[ CIRCLES_KEY ].map{ |circle_hash| Circle.from_h( circle_hash )},
175
+ :geocodes => area_hash[ GEOCODES_KEY ].map{ |geocode_hash| Geocode.from_h( geocode_hash )},
176
+ :polygons => area_hash[ POLYGONS_KEY ].map{ |polygon_hash| Polygon.from_h( polygon_hash )})
177
+ end
178
+ end
179
+ end
180
+ end