amazon-ecs-eb 2.2.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,111 @@
1
+ 2.2.6 2014-08-14
2
+ ----------------
3
+ * added exponential backoff (increased dependency).
4
+
5
+ 2.2.5 2014-03-08
6
+ ----------------
7
+ * Replaced Digest::Digest deprecated call
8
+ * Read AWS_ACCESS_KEY_ID and AWS_SECRET_KEY env variables when running test
9
+
10
+ 2.2.4 2012-01-01
11
+ ----------------
12
+ * Parse with UTF-8 encoding, happy new year :)
13
+
14
+ 2.2.3 2011-12-05
15
+ ----------------
16
+ * Add Amazon Spain API
17
+
18
+ 2.2.2 2011-11-18
19
+ ----------------
20
+ * Add VERSION constant in Amazon::Ecs
21
+ * Update dependent gems version in Gemfile
22
+ * Add Gemfile as part of the gem content
23
+ * Update amazon-ecs.gemspec
24
+
25
+ 2.2.1 2011-10-25
26
+ ----------------
27
+ * Add Gemfile
28
+ * Rake, run test task by default
29
+ * Rename README to README.rdoc
30
+ * Upate README, add search index information
31
+
32
+ 2.2.0 2011-9-06
33
+ ----------------
34
+ * Upgrade to Product Advertising API version to 2011-08-01
35
+ * Fix IT service url and add support for CN URL
36
+ * Add unit test for all service urls
37
+
38
+ 2.1.1 2011-09-05
39
+ ----------------
40
+ * Fix wrong JP service url
41
+ * Add Ecs.browser_node_lookup method
42
+
43
+ 2.1.0 2011-08-18
44
+ ----------------
45
+ * Update service urls
46
+ * Raise error when aws_secret_key is not found
47
+
48
+ 2.0.0 2011-05-09
49
+ ----------------
50
+ * Replace Hpricot parser with Nokogiri
51
+ * Element/Attribute retrieval to match XPath, name not automatically downcased as in Hpricot
52
+
53
+ 1.2.2 2011-05-07
54
+ ----------------
55
+ * Add marshal_dump and marshal_load support
56
+
57
+ 1.2.1 2011-02-23
58
+ ----------------
59
+ * Add support for Amazon Italy
60
+
61
+ 1.2.0 2011-02-07
62
+ ----------------
63
+ * Remove test/ecs_signature_test.rb and test/test_helper.rb
64
+ * Fix multibyte search on Ruby 1.9 returns HTTP Response: 403 Forbidden
65
+
66
+ 1.1.0 2010-11-11
67
+ ----------------
68
+ * Add get_elements, get_element, and attributes instance methods in Amazon::Element
69
+ * Deprecate Amazon::Element#search_and_convert
70
+
71
+ 1.0.0 2010-11-09
72
+ ----------------
73
+ * Set default Amazon API version to 2010-10-01
74
+
75
+ 0.5.7 2009-08-28
76
+ ----------------
77
+ * Added support for to sign request using openssl with fallback on ruby-hmac
78
+
79
+ 0.5.6 2009-07-21
80
+ ----------------
81
+ * Update parameter value encoding to support special characters
82
+
83
+ 0.5.5 2009-07-18
84
+ ----------------
85
+ * Sign request
86
+
87
+ 0.5.4 2008-01-02
88
+ ----------------
89
+ * Add Response#error_code
90
+
91
+ 0.5.3 2007-09-12
92
+ ----------------
93
+ * send_request to use default options.
94
+
95
+ 0.5.2 2007-09-08
96
+ ----------------
97
+ * Fixed Amazon::Element.get_unescaped error when result returned for given element path is nil
98
+
99
+ 0.5.1 2007-02-08
100
+ ----------------
101
+ * Fixed Amazon Japan and France URL error
102
+ * Removed opts.delete(:search_index) from item_lookup, SearchIndex param is allowed
103
+ when looking for a book with IdType other than the ASIN.
104
+ * Check for defined? RAILS_DEFAULT_LOGGER to avoid exception for non-rails ruby app
105
+ * Added check for LOGGER constant if RAILS_DEFAULT_LOGGER is not defined
106
+ * Added Ecs.configure(&proc) method for easier configuration of default options
107
+ * Added Element#search_and_convert method
108
+
109
+ 0.5.0 2006-09-12
110
+ ----------------
111
+ Initial Release
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'nokogiri', '~> 1.4'
4
+ gem 'ruby-hmac', '~> 0.3'
5
+ gem 'exponential-backoff', '~> 0.0.2'
6
+
7
+ group :dev do
8
+ gem 'rake'
9
+ end
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2011 Herryanto Siatono
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.
21
+
@@ -0,0 +1,24 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+ require 'rubygems/package_task'
5
+ require 'rake/packagetask'
6
+
7
+ desc "Create the RDOC html files"
8
+ rd = RDoc::Task.new { |rdoc|
9
+ rdoc.rdoc_dir = 'doc'
10
+ rdoc.title = "amazon-ecs"
11
+ rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'Readme.rdoc'
12
+ rdoc.rdoc_files.include('Readme.rdoc', 'CHANGELOG')
13
+ rdoc.rdoc_files.include('lib/**/*.rb')
14
+ rdoc.rdoc_files.include('test/**/*.rb')
15
+ }
16
+
17
+ desc "Run the unit tests in test"
18
+ Rake::TestTask.new(:test) do |t|
19
+ t.libs << "test"
20
+ t.pattern = 'test/**/*_test.rb'
21
+ t.verbose = true
22
+ end
23
+
24
+ task :default => :test
@@ -0,0 +1,141 @@
1
+ == amazon-ecs
2
+
3
+ Generic Product Advertising Ruby API using Nokogiri. Uses Response and
4
+ Element wrapper classes for easy access to the REST API XML output.
5
+
6
+ It is generic, so you can easily extend <tt>Amazon::Ecs</tt> to support
7
+ other not implemented REST operations; and it is also generic because it just wraps around
8
+ Nokogiri element object, instead of providing one-to-one object/attributes to XML elements map.
9
+
10
+ The idea is as the API evolves, there is a change in REST XML output structure,
11
+ no updates will be required on <tt>amazon-ecs</tt> gem,
12
+ instead you just need to update the element path.
13
+
14
+ For HPricot dependency implementation, please install 1.2.x version or checkout v1.2 branch.
15
+
16
+ == INSTALLATION
17
+
18
+ $ gem install amazon-ecs
19
+
20
+ == EXAMPLE
21
+
22
+ require 'amazon/ecs'
23
+
24
+ # Set the default options; options will be camelized and converted to REST request parameters.
25
+ # associate_tag and AWS_access_key_id are required options, associate_tag is required option
26
+ # since API version 2011-08-01.
27
+ #
28
+ # To sign your request, include AWS_secret_key.
29
+ Amazon::Ecs.options = {
30
+ :associate_tag => '[your associate tag]',
31
+ :AWS_access_key_id => '[your developer token]',
32
+ :AWS_secret_key => '[your secret access key]'
33
+ }
34
+
35
+ # alternatively,
36
+ Amazon::Ecs.configure do |options|
37
+ options[:associate_tag] = '[your associate tag]'
38
+ options[:AWS_access_key_id] = '[your access key]'
39
+ options[:AWS_secret_key] = '[you secret key]'
40
+ end
41
+
42
+ # options provided on method call will merge with the default options
43
+ res = Amazon::Ecs.item_search('ruby', {:response_group => 'Medium', :sort => 'salesrank'})
44
+
45
+ # search amazon uk
46
+ res = Amazon::Ecs.item_search('ruby', :country => 'uk')
47
+
48
+ # search all items, default search index is Books
49
+ res = Amazon::Ecs.item_search('ruby', :search_index => 'All')
50
+
51
+ # some common response object methods
52
+ res.is_valid_request? # return true if request is valid
53
+ res.has_error? # return true if there is an error
54
+ res.error # return error message if there is any
55
+ res.total_pages # return total pages
56
+ res.total_results # return total results
57
+ res.item_page # return current page no if :item_page option is provided
58
+
59
+ # traverse through each item (Amazon::Element)
60
+ res.items.each do |item|
61
+ # retrieve string value using XML path
62
+ item.get('ASIN')
63
+ item.get('ItemAttributes/Title')
64
+
65
+ # return Amazon::Element instance
66
+ item_attributes = item.get_element('ItemAttributes')
67
+ item_attributes.get('Title')
68
+
69
+ # return first author or a string array of authors
70
+ item_attributes.get('Author') # 'Author 1'
71
+ item_attributes.get_array('Author') # ['Author 1', 'Author 2', ...]
72
+
73
+ # return an hash of children text values with the element names as the keys
74
+ item.get_hash('SmallImage') # {:url => ..., :width => ..., :height => ...}
75
+
76
+ # return the first matching path as Amazon::Element
77
+ item_height = item.get_element('ItemDimensions/Height')
78
+
79
+ # retrieve attributes from Amazon::Element
80
+ item_height.attributes['Units'] # 'hundredths-inches'
81
+
82
+ # return an array of Amazon::Element
83
+ authors = item.get_elements('Author')
84
+
85
+ # return Nokogiri::XML::NodeSet object or nil if not found
86
+ reviews = item/'EditorialReview'
87
+
88
+ # traverse through Nokogiri elements
89
+ reviews.each do |review|
90
+ # Getting hash value out of Nokogiri element
91
+ Amazon::Element.get_hash(review) # [:source => ..., :content ==> ...]
92
+
93
+ # Or to get unescaped HTML values
94
+ Amazon::Element.get_unescaped(review, 'Source')
95
+ Amazon::Element.get_unescaped(review, 'Content')
96
+
97
+ # Or this way
98
+ el = Amazon::Element.new(review)
99
+ el.get_unescaped('Source')
100
+ el.get_unescaped('Content')
101
+ end
102
+ end
103
+
104
+ # Extend Amazon::Ecs, replace 'other_operation' with the appropriate name
105
+ module Amazon
106
+ class Ecs
107
+ def self.other_operation(item_id, opts={})
108
+ opts[:operation] = '[other valid operation supported by Product Advertising API]'
109
+
110
+ # setting default option value
111
+ opts[:item_id] = item_id
112
+
113
+ self.send_request(opts)
114
+ end
115
+ end
116
+ end
117
+
118
+ Amazon::Ecs.other_operation('[item_id]', :param1 => 'abc', :param2 => 'xyz')
119
+
120
+ Refer to Amazon Product Advertising API documentation for more information
121
+ on other valid operations, request parameters and the XML response output:
122
+ https://affiliate-program.amazon.com/gp/advertising/api/detail/main.html
123
+
124
+ To get a sample of Amazon REST XML response output, use AWSZone.com scratch pad:
125
+ http://www.awszone.com/scratchpads/aws/ecs.us/index.aws
126
+
127
+ == SOURCE CODES
128
+
129
+ * http://github.com/jugend/amazon-ecs
130
+
131
+ == CREDITS
132
+
133
+ Thanks to Dan Milne (http://da.nmilne.com/) for the signed request patch.
134
+
135
+ Thanks to Bryan Housel (https://github.com/bhousel/amazon-ecs) for the initial Nokogiri patch
136
+
137
+ == LICENSE
138
+
139
+ (The MIT License)
140
+
141
+ Copyright (c) 2011 Herryanto Siatono, http://www.siatono.com
Binary file
@@ -0,0 +1,38 @@
1
+ require 'rake'
2
+ # -*- encoding: utf-8 -*-
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require 'amazon/ecs'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = %q{amazon-ecs-eb}
8
+ gem.version = Amazon::Ecs::VERSION
9
+ gem.platform = Gem::Platform::RUBY
10
+ gem.authors = ["Laurens Greven"]
11
+ gem.email = %q{laurensgreven@gmail.com}
12
+ gem.homepage = %q{https://github.com/jugend/amazon-ecs}
13
+ gem.summary = %q{Generic Amazon Product Advertising Ruby API.}
14
+ gem.description = %q{Generic Amazon Product Advertising Ruby API with exponential backoff.}
15
+
16
+ gem.files = Rake::FileList['**/*']
17
+ gem.test_files = Rake::FileList['test/**/*']
18
+ gem.require_paths = ["lib"]
19
+
20
+ if gem.respond_to? :specification_version then
21
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
22
+ gem.specification_version = 2
23
+
24
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
25
+ gem.add_runtime_dependency("nokogiri", "~> 1.4")
26
+ gem.add_runtime_dependency("ruby-hmac", "~> 0.3")
27
+ gem.add_runtime_dependency("exponential-backoff", "~> 0.0.2")
28
+ else
29
+ gem.add_dependency("nokogiri", "~> 1.4")
30
+ gem.add_dependency("ruby-hmac", "~> 0.3")
31
+ gem.add_dependency("exponential-backoff", "~> 0.0.2")
32
+ end
33
+ else
34
+ gem.add_dependency("nokogiri", "~> 1.4")
35
+ gem.add_dependency("ruby-hmac", "~> 0.3")
36
+ gem.add_dependency("exponential-backoff", "~> 0.0.2")
37
+ end
38
+ end
@@ -0,0 +1,404 @@
1
+ #--
2
+ # Copyright (c) 2010 Herryanto Siatono
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'net/http'
25
+ require 'nokogiri'
26
+ require 'cgi'
27
+ require 'hmac-sha2'
28
+ require 'base64'
29
+ require 'openssl'
30
+ require 'exponential_backoff'
31
+
32
+ module Amazon
33
+ class RequestError < StandardError; end
34
+
35
+ class Ecs
36
+ VERSION = '2.2.12'
37
+
38
+ SERVICE_URLS = {
39
+ :us => 'http://ecs.amazonaws.com/onca/xml',
40
+ :uk => 'http://ecs.amazonaws.co.uk/onca/xml',
41
+ :ca => 'http://ecs.amazonaws.ca/onca/xml',
42
+ :de => 'http://ecs.amazonaws.de/onca/xml',
43
+ :jp => 'http://ecs.amazonaws.jp/onca/xml',
44
+ :fr => 'http://ecs.amazonaws.fr/onca/xml',
45
+ :it => 'http://webservices.amazon.it/onca/xml',
46
+ :cn => 'http://webservices.amazon.cn/onca/xml',
47
+ :es => 'http://webservices.amazon.es/onca/xml'
48
+ }
49
+
50
+ OPENSSL_DIGEST_SUPPORT = OpenSSL::Digest.constants.include?( 'SHA256' ) ||
51
+ OpenSSL::Digest.constants.include?( :SHA256 )
52
+
53
+ OPENSSL_DIGEST = OpenSSL::Digest.new( 'sha256' ) if OPENSSL_DIGEST_SUPPORT
54
+
55
+ @@options = {
56
+ :version => "2011-08-01",
57
+ :service => "AWSECommerceService"
58
+ }
59
+
60
+ @@debug = false
61
+
62
+ # Default search options
63
+ def self.options
64
+ @@options
65
+ end
66
+
67
+ # Set default search options
68
+ def self.options=(opts)
69
+ @@options = opts
70
+ end
71
+
72
+ # Get debug flag.
73
+ def self.debug
74
+ @@debug
75
+ end
76
+
77
+ # Set debug flag to true or false.
78
+ def self.debug=(dbg)
79
+ @@debug = dbg
80
+ end
81
+
82
+ def self.configure(&proc)
83
+ raise ArgumentError, "Block is required." unless block_given?
84
+ yield @@options
85
+ end
86
+
87
+ # Search amazon items with search terms. Default search index option is 'Books'.
88
+ # For other search type other than keywords, please specify :type => [search type param name].
89
+ def self.item_search(terms, opts = {})
90
+ opts[:operation] = 'ItemSearch'
91
+ opts[:search_index] = opts[:search_index] || 'Books'
92
+
93
+ type = opts.delete(:type)
94
+ if type
95
+ opts[type.to_sym] = terms
96
+ else
97
+ opts[:keywords] = terms
98
+ end
99
+
100
+ self.send_request(opts)
101
+ end
102
+
103
+ # Search an item by ASIN no.
104
+ def self.item_lookup(item_id, opts = {})
105
+ opts[:operation] = 'ItemLookup'
106
+ opts[:item_id] = item_id
107
+
108
+ self.send_request(opts)
109
+ end
110
+
111
+ # Search a browse node by BrowseNodeId
112
+ def self.browse_node_lookup(browse_node_id, opts = {})
113
+ opts[:operation] = 'BrowseNodeLookup'
114
+ opts[:browse_node_id] = browse_node_id
115
+
116
+ self.send_request(opts)
117
+ end
118
+
119
+ # Generic send request to ECS REST service. You have to specify the :operation parameter.
120
+ def self.send_request(opts)
121
+
122
+ # setting up backoff options for errorless and overloadless requesting
123
+ # numbers taken from amazon API guidelines
124
+ backoff = ExponentialBackoff.new(0.2, 30)
125
+ backoff.multiplier = 2
126
+ backoff.randomize_factor = 0.25
127
+
128
+ opts = self.options.merge(opts) if self.options
129
+
130
+ # Include other required options
131
+ opts[:timestamp] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
132
+
133
+ request_url = prepare_url(opts)
134
+ log "Request URL: #{request_url}"
135
+
136
+
137
+
138
+ backoff.until_success do |interval, retry_count|
139
+ # quit requests after a few failed attempts
140
+ Response.new(res.error) if retry_count > 6
141
+ res = Net::HTTP.get_response(URI::parse(request_url))
142
+
143
+ unless res.kind_of? Net::HTTPSuccess
144
+ # raise Amazon::RequestError, "HTTP Response: #{res.code} #{res.message}"
145
+ print "Failed with: #{res.code} #{res.message}"
146
+ end
147
+ return Response.new(res.body)
148
+ end
149
+ end
150
+
151
+ def self.validate_request(opts)
152
+ raise Amazon::RequestError, "" if opts[:associate_tag]
153
+ end
154
+
155
+ # Response object returned after a REST call to Amazon service.
156
+ class Response
157
+
158
+ # XML input is in string format
159
+ def initialize(xml)
160
+ @doc = Nokogiri::XML(xml, nil, 'UTF-8')
161
+ @doc.remove_namespaces!
162
+ # @doc.xpath("//*").each { |elem| elem.name = elem.name.downcase }
163
+ # @doc.xpath("//@*").each { |att| att.name = att.name.downcase }
164
+ end
165
+
166
+ # Return Nokogiri::XML::Document object.
167
+ def doc
168
+ @doc
169
+ end
170
+
171
+ # Return true if request is valid.
172
+ def is_valid_request?
173
+ Element.get(@doc, "//IsValid") == "True"
174
+ end
175
+
176
+ # Return true if response has an error.
177
+ def has_error?
178
+ !(error.nil? || error.empty?)
179
+ end
180
+
181
+ # Return error message.
182
+ def error
183
+ Element.get(@doc, "//Error/Message")
184
+ end
185
+
186
+ # Return error code
187
+ def error_code
188
+ Element.get(@doc, "//Error/Code")
189
+ end
190
+
191
+ # Return an array of Amazon::Element item objects.
192
+ def items
193
+ @items ||= (@doc/"Item").collect { |item| Element.new(item) }
194
+ end
195
+
196
+ # Return the first item (Amazon::Element)
197
+ def first_item
198
+ items.first
199
+ end
200
+
201
+ # Return current page no if :item_page option is when initiating the request.
202
+ def item_page
203
+ @item_page ||= Element.get(@doc, "//ItemPage").to_i
204
+ end
205
+
206
+ # Return total results.
207
+ def total_results
208
+ @total_results ||= Element.get(@doc, "//TotalResults").to_i
209
+ end
210
+
211
+ # Return total pages.
212
+ def total_pages
213
+ @total_pages ||= Element.get(@doc, "//TotalPages").to_i
214
+ end
215
+
216
+ def marshal_dump
217
+ @doc.to_s
218
+ end
219
+
220
+ def marshal_load(xml)
221
+ initialize(xml)
222
+ end
223
+ end
224
+
225
+ protected
226
+ def self.log(s)
227
+ return unless self.debug
228
+ if defined? RAILS_DEFAULT_LOGGER
229
+ RAILS_DEFAULT_LOGGER.error(s)
230
+ elsif defined? LOGGER
231
+ LOGGER.error(s)
232
+ else
233
+ puts s
234
+ end
235
+ end
236
+
237
+ private
238
+ def self.prepare_url(opts)
239
+ country = opts.delete(:country)
240
+ country = (country.nil?) ? 'us' : country
241
+ request_url = SERVICE_URLS[country.to_sym]
242
+ raise Amazon::RequestError, "Invalid country '#{country}'" unless request_url
243
+
244
+ secret_key = opts.delete(:AWS_secret_key)
245
+ request_host = URI.parse(request_url).host
246
+
247
+ qs = ''
248
+
249
+ opts = opts.collect do |a,b|
250
+ [camelize(a.to_s), b.to_s]
251
+ end
252
+
253
+ opts = opts.sort do |c,d|
254
+ c[0].to_s <=> d[0].to_s
255
+ end
256
+
257
+ opts.each do |e|
258
+ log "Adding #{e[0]}=#{e[1]}"
259
+ next unless e[1]
260
+ e[1] = e[1].join(',') if e[1].is_a? Array
261
+ # v = URI.encode(e[1].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
262
+ v = self.url_encode(e[1].to_s)
263
+ qs << "&" unless qs.length == 0
264
+ qs << "#{e[0]}=#{v}"
265
+ end
266
+
267
+ signature = ''
268
+ unless secret_key.nil?
269
+ request_to_sign="GET\n#{request_host}\n/onca/xml\n#{qs}"
270
+ signature = "&Signature=#{sign_request(request_to_sign, secret_key)}"
271
+ end
272
+
273
+ "#{request_url}?#{qs}#{signature}"
274
+ end
275
+
276
+ def self.url_encode(string)
277
+ string.gsub( /([^a-zA-Z0-9_.~-]+)/ ) do
278
+ '%' + $1.unpack( 'H2' * $1.bytesize ).join( '%' ).upcase
279
+ end
280
+ end
281
+
282
+ def self.camelize(s)
283
+ s.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
284
+ end
285
+
286
+ def self.sign_request(url, key)
287
+ return nil if key.nil?
288
+
289
+ if (OPENSSL_DIGEST_SUPPORT)
290
+ signature = OpenSSL::HMAC.digest(OPENSSL_DIGEST, key, url)
291
+ signature = [signature].pack('m').chomp
292
+ else
293
+ signature = Base64.encode64( HMAC::SHA256.digest(key, url) ).strip
294
+ end
295
+ signature = URI.escape(signature, Regexp.new("[+=]"))
296
+ return signature
297
+ end
298
+ end
299
+
300
+ # Internal wrapper class to provide convenient method to access Nokogiri element value.
301
+ class Element
302
+ class << self
303
+ # Return the text value of an element.
304
+ def get(element, path='.')
305
+ return unless element
306
+ result = element.at_xpath(path)
307
+ result = result.inner_html if result
308
+ result
309
+ end
310
+
311
+ # Return an unescaped text value of an element.
312
+ def get_unescaped(element, path='.')
313
+ result = self.get(element, path)
314
+ CGI::unescapeHTML(result) if result
315
+ end
316
+
317
+ # Return an array of values based on the given path.
318
+ def get_array(element, path='.')
319
+ return unless element
320
+
321
+ result = element/path
322
+ if (result.is_a? Nokogiri::XML::NodeSet) || (result.is_a? Array)
323
+ result.collect { |item| self.get(item) }
324
+ else
325
+ [self.get(result)]
326
+ end
327
+ end
328
+
329
+ # Return child element text values of the given path.
330
+ def get_hash(element, path='.')
331
+ return unless element
332
+
333
+ result = element.at_xpath(path)
334
+ if result
335
+ hash = {}
336
+ result = result.children
337
+ result.each do |item|
338
+ hash[item.name] = item.inner_html
339
+ end
340
+ hash
341
+ end
342
+ end
343
+ end
344
+
345
+ # Pass Nokogiri::XML::Element object
346
+ def initialize(element)
347
+ @element = element
348
+ end
349
+
350
+ # Returns Nokogiri::XML::Element object
351
+ def elem
352
+ @element
353
+ end
354
+
355
+ # Returns a Nokogiri::XML::NodeSet of elements matching the given path. Example: element/"author".
356
+ def /(path)
357
+ elements = @element/path
358
+ return nil if elements.size == 0
359
+ elements
360
+ end
361
+
362
+ # Return an array of Amazon::Element matching the given path
363
+ def get_elements(path)
364
+ elements = self./(path)
365
+ return unless elements
366
+ elements = elements.map{|element| Element.new(element)}
367
+ end
368
+
369
+ # Similar with search_and_convert but always return first element if more than one elements found
370
+ def get_element(path)
371
+ elements = get_elements(path)
372
+ elements[0] if elements
373
+ end
374
+
375
+ # Get the text value of the given path, leave empty to retrieve current element value.
376
+ def get(path='.')
377
+ Element.get(@element, path)
378
+ end
379
+
380
+ # Get the unescaped HTML text of the given path.
381
+ def get_unescaped(path='.')
382
+ Element.get_unescaped(@element, path)
383
+ end
384
+
385
+ # Get the array values of the given path.
386
+ def get_array(path='.')
387
+ Element.get_array(@element, path)
388
+ end
389
+
390
+ # Get the children element text values in hash format with the element names as the hash keys.
391
+ def get_hash(path='.')
392
+ Element.get_hash(@element, path)
393
+ end
394
+
395
+ def attributes
396
+ return unless self.elem
397
+ self.elem.attributes
398
+ end
399
+
400
+ def to_s
401
+ elem.to_s if elem
402
+ end
403
+ end
404
+ end