rcap 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,11 @@
1
+ = Change Log
2
+
3
+ == 0.2 - 20th November 2009
4
+
5
+ * Implemented to_s/inspect methods for all classes
6
+ * Implemented YAML generation and parsing
7
+ * Documentation improvements
8
+
9
+ == 0.1 - 5th November 2009
10
+
11
+ * Initial release
@@ -4,29 +4,49 @@
4
4
 
5
5
  The Common Alerting Protocol is a lightweight standard to facilitate the distribution of alerting data. RCAP is an implementation of the CAP in Ruby. It allows for the creation of RCAP messages from Ruby applications and the parsing of external messages.
6
6
 
7
- RCAP supports CAP Version 1.1.
7
+ RCAP currently supports only CAP Version 1.1.
8
8
 
9
- == Usage
9
+ == Version
10
10
 
11
- To include RCAP into your application add the following require
11
+ 0.2
12
12
 
13
- require 'rcap/rcap'
13
+ == Dependencies
14
14
 
15
- === Creating an Alert
15
+ RCAP depends on the following gems
16
16
 
17
- ==== From XML
17
+ * {Assistance}[http://assistance.rubyforge.org]
18
+ * {UUIDTools}[http://uuidtools.rubyforge.org]
18
19
 
19
- RCAP allows for the parsing of a CAP XML string into a collection of RCAP objects
20
+ RCAP uses the REXML API, included in Ruby, to parse and generate XML.
20
21
 
21
- alert = RCAP::Alert.from_xml( xml_string )
22
+ == Installation
22
23
 
23
- Currently RCAP only supports version 1.1 of the CAP standard and the parser is as strict as possible when parsing data.
24
+ RCAP is distributed as a Ruby gem and is available from {Gemcutter}[http://gemcutter.org]. If you have Gemcutter set as a source of your gems then RCAP can be installed from the command line
25
+
26
+ gem install rcap
27
+
28
+ The gem is also available for download and manual installtion at http://www.aimred.com/gems .
24
29
 
25
- ==== Programatically
30
+ == Web resources
26
31
 
27
- Alerts can also be created programatically by initialising the various RCAP classes manually
32
+ * The RCAP project page can be found at http://www.aimred.com/projects/rcap
33
+ * The RCAP API docs can be fount at http://www.aimred.com/projects/rcap/api
34
+ * A public git repository can be found at http:///www.aimred.com/git/rcap.git
35
+
36
+ == Usage
37
+
38
+ To include RCAP into your application add the following require
39
+
40
+ require 'rcap'
41
+
42
+ All RCAP classes reside in the RCAP namespace but including the RCAP module makes the classes available at the top level without the RCAP prefix.
28
43
 
44
+ alert = RCAP::Alert.new(...
45
+
29
46
  include RCAP # Include RCAP module into namespace
47
+ alert = Alert.new(...
48
+
49
+ === Creating an Alert
30
50
 
31
51
  alert = Alert.new( :sender => 'cape_town_disaster_relief@capetown.municipal.za',
32
52
  :status => Alert::STATUS_ACTUAL,
@@ -49,6 +69,12 @@ Alerts can also be created programatically by initialising the various RCAP clas
49
69
  puts alert.infos[0].language # Print out "en-ZA"
50
70
  puts alert.infos[0].categories.join( ' ' ) # Print out "Transport Fire"
51
71
 
72
+ === Exporting an Alert
73
+
74
+ ==== To XML
75
+
76
+ Using the alert message created above
77
+
52
78
  puts alert.to_xml # Print out CAP XML message
53
79
 
54
80
  Will print the following CAP XML
@@ -57,7 +83,7 @@ Will print the following CAP XML
57
83
  <alert xmlns='urn:oasis:names:tc:emergency:cap:1.1'>
58
84
  <identifier>494207a7-f86b-4060-8318-a4b2a3ce565e</identifier>
59
85
  <sender>cape_town_disaster_relief@capetown.municipal.za</sender>
60
- <sent>2009-10-26T21:04:51+2:00</sent>
86
+ <sent>2009-10-26T21:04:51+02:00</sent>
61
87
  <status>Actual</status>
62
88
  <msgType>Alert</msgType>
63
89
  <scope>Public</scope>
@@ -79,36 +105,56 @@ Will print the following CAP XML
79
105
  </info>
80
106
  </alert>
81
107
 
108
+ ==== To YAML
82
109
 
83
- You can also create the various message elements seperately and then combine them later:
110
+ YAML is a plain text serialization format designed to be easily readable and editable by both human and machine. RCAP has custom YAML generation and parsing methods to produce a YAML document that is as human friednly as possible. The following code
84
111
 
85
- # Setting attributes at initialisation
86
- alert = Alert.new( :sender => 'cape_town_disaster_relief@capetown.municipal.za',
87
- :status => Alert::STATUS_ACTUAL,
88
- :msg_type => Alert::MSG_TYPE_ALERT,
89
- :scope => Alert::SCOPE_PUBLIC )
90
-
91
- # Setting attributes after initialisation
92
- info = Info.new
93
- info.event = 'Liquid Petroleoum Tanker Fire'
94
- info.language = 'en-ZA'
95
- info.categories = [ Info::CATEGORY_TRANSPORT, Info::CATEGORY_FIRE ]
96
- info.urgency = Info::URGENCY_IMMEDIATE
97
- info.severity = Info::SEVERITY_SEVERE
98
- info.certainty = Info::CERTAINTY_OBSERVED
112
+ alert.to_yaml
113
+
114
+ will produce the following YAML document
99
115
 
116
+ ---
117
+ Identifier: 2a1ba96d-16e4-4f52-85ea-0258c1440bd5
118
+ Sender: cape_town_disaster_relief@capetown.municipal.za
119
+ Sent: 2009-11-19T02:41:29+02:00
120
+ Status: Actual
121
+ Message Type: Alert
122
+ Scope: Public
123
+ Information:
124
+ - Language: en-ZA
125
+ Categories: [Transport, Fire]
126
+ Event: Liquid Petroleoum Tanker Fire
127
+ Urgency: Immediate
128
+ Severity: Severe
129
+ Certainty: Observed
130
+ Headline: LIQUID PETROLEOUM TANKER FIRE ON N2 INCOMING FREEWAY
131
+ Description: |-
132
+ A liquid petroleoum tanker has caught fire on the N2 incoming freeway 1km
133
+ after the R300 interchange. Municipal fire fighting crews have been dispatched.
134
+ Traffic control officers are on the scene and have diverted traffic onto
135
+ alternate routes.
100
136
 
101
- resource = Resource.new( :resource_desc => 'Cape Town Municipal Traffic Management Guidelines',
102
- :uri => 'http://capetown.municipal.za/traffic/management_guidelines.pdf',
103
- :mime_type => 'application/pdf')
137
+ Note: If you use Ruby 1.8 the order of the attributes is jumbled due to hashes being unorderd (Ruby 1.9 implements ordered hashes). This does not affect the ability to parse documents generated from RCAP::Alert#to_yaml, it just makes things the output slightly messy.
104
138
 
105
- # Combining all elements
106
- info.resources << resource
107
- alert.infos << info
139
+ === Parsing an Alert From An External Source
140
+
141
+ ==== From XML
142
+
143
+ RCAP allows for the parsing of a CAP XML string
144
+
145
+ alert = RCAP::Alert.from_xml( xml_string )
146
+
147
+ Currently RCAP only supports version 1.1 of the CAP standard and the parser is as strict as possible when parsing data.
148
+
149
+ ==== From YAML
150
+
151
+ Alert messgaes can be read in from text files containing data formatted in YAML as generated by Alert#to_yaml.
152
+
153
+ alert = RCAP::Alert.from_yaml( yaml_string )
108
154
 
109
155
  === Validating an alert
110
156
 
111
- The RCAP API aims to codify as many of the rules of the CAP XML format into validation rules that can be checked using the Assistance API (http://assistance.rubyforge.org). The following Info object has two attributes ('severity' and 'certainty') set to incorrect values.
157
+ The RCAP API aims to codify as many of the rules of the CAP XML format into validation rules that can be checked using the Assistance API. The following Info object has two attributes ('severity' and 'certainty') set to incorrect values.
112
158
 
113
159
  info = Info.new( :event => 'Liquid Petroleoum Tanker Fire',
114
160
  :language => 'en-ZA',
@@ -125,19 +171,14 @@ Will produce the folling output:
125
171
  severity is not present
126
172
  certainty can only be assigned the following values: Observed, Likely, Possible, Unlikely, Unknown
127
173
 
128
- A full spec suite using RSpec (http://www.rspec.info) was used to test the validations and currently numbers over 140 tests.
174
+ All RCAP classes include the Validation module.
129
175
 
130
- == Version
176
+ A full spec suite using {RSpec}[http://www.rspec.info] was used to test the validations and currently numbers over 150 tests.
131
177
 
132
- 0.1
178
+ === DateTime and Time
133
179
 
134
- == Dependencies
180
+ It is highly recommended that when dealing with date and time fields (onset, expires etc) that the DateTime class is used to ensure the correct formatting of dates. The Time class can be used when generating a CAP alert XML message however any CAP alert that is parsed from an external XML source will use DateTime by default.
135
181
 
136
- RCAP depends on the following gems
137
-
138
- * Assistance - http://assistance.rubyforge.org
139
- * UUIDTools - http://uuidtools.rubyforge.org
140
-
141
182
  == Authors
142
183
 
143
184
  Farrel Lifson - farrel.lifson@aimred.com
@@ -148,4 +189,4 @@ RCAP is released under the BSD License.
148
189
 
149
190
  == Copyright
150
191
 
151
- 2009 Aimred CC
192
+ 2009-2010 Aimred CC
data/lib/rcap/alert.rb CHANGED
@@ -10,24 +10,27 @@ module RCAP
10
10
  class Alert
11
11
  include Validation
12
12
 
13
- STATUS_ACTUAL = "Actual"
14
- STATUS_EXERCISE = "Exercise"
15
- STATUS_SYSTEM = "System"
16
- STATUS_TEST = "Test"
17
- STATUS_DRAFT = "Draft"
18
- ALL_STATUSES = [ STATUS_ACTUAL, STATUS_EXERCISE, STATUS_SYSTEM, STATUS_TEST, STATUS_DRAFT ] #:nodoc:
19
-
20
- MSG_TYPE_ALERT = "Alert"
21
- MSG_TYPE_UPDATE = "Update"
22
- MSG_TYPE_CANCEL = "Cancel"
23
- MSG_TYPE_ACK = "Ack"
24
- MSG_TYPE_ERROR = "Error"
25
- ALL_MSG_TYPES = [ MSG_TYPE_ALERT, MSG_TYPE_UPDATE, MSG_TYPE_CANCEL, MSG_TYPE_ACK, MSG_TYPE_ERROR ] #:nodoc:
26
-
27
- SCOPE_PUBLIC = "Public"
28
- SCOPE_RESTRICTED = "Restricted"
29
- SCOPE_PRIVATE = "Private"
30
- ALL_SCOPES = [ SCOPE_PUBLIC, SCOPE_PRIVATE, SCOPE_RESTRICTED ] #:nodoc:
13
+ STATUS_ACTUAL = "Actual" # :nodoc:
14
+ STATUS_EXERCISE = "Exercise" # :nodoc:
15
+ STATUS_SYSTEM = "System" # :nodoc:
16
+ STATUS_TEST = "Test" # :nodoc:
17
+ STATUS_DRAFT = "Draft" # :nodoc:
18
+ # Valid values for status
19
+ VALID_STATUSES = [ STATUS_ACTUAL, STATUS_EXERCISE, STATUS_SYSTEM, STATUS_TEST, STATUS_DRAFT ]
20
+
21
+ MSG_TYPE_ALERT = "Alert" # :nodoc:
22
+ MSG_TYPE_UPDATE = "Update" # :nodoc:
23
+ MSG_TYPE_CANCEL = "Cancel" # :nodoc:
24
+ MSG_TYPE_ACK = "Ack" # :nodoc:
25
+ MSG_TYPE_ERROR = "Error" # :nodoc:
26
+ # Valid values for msg_type
27
+ VALID_MSG_TYPES = [ MSG_TYPE_ALERT, MSG_TYPE_UPDATE, MSG_TYPE_CANCEL, MSG_TYPE_ACK, MSG_TYPE_ERROR ]
28
+
29
+ SCOPE_PUBLIC = "Public" # :nodoc:
30
+ SCOPE_RESTRICTED = "Restricted" # :nodoc:
31
+ SCOPE_PRIVATE = "Private" # :nodoc:
32
+ # Valid values for scope
33
+ VALID_SCOPES = [ SCOPE_PUBLIC, SCOPE_PRIVATE, SCOPE_RESTRICTED ]
31
34
 
32
35
  XML_ELEMENT_NAME = 'alert' # :nodoc:
33
36
  IDENTIFIER_ELEMENT_NAME = 'identifier' # :nodoc:
@@ -64,9 +67,11 @@ module RCAP
64
67
  attr_accessor( :sender )
65
68
  # Sent Time - If not set will value will be time of creation.
66
69
  attr_accessor( :sent )
70
+ # Value can only be one of VALID_STATUSES
67
71
  attr_accessor( :status )
68
- # Message Type
72
+ # Value can only be one of VALID_MSG_TYPES
69
73
  attr_accessor( :msg_type )
74
+ # Value can only be one of VALID_SCOPES
70
75
  attr_accessor( :scope )
71
76
  attr_accessor( :source )
72
77
  # Depends on scope being SCOPE_RESTRICTED.
@@ -85,9 +90,9 @@ module RCAP
85
90
 
86
91
  validates_presence_of( :identifier, :sender, :sent, :status, :msg_type, :scope )
87
92
 
88
- validates_inclusion_of( :status, :in => ALL_STATUSES )
89
- validates_inclusion_of( :msg_type, :in => ALL_MSG_TYPES )
90
- validates_inclusion_of( :scope, :in => ALL_SCOPES )
93
+ validates_inclusion_of( :status, :in => VALID_STATUSES )
94
+ validates_inclusion_of( :msg_type, :in => VALID_MSG_TYPES )
95
+ validates_inclusion_of( :scope, :in => VALID_SCOPES )
91
96
 
92
97
  validates_format_of( :identifier, :with => ALLOWED_CHARACTERS )
93
98
  validates_format_of( :sender , :with => ALLOWED_CHARACTERS )
@@ -152,35 +157,123 @@ module RCAP
152
157
  self.to_xml_document.to_s
153
158
  end
154
159
 
155
- # Returns a string of the format 'sender,identifier,sent' suitable for usage as a reference in a CAP message.
160
+ # Returns a string representation of the alert suitable for usage as a reference in a CAP message of the form
161
+ # sender,identifier,sent
156
162
  def to_reference
157
163
  "#{ self.sender },#{ self.identifier },#{ self.sent }"
158
164
  end
159
165
 
166
+ def inspect # :nodoc:
167
+ alert_inspect = <<EOF
168
+ Identifier: #{ self.identifier }
169
+ Sender: #{ self.sender }
170
+ Sent: #{ self.sent }
171
+ Status: #{ self.status }
172
+ Message Type: #{ self.msg_type }
173
+ Source: #{ self.source }
174
+ Scope: #{ self.scope }
175
+ Restriction: #{ self.restriction }
176
+ Addresses: #{ self.addresses.to_s_for_cap }
177
+ Code: #{ self.code }
178
+ Note: #{ self.note }
179
+ References: #{ self.references.join( ' ' )}
180
+ Incidents: #{ self.incidents.join( ' ')}
181
+ Information:
182
+ #{ self.infos.map{ |info| " " + info.to_s }.join( "\n" )}
183
+ EOF
184
+ RCAP.format_lines_for_inspect( 'ALERT', alert_inspect )
185
+ end
186
+
187
+ # Returns a string representation of the alert of the form
188
+ # sender/identifier/sent
189
+ # See Alert#to_reference for another string representation suitable as a CAP reference.
190
+ def to_s
191
+ "#{ self.sender }/#{ self.identifier }/#{ self.sent }"
192
+ end
193
+
160
194
  def self.from_xml_element( alert_xml_element ) # :nodoc:
161
- alert = RCAP::Alert.new( :identifier => RCAP.xpath_text( alert_xml_element, RCAP::Alert::IDENTIFIER_XPATH ),
162
- :sender => RCAP.xpath_text( alert_xml_element, SENDER_XPATH ),
163
- :sent => (( sent = RCAP.xpath_first( alert_xml_element, SENT_XPATH )) ? DateTime.parse( sent.text ) : nil ),
164
- :status => RCAP.xpath_text( alert_xml_element, STATUS_XPATH ),
165
- :msg_type => RCAP.xpath_text( alert_xml_element, MSG_TYPE_XPATH ),
166
- :source => RCAP.xpath_text( alert_xml_element, SOURCE_XPATH ),
167
- :scope => RCAP.xpath_text( alert_xml_element, SCOPE_XPATH ),
168
- :restriction => RCAP.xpath_text( alert_xml_element, RESTRICTION_XPATH ),
169
- :addresses => (( address = RCAP.xpath_text( alert_xml_element, ADDRESSES_XPATH )) ? address.unpack_cap_list : nil ),
170
- :code => RCAP.xpath_text( alert_xml_element, CODE_XPATH ),
171
- :note => RCAP.xpath_text( alert_xml_element, NOTE_XPATH ),
172
- :references => (( references = RCAP.xpath_text( alert_xml_element, REFERENCES_XPATH )) ? references.split( ' ' ) : nil ),
173
- :incidents => (( incidents = RCAP.xpath_text( alert_xml_element, INCIDENTS_XPATH )) ? incidents.split( ' ' ) : nil ),
174
- :infos => RCAP.xpath_match( alert_xml_element, RCAP::Info::XPATH ).map{ |element| RCAP::Info.from_xml_element( element )})
195
+ RCAP::Alert.new( :identifier => RCAP.xpath_text( alert_xml_element, RCAP::Alert::IDENTIFIER_XPATH ),
196
+ :sender => RCAP.xpath_text( alert_xml_element, SENDER_XPATH ),
197
+ :sent => (( sent = RCAP.xpath_first( alert_xml_element, SENT_XPATH )) ? DateTime.parse( sent.text ) : nil ),
198
+ :status => RCAP.xpath_text( alert_xml_element, STATUS_XPATH ),
199
+ :msg_type => RCAP.xpath_text( alert_xml_element, MSG_TYPE_XPATH ),
200
+ :source => RCAP.xpath_text( alert_xml_element, SOURCE_XPATH ),
201
+ :scope => RCAP.xpath_text( alert_xml_element, SCOPE_XPATH ),
202
+ :restriction => RCAP.xpath_text( alert_xml_element, RESTRICTION_XPATH ),
203
+ :addresses => (( address = RCAP.xpath_text( alert_xml_element, ADDRESSES_XPATH )) ? address.unpack_cap_list : nil ),
204
+ :code => RCAP.xpath_text( alert_xml_element, CODE_XPATH ),
205
+ :note => RCAP.xpath_text( alert_xml_element, NOTE_XPATH ),
206
+ :references => (( references = RCAP.xpath_text( alert_xml_element, REFERENCES_XPATH )) ? references.split( ' ' ) : nil ),
207
+ :incidents => (( incidents = RCAP.xpath_text( alert_xml_element, INCIDENTS_XPATH )) ? incidents.split( ' ' ) : nil ),
208
+ :infos => RCAP.xpath_match( alert_xml_element, RCAP::Info::XPATH ).map{ |element| RCAP::Info.from_xml_element( element )})
175
209
  end
176
210
 
177
211
  def self.from_xml_document( xml_document ) # :nodoc:
178
212
  self.from_xml_element( xml_document.root )
179
213
  end
180
214
 
181
- # Initialised an Alert object from the XML string.
182
- def self.from_xml( xml_string )
183
- self.from_xml_document( REXML::Document.new( xml_string ))
215
+ # Initialise an Alert object from an XML string. Any object that is a subclass of IO (e.g. File) can be passed in.
216
+ def self.from_xml( xml )
217
+ self.from_xml_document( REXML::Document.new( xml ))
184
218
  end
185
- end
219
+
220
+ IDENTIFIER_YAML = "Identifier" # :nodoc:
221
+ SENDER_YAML = "Sender" # :nodoc:
222
+ SENT_YAML = "Sent" # :nodoc:
223
+ STATUS_YAML = "Status" # :nodoc:
224
+ MSG_TYPE_YAML = "Message Type" # :nodoc:
225
+ SOURCE_YAML = "Source" # :nodoc:
226
+ SCOPE_YAML = "Scope" # :nodoc:
227
+ RESTRICTION_YAML = "Restriction" # :nodoc:
228
+ ADDRESSES_YAML = "Addresses" # :nodoc:
229
+ CODE_YAML = "Code" # :nodoc:
230
+ NOTE_YAML = "Note" # :nodoc:
231
+ REFERENCES_YAML = "References" # :nodoc:
232
+ INCIDENTS_YAML = "Incidents" # :nodoc:
233
+ INFOS_YAML = "Information" # :nodoc:
234
+
235
+ # Returns a string containing the YAML representation of the alert.
236
+ def to_yaml( options = {} )
237
+ RCAP.attribute_values_to_yaml_hash(
238
+ [ IDENTIFIER_YAML, self.identifier ],
239
+ [ SENDER_YAML, self.sender ],
240
+ [ SENT_YAML, self.sent ],
241
+ [ STATUS_YAML, self.status ],
242
+ [ MSG_TYPE_YAML, self.msg_type ],
243
+ [ SOURCE_YAML, self.source ],
244
+ [ SCOPE_YAML, self.scope ],
245
+ [ RESTRICTION_YAML, self.restriction ],
246
+ [ ADDRESSES_YAML, self.addresses ],
247
+ [ CODE_YAML, self.code ],
248
+ [ NOTE_YAML, self.note ],
249
+ [ REFERENCES_YAML, self.references ],
250
+ [ INCIDENTS_YAML, self.incidents ],
251
+ [ INFOS_YAML, self.infos ]
252
+ ).to_yaml( options )
253
+ end
254
+
255
+ # Initialise an Alert object from a YAML string. Any object that is a subclass of IO (e.g. File) can be passed in.
256
+ def self.from_yaml( yaml )
257
+ self.from_yaml_data( YAML.load( yaml ))
258
+ end
259
+
260
+ def self.from_yaml_data( alert_yaml_data ) # :nodoc:
261
+ Alert.new(
262
+ :identifier => alert_yaml_data[ IDENTIFIER_YAML ],
263
+ :sender => alert_yaml_data[ SENDER_YAML ],
264
+ :sent => ( sent = alert_yaml_data[ SENT_YAML ]).blank? ? nil : DateTime.parse( sent.to_s ),
265
+ :status => alert_yaml_data[ STATUS_YAML ],
266
+ :msg_type => alert_yaml_data[ MSG_TYPE_YAML ],
267
+ :source => alert_yaml_data[ SOURCE_YAML ],
268
+ :scope => alert_yaml_data[ SCOPE_YAML ],
269
+ :restriction => alert_yaml_data[ RESTRICTION_YAML ],
270
+ :addresses => alert_yaml_data[ ADDRESSES_YAML ],
271
+ :code => alert_yaml_data[ CODE_YAML ],
272
+ :note => alert_yaml_data[ NOTE_YAML ],
273
+ :references => alert_yaml_data[ REFERENCES_YAML ],
274
+ :incidents => alert_yaml_data[ INCIDENTS_YAML ],
275
+ :infos => Array( alert_yaml_data[ INFOS_YAML ]).map{ |info_yaml_data| RCAP::Info.from_yaml_data( info_yaml_data )}
276
+ )
277
+ end
278
+ end
186
279
  end
data/lib/rcap/area.rb CHANGED
@@ -55,7 +55,7 @@ module RCAP
55
55
  @circles.inject( xml_element, &add_to_xml_element )
56
56
  @geocodes.inject( xml_element, &add_to_xml_element )
57
57
  xml_element.add_element( ALTITUDE_ELEMENT_NAME ).add_text( @altitude.to_s ) unless self.altitude.blank?
58
- xml_element.add_element( CEILING_ELEMENT_NAME ).add_text( @ceiling.to_s ) unless self.altitude.blank?
58
+ xml_element.add_element( CEILING_ELEMENT_NAME ).add_text( @ceiling.to_s ) unless self.altitude.blank?
59
59
  xml_element
60
60
  end
61
61
 
@@ -69,14 +69,62 @@ module RCAP
69
69
  comparison_attributes.call( self ) == comparison_attributes.call( other )
70
70
  end
71
71
 
72
+ def inspect # :nodoc:
73
+ area_inspect = <<EOF
74
+ Area Description: #{ self.area_desc }
75
+ Polygons:
76
+ #{ self.polygons.map{ |polygon| " " + polygon.inspect }.join("\n" )}
77
+ Circles: #{ self.circles.inspect }
78
+ Geocodes: #{ self.geocodes.inspect }
79
+ EOF
80
+ RCAP.format_lines_for_inspect( 'AREA', area_inspect )
81
+ end
82
+
83
+ # Returns a string representation of the area of the form
84
+ # area_desc
85
+ def to_s
86
+ self.area_desc
87
+ end
88
+
72
89
  def self.from_xml_element( area_xml_element ) # :nodoc:
73
- area = RCAP::Area.new( :area_desc => RCAP.xpath_text( area_xml_element, AREA_DESC_XPATH ),
74
- :altitude => (( alt = RCAP.xpath_text( area_xml_element, ALTITUDE_XPATH )) ? alt.to_f : nil ),
75
- :ceiling => (( ceil = RCAP.xpath_text( area_xml_element, CEILING_XPATH )) ? ceil.to_f : nil ),
76
- :circles => RCAP.xpath_match( area_xml_element, RCAP::Circle::XPATH ).map{ |circle_element| RCAP::Circle.from_xml_element( circle_element )},
77
- :geocodes => RCAP.xpath_match( area_xml_element, RCAP::Geocode::XPATH ).map{ |geocode_element| RCAP::Geocode.from_xml_element( geocode_element )},
78
- :polygons => RCAP.xpath_match( area_xml_element, RCAP::Polygon::XPATH ).map{ |polygon_element| RCAP::Polygon.from_xml_element( polygon_element )})
79
- area
90
+ RCAP::Area.new( :area_desc => RCAP.xpath_text( area_xml_element, AREA_DESC_XPATH ),
91
+ :altitude => (( alt = RCAP.xpath_text( area_xml_element, ALTITUDE_XPATH )) ? alt.to_f : nil ),
92
+ :ceiling => (( ceil = RCAP.xpath_text( area_xml_element, CEILING_XPATH )) ? ceil.to_f : nil ),
93
+ :circles => RCAP.xpath_match( area_xml_element, RCAP::Circle::XPATH ).map{ |circle_element| RCAP::Circle.from_xml_element( circle_element )},
94
+ :geocodes => RCAP.xpath_match( area_xml_element, RCAP::Geocode::XPATH ).map{ |geocode_element| RCAP::Geocode.from_xml_element( geocode_element )},
95
+ :polygons => RCAP.xpath_match( area_xml_element, RCAP::Polygon::XPATH ).map{ |polygon_element| RCAP::Polygon.from_xml_element( polygon_element )})
80
96
  end
97
+
98
+ AREA_DESC_YAML = 'Area Description' # :nodoc:
99
+ ALTITUDE_YAML = 'Altitude' # :nodoc:
100
+ CEILING_YAML = 'Ceiling' # :nodoc:
101
+ CIRCLES_YAML = 'Circles' # :nodoc:
102
+ GEOCODES_YAML = 'Geocodes' # :nodoc:
103
+ POLYGONS_YAML = 'Polygons' # :nodoc:
104
+
105
+ def to_yaml( options = {} ) # :nodoc:
106
+ circles_yaml = self.circles.map{ |circle| [[ circle.point.lattitude, circle.point.longitude ], circle.radius ]}
107
+ def circles_yaml.to_yaml_style; :inline; end
108
+
109
+ RCAP.attribute_values_to_yaml_hash(
110
+ [ AREA_DESC_YAML, self.area_desc ],
111
+ [ ALTITUDE_YAML, self.altitude ],
112
+ [ CEILING_YAML, self.ceiling ],
113
+ [ CIRCLES_YAML, circles_yaml ],
114
+ [ GEOCODES_YAML, self.geocodes.inject({}){|h,geocode| h.merge( geocode.name => geocode.value )}],
115
+ [ POLYGONS_YAML, self.polygons ]
116
+ ).to_yaml( options )
117
+ end
118
+
119
+ def from_yaml_data( area_yaml_data ) # :nodoc:
120
+ Area.new(
121
+ :area_desc => area_yaml_data[ AREA_DESC_YAML ],
122
+ :altitude => area_yaml_data[ ALTITUDE_YAML ],
123
+ :ceiling => area_yaml_data[ CEILING_YAML ],
124
+ :circles => Array( area_yaml_data[ CIRCLES_YAML ]).map{ |circle_yaml_data| RCAP::Circle.from_yaml_data( circle_yaml_data )},
125
+ :geocodes => Array( area_yaml_data[ GEOCODES_YAML ]).map{ |name, value| RCAP::Geocode.new( :name => name, :value => value )},
126
+ :polygons => Array( area_yaml_data[ POLYGONS_YAML ]).map{ |polyon_yaml_data| RCAP::Polygon.from_yaml_data( polyon_yaml_data )}
127
+ )
128
+ end
81
129
  end
82
130
  end
data/lib/rcap/circle.rb CHANGED
@@ -23,6 +23,8 @@ module RCAP
23
23
  @radius = attributes[ :radius ]
24
24
  end
25
25
 
26
+ # Returns a string representation of the circle of the form
27
+ # point radius
26
28
  def to_s # :nodoc:
27
29
  "#{ self.point.to_s } #{ self.radius }"
28
30
  end
@@ -58,5 +60,11 @@ module RCAP
58
60
  def ==( other )
59
61
  self.point == other.point && self.radius == other.radius
60
62
  end
63
+
64
+ def self.from_yaml_data( circle_yaml_data ) # :nodoc:
65
+ point_yaml_data, radius = circle_yaml_data
66
+ self.new( :point => RCAP::Point.new( :lattitude => point_yaml_data[ 0 ], longitude => point_yaml_data[ 1 ]),
67
+ :radius => radius )
68
+ end
61
69
  end
62
70
  end
data/lib/rcap/geocode.rb CHANGED
@@ -13,8 +13,8 @@ module RCAP
13
13
  end
14
14
 
15
15
  def self.from_xml_element( geocode_xml_element ) # :nodoc:
16
- self.new( :name => RCAP.xpath_text( geocode_xml_element, NAME_XPATH ),
17
- :value => RCAP.xpath_text( geocode_xml_element, VALUE_XPATH ))
16
+ self.new( :name => RCAP.xpath_text( geocode_xml_element, NAME_XPATH ),
17
+ :value => RCAP.xpath_text( geocode_xml_element, VALUE_XPATH ))
18
18
  end
19
19
  end
20
20
  end
data/lib/rcap/info.rb CHANGED
@@ -11,57 +11,62 @@ module RCAP
11
11
  class Info
12
12
  include Validation
13
13
 
14
- CATEGORY_GEO = "Geo"
15
- CATEGORY_MET = "Met"
16
- CATEGORY_SAFETY = "Safety"
17
- CATEGORY_SECURITY = "Security"
18
- CATEGORY_RESCUE = "Rescue"
19
- CATEGORY_FIRE = "Fire"
20
- CATEGORY_HEALTH = "Health"
21
- CATEGORY_ENV = "Env"
22
- CATEGORY_TRANSPORT = "Transport"
23
- CATEGORY_INFRA = "Infra"
24
- CATEGORY_CBRNE = "CBRNE"
25
- CATEGORY_OTHER = "Other"
26
- ALL_CATEGORIES = [ CATEGORY_GEO, CATEGORY_MET, CATEGORY_SAFETY,
14
+ CATEGORY_GEO = "Geo" # :nodoc:
15
+ CATEGORY_MET = "Met" # :nodoc:
16
+ CATEGORY_SAFETY = "Safety" # :nodoc:
17
+ CATEGORY_SECURITY = "Security" # :nodoc:
18
+ CATEGORY_RESCUE = "Rescue" # :nodoc:
19
+ CATEGORY_FIRE = "Fire" # :nodoc:
20
+ CATEGORY_HEALTH = "Health" # :nodoc:
21
+ CATEGORY_ENV = "Env" # :nodoc:
22
+ CATEGORY_TRANSPORT = "Transport" # :nodoc:
23
+ CATEGORY_INFRA = "Infra" # :nodoc:
24
+ CATEGORY_CBRNE = "CBRNE" # :nodoc:
25
+ CATEGORY_OTHER = "Other" # :nodoc:
26
+ # Valid values for categories
27
+ VALID_CATEGORIES = [ CATEGORY_GEO, CATEGORY_MET, CATEGORY_SAFETY,
27
28
  CATEGORY_SECURITY, CATEGORY_RESCUE, CATEGORY_FIRE, CATEGORY_HEALTH,
28
29
  CATEGORY_ENV, CATEGORY_TRANSPORT, CATEGORY_INFRA, CATEGORY_CBRNE,
29
- CATEGORY_OTHER ] # :nodoc:
30
-
31
- RESPONSE_TYPE_SHELTER = "Shelter"
32
- RESPONSE_TYPE_EVACUATE = "Evacuate"
33
- RESPONSE_TYPE_PREPARE = "Prepare"
34
- RESPONSE_TYPE_EXECUTE = "Execute"
35
- RESPONSE_TYPE_MONITOR = "Monitor"
36
- RESPONSE_TYPE_ASSESS = "Assess"
37
- RESPONSE_TYPE_NONE = "None"
38
- ALL_RESPONSE_TYPES = [ RESPONSE_TYPE_SHELTER, RESPONSE_TYPE_EVACUATE,
30
+ CATEGORY_OTHER ]
31
+
32
+ RESPONSE_TYPE_SHELTER = "Shelter" # :nodoc:
33
+ RESPONSE_TYPE_EVACUATE = "Evacuate" # :nodoc:
34
+ RESPONSE_TYPE_PREPARE = "Prepare" # :nodoc:
35
+ RESPONSE_TYPE_EXECUTE = "Execute" # :nodoc:
36
+ RESPONSE_TYPE_MONITOR = "Monitor" # :nodoc:
37
+ RESPONSE_TYPE_ASSESS = "Assess" # :nodoc:
38
+ RESPONSE_TYPE_NONE = "None" # :nodoc:
39
+ # Valid values for response_type
40
+ VALID_RESPONSE_TYPES = [ RESPONSE_TYPE_SHELTER, RESPONSE_TYPE_EVACUATE,
39
41
  RESPONSE_TYPE_PREPARE, RESPONSE_TYPE_EXECUTE, RESPONSE_TYPE_MONITOR,
40
- RESPONSE_TYPE_ASSESS, RESPONSE_TYPE_NONE ] # :nodoc:
41
-
42
- URGENCY_IMMEDIATE = "Immediate"
43
- URGENCY_EXPECTED = "Expected"
44
- URGENCY_FUTURE = "Future"
45
- URGENCY_PAST = "Past"
46
- URGENCY_UNKNOWN = "Unknown"
47
- ALL_URGENCIES = [ URGENCY_IMMEDIATE, URGENCY_EXPECTED, URGENCY_FUTURE,
48
- URGENCY_PAST, URGENCY_UNKNOWN ] # :nodoc:
49
-
50
- SEVERITY_EXTREME = "Extreme"
51
- SEVERITY_SEVERE = "Severe"
52
- SEVERITY_MODERATE = "Moderate"
53
- SEVERITY_MINOR = "Minor"
54
- SEVERITY_UNKNOWN = "Unknown"
55
- ALL_SEVERITIES = [ SEVERITY_EXTREME, SEVERITY_SEVERE, SEVERITY_MODERATE,
56
- SEVERITY_MINOR, SEVERITY_UNKNOWN ] # :nodoc:
57
-
58
- CERTAINTY_OBSERVED = "Observed"
59
- CERTAINTY_LIKELY = "Likely"
60
- CERTAINTY_POSSIBLE = "Possible"
61
- CERTAINTY_UNLIKELY = "Unlikely"
62
- CERTAINTY_UNKNOWN = "Unknown"
63
- ALL_CERTAINTIES = [ CERTAINTY_OBSERVED, CERTAINTY_LIKELY,
64
- CERTAINTY_POSSIBLE, CERTAINTY_UNLIKELY, CERTAINTY_UNKNOWN ] # :nodoc:
42
+ RESPONSE_TYPE_ASSESS, RESPONSE_TYPE_NONE ]
43
+
44
+ URGENCY_IMMEDIATE = "Immediate" # :nodoc:
45
+ URGENCY_EXPECTED = "Expected" # :nodoc:
46
+ URGENCY_FUTURE = "Future" # :nodoc:
47
+ URGENCY_PAST = "Past" # :nodoc:
48
+ URGENCY_UNKNOWN = "Unknown" # :nodoc:
49
+ # Valid values for urgency
50
+ VALID_URGENCIES = [ URGENCY_IMMEDIATE, URGENCY_EXPECTED, URGENCY_FUTURE,
51
+ URGENCY_PAST, URGENCY_UNKNOWN ]
52
+
53
+ SEVERITY_EXTREME = "Extreme" # :nodoc:
54
+ SEVERITY_SEVERE = "Severe" # :nodoc:
55
+ SEVERITY_MODERATE = "Moderate" # :nodoc:
56
+ SEVERITY_MINOR = "Minor" # :nodoc:
57
+ SEVERITY_UNKNOWN = "Unknown" # :nodoc:
58
+ # Valid values for severity
59
+ VALID_SEVERITIES = [ SEVERITY_EXTREME, SEVERITY_SEVERE, SEVERITY_MODERATE,
60
+ SEVERITY_MINOR, SEVERITY_UNKNOWN ]
61
+
62
+ CERTAINTY_OBSERVED = "Observed" # :nodoc:
63
+ CERTAINTY_LIKELY = "Likely" # :nodoc:
64
+ CERTAINTY_POSSIBLE = "Possible" # :nodoc:
65
+ CERTAINTY_UNLIKELY = "Unlikely" # :nodoc:
66
+ CERTAINTY_UNKNOWN = "Unknown" # :nodoc:
67
+ # Valid valies for certainty
68
+ VALID_CERTAINTIES = [ CERTAINTY_OBSERVED, CERTAINTY_LIKELY,
69
+ CERTAINTY_POSSIBLE, CERTAINTY_UNLIKELY, CERTAINTY_UNKNOWN ]
65
70
 
66
71
  XML_ELEMENT_NAME = 'info' # :nodoc:
67
72
  LANGUAGE_ELEMENT_NAME = 'language' # :nodoc:
@@ -107,16 +112,19 @@ module RCAP
107
112
 
108
113
  validates_presence_of( :event, :urgency, :severity, :certainty )
109
114
  validates_length_of( :categories, :minimum => 1 )
110
- validates_inclusion_of( :certainty, :allow_nil => true, :in => ALL_CERTAINTIES, :message => "can only be assigned the following values: #{ ALL_CERTAINTIES.join(', ') }")
111
- validates_inclusion_of( :severity, :allow_nil => true, :in => ALL_SEVERITIES, :message => "can only be assigned the following values: #{ ALL_SEVERITIES.join(', ') }" )
112
- validates_inclusion_of( :urgency, :allow_nil => true, :in => ALL_URGENCIES, :message => "can only be assigned the following values: #{ ALL_URGENCIES.join(', ') }" )
113
- validates_inclusion_of_members_of( :response_types, :in => ALL_RESPONSE_TYPES, :allow_blank => true )
114
- validates_inclusion_of_members_of( :categories, :in => ALL_CATEGORIES, :allow_blank => true )
115
+ validates_inclusion_of( :certainty, :allow_nil => true, :in => VALID_CERTAINTIES, :message => "can only be assigned the following values: #{ VALID_CERTAINTIES.join(', ') }")
116
+ validates_inclusion_of( :severity, :allow_nil => true, :in => VALID_SEVERITIES, :message => "can only be assigned the following values: #{ VALID_SEVERITIES.join(', ') }" )
117
+ validates_inclusion_of( :urgency, :allow_nil => true, :in => VALID_URGENCIES, :message => "can only be assigned the following values: #{ VALID_URGENCIES.join(', ') }" )
118
+ validates_inclusion_of_members_of( :response_types, :in => VALID_RESPONSE_TYPES, :allow_blank => true )
119
+ validates_inclusion_of_members_of( :categories, :in => VALID_CATEGORIES, :allow_blank => true )
115
120
  validates_collection_of( :resources, :areas )
116
121
 
117
122
  attr_accessor( :event )
123
+ # Value can only be one of VALID_URGENCIES
118
124
  attr_accessor( :urgency )
125
+ # Value can only be one of VALID_SEVERITIES
119
126
  attr_accessor( :severity )
127
+ # Value can only be one of VALID_CERTAINTIES
120
128
  attr_accessor( :certainty )
121
129
  attr_accessor( :language )
122
130
  attr_accessor( :audience )
@@ -124,7 +132,7 @@ module RCAP
124
132
  attr_accessor( :effective )
125
133
  # Expected start of event
126
134
  attr_accessor( :onset )
127
- # Effected expiry time of information
135
+ # Effective expiry time of information
128
136
  attr_accessor( :expires )
129
137
  attr_accessor( :sender_name )
130
138
  attr_accessor( :headline )
@@ -133,7 +141,7 @@ module RCAP
133
141
  attr_accessor( :web )
134
142
  attr_accessor( :contact )
135
143
 
136
- # Collection of textual categories
144
+ # Collection of textual categories; elements can be one of VALID_CATEGORIES
137
145
  attr_reader( :categories )
138
146
  # Collection of textual response types
139
147
  attr_reader( :response_types )
@@ -186,15 +194,15 @@ module RCAP
186
194
  @event_codes.each do |event_code|
187
195
  xml_element.add_element( event_code.to_xml_element )
188
196
  end
189
- xml_element.add_element( EFFECTIVE_ELEMENT_NAME ).add_text( self.effective.to_s ) if self.effective
190
- xml_element.add_element( ONSET_ELEMENT_NAME ).add_text( self.onset.to_s ) if self.onset
191
- xml_element.add_element( EXPIRES_ELEMENT_NAME ).add_text( self.expires.to_s ) if self.expires
192
- xml_element.add_element( SENDER_NAME_ELEMENT_NAME ).add_text( self.sender_name ) if self.sender_name
193
- xml_element.add_element( HEADLINE_ELEMENT_NAME ).add_text( self.headline ) if self.headline
194
- xml_element.add_element( DESCRIPTION_ELEMENT_NAME ).add_text( self.description ) if self.description
195
- xml_element.add_element( INSTRUCTION_ELEMENT_NAME ).add_text( self.instruction ) if self.instruction
196
- xml_element.add_element( WEB_ELEMENT_NAME ).add_text( self.web ) if self.web
197
- xml_element.add_element( CONTACT_ELEMENT_NAME ).add_text( self.contact ) if self.contact
197
+ xml_element.add_element( EFFECTIVE_ELEMENT_NAME ).add_text( self.effective.to_s_for_cap ) if self.effective
198
+ xml_element.add_element( ONSET_ELEMENT_NAME ).add_text( self.onset.to_s_for_cap ) if self.onset
199
+ xml_element.add_element( EXPIRES_ELEMENT_NAME ).add_text( self.expires.to_s_for_cap ) if self.expires
200
+ xml_element.add_element( SENDER_NAME_ELEMENT_NAME ).add_text( self.sender_name ) if self.sender_name
201
+ xml_element.add_element( HEADLINE_ELEMENT_NAME ).add_text( self.headline ) if self.headline
202
+ xml_element.add_element( DESCRIPTION_ELEMENT_NAME ).add_text( self.description ) if self.description
203
+ xml_element.add_element( INSTRUCTION_ELEMENT_NAME ).add_text( self.instruction ) if self.instruction
204
+ xml_element.add_element( WEB_ELEMENT_NAME ).add_text( self.web ) if self.web
205
+ xml_element.add_element( CONTACT_ELEMENT_NAME ).add_text( self.contact ) if self.contact
198
206
  @parameters.each do |parameter|
199
207
  xml_element.add_element( parameter.to_xml_element )
200
208
  end
@@ -211,6 +219,43 @@ module RCAP
211
219
  self.to_xml_element.to_s
212
220
  end
213
221
 
222
+ def inspect # :nodoc:
223
+ info_inspect = <<EOF
224
+ Language: #{ self.language }
225
+ Categories: #{ self.categories.to_s_for_cap }
226
+ Event: #{ self.event }
227
+ Response Types: #{ self.response_types.to_s_for_cap }
228
+ Urgency: #{ self.urgency }
229
+ Severity: #{ self.severity }
230
+ Certainty: #{ self.certainty }
231
+ Audience: #{ self.audience }
232
+ Event Codes: #{ self.event_codes.inspect }
233
+ Effective: #{ self.effective }
234
+ Onset: #{ self.onset }
235
+ Expires: #{ self.expires }
236
+ Sender Name: #{ self.sender_name }
237
+ Headline: #{ self.headline }
238
+ Description:
239
+ #{ self.description.to_s.lines.map{ |line| " " + line }.join }
240
+ Instruction: #{ self.instruction }
241
+ Web: #{ self.web }
242
+ Contact: #{ self.contact }
243
+ Parameters:
244
+ #{ self.parameters.map{ |parameter| parameter.inspect }.join( "\n" )}
245
+ Resources:
246
+ #{ self.resources.map{ |resource| " " + resource.inspect }.join( "\n" )}
247
+ Area:
248
+ #{ self.areas.map{ |area| " #{ area }" }.join( "\n" )}
249
+ EOF
250
+ RCAP.format_lines_for_inspect( 'INFO', info_inspect )
251
+ end
252
+
253
+ # Returns a string representation of the event of the form
254
+ # event(urgency/severity/certainty)
255
+ def to_s
256
+ "#{ self.event }(#{ self.urgency }/#{ self.severity }/#{ self.certainty })"
257
+ end
258
+
214
259
  def self.from_xml_element( info_xml_element ) # :nodoc:
215
260
  self.new(
216
261
  :language => RCAP.xpath_text( info_xml_element, LANGUAGE_XPATH ) || DEFAULT_LANGUAGE,
@@ -230,9 +275,92 @@ module RCAP
230
275
  :instruction => RCAP.xpath_text( info_xml_element, INSTRUCTION_XPATH ),
231
276
  :web => RCAP.xpath_text( info_xml_element, WEB_XPATH ),
232
277
  :contact => RCAP.xpath_text( info_xml_element, CONTACT_XPATH ),
233
- :event_codes => RCAP.xpath_match( info_xml_element, RCAP::EventCode::XPATH ).map{ |element| RCAP::EventCode.from_xml_element( element )},
234
- :parameters => RCAP.xpath_match( info_xml_element, RCAP::Parameter::XPATH ).map{ |element| RCAP::Parameter.from_xml_element( element )},
235
- :areas => RCAP.xpath_match( info_xml_element, RCAP::Area::XPATH ).map{ |element| RCAP::Area.from_xml_element( element )}
278
+ :event_codes => RCAP.xpath_match( info_xml_element, RCAP::EventCode::XPATH ).map{ |element| RCAP::EventCode.from_xml_element( element )},
279
+ :parameters => RCAP.xpath_match( info_xml_element, RCAP::Parameter::XPATH ).map{ |element| RCAP::Parameter.from_xml_element( element )},
280
+ :resources => RCAP.xpath_match( info_xml_element, RCAP::Resource::XPATH ).map{ |element| RCAP::Resource.from_xml_element( element )},
281
+ :areas => RCAP.xpath_match( info_xml_element, RCAP::Area::XPATH ).map{ |element| RCAP::Area.from_xml_element( element )}
282
+ )
283
+ end
284
+
285
+ LANGUAGE_YAML = 'Language'
286
+ CATEGORIES_YAML = 'Categories'
287
+ EVENT_YAML = 'Event'
288
+ RESPONSE_TYPES_YAML = 'Response Types'
289
+ URGENCY_YAML = 'Urgency'
290
+ SEVERITY_YAML = 'Severity'
291
+ CERTAINTY_YAML = 'Certainty'
292
+ AUDIENCE_YAML = 'Audience'
293
+ EFFECTIVE_YAML = 'Effective'
294
+ ONSET_YAML = 'Onset'
295
+ EXPIRES_YAML = 'Expires'
296
+ SENDER_NAME_YAML = 'Sender Name'
297
+ HEADLINE_YAML = 'Headline'
298
+ DESCRIPTION_YAML = 'Description'
299
+ INSTRUCTION_YAML = 'Instruction'
300
+ WEB_YAML = 'Web'
301
+ CONTACT_YAML = 'Contact'
302
+ EVENT_CODES_YAML = 'Event Codes'
303
+ PARAMETERS_YAML = 'Parameters'
304
+ RESOURCES_YAML = 'Resources'
305
+ AREAS_YAML = 'Areas'
306
+
307
+ def to_yaml( options = {} ) # :nodoc:
308
+ response_types_yaml = self.response_types
309
+ def response_types_yaml.to_yaml_style; :inline; end
310
+
311
+ categories_yaml = self.categories
312
+ def categories_yaml.to_yaml_style; :inline; end
313
+
314
+ parameter_to_hash = lambda{ |hash, parameter| hash.merge( parameter.name => parameter.value )}
315
+
316
+ RCAP.attribute_values_to_yaml_hash(
317
+ [ LANGUAGE_YAML, self.language ],
318
+ [ CATEGORIES_YAML, categories_yaml ],
319
+ [ EVENT_YAML, self.event ],
320
+ [ RESPONSE_TYPES_YAML, response_types_yaml ],
321
+ [ URGENCY_YAML, self.urgency ],
322
+ [ SEVERITY_YAML, self.severity ],
323
+ [ CERTAINTY_YAML, self.certainty ],
324
+ [ AUDIENCE_YAML, self.audience ],
325
+ [ EFFECTIVE_YAML, self.effective ],
326
+ [ ONSET_YAML, self.onset ],
327
+ [ EXPIRES_YAML, self.expires ],
328
+ [ SENDER_NAME_YAML, self.sender_name ],
329
+ [ HEADLINE_YAML, self.headline ],
330
+ [ DESCRIPTION_YAML, self.description ],
331
+ [ INSTRUCTION_YAML, self.instruction ],
332
+ [ WEB_YAML, self.web ],
333
+ [ CONTACT_YAML, self.contact ],
334
+ [ EVENT_CODES_YAML, self.event_codes.inject({}, &parameter_to_hash )],
335
+ [ PARAMETERS_YAML, self.parameters.inject({}, &parameter_to_hash )],
336
+ [ RESOURCES_YAML, self.resources ],
337
+ [ AREAS_YAML, self.areas ]
338
+ ).to_yaml( options )
339
+ end
340
+
341
+ def self.from_yaml_data( info_yaml_data ) # :nodoc:
342
+ self.new(
343
+ :language => info_yaml_data [ LANGUAGE_YAML ],
344
+ :categories => info_yaml_data [ CATEGORIES_YAML ],
345
+ :event => info_yaml_data [ EVENT_YAML ],
346
+ :response_types => info_yaml_data [ RESPONSE_TYPES_YAML ],
347
+ :urgency => info_yaml_data [ URGENCY_YAML ],
348
+ :severity => info_yaml_data [ SEVERITY_YAML ],
349
+ :certainty => info_yaml_data [ CERTAINTY_YAML ],
350
+ :audience => info_yaml_data [ AUDIENCE_YAML ],
351
+ :effective => ( effective = info_yaml_data[ EFFECTIVE_YAML ]).blank? ? nil : DateTime.parse( effective.to_s ),
352
+ :onset => ( onset = info_yaml_data[ ONSET_YAML ]).blank? ? nil : DateTime.parse( onset.to_s ),
353
+ :expires => ( expires = info_yaml_data[ EXPIRES_YAML ]).blank? ? nil : DateTime.parse( expires.to_s ),
354
+ :sender_name => info_yaml_data [ SENDER_NAME_YAML ],
355
+ :headline => info_yaml_data [ HEADLINE_YAML ],
356
+ :description => info_yaml_data [ DESCRIPTION_YAML ],
357
+ :instruction => info_yaml_data [ INSTRUCTION_YAML ],
358
+ :web => info_yaml_data [ WEB_YAML ],
359
+ :contact => info_yaml_data [ CONTACT_YAML ],
360
+ :event_codes => Array( info_yaml_data [ EVENT_CODES_YAML ]).map{ |name,value| RCAP::EventCode.new( :name => name, :value => value )},
361
+ :parameters => Array( info_yaml_data [ PARAMETERS_YAML ]).map{ |parameter_yaml_data| RCAP::Parameter.new( :name => name, :value => value )},
362
+ :resources => Array( info_yaml_data [ RESOURCES_YAML ]).map{ |resource_yaml_data| RCAP::Resource.from_yaml_data( resource_yaml_data )},
363
+ :areas => Array( info_yaml_data [ AREAS_YAML ]).map{ |area_yaml_data| RCAP::Area.from_yaml_data( area_yaml_data )}
236
364
  )
237
365
  end
238
366
  end
@@ -34,10 +34,12 @@ module RCAP
34
34
  end
35
35
 
36
36
  def inspect # :nodoc:
37
- "#{ self.name }: #{ self.value }"
37
+ "#{ self.name }: #{ self.value }"
38
38
  end
39
39
 
40
- def to_s # :nodoc:
40
+ # Returns a string representation of the parameter of the form
41
+ # name: value
42
+ def to_s
41
43
  self.inspect
42
44
  end
43
45
 
data/lib/rcap/point.rb CHANGED
@@ -22,7 +22,9 @@ module RCAP
22
22
  @longitude = attributes[ :longitude ]
23
23
  end
24
24
 
25
- def to_s # :nodoc:
25
+ # Returns a string representation of the point of the form
26
+ # lattitude,longitude
27
+ def to_s
26
28
  "#{ self.lattitude },#{ self.longitude }"
27
29
  end
28
30
 
@@ -32,8 +34,7 @@ module RCAP
32
34
 
33
35
  # Two points are equivalent if they have the same lattitude and longitude
34
36
  def ==( other )
35
- self.lattitude == other.lattitude &&
36
- self.longitude == other.longitude
37
+ [ self.lattitude, self.longitude ] == [ other.lattitude, other.longitude ]
37
38
  end
38
39
  end
39
40
  end
data/lib/rcap/polygon.rb CHANGED
@@ -18,7 +18,10 @@ module RCAP
18
18
  @points = Array( attributes[ :points ])
19
19
  end
20
20
 
21
- def to_s # :nodoc:
21
+ # Returns a string representation of the polygon of the form
22
+ # points[0] points[1] points[2] ... points[n-1] points[0]
23
+ # where each point is formatted with RCAP::Point#to_s
24
+ def to_s
22
25
  (@points.map{ |point| point.to_s } + [ @points.first ]).join( ' ' )
23
26
  end
24
27
 
@@ -46,5 +49,19 @@ module RCAP
46
49
  points = coordinates.map{ |lattitude, longitude| RCAP::Point.new( :lattitude => lattitude, :longitude => longitude )}[0..-2]
47
50
  polygon = self.new( :points => points )
48
51
  end
52
+
53
+
54
+ def to_yaml( options = {} ) # :nodoc:
55
+ yaml_points = self.points.map{ |point| [ point.lattitude, point.longitude ]}
56
+ def yaml_points.to_yaml_style; :inline; end
57
+
58
+ yaml_points.to_yaml( options )
59
+ end
60
+
61
+ def self.from_yaml_data( polyon_yaml_data ) # :nodoc:
62
+ self.new(
63
+ :points => Array( polygon_yaml_data ).map{ |lattitude, longitude| Point.new( :lattitude => lattitude, :longitude => longitude )}
64
+ )
65
+ end
49
66
  end
50
67
  end
data/lib/rcap/resource.rb CHANGED
@@ -69,13 +69,48 @@ module RCAP
69
69
  [ self.resource_desc, self.uri, self.mime_type, self.size ? format( "%.1fKB", self.size_in_kb ) : nil ].compact.join(' - ')
70
70
  end
71
71
 
72
+ # Returns a string representation of the resource of the form
73
+ # resource_desc
74
+ def to_s
75
+ self.resource_desc
76
+ end
77
+
72
78
  def self.from_xml_element( resource_xml_element ) # :nodoc:
73
79
  resource = self.new( :resource_desc => RCAP.xpath_text( resource_xml_element, RESOURCE_DESC_XPATH ),
74
- :uri => RCAP.xpath_text( resource_xml_element, URI_XPATH ),
75
- :mime_type => RCAP.xpath_text( resource_xml_element, MIME_TYPE_XPATH ),
76
- :deref_uri => RCAP.xpath_text( resource_xml_element, DEREF_URI_XPATH ),
77
- :size => RCAP.xpath_text( resource_xml_element, SIZE_XPATH ),
78
- :digest => RCAP.xpath_text( resource_xml_element, DIGEST_XPATH ))
80
+ :uri => RCAP.xpath_text( resource_xml_element, URI_XPATH ),
81
+ :mime_type => RCAP.xpath_text( resource_xml_element, MIME_TYPE_XPATH ),
82
+ :deref_uri => RCAP.xpath_text( resource_xml_element, DEREF_URI_XPATH ),
83
+ :size => RCAP.xpath_text( resource_xml_element, SIZE_XPATH ),
84
+ :digest => RCAP.xpath_text( resource_xml_element, DIGEST_XPATH ))
85
+ end
86
+
87
+ RESOURCE_DESC_YAML = "Resource Description" # :nodoc:
88
+ URI_YAML = "URI" # :nodoc:
89
+ MIME_TYPE_YAML = "Mime Type" # :nodoc:
90
+ DEREF_URI_YAML = "Derefrenced URI Data" # :nodoc:
91
+ SIZE_YAML = "Size" # :nodoc:
92
+ DIGEST_YAML = "Digest" # :nodoc:
93
+
94
+ def to_yaml( options ) # :nodoc:
95
+ RCAP.attribute_values_to_yaml_hash(
96
+ [ RESOURCE_DESC_YAML, self.resource_desc ],
97
+ [ URI_YAML, self.uri ],
98
+ [ MIME_TYPE_YAML, self.mime_type ],
99
+ [ DEREF_URI_YAML, self.deref_uri ],
100
+ [ SIZE_YAML, self.size ],
101
+ [ DIGEST_YAML, self.digest ]
102
+ ).to_yaml( options )
103
+ end
104
+
105
+ def self.from_yaml_data( resource_yaml_data ) # :nodoc:
106
+ self.new(
107
+ :resource_desc => reource_yaml_data[ RESOURCE_DESC_YAML ],
108
+ :uri => reource_yaml_data[ URI_YAML ],
109
+ :mime_type => reource_yaml_data[ MIME_TYPE_YAML ],
110
+ :deref_uri => reource_yaml_data[ DEREF_URI_YAML ],
111
+ :size => reource_yaml_data[ SIZE_YAML ],
112
+ :digest => reource_yaml_data[ DIGEST_YAML ]
113
+ )
79
114
  end
80
115
  end
81
116
  end
@@ -29,8 +29,11 @@ class DateTime # :nodoc:
29
29
  end
30
30
 
31
31
  class Time # :nodoc:
32
- def to_s_for_cap
32
+ RCAP_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
33
+ RCAP_ZONE_FORMAT = "%+02i:00"
33
34
 
35
+ def to_s_for_cap
36
+ self.strftime( RCAP_TIME_FORMAT ) + format( RCAP_ZONE_FORMAT , self.utc_hours_offset )
34
37
  end
35
38
  end
36
39
 
@@ -47,4 +50,17 @@ module RCAP # :nodoc:
47
50
  def self.xpath_match( xml_element, xpath )
48
51
  REXML::XPath.match( xml_element, xpath, { 'cap' => RCAP::XMLNS })
49
52
  end
53
+
54
+ def self.format_lines_for_inspect( header, inspect_string )
55
+ max_line_length = inspect_string.lines.inject( 0 ){ |max_length, line| line.chomp.length > max_length ? line.chomp.length : max_length }
56
+ "\n." + '-' * max_line_length + ".\n"+
57
+ '|' + header.ljust( max_line_length ) + "|\n"+
58
+ '|' + '-' * max_line_length + "|\n"+
59
+ inspect_string.lines.map{ |line| '|' + line.chomp.ljust( max_line_length ) +'|'}.join( "\n" ) + "\n" +
60
+ "'" + '-' * max_line_length + "'\n"
61
+ end
62
+
63
+ def self.attribute_values_to_yaml_hash( *attribute_values )
64
+ Hash[ *attribute_values.reject{ |key, value| value.blank? }.flatten( 1 )]
65
+ end
50
66
  end
@@ -1,8 +1,8 @@
1
1
  module Validation # :nodoc:
2
2
  module ClassMethods # :nodoc:
3
3
 
4
- NUMBER_RE = /^-{0,1}\d*\.{0,1}\d+$/
5
- INTEGER_RE = /\-{0,1}A[+-]?\d+\Z/
4
+ CAP_NUMBER_RE = /^-{0,1}\d*\.{0,1}\d+$/
5
+ CAP_INTEGER_RE = /\-{0,1}A[+-]?\d+\Z/
6
6
 
7
7
  def validates_inclusion_of( *attributes )
8
8
  options = {
@@ -88,7 +88,7 @@ module Validation # :nodoc:
88
88
  :message => 'is not a number',
89
89
  }.merge!(attributes.extract_options!)
90
90
 
91
- re = options[:only_integer] ? INTEGER_RE : NUMBER_RE
91
+ re = options[:only_integer] ? CAP_INTEGER_RE : CAP_NUMBER_RE
92
92
 
93
93
  validates_each( *attributes ) do |object, attribute, value|
94
94
  next if (value.nil? && options[ :allow_nil ]) || (value.blank? && options[ :allow_blank ])
data/lib/rcap.rb CHANGED
@@ -17,5 +17,5 @@ require 'rcap/area'
17
17
 
18
18
  module RCAP
19
19
  XMLNS = "urn:oasis:names:tc:emergency:cap:1.1"
20
- VERSION = "0.1"
20
+ VERSION = "0.2"
21
21
  end
data/spec/alert_spec.rb CHANGED
@@ -20,6 +20,26 @@ describe( RCAP::Alert ) do
20
20
  it( 'should not have any incidents' ){ @alert.incidents.should( be_empty )}
21
21
  it( 'should not have any infos' ){ @alert.infos.should( be_empty )}
22
22
 
23
+ shared_examples_for( "a successfully parsed alert" ) do
24
+ it( 'should parse identifier correctly' ){ @alert.identifier.should == @original_alert.identifier }
25
+ it( 'should parse sender correctly' ){ @alert.sender.should == @original_alert.sender }
26
+ it( 'should parse sent correctly' ){ @alert.sent.should( be_close( @original_alert.sent, Rational( 1, 86400 )))}
27
+ it( 'should parse status correctly' ){ @alert.status.should == @original_alert.status }
28
+ it( 'should parse msg_type correctly' ){ @alert.msg_type.should == @original_alert.msg_type }
29
+ it( 'should parse source correctly' ){ @alert.source.should == @original_alert.source }
30
+ it( 'should parse scope correctly' ){ @alert.scope.should == @original_alert.scope }
31
+ it( 'should parse restriction correctly' ){ @alert.restriction.should == @original_alert.restriction }
32
+ it( 'should parse addresses correctly' ){ @alert.addresses.should == @original_alert.addresses }
33
+ it( 'should parse code correctly' ){ @alert.code.should == @original_alert.code }
34
+ it( 'should parse note correctly' ){ @alert.note.should == @original_alert.note }
35
+ it( 'should parse references correctly' ){ @alert.references.should == @original_alert.references }
36
+ it( 'should parse incidents correctly' ){ @alert.incidents.should == @original_alert.incidents }
37
+ it( 'should parse infos correctly' ) do
38
+ @alert.infos.size.should == @original_alert.infos.size
39
+ @alert.infos.each{ |info| info.class.should == RCAP::Info }
40
+ end
41
+ end
42
+
23
43
  context( 'from XML' ) do
24
44
  before( :each ) do
25
45
  @original_alert = RCAP::Alert.new( :sender => 'Sender',
@@ -39,24 +59,28 @@ describe( RCAP::Alert ) do
39
59
  @alert = RCAP::Alert.from_xml_element( @alert_element )
40
60
  end
41
61
 
42
- it( 'should parse identifier correctly' ){ @alert.identifier.should == @original_alert.identifier }
43
- it( 'should parse sender correctly' ){ @alert.sender.should == @original_alert.sender }
44
- it( 'should parse sent correctly' ){ @alert.sent.should( be_close( @original_alert.sent, Rational( 1, 86400 )))}
45
- it( 'should parse status correctly' ){ @alert.status.should == @original_alert.status }
46
- it( 'should parse msg_type correctly' ){ @alert.msg_type.should == @original_alert.msg_type }
47
- it( 'should parse source correctly' ){ @alert.source.should == @original_alert.source }
48
- it( 'should parse scope correctly' ){ @alert.scope.should == @original_alert.scope }
49
- it( 'should parse restriction correctly' ){ @alert.restriction.should == @original_alert.restriction }
50
- it( 'should parse addresses correctly' ){ @alert.addresses.should == @original_alert.addresses }
51
- it( 'should parse code correctly' ){ @alert.code.should == @original_alert.code }
52
- it( 'should parse note correctly' ){ @alert.note.should == @original_alert.note }
53
- it( 'should parse references correctly' ){ @alert.references.should == @original_alert.references }
54
- it( 'should parse incidents correctly' ){ @alert.incidents.should == @original_alert.incidents }
55
- it( 'should parse infos correctly' ) do
56
- @alert.infos.size.should == @original_alert.infos.size
57
- @alert.infos.each{ |info| info.class.should == RCAP::Info }
58
- end
62
+ it_should_behave_like( "a successfully parsed alert" )
63
+
59
64
  end
65
+
66
+ context( 'from YAML' ) do
67
+ before( :each ) do
68
+ @original_alert = RCAP::Alert.new( :sender => 'Sender',
69
+ :status => RCAP::Alert::STATUS_TEST,
70
+ :scope => RCAP::Alert::SCOPE_PUBLIC,
71
+ :source => 'Source',
72
+ :restriction => 'No Restriction',
73
+ :addresses => [ 'Address 1', 'Address 2'],
74
+ :code => 'Code',
75
+ :note => 'Note',
76
+ :references => [ RCAP::Alert.new( :sender => 'Sender1' ).to_reference, RCAP::Alert.new( :sender => 'Sender2' ).to_reference ],
77
+ :incidents => [ 'Incident1', 'Incident2' ],
78
+ :infos => [ RCAP::Info.new, RCAP::Info.new ])
79
+ @yaml_string = @original_alert.to_yaml
80
+ @alert = RCAP::Alert.from_yaml( @yaml_string )
81
+ end
82
+ it_should_behave_like( "a successfully parsed alert" )
83
+ end
60
84
  end
61
85
 
62
86
  describe( 'is not valid if it' ) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rcap
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.1"
4
+ version: "0.2"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Farrel Lifson
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-03 00:00:00 +02:00
12
+ date: 2009-11-20 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -39,8 +39,8 @@ executables: []
39
39
  extensions: []
40
40
 
41
41
  extra_rdoc_files:
42
- - README
43
- - CHANGELOG
42
+ - README.rdoc
43
+ - CHANGELOG.rdoc
44
44
  files:
45
45
  - lib/rcap.rb
46
46
  - lib/rcap/area.rb
@@ -55,8 +55,8 @@ files:
55
55
  - lib/rcap/parameter.rb
56
56
  - lib/rcap/event_code.rb
57
57
  - lib/rcap/info.rb
58
- - README
59
- - CHANGELOG
58
+ - README.rdoc
59
+ - CHANGELOG.rdoc
60
60
  has_rdoc: true
61
61
  homepage: http://www.aimred.com/projects/rcap
62
62
  licenses: []
data/CHANGELOG DELETED
@@ -1,2 +0,0 @@
1
- 0.1 - 5th November 2009
2
- * Initial release