amazon-ecs-eb 2.2.12

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