rcap 1.3.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -0
- data/README.md +78 -151
- data/Rakefile +1 -1
- data/lib/rcap/alert.rb +2 -2
- data/lib/rcap/base/alert.rb +446 -0
- data/lib/rcap/base/area.rb +228 -0
- data/lib/rcap/base/circle.rb +121 -0
- data/lib/rcap/base/event_code.rb +6 -0
- data/lib/rcap/base/geocode.rb +6 -0
- data/lib/rcap/base/info.rb +498 -0
- data/lib/rcap/base/parameter.rb +88 -0
- data/lib/rcap/base/point.rb +87 -0
- data/lib/rcap/base/polygon.rb +120 -0
- data/lib/rcap/base/resource.rb +168 -0
- data/lib/rcap/cap_1_0/alert.rb +59 -342
- data/lib/rcap/cap_1_0/area.rb +13 -188
- data/lib/rcap/cap_1_0/circle.rb +2 -100
- data/lib/rcap/cap_1_0/event_code.rb +8 -3
- data/lib/rcap/cap_1_0/geocode.rb +8 -3
- data/lib/rcap/cap_1_0/info.rb +16 -468
- data/lib/rcap/cap_1_0/parameter.rb +9 -67
- data/lib/rcap/cap_1_0/point.rb +2 -61
- data/lib/rcap/cap_1_0/polygon.rb +5 -95
- data/lib/rcap/cap_1_0/resource.rb +4 -144
- data/lib/rcap/cap_1_1/alert.rb +7 -412
- data/lib/rcap/cap_1_1/area.rb +13 -188
- data/lib/rcap/cap_1_1/circle.rb +2 -100
- data/lib/rcap/cap_1_1/event_code.rb +8 -3
- data/lib/rcap/cap_1_1/geocode.rb +7 -3
- data/lib/rcap/cap_1_1/info.rb +127 -386
- data/lib/rcap/cap_1_1/parameter.rb +4 -76
- data/lib/rcap/cap_1_1/point.rb +2 -61
- data/lib/rcap/cap_1_1/polygon.rb +5 -95
- data/lib/rcap/cap_1_1/resource.rb +37 -143
- data/lib/rcap/cap_1_2/alert.rb +8 -413
- data/lib/rcap/cap_1_2/area.rb +13 -188
- data/lib/rcap/cap_1_2/circle.rb +2 -100
- data/lib/rcap/cap_1_2/event_code.rb +8 -3
- data/lib/rcap/cap_1_2/geocode.rb +8 -3
- data/lib/rcap/cap_1_2/info.rb +132 -419
- data/lib/rcap/cap_1_2/parameter.rb +4 -76
- data/lib/rcap/cap_1_2/point.rb +2 -61
- data/lib/rcap/cap_1_2/polygon.rb +5 -92
- data/lib/rcap/cap_1_2/resource.rb +31 -134
- data/lib/rcap/config.rb +3 -0
- data/lib/{extensions → rcap/extensions}/array.rb +1 -1
- data/lib/rcap/extensions/date.rb +11 -0
- data/lib/{extensions → rcap/extensions}/date_time.rb +2 -5
- data/lib/{extensions → rcap/extensions}/string.rb +1 -1
- data/lib/{extensions → rcap/extensions}/time.rb +2 -4
- data/lib/rcap/utilities.rb +11 -11
- data/lib/rcap/validations.rb +7 -2
- data/lib/rcap/version.rb +1 -1
- data/lib/rcap.rb +21 -4
- data/spec/alert_spec.rb +69 -37
- data/spec/cap_1_0/alert_spec.rb +46 -61
- data/spec/cap_1_0/area_spec.rb +77 -37
- data/spec/cap_1_0/circle_spec.rb +26 -6
- data/spec/cap_1_0/event_code_spec.rb +10 -3
- data/spec/cap_1_0/geocode_spec.rb +11 -4
- data/spec/cap_1_0/info_spec.rb +74 -77
- data/spec/cap_1_0/parameter_spec.rb +18 -5
- data/spec/cap_1_0/point_spec.rb +9 -2
- data/spec/cap_1_0/polygon_spec.rb +52 -9
- data/spec/cap_1_0/resource_spec.rb +28 -21
- data/spec/cap_1_1/alert_spec.rb +47 -60
- data/spec/cap_1_1/area_spec.rb +66 -43
- data/spec/cap_1_1/circle_spec.rb +29 -6
- data/spec/cap_1_1/event_code_spec.rb +11 -3
- data/spec/cap_1_1/geocode_spec.rb +11 -3
- data/spec/cap_1_1/info_spec.rb +262 -118
- data/spec/cap_1_1/parameter_spec.rb +12 -3
- data/spec/cap_1_1/point_spec.rb +8 -2
- data/spec/cap_1_1/polygon_spec.rb +57 -18
- data/spec/cap_1_1/resource_spec.rb +70 -20
- data/spec/cap_1_2/alert_spec.rb +97 -110
- data/spec/cap_1_2/area_spec.rb +59 -41
- data/spec/cap_1_2/circle_spec.rb +15 -8
- data/spec/cap_1_2/event_code_spec.rb +11 -3
- data/spec/cap_1_2/geocode_spec.rb +11 -3
- data/spec/cap_1_2/info_spec.rb +266 -119
- data/spec/cap_1_2/parameter_spec.rb +11 -3
- data/spec/cap_1_2/point_spec.rb +10 -3
- data/spec/cap_1_2/polygon_spec.rb +25 -10
- data/spec/cap_1_2/resource_spec.rb +33 -28
- data/spec/{utilities_spec.rb → extensions_spec.rb} +0 -0
- data/spec/validations_spec.rb +18 -2
- 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
|