papercavalier-ruby-aaws 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +3 -0
  2. data/COPYING +340 -0
  3. data/INSTALL +260 -0
  4. data/NEWS +808 -0
  5. data/README +679 -0
  6. data/README.rdoc +140 -0
  7. data/Rakefile +17 -0
  8. data/VERSION.yml +5 -0
  9. data/example/batch_operation +28 -0
  10. data/example/browse_node_lookup1 +46 -0
  11. data/example/customer_content_lookup1 +27 -0
  12. data/example/customer_content_search1 +21 -0
  13. data/example/example1 +78 -0
  14. data/example/help1 +24 -0
  15. data/example/item_lookup1 +56 -0
  16. data/example/item_lookup2 +56 -0
  17. data/example/item_search1 +30 -0
  18. data/example/item_search2 +37 -0
  19. data/example/item_search3 +23 -0
  20. data/example/list_lookup1 +29 -0
  21. data/example/list_search1 +30 -0
  22. data/example/multiple_operation1 +69 -0
  23. data/example/seller_listing_lookup1 +30 -0
  24. data/example/seller_listing_search1 +29 -0
  25. data/example/seller_lookup1 +45 -0
  26. data/example/shopping_cart1 +42 -0
  27. data/example/similarity_lookup1 +48 -0
  28. data/example/tag_lookup1 +34 -0
  29. data/example/transaction_lookup1 +25 -0
  30. data/example/vehicle_search +22 -0
  31. data/lib/amazon.rb +165 -0
  32. data/lib/amazon/aws.rb +1493 -0
  33. data/lib/amazon/aws/cache.rb +141 -0
  34. data/lib/amazon/aws/search.rb +464 -0
  35. data/lib/amazon/aws/shoppingcart.rb +537 -0
  36. data/lib/amazon/locale.rb +102 -0
  37. data/test/setup.rb +56 -0
  38. data/test/tc_amazon.rb +20 -0
  39. data/test/tc_aws.rb +160 -0
  40. data/test/tc_browse_node_lookup.rb +49 -0
  41. data/test/tc_customer_content_lookup.rb +49 -0
  42. data/test/tc_help.rb +44 -0
  43. data/test/tc_item_lookup.rb +47 -0
  44. data/test/tc_item_search.rb +105 -0
  45. data/test/tc_list_lookup.rb +60 -0
  46. data/test/tc_list_search.rb +44 -0
  47. data/test/tc_multiple_operation.rb +375 -0
  48. data/test/tc_operation_request.rb +64 -0
  49. data/test/tc_seller_listing_lookup.rb +47 -0
  50. data/test/tc_seller_listing_search.rb +55 -0
  51. data/test/tc_seller_lookup.rb +44 -0
  52. data/test/tc_serialisation.rb +107 -0
  53. data/test/tc_shopping_cart.rb +214 -0
  54. data/test/tc_similarity_lookup.rb +48 -0
  55. data/test/tc_tag_lookup.rb +24 -0
  56. data/test/tc_transaction_lookup.rb +24 -0
  57. data/test/tc_vehicle_operations.rb +118 -0
  58. data/test/ts_aws.rb +24 -0
  59. metadata +141 -0
