google-spreadsheet-ruby 0.1.8 → 0.2.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/README.rdoc CHANGED
@@ -16,16 +16,19 @@ Example:
16
16
  require "google_spreadsheet"
17
17
 
18
18
  # Logs in.
19
- # You can also use OAuth. See document of GoogleSpreadsheet.login_with_oauth for details.
19
+ # You can also use OAuth. See document of
20
+ # GoogleSpreadsheet.login_with_oauth for details.
20
21
  session = GoogleSpreadsheet.login("username@gmail.com", "mypassword")
21
22
 
22
- # First worksheet of http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=en
23
+ # First worksheet of
24
+ # https://docs.google.com/spreadsheet/ccc?key=pz7XtlQC-PYx-jrVMJErTcg
23
25
  ws = session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg").worksheets[0]
24
26
 
25
27
  # Gets content of A2 cell.
26
28
  p ws[2, 1] #==> "hoge"
27
29
 
28
- # Changes content of cells. Changes are not sent to the server until you call ws.save().
30
+ # Changes content of cells.
31
+ # Changes are not sent to the server until you call ws.save().
29
32
  ws[2, 1] = "foo"
30
33
  ws[2, 2] = "bar"
31
34
  ws.save()
@@ -0,0 +1,20 @@
1
+ module GoogleSpreadsheet
2
+
3
+ class Acl
4
+
5
+ # Returns the number of entries.
6
+ def size
7
+ end
8
+
9
+ # Returns GoogleSpreadsheet::AclEntry object at +index+.
10
+ def [](index)
11
+ end
12
+
13
+ # Iterates over GoogleSpreadsheet::AclEntry objects.
14
+ def each(&block)
15
+ yield(entry)
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,33 @@
1
+ module GoogleSpreadsheet
2
+
3
+ class AclEntry
4
+
5
+ # Type of the scope. One of:
6
+ #
7
+ # - "user": scope is a user's email address.
8
+ # - "group": scope is a Google Group email address.
9
+ # - "domain": scope is a Google Apps domain.
10
+ # - "default": Publicly shared with all users. scope is +nil+.
11
+ attr_reader(:scope_type)
12
+
13
+ # The scope. See scope_type.
14
+ attr_reader(:scope)
15
+
16
+ # The role given to the scope. One of:
17
+ # - "owner": The owner.
18
+ # - "writer": With read/write access.
19
+ # - "reader": With read-only access.
20
+ attr_reader(:role)
21
+
22
+ # Title of the entry.
23
+ attr_reader(:title)
24
+
25
+ # Edit URL of the entry.
26
+ attr_reader(:edit_url)
27
+
28
+ # E-tag of the entry.
29
+ attr_reader(:etag)
30
+
31
+ end
32
+
33
+ end
@@ -1,17 +1,8 @@
1
1
  # Author: Hiroshi Ichikawa <http://gimite.net/>
2
2
  # The license of this source is "New BSD Licence"
3
3
 
4
- require "enumerator"
5
- require "set"
6
- require "net/https"
7
- require "open-uri"
8
- require "cgi"
9
- require "uri"
10
- require "rubygems"
11
- require "nokogiri"
12
- require "oauth"
13
- require "oauth2"
14
- Net::HTTP.version_1_2
4
+ require "google_spreadsheet/session"
5
+
15
6
 
16
7
  module GoogleSpreadsheet
17
8
 
@@ -53,15 +44,17 @@ module GoogleSpreadsheet
53
44
  #
54
45
  # OAuth1 code example:
55
46
  #
56
- # 1) First generate OAuth consumer object with key and secret for your site by registering site with google
47
+ # 1) First generate OAuth consumer object with key and secret for your site by registering site
48
+ # with Google.
57
49
  # @consumer = OAuth::Consumer.new( "key","secret", {:site=>"https://agree2"})
58
- # 2) Request token with OAuth
50
+ # 2) Request token with OAuth.
59
51
  # @request_token = @consumer.get_request_token
60
52
  # session[:request_token] = @request_token
61
53
  # redirect_to @request_token.authorize_url
62
- # 3) Create an oauth access token
54
+ # 3) Create an oauth access token.
63
55
  # @oauth_access_token = @request_token.get_access_token
64
- # @access_token = OAuth::AccessToken.new(@consumer, @oauth_access_token.token, @oauth_access_token.secret)
56
+ # @access_token = OAuth::AccessToken.new(
57
+ # @consumer, @oauth_access_token.token, @oauth_access_token.secret)
65
58
  #
66
59
  # See these documents for details:
67
60
  #
@@ -123,1227 +116,5 @@ module GoogleSpreadsheet
123
116
  end
124
117
  return session
125
118
  end
