rcap 1.3.1 → 2.0.0

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