rcap 1.3.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/README.md +78 -151
  3. data/Rakefile +1 -1
  4. data/lib/rcap/alert.rb +2 -2
  5. data/lib/rcap/base/alert.rb +446 -0
  6. data/lib/rcap/base/area.rb +228 -0
  7. data/lib/rcap/base/circle.rb +121 -0
  8. data/lib/rcap/base/event_code.rb +6 -0
  9. data/lib/rcap/base/geocode.rb +6 -0
  10. data/lib/rcap/base/info.rb +498 -0
  11. data/lib/rcap/base/parameter.rb +88 -0
  12. data/lib/rcap/base/point.rb +87 -0
  13. data/lib/rcap/base/polygon.rb +120 -0
  14. data/lib/rcap/base/resource.rb +168 -0
  15. data/lib/rcap/cap_1_0/alert.rb +59 -342
  16. data/lib/rcap/cap_1_0/area.rb +13 -188
  17. data/lib/rcap/cap_1_0/circle.rb +2 -100
  18. data/lib/rcap/cap_1_0/event_code.rb +8 -3
  19. data/lib/rcap/cap_1_0/geocode.rb +8 -3
  20. data/lib/rcap/cap_1_0/info.rb +16 -468
  21. data/lib/rcap/cap_1_0/parameter.rb +9 -67
  22. data/lib/rcap/cap_1_0/point.rb +2 -61
  23. data/lib/rcap/cap_1_0/polygon.rb +5 -95
  24. data/lib/rcap/cap_1_0/resource.rb +4 -144
  25. data/lib/rcap/cap_1_1/alert.rb +7 -412
  26. data/lib/rcap/cap_1_1/area.rb +13 -188
  27. data/lib/rcap/cap_1_1/circle.rb +2 -100
  28. data/lib/rcap/cap_1_1/event_code.rb +8 -3
  29. data/lib/rcap/cap_1_1/geocode.rb +7 -3
  30. data/lib/rcap/cap_1_1/info.rb +127 -386
  31. data/lib/rcap/cap_1_1/parameter.rb +4 -76
  32. data/lib/rcap/cap_1_1/point.rb +2 -61
  33. data/lib/rcap/cap_1_1/polygon.rb +5 -95
  34. data/lib/rcap/cap_1_1/resource.rb +37 -143
  35. data/lib/rcap/cap_1_2/alert.rb +8 -413
  36. data/lib/rcap/cap_1_2/area.rb +13 -188
  37. data/lib/rcap/cap_1_2/circle.rb +2 -100
  38. data/lib/rcap/cap_1_2/event_code.rb +8 -3
  39. data/lib/rcap/cap_1_2/geocode.rb +8 -3
  40. data/lib/rcap/cap_1_2/info.rb +132 -419
  41. data/lib/rcap/cap_1_2/parameter.rb +4 -76
  42. data/lib/rcap/cap_1_2/point.rb +2 -61
  43. data/lib/rcap/cap_1_2/polygon.rb +5 -92
  44. data/lib/rcap/cap_1_2/resource.rb +31 -134
  45. data/lib/rcap/config.rb +3 -0
  46. data/lib/{extensions → rcap/extensions}/array.rb +1 -1
  47. data/lib/rcap/extensions/date.rb +11 -0
  48. data/lib/{extensions → rcap/extensions}/date_time.rb +2 -5
  49. data/lib/{extensions → rcap/extensions}/string.rb +1 -1
  50. data/lib/{extensions → rcap/extensions}/time.rb +2 -4
  51. data/lib/rcap/utilities.rb +11 -11
  52. data/lib/rcap/validations.rb +7 -2
  53. data/lib/rcap/version.rb +1 -1
  54. data/lib/rcap.rb +21 -4
  55. data/spec/alert_spec.rb +69 -37
  56. data/spec/cap_1_0/alert_spec.rb +46 -61
  57. data/spec/cap_1_0/area_spec.rb +77 -37
  58. data/spec/cap_1_0/circle_spec.rb +26 -6
  59. data/spec/cap_1_0/event_code_spec.rb +10 -3
  60. data/spec/cap_1_0/geocode_spec.rb +11 -4
  61. data/spec/cap_1_0/info_spec.rb +74 -77
  62. data/spec/cap_1_0/parameter_spec.rb +18 -5
  63. data/spec/cap_1_0/point_spec.rb +9 -2
  64. data/spec/cap_1_0/polygon_spec.rb +52 -9
  65. data/spec/cap_1_0/resource_spec.rb +28 -21
  66. data/spec/cap_1_1/alert_spec.rb +47 -60
  67. data/spec/cap_1_1/area_spec.rb +66 -43
  68. data/spec/cap_1_1/circle_spec.rb +29 -6
  69. data/spec/cap_1_1/event_code_spec.rb +11 -3
  70. data/spec/cap_1_1/geocode_spec.rb +11 -3
  71. data/spec/cap_1_1/info_spec.rb +262 -118
  72. data/spec/cap_1_1/parameter_spec.rb +12 -3
  73. data/spec/cap_1_1/point_spec.rb +8 -2
  74. data/spec/cap_1_1/polygon_spec.rb +57 -18
  75. data/spec/cap_1_1/resource_spec.rb +70 -20
  76. data/spec/cap_1_2/alert_spec.rb +97 -110
  77. data/spec/cap_1_2/area_spec.rb +59 -41
  78. data/spec/cap_1_2/circle_spec.rb +15 -8
  79. data/spec/cap_1_2/event_code_spec.rb +11 -3
  80. data/spec/cap_1_2/geocode_spec.rb +11 -3
  81. data/spec/cap_1_2/info_spec.rb +266 -119
  82. data/spec/cap_1_2/parameter_spec.rb +11 -3
  83. data/spec/cap_1_2/point_spec.rb +10 -3
  84. data/spec/cap_1_2/polygon_spec.rb +25 -10
  85. data/spec/cap_1_2/resource_spec.rb +33 -28
  86. data/spec/{utilities_spec.rb → extensions_spec.rb} +0 -0
  87. data/spec/validations_spec.rb +18 -2
  88. metadata +20 -46
