eaal 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,9 @@
1
+ == 0.1.5 2009-04-10
2
+ * 1 fix:
3
+ * code cleanup by JamesHarrison
4
+ * New Feature:
5
+ * memcached handler by JamesHarrison
6
+
1
7
  == 0.1.4 2009-04-02
2
8
 
3
9
  * 1 fix:
@@ -1,19 +1,22 @@
1
1
  History.txt
2
+ LICENSE.txt
2
3
  Manifest.txt
3
4
  README.rdoc
4
- LICENSE.txt
5
5
  Rakefile
6
6
  lib/eaal.rb
7
- lib/eaal/eaal.rb
8
- lib/eaal/eaal_cache.rb
9
- lib/eaal/eaal_exception.rb
10
- lib/eaal/eaal_result.rb
11
- lib/eaal/eaal_rowset.rb
7
+ lib/eaal/api.rb
8
+ lib/eaal/cache/base.rb
9
+ lib/eaal/cache/file.rb
10
+ lib/eaal/cache/memcached.rb
11
+ lib/eaal/exception.rb
12
+ lib/eaal/result.rb
13
+ lib/eaal/rowset.rb
12
14
  script/console
13
15
  script/destroy
14
16
  script/generate
15
- test/test_helper.rb
16
- test/test_eaal.rb
17
17
  test/fixtures/test/test/account/Characters/Request_.xml
18
- test/fixtures/test/test/char/Killlog/Request_characterID:12345.xml
19
18
  test/fixtures/test/test/char/Killlog/Request_.xml
19
+ test/fixtures/test/test/char/Killlog/Request_characterID:12345.xml
20
+ test/fixtures/test/test/eve/AllianceList/Request_.xml
21
+ test/test_eaal.rb
22
+ test/test_helper.rb
@@ -88,6 +88,10 @@ the XML file will be load.
88
88
 
89
89
  * sudo gem install eaal
90
90
 
91
+ == THANKS:
92
+ special thanks go to James "Ix_Forres" Harrison for his code cleanups and
93
+ his memcache cache handler
94
+
91
95
  == LICENSE:
92
96
 
93
97
  (The MIT License)
@@ -111,4 +115,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
111
115
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
112
116
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
113
117
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
114
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
118
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,27 +1,27 @@
1
- %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
- require File.dirname(__FILE__) + '/lib/eaal.rb'
3
-
4
- # Generate all the Rake tasks
5
- # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
- $hoe = Hoe.new('eaal', EAAL.version) do |p|
7
- p.developer('Peter Petermann', 'PeterPetermann@gmx.net')
8
- p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
- p.rubyforge_name = p.name # TODO this is default value
10
- p.extra_deps = [
11
- ['activesupport','>= 2.0.2'], ['hpricot', '>= 0.6']
12
- ]
13
- p.extra_dev_deps = [
14
- ['newgem', ">= #{::Newgem::VERSION}"]
15
- ]
16
-
17
- p.clean_globs |= %w[**/.DS_Store tmp *.log]
18
- path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
19
- p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
20
- p.rsync_args = '-av --delete --ignore-errors'
21
- end
22
-
23
- require 'newgem/tasks' # load /tasks/*.rake
24
- Dir['tasks/**/*.rake'].each { |t| load t }
25
-
26
- # TODO - want other tests/tasks run by default? Add them to the list
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/eaal.rb'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('eaal', EAAL.version) do |p|
7
+ p.developer('Peter Petermann', 'PeterPetermann@gmx.net')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.rubyforge_name = p.name # TODO this is default value
10
+ p.extra_deps = [
11
+ ['activesupport','>= 2.0.2'], ['hpricot', '>= 0.6'], ['memcache-client','>= 1.7.1']
12
+ ]
13
+ p.extra_dev_deps = [
14
+ ['newgem', ">= #{::Newgem::VERSION}"]
15
+ ]
16
+
17
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
18
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
19
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
20
+ p.rsync_args = '-av --delete --ignore-errors'
21
+ end
22
+
23
+ require 'newgem/tasks' # load /tasks/*.rake
24
+ Dir['tasks/**/*.rake'].each { |t| load t }
25
+
26
+ # TODO - want other tests/tasks run by default? Add them to the list
27
27
  # task :default => [:spec, :features]
