rcap 0.4 → 1.0.0.rc.1
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.
- data/.gitignore +7 -0
- data/CHANGELOG.rdoc +6 -0
- data/Gemfile +4 -0
- data/README.rdoc +104 -85
- data/Rakefile +35 -0
- data/lib/rcap.rb +21 -14
- data/lib/rcap/alert.rb +26 -325
- data/lib/rcap/cap_1_1/alert.rb +363 -0
- data/lib/rcap/cap_1_1/area.rb +180 -0
- data/lib/rcap/cap_1_1/circle.rb +81 -0
- data/lib/rcap/cap_1_1/event_code.rb +22 -0
- data/lib/rcap/cap_1_1/geocode.rb +22 -0
- data/lib/rcap/cap_1_1/info.rb +470 -0
- data/lib/rcap/cap_1_1/parameter.rb +68 -0
- data/lib/rcap/cap_1_1/point.rb +55 -0
- data/lib/rcap/cap_1_1/polygon.rb +89 -0
- data/lib/rcap/cap_1_1/resource.rb +145 -0
- data/lib/rcap/cap_1_2/alert.rb +363 -0
- data/lib/rcap/cap_1_2/area.rb +180 -0
- data/lib/rcap/cap_1_2/circle.rb +81 -0
- data/lib/rcap/cap_1_2/event_code.rb +22 -0
- data/lib/rcap/cap_1_2/geocode.rb +22 -0
- data/lib/rcap/cap_1_2/info.rb +472 -0
- data/lib/rcap/cap_1_2/parameter.rb +68 -0
- data/lib/rcap/cap_1_2/point.rb +55 -0
- data/lib/rcap/cap_1_2/polygon.rb +90 -0
- data/lib/rcap/cap_1_2/resource.rb +147 -0
- data/lib/rcap/utilities.rb +14 -9
- data/lib/rcap/validations.rb +39 -7
- data/lib/rcap/version.rb +3 -0
- data/rcap.gemspec +30 -0
- data/spec/alert_spec.rb +109 -172
- data/spec/cap_1_1/alert_spec.rb +222 -0
- data/spec/cap_1_1/area_spec.rb +247 -0
- data/spec/cap_1_1/circle_spec.rb +88 -0
- data/spec/{geocode_spec.rb → cap_1_1/geocode_spec.rb} +8 -8
- data/spec/{info_spec.rb → cap_1_1/info_spec.rb} +143 -75
- data/spec/{point_spec.rb → cap_1_1/point_spec.rb} +8 -8
- data/spec/cap_1_1/polygon_spec.rb +97 -0
- data/spec/{resource_spec.rb → cap_1_1/resource_spec.rb} +24 -24
- data/spec/cap_1_2/alert_spec.rb +233 -0
- data/spec/cap_1_2/area_spec.rb +248 -0
- data/spec/cap_1_2/circle_spec.rb +95 -0
- data/spec/cap_1_2/geocode_spec.rb +38 -0
- data/spec/cap_1_2/info_spec.rb +338 -0
- data/spec/cap_1_2/point_spec.rb +46 -0
- data/spec/cap_1_2/polygon_spec.rb +102 -0
- data/spec/cap_1_2/resource_spec.rb +161 -0
- data/spec/spec.opts +2 -0
- data/spec/validations_spec.rb +80 -7
- metadata +122 -37
- data/lib/rcap/area.rb +0 -156
- data/lib/rcap/circle.rb +0 -78
- data/lib/rcap/event_code.rb +0 -20
- data/lib/rcap/geocode.rb +0 -20
- data/lib/rcap/info.rb +0 -437
- data/lib/rcap/parameter.rb +0 -66
- data/lib/rcap/point.rb +0 -53
- data/lib/rcap/polygon.rb +0 -77
- data/lib/rcap/resource.rb +0 -143
- data/spec/area_spec.rb +0 -179
- data/spec/circle_spec.rb +0 -88
- data/spec/polygon_spec.rb +0 -68
@@ -0,0 +1,68 @@
|
|
1
|
+
module RCAP
|
2
|
+
module CAP_1_1
|
3
|
+
# A Parameter object is valid if
|
4
|
+
# * it has a name
|
5
|
+
# * it has a value
|
6
|
+
class Parameter
|
7
|
+
include Validation
|
8
|
+
|
9
|
+
validates_presence_of( :name, :value )
|
10
|
+
|
11
|
+
attr_accessor( :name, :value )
|
12
|
+
|
13
|
+
XML_ELEMENT_NAME = "parameter" # :nodoc:
|
14
|
+
NAME_ELEMENT_NAME = "valueName" # :nodoc:
|
15
|
+
VALUE_ELEMENT_NAME = "value" # :nodoc:
|
16
|
+
|
17
|
+
XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
|
18
|
+
NAME_XPATH = "cap:#{ NAME_ELEMENT_NAME }" # :nodoc:
|
19
|
+
VALUE_XPATH = "cap:#{ VALUE_ELEMENT_NAME }" # :nodoc:
|
20
|
+
|
21
|
+
def initialize( attributes = {} )
|
22
|
+
@name = attributes[ :name ]
|
23
|
+
@value = attributes[ :value ]
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_xml_element # :nodoc:
|
27
|
+
xml_element = REXML::Element.new( XML_ELEMENT_NAME )
|
28
|
+
xml_element.add_element( NAME_ELEMENT_NAME ).add_text( self.name )
|
29
|
+
xml_element.add_element( VALUE_ELEMENT_NAME ).add_text( self.value )
|
30
|
+
xml_element
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_xml # :nodoc:
|
34
|
+
self.to_xml_element.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect # :nodoc:
|
38
|
+
"#{ self.name }: #{ self.value }"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns a string representation of the parameter of the form
|
42
|
+
# name: value
|
43
|
+
def to_s
|
44
|
+
self.inspect
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.from_xml_element( parameter_xml_element ) # :nodoc:
|
48
|
+
Parameter.new( :name => RCAP.xpath_text( parameter_xml_element, NAME_XPATH, Alert::XMLNS ),
|
49
|
+
:value => RCAP.xpath_text( parameter_xml_element, VALUE_XPATH, Alert::XMLNS ))
|
50
|
+
end
|
51
|
+
|
52
|
+
# Two parameters are equivalent if they have the same name and value.
|
53
|
+
def ==( other )
|
54
|
+
[ self.name, self.value ] == [ other.name, other.value ]
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_h # :nodoc:
|
58
|
+
RCAP.attribute_values_to_hash(
|
59
|
+
[ @name, @value ])
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.from_h( hash ) # :nodoc:
|
63
|
+
key = hash.keys.first
|
64
|
+
self.new( :name => key, :value => hash[ key ])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RCAP
|
2
|
+
module CAP_1_1
|
3
|
+
# A Point object is valid if
|
4
|
+
# * it has a lattitude within the minimum and maximum lattitude values
|
5
|
+
# * it has a longitude within the minimum and maximum longitude values
|
6
|
+
class Point
|
7
|
+
include Validation
|
8
|
+
|
9
|
+
MAX_LONGITUDE = 180
|
10
|
+
MIN_LONGITUDE = -180
|
11
|
+
MAX_LATTITUDE = 90
|
12
|
+
MIN_LATTITUDE= -90
|
13
|
+
|
14
|
+
attr_accessor( :lattitude )
|
15
|
+
attr_accessor( :longitude )
|
16
|
+
|
17
|
+
validates_numericality_of( :lattitude, :longitude )
|
18
|
+
validates_inclusion_of( :lattitude, :in => MIN_LATTITUDE..MAX_LATTITUDE )
|
19
|
+
validates_inclusion_of( :longitude, :in => MIN_LONGITUDE..MAX_LONGITUDE)
|
20
|
+
|
21
|
+
def initialize( attributes = {} )
|
22
|
+
@lattitude = attributes[ :lattitude ]
|
23
|
+
@longitude = attributes[ :longitude ]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns a string representation of the point of the form
|
27
|
+
# lattitude,longitude
|
28
|
+
def to_s
|
29
|
+
"#{ self.lattitude },#{ self.longitude }"
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect # :nodoc:
|
33
|
+
'('+self.to_s+')'
|
34
|
+
end
|
35
|
+
|
36
|
+
# Two points are equivalent if they have the same lattitude and longitude
|
37
|
+
def ==( other )
|
38
|
+
[ self.lattitude, self.longitude ] == [ other.lattitude, other.longitude ]
|
39
|
+
end
|
40
|
+
|
41
|
+
LATTITUDE_KEY = 'lattitude_hash' # :nodoc:
|
42
|
+
LONGITUDE_KEY = 'longitude_hash' # :nodoc:
|
43
|
+
|
44
|
+
def to_h # :nodoc:
|
45
|
+
RCAP.attribute_values_to_hash(
|
46
|
+
[ LATTITUDE_KEY, self.lattitude ],
|
47
|
+
[ LONGITUDE_KEY, self.longitude ])
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.from_h( point_hash ) # :nodoc:
|
51
|
+
self.new( :lattitude => point_hash[ LATTITUDE_KEY ], :longitude => point_hash[ LONGITUDE_KEY ])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module RCAP
|
2
|
+
module CAP_1_1
|
3
|
+
# A Polygon object is valid if
|
4
|
+
# * it has a minimum of three points
|
5
|
+
# * each Point object in the points collection is valid
|
6
|
+
class Polygon
|
7
|
+
include Validation
|
8
|
+
|
9
|
+
# Collection of Point objects.
|
10
|
+
attr_reader( :points )
|
11
|
+
|
12
|
+
validates_length_of( :points, :minimum => 3 )
|
13
|
+
validates_collection_of( :points )
|
14
|
+
|
15
|
+
XML_ELEMENT_NAME = 'polygon' # :nodoc:
|
16
|
+
XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
|
17
|
+
|
18
|
+
def initialize( attributes = {})
|
19
|
+
@points = Array( attributes[ :points ])
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates a new Point object and adds it to the points array. The
|
23
|
+
# poitn_attributes are passed as a parameter to Point.new.
|
24
|
+
def add_point( point_attributes = {})
|
25
|
+
point = Point.new( point_attributes )
|
26
|
+
self.points << point
|
27
|
+
point
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns a string representation of the polygon of the form
|
31
|
+
# points[0] points[1] points[2] ... points[n-1] points[0]
|
32
|
+
# where each point is formatted with Point#to_s
|
33
|
+
def to_s
|
34
|
+
(@points + [ @points.first ]).join( ' ' )
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect # :nodoc:
|
38
|
+
"(#{ @points.map{|point| point.inspect}.join(', ')})"
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_xml_element # :nodoc:
|
42
|
+
xml_element = REXML::Element.new( XML_ELEMENT_NAME )
|
43
|
+
xml_element.add_text( self.to_s )
|
44
|
+
xml_element
|
45
|
+
end
|
46
|
+
|
47
|
+
# Two polygons are equivalent if their collection of points is equivalent.
|
48
|
+
def ==( other )
|
49
|
+
self.points == other.points
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.parse_polygon_string( polygon_string ) # :nodoc:
|
53
|
+
polygon_string.split( ' ' ).map{ |coordinate_string| coordinate_string.split( ',' ).map{|coordinate| coordinate.to_f }}
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.from_xml_element( polygon_xml_element ) # :nodoc:
|
57
|
+
if !polygon_xml_element.text.nil?
|
58
|
+
coordinates = self.parse_polygon_string( polygon_xml_element.text )
|
59
|
+
points = coordinates.map{ |lattitude, longitude| Point.new( :lattitude => lattitude, :longitude => longitude )}[0..-2]
|
60
|
+
polygon = self.new( :points => points )
|
61
|
+
else
|
62
|
+
self.new
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def to_yaml( options = {} ) # :nodoc:
|
68
|
+
yaml_points = self.points.map{ |point| [ point.lattitude, point.longitude ]}
|
69
|
+
def yaml_points.to_yaml_style; :inline; end
|
70
|
+
|
71
|
+
yaml_points.to_yaml( options )
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.from_yaml_data( polygon_yaml_data ) # :nodoc:
|
75
|
+
self.new( :points => Array( polygon_yaml_data ).map{ |lattitude, longitude| Point.new( :lattitude => lattitude, :longitude => longitude )})
|
76
|
+
end
|
77
|
+
|
78
|
+
POINTS_KEY = 'points' # :nodoc:
|
79
|
+
|
80
|
+
def to_h # :nodoc:
|
81
|
+
{ POINTS_KEY => self.points.map{ |point| point.to_h }}
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.from_h( polygon_hash ) # :nodoc:
|
85
|
+
self.new( :points => polygon_hash[ POINTS_KEY ].map{ |point_hash| Point.from_h( point_hash )})
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module RCAP
|
2
|
+
module CAP_1_1
|
3
|
+
# A Resourse object is valid if
|
4
|
+
# * it has a resource description
|
5
|
+
class Resource
|
6
|
+
include Validation
|
7
|
+
|
8
|
+
# Resource Description
|
9
|
+
attr_accessor( :resource_desc )
|
10
|
+
attr_accessor( :mime_type )
|
11
|
+
# Expressed in bytes
|
12
|
+
attr_accessor( :size )
|
13
|
+
# Resource location
|
14
|
+
attr_accessor( :uri )
|
15
|
+
# Dereferenced URI - contents of URI Base64 encoded
|
16
|
+
attr_accessor( :deref_uri )
|
17
|
+
# SHA-1 hash of contents of resource
|
18
|
+
attr_accessor( :digest )
|
19
|
+
|
20
|
+
validates_presence_of( :resource_desc )
|
21
|
+
|
22
|
+
XML_ELEMENT_NAME = 'resource' # :nodoc:
|
23
|
+
MIME_TYPE_ELEMENT_NAME = 'mimeType' # :nodoc:
|
24
|
+
SIZE_ELEMENT_NAME = 'size' # :nodoc:
|
25
|
+
URI_ELEMENT_NAME = 'uri' # :nodoc:
|
26
|
+
DEREF_URI_ELEMENT_NAME = 'derefUri' # :nodoc:
|
27
|
+
DIGEST_ELEMENT_NAME = 'digest' # :nodoc:
|
28
|
+
RESOURCE_DESC_ELEMENT_NAME = 'resourceDesc' # :nodoc:
|
29
|
+
|
30
|
+
XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
|
31
|
+
MIME_TYPE_XPATH = "cap:#{ MIME_TYPE_ELEMENT_NAME }" # :nodoc:
|
32
|
+
SIZE_XPATH = "cap:#{ SIZE_ELEMENT_NAME }" # :nodoc:
|
33
|
+
URI_XPATH = "cap:#{ URI_ELEMENT_NAME }" # :nodoc:
|
34
|
+
DEREF_URI_XPATH = "cap:#{ DEREF_URI_ELEMENT_NAME }" # :nodoc:
|
35
|
+
DIGEST_XPATH = "cap:#{ DIGEST_ELEMENT_NAME }" # :nodoc:
|
36
|
+
RESOURCE_DESC_XPATH = "cap:#{ RESOURCE_DESC_ELEMENT_NAME }" # :nodoc:
|
37
|
+
|
38
|
+
def initialize( attributes = {} )
|
39
|
+
@mime_type = attributes[ :mime_type ]
|
40
|
+
@size = attributes[ :size ]
|
41
|
+
@uri = attributes[ :uri ]
|
42
|
+
@deref_uri = attributes[ :deref_uri ]
|
43
|
+
@digest = attributes[ :digest ]
|
44
|
+
@resource_desc = attributes[ :resource_desc ]
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_xml_element # :nodoc:
|
48
|
+
xml_element = REXML::Element.new( XML_ELEMENT_NAME )
|
49
|
+
xml_element.add_element( RESOURCE_DESC_ELEMENT_NAME ).add_text( self.resource_desc )
|
50
|
+
xml_element.add_element( MIME_TYPE_ELEMENT_NAME ).add_text( self.mime_type ) if self.mime_type
|
51
|
+
xml_element.add_element( SIZE_ELEMENT_NAME ).add_text( self.size ) if self.size
|
52
|
+
xml_element.add_element( URI_ELEMENT_NAME ).add_text( self.uri ) if self.uri
|
53
|
+
xml_element.add_element( DEREF_URI_ELEMENT_NAME ).add_text( self.deref_uri ) if self.deref_uri
|
54
|
+
xml_element.add_element( DIGEST_ELEMENT_NAME ).add_text( self.digest ) if self.digest
|
55
|
+
xml_element
|
56
|
+
end
|
57
|
+
|
58
|
+
# If size is defined returns the size in kilobytes
|
59
|
+
def size_in_kb
|
60
|
+
if self.size
|
61
|
+
self.size.to_f/1024
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_xml # :nodoc:
|
66
|
+
self.to_xml_element.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
def inspect # :nodoc:
|
70
|
+
[ self.resource_desc, self.uri, self.mime_type, self.size ? format( "%.1fKB", self.size_in_kb ) : nil ].compact.join(' - ')
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a string representation of the resource of the form
|
74
|
+
# resource_desc
|
75
|
+
def to_s
|
76
|
+
self.resource_desc
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.from_xml_element( resource_xml_element ) # :nodoc:
|
80
|
+
resource = self.new( :resource_desc => RCAP.xpath_text( resource_xml_element, RESOURCE_DESC_XPATH, Alert::XMLNS ),
|
81
|
+
:uri => RCAP.xpath_text( resource_xml_element, URI_XPATH, Alert::XMLNS ),
|
82
|
+
:mime_type => RCAP.xpath_text( resource_xml_element, MIME_TYPE_XPATH, Alert::XMLNS ),
|
83
|
+
:deref_uri => RCAP.xpath_text( resource_xml_element, DEREF_URI_XPATH, Alert::XMLNS ),
|
84
|
+
:size => RCAP.xpath_text( resource_xml_element, SIZE_XPATH, Alert::XMLNS ),
|
85
|
+
:digest => RCAP.xpath_text( resource_xml_element, DIGEST_XPATH, Alert::XMLNS ))
|
86
|
+
end
|
87
|
+
|
88
|
+
RESOURCE_DESC_YAML = "Resource Description" # :nodoc:
|
89
|
+
URI_YAML = "URI" # :nodoc:
|
90
|
+
MIME_TYPE_YAML = "Mime Type" # :nodoc:
|
91
|
+
DEREF_URI_YAML = "Derefrenced URI Data" # :nodoc:
|
92
|
+
SIZE_YAML = "Size" # :nodoc:
|
93
|
+
DIGEST_YAML = "Digest" # :nodoc:
|
94
|
+
|
95
|
+
def to_yaml( options ) # :nodoc:
|
96
|
+
RCAP.attribute_values_to_hash(
|
97
|
+
[ RESOURCE_DESC_YAML, self.resource_desc ],
|
98
|
+
[ URI_YAML, self.uri ],
|
99
|
+
[ MIME_TYPE_YAML, self.mime_type ],
|
100
|
+
[ DEREF_URI_YAML, self.deref_uri ],
|
101
|
+
[ SIZE_YAML, self.size ],
|
102
|
+
[ DIGEST_YAML, self.digest ]
|
103
|
+
).to_yaml( options )
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.from_yaml_data( resource_yaml_data ) # :nodoc:
|
107
|
+
self.new(
|
108
|
+
:resource_desc => reource_yaml_data[ RESOURCE_DESC_YAML ],
|
109
|
+
:uri => reource_yaml_data[ URI_YAML ],
|
110
|
+
:mime_type => reource_yaml_data[ MIME_TYPE_YAML ],
|
111
|
+
:deref_uri => reource_yaml_data[ DEREF_URI_YAML ],
|
112
|
+
:size => reource_yaml_data[ SIZE_YAML ],
|
113
|
+
:digest => reource_yaml_data[ DIGEST_YAML ]
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
RESOURCE_DESC_KEY = 'resource_desc' # :nodoc:
|
118
|
+
URI_KEY = 'uri' # :nodoc:
|
119
|
+
MIME_TYPE_KEY = 'mime_type' # :nodoc:
|
120
|
+
DEREF_URI_KEY = 'deref_uri' # :nodoc:
|
121
|
+
SIZE_KEY = 'size' # :nodoc:
|
122
|
+
DIGEST_KEY = 'digest' # :nodoc:
|
123
|
+
|
124
|
+
def to_h # :nodoc:
|
125
|
+
RCAP.attribute_values_to_hash(
|
126
|
+
[ RESOURCE_DESC_KEY, self.resource_desc ],
|
127
|
+
[ URI_KEY, self.uri],
|
128
|
+
[ MIME_TYPE_KEY, self.mime_type],
|
129
|
+
[ DEREF_URI_KEY, self.deref_uri],
|
130
|
+
[ SIZE_KEY, self.size ],
|
131
|
+
[ DIGEST_KEY, self.digest ])
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.from_h( resource_hash ) # :nodoc:
|
135
|
+
self.new(
|
136
|
+
:resource_desc => resource_hash[ RESOURCE_DESC_KEY ],
|
137
|
+
:uri => resource_hash[ URI_KEY ],
|
138
|
+
:mime_type => resource_hash[ MIME_TYPE_KEY ],
|
139
|
+
:deref_uri => resource_hash[ DEREF_URI_KEY ],
|
140
|
+
:size => resource_hash[ SIZE_KEY ],
|
141
|
+
:digest => resource_hash[ DIGEST_KEY ])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,363 @@
|
|
1
|
+
module RCAP
|
2
|
+
module CAP_1_2
|
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 = "urn:oasis:names:tc:emergency:cap:1.2"
|
15
|
+
CAP_VERSION = "1.2"
|
16
|
+
|
17
|
+
STATUS_ACTUAL = "Actual" # :nodoc:
|
18
|
+
STATUS_EXERCISE = "Exercise" # :nodoc:
|
19
|
+
STATUS_SYSTEM = "System" # :nodoc:
|
20
|
+
STATUS_TEST = "Test" # :nodoc:
|
21
|
+
STATUS_DRAFT = "Draft" # :nodoc:
|
22
|
+
# Valid values for status
|
23
|
+
VALID_STATUSES = [ STATUS_ACTUAL, STATUS_EXERCISE, STATUS_SYSTEM, STATUS_TEST, STATUS_DRAFT ]
|
24
|
+
|
25
|
+
MSG_TYPE_ALERT = "Alert" # :nodoc:
|
26
|
+
MSG_TYPE_UPDATE = "Update" # :nodoc:
|
27
|
+
MSG_TYPE_CANCEL = "Cancel" # :nodoc:
|
28
|
+
MSG_TYPE_ACK = "Ack" # :nodoc:
|
29
|
+
MSG_TYPE_ERROR = "Error" # :nodoc:
|
30
|
+
# Valid values for msg_type
|
31
|
+
VALID_MSG_TYPES = [ MSG_TYPE_ALERT, MSG_TYPE_UPDATE, MSG_TYPE_CANCEL, MSG_TYPE_ACK, MSG_TYPE_ERROR ]
|
32
|
+
|
33
|
+
SCOPE_PUBLIC = "Public" # :nodoc:
|
34
|
+
SCOPE_RESTRICTED = "Restricted" # :nodoc:
|
35
|
+
SCOPE_PRIVATE = "Private" # :nodoc:
|
36
|
+
# Valid values for scope
|
37
|
+
VALID_SCOPES = [ SCOPE_PUBLIC, SCOPE_PRIVATE, SCOPE_RESTRICTED ]
|
38
|
+
|
39
|
+
XML_ELEMENT_NAME = 'alert' # :nodoc:
|
40
|
+
IDENTIFIER_ELEMENT_NAME = 'identifier' # :nodoc:
|
41
|
+
SENDER_ELEMENT_NAME = 'sender' # :nodoc:
|
42
|
+
SENT_ELEMENT_NAME = 'sent' # :nodoc:
|
43
|
+
STATUS_ELEMENT_NAME = 'status' # :nodoc:
|
44
|
+
MSG_TYPE_ELEMENT_NAME = 'msgType' # :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
|
+
SOURCE_XPATH = "cap:#{ SOURCE_ELEMENT_NAME }" # :nodoc:
|
61
|
+
SCOPE_XPATH = "cap:#{ SCOPE_ELEMENT_NAME }" # :nodoc:
|
62
|
+
RESTRICTION_XPATH = "cap:#{ RESTRICTION_ELEMENT_NAME }" # :nodoc:
|
63
|
+
ADDRESSES_XPATH = "cap:#{ ADDRESSES_ELEMENT_NAME }" # :nodoc:
|
64
|
+
CODE_XPATH = "cap:#{ CODE_ELEMENT_NAME }" # :nodoc:
|
65
|
+
NOTE_XPATH = "cap:#{ NOTE_ELEMENT_NAME }" # :nodoc:
|
66
|
+
REFERENCES_XPATH = "cap:#{ REFERENCES_ELEMENT_NAME }" # :nodoc:
|
67
|
+
INCIDENTS_XPATH = "cap:#{ INCIDENTS_ELEMENT_NAME }" # :nodoc:
|
68
|
+
|
69
|
+
# If not set a UUID will be set by default
|
70
|
+
attr_accessor( :identifier)
|
71
|
+
attr_accessor( :sender )
|
72
|
+
# Sent Time - If not set will value will be time of creation.
|
73
|
+
attr_accessor( :sent )
|
74
|
+
# Value can only be one of VALID_STATUSES
|
75
|
+
attr_accessor( :status )
|
76
|
+
# Value can only be one of VALID_MSG_TYPES
|
77
|
+
attr_accessor( :msg_type )
|
78
|
+
# Value can only be one of VALID_SCOPES
|
79
|
+
attr_accessor( :scope )
|
80
|
+
attr_accessor( :source )
|
81
|
+
# Depends on scope being SCOPE_RESTRICTED.
|
82
|
+
attr_accessor( :restriction )
|
83
|
+
attr_accessor( :note )
|
84
|
+
|
85
|
+
# Collection of address strings. Depends on scope being SCOPE_PRIVATE.
|
86
|
+
attr_reader( :addresses )
|
87
|
+
attr_reader( :codes )
|
88
|
+
# Collection of reference strings - see Alert#to_reference
|
89
|
+
attr_reader( :references)
|
90
|
+
# Collection of incident strings
|
91
|
+
attr_reader( :incidents )
|
92
|
+
# Collection of Info objects
|
93
|
+
attr_reader( :infos )
|
94
|
+
|
95
|
+
validates_presence_of( :identifier, :sender, :sent, :status, :msg_type, :scope )
|
96
|
+
|
97
|
+
validates_inclusion_of( :status, in: VALID_STATUSES )
|
98
|
+
validates_inclusion_of( :msg_type, in: VALID_MSG_TYPES )
|
99
|
+
validates_inclusion_of( :scope, in: VALID_SCOPES )
|
100
|
+
|
101
|
+
validates_format_of( :identifier, with: ALLOWED_CHARACTERS )
|
102
|
+
validates_format_of( :sender , with: ALLOWED_CHARACTERS )
|
103
|
+
|
104
|
+
validates_conditional_presence_of( :addresses, when: :scope, is: SCOPE_PRIVATE )
|
105
|
+
validates_conditional_presence_of( :restriction, when: :scope, is: SCOPE_RESTRICTED )
|
106
|
+
|
107
|
+
validates_collection_of( :infos )
|
108
|
+
|
109
|
+
def initialize( attributes = {})
|
110
|
+
@identifier = attributes[ :identifier ]
|
111
|
+
@sender = attributes[ :sender ]
|
112
|
+
@sent = attributes[ :sent ]
|
113
|
+
@status = attributes[ :status ]
|
114
|
+
@msg_type = attributes[ :msg_type ]
|
115
|
+
@scope = attributes[ :scope ]
|
116
|
+
@source = attributes[ :source ]
|
117
|
+
@restriction = attributes[ :restriction ]
|
118
|
+
@addresses = Array( attributes[ :addresses ])
|
119
|
+
@codes = Array( attributes[ :codes ])
|
120
|
+
@references = Array( attributes[ :references ])
|
121
|
+
@incidents = Array( attributes[ :incidents ])
|
122
|
+
@infos = Array( attributes[ :infos ])
|
123
|
+
end
|
124
|
+
|
125
|
+
# Creates a new Info object and adds it to the infos array. The
|
126
|
+
# info_attributes are passed as a parameter to Info.new.
|
127
|
+
def add_info( info_attributes = {})
|
128
|
+
info = Info.new( info_attributes )
|
129
|
+
self.infos << info
|
130
|
+
info
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_xml_element #:nodoc:
|
134
|
+
xml_element = REXML::Element.new( XML_ELEMENT_NAME )
|
135
|
+
xml_element.add_namespace( XMLNS )
|
136
|
+
xml_element.add_element( IDENTIFIER_ELEMENT_NAME ).add_text( self.identifier ) if self.identifier
|
137
|
+
xml_element.add_element( SENDER_ELEMENT_NAME ).add_text( self.sender ) if self.sender
|
138
|
+
xml_element.add_element( SENT_ELEMENT_NAME ).add_text( self.sent.to_s_for_cap ) if self.sent
|
139
|
+
xml_element.add_element( STATUS_ELEMENT_NAME ).add_text( self.status ) if self.status
|
140
|
+
xml_element.add_element( MSG_TYPE_ELEMENT_NAME ).add_text( self.msg_type ) if self.msg_type
|
141
|
+
xml_element.add_element( SOURCE_ELEMENT_NAME ).add_text( self.source ) if self.source
|
142
|
+
xml_element.add_element( SCOPE_ELEMENT_NAME ).add_text( self.scope ) if self.scope
|
143
|
+
xml_element.add_element( RESTRICTION_ELEMENT_NAME ).add_text( self.restriction ) if self.restriction
|
144
|
+
unless self.addresses.empty?
|
145
|
+
xml_element.add_element( ADDRESSES_ELEMENT_NAME ).add_text( self.addresses.to_s_for_cap )
|
146
|
+
end
|
147
|
+
self.codes.each do |code|
|
148
|
+
xml_element.add_element( CODE_ELEMENT_NAME ).add_text( code )
|
149
|
+
end
|
150
|
+
xml_element.add_element( NOTE_ELEMENT_NAME ).add_text( self.note ) if self.note
|
151
|
+
unless self.references.empty?
|
152
|
+
xml_element.add_element( REFERENCES_ELEMENT_NAME ).add_text( self.references.join( ' ' ))
|
153
|
+
end
|
154
|
+
unless self.incidents.empty?
|
155
|
+
xml_element.add_element( INCIDENTS_ELEMENT_NAME ).add_text( self.incidents.join( ' ' ))
|
156
|
+
end
|
157
|
+
self.infos.each do |info|
|
158
|
+
xml_element.add_element( info.to_xml_element )
|
159
|
+
end
|
160
|
+
xml_element
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_xml_document #:nodoc:
|
164
|
+
xml_document = REXML::Document.new
|
165
|
+
xml_document.add( REXML::XMLDecl.new )
|
166
|
+
xml_document.add( self.to_xml_element )
|
167
|
+
xml_document
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns a string containing the XML representation of the alert.
|
171
|
+
def to_xml
|
172
|
+
self.to_xml_document.to_s
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns a string representation of the alert suitable for usage as a reference in a CAP message of the form
|
176
|
+
# sender,identifier,sent
|
177
|
+
def to_reference
|
178
|
+
"#{ self.sender },#{ self.identifier },#{ self.sent }"
|
179
|
+
end
|
180
|
+
|
181
|
+
def inspect # :nodoc:
|
182
|
+
alert_inspect = <<EOF
|
183
|
+
CAP Version: #{ CAP_VERSION }
|
184
|
+
Identifier: #{ self.identifier }
|
185
|
+
Sender: #{ self.sender }
|
186
|
+
Sent: #{ self.sent }
|
187
|
+
Status: #{ self.status }
|
188
|
+
Message Type: #{ self.msg_type }
|
189
|
+
Source: #{ self.source }
|
190
|
+
Scope: #{ self.scope }
|
191
|
+
Restriction: #{ self.restriction }
|
192
|
+
Addresses: #{ self.addresses.to_s_for_cap }
|
193
|
+
Codes:
|
194
|
+
#{ self.codes.map{ |code| " #{ code }" }.join("\n")}
|
195
|
+
Note: #{ self.note }
|
196
|
+
References: #{ self.references.join( ' ' )}
|
197
|
+
Incidents: #{ self.incidents.join( ' ')}
|
198
|
+
Information:
|
199
|
+
#{ self.infos.map{ |info| " " + info.to_s }.join( "\n" )}
|
200
|
+
EOF
|
201
|
+
RCAP.format_lines_for_inspect( 'ALERT', alert_inspect )
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns a string representation of the alert of the form
|
205
|
+
# sender/identifier/sent
|
206
|
+
# See Alert#to_reference for another string representation suitable as a CAP reference.
|
207
|
+
def to_s
|
208
|
+
"#{ self.sender }/#{ self.identifier }/#{ self.sent }"
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.from_xml_element( alert_xml_element ) # :nodoc:
|
212
|
+
self.new( :identifier => RCAP.xpath_text( alert_xml_element, IDENTIFIER_XPATH, Alert::XMLNS ),
|
213
|
+
:sender => RCAP.xpath_text( alert_xml_element, SENDER_XPATH, Alert::XMLNS ),
|
214
|
+
:sent => (( sent = RCAP.xpath_first( alert_xml_element, SENT_XPATH, Alert::XMLNS )) ? DateTime.parse( sent.text ) : nil ),
|
215
|
+
:status => RCAP.xpath_text( alert_xml_element, STATUS_XPATH, Alert::XMLNS ),
|
216
|
+
:msg_type => RCAP.xpath_text( alert_xml_element, MSG_TYPE_XPATH, Alert::XMLNS ),
|
217
|
+
:source => RCAP.xpath_text( alert_xml_element, SOURCE_XPATH, Alert::XMLNS ),
|
218
|
+
:scope => RCAP.xpath_text( alert_xml_element, SCOPE_XPATH, Alert::XMLNS ),
|
219
|
+
:restriction => RCAP.xpath_text( alert_xml_element, RESTRICTION_XPATH, Alert::XMLNS ),
|
220
|
+
:addresses => (( address = RCAP.xpath_text( alert_xml_element, ADDRESSES_XPATH, Alert::XMLNS )) ? address.unpack_cap_list : nil ),
|
221
|
+
:codes => RCAP.xpath_match( alert_xml_element, CODE_XPATH, Alert::XMLNS ).map{ |element| element.text },
|
222
|
+
:note => RCAP.xpath_text( alert_xml_element, NOTE_XPATH, Alert::XMLNS ),
|
223
|
+
:references => (( references = RCAP.xpath_text( alert_xml_element, REFERENCES_XPATH, Alert::XMLNS )) ? references.split( ' ' ) : nil ),
|
224
|
+
:incidents => (( incidents = RCAP.xpath_text( alert_xml_element, INCIDENTS_XPATH, Alert::XMLNS )) ? incidents.split( ' ' ) : nil ),
|
225
|
+
:infos => RCAP.xpath_match( alert_xml_element, Info::XPATH, Alert::XMLNS ).map{ |element| Info.from_xml_element( element )})
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.from_xml_document( xml_document ) # :nodoc:
|
229
|
+
self.from_xml_element( xml_document.root )
|
230
|
+
end
|
231
|
+
|
232
|
+
# Initialise an Alert object from an XML string. Any object that is a subclass of IO (e.g. File) can be passed in.
|
233
|
+
def self.from_xml( xml )
|
234
|
+
self.from_xml_document( REXML::Document.new( xml ))
|
235
|
+
end
|
236
|
+
|
237
|
+
CAP_VERSION_YAML = "CAP Version" # :nodoc:
|
238
|
+
IDENTIFIER_YAML = "Identifier" # :nodoc:
|
239
|
+
SENDER_YAML = "Sender" # :nodoc:
|
240
|
+
SENT_YAML = "Sent" # :nodoc:
|
241
|
+
STATUS_YAML = "Status" # :nodoc:
|
242
|
+
MSG_TYPE_YAML = "Message Type" # :nodoc:
|
243
|
+
SOURCE_YAML = "Source" # :nodoc:
|
244
|
+
SCOPE_YAML = "Scope" # :nodoc:
|
245
|
+
RESTRICTION_YAML = "Restriction" # :nodoc:
|
246
|
+
ADDRESSES_YAML = "Addresses" # :nodoc:
|
247
|
+
CODES_YAML = "Codes" # :nodoc:
|
248
|
+
NOTE_YAML = "Note" # :nodoc:
|
249
|
+
REFERENCES_YAML = "References" # :nodoc:
|
250
|
+
INCIDENTS_YAML = "Incidents" # :nodoc:
|
251
|
+
INFOS_YAML = "Information" # :nodoc:
|
252
|
+
|
253
|
+
# Returns a string containing the YAML representation of the alert.
|
254
|
+
def to_yaml( options = {} )
|
255
|
+
RCAP.attribute_values_to_hash(
|
256
|
+
[ CAP_VERSION_YAML, CAP_VERSION ],
|
257
|
+
[ IDENTIFIER_YAML, self.identifier ],
|
258
|
+
[ SENDER_YAML, self.sender ],
|
259
|
+
[ SENT_YAML, self.sent ],
|
260
|
+
[ STATUS_YAML, self.status ],
|
261
|
+
[ MSG_TYPE_YAML, self.msg_type ],
|
262
|
+
[ SOURCE_YAML, self.source ],
|
263
|
+
[ SCOPE_YAML, self.scope ],
|
264
|
+
[ RESTRICTION_YAML, self.restriction ],
|
265
|
+
[ ADDRESSES_YAML, self.addresses ],
|
266
|
+
[ CODES_YAML, self.codes ],
|
267
|
+
[ NOTE_YAML, self.note ],
|
268
|
+
[ REFERENCES_YAML, self.references ],
|
269
|
+
[ INCIDENTS_YAML, self.incidents ],
|
270
|
+
[ INFOS_YAML, self.infos ]
|
271
|
+
).to_yaml( options )
|
272
|
+
end
|
273
|
+
|
274
|
+
# Initialise an Alert object from a YAML string. Any object that is a subclass of IO (e.g. File) can be passed in.
|
275
|
+
def self.from_yaml( yaml )
|
276
|
+
self.from_yaml_data( YAML.load( yaml ))
|
277
|
+
end
|
278
|
+
|
279
|
+
def self.from_yaml_data( alert_yaml_data ) # :nodoc:
|
280
|
+
Alert.new(
|
281
|
+
:identifier => alert_yaml_data[ IDENTIFIER_YAML ],
|
282
|
+
:sender => alert_yaml_data[ SENDER_YAML ],
|
283
|
+
:sent => ( sent = alert_yaml_data[ SENT_YAML ]).blank? ? nil : DateTime.parse( sent.to_s ),
|
284
|
+
:status => alert_yaml_data[ STATUS_YAML ],
|
285
|
+
:msg_type => alert_yaml_data[ MSG_TYPE_YAML ],
|
286
|
+
:source => alert_yaml_data[ SOURCE_YAML ],
|
287
|
+
:scope => alert_yaml_data[ SCOPE_YAML ],
|
288
|
+
:restriction => alert_yaml_data[ RESTRICTION_YAML ],
|
289
|
+
:addresses => alert_yaml_data[ ADDRESSES_YAML ],
|
290
|
+
:codes => alert_yaml_data[ CODES_YAML ],
|
291
|
+
:note => alert_yaml_data[ NOTE_YAML ],
|
292
|
+
:references => alert_yaml_data[ REFERENCES_YAML ],
|
293
|
+
:incidents => alert_yaml_data[ INCIDENTS_YAML ],
|
294
|
+
:infos => Array( alert_yaml_data[ INFOS_YAML ]).map{ |info_yaml_data| Info.from_yaml_data( info_yaml_data )}
|
295
|
+
)
|
296
|
+
end
|
297
|
+
|
298
|
+
CAP_VERSION_KEY = 'cap_version' # :nodoc:
|
299
|
+
IDENTIFIER_KEY = 'identifier' # :nodoc:
|
300
|
+
SENDER_KEY = 'sender' # :nodoc:
|
301
|
+
SENT_KEY = 'sent' # :nodoc:
|
302
|
+
STATUS_KEY = 'status' # :nodoc:
|
303
|
+
MSG_TYPE_KEY = 'msg_type' # :nodoc:
|
304
|
+
SOURCE_KEY = 'source' # :nodoc:
|
305
|
+
SCOPE_KEY = 'scope' # :nodoc:
|
306
|
+
RESTRICTION_KEY = 'restriction' # :nodoc:
|
307
|
+
ADDRESSES_KEY = 'addresses' # :nodoc:
|
308
|
+
CODES_KEY = 'codes' # :nodoc:
|
309
|
+
NOTE_KEY = 'note' # :nodoc:
|
310
|
+
REFERENCES_KEY = 'references' # :nodoc:
|
311
|
+
INCIDENTS_KEY = 'incidents' # :nodoc:
|
312
|
+
INFOS_KEY = 'infos' # :nodoc:
|
313
|
+
|
314
|
+
# Returns a Hash representation of an Alert object
|
315
|
+
def to_h
|
316
|
+
RCAP.attribute_values_to_hash( [ CAP_VERSION_KEY, CAP_VERSION ],
|
317
|
+
[ IDENTIFIER_KEY, self.identifier ],
|
318
|
+
[ SENDER_KEY, self.sender ],
|
319
|
+
[ SENT_KEY, RCAP.to_s_for_cap( self.sent )],
|
320
|
+
[ STATUS_KEY, self.status ],
|
321
|
+
[ MSG_TYPE_KEY, self.msg_type ],
|
322
|
+
[ SOURCE_KEY, self.source ],
|
323
|
+
[ SCOPE_KEY, self.scope ],
|
324
|
+
[ RESTRICTION_KEY, self.restriction ],
|
325
|
+
[ ADDRESSES_KEY, self.addresses ],
|
326
|
+
[ CODES_KEY, self.codes ],
|
327
|
+
[ NOTE_KEY, self.note ],
|
328
|
+
[ REFERENCES_KEY, self.references ],
|
329
|
+
[ INCIDENTS_KEY, self.incidents ],
|
330
|
+
[ INFOS_KEY, self.infos.map{ |info| info.to_h }])
|
331
|
+
end
|
332
|
+
|
333
|
+
# Initialises an Alert object from a Hash produced by Alert#to_h
|
334
|
+
def self.from_h( alert_hash )
|
335
|
+
self.new(
|
336
|
+
:identifier => alert_hash[ IDENTIFIER_KEY ],
|
337
|
+
:sender => alert_hash[ SENDER_KEY ],
|
338
|
+
:sent => RCAP.parse_datetime( alert_hash[ SENT_KEY ]),
|
339
|
+
:status => alert_hash[ STATUS_KEY ],
|
340
|
+
:msg_type => alert_hash[ MSG_TYPE_KEY ],
|
341
|
+
:source => alert_hash[ SOURCE_KEY ],
|
342
|
+
:scope => alert_hash[ SCOPE_KEY ],
|
343
|
+
:restriction => alert_hash[ RESTRICTION_KEY ],
|
344
|
+
:addresses => alert_hash[ ADDRESSES_KEY ],
|
345
|
+
:codes => alert_hash[ CODES_KEY ],
|
346
|
+
:note => alert_hash[ NOTE_KEY ],
|
347
|
+
:references => alert_hash[ REFERENCES_KEY ],
|
348
|
+
:incidents => alert_hash[ INCIDENTS_KEY ],
|
349
|
+
:infos => Array( alert_hash[ INFOS_KEY ]).map{ |info_hash| Info.from_h( info_hash )})
|
350
|
+
end
|
351
|
+
|
352
|
+
# Returns a JSON string representation of an Alert object
|
353
|
+
def to_json
|
354
|
+
self.to_h.to_json
|
355
|
+
end
|
356
|
+
|
357
|
+
# Initiialises an Alert object from a JSON string produced by Alert#to_json
|
358
|
+
def self.from_json( json_string )
|
359
|
+
self.from_h( JSON.parse( json_string ))
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|