rcap 0.4 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +7 -0
  2. data/CHANGELOG.rdoc +6 -0
  3. data/Gemfile +4 -0
  4. data/README.rdoc +104 -85
  5. data/Rakefile +35 -0
  6. data/lib/rcap.rb +21 -14
  7. data/lib/rcap/alert.rb +26 -325
  8. data/lib/rcap/cap_1_1/alert.rb +363 -0
  9. data/lib/rcap/cap_1_1/area.rb +180 -0
  10. data/lib/rcap/cap_1_1/circle.rb +81 -0
  11. data/lib/rcap/cap_1_1/event_code.rb +22 -0
  12. data/lib/rcap/cap_1_1/geocode.rb +22 -0
  13. data/lib/rcap/cap_1_1/info.rb +470 -0
  14. data/lib/rcap/cap_1_1/parameter.rb +68 -0
  15. data/lib/rcap/cap_1_1/point.rb +55 -0
  16. data/lib/rcap/cap_1_1/polygon.rb +89 -0
  17. data/lib/rcap/cap_1_1/resource.rb +145 -0
  18. data/lib/rcap/cap_1_2/alert.rb +363 -0
  19. data/lib/rcap/cap_1_2/area.rb +180 -0
  20. data/lib/rcap/cap_1_2/circle.rb +81 -0
  21. data/lib/rcap/cap_1_2/event_code.rb +22 -0
  22. data/lib/rcap/cap_1_2/geocode.rb +22 -0
  23. data/lib/rcap/cap_1_2/info.rb +472 -0
  24. data/lib/rcap/cap_1_2/parameter.rb +68 -0
  25. data/lib/rcap/cap_1_2/point.rb +55 -0
  26. data/lib/rcap/cap_1_2/polygon.rb +90 -0
  27. data/lib/rcap/cap_1_2/resource.rb +147 -0
  28. data/lib/rcap/utilities.rb +14 -9
  29. data/lib/rcap/validations.rb +39 -7
  30. data/lib/rcap/version.rb +3 -0
  31. data/rcap.gemspec +30 -0
  32. data/spec/alert_spec.rb +109 -172
  33. data/spec/cap_1_1/alert_spec.rb +222 -0
  34. data/spec/cap_1_1/area_spec.rb +247 -0
  35. data/spec/cap_1_1/circle_spec.rb +88 -0
  36. data/spec/{geocode_spec.rb → cap_1_1/geocode_spec.rb} +8 -8
  37. data/spec/{info_spec.rb → cap_1_1/info_spec.rb} +143 -75
  38. data/spec/{point_spec.rb → cap_1_1/point_spec.rb} +8 -8
  39. data/spec/cap_1_1/polygon_spec.rb +97 -0
  40. data/spec/{resource_spec.rb → cap_1_1/resource_spec.rb} +24 -24
  41. data/spec/cap_1_2/alert_spec.rb +233 -0
  42. data/spec/cap_1_2/area_spec.rb +248 -0
  43. data/spec/cap_1_2/circle_spec.rb +95 -0
  44. data/spec/cap_1_2/geocode_spec.rb +38 -0
  45. data/spec/cap_1_2/info_spec.rb +338 -0
  46. data/spec/cap_1_2/point_spec.rb +46 -0
  47. data/spec/cap_1_2/polygon_spec.rb +102 -0
  48. data/spec/cap_1_2/resource_spec.rb +161 -0
  49. data/spec/spec.opts +2 -0
  50. data/spec/validations_spec.rb +80 -7
  51. metadata +122 -37
  52. data/lib/rcap/area.rb +0 -156
  53. data/lib/rcap/circle.rb +0 -78
  54. data/lib/rcap/event_code.rb +0 -20
  55. data/lib/rcap/geocode.rb +0 -20
  56. data/lib/rcap/info.rb +0 -437
  57. data/lib/rcap/parameter.rb +0 -66
  58. data/lib/rcap/point.rb +0 -53
  59. data/lib/rcap/polygon.rb +0 -77
  60. data/lib/rcap/resource.rb +0 -143
  61. data/spec/area_spec.rb +0 -179
  62. data/spec/circle_spec.rb +0 -88
  63. 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