126
-
127
-
128
- module Util #:nodoc:
129
-
130
- module_function
131
-
132
- def encode_query(params)
133
- return params.map(){ |k, v| CGI.escape(k) + "=" + CGI.escape(v) }.join("&")
134
- end
135
-
136
- def concat_url(url, piece)
137
- (url_base, url_query) = url.split(/\?/, 2)
138
- (piece_base, piece_query) = piece.split(/\?/, 2)
139
- result_query = [url_query, piece_query].select(){ |s| s && !s.empty? }.join("&")
140
- return url_base + piece_base + (result_query.empty? ? "" : "?#{result_query}")
141
- end
142
-
143
- def h(str)
144
- return CGI.escapeHTML(str.to_s())
145
- end
146
-
147
- end
148
-
149
-
150
- # Raised when spreadsheets.google.com has returned error.
151
- class Error < RuntimeError
152
-
153
- end
154
-
155
-
156
- # Raised when GoogleSpreadsheet.login has failed.
157
- class AuthenticationError < GoogleSpreadsheet::Error
158
-
159
- end
160
-
161
-
162
- class ClientLoginFetcher #:nodoc:
163
-
164
- def initialize(auth_tokens, proxy)
165
- @auth_tokens = auth_tokens
166
- if proxy
167
- @proxy = proxy
168
- elsif ENV["http_proxy"] && !ENV["http_proxy"].empty?
169
- proxy_url = URI.parse(ENV["http_proxy"])
170
- @proxy = Net::HTTP.Proxy(proxy_url.host, proxy_url.port)
171
- else
172
- @proxy = Net::HTTP
173
- end
174
- end
175
-
176
- attr_accessor(:auth_tokens)
177
-
178
- def request_raw(method, url, data, extra_header, auth)
179
- uri = URI.parse(url)
180
- http = @proxy.new(uri.host, uri.port)
181
- http.use_ssl = true
182
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
183
- http.start() do
184
- path = uri.path + (uri.query ? "?#{uri.query}" : "")
185
- header = auth_header(auth).merge(extra_header)
186
- if method == :delete || method == :get
187
- return http.__send__(method, path, header)
188
- else
189
- return http.__send__(method, path, data, header)
190
- end
191
- end
192
- end
193
-
194
- private
195
-
196
- def auth_header(auth)
197
- token = auth == :none ? nil : @auth_tokens[auth]
198
- if token
199
- return {"Authorization" => "GoogleLogin auth=#{token}"}
200
- else
201
- return {}
202
- end
203
- end
204
-
205
- end
206
-
207
-
208
- class OAuth1Fetcher #:nodoc:
209
-
210
- def initialize(oauth1_token)
211
- @oauth1_token = oauth1_token
212
- end
213
-
214
- def request_raw(method, url, data, extra_header, auth)
215
- if method == :delete || method == :get
216
- return @oauth1_token.__send__(method, url, extra_header)
217
- else
218
- return @oauth1_token.__send__(method, url, data, extra_header)
219
- end
220
- end
221
-
222
- end
223
-
224
-
225
- class OAuth2Fetcher #:nodoc:
226
-
227
- Response = Struct.new(:code, :body)
228
-
229
- def initialize(oauth2_token)
230
- @oauth2_token = oauth2_token
231
- end
232
-
233
- def request_raw(method, url, data, extra_header, auth)
234
- if method == :delete || method == :get
235
- raw_res = @oauth2_token.request(method, url, {:header => extra_header})
236
- else
237
- raw_res = @oauth2_token.request(method, url, {:header => extra_header, :body => data})
238
- end
239
- return Response.new(raw_res.status.to_s(), raw_res.body)
240
- end
241
-
242
- end
243
-
244
-
245
- # Use GoogleSpreadsheet.login or GoogleSpreadsheet.saved_session to get
246
- # GoogleSpreadsheet::Session object.
247
- class Session
248
-
249
- include(Util)
250
- extend(Util)
251
-
252
- # The same as GoogleSpreadsheet.login.
253
- def self.login(mail, password, proxy = nil)
254
- session = Session.new(nil, ClientLoginFetcher.new({}, proxy))
255
- session.login(mail, password)
256
- return session
257
- end
258
-
259
- # The same as GoogleSpreadsheet.login_with_oauth.
260
- def self.login_with_oauth(oauth_token)
261
- case oauth_token
262
- when OAuth::AccessToken
263
- fetcher = OAuth1Fetcher.new(oauth_token)
264
- when OAuth2::AccessToken
265
- fetcher = OAuth2Fetcher.new(oauth_token)
266
- else
267
- raise(GoogleSpreadsheet::Error,
268
- "oauth_token is neither OAuth::Token nor OAuth2::Token: %p" % oauth_token)
269
- end
270
- return Session.new(nil, fetcher)
271
- end
272
-
273
- # The same as GoogleSpreadsheet.restore_session.
274
- def self.restore_session(auth_tokens, proxy = nil)
275
- return Session.new(auth_tokens, nil, proxy)
276
- end
277
-
278
- # DEPRECATED: Use GoogleSpreadsheet.restore_session instead.
279
- def initialize(auth_tokens = nil, fetcher = nil, proxy = nil)
280
- if fetcher
281
- @fetcher = fetcher
282
- else
283
- @fetcher = ClientLoginFetcher.new(auth_tokens || {}, proxy)
284
- end
285
- end
286
-
287
- # Authenticates with given +mail+ and +password+, and updates current session object
288
- # if succeeds. Raises GoogleSpreadsheet::AuthenticationError if fails.
289
- # Google Apps account is supported.
290
- def login(mail, password)
291
- if !@fetcher.is_a?(ClientLoginFetcher)
292
- raise(GoogleSpreadsheet::Error,
293
- "Cannot call login for session created by login_with_oauth.")
294
- end
295
- begin
296
- @fetcher.auth_tokens = {
297
- :wise => authenticate(mail, password, :wise),
298
- :writely => authenticate(mail, password, :writely),
299
- }
300
- rescue GoogleSpreadsheet::Error => ex
301
- return true if @on_auth_fail && @on_auth_fail.call()
302
- raise(AuthenticationError, "authentication failed for #{mail}: #{ex.message}")
303
- end
304
- end
305
-
306
- # Authentication tokens.
307
- def auth_tokens
308
- if !@fetcher.is_a?(ClientLoginFetcher)
309
- raise(GoogleSpreadsheet::Error,
310
- "Cannot call auth_tokens for session created by " +
311
- "login_with_oauth.")
312
- end
313
- return @fetcher.auth_tokens
314
- end
315
-
316
- # Authentication token.
317
- def auth_token(auth = :wise)
318
- return self.auth_tokens[auth]
319
- end
320
-
321
- # Proc or Method called when authentication has failed.
322
- # When this function returns +true+, it tries again.
323
- attr_accessor :on_auth_fail
324
-
325
- # Returns list of spreadsheets for the user as array of GoogleSpreadsheet::Spreadsheet.
326
- # You can specify query parameters described at
327
- # http://code.google.com/apis/spreadsheets/docs/2.0/reference.html#Parameters
328
- #
329
- # e.g.
330
- # session.spreadsheets
331
- # session.spreadsheets("title" => "hoge")
332
- def spreadsheets(params = {})
333
- query = encode_query(params)
334
- doc = request(:get, "https://spreadsheets.google.com/feeds/spreadsheets/private/full?#{query}")
335
- result = []
336
- doc.css("feed > entry").each() do |entry|
337
- title = entry.css("title").text
338
- url = entry.css(
339
- "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
340
- result.push(Spreadsheet.new(self, url, title))
341
- end
342
- return result
343
- end
344
-
345
- # Returns GoogleSpreadsheet::Spreadsheet with given +key+.
346
- #
347
- # e.g.
348
- # # http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=ja
349
- # session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg")
350
- def spreadsheet_by_key(key)
351
- url = "https://spreadsheets.google.com/feeds/worksheets/#{key}/private/full"
352
- return Spreadsheet.new(self, url)
353
- end
354
-
355
- # Returns GoogleSpreadsheet::Spreadsheet with given +url+. You must specify either of:
356
- # - URL of the page you open to access the spreadsheet in your browser
357
- # - URL of worksheet-based feed of the spreadseet
358
- #
359
- # e.g.
360
- # session.spreadsheet_by_url(
361
- # "http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=en")
362
- # session.spreadsheet_by_url(
363
- # "https://spreadsheets.google.com/feeds/worksheets/pz7XtlQC-PYx-jrVMJErTcg/private/full")
364
- def spreadsheet_by_url(url)
365
- # Tries to parse it as URL of human-readable spreadsheet.
366
- uri = URI.parse(url)
367
- if uri.host == "spreadsheets.google.com" && uri.path =~ /\/ccc$/
368
- if (uri.query || "").split(/&/).find(){ |s| s=~ /^key=(.*)$/ }
369
- return spreadsheet_by_key($1)
370
- end
371
- end
372
- # Assumes the URL is worksheets feed URL.
373
- return Spreadsheet.new(self, url)
374
- end
375
-
376
- # Returns GoogleSpreadsheet::Worksheet with given +url+.
377
- # You must specify URL of cell-based feed of the worksheet.
378
- #
379
- # e.g.
380
- # session.worksheet_by_url(
381
- # "http://spreadsheets.google.com/feeds/cells/pz7XtlQC-PYxNmbBVgyiNWg/od6/private/full")
382
- def worksheet_by_url(url)
383
- return Worksheet.new(self, nil, url)
384
- end
385
-
386
- # Returns GoogleSpreadsheet::Collection with given +url+.
387
- # You must specify URL of collection (folder) feed.
388
- #
389
- # e.g.
390
- # session.collection_by_url(
391
- # "http://docs.google.com/feeds/default/private/full/folder%3A" +
392
- # "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
393
- def collection_by_url(url)
394
- return Collection.new(self, url)
395
- end
396
-
397
- # Creates new spreadsheet and returns the new GoogleSpreadsheet::Spreadsheet.
398
- #
399
- # e.g.
400
- # session.create_spreadsheet("My new sheet")
401
- def create_spreadsheet(
402
- title = "Untitled",
403
- feed_url = "https://docs.google.com/feeds/documents/private/full")
404
- xml = <<-"EOS"
405
- <atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007">
406
- <atom:category scheme="http://schemas.google.com/g/2005#kind"
407
- term="http://schemas.google.com/docs/2007#spreadsheet" label="spreadsheet"/>
408
- <atom:title>#{h(title)}</atom:title>
409
- </atom:entry>
410
- EOS
411
-
412
- doc = request(:post, feed_url, :data => xml, :auth => :writely)
413
- ss_url = doc.css(
414
- "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']").first['href']
415
- return Spreadsheet.new(self, ss_url, title)
416
- end
417
-
418
- def request(method, url, params = {}) #:nodoc:
419
- # Always uses HTTPS.
420
- url = url.gsub(%r{^http://}, "https://")
421
- data = params[:data]
422
- auth = params[:auth] || :wise
423
- if params[:header]
424
- extra_header = params[:header]
425
- elsif data
426
- extra_header = {"Content-Type" => "application/atom+xml"}
427
- else
428
- extra_header = {}
429
- end
430
- response_type = params[:response_type] || :xml
431
-
432
- while true
433
- response = @fetcher.request_raw(method, url, data, extra_header, auth)
434
- if response.code == "401" && @on_auth_fail && @on_auth_fail.call()
435
- next
436
- end
437
- if !(response.code =~ /^2/)
438
- raise(
439
- response.code == "401" ? AuthenticationError : GoogleSpreadsheet::Error,
440
- "Response code #{response.code} for #{method} #{url}: " +
441
- CGI.unescapeHTML(response.body))
442
- end
443
- return convert_response(response, response_type)
444
- end
445
- end
446
-
447
- def inspect
448
- return '#<%p:0x%x>' % [self.class, self.object_id]
449
- end
450
-
451
- private
452
-
453
- def convert_response(response, response_type)
454
- case response_type
455
- when :xml
456
- return Nokogiri.XML(response.body)
457
- when :raw
458
- return response.body
459
- else
460
- raise("unknown params[:response_type]: %s" % response_type)
461
- end
462
- end
463
-
464
- def authenticate(mail, password, auth)
465
- params = {
466
- "accountType" => "HOSTED_OR_GOOGLE",
467
- "Email" => mail,
468
- "Passwd" => password,
469
- "service" => auth.to_s(),
470
- "source" => "Gimite-RubyGoogleSpreadsheet-1.00",
471
- }
472
- header = {"Content-Type" => "application/x-www-form-urlencoded"}
473
- response = request(:post,
474
- "https://www.google.com/accounts/ClientLogin",
475
- :data => encode_query(params), :auth => :none, :header => header, :response_type => :raw)
476
- return response.slice(/^Auth=(.*)$/, 1)
477
- end
478
-
479
- end
480
-
481
-
482
- # Use methods in GoogleSpreadsheet::Session to get GoogleSpreadsheet::Spreadsheet object.
483
- class Spreadsheet
484
-
485
- include(Util)
486
-
487
- SUPPORTED_EXPORT_FORMAT = Set.new(["xls", "csv", "pdf", "ods", "tsv", "html"])
488
-
489
- def initialize(session, worksheets_feed_url, title = nil) #:nodoc:
490
- @session = session
491
- @worksheets_feed_url = worksheets_feed_url
492
- @title = title
493
- end
494
-
495
- # URL of worksheet-based feed of the spreadsheet.
496
- attr_reader(:worksheets_feed_url)
497
-
498
- # Title of the spreadsheet.
499
- #
500
- # Set params[:reload] to true to force reloading the title.
501
- def title(params = {})
502
- if !@title || params[:reload]
503
- @title = spreadsheet_feed_entry(params).css("title").text
504
- end
505
- return @title
506
- end
507
-
508
- # Key of the spreadsheet.
509
- def key
510
- if !(@worksheets_feed_url =~
511
- %r{^https?://spreadsheets.google.com/feeds/worksheets/(.*)/private/.*$})
512
- raise(GoogleSpreadsheet::Error,
513
- "worksheets feed URL is in unknown format: #{@worksheets_feed_url}")
514
- end
515
- return $1
516
- end
517
-
518
- # Spreadsheet feed URL of the spreadsheet.
519
- def spreadsheet_feed_url
520
- return "https://spreadsheets.google.com/feeds/spreadsheets/private/full/#{self.key}"
521
- end
522
-
523
- # URL which you can open the spreadsheet in a Web browser with.
524
- #
525
- # e.g. "http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg"
526
- def human_url
527
- # Uses Document feed because Spreadsheet feed returns wrong URL for Apps account.
528
- return self.document_feed_entry.css("link[@rel='alternate']").first["href"]
529
- end
530
-
531
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
532
- # March 2012.
533
- #
534
- # Tables feed URL of the spreadsheet.
535
- def tables_feed_url
536
- warn(
537
- "DEPRECATED: Google Spreadsheet Table and Record feeds are deprecated and they will " +
538
- "not be available after March 2012.")
539
- return "https://spreadsheets.google.com/feeds/#{self.key}/tables"
540
- end
541
-
542
- # URL of feed used in document list feed API.
543
- def document_feed_url
544
- return "https://docs.google.com/feeds/documents/private/full/spreadsheet%3A#{self.key}"
545
- end
546
-
547
- # <entry> element of spreadsheet feed as Nokogiri::XML::Element.
548
- #
549
- # Set params[:reload] to true to force reloading the feed.
550
- def spreadsheet_feed_entry(params = {})
551
- if !@spreadsheet_feed_entry || params[:reload]
552
- @spreadsheet_feed_entry =
553
- @session.request(:get, self.spreadsheet_feed_url).css("entry").first
554
- end
555
- return @spreadsheet_feed_entry
556
- end
557
-
558
- # <entry> element of document list feed as Nokogiri::XML::Element.
559
- #
560
- # Set params[:reload] to true to force reloading the feed.
561
- def document_feed_entry(params = {})
562
- if !@document_feed_entry || params[:reload]
563
- @document_feed_entry =
564
- @session.request(:get, self.document_feed_url, :auth => :writely).css("entry").first
565
- end
566
- return @document_feed_entry
567
- end
568
-
569
- # Creates copy of this spreadsheet with the given title.
570
- def duplicate(new_title = nil)
571
- new_title ||= (self.title ? "Copy of " + self.title : "Untitled")
572
- post_url = "https://docs.google.com/feeds/default/private/full/"
573
- header = {"GData-Version" => "3.0", "Content-Type" => "application/atom+xml"}
574
- xml = <<-"EOS"
575
- <entry xmlns='http://www.w3.org/2005/Atom'>
576
- <id>#{h(self.document_feed_url)}</id>
577
- <title>#{h(new_title)}</title>
578
- </entry>
579
- EOS
580
- doc = @session.request(:post, post_url, :data => xml, :header => header, :auth => :writely)
581
- ss_url = doc.css(
582
- "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']").first["href"]
583
- return Spreadsheet.new(@session, ss_url, new_title)
584
- end
585
-
586
- # If +permanent+ is +false+, moves the spreadsheet to the trash.
587
- # If +permanent+ is +true+, deletes the spreadsheet permanently.
588
- def delete(permanent = false)
589
- @session.request(:delete,
590
- self.document_feed_url + (permanent ? "?delete=true" : ""),
591
- :auth => :writely, :header => {"If-Match" => "*"})
592
- end
593
-
594
- # Renames title of the spreadsheet.
595
- def rename(title)
596
- doc = @session.request(:get, self.document_feed_url, :auth => :writely)
597
- edit_url = doc.css("link[@rel='edit']").first["href"]
598
- xml = <<-"EOS"
599
- <atom:entry
600
- xmlns:atom="http://www.w3.org/2005/Atom"
601
- xmlns:docs="http://schemas.google.com/docs/2007">
602
- <atom:category
603
- scheme="http://schemas.google.com/g/2005#kind"
604
- term="http://schemas.google.com/docs/2007#spreadsheet" label="spreadsheet"/>
605
- <atom:title>#{h(title)}</atom:title>
606
- </atom:entry>
607
- EOS
608
-
609
- @session.request(:put, edit_url, :data => xml, :auth => :writely)
610
- end
611
-
612
- alias title= rename
613
-
614
- # Exports the spreadsheet in +format+ and returns it as String.
615
- #
616
- # +format+ can be either "xls", "csv", "pdf", "ods", "tsv" or "html".
617
- # In format such as "csv", only the worksheet specified with +worksheet_index+ is exported.
618
- def export_as_string(format, worksheet_index = nil)
619
- gid_param = worksheet_index ? "&gid=#{worksheet_index}" : ""
620
- url =
621
- "https://spreadsheets.google.com/feeds/download/spreadsheets/Export" +
622
- "?key=#{key}&exportFormat=#{format}#{gid_param}"
623
- return @session.request(:get, url, :response_type => :raw)
624
- end
625
-
626
- # Exports the spreadsheet in +format+ as a local file.
627
- #
628
- # +format+ can be either "xls", "csv", "pdf", "ods", "tsv" or "html".
629
- # If +format+ is nil, it is guessed from the file name.
630
- # In format such as "csv", only the worksheet specified with +worksheet_index+ is exported.
631
- def export_as_file(local_path, format = nil, worksheet_index = nil)
632
- if !format
633
- format = File.extname(local_path).gsub(/^\./, "")
634
- if !SUPPORTED_EXPORT_FORMAT.include?(format)
635
- raise(ArgumentError,
636
- ("Cannot guess format from the file name: %s\n" +
637
- "Specify format argument explicitly") %
638
- local_path)
639
- end
640
- end
641
- open(local_path, "wb") do |f|
642
- f.write(export_as_string(format, worksheet_index))
643
- end
644
- end
645
-
646
- # Returns worksheets of the spreadsheet as array of GoogleSpreadsheet::Worksheet.
647
- def worksheets
648
- doc = @session.request(:get, @worksheets_feed_url)
649
- result = []
650
- doc.css('entry').each() do |entry|
651
- title = entry.css('title').text
652
- url = entry.css(
653
- "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']").first['href']
654
- result.push(Worksheet.new(@session, self, url, title))
655
- end
656
- return result.freeze()
657
- end
658
-
659
- # Returns a GoogleSpreadsheet::Worksheet with the given title in the spreadsheet.
660
- #
661
- # Returns nil if not found. Returns the first one when multiple worksheets with the
662
- # title are found.
663
- def worksheet_by_title(title)
664
- return self.worksheets.find(){ |ws| ws.title == title }
665
- end
666
-
667
- # Adds a new worksheet to the spreadsheet. Returns added GoogleSpreadsheet::Worksheet.
668
- def add_worksheet(title, max_rows = 100, max_cols = 20)
669
- xml = <<-"EOS"
670
- <entry xmlns='http://www.w3.org/2005/Atom'
671
- xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
672
- <title>#{h(title)}</title>
673
- <gs:rowCount>#{h(max_rows)}</gs:rowCount>
674
- <gs:colCount>#{h(max_cols)}</gs:colCount>
675
- </entry>
676
- EOS
677
- doc = @session.request(:post, @worksheets_feed_url, :data => xml)
678
- url = doc.css(
679
- "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']").first['href']
680
- return Worksheet.new(@session, self, url, title)
681
- end
682
-
683
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
684
- # March 2012.
685
- #
686
- # Returns list of tables in the spreadsheet.
687
- def tables
688
- warn(
689
- "DEPRECATED: Google Spreadsheet Table and Record feeds are deprecated and they will " +
690
- "not be available after March 2012.")
691
- doc = @session.request(:get, self.tables_feed_url)
692
- return doc.css('entry').map(){ |e| Table.new(@session, e) }.freeze()
693
- end
694
-
695
- def inspect
696
- fields = {:worksheets_feed_url => self.worksheets_feed_url}
697
- fields[:title] = @title if @title
698
- return '#<%p %s>' % [self.class, fields.map(){ |k, v| "%s=%p" % [k, v] }.join(", ")]
699
- end
700
-
701
- end
702
-
703
- # Use GoogleSpreadsheet::Session#collection_by_url to get GoogleSpreadsheet::Collection object.
704
- class Collection
705
-
706
- include(Util)
707
-
708
- def initialize(session, collection_feed_url) #:nodoc:
709
- @session = session
710
- @collection_feed_url = collection_feed_url
711
- end
712
-
713
- # Adds the given GoogleSpreadsheet::Spreadsheet to the collection.
714
- def add(spreadsheet)
715
- contents_url = concat_url(@collection_feed_url, "/contents")
716
- header = {"GData-Version" => "3.0", "Content-Type" => "application/atom+xml"}
717
- xml = <<-"EOS"
718
- <entry xmlns="http://www.w3.org/2005/Atom">
719
- <id>#{h(spreadsheet.document_feed_url)}</id>
720
- </entry>
721
- EOS
722
- @session.request(
723
- :post, contents_url, :data => xml, :header => header, :auth => :writely)
724
- return nil
725
- end
726
-
727
- # Returns all the spreadsheets in the collection.
728
- def spreadsheets
729
- contents_url = concat_url(@collection_feed_url, "/contents")
730
- header = {"GData-Version" => "3.0", "Content-Type" => "application/atom+xml"}
731
- doc = @session.request(:get, contents_url, :header => header, :auth => :writely)
732
-
733
- return doc.css("feed > entry").map() do |entry|
734
- title = entry.css("title").text
735
- url = entry.css(
736
- "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
737
- GoogleSpreadsheet::Spreadsheet.new(@session, url, title)
738
- end
739
- end
740
-
741
- # TODO Add other operations.
742
-
743
- end
744
-
745
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
746
- # March 2012.
747
- #
748
- # Use GoogleSpreadsheet::Worksheet#add_table to create table.
749
- # Use GoogleSpreadsheet::Worksheet#tables to get GoogleSpreadsheet::Table objects.
750
- class Table
751
-
752
- include(Util)
753
-
754
- def initialize(session, entry) #:nodoc:
755
- @columns = {}
756
- @worksheet_title = entry.css('gs|worksheet').first['name']
757
- @records_url = entry.css("content")[0]["src"]
758
- @edit_url = entry.css("link[@rel='edit']")[0]['href']
759
- @session = session
760
- end
761
-
762
- # Title of the worksheet the table belongs to.
763
- attr_reader(:worksheet_title)
764
-
765
- # Adds a record.
766
- def add_record(values)
767
- fields = ""
768
- values.each() do |name, value|
769
- fields += "<gs:field name='#{h(name)}'>#{h(value)}</gs:field>"
770
- end
771
- xml =<<-EOS
772
- <entry
773
- xmlns="http://www.w3.org/2005/Atom"
774
- xmlns:gs="http://schemas.google.com/spreadsheets/2006">
775
- #{fields}
776
- </entry>
777
- EOS
778
- @session.request(:post, @records_url, :data => xml)
779
- end
780
-
781
- # Returns records in the table.
782
- def records
783
- doc = @session.request(:get, @records_url)
784
- return doc.css('entry').map(){ |e| Record.new(@session, e) }
785
- end
786
-
787
- # Deletes this table. Deletion takes effect right away without calling save().
788
- def delete
789
- @session.request(:delete, @edit_url, :header => {"If-Match" => "*"})
790
- end
791
-
792
- end
793
-
794
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
795
- # March 2012.
796
- #
797
- # Use GoogleSpreadsheet::Table#records to get GoogleSpreadsheet::Record objects.
798
- class Record < Hash
799
- include(Util)
800
-
801
- def initialize(session, entry) #:nodoc:
802
- @session = session
803
- entry.css('gs|field').each() do |field|
804
- self[field["name"]] = field.inner_text
805
- end
806
- end
807
-
808
- def inspect #:nodoc:
809
- content = self.map(){ |k, v| "%p => %p" % [k, v] }.join(", ")
810
- return "\#<%p:{%s}>" % [self.class, content]
811
- end
812
-
813
- end
814
-
815
- # A worksheet (i.e. a tab) in a spreadsheet.
816
- # Use GoogleSpreadsheet::Spreadsheet#worksheets to get GoogleSpreadsheet::Worksheet object.
817
- class Worksheet
818
-
819
- include(Util)
820
-
821
- def initialize(session, spreadsheet, cells_feed_url, title = nil) #:nodoc:
822
- @session = session
823
- @spreadsheet = spreadsheet
824
- @cells_feed_url = cells_feed_url
825
- @title = title
826
-
827
- @cells = nil
828
- @input_values = nil
829
- @modified = Set.new()
830
- @list = nil
831
- end
832
-
833
- # URL of cell-based feed of the worksheet.
834
- attr_reader(:cells_feed_url)
835
-
836
- # URL of worksheet feed URL of the worksheet.
837
- def worksheet_feed_url
838
- # I don't know good way to get worksheet feed URL from cells feed URL.
839
- # Probably it would be cleaner to keep worksheet feed URL and get cells feed URL
840
- # from it.
841
- if !(@cells_feed_url =~
842
- %r{^https?://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full((\?.*)?)$})
843
- raise(GoogleSpreadsheet::Error,
844
- "cells feed URL is in unknown format: #{@cells_feed_url}")
845
- end
846
- return "https://spreadsheets.google.com/feeds/worksheets/#{$1}/private/full/#{$2}#{$3}"
847
- end
848
-
849
- # GoogleSpreadsheet::Spreadsheet which this worksheet belongs to.
850
- def spreadsheet
851
- if !@spreadsheet
852
- if !(@cells_feed_url =~
853
- %r{^https?://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full(\?.*)?$})
854
- raise(GoogleSpreadsheet::Error,
855
- "cells feed URL is in unknown format: #{@cells_feed_url}")
856
- end
857
- @spreadsheet = @session.spreadsheet_by_key($1)
858
- end
859
- return @spreadsheet
860
- end
861
-
862
- # Returns content of the cell as String. Top-left cell is [1, 1].
863
- def [](row, col)
864
- return self.cells[[row, col]] || ""
865
- end
866
-
867
- # Updates content of the cell.
868
- # Note that update is not sent to the server until you call save().
869
- # Top-left cell is [1, 1].
870
- #
871
- # e.g.
872
- # worksheet[2, 1] = "hoge"
873
- # worksheet[1, 3] = "=A1+B1"
874
- def []=(row, col, value)
875
- reload() if !@cells
876
- @cells[[row, col]] = value
877
- @input_values[[row, col]] = value
878
- @modified.add([row, col])
879
- self.max_rows = row if row > @max_rows
880
- self.max_cols = col if col > @max_cols
881
- end
882
-
883
- # Returns the value or the formula of the cell. Top-left cell is [1, 1].
884
- #
885
- # If user input "=A1+B1" to cell [1, 3]:
886
- # worksheet[1, 3] #=> "3" for example
887
- # worksheet.input_value(1, 3) #=> "=RC[-2]+RC[-1]"
888
- def input_value(row, col)
889
- reload() if !@cells
890
- return @input_values[[row, col]] || ""
891
- end
892
-
893
- # Row number of the bottom-most non-empty row.
894
- def num_rows
895
- reload() if !@cells
896
- return @input_values.select(){ |(r, c), v| !v.empty? }.map(){ |(r, c), v| r }.max || 0
897
- end
898
-
899
- # Column number of the right-most non-empty column.
900
- def num_cols
901
- reload() if !@cells
902
- return @input_values.select(){ |(r, c), v| !v.empty? }.map(){ |(r, c), v| c }.max || 0
903
- end
904
-
905
- # Number of rows including empty rows.
906
- def max_rows
907
- reload() if !@cells
908
- return @max_rows
909
- end
910
-
911
- # Updates number of rows.
912
- # Note that update is not sent to the server until you call save().
913
- def max_rows=(rows)
914
- reload() if !@cells
915
- @max_rows = rows
916
- @meta_modified = true
917
- end
918
-
919
- # Number of columns including empty columns.
920
- def max_cols
921
- reload() if !@cells
922
- return @max_cols
923
- end
924
-
925
- # Updates number of columns.
926
- # Note that update is not sent to the server until you call save().
927
- def max_cols=(cols)
928
- reload() if !@cells
929
- @max_cols = cols
930
- @meta_modified = true
931
- end
932
-
933
- # Title of the worksheet (shown as tab label in Web interface).
934
- def title
935
- reload() if !@title
936
- return @title
937
- end
938
-
939
- # Updates title of the worksheet.
940
- # Note that update is not sent to the server until you call save().
941
- def title=(title)
942
- reload() if !@cells
943
- @title = title
944
- @meta_modified = true
945
- end
946
-
947
- def cells #:nodoc:
948
- reload() if !@cells
949
- return @cells
950
- end
951
-
952
- # An array of spreadsheet rows. Each row contains an array of
953
- # columns. Note that resulting array is 0-origin so
954
- # worksheet.rows[0][0] == worksheet[1, 1].
955
- def rows(skip = 0)
956
- nc = self.num_cols
957
- result = ((1 + skip)..self.num_rows).map() do |row|
958
- (1..nc).map(){ |col| self[row, col] }.freeze()
959
- end
960
- return result.freeze()
961
- end
962
-
963
- # Reloads content of the worksheets from the server.
964
- # Note that changes you made by []= etc. is discarded if you haven't called save().
965
- def reload()
966
- doc = @session.request(:get, @cells_feed_url)
967
- @max_rows = doc.css('gs|rowCount').text.to_i
968
- @max_cols = doc.css('gs|colCount').text.to_i
969
- @title = doc.css('feed > title')[0].text
970
-
971
- @cells = {}
972
- @input_values = {}
973
- doc.css('feed > entry').each() do |entry|
974
- cell = entry.css('gs|cell').first
975
- row = cell["row"].to_i()
976
- col = cell["col"].to_i()
977
- @cells[[row, col]] = cell.inner_text
978
- @input_values[[row, col]] = cell["inputValue"]
979
- end
980
- @modified.clear()
981
- @meta_modified = false
982
- return true
983
- end
984
-
985
- # Saves your changes made by []=, etc. to the server.
986
- def save()
987
- sent = false
988
-
989
- if @meta_modified
990
-
991
- ws_doc = @session.request(:get, self.worksheet_feed_url)
992
- edit_url = ws_doc.css("link[@rel='edit']").first['href']
993
- xml = <<-"EOS"
994
- <entry xmlns='http://www.w3.org/2005/Atom'
995
- xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
996
- <title>#{h(self.title)}</title>
997
- <gs:rowCount>#{h(self.max_rows)}</gs:rowCount>
998
- <gs:colCount>#{h(self.max_cols)}</gs:colCount>
999
- </entry>
1000
- EOS
1001
-
1002
- @session.request(:put, edit_url, :data => xml)
1003
-
1004
- @meta_modified = false
1005
- sent = true
1006
-
1007
- end
1008
-
1009
- if !@modified.empty?
1010
-
1011
- # Gets id and edit URL for each cell.
1012
- # Note that return-empty=true is required to get those info for empty cells.
1013
- cell_entries = {}
1014
- rows = @modified.map(){ |r, c| r }
1015
- cols = @modified.map(){ |r, c| c }
1016
- url = concat_url(@cells_feed_url,
1017
- "?return-empty=true&min-row=#{rows.min}&max-row=#{rows.max}" +
1018
- "&min-col=#{cols.min}&max-col=#{cols.max}")
1019
- doc = @session.request(:get, url)
1020
-
1021
- doc.css('entry').each() do |entry|
1022
- row = entry.css('gs|cell').first['row'].to_i
1023
- col = entry.css('gs|cell').first['col'].to_i
1024
- cell_entries[[row, col]] = entry
1025
- end
1026
-
1027
- # Updates cell values using batch operation.
1028
- # If the data is large, we split it into multiple operations, otherwise batch may fail.
1029
- @modified.each_slice(250) do |chunk|
1030
-
1031
- xml = <<-EOS
1032
- <feed xmlns="http://www.w3.org/2005/Atom"
1033
- xmlns:batch="http://schemas.google.com/gdata/batch"
1034
- xmlns:gs="http://schemas.google.com/spreadsheets/2006">
1035
- <id>#{h(@cells_feed_url)}</id>
1036
- EOS
1037
- for row, col in chunk
1038
- value = @cells[[row, col]]
1039
- entry = cell_entries[[row, col]]
1040
- id = entry.css('id').text
1041
- edit_url = entry.css("link[@rel='edit']").first['href']
1042
- xml << <<-EOS
1043
- <entry>
1044
- <batch:id>#{h(row)},#{h(col)}</batch:id>
1045
- <batch:operation type="update"/>
1046
- <id>#{h(id)}</id>
1047
- <link rel="edit" type="application/atom+xml"
1048
- href="#{h(edit_url)}"/>
1049
- <gs:cell row="#{h(row)}" col="#{h(col)}" inputValue="#{h(value)}"/>
1050
- </entry>
1051
- EOS
1052
- end
1053
- xml << <<-"EOS"
1054
- </feed>
1055
- EOS
1056
-
1057
- batch_url = concat_url(@cells_feed_url, "/batch")
1058
- result = @session.request(:post, batch_url, :data => xml)
1059
- result.css('atom|entry').each() do |entry|
1060
- interrupted = entry.css('batch|interrupted').first
1061
- if interrupted
1062
- raise(GoogleSpreadsheet::Error, "Update has failed: %s" %
1063
- interrupted["reason"])
1064
- end
1065
- if !(entry.css('batch|status').first['code'] =~ /^2/)
1066
- raise(GoogleSpreadsheet::Error, "Updating cell %s has failed: %s" %
1067
- [entry.css('atom|id').text, entry.css('batch|status').first['reason']])
1068
- end
1069
- end
1070
-
1071
- end
1072
-
1073
- @modified.clear()
1074
- sent = true
1075
-
1076
- end
1077
- return sent
1078
- end
1079
-
1080
- # Calls save() and reload().
1081
- def synchronize()
1082
- save()
1083
- reload()
1084
- end
1085
-
1086
- # Deletes this worksheet. Deletion takes effect right away without calling save().
1087
- def delete()
1088
- ws_doc = @session.request(:get, self.worksheet_feed_url)
1089
- edit_url = ws_doc.css("link[@rel='edit']").first['href']
1090
- @session.request(:delete, edit_url)
1091
- end
1092
-
1093
- # Returns true if you have changes made by []= which haven't been saved.
1094
- def dirty?
1095
- return !@modified.empty?
1096
- end
1097
-
1098
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
1099
- # March 2012.
1100
- #
1101
- # Creates table for the worksheet and returns GoogleSpreadsheet::Table.
1102
- # See this document for details:
1103
- # http://code.google.com/intl/en/apis/spreadsheets/docs/3.0/developers_guide_protocol.html#TableFeeds
1104
- def add_table(table_title, summary, columns, options)
1105
- warn(
1106
- "DEPRECATED: Google Spreadsheet Table and Record feeds are deprecated and they will " +
1107
- "not be available after March 2012.")
1108
- default_options = { :header_row => 1, :num_rows => 0, :start_row => 2}
1109
- options = default_options.merge(options)
1110
-
1111
- column_xml = ""
1112
- columns.each() do |index, name|
1113
- column_xml += "<gs:column index='#{h(index)}' name='#{h(name)}'/>\n"
1114
- end
1115
-
1116
- xml = <<-"EOS"
1117
- <entry xmlns="http://www.w3.org/2005/Atom"
1118
- xmlns:gs="http://schemas.google.com/spreadsheets/2006">
1119
- <title type='text'>#{h(table_title)}</title>
1120
- <summary type='text'>#{h(summary)}</summary>
1121
- <gs:worksheet name='#{h(self.title)}' />
1122
- <gs:header row='#{options[:header_row]}' />
1123
- <gs:data numRows='#{options[:num_rows]}' startRow='#{options[:start_row]}'>
1124
- #{column_xml}
1125
- </gs:data>
1126
- </entry>
1127
- EOS
1128
-
1129
- result = @session.request(:post, self.spreadsheet.tables_feed_url, :data => xml)
1130
- return Table.new(@session, result)
1131
- end
1132
-
1133
- # Returns list of tables for the workwheet.
1134
- def tables
1135
- return self.spreadsheet.tables.select(){ |t| t.worksheet_title == self.title }
1136
- end
1137
-
1138
- # List feed URL of the worksheet.
1139
- def list_feed_url
1140
- # Gets the worksheets metafeed.
1141
- entry = @session.request(:get, self.worksheet_feed_url)
1142
-
1143
- # Gets the URL of list-based feed for the given spreadsheet.
1144
- return entry.css(
1145
- "link[@rel='http://schemas.google.com/spreadsheets/2006#listfeed']").first['href']
1146
- end
1147
-
1148
- # Provides access to cells using column names, assuming the first row contains column
1149
- # names. Returned object is GoogleSpreadsheet::List which you can use mostly as
1150
- # Array of Hash.
1151
- #
1152
- # e.g. Assuming the first row is ["x", "y"]:
1153
- # worksheet.list[0]["x"] #=> "1" # i.e. worksheet[2, 1]
1154
- # worksheet.list[0]["y"] #=> "2" # i.e. worksheet[2, 2]
1155
- # worksheet.list[1]["x"] = "3" # i.e. worksheet[3, 1] = "3"
1156
- # worksheet.list[1]["y"] = "4" # i.e. worksheet[3, 2] = "4"
1157
- # worksheet.list.push({"x" => "5", "y" => "6"})
1158
- #
1159
- # Note that update is not sent to the server until you call save().
1160
- def list
1161
- return @list ||= List.new(self)
1162
- end
1163
-
1164
- def inspect
1165
- fields = {:worksheet_feed_url => self.worksheet_feed_url}
1166
- fields[:title] = @title if @title
1167
- return '#<%p %s>' % [self.class, fields.map(){ |k, v| "%s=%p" % [k, v] }.join(", ")]
1168
- end
1169
-
1170
- end
1171
-
1172
-
1173
- # Provides access to cells using column names.
1174
- # Use GoogleSpreadsheet::Worksheet#list to get GoogleSpreadsheet::List object.
1175
- #--
1176
- # This is implemented as wrapper of GoogleSpreadsheet::Worksheet i.e. using cells
1177
- # feed, not list feed. In this way, we can easily provide consistent API as
1178
- # GoogleSpreadsheet::Worksheet using save()/reload().
1179
- class List
1180
-
1181
- include(Enumerable)
1182
-
1183
- def initialize(worksheet) #:nodoc:
1184
- @worksheet = worksheet
1185
- end
1186
-
1187
- # Number of non-empty rows in the worksheet excluding the first row.
1188
- def size
1189
- return @worksheet.num_rows - 1
1190
- end
1191
-
1192
- # Returns Hash-like object (GoogleSpreadsheet::ListRow) for the row with the
1193
- # index. Keys of the object are colum names (the first row).
1194
- # The second row has index 0.
1195
- #
1196
- # Note that updates to the returned object are not sent to the server until
1197
- # you call GoogleSpreadsheet::Worksheet#save().
1198
- def [](index)
1199
- return ListRow.new(self, index)
1200
- end
1201
-
1202
- # Updates the row with the index with the given Hash object.
1203
- # Keys of +hash+ are colum names (the first row).
1204
- # The second row has index 0.
1205
- #
1206
- # Note that update is not sent to the server until
1207
- # you call GoogleSpreadsheet::Worksheet#save().
1208
- def []=(index, hash)
1209
- self[index].replace(hash)
1210
- end
1211
-
1212
- # Iterates over Hash-like object (GoogleSpreadsheet::ListRow) for each row
1213
- # (except for the first row).
1214
- # Keys of the object are colum names (the first row).
1215
- def each(&block)
1216
- for i in 0...self.size
1217
- yield(self[i])
1218
- end
1219
- end
1220
-
1221
- # Column names i.e. the contents of the first row.
1222
- # Duplicates are removed.
1223
- def keys
1224
- return (1..@worksheet.num_cols).map(){ |i| @worksheet[1, i] }.uniq()
1225
- end
1226
-
1227
- # Updates column names i.e. the contents of the first row.
1228
- #
1229
- # Note that update is not sent to the server until
1230
- # you call GoogleSpreadsheet::Worksheet#save().
1231
- def keys=(ary)
1232
- for i in 1..ary.size
1233
- @worksheet[1, i] = ary[i - 1]
1234
- end
1235
- for i in (ary.size + 1)..@worksheet.num_cols
1236
- @worksheet[1, i] = ""
1237
- end
1238
- end
1239
-
1240
- # Adds a new row to the bottom.
1241
- # Keys of +hash+ are colum names (the first row).
1242
- # Returns GoogleSpreadsheet::ListRow for the new row.
1243
- #
1244
- # Note that update is not sent to the server until
1245
- # you call GoogleSpreadsheet::Worksheet#save().
1246
- def push(hash)
1247
- row = self[self.size]
1248
- row.update(hash)
1249
- return row
1250
- end
1251
-
1252
- # Returns all rows (except for the first row) as Array of Hash.
1253
- # Keys of Hash objects are colum names (the first row).
1254
- def to_hash_array()
1255
- return self.map(){ |r| r.to_hash() }
1256
- end
1257
-
1258
- def get(index, key) #:nodoc:
1259
- return @worksheet[index + 2, key_to_col(key)]
1260
- end
1261
-
1262
- def set(index, key, value) #:nodoc:
1263
- @worksheet[index + 2, key_to_col(key)] = value
1264
- end
1265
-
1266
- private
1267
-
1268
- def key_to_col(key)
1269
- key = key.to_s()
1270
- col = (1..@worksheet.num_cols).find(){ |c| @worksheet[1, c] == key }
1271
- raise(GoogleSpreadsheet::Error, "colunm doesn't exist: %p" % key) if !col
1272
- return col
1273
- end
1274
-
1275
- end
1276
-
1277
- # Hash-like object returned by GoogleSpreadsheet::List#[].
1278
- class ListRow
1279
-
1280
- include(Enumerable)
1281
- extend(Forwardable)
1282
-
1283
- def_delegators(:to_hash,
1284
- :keys, :values, :each_key, :each_value, :each, :each_pair, :hash,
1285
- :assoc, :fetch, :flatten, :key, :invert, :size, :length, :rassoc,
1286
- :merge, :reject, :select, :sort, :to_a, :values_at)
1287
-
1288
- def initialize(list, index) #:nodoc:
1289
- @list = list
1290
- @index = index
1291
- end
1292
-
1293
- def [](key)
1294
- return @list.get(@index, key)
1295
- end
1296
-
1297
- def []=(key, value)
1298
- @list.set(@index, key, value)
1299
- end
1300
-
1301
- def has_key?(key)
1302
- return @list.keys.include?(key)
1303
- end
1304
-
1305
- alias include? has_key?
1306
- alias key? has_key?
1307
- alias member? has_key?
1308
-
1309
- def update(hash)
1310
- for k, v in hash
1311
- self[k] = v
1312
- end
1313
- end
1314
-
1315
- alias merge! update
1316
-
1317
- def replace(hash)
1318
- clear()
1319
- update(hash)
1320
- end
1321
-
1322
- def clear()
1323
- for key in @list.keys
1324
- self[key] = ""
1325
- end
1326
- end
1327
-
1328
- def to_hash()
1329
- result = {}
1330
- for key in @list.keys
1331
- result[key] = self[key]
1332
- end
1333
- return result
1334
- end
1335
-
1336
- def ==(other)
1337
- return self.class == other.class && self.to_hash() == other.to_hash()
1338
- end
1339
-
1340
- alias === ==
1341
- alias eql? ==
1342
-
1343
- def inspect
1344
- return "\#<%p %p>" % [self.class, to_hash()]
1345
- end
1346
-
1347
- end
1348
119
 
1349
120
  end