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.
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