rcap 0.4 → 1.0.0.rc.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.
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