rcap 1.0.1 → 1.1.0

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