@@ -1,6 +1,45 @@
1
- path = File.dirname(__FILE__) + '/eaal/'
2
- require path + 'eaal_cache.rb'
3
- require path + 'eaal_exception.rb'
4
- require path + 'eaal_result.rb'
5
- require path + 'eaal_rowset.rb'
6
- require path + 'eaal.rb'
1
+ #--
2
+ # EAAL by Peter Petermann <PeterPetermann@gmx.net>
3
+ # This library is licensed under the terms found in
4
+ # the LICENSE file distributed with it
5
+ #
6
+ # TODO:
7
+ # - more documenation
8
+ # - write tests (i know, i know, i fail badly)
9
+ # - more error handling (im certain i missed a few possibles)
10
+ # - cleanup (you can see that this is my first project in ruby, cant you?)
11
+ #
12
+ # THANKS:
13
+ # thanks go to all people on irc.coldfront.net, channel #eve-dev
14
+ # special thanks go to lisa (checkout her eve api library, reve,
15
+ # much more mature then mine) for answering my endless questions
16
+ # about ruby stuff (and for one or two snippets i stole from reve)
17
+ #++
18
+ # Neat little hack to get around path issues on require
19
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
20
+
21
+ # External libs
22
+ require 'rubygems'
23
+ require 'hpricot'
24
+ require 'activesupport'
25
+ require 'net/http'
26
+ require 'uri'
27
+ require 'cgi'
28
+ # And now EAAL stuff
29
+ require 'eaal/cache/base'
30
+ require 'eaal/cache/file'
31
+ require 'eaal/cache/memcached'
32
+ require 'eaal/exception'
33
+ require 'eaal/result'
34
+ require 'eaal/rowset'
35
+ module EAAL
36
+ mattr_reader :version_string, :version
37
+ @@version = "0.1.5"
38
+ @@version_string = "EAAL" + EAAL.version # the version string, used as client name in http requests
39
+
40
+ mattr_accessor :api_base, :additional_request_parameters, :cache
41
+ @@api_base = "http://api.eve-online.com/" # the url used as basis for all requests, you might want to use gatecamper url or a personal proxy instead
42
+ @@additional_request_parameters = {} # hash, if :key => value pairs are added those will be added to each request
43
+ @@cache = EAAL::Cache::NoCache.new # caching object, see EAAL::Cache::FileCache for an Example
44
+ end
45
+ require 'eaal/api'
@@ -0,0 +1,77 @@
1
+
2
+ # EAAL::API class
3
+ # Usage Example:
4
+ # api = EAAL::API.new("my userid", "my API key")
5
+ # result = api.Characters
6
+ # result.characters.each{|character|
7
+ # puts character.name
8
+ # }
9
+ class EAAL::API
10
+ attr_accessor :userid, :key, :scope
11
+
12
+ # constructor
13
+ # Expects:
14
+ # * userid (String | Integer) the users id
15
+ # * key (String) the apikey Full or Restricted
16
+ # * scope (String) defaults to account
17
+ def initialize(userid, key, scope="account")
18
+ self.userid = userid.to_s
19
+ self.key = key.to_s
20
+ self.scope = scope.to_s
21
+ end
22
+
23
+ # create an xml request according to the method called
24
+ # this is used to dynamicaly create api calls and
25
+ # should usually not be called directly
26
+ # * method (const)
27
+ # * args
28
+ def method_missing(method, *args)
29
+ scope = self.scope
30
+ args_hash = args.first
31
+ args_hash = {} unless args_hash
32
+ self.request_xml(scope, method.id2name, args_hash)
33
+ end
34
+
35
+ # make a request to the api. will use cache if set.
36
+ # usually not called by the user directly
37
+ # * scope (String)
38
+ # * name (String)
39
+ # * opts (Hash)
40
+ def request_xml(scope, name, opts)
41
+ opts = EAAL.additional_request_parameters.merge(opts)
42
+ xml = EAAL.cache.load(self.userid, self.key, scope, name,opts)
43
+ if not xml
44
+ source = URI.parse(EAAL.api_base + scope + '/' + name +'.xml.aspx')
45
+ req_path = source.path + format_url_request(opts.merge({
46
+ :userid => self.userid,
47
+ :apikey => self.key}))
48
+ req = Net::HTTP::Get.new(req_path)
49
+ req[EAAL.version_string]
50
+ res = Net::HTTP.new(source.host, source.port).start {|http| http.request(req) } #one request for now
51
+ case res
52
+ when Net::HTTPOK
53
+ when Net::HTTPNotFound
54
+ raise EAAL::Exception::APINotFoundError.new("The requested API (#{scope} / #{name}) could not be found.")
55
+ else
56
+ raise EAAL::Exception::HTTPError.new("An HTTP Error occured, body: " + res.body)
57
+ end
58
+ EAAL.cache.save(self.userid, self.key, scope,name,opts, res.body)
59
+ xml = res.body
60
+ end
61
+ doc = Hpricot.XML(xml)
62
+ result = EAAL::Result.new(scope.capitalize + name, doc)
63
+ end
64
+
65
+ # Turns a hash into ?var=baz&bam=boo
66
+ # stolen from Reve (thx lisa)
67
+ # * opts (Hash)
68
+ def format_url_request(opts)
69
+ req = "?"
70
+ opts.stringify_keys!
71
+ opts.keys.sort.each do |key|
72
+ req += "#{CGI.escape(key.to_s)}=#{CGI.escape(opts[key].to_s)}&" if opts[key]
73
+ end
74
+ req.chop # We are lazy and append a & to each pair even if it's the last one. FIXME: Don't do this.
75
+ end
76
+
77
+ end
@@ -0,0 +1,27 @@
1
+ #--
2
+ # EAAL by Peter Petermann <PeterPetermann@gmx.net>
3
+ # This library is licensed under the terms found in
4
+ # the LICENSE file distributed with it
5
+ #++
6
+ require 'fileutils'
7
+
8
+ module EAAL
9
+ # The Classes in this module are objects that may be used as value
10
+ # of EAAL.cache.
11
+ # By default EAAL uses the NoCache class, where no caching is done.
12
+ # If a working cache class is used it will store the xml data
13
+ # and return it, so no requests to the API are done (as long as valid xml is available)
14
+ module Cache
15
+ # NoCache class
16
+ # dummy class which is used for non-caching behaviour (default)
17
+ class NoCache
18
+ def load(userid, apikey, scope, name, args)
19
+ false
20
+ end
21
+ def save(userid, apikey, scope, name, args, xml)
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+
@@ -0,0 +1,62 @@
1
+ # EAAL::Cache::FileCache
2
+ # File based xml cache which respects the cachedUntil of the Eve API
3
+ # Usage:
4
+ # EAAL.cache = EAAL::Cache::FileCache.new
5
+ # Or
6
+ # EAAL.cache = EAAL::Cache::FileCache.new("/path/to/place/to/store/xml/data")
7
+ class EAAL::Cache::FileCache
8
+ attr_accessor :basepath
9
+
10
+ # constructor, takes one argument which is the path
11
+ # where files should be written
12
+ # * basepath (String) path which should be used to store cached data. defaults to $HOME/.eaal/cache/
13
+ def initialize(basepath = "#{ENV['HOME']}/.eaal/cache")
14
+ if basepath[(basepath.length) -1, basepath.length] != "/"
15
+ basepath += "/"
16
+ end
17
+ @basepath = basepath
18
+ end
19
+
20
+ # create the path/filename for the cache file
21
+ def filename(userid, apikey, scope, name, args)
22
+ ret =""
23
+ args.delete_if { |k,v| (v || "").to_s.length == 0 }
24
+ h = args.stringify_keys
25
+ ret += h.sort.flatten.collect{ |e| e.to_s }.join(':')
26
+ hash = ret.gsub(/:$/,'')
27
+ "#{@basepath}#{userid}/#{apikey}/#{scope}/#{name}/Request_#{hash}.xml"
28
+ end
29
+
30
+ # load xml if available, return false if not available, or cachedUntil ran out
31
+ def load(userid, apikey, scope, name, args)
32
+ filename = self.filename(userid, apikey,scope,name,args)
33
+ if not File.exist?(filename)
34
+ ret = false
35
+ else
36
+ xml = File.open(filename).read
37
+ if self.validate_cache(xml, name)
38
+ ret = xml
39
+ else
40
+ ret = false
41
+ end
42
+ end
43
+ ret
44
+ end
45
+
46
+ # validate cached datas cachedUntil
47
+ def validate_cache(xml, name)
48
+ doc = Hpricot.XML(xml)
49
+ if name == "WalletJournal"
50
+ Time.at((doc/"/eveapi/cachedUntil").inner_html.to_time.to_i + 3600) > Time.now
51
+ else
52
+ (doc/"/eveapi/cachedUntil").inner_html.to_time > Time.now
53
+ end
54
+ end
55
+
56
+ # save xml data to file
57
+ def save(userid, apikey, scope, name, args, xml)
58
+ filename = self.filename(userid, apikey,scope,name,args)
59
+ FileUtils.mkdir_p(File.dirname(filename))
60
+ File.open(filename,'w') { |f| f.print xml }
61
+ end
62
+ end
@@ -0,0 +1,30 @@
1
+ # EAAL::Cache::Memcached
2
+ # Cache class to allow use of Memcached server(s) as a local cache option.
3
+ require 'memcache'
4
+ class EAAL::Cache::MemcachedCache
5
+
6
+ def initialize(servers="localhost:11211",options={})
7
+ o = {:namespace => 'eaal'}.merge(options)
8
+ $cache = MemCache.new(servers,o)
9
+ end
10
+ # Returns the memcached key for a given set of args.
11
+ # Does not use the full API key string as the risk of a collision is astronomically high.
12
+ def key(userid, apikey, scope, name, args)
13
+ "#{userid}#{apikey[0..25]}#{scope}#{name}#{args}"
14
+ end
15
+ # Saves to cache. It is worth noting that memcached handles expiry for us unlike FileCache
16
+ # as a result, there is no requirement for a validate_cache method- we just get MC to expire
17
+ # the key when we can go get a new copy.
18
+ def save(userid, apikey, scope, name, args, xml)
19
+ k = key(userid, apikey, scope, name, args)
20
+ cached_until = xml.match(/<cachedUntil>(.+)<\/cachedUntil>/)[1].to_time
21
+ expires_in = (name=='WalletJournal' ? cached_until.to_i+3600 : cached_until.to_i )-Time.now.to_i
22
+ $cache.delete(k)
23
+ $cache.add(k,xml,expires_in)
24
+ end
25
+ # Loads from the cache if there's a value for it.
26
+ def load(userid, apikey, scope, name, args)
27
+ k = key(userid, apikey, scope, name, args)
28
+ ($cache.get(k,xml,expires_in) or false) rescue false
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ #--
2
+ # EAAL by Peter Petermann <PeterPetermann@gmx.net>
3
+ # This library is licensed under the terms found in
4
+ # the LICENSE file distributed with it
5
+ #++
6
+ module EAAL::Exception
7
+ # creates the class for an EveAPIException
8
+ def self.EveAPIException(nr)
9
+ classname = "EveAPIException#{nr}"
10
+ if not Object.const_defined? classname
11
+ klass = Object.const_set(classname, Class.new(EAAL::Exception::EveAPIException))
12
+ else
13
+ klass = Object.const_get(classname)
14
+ end
15
+ klass
16
+ end
17
+
18
+ # raise the eve API exceptions, class will be dynamicaly created by classname
19
+ # EveAPIException followed by the APIs exception Number
20
+ def self.raiseEveAPIException(nr, msg)
21
+ raise EAAL::Exception.EveAPIException(nr).new(msg)
22
+ end
23
+
24
+ # all EAAL exceptions should extend this.
25
+ class EAALError < StandardError
26
+ end
27
+
28
+ # Used when an http error is encountered
29
+ class HTTPError < EAALError
30
+ end
31
+
32
+ # Used when the Eve API returns a 404
33
+ class APINotFoundError < HTTPError
34
+ end
35
+
36
+ # All API Errors should be derived from this
37
+ class EveAPIException < EAALError
38
+ end
39
+
40
+ end
@@ -1,123 +1,123 @@
1
- #--
2
- # EAAL by Peter Petermann <PeterPetermann@gmx.net>
3
- # This library is licensed under the terms found in
4
- # the LICENSE file distributed with it
5
- #++
6
- module EAAL
7
-
8
- module Result
9
-
10
- # base class for automated result class creation
11
- class ResultBase
12
- attr_accessor :request_time, :cached_until
13
- end
14
-
15
- # Result Container class, ...
16
- class ResultContainer
17
- attr_accessor :container
18
-
19
- def initialize
20
- self.container = {}
21
- end
22
-
23
- def add_element(key, val)
24
- self.container.merge!({key => val})
25
- end
26
-
27
- def method_missing(method, *args)
28
- self.container[method.id2name]
29
- end
30
- end
31
-
32
- # result element
33
- class ResultElement
34
- attr_accessor :name, :value, :attribs
35
- def initialize(name, value)
36
- self.name = name
37
- self.value = value
38
- self.attribs = {}
39
- end
40
-
41
- def add_attrib(key, val)
42
- self.attribs.merge!({key => val})
43
- end
44
-
45
- def method_missing(method, *args)
46
- if self.attribs.has_key?(method.id2name)
47
- self.attribs[method.id2name]
48
- else
49
- self.value.send(method, *args)
50
- end
51
-
52
- end
53
-
54
- # parses an xml element to create either the ResultElement, ResultContainer or Rowset
55
- # necessary
56
- def self.parse_element(prefix, element)
57
- if element.name == "rowset"
58
- re = EAAL::Rowset.new(prefix, element)
59
- else
60
- key = element.name
61
- if element.children && element.containers.length > 0
62
- container = ResultContainer.new
63
- element.containers.each { |celement|
64
- cel = EAAL::Result::ResultElement.parse_element(prefix, celement)
65
- if element.attributes.length > 0
66
- container.add_element(cel.name, cel)
67
- else
68
- container.add_element(cel.name, cel.value)
69
- end
70
- }
71
- value = container
72
- else
73
- value = element.inner_html
74
- end
75
- re = ResultElement.new(key, value)
76
- if element.attributes.length > 0
77
- re.attribs.merge!(element.attributes)
78
- end
79
- end
80
- re
81
- end
82
- end
83
-
84
- # create a new result derived from ResultBase
85
- def self.new(prefix, xml)
86
- classname = prefix + 'Result'
87
- members = []
88
- values = {}
89
- if (xml/"eveapi/error").length > 0
90
- error = (xml/"eveapi/error").first
91
- raise EAAL::Exception.raiseEveAPIException(error["code"], error.inner_html)
92
- end
93
- if (xml/"eveapi/result").length < 1
94
- raise EAAL::Exception::EAALError.new("Unknown API error, no result element was found")
95
- end
96
- elements = (xml/"eveapi/result").first.containers
97
- elements.each {|element|
98
- el = EAAL::Result::ResultElement.parse_element(prefix, element)
99
- members << el.name
100
- if el.kind_of? EAAL::Rowset::RowsetBase
101
- values.merge!({el.name => el})
102
- else
103
- values.merge!({el.name => el.value})
104
- end
105
- }
106
- if not Object.const_defined? classname
107
- klass = Object.const_set(classname, Class.new(EAAL::Result::ResultBase))
108
- klass.class_eval do
109
- attr_accessor *members
110
- end
111
- else
112
- klass = Object.const_get(classname)
113
- end
114
- result = klass.new
115
- result.request_time = (xml/"eveapi/currentTime").first.inner_html
116
- result.cached_until = (xml/"eveapi/cachedUntil").first.inner_html
117
- values.each { |key,value|
118
- result.send(key + "=", value)
119
- }
120
- result
121
- end
122
- end
123
- end
1
+ #--
2
+ # EAAL by Peter Petermann <PeterPetermann@gmx.net>
3
+ # This library is licensed under the terms found in
4
+ # the LICENSE file distributed with it
5
+ #++
6
+ module EAAL
7
+
8
+ module Result
9
+
10
+ # base class for automated result class creation
11
+ class ResultBase
12
+ attr_accessor :request_time, :cached_until
13
+ end
14
+
15
+ # Result Container class, ...
16
+ class ResultContainer
17
+ attr_accessor :container
18
+
19
+ def initialize
20
+ self.container = {}
21
+ end
22
+
23
+ def add_element(key, val)
24
+ self.container.merge!({key => val})
25
+ end
26
+
27
+ def method_missing(method, *args)
28
+ self.container[method.id2name]
29
+ end
30
+ end
31
+
32
+ # result element
33
+ class ResultElement
34
+ attr_accessor :name, :value, :attribs
35
+ def initialize(name, value)
36
+ self.name = name
37
+ self.value = value
38
+ self.attribs = {}
39
+ end
40
+
41
+ def add_attrib(key, val)
42
+ self.attribs.merge!({key => val})
43
+ end
44
+
45
+ def method_missing(method, *args)
46
+ if self.attribs.has_key?(method.id2name)
47
+ self.attribs[method.id2name]
48
+ else
49
+ self.value.send(method, *args)
50
+ end
51
+
52
+ end
53
+
54
+ # parses an xml element to create either the ResultElement, ResultContainer or Rowset
55
+ # necessary
56
+ def self.parse_element(prefix, element)
57
+ if element.name == "rowset"
58
+ re = EAAL::Rowset.new(prefix, element)
59
+ else
60
+ key = element.name
61
+ if element.children && element.containers.length > 0
62
+ container = ResultContainer.new
63
+ element.containers.each { |celement|
64
+ cel = EAAL::Result::ResultElement.parse_element(prefix, celement)
65
+ if element.attributes.length > 0
66
+ container.add_element(cel.name, cel)
67
+ else
68
+ container.add_element(cel.name, cel.value)
69
+ end
70
+ }
71
+ value = container
72
+ else
73
+ value = element.inner_html
74
+ end
75
+ re = ResultElement.new(key, value)
76
+ if element.attributes.length > 0
77
+ re.attribs.merge!(element.attributes)
78
+ end
79
+ end
80
+ re
81
+ end
82
+ end
83
+
84
+ # create a new result derived from ResultBase
85
+ def self.new(prefix, xml)
86
+ classname = prefix + 'Result'
87
+ members = []
88
+ values = {}
89
+ if (xml/"eveapi/error").length > 0
90
+ error = (xml/"eveapi/error").first
91
+ raise EAAL::Exception.raiseEveAPIException(error["code"], error.inner_html)
92
+ end
93
+ if (xml/"eveapi/result").length < 1
94
+ raise EAAL::Exception::EAALError.new("Unknown API error, no result element was found")
95
+ end
96
+ elements = (xml/"eveapi/result").first.containers
97
+ elements.each {|element|
98
+ el = EAAL::Result::ResultElement.parse_element(prefix, element)
99
+ members << el.name
100
+ if el.kind_of? EAAL::Rowset::RowsetBase
101
+ values.merge!({el.name => el})
102
+ else
103
+ values.merge!({el.name => el.value})
104
+ end
105
+ }
106
+ if not Object.const_defined? classname
107
+ klass = Object.const_set(classname, Class.new(EAAL::Result::ResultBase))
108
+ klass.class_eval do
109
+ attr_accessor(*members)
110
+ end
111
+ else
112
+ klass = Object.const_get(classname)
113
+ end
114
+ result = klass.new
115
+ result.request_time = (xml/"eveapi/currentTime").first.inner_html
116
+ result.cached_until = (xml/"eveapi/cachedUntil").first.inner_html
117
+ values.each { |key,value|
118
+ result.send(key + "=", value)
119
+ }
120
+ result
121
+ end
122
+ end
123
+ end