api_object 0.0.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/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +115 -0
- data/Rakefile +16 -0
- data/api_object.gemspec +33 -0
- data/lib/api_object/config_params.rb +23 -0
- data/lib/api_object/query.rb +20 -0
- data/lib/api_object/version.rb +3 -0
- data/lib/api_object.rb +95 -0
- data/test/data/estimate/glen.xml +251 -0
- data/test/data/estimate/sixteenth.xml +246 -0
- data/test/data/estimate/twenty_fourth.xml +233 -0
- data/test/unit/api_object_test.rb +42 -0
- data/test/unit/load_from_xml.rb +10 -0
- data/test/unit/station.rb +67 -0
- metadata +128 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 TJ Moskun
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Initialize an object from an external API
|
2
|
+
|
3
|
+
There is a number of external APIs, which provide data over XML or JSON. For example, BART system API http://api.bart.gov/docs/overview/index.aspx. This gem is to designed to load the external API data into a set of nested Ruby objects.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
gem install api_object
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
### Example - BART train departure estimate
|
12
|
+
|
13
|
+
1) Subclass your objects from ActiveApi::ApiObject
|
14
|
+
|
15
|
+
```
|
16
|
+
class Station < ActiveApi::ApiObject
|
17
|
+
end
|
18
|
+
|
19
|
+
class Estimate < ActiveApi::ApiObject
|
20
|
+
end
|
21
|
+
|
22
|
+
class Station < ActiveApi::ApiObject
|
23
|
+
end
|
24
|
+
```
|
25
|
+
2) Specify the url to load the data from, optionally a command, an api key and parameters(options) for the url; such as the url would look like "http://\<api_url\>/\<command\>?key=\<api_key\>&\<parameter1=value1¶meter2=value2...\>".
|
26
|
+
|
27
|
+
This will be defined in the upper object over the function "initialize_from_api". Options for this function:
|
28
|
+
|
29
|
+
```
|
30
|
+
:url - specify url
|
31
|
+
|
32
|
+
:command - specify command
|
33
|
+
|
34
|
+
:key - api key
|
35
|
+
|
36
|
+
:url_options - parameters
|
37
|
+
```
|
38
|
+
|
39
|
+
|
40
|
+
The following is designed to generate real time departure estimates for BART stations:
|
41
|
+
|
42
|
+
```
|
43
|
+
class Station
|
44
|
+
|
45
|
+
initialize_from_api :url => "http://api.bart.gov/api/", :command => 'etd.aspx', :key => 'MW9S-E7SL-26DU-VV8V', :url_options => {:cmd => 'etd'}
|
46
|
+
|
47
|
+
end
|
48
|
+
```
|
49
|
+
In this example, the url generated to get real time departure estimates from the Richmond station will be:
|
50
|
+
http://api.bart.gov/api/etd.aspx?cmd=etd&orig=RICH&key=MW9S-E7SL-26DU-VV8V
|
51
|
+
|
52
|
+
3) Define class attributes and mapping of the attributes to the api where the api name is different. To define api simple type mappings, use "api_column \<attribute name\>, \<api attribute name\>".
|
53
|
+
To define api association mapping, use "api_association \<association attribute name\>, \<api attribute name\>, :as => \<association class name\>". Either the second, or the third parameters could be omitted. If the third parameter is omitted, it's mapped to the class name by the attribute name defined in the class.
|
54
|
+
|
55
|
+
In the following example, a simple attribute name is "abbreviation", but the name defined in the api XML documents is "abbr". An association is defined in the attribute :est, the api mapping is :etd and it's an object of the class Estimate.
|
56
|
+
|
57
|
+
```
|
58
|
+
class Station < ActiveApi::ApiObject
|
59
|
+
|
60
|
+
initialize_from_api :url => "http://api.bart.gov/api/", :command => 'etd.aspx', :key => 'MW9S-E7SL-26DU-VV8V', :url_options => {:cmd => 'etd'}
|
61
|
+
|
62
|
+
attr_reader :name, :abbreviation, :date, :time, :est
|
63
|
+
|
64
|
+
api_column :abbreviation, :abbr
|
65
|
+
api_association :est, :etd, :as => Estimate
|
66
|
+
|
67
|
+
end
|
68
|
+
```
|
69
|
+
4) To load api data into an object, use the class method "get_results(options)". In the example, get real time estimates for the station Glen Park.
|
70
|
+
|
71
|
+
```
|
72
|
+
data = Station.get_results(:orig => 'GLEN')
|
73
|
+
```
|
74
|
+
|
75
|
+
Please, note that data loaded might be either a hash, or an array of hashes, depending on the api.
|
76
|
+
|
77
|
+
5) Create an object from the data
|
78
|
+
|
79
|
+
If the example, the data received is a hash, so create an object.
|
80
|
+
|
81
|
+
```
|
82
|
+
station = Station.new(data)
|
83
|
+
```
|
84
|
+
|
85
|
+
If the data is an array of hashes, then it might be used to create an array of objects
|
86
|
+
|
87
|
+
```
|
88
|
+
stations = data.map {|d| Station.new(d)}
|
89
|
+
```
|
90
|
+
|
91
|
+
6) Limitations
|
92
|
+
|
93
|
+
* Api data must be presented either in XML or in JSON format. The distinction between XML and JSON is determinted automatically.
|
94
|
+
* This is still a very early version and needs more testing. If something is not working, feel free to submit bugs and or/contribute.
|
95
|
+
|
96
|
+
## Compatibility
|
97
|
+
|
98
|
+
Ruby 1.9.3
|
99
|
+
|
100
|
+
## License
|
101
|
+
|
102
|
+
MIT License. Copyright 2012 TJ Moskun.
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
desc 'Run tests'
|
4
|
+
task :test do
|
5
|
+
result = system("ruby -Ilib -Itest -e 'ARGV.each { |f| load f }' test/unit/*")
|
6
|
+
exit(result ? 0 : 1)
|
7
|
+
end
|
8
|
+
|
9
|
+
task :ci => [:test]
|
10
|
+
task :default => [:test]
|
11
|
+
|
12
|
+
namespace :pre_commit do
|
13
|
+
desc "run the tests"
|
14
|
+
task :ci => [:test]
|
15
|
+
end
|
16
|
+
|
data/api_object.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "api_object/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "api_object"
|
7
|
+
s.version = ApiObject::VERSION
|
8
|
+
s.authors = ["tmoskun"]
|
9
|
+
s.email = ["tanyamoskun@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/tmoskun/api_object"
|
11
|
+
s.summary = %q{An interface to load objects from external APIs provided in XML and JSON formats}
|
12
|
+
s.description = %q{An interface to load objects from external APIs provided in XML and JSON formats}
|
13
|
+
|
14
|
+
s.rubyforge_project = "api_object"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
|
25
|
+
#s.add_development_dependency "sqlite3"
|
26
|
+
s.add_development_dependency "activemodel"
|
27
|
+
s.add_development_dependency "rspec"
|
28
|
+
s.add_development_dependency "minitest"
|
29
|
+
s.add_dependency "activesupport"
|
30
|
+
s.add_dependency('nori', '>=1.1')
|
31
|
+
s.add_dependency('rest-client', '>= 1.6')
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ConfigParams
|
2
|
+
|
3
|
+
IPV4_REGEXP = /\A(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)(?:\.(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)){3}\z/
|
4
|
+
TIMEOUT = 3
|
5
|
+
FALLBACK_TIMEOUT = 3
|
6
|
+
|
7
|
+
def timeout
|
8
|
+
TIMEOUT
|
9
|
+
end
|
10
|
+
|
11
|
+
def timeout= timeout
|
12
|
+
self.TIMEOUT = timeout
|
13
|
+
end
|
14
|
+
|
15
|
+
def fallback_timeout
|
16
|
+
FALLBACK_TIMEOUT
|
17
|
+
end
|
18
|
+
|
19
|
+
def fallback_timeout= fallback_timeout
|
20
|
+
self.FALLBACK_TIMEOUT = fallback_timeout
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'nori'
|
3
|
+
require 'rest-client'
|
4
|
+
require_relative 'config_params'
|
5
|
+
|
6
|
+
module Query
|
7
|
+
include ConfigParams
|
8
|
+
|
9
|
+
def query_api url, command = nil, url_options = {}, options = {}
|
10
|
+
result = nil
|
11
|
+
cmd_url = url + (command.nil? ? '':"#{command}?")
|
12
|
+
request_url = cmd_url + url_options.to_a.map{|opt| opt[0].to_s + "=" + opt[1]}.join("&")
|
13
|
+
Timeout.timeout(self.fallback_timeout) do
|
14
|
+
result_raw = RestClient::Request.execute(:method => :get, :url => request_url, :timeout => self.timeout)
|
15
|
+
result = result_raw.start_with?('<?xml') ? Nori.parse(result_raw) : JSON.parse(result_raw)
|
16
|
+
end
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/api_object.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require "api_object/version"
|
2
|
+
require "api_object/query"
|
3
|
+
require "active_support/all"
|
4
|
+
|
5
|
+
module ActiveApi
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def self.extended(base)
|
10
|
+
base.send(:extend, Query)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_from_api options = {}
|
14
|
+
class_attribute :url, :command, :key, :url_options, :object_name
|
15
|
+
self.url, self.command, self.key, self.url_options, self.object_name = [options[:url], options[:command], options[:key], (options[:url_options] || {}), (options[:object_name] || self.to_s.downcase.gsub(/^(.+::)(.+)$/, '\2'))]
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_results options = {}
|
19
|
+
self.url_options.merge!(:key => self.key) unless self.key.nil?
|
20
|
+
result = query_api(self.url, self.command, self.url_options.merge(options))
|
21
|
+
other_keys = {}
|
22
|
+
unless result.nil?
|
23
|
+
until result.keys.include?(self.object_name.to_s)
|
24
|
+
result = result[result.keys[0]]
|
25
|
+
return nil if (result.nil? || result.empty? || result.instance_of?(Array))
|
26
|
+
other_keys.merge!(result.keys.inject({}) { |h, k| (self.method_defined?(k) ? h.merge(k => result[k]) : h)})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
other_keys.merge(result[self.object_name.to_s])
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
module InstanceMethods
|
35
|
+
|
36
|
+
def initialize(*args)
|
37
|
+
args.first.each do |k, v|
|
38
|
+
unless defined?(k).nil? # check if it's included as a reader attribute
|
39
|
+
k = self.columns[k.to_sym] unless self.columns[k.to_sym].nil?
|
40
|
+
klass = self.assoc[k.to_sym] || k
|
41
|
+
result = v.instance_of?(Array) ? v.inject([]) {|arr, v1| arr << init_object(v1, klass)} : init_object(v, klass)
|
42
|
+
instance_variable_set("@#{k.to_s}", result)
|
43
|
+
end
|
44
|
+
end if (args.length == 1 && args.first.is_a?(Hash))
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def persisted?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def init_object value, klass
|
56
|
+
if value.instance_of?(Hash)
|
57
|
+
klass = (get_module_name + klass.to_s.capitalize).constantize unless klass.class == Class
|
58
|
+
klass.new(value)
|
59
|
+
else
|
60
|
+
value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_module_name
|
65
|
+
(self.class.name =~ /^(.+::).+$/) ? $1 : ''
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
module SingletonMethods
|
71
|
+
def api_column column_name, api_name
|
72
|
+
self.columns.merge!(api_name => column_name)
|
73
|
+
end
|
74
|
+
|
75
|
+
def api_association (*args)
|
76
|
+
options = args.extract_options!
|
77
|
+
self.columns.merge!(args[1] => args[0]) if args.length >= 2
|
78
|
+
self.assoc.merge!(args[0] => options[:as]) unless options[:as].nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
class ApiObject
|
84
|
+
extend ClassMethods
|
85
|
+
include InstanceMethods
|
86
|
+
extend SingletonMethods
|
87
|
+
|
88
|
+
class_attribute :columns, :assoc
|
89
|
+
self.columns, self.assoc = [{}, {}]
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
end
|
95
|
+
|
@@ -0,0 +1,251 @@
|
|
1
|
+
<root>
|
2
|
+
<uri>
|
3
|
+
<![CDATA[ http://api.bart.gov/api/etd.aspx?cmd=etd&orig=GLEN ]]>
|
4
|
+
</uri>
|
5
|
+
<date>03/29/2012</date>
|
6
|
+
<time>06:02:01 PM PDT</time>
|
7
|
+
<station>
|
8
|
+
<name>Glen Park</name>
|
9
|
+
<abbr>GLEN</abbr>
|
10
|
+
<etd>
|
11
|
+
<destination>Daly City</destination>
|
12
|
+
<abbreviation>DALY</abbreviation>
|
13
|
+
<estimate>
|
14
|
+
<minutes>3</minutes>
|
15
|
+
<platform>1</platform>
|
16
|
+
<direction>South</direction>
|
17
|
+
<length>9</length>
|
18
|
+
<color>GREEN</color>
|
19
|
+
<hexcolor>#339933</hexcolor>
|
20
|
+
<bikeflag>1</bikeflag>
|
21
|
+
</estimate>
|
22
|
+
<estimate>
|
23
|
+
<minutes>10</minutes>
|
24
|
+
<platform>1</platform>
|
25
|
+
<direction>South</direction>
|
26
|
+
<length>8</length>
|
27
|
+
<color>BLUE</color>
|
28
|
+
<hexcolor>#0099cc</hexcolor>
|
29
|
+
<bikeflag>0</bikeflag>
|
30
|
+
</estimate>
|
31
|
+
<estimate>
|
32
|
+
<minutes>17</minutes>
|
33
|
+
<platform>1</platform>
|
34
|
+
<direction>South</direction>
|
35
|
+
<length>9</length>
|
36
|
+
<color>GREEN</color>
|
37
|
+
<hexcolor>#339933</hexcolor>
|
38
|
+
<bikeflag>1</bikeflag>
|
39
|
+
</estimate>
|
40
|
+
</etd>
|
41
|
+
<etd>
|
42
|
+
<destination>Dublin/Pleasanton</destination>
|
43
|
+
<abbreviation>DUBL</abbreviation>
|
44
|
+
<estimate>
|
45
|
+
<minutes>11</minutes>
|
46
|
+
<platform>2</platform>
|
47
|
+
<direction>North</direction>
|
48
|
+
<length>8</length>
|
49
|
+
<color>BLUE</color>
|
50
|
+
<hexcolor>#0099cc</hexcolor>
|
51
|
+
<bikeflag>1</bikeflag>
|
52
|
+
</estimate>
|
53
|
+
<estimate>
|
54
|
+
<minutes>27</minutes>
|
55
|
+
<platform>2</platform>
|
56
|
+
<direction>North</direction>
|
57
|
+
<length>8</length>
|
58
|
+
<color>BLUE</color>
|
59
|
+
<hexcolor>#0099cc</hexcolor>
|
60
|
+
<bikeflag>1</bikeflag>
|
61
|
+
</estimate>
|
62
|
+
<estimate>
|
63
|
+
<minutes>41</minutes>
|
64
|
+
<platform>2</platform>
|
65
|
+
<direction>North</direction>
|
66
|
+
<length>8</length>
|
67
|
+
<color>BLUE</color>
|
68
|
+
<hexcolor>#0099cc</hexcolor>
|
69
|
+
<bikeflag>1</bikeflag>
|
70
|
+
</estimate>
|
71
|
+
</etd>
|
72
|
+
<etd>
|
73
|
+
<destination>Fremont</destination>
|
74
|
+
<abbreviation>FRMT</abbreviation>
|
75
|
+
<estimate>
|
76
|
+
<minutes>4</minutes>
|
77
|
+
<platform>2</platform>
|
78
|
+
<direction>North</direction>
|
79
|
+
<length>8</length>
|
80
|
+
<color>GREEN</color>
|
81
|
+
<hexcolor>#339933</hexcolor>
|
82
|
+
<bikeflag>1</bikeflag>
|
83
|
+
</estimate>
|
84
|
+
<estimate>
|
85
|
+
<minutes>18</minutes>
|
86
|
+
<platform>2</platform>
|
87
|
+
<direction>North</direction>
|
88
|
+
<length>9</length>
|
89
|
+
<color>GREEN</color>
|
90
|
+
<hexcolor>#339933</hexcolor>
|
91
|
+
<bikeflag>1</bikeflag>
|
92
|
+
</estimate>
|
93
|
+
<estimate>
|
94
|
+
<minutes>33</minutes>
|
95
|
+
<platform>2</platform>
|
96
|
+
<direction>North</direction>
|
97
|
+
<length>9</length>
|
98
|
+
<color>GREEN</color>
|
99
|
+
<hexcolor>#339933</hexcolor>
|
100
|
+
<bikeflag>1</bikeflag>
|
101
|
+
</estimate>
|
102
|
+
</etd>
|
103
|
+
<etd>
|
104
|
+
<destination>Millbrae</destination>
|
105
|
+
<abbreviation>MLBR</abbreviation>
|
106
|
+
<estimate>
|
107
|
+
<minutes>6</minutes>
|
108
|
+
<platform>1</platform>
|
109
|
+
<direction>South</direction>
|
110
|
+
<length>9</length>
|
111
|
+
<color>RED</color>
|
112
|
+
<hexcolor>#ff0000</hexcolor>
|
113
|
+
<bikeflag>0</bikeflag>
|
114
|
+
</estimate>
|
115
|
+
<estimate>
|
116
|
+
<minutes>15</minutes>
|
117
|
+
<platform>1</platform>
|
118
|
+
<direction>South</direction>
|
119
|
+
<length>8</length>
|
120
|
+
<color>RED</color>
|
121
|
+
<hexcolor>#ff0000</hexcolor>
|
122
|
+
<bikeflag>1</bikeflag>
|
123
|
+
</estimate>
|
124
|
+
<estimate>
|
125
|
+
<minutes>28</minutes>
|
126
|
+
<platform>1</platform>
|
127
|
+
<direction>South</direction>
|
128
|
+
<length>10</length>
|
129
|
+
<color>RED</color>
|
130
|
+
<hexcolor>#ff0000</hexcolor>
|
131
|
+
<bikeflag>1</bikeflag>
|
132
|
+
</estimate>
|
133
|
+
</etd>
|
134
|
+
<etd>
|
135
|
+
<destination>Pittsburg/Bay Point</destination>
|
136
|
+
<abbreviation>PITT</abbreviation>
|
137
|
+
<estimate>
|
138
|
+
<minutes>13</minutes>
|
139
|
+
<platform>2</platform>
|
140
|
+
<direction>North</direction>
|
141
|
+
<length>9</length>
|
142
|
+
<color>YELLOW</color>
|
143
|
+
<hexcolor>#ffff33</hexcolor>
|
144
|
+
<bikeflag>1</bikeflag>
|
145
|
+
</estimate>
|
146
|
+
<estimate>
|
147
|
+
<minutes>28</minutes>
|
148
|
+
<platform>2</platform>
|
149
|
+
<direction>North</direction>
|
150
|
+
<length>10</length>
|
151
|
+
<color>YELLOW</color>
|
152
|
+
<hexcolor>#ffff33</hexcolor>
|
153
|
+
<bikeflag>1</bikeflag>
|
154
|
+
</estimate>
|
155
|
+
<estimate>
|
156
|
+
<minutes>43</minutes>
|
157
|
+
<platform>2</platform>
|
158
|
+
<direction>North</direction>
|
159
|
+
<length>9</length>
|
160
|
+
<color>YELLOW</color>
|
161
|
+
<hexcolor>#ffff33</hexcolor>
|
162
|
+
<bikeflag>1</bikeflag>
|
163
|
+
</estimate>
|
164
|
+
</etd>
|
165
|
+
<etd>
|
166
|
+
<destination>Richmond</destination>
|
167
|
+
<abbreviation>RICH</abbreviation>
|
168
|
+
<estimate>
|
169
|
+
<minutes>13</minutes>
|
170
|
+
<platform>2</platform>
|
171
|
+
<direction>North</direction>
|
172
|
+
<length>10</length>
|
173
|
+
<color>RED</color>
|
174
|
+
<hexcolor>#ff0000</hexcolor>
|
175
|
+
<bikeflag>1</bikeflag>
|
176
|
+
</estimate>
|
177
|
+
<estimate>
|
178
|
+
<minutes>25</minutes>
|
179
|
+
<platform>2</platform>
|
180
|
+
<direction>North</direction>
|
181
|
+
<length>10</length>
|
182
|
+
<color>RED</color>
|
183
|
+
<hexcolor>#ff0000</hexcolor>
|
184
|
+
<bikeflag>1</bikeflag>
|
185
|
+
</estimate>
|
186
|
+
<estimate>
|
187
|
+
<minutes>37</minutes>
|
188
|
+
<platform>2</platform>
|
189
|
+
<direction>North</direction>
|
190
|
+
<length>10</length>
|
191
|
+
<color>RED</color>
|
192
|
+
<hexcolor>#ff0000</hexcolor>
|
193
|
+
<bikeflag>1</bikeflag>
|
194
|
+
</estimate>
|
195
|
+
</etd>
|
196
|
+
<etd>
|
197
|
+
<destination>SF Airport</destination>
|
198
|
+
<abbreviation>SFIA</abbreviation>
|
199
|
+
<estimate>
|
200
|
+
<minutes>8</minutes>
|
201
|
+
<platform>1</platform>
|
202
|
+
<direction>South</direction>
|
203
|
+
<length>10</length>
|
204
|
+
<color>YELLOW</color>
|
205
|
+
<hexcolor>#ffff33</hexcolor>
|
206
|
+
<bikeflag>1</bikeflag>
|
207
|
+
</estimate>
|
208
|
+
<estimate>
|
209
|
+
<minutes>21</minutes>
|
210
|
+
<platform>1</platform>
|
211
|
+
<direction>South</direction>
|
212
|
+
<length>10</length>
|
213
|
+
<color>YELLOW</color>
|
214
|
+
<hexcolor>#ffff33</hexcolor>
|
215
|
+
<bikeflag>1</bikeflag>
|
216
|
+
</estimate>
|
217
|
+
</etd>
|
218
|
+
<etd>
|
219
|
+
<destination>SFO/Millbrae</destination>
|
220
|
+
<abbreviation>MLBR</abbreviation>
|
221
|
+
<estimate>
|
222
|
+
<minutes>35</minutes>
|
223
|
+
<platform>1</platform>
|
224
|
+
<direction>South</direction>
|
225
|
+
<length>10</length>
|
226
|
+
<color>YELLOW</color>
|
227
|
+
<hexcolor>#ffff33</hexcolor>
|
228
|
+
<bikeflag>1</bikeflag>
|
229
|
+
</estimate>
|
230
|
+
<estimate>
|
231
|
+
<minutes>50</minutes>
|
232
|
+
<platform>1</platform>
|
233
|
+
<direction>South</direction>
|
234
|
+
<length>9</length>
|
235
|
+
<color>YELLOW</color>
|
236
|
+
<hexcolor>#ffff33</hexcolor>
|
237
|
+
<bikeflag>1</bikeflag>
|
238
|
+
</estimate>
|
239
|
+
<estimate>
|
240
|
+
<minutes>65</minutes>
|
241
|
+
<platform>1</platform>
|
242
|
+
<direction>South</direction>
|
243
|
+
<length>9</length>
|
244
|
+
<color>YELLOW</color>
|
245
|
+
<hexcolor>#ffff33</hexcolor>
|
246
|
+
<bikeflag>1</bikeflag>
|
247
|
+
</estimate>
|
248
|
+
</etd>
|
249
|
+
</station>
|
250
|
+
<message/>
|
251
|
+
</root>
|
@@ -0,0 +1,246 @@
|
|
1
|
+
<root>
|
2
|
+
<uri>
|
3
|
+
<![CDATA[ http://api.bart.gov/api/etd.aspx?cmd=etd&orig=16TH ]]>
|
4
|
+
</uri>
|
5
|
+
<date>03/29/2012</date>
|
6
|
+
<time>06:08:30 PM PDT</time>
|
7
|
+
<station>
|
8
|
+
<name>16th St. Mission</name>
|
9
|
+
<abbr>16TH</abbr>
|
10
|
+
<etd>
|
11
|
+
<destination>24th Street</destination>
|
12
|
+
<abbreviation>24TH</abbreviation>
|
13
|
+
<estimate>
|
14
|
+
<minutes>2</minutes>
|
15
|
+
<platform>1</platform>
|
16
|
+
<direction>South</direction>
|
17
|
+
<length>9</length>
|
18
|
+
<color>YELLOW</color>
|
19
|
+
<hexcolor>#ffff33</hexcolor>
|
20
|
+
<bikeflag>0</bikeflag>
|
21
|
+
</estimate>
|
22
|
+
</etd>
|
23
|
+
<etd>
|
24
|
+
<destination>Daly City</destination>
|
25
|
+
<abbreviation>DALY</abbreviation>
|
26
|
+
<estimate>
|
27
|
+
<minutes>Leaving</minutes>
|
28
|
+
<platform>1</platform>
|
29
|
+
<direction>South</direction>
|
30
|
+
<length>8</length>
|
31
|
+
<color>BLUE</color>
|
32
|
+
<hexcolor>#0099cc</hexcolor>
|
33
|
+
<bikeflag>0</bikeflag>
|
34
|
+
</estimate>
|
35
|
+
<estimate>
|
36
|
+
<minutes>6</minutes>
|
37
|
+
<platform>1</platform>
|
38
|
+
<direction>South</direction>
|
39
|
+
<length>9</length>
|
40
|
+
<color>GREEN</color>
|
41
|
+
<hexcolor>#339933</hexcolor>
|
42
|
+
<bikeflag>0</bikeflag>
|
43
|
+
</estimate>
|
44
|
+
<estimate>
|
45
|
+
<minutes>13</minutes>
|
46
|
+
<platform>1</platform>
|
47
|
+
<direction>South</direction>
|
48
|
+
<length>8</length>
|
49
|
+
<color>BLUE</color>
|
50
|
+
<hexcolor>#0099cc</hexcolor>
|
51
|
+
<bikeflag>0</bikeflag>
|
52
|
+
</estimate>
|
53
|
+
</etd>
|
54
|
+
<etd>
|
55
|
+
<destination>Dublin/Pleasanton</destination>
|
56
|
+
<abbreviation>DUBL</abbreviation>
|
57
|
+
<estimate>
|
58
|
+
<minutes>10</minutes>
|
59
|
+
<platform>2</platform>
|
60
|
+
<direction>North</direction>
|
61
|
+
<length>8</length>
|
62
|
+
<color>BLUE</color>
|
63
|
+
<hexcolor>#0099cc</hexcolor>
|
64
|
+
<bikeflag>1</bikeflag>
|
65
|
+
</estimate>
|
66
|
+
<estimate>
|
67
|
+
<minutes>24</minutes>
|
68
|
+
<platform>2</platform>
|
69
|
+
<direction>North</direction>
|
70
|
+
<length>8</length>
|
71
|
+
<color>BLUE</color>
|
72
|
+
<hexcolor>#0099cc</hexcolor>
|
73
|
+
<bikeflag>1</bikeflag>
|
74
|
+
</estimate>
|
75
|
+
<estimate>
|
76
|
+
<minutes>39</minutes>
|
77
|
+
<platform>2</platform>
|
78
|
+
<direction>North</direction>
|
79
|
+
<length>8</length>
|
80
|
+
<color>BLUE</color>
|
81
|
+
<hexcolor>#0099cc</hexcolor>
|
82
|
+
<bikeflag>1</bikeflag>
|
83
|
+
</estimate>
|
84
|
+
</etd>
|
85
|
+
<etd>
|
86
|
+
<destination>Fremont</destination>
|
87
|
+
<abbreviation>FRMT</abbreviation>
|
88
|
+
<estimate>
|
89
|
+
<minutes>3</minutes>
|
90
|
+
<platform>2</platform>
|
91
|
+
<direction>North</direction>
|
92
|
+
<length>8</length>
|
93
|
+
<color>GREEN</color>
|
94
|
+
<hexcolor>#339933</hexcolor>
|
95
|
+
<bikeflag>1</bikeflag>
|
96
|
+
</estimate>
|
97
|
+
<estimate>
|
98
|
+
<minutes>16</minutes>
|
99
|
+
<platform>2</platform>
|
100
|
+
<direction>North</direction>
|
101
|
+
<length>9</length>
|
102
|
+
<color>GREEN</color>
|
103
|
+
<hexcolor>#339933</hexcolor>
|
104
|
+
<bikeflag>1</bikeflag>
|
105
|
+
</estimate>
|
106
|
+
<estimate>
|
107
|
+
<minutes>31</minutes>
|
108
|
+
<platform>2</platform>
|
109
|
+
<direction>North</direction>
|
110
|
+
<length>9</length>
|
111
|
+
<color>GREEN</color>
|
112
|
+
<hexcolor>#339933</hexcolor>
|
113
|
+
<bikeflag>1</bikeflag>
|
114
|
+
</estimate>
|
115
|
+
</etd>
|
116
|
+
<etd>
|
117
|
+
<destination>Millbrae</destination>
|
118
|
+
<abbreviation>MLBR</abbreviation>
|
119
|
+
<estimate>
|
120
|
+
<minutes>4</minutes>
|
121
|
+
<platform>1</platform>
|
122
|
+
<direction>South</direction>
|
123
|
+
<length>8</length>
|
124
|
+
<color>RED</color>
|
125
|
+
<hexcolor>#ff0000</hexcolor>
|
126
|
+
<bikeflag>0</bikeflag>
|
127
|
+
</estimate>
|
128
|
+
<estimate>
|
129
|
+
<minutes>18</minutes>
|
130
|
+
<platform>1</platform>
|
131
|
+
<direction>South</direction>
|
132
|
+
<length>10</length>
|
133
|
+
<color>RED</color>
|
134
|
+
<hexcolor>#ff0000</hexcolor>
|
135
|
+
<bikeflag>1</bikeflag>
|
136
|
+
</estimate>
|
137
|
+
</etd>
|
138
|
+
<etd>
|
139
|
+
<destination>Pittsburg/Bay Point</destination>
|
140
|
+
<abbreviation>PITT</abbreviation>
|
141
|
+
<estimate>
|
142
|
+
<minutes>6</minutes>
|
143
|
+
<platform>2</platform>
|
144
|
+
<direction>North</direction>
|
145
|
+
<length>9</length>
|
146
|
+
<color>YELLOW</color>
|
147
|
+
<hexcolor>#ffff33</hexcolor>
|
148
|
+
<bikeflag>1</bikeflag>
|
149
|
+
</estimate>
|
150
|
+
<estimate>
|
151
|
+
<minutes>12</minutes>
|
152
|
+
<platform>2</platform>
|
153
|
+
<direction>North</direction>
|
154
|
+
<length>9</length>
|
155
|
+
<color>YELLOW</color>
|
156
|
+
<hexcolor>#ffff33</hexcolor>
|
157
|
+
<bikeflag>1</bikeflag>
|
158
|
+
</estimate>
|
159
|
+
<estimate>
|
160
|
+
<minutes>28</minutes>
|
161
|
+
<platform>2</platform>
|
162
|
+
<direction>North</direction>
|
163
|
+
<length>10</length>
|
164
|
+
<color>YELLOW</color>
|
165
|
+
<hexcolor>#ffff33</hexcolor>
|
166
|
+
<bikeflag>1</bikeflag>
|
167
|
+
</estimate>
|
168
|
+
</etd>
|
169
|
+
<etd>
|
170
|
+
<destination>Richmond</destination>
|
171
|
+
<abbreviation>RICH</abbreviation>
|
172
|
+
<estimate>
|
173
|
+
<minutes>11</minutes>
|
174
|
+
<platform>2</platform>
|
175
|
+
<direction>North</direction>
|
176
|
+
<length>10</length>
|
177
|
+
<color>RED</color>
|
178
|
+
<hexcolor>#ff0000</hexcolor>
|
179
|
+
<bikeflag>1</bikeflag>
|
180
|
+
</estimate>
|
181
|
+
<estimate>
|
182
|
+
<minutes>20</minutes>
|
183
|
+
<platform>2</platform>
|
184
|
+
<direction>North</direction>
|
185
|
+
<length>10</length>
|
186
|
+
<color>RED</color>
|
187
|
+
<hexcolor>#ff0000</hexcolor>
|
188
|
+
<bikeflag>1</bikeflag>
|
189
|
+
</estimate>
|
190
|
+
<estimate>
|
191
|
+
<minutes>35</minutes>
|
192
|
+
<platform>2</platform>
|
193
|
+
<direction>North</direction>
|
194
|
+
<length>10</length>
|
195
|
+
<color>RED</color>
|
196
|
+
<hexcolor>#ff0000</hexcolor>
|
197
|
+
<bikeflag>1</bikeflag>
|
198
|
+
</estimate>
|
199
|
+
</etd>
|
200
|
+
<etd>
|
201
|
+
<destination>SF Airport</destination>
|
202
|
+
<abbreviation>SFIA</abbreviation>
|
203
|
+
<estimate>
|
204
|
+
<minutes>9</minutes>
|
205
|
+
<platform>1</platform>
|
206
|
+
<direction>South</direction>
|
207
|
+
<length>10</length>
|
208
|
+
<color>YELLOW</color>
|
209
|
+
<hexcolor>#ffff33</hexcolor>
|
210
|
+
<bikeflag>0</bikeflag>
|
211
|
+
</estimate>
|
212
|
+
</etd>
|
213
|
+
<etd>
|
214
|
+
<destination>SFO/Millbrae</destination>
|
215
|
+
<abbreviation>MLBR</abbreviation>
|
216
|
+
<estimate>
|
217
|
+
<minutes>24</minutes>
|
218
|
+
<platform>1</platform>
|
219
|
+
<direction>South</direction>
|
220
|
+
<length>10</length>
|
221
|
+
<color>YELLOW</color>
|
222
|
+
<hexcolor>#ffff33</hexcolor>
|
223
|
+
<bikeflag>1</bikeflag>
|
224
|
+
</estimate>
|
225
|
+
<estimate>
|
226
|
+
<minutes>39</minutes>
|
227
|
+
<platform>1</platform>
|
228
|
+
<direction>South</direction>
|
229
|
+
<length>9</length>
|
230
|
+
<color>YELLOW</color>
|
231
|
+
<hexcolor>#ffff33</hexcolor>
|
232
|
+
<bikeflag>1</bikeflag>
|
233
|
+
</estimate>
|
234
|
+
<estimate>
|
235
|
+
<minutes>54</minutes>
|
236
|
+
<platform>1</platform>
|
237
|
+
<direction>South</direction>
|
238
|
+
<length>9</length>
|
239
|
+
<color>YELLOW</color>
|
240
|
+
<hexcolor>#ffff33</hexcolor>
|
241
|
+
<bikeflag>1</bikeflag>
|
242
|
+
</estimate>
|
243
|
+
</etd>
|
244
|
+
</station>
|
245
|
+
<message/>
|
246
|
+
</root>
|
@@ -0,0 +1,233 @@
|
|
1
|
+
<root>
|
2
|
+
<uri>
|
3
|
+
<![CDATA[ http://api.bart.gov/api/etd.aspx?cmd=etd&orig=24TH ]]>
|
4
|
+
</uri>
|
5
|
+
<date>03/29/2012</date>
|
6
|
+
<time>06:09:02 PM PDT</time>
|
7
|
+
<station>
|
8
|
+
<name>24th St. Mission</name>
|
9
|
+
<abbr>24TH</abbr>
|
10
|
+
<etd>
|
11
|
+
<destination>Daly City</destination>
|
12
|
+
<abbreviation>DALY</abbreviation>
|
13
|
+
<estimate>
|
14
|
+
<minutes>2</minutes>
|
15
|
+
<platform>1</platform>
|
16
|
+
<direction>South</direction>
|
17
|
+
<length>8</length>
|
18
|
+
<color>BLUE</color>
|
19
|
+
<hexcolor>#0099cc</hexcolor>
|
20
|
+
<bikeflag>0</bikeflag>
|
21
|
+
</estimate>
|
22
|
+
<estimate>
|
23
|
+
<minutes>8</minutes>
|
24
|
+
<platform>1</platform>
|
25
|
+
<direction>South</direction>
|
26
|
+
<length>9</length>
|
27
|
+
<color>GREEN</color>
|
28
|
+
<hexcolor>#339933</hexcolor>
|
29
|
+
<bikeflag>1</bikeflag>
|
30
|
+
</estimate>
|
31
|
+
<estimate>
|
32
|
+
<minutes>15</minutes>
|
33
|
+
<platform>1</platform>
|
34
|
+
<direction>South</direction>
|
35
|
+
<length>8</length>
|
36
|
+
<color>BLUE</color>
|
37
|
+
<hexcolor>#0099cc</hexcolor>
|
38
|
+
<bikeflag>0</bikeflag>
|
39
|
+
</estimate>
|
40
|
+
</etd>
|
41
|
+
<etd>
|
42
|
+
<destination>Dublin/Pleasanton</destination>
|
43
|
+
<abbreviation>DUBL</abbreviation>
|
44
|
+
<estimate>
|
45
|
+
<minutes>8</minutes>
|
46
|
+
<platform>2</platform>
|
47
|
+
<direction>North</direction>
|
48
|
+
<length>8</length>
|
49
|
+
<color>BLUE</color>
|
50
|
+
<hexcolor>#0099cc</hexcolor>
|
51
|
+
<bikeflag>1</bikeflag>
|
52
|
+
</estimate>
|
53
|
+
<estimate>
|
54
|
+
<minutes>22</minutes>
|
55
|
+
<platform>2</platform>
|
56
|
+
<direction>North</direction>
|
57
|
+
<length>8</length>
|
58
|
+
<color>BLUE</color>
|
59
|
+
<hexcolor>#0099cc</hexcolor>
|
60
|
+
<bikeflag>1</bikeflag>
|
61
|
+
</estimate>
|
62
|
+
<estimate>
|
63
|
+
<minutes>37</minutes>
|
64
|
+
<platform>2</platform>
|
65
|
+
<direction>North</direction>
|
66
|
+
<length>8</length>
|
67
|
+
<color>BLUE</color>
|
68
|
+
<hexcolor>#0099cc</hexcolor>
|
69
|
+
<bikeflag>1</bikeflag>
|
70
|
+
</estimate>
|
71
|
+
</etd>
|
72
|
+
<etd>
|
73
|
+
<destination>Fremont</destination>
|
74
|
+
<abbreviation>FRMT</abbreviation>
|
75
|
+
<estimate>
|
76
|
+
<minutes>14</minutes>
|
77
|
+
<platform>2</platform>
|
78
|
+
<direction>North</direction>
|
79
|
+
<length>9</length>
|
80
|
+
<color>GREEN</color>
|
81
|
+
<hexcolor>#339933</hexcolor>
|
82
|
+
<bikeflag>1</bikeflag>
|
83
|
+
</estimate>
|
84
|
+
<estimate>
|
85
|
+
<minutes>29</minutes>
|
86
|
+
<platform>2</platform>
|
87
|
+
<direction>North</direction>
|
88
|
+
<length>9</length>
|
89
|
+
<color>GREEN</color>
|
90
|
+
<hexcolor>#339933</hexcolor>
|
91
|
+
<bikeflag>1</bikeflag>
|
92
|
+
</estimate>
|
93
|
+
<estimate>
|
94
|
+
<minutes>44</minutes>
|
95
|
+
<platform>2</platform>
|
96
|
+
<direction>North</direction>
|
97
|
+
<length>8</length>
|
98
|
+
<color>GREEN</color>
|
99
|
+
<hexcolor>#339933</hexcolor>
|
100
|
+
<bikeflag>1</bikeflag>
|
101
|
+
</estimate>
|
102
|
+
</etd>
|
103
|
+
<etd>
|
104
|
+
<destination>Millbrae</destination>
|
105
|
+
<abbreviation>MLBR</abbreviation>
|
106
|
+
<estimate>
|
107
|
+
<minutes>5</minutes>
|
108
|
+
<platform>1</platform>
|
109
|
+
<direction>South</direction>
|
110
|
+
<length>8</length>
|
111
|
+
<color>RED</color>
|
112
|
+
<hexcolor>#ff0000</hexcolor>
|
113
|
+
<bikeflag>1</bikeflag>
|
114
|
+
</estimate>
|
115
|
+
<estimate>
|
116
|
+
<minutes>19</minutes>
|
117
|
+
<platform>1</platform>
|
118
|
+
<direction>South</direction>
|
119
|
+
<length>10</length>
|
120
|
+
<color>RED</color>
|
121
|
+
<hexcolor>#ff0000</hexcolor>
|
122
|
+
<bikeflag>1</bikeflag>
|
123
|
+
</estimate>
|
124
|
+
</etd>
|
125
|
+
<etd>
|
126
|
+
<destination>Pittsburg/Bay Point</destination>
|
127
|
+
<abbreviation>PITT</abbreviation>
|
128
|
+
<estimate>
|
129
|
+
<minutes>4</minutes>
|
130
|
+
<platform>2</platform>
|
131
|
+
<direction>North</direction>
|
132
|
+
<length>9</length>
|
133
|
+
<color>YELLOW</color>
|
134
|
+
<hexcolor>#ffff33</hexcolor>
|
135
|
+
<bikeflag>1</bikeflag>
|
136
|
+
</estimate>
|
137
|
+
<estimate>
|
138
|
+
<minutes>9</minutes>
|
139
|
+
<platform>2</platform>
|
140
|
+
<direction>North</direction>
|
141
|
+
<length>9</length>
|
142
|
+
<color>YELLOW</color>
|
143
|
+
<hexcolor>#ffff33</hexcolor>
|
144
|
+
<bikeflag>1</bikeflag>
|
145
|
+
</estimate>
|
146
|
+
<estimate>
|
147
|
+
<minutes>26</minutes>
|
148
|
+
<platform>2</platform>
|
149
|
+
<direction>North</direction>
|
150
|
+
<length>10</length>
|
151
|
+
<color>YELLOW</color>
|
152
|
+
<hexcolor>#ffff33</hexcolor>
|
153
|
+
<bikeflag>1</bikeflag>
|
154
|
+
</estimate>
|
155
|
+
</etd>
|
156
|
+
<etd>
|
157
|
+
<destination>Richmond</destination>
|
158
|
+
<abbreviation>RICH</abbreviation>
|
159
|
+
<estimate>
|
160
|
+
<minutes>8</minutes>
|
161
|
+
<platform>2</platform>
|
162
|
+
<direction>North</direction>
|
163
|
+
<length>10</length>
|
164
|
+
<color>RED</color>
|
165
|
+
<hexcolor>#ff0000</hexcolor>
|
166
|
+
<bikeflag>1</bikeflag>
|
167
|
+
</estimate>
|
168
|
+
<estimate>
|
169
|
+
<minutes>18</minutes>
|
170
|
+
<platform>2</platform>
|
171
|
+
<direction>North</direction>
|
172
|
+
<length>10</length>
|
173
|
+
<color>RED</color>
|
174
|
+
<hexcolor>#ff0000</hexcolor>
|
175
|
+
<bikeflag>1</bikeflag>
|
176
|
+
</estimate>
|
177
|
+
<estimate>
|
178
|
+
<minutes>33</minutes>
|
179
|
+
<platform>2</platform>
|
180
|
+
<direction>North</direction>
|
181
|
+
<length>10</length>
|
182
|
+
<color>RED</color>
|
183
|
+
<hexcolor>#ff0000</hexcolor>
|
184
|
+
<bikeflag>1</bikeflag>
|
185
|
+
</estimate>
|
186
|
+
</etd>
|
187
|
+
<etd>
|
188
|
+
<destination>SF Airport</destination>
|
189
|
+
<abbreviation>SFIA</abbreviation>
|
190
|
+
<estimate>
|
191
|
+
<minutes>11</minutes>
|
192
|
+
<platform>1</platform>
|
193
|
+
<direction>South</direction>
|
194
|
+
<length>10</length>
|
195
|
+
<color>YELLOW</color>
|
196
|
+
<hexcolor>#ffff33</hexcolor>
|
197
|
+
<bikeflag>1</bikeflag>
|
198
|
+
</estimate>
|
199
|
+
</etd>
|
200
|
+
<etd>
|
201
|
+
<destination>SFO/Millbrae</destination>
|
202
|
+
<abbreviation>MLBR</abbreviation>
|
203
|
+
<estimate>
|
204
|
+
<minutes>25</minutes>
|
205
|
+
<platform>1</platform>
|
206
|
+
<direction>South</direction>
|
207
|
+
<length>10</length>
|
208
|
+
<color>YELLOW</color>
|
209
|
+
<hexcolor>#ffff33</hexcolor>
|
210
|
+
<bikeflag>1</bikeflag>
|
211
|
+
</estimate>
|
212
|
+
<estimate>
|
213
|
+
<minutes>40</minutes>
|
214
|
+
<platform>1</platform>
|
215
|
+
<direction>South</direction>
|
216
|
+
<length>9</length>
|
217
|
+
<color>YELLOW</color>
|
218
|
+
<hexcolor>#ffff33</hexcolor>
|
219
|
+
<bikeflag>1</bikeflag>
|
220
|
+
</estimate>
|
221
|
+
<estimate>
|
222
|
+
<minutes>55</minutes>
|
223
|
+
<platform>1</platform>
|
224
|
+
<direction>South</direction>
|
225
|
+
<length>9</length>
|
226
|
+
<color>YELLOW</color>
|
227
|
+
<hexcolor>#ffff33</hexcolor>
|
228
|
+
<bikeflag>1</bikeflag>
|
229
|
+
</estimate>
|
230
|
+
</etd>
|
231
|
+
</station>
|
232
|
+
<message/>
|
233
|
+
</root>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative 'station'
|
3
|
+
|
4
|
+
class ApiObjectTest < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
include ActiveApi
|
7
|
+
include TestObjects
|
8
|
+
|
9
|
+
@@data_directory = File.expand_path('../../data', __FILE__)
|
10
|
+
@@estimate_directory = @@data_directory + "/estimate"
|
11
|
+
|
12
|
+
@@glen_estimate = Station.load_from_xml(File.read(@@estimate_directory + '/glen.xml'))
|
13
|
+
@@sixteenth_estimate = Station.load_from_xml(File.read(@@estimate_directory + '/sixteenth.xml'))
|
14
|
+
@@twenty_fourth_estimate = Station.load_from_xml(File.read(@@estimate_directory + '/twenty_fourth.xml'))
|
15
|
+
|
16
|
+
def test_should_get_correct_estimates
|
17
|
+
glen_estimate = Station.new(Station.get_results(:orig => 'GLEN'))
|
18
|
+
now = DateTime.strptime(glen_estimate.date + " " + glen_estimate.time, "%m/%d/%Y %I:%M:%S %p %Z")
|
19
|
+
if business_time? now
|
20
|
+
sixteenth_estimate = Station.new(Station.get_results(:orig => '16TH'))
|
21
|
+
twenty_fourth_estimate = Station.new(Station.get_results(:orig => '24TH'))
|
22
|
+
assert_equal(glen_estimate, @@glen_estimate)
|
23
|
+
assert_equal(sixteenth_estimate, @@sixteenth_estimate)
|
24
|
+
assert_equal(twenty_fourth_estimate, @@twenty_fourth_estimate)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
private
|
30
|
+
def business_time? now
|
31
|
+
unless now.to_date.sunday?
|
32
|
+
#t = now.to_time.localtime(now.zone)
|
33
|
+
#t1 = DateTime.strptime("#{now.month}/#{now.day}/#{now.year} 10:00:00 AM #{now.zone}", "%m/%d/%Y %I:%M:%S %p %:z").to_time.localtime(now.zone)
|
34
|
+
#t2 = DateTime.strptime("#{now.month}/#{now.day}/#{now.year} 08:00:00 PM #{now.zone}", "%m/%d/%Y %I:%M:%S %p %:z").to_time.localtime(now.zone)
|
35
|
+
t1 = DateTime.strptime("#{now.month}/#{now.day}/#{now.year} 10:00:00 AM #{now.zone}", "%m/%d/%Y %I:%M:%S %p %:z")
|
36
|
+
t2 = DateTime.strptime("#{now.month}/#{now.day}/#{now.year} 08:00:00 PM #{now.zone}", "%m/%d/%Y %I:%M:%S %p %:z")
|
37
|
+
return now > t1 && now < t2
|
38
|
+
end
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module LoadFromXML
|
2
|
+
def load_from_xml xml
|
3
|
+
hash = Nori.parse(xml)
|
4
|
+
hash = hash["root"] if hash.keys.include?("root")
|
5
|
+
timestamp = hash["date"].nil? ? {} : {"date" => hash["date"], "time" => hash["time"]}
|
6
|
+
hash = hash["stations"] if hash.keys.include?("stations")
|
7
|
+
hash = hash["station"] if hash.keys.include?("station")
|
8
|
+
new(timestamp.merge(hash))
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'api_object'
|
2
|
+
require_relative 'load_from_xml'
|
3
|
+
module TestObjects
|
4
|
+
|
5
|
+
module StationMethods
|
6
|
+
def to_s
|
7
|
+
inspect
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
class Departure < ActiveApi::ApiObject
|
13
|
+
include StationMethods
|
14
|
+
attr_reader :minutes, :platform, :direction, :length, :color, :bikes_allowed
|
15
|
+
|
16
|
+
api_column :bikes_allowed, :bikeflag
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
"#{@platform} platform - #{@minutes}#{(@minutes == 'Leaving') ? '':' minutes'}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(other_)
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
class Estimate < ActiveApi::ApiObject
|
29
|
+
include StationMethods
|
30
|
+
attr_reader :abbreviation, :destination, :estimate
|
31
|
+
|
32
|
+
api_association :estimate, :as => Departure
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
"destination #{@destination} \n\t\t #{@estimate.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(other_est)
|
39
|
+
return false if other_est.nil?
|
40
|
+
self.abbreviation == other_est.abbreviation && self.destination == other_est.destination
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class Station < ActiveApi::ApiObject
|
46
|
+
extend LoadFromXML
|
47
|
+
include StationMethods
|
48
|
+
|
49
|
+
attr_reader :name, :abbreviation, :date, :time, :est
|
50
|
+
|
51
|
+
initialize_from_api :url => "http://api.bart.gov/api/", :command => 'etd.aspx', :key => 'MW9S-E7SL-26DU-VV8V', :url_options => {:cmd => 'etd'}
|
52
|
+
|
53
|
+
api_column :abbreviation, :abbr
|
54
|
+
api_association :est, :etd, :as => TestObjects::Estimate
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"#{@name} at #{@date}, #{@time} \n\t #{@est.instance_of?(Array) ? (@est.inject('') {|str, e| (str + e.inspect + "\n\t")}) : @est}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def ==(other_station)
|
61
|
+
return false if other_station.nil?
|
62
|
+
self.abbreviation == other_station.abbreviation && self.est == other_station.est
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: api_object
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- tmoskun
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activemodel
|
16
|
+
requirement: &71094620 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *71094620
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70815040 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70815040
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest
|
38
|
+
requirement: &70814760 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70814760
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: activesupport
|
49
|
+
requirement: &70814480 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70814480
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: nori
|
60
|
+
requirement: &70814230 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '1.1'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70814230
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rest-client
|
71
|
+
requirement: &70813950 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '1.6'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70813950
|
80
|
+
description: An interface to load objects from external APIs provided in XML and JSON
|
81
|
+
formats
|
82
|
+
email:
|
83
|
+
- tanyamoskun@gmail.com
|
84
|
+
executables: []
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- Gemfile
|
89
|
+
- LICENSE
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- api_object.gemspec
|
93
|
+
- lib/api_object.rb
|
94
|
+
- lib/api_object/config_params.rb
|
95
|
+
- lib/api_object/query.rb
|
96
|
+
- lib/api_object/version.rb
|
97
|
+
- test/data/estimate/glen.xml
|
98
|
+
- test/data/estimate/sixteenth.xml
|
99
|
+
- test/data/estimate/twenty_fourth.xml
|
100
|
+
- test/unit/api_object_test.rb
|
101
|
+
- test/unit/load_from_xml.rb
|
102
|
+
- test/unit/station.rb
|
103
|
+
homepage: https://github.com/tmoskun/api_object
|
104
|
+
licenses: []
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project: api_object
|
123
|
+
rubygems_version: 1.8.17
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: An interface to load objects from external APIs provided in XML and JSON
|
127
|
+
formats
|
128
|
+
test_files: []
|