rcap 1.0.1 → 1.1.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 (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