ratis 2.5.2.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/README.md +157 -0
- data/lib/ratis.rb +49 -0
- data/lib/ratis/closest_stop.rb +43 -0
- data/lib/ratis/config.rb +21 -0
- data/lib/ratis/core_ext.rb +35 -0
- data/lib/ratis/errors.rb +70 -0
- data/lib/ratis/itinerary.rb +55 -0
- data/lib/ratis/landmark.rb +29 -0
- data/lib/ratis/landmark_category.rb +22 -0
- data/lib/ratis/location.rb +71 -0
- data/lib/ratis/next_bus.rb +93 -0
- data/lib/ratis/point_2_point.rb +112 -0
- data/lib/ratis/request.rb +39 -0
- data/lib/ratis/route.rb +31 -0
- data/lib/ratis/route_stops.rb +40 -0
- data/lib/ratis/schedule.rb +7 -0
- data/lib/ratis/schedule_group.rb +7 -0
- data/lib/ratis/schedule_nearby.rb +79 -0
- data/lib/ratis/schedule_trip.rb +7 -0
- data/lib/ratis/service.rb +7 -0
- data/lib/ratis/stop.rb +12 -0
- data/lib/ratis/timetable.rb +36 -0
- data/lib/ratis/version.rb +33 -0
- data/lib/ratis/walk.rb +37 -0
- metadata +217 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
module Ratis
|
2
|
+
|
3
|
+
class LandmarkCategory
|
4
|
+
|
5
|
+
attr_accessor :type, :description
|
6
|
+
|
7
|
+
def self.all
|
8
|
+
|
9
|
+
response = Request.get 'Getcategories'
|
10
|
+
return [] unless response.success?
|
11
|
+
|
12
|
+
response.to_array(:getcategories_response, :types, :typeinfo).map do |typeinfo|
|
13
|
+
atis_landmark_category = LandmarkCategory.new
|
14
|
+
atis_landmark_category.type = typeinfo[:type]
|
15
|
+
atis_landmark_category.description = typeinfo[:description]
|
16
|
+
atis_landmark_category
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Ratis
|
2
|
+
|
3
|
+
class Location
|
4
|
+
|
5
|
+
attr_accessor :name, :area, :response, :areacode, :latitude, :longitude, :landmark_id, :address, :startaddr, :endaddr, :address_string
|
6
|
+
|
7
|
+
def self.where(conditions)
|
8
|
+
location = conditions.delete :location
|
9
|
+
media = (conditions.delete(:media) || :w).to_s.upcase
|
10
|
+
max_answers = conditions.delete(:max_answers) || 20
|
11
|
+
|
12
|
+
raise ArgumentError.new('You must provide a location') unless location
|
13
|
+
raise ArgumentError.new('You must provide media of A|W|I') unless ['A','W','I'].include? media
|
14
|
+
raise ArgumentError.new('You must provide a numeric max_answers') unless (Integer max_answers rescue false)
|
15
|
+
Ratis.all_conditions_used? conditions
|
16
|
+
|
17
|
+
response = Request.get 'Locate', {'Location' => location, 'Media' => media, 'Maxanswers' => max_answers}
|
18
|
+
return [] unless response.success?
|
19
|
+
|
20
|
+
meta = response.to_hash[:locate_response]
|
21
|
+
locations = response.to_array :locate_response, :location
|
22
|
+
|
23
|
+
locations.map do |location_hash|
|
24
|
+
location = Ratis::Location.new
|
25
|
+
location.name = location_hash[:name]
|
26
|
+
location.area = location_hash[:area]
|
27
|
+
location.response = meta[:responsecode]
|
28
|
+
location.areacode = location_hash[:areacode]
|
29
|
+
location.latitude = location_hash[:latitude]
|
30
|
+
location.longitude = location_hash[:longitude]
|
31
|
+
location.landmark_id = location_hash[:landmarkid] || 0
|
32
|
+
location.address = location_hash[:address] || ''
|
33
|
+
location.startaddr = location_hash[:startaddr] || ''
|
34
|
+
location.endaddr = location_hash[:endaddr] || ''
|
35
|
+
location.address_string = build_address_string location_hash
|
36
|
+
location
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_a
|
41
|
+
[latitude, longitude, name, landmark_id]
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_hash
|
45
|
+
keys = [:latitude, :longitude, :name, :area, :address, :startaddr, :endaddr, :address_string, :landmark_id]
|
46
|
+
Hash[keys.map { |k| [k, send(k)] }]
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def self.build_address_string(location_hash)
|
52
|
+
address_string = ''
|
53
|
+
address = location_hash[:address]
|
54
|
+
name = location_hash[:name]
|
55
|
+
area = location_hash[:area]
|
56
|
+
|
57
|
+
if !address.blank?
|
58
|
+
address_string << "#{address} #{name} (in #{area})"
|
59
|
+
else
|
60
|
+
startaddr = location_hash[:startaddr]
|
61
|
+
if !startaddr.blank?
|
62
|
+
endaddr = location_hash[:endaddr]
|
63
|
+
address_string << "#{startaddr} - #{endaddr} #{name} (in #{area})"
|
64
|
+
else
|
65
|
+
address_string << "#{name} (in #{area})"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Ratis
|
2
|
+
|
3
|
+
class NextBus
|
4
|
+
|
5
|
+
attr_accessor :stops, :runs
|
6
|
+
|
7
|
+
# Initializes a NextBus object with stops and runs.
|
8
|
+
# @return [NextBus]
|
9
|
+
#
|
10
|
+
# == Parameters:
|
11
|
+
#
|
12
|
+
# [stops] <em>Optional</em> -
|
13
|
+
# An array of stops. Defaults to empty array.
|
14
|
+
# [runs] <em>Optional</em> -
|
15
|
+
# An array of runs. Defaults to empty array.
|
16
|
+
|
17
|
+
def initialize(_stops = [], _runs = [])
|
18
|
+
@stops, @runs = _stops, _runs
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns results of NextBus query containing arrays of stops and runs.
|
22
|
+
# @return [NextBus] containing next buses.
|
23
|
+
#
|
24
|
+
# == Parameters:
|
25
|
+
#
|
26
|
+
# Takes hash of conditions
|
27
|
+
#
|
28
|
+
# [option1] <b>Required</b> -
|
29
|
+
# Description of required param
|
30
|
+
# [option2] <em>Optional</em> -
|
31
|
+
# Description of optional param
|
32
|
+
# [option3] <em>Optional</em> -
|
33
|
+
# Description of optional param
|
34
|
+
# [option4] <em>Optional</em> -
|
35
|
+
# Description of optional param
|
36
|
+
|
37
|
+
def self.where(conditions)
|
38
|
+
stop_id = conditions.delete :stop_id
|
39
|
+
app_id = conditions.delete(:app_id) || 'na'
|
40
|
+
|
41
|
+
raise ArgumentError.new('You must provide a stop ID') unless stop_id
|
42
|
+
Ratis.all_conditions_used? conditions
|
43
|
+
|
44
|
+
response = Request.get 'Nextbus2', { 'Stopid' => stop_id, 'Appid' => app_id }
|
45
|
+
return [] unless response.success?
|
46
|
+
|
47
|
+
stops = response.to_array :nextbus2_response, :stops, :stop
|
48
|
+
runs = response.to_array :nextbus2_response, :runs, :run
|
49
|
+
|
50
|
+
NextBus.new stops, runs
|
51
|
+
end
|
52
|
+
|
53
|
+
# Gets description of first stop
|
54
|
+
# @return [String] Description of first stop or nil.
|
55
|
+
|
56
|
+
def first_stop_description
|
57
|
+
stops.first ? stops.first[:description] : nil
|
58
|
+
end
|
59
|
+
|
60
|
+
# Details of NextBus instance in a hash.
|
61
|
+
# @return [Hash] NextBus details in a hash.
|
62
|
+
|
63
|
+
def to_hash
|
64
|
+
{ :stopname => first_stop_description,
|
65
|
+
:signs => runs.map { |run| run[:sign] }.uniq,
|
66
|
+
:runs => runs.map do |run|
|
67
|
+
{ :time => run[:estimatedtime],
|
68
|
+
:sign => run[:sign],
|
69
|
+
:adherence => run[:adherence],
|
70
|
+
:route => run[:route]
|
71
|
+
}
|
72
|
+
end
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Details of NextBus instance in a hash to be transformed to xml
|
77
|
+
# @private
|
78
|
+
|
79
|
+
def to_hash_for_xml
|
80
|
+
{ :stopname => first_stop_description,
|
81
|
+
:runs => runs.map do |run|
|
82
|
+
{ :time => run[:estimatedtime],
|
83
|
+
:scheduled_time => run[:triptime],
|
84
|
+
:sign => run[:sign],
|
85
|
+
:adherence => run[:adherence],
|
86
|
+
:route => run[:route]
|
87
|
+
}
|
88
|
+
end
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Ratis
|
2
|
+
|
3
|
+
class Point2Point
|
4
|
+
|
5
|
+
def self.where(conditions)
|
6
|
+
routes_only = conditions.delete(:routes_only)
|
7
|
+
|
8
|
+
origin_lat = conditions.delete(:origin_lat).to_f
|
9
|
+
origin_long = conditions.delete(:origin_long).to_f
|
10
|
+
destination_lat = conditions.delete(:destination_lat).to_f
|
11
|
+
destination_long = conditions.delete(:destination_long).to_f
|
12
|
+
|
13
|
+
date = conditions.delete :date
|
14
|
+
start_time = conditions.delete :start_time
|
15
|
+
end_time = conditions.delete :end_time
|
16
|
+
|
17
|
+
raise ArgumentError.new("You must specify routes only with true, false, 'y' or 'n'") unless routes_only.y_or_n rescue false
|
18
|
+
|
19
|
+
raise ArgumentError.new('You must provide an origin latitude') unless Ratis.valid_latitude? origin_lat
|
20
|
+
raise ArgumentError.new('You must provide an origin longitude') unless Ratis.valid_longitude? origin_long
|
21
|
+
raise ArgumentError.new('You must provide an destination latitude') unless Ratis.valid_latitude? destination_lat
|
22
|
+
raise ArgumentError.new('You must provide an destination longitude') unless Ratis.valid_longitude? destination_long
|
23
|
+
|
24
|
+
raise ArgumentError.new('You must provide a date DD/MM/YYYY') unless DateTime.strptime(date, '%d/%m/%Y') rescue false
|
25
|
+
raise ArgumentError.new('You must provide a start time as 24-hour HHMM') unless DateTime.strptime(start_time, '%H%M') rescue false
|
26
|
+
raise ArgumentError.new('You must provide an end time as 24-hour HHMM') unless DateTime.strptime(end_time, '%H%M') rescue false
|
27
|
+
|
28
|
+
Ratis.all_conditions_used? conditions
|
29
|
+
|
30
|
+
response = Request.get 'Point2point', 'Routesonly' => routes_only.y_or_n.upcase,
|
31
|
+
'Originlat' => origin_lat, 'Originlong' => origin_long,
|
32
|
+
'Destinationlat' => destination_lat, 'Destinationlong' => destination_long,
|
33
|
+
'Date' => date, 'Starttime' => start_time, 'Endtime' => end_time
|
34
|
+
|
35
|
+
return nil unless response.success?
|
36
|
+
|
37
|
+
return parse_routes_only_yes response if routes_only.y_or_n.downcase == 'y'
|
38
|
+
return parse_routes_only_no response if routes_only.y_or_n.downcase == 'n'
|
39
|
+
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def self.parse_routes_only_yes(response)
|
46
|
+
response.to_array(:point2point_response, :routes, :service).map do |service|
|
47
|
+
atis_service = Service.new
|
48
|
+
atis_service.route = service[:route]
|
49
|
+
atis_service.direction = service[:direction]
|
50
|
+
atis_service.service_type = service[:servicetype]
|
51
|
+
atis_service.signage = service[:signage]
|
52
|
+
atis_service.route_type = service[:routetype]
|
53
|
+
atis_service
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.parse_routes_only_no(response)
|
58
|
+
return nil unless response.success?
|
59
|
+
|
60
|
+
atis_schedule = Schedule.new
|
61
|
+
atis_schedule.groups = response.to_array(:point2point_response, :groups, :group).map do |group|
|
62
|
+
atis_schedule_group = ScheduleGroup.new
|
63
|
+
|
64
|
+
# Point2point 1.3 uses inconsistent tag naming, thus: <onstop> <onwalk...>, but <offstop> <offstopwalk...>
|
65
|
+
# this docs says this is fixed in 1.4, so watch out
|
66
|
+
atis_schedule_group.on_stop = atis_stop_from_hash 'on', group[:onstop]
|
67
|
+
atis_schedule_group.off_stop = atis_stop_from_hash 'offstop', group[:offstop]
|
68
|
+
|
69
|
+
atis_schedule_group.trips = group.to_array(:trips, :trip).map do |trip|
|
70
|
+
atis_trip = ScheduleTrip.new
|
71
|
+
atis_trip.on_time = trip[:ontime]
|
72
|
+
atis_trip.off_time = trip[:offtime]
|
73
|
+
|
74
|
+
atis_trip.service = trip.to_array(:service).map do |service|
|
75
|
+
atis_service = Service.new
|
76
|
+
|
77
|
+
atis_service.route = service[:route]
|
78
|
+
atis_service.direction = service[:direction]
|
79
|
+
atis_service.service_type = service[:servicetype]
|
80
|
+
atis_service.signage = service[:signage]
|
81
|
+
atis_service.route_type = service[:routetype]
|
82
|
+
atis_service.exception = service[:exception]
|
83
|
+
atis_service
|
84
|
+
end.first
|
85
|
+
|
86
|
+
atis_trip
|
87
|
+
end
|
88
|
+
|
89
|
+
atis_schedule_group
|
90
|
+
end
|
91
|
+
|
92
|
+
atis_schedule
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.atis_stop_from_hash(prefix, stop)
|
96
|
+
return nil if stop.blank?
|
97
|
+
|
98
|
+
atis_stop = Stop.new
|
99
|
+
atis_stop.description = stop[:description]
|
100
|
+
atis_stop.atis_stop_id = stop[:atisstopid].to_i
|
101
|
+
atis_stop.latitude = stop[:lat].to_f
|
102
|
+
atis_stop.longitude = stop[:long].to_f
|
103
|
+
|
104
|
+
# It appears that both *walk and *walkdist are used for the walk distance, covering both here
|
105
|
+
atis_stop.walk_dist = (stop["#{prefix}walk".to_sym] || stop["#{prefix}walkdist".to_sym]).to_f
|
106
|
+
atis_stop.walk_dir = stop["#{prefix}walkdir".to_sym]
|
107
|
+
atis_stop.walk_hint = stop["#{prefix}walkhint".to_sym]
|
108
|
+
atis_stop
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Ratis
|
2
|
+
|
3
|
+
class Request
|
4
|
+
|
5
|
+
extend Savon::Model
|
6
|
+
|
7
|
+
def initialize(config = nil)
|
8
|
+
config = Ratis.config if config.nil?
|
9
|
+
raise Errors::ConfigError('It appears that Ratis.configure has not been called') unless config.valid?
|
10
|
+
self.class.client do
|
11
|
+
wsdl.endpoint = Ratis.config.endpoint
|
12
|
+
wsdl.namespace = Ratis.config.namespace
|
13
|
+
http.proxy = Ratis.config.proxy unless Ratis.config.proxy.blank?
|
14
|
+
http.open_timeout = Ratis.config.timeout unless Ratis.config.timeout.blank?
|
15
|
+
end
|
16
|
+
rescue ArgumentError => e
|
17
|
+
raise ArgumentError.new 'Invalid ATIS SOAP server configuration: ' + e.message
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.get(action, params = {})
|
21
|
+
begin
|
22
|
+
req = new Ratis.config
|
23
|
+
response = client.request action, :soap_action => "#{Ratis.config.namespace}##{action}", :xmlns => Ratis.config.namespace do
|
24
|
+
soap.body = params unless params.blank?
|
25
|
+
end
|
26
|
+
|
27
|
+
version = response.to_hash["#{action.downcase}_response".to_sym][:version]
|
28
|
+
|
29
|
+
response
|
30
|
+
rescue Errno::ECONNREFUSED => e
|
31
|
+
raise Errno::ECONNREFUSED.new 'Refused request to ATIS SOAP server'
|
32
|
+
rescue Savon::SOAP::Fault => e
|
33
|
+
raise Errors::SoapError.new e
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/ratis/route.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Ratis
|
2
|
+
|
3
|
+
class Route
|
4
|
+
|
5
|
+
attr_accessor :short_name, :directions
|
6
|
+
|
7
|
+
def self.all
|
8
|
+
response = Request.get 'Allroutes'
|
9
|
+
return [] unless response.success?
|
10
|
+
|
11
|
+
routes = response.to_hash[:allroutes_response][:routes].split(/\n/)
|
12
|
+
atis_routes = routes.map do |r|
|
13
|
+
r.strip!
|
14
|
+
next if r.blank?
|
15
|
+
r = r.split(/, /)
|
16
|
+
Route.new r[0].strip, r[1..-1].map(&:strip)
|
17
|
+
end
|
18
|
+
atis_routes.compact
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(short_name, directions)
|
22
|
+
self.short_name = short_name
|
23
|
+
self.directions = directions
|
24
|
+
end
|
25
|
+
|
26
|
+
def timetable(conditions)
|
27
|
+
Timetable.where conditions.merge :route_short_name => short_name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ratis
|
2
|
+
|
3
|
+
################################# EXAMPLE ############################################
|
4
|
+
#
|
5
|
+
# Ratis::RouteStops.all :route => 8, :direction => 'S', :order => 'S'
|
6
|
+
#
|
7
|
+
######################################################################################
|
8
|
+
|
9
|
+
class RouteStops
|
10
|
+
|
11
|
+
def self.all(conditions)
|
12
|
+
route = conditions.delete :route
|
13
|
+
direction = conditions.delete(:direction).to_s.upcase
|
14
|
+
order = conditions.delete(:order).to_s.upcase
|
15
|
+
|
16
|
+
raise ArgumentError.new('You must provide a route') unless route
|
17
|
+
raise ArgumentError.new('You must provide a direction') unless direction
|
18
|
+
|
19
|
+
Ratis.all_conditions_used? conditions
|
20
|
+
|
21
|
+
request_params = {'Route' => route, 'Direction' => direction }
|
22
|
+
request_params.merge! order ? { 'Order' => order } : {}
|
23
|
+
response = Request.get 'Routestops', request_params
|
24
|
+
|
25
|
+
return [] unless response.success?
|
26
|
+
|
27
|
+
response.to_hash[:routestops_response][:stops][:stop].map do |s|
|
28
|
+
stop = Stop.new
|
29
|
+
stop.description = s[:description]
|
30
|
+
stop.area = s[:area]
|
31
|
+
stop.atis_stop_id = s[:atisstopid]
|
32
|
+
stop.stop_seq = s[:stopseq]
|
33
|
+
stop.latitude, stop.longitude = s[:point].split ','
|
34
|
+
stop
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Ratis
|
2
|
+
|
3
|
+
class ScheduleNearby
|
4
|
+
|
5
|
+
attr_accessor :atstops
|
6
|
+
|
7
|
+
def self.where(conditions)
|
8
|
+
latitude = conditions.delete :latitude
|
9
|
+
longitude = conditions.delete :longitude
|
10
|
+
date = conditions.delete :date
|
11
|
+
time = conditions.delete :time
|
12
|
+
window = conditions.delete :window
|
13
|
+
walk_distance = conditions.delete :walk_distance
|
14
|
+
landmark_id = conditions.delete :landmark_id
|
15
|
+
stop_id = conditions.delete(:stop_id) || ''
|
16
|
+
app_id = conditions.delete(:app_id) || 'na'
|
17
|
+
|
18
|
+
raise ArgumentError.new('You must provide latitude') unless latitude
|
19
|
+
raise ArgumentError.new('You must provide longitude') unless longitude
|
20
|
+
raise ArgumentError.new('You must provide date') unless date
|
21
|
+
raise ArgumentError.new('You must provide time') unless time
|
22
|
+
raise ArgumentError.new('You must provide window') unless window
|
23
|
+
raise ArgumentError.new('You must provide walk_distance') unless walk_distance
|
24
|
+
raise ArgumentError.new('You must provide landmark_id') unless landmark_id
|
25
|
+
Ratis.all_conditions_used? conditions
|
26
|
+
|
27
|
+
response = Request.get 'Schedulenearby',
|
28
|
+
{'Locationlat' => latitude, 'Locationlong' => longitude,
|
29
|
+
'Date' => date, 'Time' => time, 'Window' => window, 'Walkdist' => walk_distance,
|
30
|
+
'Landmarkid' => landmark_id, 'Stopid' => stop_id, 'Appid' => app_id
|
31
|
+
}
|
32
|
+
|
33
|
+
return [] unless response.success?
|
34
|
+
|
35
|
+
atstops = response.to_array :schedulenearby_response, :atstop
|
36
|
+
atstops.map do |atstop|
|
37
|
+
atstop[:services] = atstop.to_array :service
|
38
|
+
|
39
|
+
atstop[:services].map do |service|
|
40
|
+
service[:tripinfos] = service.to_array :tripinfo
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
schedule_nearby = ScheduleNearby.new
|
45
|
+
schedule_nearby.atstops = atstops
|
46
|
+
|
47
|
+
schedule_nearby
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_hash
|
51
|
+
{
|
52
|
+
:atstops => atstops.map do |atstop|
|
53
|
+
{
|
54
|
+
:description => atstop[:description],
|
55
|
+
:walkdist => atstop[:walkdist],
|
56
|
+
:walkdir => atstop[:walkdir],
|
57
|
+
:stopid => atstop[:stopid],
|
58
|
+
:lat => atstop[:lat],
|
59
|
+
:long => atstop[:long],
|
60
|
+
:services => atstop[:services].map do |service|
|
61
|
+
{
|
62
|
+
:route => service[:route],
|
63
|
+
:routetype => service[:routetype],
|
64
|
+
:operator => service[:operator],
|
65
|
+
:sign => service[:sign],
|
66
|
+
:times => service[:times],
|
67
|
+
:trips => service[:tripinfos].map do |tripinfo|
|
68
|
+
{ :triptime => tripinfo[:triptime] }
|
69
|
+
end
|
70
|
+
}
|
71
|
+
end
|
72
|
+
}
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|