@@ -0,0 +1,446 @@
1
+ module RCAP
2
+ module Base
3
+ class Alert
4
+ include Validation
5
+
6
+ STATUS_ACTUAL = "Actual"
7
+ STATUS_EXERCISE = "Exercise"
8
+ STATUS_SYSTEM = "System"
9
+ STATUS_TEST = "Test"
10
+ # Valid values for status
11
+ VALID_STATUSES = [ STATUS_ACTUAL, STATUS_EXERCISE, STATUS_SYSTEM, STATUS_TEST ]
12
+
13
+ MSG_TYPE_ALERT = "Alert"
14
+ MSG_TYPE_UPDATE = "Update"
15
+ MSG_TYPE_CANCEL = "Cancel"
16
+ MSG_TYPE_ACK = "Ack"
17
+ MSG_TYPE_ERROR = "Error"
18
+ # Valid values for msg_type
19
+ VALID_MSG_TYPES = [ MSG_TYPE_ALERT, MSG_TYPE_UPDATE, MSG_TYPE_CANCEL, MSG_TYPE_ACK, MSG_TYPE_ERROR ]
20
+
21
+ SCOPE_PUBLIC = "Public"
22
+ SCOPE_RESTRICTED = "Restricted"
23
+ SCOPE_PRIVATE = "Private"
24
+ # Valid values for scope
25
+ VALID_SCOPES = [ SCOPE_PUBLIC, SCOPE_PRIVATE, SCOPE_RESTRICTED ]
26
+
27
+ # @return [String] If not set a UUID will be set by default on object initialisation
28
+ attr_accessor( :identifier)
29
+ # @return [String]
30
+ attr_accessor( :sender )
31
+ # @return [DateTime] If not set will be time of creation.
32
+ attr_accessor( :sent )
33
+ # @return [String] Can only be one of {VALID_STATUSES}
34
+ attr_accessor( :status )
35
+ # @return [String] Can only be one of {VALID_MSG_TYPES}
36
+ attr_accessor( :msg_type )
37
+ # @return [String] Can only be one of {VALID_SCOPES}
38
+ attr_accessor( :scope )
39
+ # @return [String]
40
+ attr_accessor( :source )
41
+ # @return [String ] Required if scope is {SCOPE_RESTRICTED}
42
+ attr_accessor( :restriction )
43
+ # @return [String]
44
+ attr_accessor( :note )
45
+
46
+ # @return [Array<String>] Collection of address strings. Depends on scope being {SCOPE_PRIVATE}
47
+ attr_reader( :addresses )
48
+ # @return [Array<String>]
49
+ attr_reader( :codes )
50
+ # @return [Array<String>] Collection of references to previous alerts
51
+ # @see #to_reference
52
+ attr_reader( :references)
53
+ # @return [Array<String>] Collection of incident strings
54
+ attr_reader( :incidents )
55
+ # @return [Array<Info>]
56
+ attr_reader( :infos )
57
+
58
+ validates_presence_of( :identifier, :sender, :sent, :status, :msg_type, :scope )
59
+
60
+ validates_inclusion_of( :status, :in => VALID_STATUSES )
61
+ validates_inclusion_of( :msg_type, :in => VALID_MSG_TYPES )
62
+ validates_inclusion_of( :scope, :in => VALID_SCOPES )
63
+
64
+ validates_format_of( :identifier, :with => ALLOWED_CHARACTERS )
65
+ validates_format_of( :sender , :with => ALLOWED_CHARACTERS )
66
+
67
+ validates_conditional_presence_of( :addresses, :when => :scope, :is => SCOPE_PRIVATE )
68
+ validates_conditional_presence_of( :restriction, :when => :scope, :is => SCOPE_RESTRICTED )
69
+
70
+ validates_collection_of( :infos )
71
+
72
+ # Initialises a new Alert object. Yields the initialised alert to a block.
73
+ #
74
+ # @example
75
+ # alert = Alert.new do |alert|
76
+ # alert.sender = alerts@example.org
77
+ # alert.status = Alert::STATUS_ACTUAL
78
+ # alert.msg_type = Alert::MSG_TYPE_ALERT
79
+ # alert.scope = Alert::SCOPE_PUBLIC
80
+ # end
81
+ #
82
+ # @yieldparam alert [Alert] The newly initialised Alert.
83
+ def initialize
84
+ @identifier = RCAP.generate_identifier
85
+ @addresses = []
86
+ @codes = []
87
+ @references = []
88
+ @incidents = []
89
+ @infos = []
90
+ yield( self ) if block_given?
91
+ end
92
+
93
+ # Creates a new {Info} object and adds it to the {#infos} array.
94
+ #
95
+ # @see Info#initialize
96
+ # @yield [Info] The newly initialised Info object.
97
+ # @return [Info] The initialised Info object after being yielded to the block
98
+ def add_info
99
+ self.info_class.new.tap do |info|
100
+ yield( info ) if block_given?
101
+ @infos << info
102
+ end
103
+ end
104
+
105
+ XML_ELEMENT_NAME = 'alert'
106
+ IDENTIFIER_ELEMENT_NAME = 'identifier'
107
+ SENDER_ELEMENT_NAME = 'sender'
108
+ SENT_ELEMENT_NAME = 'sent'
109
+ STATUS_ELEMENT_NAME = 'status'
110
+ MSG_TYPE_ELEMENT_NAME = 'msgType'
111
+ SOURCE_ELEMENT_NAME = 'source'
112
+ SCOPE_ELEMENT_NAME = 'scope'
113
+ RESTRICTION_ELEMENT_NAME = 'restriction'
114
+ ADDRESSES_ELEMENT_NAME = 'addresses'
115
+ CODE_ELEMENT_NAME = 'code'
116
+ NOTE_ELEMENT_NAME = 'note'
117
+ REFERENCES_ELEMENT_NAME = 'references'
118
+ INCIDENTS_ELEMENT_NAME = 'incidents'
119
+
120
+ # @return [REXML::Element]
121
+ def to_xml_element
122
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
123
+ xml_element.add_namespace( self.class::XMLNS )
124
+ xml_element.add_element( IDENTIFIER_ELEMENT_NAME ).add_text( @identifier ) if @identifier
125
+ xml_element.add_element( SENDER_ELEMENT_NAME ).add_text( @sender ) if @sender
126
+ xml_element.add_element( SENT_ELEMENT_NAME ).add_text( @sent.to_s_for_cap ) if @sent
127
+ xml_element.add_element( STATUS_ELEMENT_NAME ).add_text( @status ) if @status
128
+ xml_element.add_element( MSG_TYPE_ELEMENT_NAME ).add_text( @msg_type ) if @msg_type
129
+ xml_element.add_element( SOURCE_ELEMENT_NAME ).add_text( @source ) if @source
130
+ xml_element.add_element( SCOPE_ELEMENT_NAME ).add_text( @scope ) if @scope
131
+ xml_element.add_element( RESTRICTION_ELEMENT_NAME ).add_text( @restriction ) if @restriction
132
+ if @addresses.any?
133
+ xml_element.add_element( ADDRESSES_ELEMENT_NAME ).add_text( @addresses.to_s_for_cap )
134
+ end
135
+ @codes.each do |code|
136
+ xml_element.add_element( CODE_ELEMENT_NAME ).add_text( code )
137
+ end
138
+ xml_element.add_element( NOTE_ELEMENT_NAME ).add_text( @note ) if @note
139
+ if @references.any?
140
+ xml_element.add_element( REFERENCES_ELEMENT_NAME ).add_text( @references.join( ' ' ))
141
+ end
142
+ if @incidents.any?
143
+ xml_element.add_element( INCIDENTS_ELEMENT_NAME ).add_text( @incidents.join( ' ' ))
144
+ end
145
+ @infos.each do |info|
146
+ xml_element.add_element( info.to_xml_element )
147
+ end
148
+ xml_element
149
+ end
150
+
151
+ # @return [REXML::Document]
152
+ def to_xml_document
153
+ xml_document = REXML::Document.new
154
+ xml_document.add( REXML::XMLDecl.new )
155
+ xml_document.add( self.to_xml_element )
156
+ xml_document
157
+ end
158
+
159
+ # Returns a string containing the XML representation of the alert.
160
+ #
161
+ # @param [true,false] pretty_print Pretty print output
162
+ # @return [String]
163
+ def to_xml( pretty_print = false )
164
+ if pretty_print
165
+ xml_document = ""
166
+ RCAP::XML_PRETTY_PRINTER.write( self.to_xml_document, xml_document )
167
+ xml_document
168
+ else
169
+ self.to_xml_document.to_s
170
+ end
171
+ end
172
+
173
+ # Returns a string representation of the alert suitable for usage as a reference in a CAP message of the form
174
+ # sender,identifier,sent
175
+ #
176
+ # @return [String]
177
+ def to_reference
178
+ "#{ @sender },#{ @identifier },#{ @sent }"
179
+ end
180
+
181
+ # @return [String]
182
+ def inspect
183
+ alert_inspect = [ "CAP Version: #{ self.class::CAP_VERSION }",
184
+ "Identifier: #{ @identifier }",
185
+ "Sender: #{ @sender }",
186
+ "Sent: #{ @sent }",
187
+ "Status: #{ @status }",
188
+ "Message Type: #{ @msg_type }",
189
+ "Source: #{ @source }",
190
+ "Scope: #{ @scope }",
191
+ "Restriction: #{ @restriction }",
192
+ "Addresses: #{ @addresses.to_s_for_cap }",
193
+ "Codes:",
194
+ @codes.map{ |code| " " + code }.join("\n")+"",
195
+ "Note: #{ @note }",
196
+ "References: #{ @references.join( ' ' )}",
197
+ "Incidents: #{ @incidents.join( ' ')}",
198
+ "Information:",
199
+ @infos.map{ |info| " " + info.to_s }.join( "\n" )].join( "\n" )
200
+ RCAP.format_lines_for_inspect( 'ALERT', alert_inspect )
201
+ end
202
+
203
+ # Returns a string representation of the alert of the form
204
+ # sender/identifier/sent
205
+ # See {#to_reference} for another string representation suitable as a CAP reference.
206
+ #
207
+ # @return [String]
208
+ def to_s
209
+ "#{ @sender }/#{ @identifier }/#{ @sent }"
210
+ end
211
+
212
+ XPATH = 'cap:alert'
213
+ IDENTIFIER_XPATH = "cap:#{ IDENTIFIER_ELEMENT_NAME }"
214
+ SENDER_XPATH = "cap:#{ SENDER_ELEMENT_NAME }"
215
+ SENT_XPATH = "cap:#{ SENT_ELEMENT_NAME }"
216
+ STATUS_XPATH = "cap:#{ STATUS_ELEMENT_NAME }"
217
+ MSG_TYPE_XPATH = "cap:#{ MSG_TYPE_ELEMENT_NAME }"
218
+ SOURCE_XPATH = "cap:#{ SOURCE_ELEMENT_NAME }"
219
+ SCOPE_XPATH = "cap:#{ SCOPE_ELEMENT_NAME }"
220
+ RESTRICTION_XPATH = "cap:#{ RESTRICTION_ELEMENT_NAME }"
221
+ ADDRESSES_XPATH = "cap:#{ ADDRESSES_ELEMENT_NAME }"
222
+ CODE_XPATH = "cap:#{ CODE_ELEMENT_NAME }"
223
+ NOTE_XPATH = "cap:#{ NOTE_ELEMENT_NAME }"
224
+ REFERENCES_XPATH = "cap:#{ REFERENCES_ELEMENT_NAME }"
225
+ INCIDENTS_XPATH = "cap:#{ INCIDENTS_ELEMENT_NAME }"
226
+
227
+ # @param [REXML::Element] alert_xml_element
228
+ # @return [RCAP::CAP_1_0::Alert]
229
+ def self.from_xml_element( alert_xml_element )
230
+ self.new do |alert|
231
+ alert.identifier = RCAP.xpath_text( alert_xml_element, IDENTIFIER_XPATH, alert.xmlns )
232
+ alert.sender = RCAP.xpath_text( alert_xml_element, SENDER_XPATH, alert.xmlns )
233
+ alert.sent = (( sent = RCAP.xpath_first( alert_xml_element, SENT_XPATH, alert.xmlns )) ? DateTime.parse( sent.text ) : nil )
234
+ alert.status = RCAP.xpath_text( alert_xml_element, STATUS_XPATH, alert.xmlns )
235
+ alert.msg_type = RCAP.xpath_text( alert_xml_element, MSG_TYPE_XPATH, alert.xmlns )
236
+ alert.source = RCAP.xpath_text( alert_xml_element, SOURCE_XPATH, alert.xmlns )
237
+ alert.scope = RCAP.xpath_text( alert_xml_element, SCOPE_XPATH, alert.xmlns )
238
+ alert.restriction = RCAP.xpath_text( alert_xml_element, RESTRICTION_XPATH, alert.xmlns )
239
+
240
+ (( address = RCAP.xpath_text( alert_xml_element, ADDRESSES_XPATH, alert.xmlns )) ? address.unpack_cap_list : [] ).each do |address|
241
+ alert.addresses << address
242
+ end
243
+ RCAP.xpath_match( alert_xml_element, CODE_XPATH, alert.xmlns ).each do |element|
244
+ alert.codes << element.text
245
+ end
246
+ alert.note = RCAP.xpath_text( alert_xml_element, NOTE_XPATH, alert.xmlns )
247
+ (( references = RCAP.xpath_text( alert_xml_element, REFERENCES_XPATH, alert.xmlns )) ? references.split( ' ' ) : []).each do |reference|
248
+ alert.references << reference
249
+ end
250
+
251
+ (( incidents = RCAP.xpath_text( alert_xml_element, INCIDENTS_XPATH, alert.xmlns )) ? incidents.split( ' ' ) : []).each do |incident|
252
+ alert.incidents << incident
253
+ end
254
+
255
+ RCAP.xpath_match( alert_xml_element, Info::XPATH, alert.xmlns ).each do |element|
256
+ alert.infos << alert.info_class.from_xml_element( element )
257
+ end
258
+ end
259
+ end
260
+
261
+ # @param [REXML::Document] xml_document
262
+ # @return [Alert]
263
+ def self.from_xml_document( xml_document )
264
+ self.from_xml_element( xml_document.root )
265
+ end
266
+
267
+ # Initialise an Alert object from an XML string. Any object that is a subclass of IO (e.g. File) can be passed in.
268
+ #
269
+ # @param [String] xml
270
+ # @return [Alert]
271
+ def self.from_xml( xml )
272
+ self.from_xml_document( REXML::Document.new( xml ))
273
+ end
274
+
275
+ CAP_VERSION_YAML = "CAP Version"
276
+ IDENTIFIER_YAML = "Identifier"
277
+ SENDER_YAML = "Sender"
278
+ SENT_YAML = "Sent"
279
+ STATUS_YAML = "Status"
280
+ MSG_TYPE_YAML = "Message Type"
281
+ SOURCE_YAML = "Source"
282
+ SCOPE_YAML = "Scope"
283
+ RESTRICTION_YAML = "Restriction"
284
+ ADDRESSES_YAML = "Addresses"
285
+ CODES_YAML = "Codes"
286
+ NOTE_YAML = "Note"
287
+ REFERENCES_YAML = "References"
288
+ INCIDENTS_YAML = "Incidents"
289
+ INFOS_YAML = "Information"
290
+
291
+ # Returns a string containing the YAML representation of the alert.
292
+ #
293
+ # @return [String]
294
+ def to_yaml( options = {} )
295
+ RCAP.attribute_values_to_hash( [ CAP_VERSION_YAML, self.class::CAP_VERSION ],
296
+ [ IDENTIFIER_YAML, @identifier ],
297
+ [ SENDER_YAML, @sender ],
298
+ [ SENT_YAML, @sent ],
299
+ [ STATUS_YAML, @status ],
300
+ [ MSG_TYPE_YAML, @msg_type ],
301
+ [ SOURCE_YAML, @source ],
302
+ [ SCOPE_YAML, @scope ],
303
+ [ RESTRICTION_YAML, @restriction ],
304
+ [ ADDRESSES_YAML, @addresses ],
305
+ [ CODES_YAML, @codes ],
306
+ [ NOTE_YAML, @note ],
307
+ [ REFERENCES_YAML, @references ],
308
+ [ INCIDENTS_YAML, @incidents ],
309
+ [ INFOS_YAML, @infos ]).to_yaml( options )
310
+ end
311
+
312
+ # Initialise an Alert object from a YAML string. Any object that is a subclass of IO (e.g. File) can be passed in.
313
+ #
314
+ # @param [String] yaml
315
+ # @return [Alert]
316
+ def self.from_yaml( yaml )
317
+ self.from_yaml_data( YAML.load( yaml ))
318
+ end
319
+
320
+ # Initialise an Alert object from a hash reutrned from YAML.load.
321
+ #
322
+ # @param [hash] alert_yaml_data
323
+ # @return [Alert]
324
+ def self.from_yaml_data( alert_yaml_data )
325
+ self.new do |alert|
326
+ alert.identifier = alert_yaml_data[ IDENTIFIER_YAML ]
327
+ alert.sender = alert_yaml_data[ SENDER_YAML ]
328
+ alert.sent = ( sent = alert_yaml_data[ SENT_YAML ]).blank? ? nil : DateTime.parse( sent.to_s )
329
+ alert.status = alert_yaml_data[ STATUS_YAML ]
330
+ alert.msg_type = alert_yaml_data[ MSG_TYPE_YAML ]
331
+ alert.source = alert_yaml_data[ SOURCE_YAML ]
332
+ alert.scope = alert_yaml_data[ SCOPE_YAML ]
333
+ alert.restriction = alert_yaml_data[ RESTRICTION_YAML ]
334
+ Array( alert_yaml_data[ ADDRESSES_YAML ]).each do |address|
335
+ alert.addresses << address
336
+ end
337
+ Array( alert_yaml_data[ CODES_YAML ]).each do |code|
338
+ alert.codes << code
339
+ end
340
+ alert.note = alert_yaml_data[ NOTE_YAML ]
341
+ Array( alert_yaml_data[ REFERENCES_YAML ]).each do |reference|
342
+ alert.references << reference
343
+ end
344
+ Array( alert_yaml_data[ INCIDENTS_YAML ]).each do |incident|
345
+ alert.incidents << incident
346
+ end
347
+ Array( alert_yaml_data[ INFOS_YAML ]).each do |info_yaml_data|
348
+ alert.infos << alert.info_class.from_yaml_data( info_yaml_data )
349
+ end
350
+ end
351
+ end
352
+
353
+ CAP_VERSION_KEY = 'cap_version'
354
+ IDENTIFIER_KEY = 'identifier'
355
+ SENDER_KEY = 'sender'
356
+ SENT_KEY = 'sent'
357
+ STATUS_KEY = 'status'
358
+ MSG_TYPE_KEY = 'msg_type'
359
+ SOURCE_KEY = 'source'
360
+ SCOPE_KEY = 'scope'
361
+ RESTRICTION_KEY = 'restriction'
362
+ ADDRESSES_KEY = 'addresses'
363
+ CODES_KEY = 'codes'
364
+ NOTE_KEY = 'note'
365
+ REFERENCES_KEY = 'references'
366
+ INCIDENTS_KEY = 'incidents'
367
+ INFOS_KEY = 'infos'
368
+
369
+ # Returns a Hash representation of an Alert object
370
+ #
371
+ # @return [Hash]
372
+ def to_h
373
+ RCAP.attribute_values_to_hash( [ CAP_VERSION_KEY, self.class::CAP_VERSION ],
374
+ [ IDENTIFIER_KEY, @identifier ],
375
+ [ SENDER_KEY, @sender ],
376
+ [ SENT_KEY, RCAP.to_s_for_cap( @sent )],
377
+ [ STATUS_KEY, @status ],
378
+ [ MSG_TYPE_KEY, @msg_type ],
379
+ [ SOURCE_KEY, @source ],
380
+ [ SCOPE_KEY, @scope ],
381
+ [ RESTRICTION_KEY, @restriction ],
382
+ [ ADDRESSES_KEY, @addresses ],
383
+ [ CODES_KEY, @codes ],
384
+ [ NOTE_KEY, @note ],
385
+ [ REFERENCES_KEY, @references ],
386
+ [ INCIDENTS_KEY, @incidents ],
387
+ [ INFOS_KEY, @infos.map{ |info| info.to_h }])
388
+ end
389
+
390
+ # Initialises an Alert object from a Hash produced by Alert#to_h
391
+ #
392
+ # @param [Hash] alert_hash
393
+ # @return [RCAP::CAP_1_0::Alert]
394
+ def self.from_h( alert_hash )
395
+ self.new do |alert|
396
+ alert.identifier = alert_hash[ IDENTIFIER_KEY ]
397
+ alert.sender = alert_hash[ SENDER_KEY ]
398
+ alert.sent = RCAP.parse_datetime( alert_hash[ SENT_KEY ])
399
+ alert.status = alert_hash[ STATUS_KEY ]
400
+ alert.msg_type = alert_hash[ MSG_TYPE_KEY ]
401
+ alert.source = alert_hash[ SOURCE_KEY ]
402
+ alert.scope = alert_hash[ SCOPE_KEY ]
403
+ alert.restriction = alert_hash[ RESTRICTION_KEY ]
404
+ Array( alert_hash[ ADDRESSES_KEY ]).each do |address|
405
+ alert.addresses << address
406
+ end
407
+ Array( alert_hash[ CODES_KEY ]).each do |code|
408
+ alert.codes << code
409
+ end
410
+ alert.note = alert_hash[ NOTE_KEY ]
411
+ Array( alert_hash[ REFERENCES_KEY ]).each do |reference|
412
+ alert.references << reference
413
+ end
414
+
415
+ Array( alert_hash[ INCIDENTS_KEY ]).each do |incident|
416
+ alert.incidents << incident
417
+ end
418
+
419
+ Array( alert_hash[ INFOS_KEY ]).each do |info_hash|
420
+ alert.infos << alert.info_class.from_h( info_hash )
421
+ end
422
+ end
423
+ end
424
+
425
+ # Returns a JSON string representation of an Alert object
426
+ #
427
+ # @param [true,false] pretty_print
428
+ # @return [String]
429
+ def to_json( pretty_print = false )
430
+ if pretty_print
431
+ JSON.pretty_generate( self.to_h )
432
+ else
433
+ self.to_h.to_json
434
+ end
435
+ end
436
+
437
+ # Initialises an Alert object from a JSON string produced by Alert#to_json
438
+ #
439
+ # @param [String] json_string
440
+ # @return [Alert]
441
+ def self.from_json( json_string )
442
+ self.from_h( JSON.parse( json_string ))
443
+ end
444
+ end
445
+ end
446
+ end
@@ -0,0 +1,228 @@
1
+ module RCAP
2
+ module Base
3
+ class Area
4
+ include Validation
5
+
6
+ # @return [String] Textual description of area.
7
+ attr_accessor( :area_desc )
8
+ # @return [Numeric] Expressed in feet above sea level
9
+ attr_accessor( :altitude )
10
+ # @return [Numeric] Expressed in feet above sea level.
11
+ attr_accessor( :ceiling )
12
+ # @return [Array<Circle>]
13
+ attr_reader( :circles )
14
+ # @return [Array<Geocode>]
15
+ attr_reader( :geocodes )
16
+ # @return [Array<Polygon>]
17
+ attr_reader( :polygons )
18
+
19
+ validates_presence_of( :area_desc )
20
+ validates_collection_of( :circles, :geocodes, :polygons, allow_empty: true )
21
+ validates_dependency_of( :ceiling, on: :altitude )
22
+
23
+ XML_ELEMENT_NAME = 'area'
24
+ AREA_DESC_ELEMENT_NAME = 'areaDesc'
25
+ ALTITUDE_ELEMENT_NAME = 'altitude'
26
+ CEILING_ELEMENT_NAME = 'ceiling'
27
+
28
+ XPATH = "cap:#{ XML_ELEMENT_NAME }"
29
+ AREA_DESC_XPATH = "cap:#{ AREA_DESC_ELEMENT_NAME }"
30
+ ALTITUDE_XPATH = "cap:#{ ALTITUDE_ELEMENT_NAME }"
31
+ CEILING_XPATH = "cap:#{ CEILING_ELEMENT_NAME }"
32
+
33
+ # @example
34
+ # Area.new do |area|
35
+ # area.area_desc = 'Cape Town CVD'
36
+ # end
37
+ def initialize
38
+ @area_desc = nil
39
+ @altitude = nil
40
+ @ceiling = nil
41
+
42
+ @circles = []
43
+ @geocodes = []
44
+ @polygons = []
45
+ yield( self ) if block_given?
46
+ end
47
+
48
+ # Creates a new {Polygon} object and adds it to the {#polygons} array.
49
+ #
50
+ # @return [Polygon]
51
+ def add_polygon
52
+ self.polygon_class.new.tap do |polygon|
53
+ yield( polygon ) if block_given?
54
+ @polygons << polygon
55
+ end
56
+ end
57
+
58
+ # Creates a new {Circle} object and adds it to the {#circles} array.
59
+ #
60
+ # @return [Circle]
61
+ def add_circle
62
+ self.circle_class.new.tap do |circle|
63
+ yield( circle ) if block_given?
64
+ @circles << circle
65
+ end
66
+ end
67
+
68
+ # Creates a new {Geocode} object and adds it to the {#geocodes} array.
69
+ #
70
+ # @return [Geocode]
71
+ def add_geocode
72
+ self.geocode_class.new do |geocode|
73
+ yield( geocode ) if block_given?
74
+ @geocodes << geocode
75
+ end
76
+ end
77
+
78
+ def self.from_xml_element( area_xml_element )
79
+ self.new do |area|
80
+ area.area_desc = RCAP.xpath_text( area_xml_element, AREA_DESC_XPATH, area.xmlns )
81
+ area.altitude = (( alt = RCAP.xpath_text( area_xml_element, ALTITUDE_XPATH, area.xmlns )) ? alt.to_f : nil )
82
+ area.ceiling = (( ceil = RCAP.xpath_text( area_xml_element, CEILING_XPATH, area.xmlns )) ? ceil.to_f : nil )
83
+
84
+ RCAP.xpath_match( area_xml_element, area.circle_class::XPATH, area.xmlns ).each do |circle_element|
85
+ area.circles << area.circle_class.from_xml_element( circle_element )
86
+ end
87
+
88
+ RCAP.xpath_match( area_xml_element, area.geocode_class::XPATH, area.xmlns ).each do |geocode_element|
89
+ area.geocodes << area.geocode_class.from_xml_element( geocode_element )
90
+ end
91
+
92
+ RCAP.xpath_match( area_xml_element, area.polygon_class::XPATH, area.xmlns ).each do |polygon_element|
93
+ area.polygons << area.polygon_class.from_xml_element( polygon_element )
94
+ end
95
+ end
96
+ end
97
+
98
+ # @return [REXML::Element]
99
+ def to_xml_element
100
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
101
+ xml_element.add_element( AREA_DESC_ELEMENT_NAME ).add_text( @area_desc.to_s )
102
+ add_to_xml_element = lambda do |element, object|
103
+ element.add_element( object.to_xml_element )
104
+ element
105
+ end
106
+ @polygons.inject( xml_element, &add_to_xml_element )
107
+ @circles.inject( xml_element, &add_to_xml_element )
108
+ @geocodes.inject( xml_element, &add_to_xml_element )
109
+ xml_element.add_element( ALTITUDE_ELEMENT_NAME ).add_text( @altitude.to_s ) unless @altitude.blank?
110
+ xml_element.add_element( CEILING_ELEMENT_NAME ).add_text( @ceiling.to_s ) unless @altitude.blank?
111
+ xml_element
112
+ end
113
+
114
+ # @return [String] XML representation of the Area
115
+ def to_xml
116
+ self.to_xml_element.to_s
117
+ end
118
+
119
+ # Implements an equality operator for the Area object. Two Area objects are equal if all their attributes are equal.
120
+ #
121
+ # @param [Area] other Area object to compare
122
+ # @return [true,false]
123
+ def ==( other )
124
+ comparison_attributes = lambda{ |area| [ area.area_desc, area.altitude, area.ceiling, area.circles, area.geocodes, area.polygons ]}
125
+ comparison_attributes.call( self ) == comparison_attributes.call( other )
126
+ end
127
+
128
+ # @return [String]
129
+ def inspect
130
+ area_inspect = "Area Description: #{ @area_desc }\n"+
131
+ "Polygons:\n"+
132
+ @polygons.map{ |polygon| " " + polygon.inspect }.join("\n" )+"\n"+
133
+ "Circles: #{ @circles.inspect }\n"+
134
+ "Geocodes: #{ @geocodes.inspect }\n"
135
+ RCAP.format_lines_for_inspect( 'AREA', area_inspect )
136
+ end
137
+
138
+ # Returns the area description
139
+ #
140
+ # @return [String]
141
+ def to_s
142
+ @area_desc
143
+ end
144
+
145
+
146
+ AREA_DESC_YAML = 'Area Description'
147
+ ALTITUDE_YAML = 'Altitude'
148
+ CEILING_YAML = 'Ceiling'
149
+ CIRCLES_YAML = 'Circles'
150
+ GEOCODES_YAML = 'Geocodes'
151
+ POLYGONS_YAML = 'Polygons'
152
+
153
+ # @return [String] YAML representation of object
154
+ def to_yaml( options = {} )
155
+ RCAP.attribute_values_to_hash( [ AREA_DESC_YAML, @area_desc ],
156
+ [ ALTITUDE_YAML, @altitude ],
157
+ [ CEILING_YAML, @ceiling ],
158
+ [ CIRCLES_YAML, @circles.map{ |circle| circle.to_a }],
159
+ [ GEOCODES_YAML, @geocodes.inject({}){|h,geocode| h.merge( geocode.name => geocode.value )}],
160
+ [ POLYGONS_YAML, @polygons ]).to_yaml( options )
161
+ end
162
+
163
+ # @param [Hash] area_yaml_data
164
+ # @return [Area]
165
+ def self.from_yaml_data( area_yaml_data )
166
+ self.new do |area|
167
+ area.area_desc = area_yaml_data[ AREA_DESC_YAML ]
168
+ area.altitude = area_yaml_data[ ALTITUDE_YAML ]
169
+ area.ceiling = area_yaml_data[ CEILING_YAML ]
170
+
171
+ Array( area_yaml_data[ CIRCLES_YAML ]).each do |circle_yaml_data|
172
+ area.circles << area.circle_class.from_yaml_data( circle_yaml_data )
173
+ end
174
+
175
+ Array( area_yaml_data[ GEOCODES_YAML ]).each do |name, value|
176
+ area.add_geocode do |geocode|
177
+ geocode.name = name
178
+ geocode.value = value
179
+ end
180
+ end
181
+
182
+ Array( area_yaml_data[ POLYGONS_YAML ]).each do |polyon_yaml_data|
183
+ area.polygons << area.polygon_class.from_yaml_data( polyon_yaml_data )
184
+ end
185
+ end
186
+ end
187
+
188
+ AREA_DESC_KEY = 'area_desc'
189
+ ALTITUDE_KEY = 'altitude'
190
+ CEILING_KEY = 'ceiling'
191
+ CIRCLES_KEY = 'circles'
192
+ GEOCODES_KEY = 'geocodes'
193
+ POLYGONS_KEY = 'polygons'
194
+
195
+ # @param [Hash] area_hash
196
+ # @return [Area]
197
+ def self.from_h( area_hash )
198
+ self.new do |area|
199
+ area.area_desc = area_hash[ AREA_DESC_KEY ]
200
+ area.altitude = area_hash[ ALTITUDE_KEY ]
201
+ area.ceiling = area_hash[ CEILING_KEY ]
202
+
203
+ Array( area_hash[ CIRCLES_KEY ]).each do |circle_array|
204
+ area.circles << area.circle_class.from_a( circle_array )
205
+ end
206
+
207
+ Array( area_hash[ GEOCODES_KEY ]).each do |geocode_hash|
208
+ area.geocodes << area.geocode_class.from_h( geocode_hash )
209
+ end
210
+
211
+ Array( area_hash[ POLYGONS_KEY ]).each do |polygon_hash|
212
+ area.polygons << area.polygon_class.from_h( polygon_hash )
213
+ end
214
+ end
215
+ end
216
+
217
+ # @return [Hash]
218
+ def to_h
219
+ RCAP.attribute_values_to_hash( [ AREA_DESC_KEY, @area_desc ],
220
+ [ ALTITUDE_KEY, @altitude ],
221
+ [ CEILING_KEY, @ceiling ],
222
+ [ CIRCLES_KEY, @circles.map{ |circle| circle.to_a }],
223
+ [ GEOCODES_KEY, @geocodes.map{ |geocode| geocode.to_h }],
224
+ [ POLYGONS_KEY, @polygons.map{ |polygon| polygon.to_h }])
225
+ end
226
+ end
227
+ end
228
+ end