@@ -0,0 +1,141 @@
1
+ # $Id: cache.rb,v 1.8 2008/06/10 06:33:46 ianmacd Exp $
2
+ #
3
+
4
+ module Amazon
5
+
6
+ module AWS
7
+
8
+ # This class provides a simple results caching system for operations
9
+ # performed by AWS.
10
+ #
11
+ # To use it, set _cache_ to *true* in either <tt>/etc/amazonrc</tt> or
12
+ # <tt>~/.amazonrc</tt>.
13
+ #
14
+ # By default, the cache directory used is <tt>/tmp/amazon</tt>, but this
15
+ # can be changed by defining _cache_dir_ in either <tt>/etc/amazonrc</tt>
16
+ # or <tt>~/.amazonrc</tt>.
17
+ #
18
+ # When a cache is used, Ruby/AWS will check the cache directory for a
19
+ # recent copy of a response to the exact operation that you are
20
+ # performing. If found, the cached response will be returned instead of
21
+ # the request being forwarded to the AWS servers for processing. If no
22
+ # (recent) copy is found, the request will be forwarded to the AWS servers
23
+ # as usual. Recency is defined here as less than 24 hours old.
24
+ #
25
+ class Cache
26
+
27
+ require 'fileutils'
28
+
29
+ begin
30
+ require 'md5'
31
+ rescue LoadError
32
+ # Ruby 1.9 has moved MD5.
33
+ #
34
+ require 'digest/md5'
35
+ end
36
+
37
+ # Exception class for bad cache paths.
38
+ #
39
+ class PathError < StandardError; end
40
+
41
+ # Length of one day in seconds
42
+ #
43
+ ONE_DAY = 86400 # :nodoc:
44
+
45
+ # Age in days below which to consider cache files valid.
46
+ #
47
+ MAX_AGE = 1.0
48
+
49
+ # Default cache location.
50
+ #
51
+ DEFAULT_CACHE_DIR = '/tmp/amazon'
52
+
53
+ attr_reader :path
54
+
55
+ def initialize(path=DEFAULT_CACHE_DIR)
56
+ path ||= DEFAULT_CACHE_DIR
57
+
58
+ ::FileUtils::mkdir_p( path ) unless File.exists? path
59
+
60
+ unless File.directory? path
61
+ raise PathError, "cache path #{path} is not a directory"
62
+ end
63
+
64
+ unless File.readable? path
65
+ raise PathError, "cache path #{path} is not readable"
66
+ end
67
+
68
+ unless File.writable? path
69
+ raise PathError, "cache path #{path} is not writable"
70
+ end
71
+
72
+ @path = path
73
+ end
74
+
75
+
76
+ # Determine whether or not the the response to a given URL is cached.
77
+ # Returns *true* or *false*.
78
+ #
79
+ def cached?(url)
80
+ digest = Digest::MD5.hexdigest( url )
81
+
82
+ cache_files = Dir.glob( File.join( @path, '*' ) ).map do |d|
83
+ File.basename( d )
84
+ end
85
+
86
+ return cache_files.include?( digest ) &&
87
+ ( Time.now - File.mtime( File.join( @path, digest ) ) ) /
88
+ ONE_DAY <= MAX_AGE
89
+ end
90
+
91
+
92
+ # Retrieve the cached response associated with _url_.
93
+ #
94
+ def fetch(url)
95
+ digest = Digest::MD5.hexdigest( url )
96
+ cache_file = File.join( @path, digest )
97
+
98
+ return nil unless File.exist? cache_file
99
+
100
+ Amazon.dprintf( 'Fetching %s from cache...', digest )
101
+ File.open( File.join( cache_file ) ).readlines.to_s
102
+ end
103
+
104
+
105
+ # Cache the data from _contents_ and associate it with _url_.
106
+ #
107
+ def store(url, contents)
108
+ digest = Digest::MD5.hexdigest( url )
109
+ cache_file = File.join( @path, digest )
110
+
111
+ Amazon.dprintf( 'Caching %s...', digest )
112
+ File.open( cache_file, 'w' ) { |f| f.puts contents }
113
+ end
114
+
115
+
116
+ # This method flushes all files from the cache directory specified
117
+ # in the object's <i>@path</i> variable.
118
+ #
119
+ def flush_all
120
+ FileUtils.rm Dir.glob( File.join( @path, '*' ) )
121
+ end
122
+
123
+
124
+ # This method flushes expired files from the cache directory specified
125
+ # in the object's <i>@path</i> variable.
126
+ #
127
+ def flush_expired
128
+ now = Time.now
129
+
130
+ expired_files = Dir.glob( File.join( @path, '*' ) ).find_all do |f|
131
+ ( now - File.mtime( f ) ) / ONE_DAY > MAX_AGE
132
+ end
133
+
134
+ FileUtils.rm expired_files
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
141
+ end
@@ -0,0 +1,464 @@
1
+ # $Id: search.rb,v 1.49 2010/03/19 19:28:19 ianmacd Exp $
2
+ #
3
+
4
+ module Amazon
5
+
6
+ module AWS
7
+
8
+ require 'amazon/aws'
9
+ require 'net/http'
10
+ require 'rexml/document'
11
+ require 'openssl'
12
+
13
+ # Load this library with:
14
+ #
15
+ # require 'amazon/aws/search'
16
+ #
17
+ module Search
18
+
19
+ class Request
20
+
21
+ include REXML
22
+
23
+ # Exception class for bad access key ID.
24
+ #
25
+ class AccessKeyIdError < Amazon::AWS::Error::AWSError; end
26
+
27
+ # Exception class for bad locales.
28
+ #
29
+ class LocaleError < Amazon::AWS::Error::AWSError; end
30
+
31
+ # Do we have support for the SHA-256 Secure Hash Algorithm?
32
+ #
33
+ # Note that Module#constants returns Strings in Ruby 1.8 and Symbols
34
+ # in 1.9.
35
+ #
36
+ DIGEST_SUPPORT = OpenSSL::Digest.constants.include?( 'SHA256' ) ||
37
+ OpenSSL::Digest.constants.include?( :SHA256 )
38
+
39
+ # Requests are authenticated using the SHA-256 Secure Hash Algorithm.
40
+ #
41
+ DIGEST = OpenSSL::Digest::Digest.new( 'sha256' ) if DIGEST_SUPPORT
42
+
43
+ attr_reader :conn, :config, :locale, :query, :user_agent
44
+ attr_writer :cache
45
+ attr_accessor :encoding
46
+
47
+ # This method is used to generate an AWS search request object.
48
+ #
49
+ # _key_id_ is your AWS {access key
50
+ # ID}[https://aws-portal.amazon.com/gp/aws/developer/registration/index.html].
51
+ # Note that your secret key, used for signing requests, can be
52
+ # specified only in your <tt>~/.amazonrc</tt> configuration file.
53
+ #
54
+ # _associate_ is your
55
+ # Associates[http://docs.amazonwebservices.com/AWSECommerceService/2009-11-01/GSG/BecominganAssociate.html]
56
+ # tag (if any), _locale_ is the locale in which you which to work
57
+ # (*us* for amazon.com[http://www.amazon.com/], *uk* for
58
+ # amazon.co.uk[http://www.amazon.co.uk], etc.), _cache_ is whether or
59
+ # not you wish to utilise a response cache, and _user_agent_ is the
60
+ # client name to pass when performing calls to AWS. By default,
61
+ # _user_agent_ will be set to a string identifying the Ruby/AWS
62
+ # library and its version number.
63
+ #
64
+ # _locale_ and _cache_ can also be set later, if you wish to change
65
+ # the current behaviour.
66
+ #
67
+ # Example:
68
+ #
69
+ # req = Request.new( '0Y44V8FAFNM119CX4TR2', 'calibanorg-20' )
70
+ #
71
+ def initialize(key_id=nil, associate=nil, locale=nil, cache=nil,
72
+ user_agent=USER_AGENT)
73
+
74
+ @config ||= Amazon::Config.new
75
+
76
+ def_locale = locale
77
+ locale = 'us' unless locale
78
+ locale.downcase!
79
+
80
+ key_id ||= @config['key_id']
81
+ cache = @config['cache'] if cache.nil?
82
+
83
+ # Take locale from config file if no locale was passed to method.
84
+ #
85
+ if @config.key?( 'locale' ) && ! def_locale
86
+ locale = @config['locale']
87
+ end
88
+ validate_locale( locale )
89
+
90
+ if key_id.nil?
91
+ raise AccessKeyIdError, 'key_id may not be nil'
92
+ end
93
+
94
+ @key_id = key_id
95
+ @tag = associate || @config['associate'] || DEF_ASSOC[locale]
96
+ @user_agent = user_agent
97
+ @cache = unless cache == 'false' || cache == false
98
+ Amazon::AWS::Cache.new( @config['cache_dir'] )
99
+ else
100
+ nil
101
+ end
102
+
103
+ # Set the following two variables from the config file. Will be
104
+ # *nil* if not present in config file.
105
+ #
106
+ @api = @config['api']
107
+ @encoding = @config['encoding']
108
+
109
+ self.locale = locale
110
+ end
111
+
112
+
113
+ # Assign a new locale. If the locale we're coming from is using the
114
+ # default Associate ID for that locale, then we use the new locale's
115
+ # default ID, too.
116
+ #
117
+ def locale=(l) # :nodoc:
118
+ old_locale = @locale ||= nil
119
+ @locale = validate_locale( l )
120
+
121
+ # Use the new locale's default ID if the ID currently in use is the
122
+ # current locale's default ID.
123
+ #
124
+ if @tag == Amazon::AWS::DEF_ASSOC[old_locale]
125
+ @tag = Amazon::AWS::DEF_ASSOC[@locale]
126
+ end
127
+
128
+ if @config.key?( @locale ) && @config[@locale].key?( 'associate' )
129
+ @tag = @config[@locale]['associate']
130
+ end
131
+
132
+ # We must now set up a new HTTP connection to the correct server for
133
+ # this locale, unless the same server is used for both.
134
+ #
135
+ unless Amazon::AWS::ENDPOINT[@locale] ==
136
+ Amazon::AWS::ENDPOINT[old_locale]
137
+ #connect( @locale )
138
+ @conn = nil
139
+ end
140
+ end
141
+
142
+
143
+ # If @cache has simply been assigned *true* at some point in time,
144
+ # assign a proper cache object to it when it is referenced. Otherwise,
145
+ # just return its value.
146
+ #
147
+ def cache # :nodoc:
148
+ if @cache == true
149
+ @cache = Amazon::AWS::Cache.new( @config['cache_dir'] )
150
+ else
151
+ @cache
152
+ end
153
+ end
154
+
155
+
156
+ # Verify the validity of a locale string. _l_ is the locale string.
157
+ #
158
+ def validate_locale(l)
159
+ unless Amazon::AWS::ENDPOINT.has_key? l
160
+ raise LocaleError, "invalid locale: #{l}"
161
+ end
162
+ l
163
+ end
164
+ private :validate_locale
165
+
166
+
167
+ # Return an HTTP connection for the current _locale_.
168
+ #
169
+ def connect(locale)
170
+ if ENV.key? 'http_proxy'
171
+ uri = URI.parse( ENV['http_proxy'] )
172
+ proxy_user = proxy_pass = nil
173
+ proxy_user, proxy_pass = uri.userinfo.split( /:/ ) if uri.userinfo
174
+ @conn = Net::HTTP::Proxy( uri.host, uri.port, proxy_user,
175
+ proxy_pass ).start(
176
+ Amazon::AWS::ENDPOINT[locale].host )
177
+ else
178
+ @conn = Net::HTTP::start( Amazon::AWS::ENDPOINT[locale].host )
179
+ end
180
+ end
181
+ private :connect
182
+
183
+
184
+ # Reconnect to the server if our connection has been lost (due to a
185
+ # time-out, etc.).
186
+ #
187
+ def reconnect # :nodoc:
188
+ connect( self.locale )
189
+ self
190
+ end
191
+
192
+
193
+ # This method checks for errors in an XML response returned by AWS.
194
+ # _xml_ is the XML node below which to search.
195
+ #
196
+ def error_check(xml)
197
+ if ! xml.nil? && xml = xml.elements['Errors/Error']
198
+ raise Amazon::AWS::Error.exception( xml )
199
+ end
200
+ end
201
+ private :error_check
202
+
203
+
204
+ # Add a timestamp to a request object's query string.
205
+ #
206
+ def timestamp # :nodoc:
207
+ @query << '&Timestamp=%s' %
208
+ [ Amazon.url_encode(
209
+ Time.now.utc.strftime( '%Y-%m-%dT%H:%M:%SZ' ) ) ]
210
+ end
211
+ private :timestamp
212
+
213
+
214
+ # Add a signature to a request object's query string. This implicitly
215
+ # also adds a timestamp.
216
+ #
217
+ def sign # :nodoc:
218
+ return false unless DIGEST_SUPPORT
219
+
220
+ timestamp
221
+ params = @query[1..-1].split( '&' ).sort.join( '&' )
222
+
223
+ sign_str = "GET\n%s\n%s\n%s" % [ ENDPOINT[@locale].host,
224
+ ENDPOINT[@locale].path,
225
+ params ]
226
+
227
+ Amazon.dprintf( 'Calculating SHA256 HMAC of "%s"...', sign_str )
228
+
229
+ hmac = OpenSSL::HMAC.digest( DIGEST,
230
+ @config['secret_key_id'],
231
+ sign_str )
232
+ Amazon.dprintf( 'SHA256 HMAC is "%s"', hmac.inspect )
233
+
234
+ base64_hmac = [ hmac ].pack( 'm' ).chomp
235
+ Amazon.dprintf( 'Base64-encoded HMAC is "%s".', base64_hmac )
236
+
237
+ signature = Amazon.url_encode( base64_hmac )
238
+
239
+ params << '&Signature=%s' % [ signature ]
240
+ @query = '?' + params
241
+
242
+ true
243
+ end
244
+
245
+
246
+ # Perform a search of the AWS database, returning an AWSObject.
247
+ #
248
+ # _operation_ is an object of a subclass of _Operation_, such as
249
+ # _ItemSearch_, _ItemLookup_, etc. It may also be a _MultipleOperation_
250
+ # object.
251
+ #
252
+ # In versions of Ruby/AWS up to prior to 0.8.0, the second parameter to
253
+ # this method was _response_group_. This way of passing response
254
+ # groups has been deprecated since 0.7.0 and completely removed in
255
+ # 0.8.0. To pair a set of response groups with an operation, assign
256
+ # directly to the operation's @response_group attribute.
257
+ #
258
+ # _nr_pages_ is the number of results pages to return. It defaults to
259
+ # <b>1</b>. If a higher number is given, pages 1 to _nr_pages_ will be
260
+ # returned. If the special value <b>:ALL_PAGES</b> is given, all
261
+ # results pages will be returned.
262
+ #
263
+ # Note that _ItemLookup_ operations can use several different
264
+ # pagination parameters. An _ItemLookup_ will typically return just
265
+ # one results page containing a single product, but <b>:ALL_PAGES</b>
266
+ # can still be used to apply the _OfferPage_ parameter to paginate
267
+ # through multiple pages of offers.
268
+ #
269
+ # Similarly, a single product may have multiple pages of reviews
270
+ # available. In such a case, it is up to the user to manually supply
271
+ # the _ReviewPage_ parameter and an appropriate value.
272
+ #
273
+ # In the same vein, variations can be returned by using the
274
+ # _VariationPage_ parameter.
275
+ #
276
+ # The pagination parameters supported by each type of operation,
277
+ # together with the maximum page number that can be retrieved for each
278
+ # type of data, are # documented in the AWS Developer's Guide:
279
+ #
280
+ # http://docs.amazonwebservices.com/AWSECommerceService/2009-11-01/DG/index.html?MaximumNumberofPages.html
281
+ #
282
+ # The pagination parameter used by <b>:ALL_PAGES</b> can be looked up
283
+ # in the Amazon::AWS::PAGINATION hash.
284
+ #
285
+ # If _operation_ is of class _MultipleOperation_, the operations
286
+ # encapsulated within will return only the first page of results,
287
+ # regardless of whether a higher number of pages is requested.
288
+ #
289
+ # If a block is passed to this method, each successive page of results
290
+ # will be yielded to the block.
291
+ #
292
+ def search(operation, nr_pages=1)
293
+ parameters = Amazon::AWS::SERVICE.
294
+ merge( { 'AWSAccessKeyId' => @key_id,
295
+ 'AssociateTag' => @tag } ).
296
+ merge( operation.query_parameters )
297
+
298
+ if nr_pages.is_a? Amazon::AWS::ResponseGroup
299
+ raise ObsolescenceError, 'Request#search method no longer accepts response_group parameter.'
300
+ end
301
+
302
+ # Pre-0.8.0 user code may have passed *nil* as the second parameter,
303
+ # in order to use the @response_group of the operation.
304
+ #
305
+ nr_pages ||= 1
306
+
307
+ # Check to see whether a particular version of the API has been
308
+ # requested. If so, overwrite Version with the new value.
309
+ #
310
+ parameters.merge!( { 'Version' => @api } ) if @api
311
+
312
+ @query = Amazon::AWS.assemble_query( parameters, @encoding )
313
+ page = Amazon::AWS.get_page( self )
314
+
315
+ # Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
316
+ #
317
+ page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
318
+
319
+ doc = Document.new( page )
320
+
321
+ # Some errors occur at the very top level of the XML. For example,
322
+ # when no Operation parameter is given. This should not be possible
323
+ # with user code, but occurred during debugging of this library.
324
+ #
325
+ error_check( doc )
326
+
327
+ # Another possible error results in a document containing nothing
328
+ # but <Result>Internal Error</Result>. This occurs when a specific
329
+ # version of the AWS API is requested, in combination with an
330
+ # operation that did not yet exist in that version of the API.
331
+ #
332
+ # For example:
333
+ #
334
+ # http://ecs.amazonaws.com/onca/xml?AWSAccessKeyId=foo&Operation=VehicleSearch&Year=2008&ResponseGroup=VehicleMakes&Service=AWSECommerceService&Version=2008-03-03
335
+ #
336
+ if xml = doc.elements['Result']
337
+ raise Amazon::AWS::Error::AWSError, xml.text
338
+ end
339
+
340
+ # Fundamental errors happen at the OperationRequest level. For
341
+ # example, if an invalid AWSAccessKeyId is used.
342
+ #
343
+ error_check( doc.elements['*/OperationRequest'] )
344
+
345
+ # Check for parameter and value errors deeper down, inside Request.
346
+ #
347
+ if operation.kind == 'MultipleOperation'
348
+
349
+ # Everything is a level deeper, because of the
350
+ # <MultiOperationResponse> container.
351
+ #
352
+ # Check for errors in the first operation.
353
+ #
354
+ error_check( doc.elements['*/*/*/Request'] )
355
+
356
+ # Check for errors in the second operation.
357
+ #
358
+ error_check( doc.elements['*/*[3]/*/Request'] )
359
+
360
+ # If second operation is batched, check for errors in its 2nd set
361
+ # of results.
362
+ #
363
+ if batched = doc.elements['*/*[3]/*[2]/Request']
364
+ error_check( batched )
365
+ end
366
+ else
367
+ error_check( doc.elements['*/*/Request'] )
368
+
369
+ # If operation is batched, check for errors in its 2nd set of
370
+ # results.
371
+ #
372
+ if batched = doc.elements['*/*[3]/Request']
373
+ error_check( batched )
374
+ end
375
+ end
376
+
377
+ if doc.elements['*/*[2]/TotalPages']
378
+ total_pages = doc.elements['*/*[2]/TotalPages'].text.to_i
379
+
380
+ # FIXME: ListLookup and MultipleOperation (and possibly others) have
381
+ # TotalPages nested one level deeper. I should take some time to
382
+ # ensure that all operations that can return multiple results pages
383
+ # are covered by either the 'if' above or the 'elsif' here.
384
+ #
385
+ elsif doc.elements['*/*[2]/*[2]/TotalPages']
386
+ total_pages = doc.elements['*/*[2]/*[2]/TotalPages'].text.to_i
387
+ else
388
+ total_pages = 1
389
+ end
390
+
391
+ # Create a root AWS object and walk the XML response tree.
392
+ #
393
+ aws = AWS::AWSObject.new( operation )
394
+ aws.walk( doc )
395
+ result = aws
396
+
397
+ # If only one page has been requested or only one page is available,
398
+ # we can stop here. First yield to the block, if given.
399
+ #
400
+ if nr_pages == 1 || ( tp = total_pages ) == 1
401
+ yield result if block_given?
402
+ return result
403
+ end
404
+
405
+ # Limit the number of pages to the maximum number available.
406
+ #
407
+ nr_pages = tp.to_i if nr_pages == :ALL_PAGES || nr_pages > tp.to_i
408
+
409
+ if PAGINATION.key? operation.kind
410
+ page_parameter = PAGINATION[operation.kind]['parameter']
411
+ max_pages = PAGINATION[operation.kind]['max_page']
412
+ else
413
+ page_parameter = 'ItemPage'
414
+ max_pages = 400
415
+ end
416
+
417
+ # Iterate over pages 2 and higher, but go no higher than MAX_PAGES.
418
+ #
419
+ 2.upto( nr_pages < max_pages ? nr_pages : max_pages ) do |page_nr|
420
+ @query = Amazon::AWS.assemble_query(
421
+ parameters.merge( { page_parameter => page_nr } ),
422
+ @encoding)
423
+ page = Amazon::AWS.get_page( self )
424
+
425
+ # Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
426
+ #
427
+ page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
428
+
429
+ doc = Document.new( page )
430
+
431
+ # Check for errors.
432
+ #
433
+ error_check( doc.elements['*/OperationRequest'] )
434
+ error_check( doc.elements['*/*/Request'] )
435
+
436
+ # Create a new AWS object and walk the XML response tree.
437
+ #
438
+ aws = AWS::AWSObject.new( operation )
439
+ aws.walk( doc )
440
+
441
+ # When dealing with multiple pages, we return not just an
442
+ # AWSObject, but an array of them.
443
+ #
444
+ result = [ result ] unless result.is_a? Array
445
+
446
+ # Append the new object to the array.
447
+ #
448
+ result << aws
449
+ end
450
+
451
+ # Yield each object to the block, if given.
452
+ #
453
+ result.each { |r| yield r } if block_given?
454
+
455
+ result
456
+ end
457
+
458
+ end
459
+
460
+ end
461
+
462
+ end
463
+
464
+ end