sprout-google-spreadsheet-ruby 0.0.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.
Files changed (3) hide show
  1. data/README.rdoc +84 -0
  2. data/lib/google_spreadsheet.rb +809 -0
  3. metadata +77 -0
@@ -0,0 +1,84 @@
1
+ This is a Ruby 1.8/1.9 library to read/write Google Spreadsheet.
2
+
3
+
4
+ = How to install
5
+
6
+ $ gem sources -a http://gems.github.com
7
+ $ sudo gem install gimite-google-spreadsheet-ruby
8
+
9
+
10
+ = How to use
11
+
12
+ Example:
13
+
14
+ require "rubygems"
15
+ require "google_spreadsheet"
16
+
17
+ # METHOD #1: With usual google username and password
18
+ # Logs in.
19
+ session = GoogleSpreadsheet.login("username@gmail.com", "mypassword")
20
+
21
+ # METHOD #2: Getting rid of usual google username and password,using google OAuth protocol
22
+ # 1) First generate OAuth consumer object with key and secret for your site by registering site with google
23
+ @consumer = OAuth::Consumer.new( "key","secret", {:site=>"https://agree2"})
24
+
25
+ # 2) Request token with OAuth
26
+ @request_token=@consumer.get_request_token
27
+ session[:request_token] = @request_token
28
+ redirect_to @request_token.authorize_url
29
+
30
+ # 3) Create an oauth access token
31
+ @oauth_access_token = @request_token.get_access_token
32
+ @access_token = OAuth::AccessToken.new(@consumer, @oauth_access_token.token, @oauth_access_token.secret)
33
+
34
+
35
+ # See these documents for details:
36
+ # http://oauth.rubyforge.org/
37
+ # http://code.google.com/apis/accounts/docs/OAuth.html
38
+
39
+ # Creates GoogleSpreadsheet object with access_token
40
+ session = GoogleSpreadsheet.authorized(@access_token)
41
+
42
+ # First worksheet of http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=en
43
+ ws = session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg").worksheets[0]
44
+
45
+ # Gets content of A2 cell.
46
+ p ws[2, 1] #==> "hoge"
47
+
48
+ # Changes content of cells. Changes are not sent to the server until you call ws.save().
49
+ ws[2, 1] = "foo"
50
+ ws[2, 2] = "bar"
51
+ ws.save()
52
+
53
+ # Dumps all cells.
54
+ for row in 1..ws.num_rows
55
+ for col in 1..ws.num_cols
56
+ p ws[row, col]
57
+ end
58
+ end
59
+
60
+ # Yet another way to do so.
61
+ p ws.rows #==> [["fuga", ""], ["foo", "bar]]
62
+
63
+ # Reloads the worksheet to get changes by other clients.
64
+ ws.reload()
65
+
66
+
67
+
68
+ API document: http://gimite.net/gimite/rubymess/google-spreadsheet-ruby/
69
+
70
+
71
+ = Source code
72
+
73
+ http://github.com/gimite/google-spreadsheet-ruby/tree/master
74
+
75
+ The license of this source is "New BSD Licence"
76
+
77
+
78
+ = Supported environments
79
+
80
+ Ruby 1.8.x and Ruby 1.9.x. Checked with Ruby 1.8.7 and Ruby 1.9.1.
81
+
82
+
83
+ = Author
84
+ Sprout Technology Services - http://sprout-technology.com
@@ -0,0 +1,809 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
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 "hpricot"
12
+ require "oauth"
13
+ Net::HTTP.version_1_2
14
+
15
+ module GoogleSpreadsheet
16
+
17
+ # Authenticates with given +mail+ and +password+, and returns GoogleSpreadsheet::Session
18
+ # if succeeds. Raises GoogleSpreadsheet::AuthenticationError if fails.
19
+ # Google Apps account is supported.
20
+ def self.login(mail, password)
21
+ return Session.login(mail, password)
22
+ end
23
+
24
+ # If already authorized by Google then just pass in your oauth_token to begin
25
+ # For generating oauth_token,you can proceed as follow:
26
+ # 1) First generate OAuth consumer object with key and secret for your site by registering site with google
27
+ # @consumer = OAuth::Consumer.new( "key","secret", {:site=>"https://agree2"})
28
+
29
+ # 2) Request token with OAuth
30
+ # @request_token=@consumer.get_request_token
31
+ # session[:request_token] = @request_token
32
+ # redirect_to @request_token.authorize_url
33
+
34
+ # 3) Create an oauth access token
35
+ # @oauth_access_token = @request_token.get_access_token
36
+ # @access_token = OAuth::AccessToken.new(@consumer, @oauth_access_token.token, @oauth_access_token.secret)
37
+
38
+ # See these documents for details:
39
+ # http://oauth.rubyforge.org/
40
+ # http://code.google.com/apis/accounts/docs/OAuth.html
41
+
42
+ def self.authorized(oauth_token)
43
+ return Session.authorized(oauth_token)
44
+ end
45
+
46
+ # Restores GoogleSpreadsheet::Session from +path+ and returns it.
47
+ # If +path+ doesn't exist or authentication has failed, prompts mail and password on console,
48
+ # authenticates with them, stores the session to +path+ and returns it.
49
+ #
50
+ # This method requires Highline library: http://rubyforge.org/projects/highline/
51
+ def self.saved_session(path = ENV["HOME"] + "/.ruby_google_spreadsheet.token")
52
+ tokens = {}
53
+ if File.exist?(path)
54
+ open(path) do |f|
55
+ for auth in [:wise, :writely]
56
+ line = f.gets()
57
+ tokens[auth] = line && line.chomp()
58
+ end
59
+ end
60
+ end
61
+ session = Session.new(tokens)
62
+ session.on_auth_fail = proc() do
63
+ begin
64
+ require "highline"
65
+ rescue LoadError
66
+ raise(LoadError,
67
+ "GoogleSpreadsheet.saved_session requires Highline library.\n" +
68
+ "Run\n" +
69
+ " \$ sudo gem install highline\n" +
70
+ "to install it.")
71
+ end
72
+ highline = HighLine.new()
73
+ mail = highline.ask("Mail: ")
74
+ password = highline.ask("Password: "){ |q| q.echo = false }
75
+ session.login(mail, password)
76
+ open(path, "w", 0600) do |f|
77
+ f.puts(session.auth_token(:wise))
78
+ f.puts(session.auth_token(:writely))
79
+ end
80
+ true
81
+ end
82
+ if !session.auth_token
83
+ session.on_auth_fail.call()
84
+ end
85
+ return session
86
+ end
87
+
88
+
89
+ module Util #:nodoc:
90
+
91
+ module_function
92
+
93
+ def encode_query(params)
94
+ return params.map(){ |k, v| CGI.escape(k) + "=" + CGI.escape(v) }.join("&")
95
+ end
96
+
97
+ def h(str)
98
+ return CGI.escapeHTML(str.to_s())
99
+ end
100
+
101
+ def as_utf8(str)
102
+ if str.respond_to?(:force_encoding)
103
+ str.force_encoding("UTF-8")
104
+ else
105
+ str
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+
112
+ # Raised when spreadsheets.google.com has returned error.
113
+ class Error < RuntimeError
114
+
115
+ end
116
+
117
+
118
+ # Raised when GoogleSpreadsheet.login has failed.
119
+ class AuthenticationError < GoogleSpreadsheet::Error
120
+
121
+ end
122
+
123
+
124
+ # Use GoogleSpreadsheet.login or GoogleSpreadsheet.saved_session to get
125
+ # GoogleSpreadsheet::Session object.
126
+ class Session
127
+
128
+ include(Util)
129
+ extend(Util)
130
+
131
+ # The same as GoogleSpreadsheet.login.
132
+ def self.login(mail, password)
133
+ session = Session.new()
134
+ session.login(mail, password)
135
+ return session
136
+ end
137
+
138
+ def self.authorized(oauth_token)
139
+ session = Session.new(nil, oauth_token)
140
+ end
141
+
142
+ # Restores session using return value of auth_tokens method of previous session.
143
+ # You can use oauth_token instead of gmail username and password if you have used Google hybrid protocol (Open ID+ OAuth)
144
+ def initialize(auth_tokens, oauth_token)
145
+ if oauth_token
146
+ @oauth_token = oauth_token
147
+ else
148
+ @auth_tokens = auth_tokens
149
+ end
150
+ end
151
+
152
+ # Authenticates with given +mail+ and +password+, and updates current session object
153
+ # if succeeds. Raises GoogleSpreadsheet::AuthenticationError if fails.
154
+ # Google Apps account is supported.
155
+ def login(mail, password)
156
+ begin
157
+ @auth_tokens = {}
158
+ authenticate(mail, password, :wise)
159
+ authenticate(mail, password, :writely)
160
+ rescue GoogleSpreadsheet::Error => ex
161
+ return true if @on_auth_fail && @on_auth_fail.call()
162
+ raise(AuthenticationError, "authentication failed for #{mail}: #{ex.message}")
163
+ end
164
+ end
165
+
166
+ # Authentication tokens.
167
+ attr_reader(:auth_tokens)
168
+
169
+ # Authentication token.
170
+ def auth_token(auth = :wise)
171
+ return @auth_tokens[auth]
172
+ end
173
+
174
+ # Proc or Method called when authentication has failed.
175
+ # When this function returns +true+, it tries again.
176
+ attr_accessor :on_auth_fail
177
+
178
+ def auth_header(auth) #:nodoc:
179
+ if auth == :none
180
+ return {}
181
+ else
182
+ return {"Authorization" => "GoogleLogin auth=#{@auth_tokens[auth]}"}
183
+ end
184
+ end
185
+
186
+ # Returns list of spreadsheets for the user as array of GoogleSpreadsheet::Spreadsheet.
187
+ # You can specify query parameters described at
188
+ # http://code.google.com/apis/spreadsheets/docs/2.0/reference.html#Parameters
189
+ #
190
+ # e.g.
191
+ # session.spreadsheets
192
+ # session.spreadsheets("title" => "hoge")
193
+ def spreadsheets(params = {})
194
+ query = encode_query(params)
195
+ doc = request(:get, "http://spreadsheets.google.com/feeds/spreadsheets/private/full?#{query}")
196
+ result = []
197
+ for entry in doc.search("entry")
198
+ title = as_utf8(entry.search("title").text)
199
+ url = as_utf8(entry.search(
200
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"])
201
+ result.push(Spreadsheet.new(self, url, title))
202
+ end
203
+ return result
204
+ end
205
+
206
+ # Returns GoogleSpreadsheet::Spreadsheet with given +key+.
207
+ #
208
+ # e.g.
209
+ # # http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=ja
210
+ # session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg")
211
+ def spreadsheet_by_key(key)
212
+ url = "http://spreadsheets.google.com/feeds/worksheets/#{key}/private/full"
213
+ return Spreadsheet.new(self, url)
214
+ end
215
+
216
+ # Returns GoogleSpreadsheet::Spreadsheet with given +url+. You must specify either of:
217
+ # - URL of the page you open to access the spreadsheet in your browser
218
+ # - URL of worksheet-based feed of the spreadseet
219
+ #
220
+ # e.g.
221
+ # session.spreadsheet_by_url(
222
+ # "http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=en")
223
+ # session.spreadsheet_by_url(
224
+ # "http://spreadsheets.google.com/feeds/worksheets/pz7XtlQC-PYx-jrVMJErTcg/private/full")
225
+ def spreadsheet_by_url(url)
226
+ # Tries to parse it as URL of human-readable spreadsheet.
227
+ uri = URI.parse(url)
228
+ if uri.host == "spreadsheets.google.com" && uri.path =~ /\/ccc$/
229
+ if (uri.query || "").split(/&/).find(){ |s| s=~ /^key=(.*)$/ }
230
+ return spreadsheet_by_key($1)
231
+ end
232
+ end
233
+ # Assumes the URL is worksheets feed URL.
234
+ return Spreadsheet.new(self, url)
235
+ end
236
+
237
+ # Returns GoogleSpreadsheet::Worksheet with given +url+.
238
+ # You must specify URL of cell-based feed of the worksheet.
239
+ #
240
+ # e.g.
241
+ # session.worksheet_by_url(
242
+ # "http://spreadsheets.google.com/feeds/cells/pz7XtlQC-PYxNmbBVgyiNWg/od6/private/full")
243
+ def worksheet_by_url(url)
244
+ return Worksheet.new(self, nil, url)
245
+ end
246
+
247
+ # Creates new spreadsheet and returns the new GoogleSpreadsheet::Spreadsheet.
248
+ #
249
+ # e.g.
250
+ # session.create_spreadsheet("My new sheet")
251
+ def create_spreadsheet(
252
+ title = "Untitled",
253
+ feed_url = "http://docs.google.com/feeds/documents/private/full")
254
+ xml = <<-"EOS"
255
+ <atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007">
256
+ <atom:category scheme="http://schemas.google.com/g/2005#kind"
257
+ term="http://schemas.google.com/docs/2007#spreadsheet" label="spreadsheet"/>
258
+ <atom:title>#{h(title)}</atom:title>
259
+ </atom:entry>
260
+ EOS
261
+
262
+ doc = request(:post, feed_url, :data => xml, :auth => :writely)
263
+ ss_url = as_utf8(doc.search(
264
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"])
265
+ return Spreadsheet.new(self, ss_url, title)
266
+ end
267
+
268
+ def request(method, url, params = {}) #:nodoc:
269
+ if @oauth_token
270
+ if method == :get
271
+ response = @oauth_token.get(url)
272
+ return Hpricot.XML(response.body)
273
+ elsif method == :post
274
+ response = @oauth_token.post(url,params[:data])
275
+ return Hpricot.XML(response.body)
276
+ end
277
+ else
278
+ uri = URI.parse(url)
279
+ data = params[:data]
280
+ auth = params[:auth] || :wise
281
+ if params[:header]
282
+ add_header = params[:header]
283
+ else
284
+ add_header = data ? {"Content-Type" => "application/atom+xml"} : {}
285
+ end
286
+ response_type = params[:response_type] || :xml
287
+
288
+ http = Net::HTTP.new(uri.host, uri.port)
289
+ http.use_ssl = uri.scheme == "https"
290
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
291
+ http.start() do
292
+ while true
293
+ path = uri.path + (uri.query ? "?#{uri.query}" : "")
294
+ header = auth_header(auth).merge(add_header)
295
+ if method == :delete || method == :get
296
+ response = http.__send__(method, path, header)
297
+ else
298
+ response = http.__send__(method, path, data, header)
299
+ end
300
+ if response.code == "401" && @on_auth_fail && @on_auth_fail.call()
301
+ next
302
+ end
303
+ if !(response.code =~ /^2/)
304
+ raise(
305
+ response.code == "401" ? AuthenticationError : GoogleSpreadsheet::Error,
306
+ "Response code #{response.code} for #{method} #{url}: " +
307
+ CGI.unescapeHTML(response.body))
308
+ end
309
+ case response_type
310
+ when :xml
311
+ return Hpricot.XML(response.body)
312
+ when :raw
313
+ return response.body
314
+ else
315
+ raise("unknown params[:response_type]: %s" % response_type)
316
+ end
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ private
323
+
324
+ def authenticate(mail, password, auth)
325
+ params = {
326
+ "accountType" => "HOSTED_OR_GOOGLE",
327
+ "Email" => mail,
328
+ "Passwd" => password,
329
+ "service" => auth.to_s(),
330
+ "source" => "Gimite-RubyGoogleSpreadsheet-1.00",
331
+ }
332
+ response = request(:post,
333
+ "https://www.google.com/accounts/ClientLogin",
334
+ :data => encode_query(params), :auth => :none, :header => {}, :response_type => :raw)
335
+ @auth_tokens[auth] = response.slice(/^Auth=(.*)$/, 1)
336
+ end
337
+
338
+ end
339
+
340
+
341
+ # Use methods in GoogleSpreadsheet::Session to get GoogleSpreadsheet::Spreadsheet object.
342
+ class Spreadsheet
343
+
344
+ include(Util)
345
+
346
+ def initialize(session, worksheets_feed_url, title = nil) #:nodoc:
347
+ @session = session
348
+ @worksheets_feed_url = worksheets_feed_url
349
+ @title = title
350
+ end
351
+
352
+ # URL of worksheet-based feed of the spreadsheet.
353
+ attr_reader(:worksheets_feed_url)
354
+
355
+ # Title of the spreadsheet. So far only available if you get this object by
356
+ # GoogleSpreadsheet::Session#spreadsheets.
357
+ attr_reader(:title)
358
+
359
+ # Key of the spreadsheet.
360
+ def key
361
+ if !(@worksheets_feed_url =~
362
+ %r{http://spreadsheets.google.com/feeds/worksheets/(.*)/private/full})
363
+ raise(GoogleSpreadsheet::Error,
364
+ "worksheets feed URL is in unknown format: #{@worksheets_feed_url}")
365
+ end
366
+ return $1
367
+ end
368
+
369
+ # Tables feed URL of the spreadsheet.
370
+ def tables_feed_url
371
+ return "http://spreadsheets.google.com/feeds/#{self.key}/tables"
372
+ end
373
+
374
+ # URL of feed used in document list feed API.
375
+ def document_feed_url
376
+ return "http://docs.google.com/feeds/documents/private/full/spreadsheet%3A#{self.key}"
377
+ end
378
+
379
+ # Creates copy of this spreadsheet with the given name.
380
+ def duplicate(new_name = nil)
381
+ new_name ||= (@title ? "Copy of " + @title : "Untitled")
382
+ get_url = "http://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=#{key}&exportFormat=ods"
383
+ ods = @session.request(:get, get_url, :response_type => :raw)
384
+
385
+ url = "http://docs.google.com/feeds/documents/private/full"
386
+ header = {
387
+ "Content-Type" => "application/x-vnd.oasis.opendocument.spreadsheet",
388
+ "Slug" => URI.encode(new_name),
389
+ }
390
+ doc = @session.request(:post, url, :data => ods, :auth => :writely, :header => header)
391
+ ss_url = as_utf8(doc.search(
392
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"])
393
+ return Spreadsheet.new(@session, ss_url, title)
394
+ end
395
+
396
+ # If +permanent+ is +false+, moves the spreadsheet to the trash.
397
+ # If +permanent+ is +true+, deletes the spreadsheet permanently.
398
+ def delete(permanent = false)
399
+ @session.request(:delete,
400
+ self.document_feed_url + (permanent ? "?delete=true" : ""),
401
+ :auth => :writely, :header => {"If-Match" => "*"})
402
+ end
403
+
404
+ # Returns worksheets of the spreadsheet as array of GoogleSpreadsheet::Worksheet.
405
+ def worksheets
406
+ doc = @session.request(:get, @worksheets_feed_url)
407
+ result = []
408
+ for entry in doc.search("entry")
409
+ title = as_utf8(entry.search("title").text)
410
+ url = as_utf8(entry.search(
411
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"])
412
+ result.push(Worksheet.new(@session, self, url, title))
413
+ end
414
+ return result.freeze()
415
+ end
416
+
417
+ # Adds a new worksheet to the spreadsheet. Returns added GoogleSpreadsheet::Worksheet.
418
+ def add_worksheet(title, max_rows = 100, max_cols = 20)
419
+ xml = <<-"EOS"
420
+ <entry xmlns='http://www.w3.org/2005/Atom'
421
+ xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
422
+ <title>#{h(title)}</title>
423
+ <gs:rowCount>#{h(max_rows)}</gs:rowCount>
424
+ <gs:colCount>#{h(max_cols)}</gs:colCount>
425
+ </entry>
426
+ EOS
427
+ doc = @session.request(:post, @worksheets_feed_url, :data => xml)
428
+ url = as_utf8(doc.search(
429
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"])
430
+ return Worksheet.new(@session, self, url, title)
431
+ end
432
+
433
+ # Returns list of tables in the spreadsheet.
434
+ def tables
435
+ doc = @session.request(:get, self.tables_feed_url)
436
+ return doc.search("entry").map(){ |e| Table.new(@session, e) }.freeze()
437
+ end
438
+
439
+ end
440
+
441
+ # Use GoogleSpreadsheet::Worksheet#add_table to create table.
442
+ # Use GoogleSpreadsheet::Worksheet#tables to get GoogleSpreadsheet::Table objects.
443
+ class Table
444
+
445
+ include(Util)
446
+
447
+ def initialize(session, entry) #:nodoc:
448
+ @columns = {}
449
+ @worksheet_title = as_utf8(entry.search("gs:worksheet")[0]["name"])
450
+ @records_url = as_utf8(entry.search("content")[0]["src"])
451
+ @session = session
452
+ end
453
+
454
+ # Title of the worksheet the table belongs to.
455
+ attr_reader(:worksheet_title)
456
+
457
+ # Adds a record.
458
+ def add_record(values)
459
+ fields = ""
460
+ values.each do |name, value|
461
+ fields += "<gs:field name='#{h(name)}'>#{h(value)}</gs:field>"
462
+ end
463
+ xml =<<-EOS
464
+ <entry
465
+ xmlns="http://www.w3.org/2005/Atom"
466
+ xmlns:gs="http://schemas.google.com/spreadsheets/2006">
467
+ #{fields}
468
+ </entry>
469
+ EOS
470
+ @session.request(:post, @records_url, :data => xml)
471
+ end
472
+
473
+ # Returns records in the table.
474
+ def records
475
+ doc = @session.request(:get, @records_url)
476
+ return doc.search("entry").map(){ |e| Record.new(@session, e) }
477
+ end
478
+
479
+ end
480
+
481
+ # Use GoogleSpreadsheet::Table#records to get GoogleSpreadsheet::Record objects.
482
+ class Record < Hash
483
+
484
+ def initialize(session, entry) #:nodoc:
485
+ @session = session
486
+ for field in entry.search("gs:field")
487
+ self[as_utf8(field["name"])] = as_utf8(field.inner_text)
488
+ end
489
+ end
490
+
491
+ def inspect #:nodoc:
492
+ content = self.map(){ |k, v| "%p => %p" % [k, v] }.join(", ")
493
+ return "\#<%p:{%s}>" % [self.class, content]
494
+ end
495
+
496
+ end
497
+
498
+ # Use GoogleSpreadsheet::Spreadsheet#worksheets to get GoogleSpreadsheet::Worksheet object.
499
+ class Worksheet
500
+
501
+ include(Util)
502
+
503
+ def initialize(session, spreadsheet, cells_feed_url, title = nil) #:nodoc:
504
+ @session = session
505
+ @spreadsheet = spreadsheet
506
+ @cells_feed_url = cells_feed_url
507
+ @title = title
508
+
509
+ @cells = nil
510
+ @input_values = nil
511
+ @modified = Set.new()
512
+ end
513
+
514
+ # URL of cell-based feed of the worksheet.
515
+ attr_reader(:cells_feed_url)
516
+
517
+ # URL of worksheet feed URL of the worksheet.
518
+ def worksheet_feed_url
519
+ # I don't know good way to get worksheet feed URL from cells feed URL.
520
+ # Probably it would be cleaner to keep worksheet feed URL and get cells feed URL
521
+ # from it.
522
+ if !(@cells_feed_url =~
523
+ %r{^http://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full$})
524
+ raise(GoogleSpreadsheet::Error,
525
+ "cells feed URL is in unknown format: #{@cells_feed_url}")
526
+ end
527
+ return "http://spreadsheets.google.com/feeds/worksheets/#{$1}/private/full/#{$2}"
528
+ end
529
+
530
+ # GoogleSpreadsheet::Spreadsheet which this worksheet belongs to.
531
+ def spreadsheet
532
+ if !@spreadsheet
533
+ if !(@cells_feed_url =~
534
+ %r{^http://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full$})
535
+ raise(GoogleSpreadsheet::Error,
536
+ "cells feed URL is in unknown format: #{@cells_feed_url}")
537
+ end
538
+ @spreadsheet = @session.spreadsheet_by_key($1)
539
+ end
540
+ return @spreadsheet
541
+ end
542
+
543
+ # Returns content of the cell as String. Top-left cell is [1, 1].
544
+ def [](row, col)
545
+ return self.cells[[row, col]] || ""
546
+ end
547
+
548
+ # Updates content of the cell.
549
+ # Note that update is not sent to the server until you call save().
550
+ # Top-left cell is [1, 1].
551
+ #
552
+ # e.g.
553
+ # worksheet[2, 1] = "hoge"
554
+ # worksheet[1, 3] = "=A1+B1"
555
+ def []=(row, col, value)
556
+ reload() if !@cells
557
+ @cells[[row, col]] = value
558
+ @input_values[[row, col]] = value
559
+ @modified.add([row, col])
560
+ self.max_rows = row if row > @max_rows
561
+ self.max_cols = col if col > @max_cols
562
+ end
563
+
564
+ # Returns the value or the formula of the cell. Top-left cell is [1, 1].
565
+ #
566
+ # If user input "=A1+B1" to cell [1, 3], worksheet[1, 3] is "3" for example and
567
+ # worksheet.input_value(1, 3) is "=RC[-2]+RC[-1]".
568
+ def input_value(row, col)
569
+ reload() if !@cells
570
+ return @input_values[[row, col]] || ""
571
+ end
572
+
573
+ # Row number of the bottom-most non-empty row.
574
+ def num_rows
575
+ reload() if !@cells
576
+ return @cells.keys.map(){ |r, c| r }.max || 0
577
+ end
578
+
579
+ # Column number of the right-most non-empty column.
580
+ def num_cols
581
+ reload() if !@cells
582
+ return @cells.keys.map(){ |r, c| c }.max || 0
583
+ end
584
+
585
+ # Number of rows including empty rows.
586
+ def max_rows
587
+ reload() if !@cells
588
+ return @max_rows
589
+ end
590
+
591
+ # Updates number of rows.
592
+ # Note that update is not sent to the server until you call save().
593
+ def max_rows=(rows)
594
+ reload() if !@cells
595
+ @max_rows = rows
596
+ @meta_modified = true
597
+ end
598
+
599
+ # Number of columns including empty columns.
600
+ def max_cols
601
+ reload() if !@cells
602
+ return @max_cols
603
+ end
604
+
605
+ # Updates number of columns.
606
+ # Note that update is not sent to the server until you call save().
607
+ def max_cols=(cols)
608
+ reload() if !@cells
609
+ @max_cols = cols
610
+ @meta_modified = true
611
+ end
612
+
613
+ # Title of the worksheet (shown as tab label in Web interface).
614
+ def title
615
+ reload() if !@title
616
+ return @title
617
+ end
618
+
619
+ # Updates title of the worksheet.
620
+ # Note that update is not sent to the server until you call save().
621
+ def title=(title)
622
+ reload() if !@cells
623
+ @title = title
624
+ @meta_modified = true
625
+ end
626
+
627
+ def cells #:nodoc:
628
+ reload() if !@cells
629
+ return @cells
630
+ end
631
+
632
+ # An array of spreadsheet rows. Each row contains an array of
633
+ # columns. Note that resulting array is 0-origin so
634
+ # worksheet.rows[0][0] == worksheet[1, 1].
635
+ def rows(skip = 0)
636
+ nc = self.num_cols
637
+ result = ((1 + skip)..self.num_rows).map() do |row|
638
+ (1..nc).map(){ |col| self[row, col] }.freeze()
639
+ end
640
+ return result.freeze()
641
+ end
642
+
643
+ # Reloads content of the worksheets from the server.
644
+ # Note that changes you made by []= is discarded if you haven't called save().
645
+ def reload()
646
+ doc = @session.request(:get, @cells_feed_url)
647
+ @max_rows = doc.search("gs:rowCount").text.to_i()
648
+ @max_cols = doc.search("gs:colCount").text.to_i()
649
+ @title = as_utf8(doc.search("/feed/title").text)
650
+
651
+ @cells = {}
652
+ @input_values = {}
653
+ for entry in doc.search("entry")
654
+ cell = entry.search("gs:cell")[0]
655
+ row = cell["row"].to_i()
656
+ col = cell["col"].to_i()
657
+ @cells[[row, col]] = as_utf8(cell.inner_text)
658
+ @input_values[[row, col]] = as_utf8(cell["inputValue"])
659
+ end
660
+ @modified.clear()
661
+ @meta_modified = false
662
+ return true
663
+ end
664
+
665
+ # Saves your changes made by []=, etc. to the server.
666
+ def save()
667
+ sent = false
668
+
669
+ if @meta_modified
670
+
671
+ ws_doc = @session.request(:get, self.worksheet_feed_url)
672
+ edit_url = ws_doc.search("link[@rel='edit']")[0]["href"]
673
+ xml = <<-"EOS"
674
+ <entry xmlns='http://www.w3.org/2005/Atom'
675
+ xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
676
+ <title>#{h(self.title)}</title>
677
+ <gs:rowCount>#{h(self.max_rows)}</gs:rowCount>
678
+ <gs:colCount>#{h(self.max_cols)}</gs:colCount>
679
+ </entry>
680
+ EOS
681
+
682
+ @session.request(:put, edit_url, :data => xml)
683
+
684
+ @meta_modified = false
685
+ sent = true
686
+
687
+ end
688
+
689
+ if !@modified.empty?
690
+
691
+ # Gets id and edit URL for each cell.
692
+ # Note that return-empty=true is required to get those info for empty cells.
693
+ cell_entries = {}
694
+ rows = @modified.map(){ |r, c| r }
695
+ cols = @modified.map(){ |r, c| c }
696
+ url = "#{@cells_feed_url}?return-empty=true&min-row=#{rows.min}&max-row=#{rows.max}" +
697
+ "&min-col=#{cols.min}&max-col=#{cols.max}"
698
+ doc = @session.request(:get, url)
699
+ for entry in doc.search("entry")
700
+ row = entry.search("gs:cell")[0]["row"].to_i()
701
+ col = entry.search("gs:cell")[0]["col"].to_i()
702
+ cell_entries[[row, col]] = entry
703
+ end
704
+
705
+ # Updates cell values using batch operation.
706
+ # If the data is large, we split it into multiple operations, otherwise batch may fail.
707
+ @modified.each_slice(250) do |chunk|
708
+
709
+ xml = <<-EOS
710
+ <feed xmlns="http://www.w3.org/2005/Atom"
711
+ xmlns:batch="http://schemas.google.com/gdata/batch"
712
+ xmlns:gs="http://schemas.google.com/spreadsheets/2006">
713
+ <id>#{h(@cells_feed_url)}</id>
714
+ EOS
715
+ for row, col in chunk
716
+ value = @cells[[row, col]]
717
+ entry = cell_entries[[row, col]]
718
+ id = entry.search("id").text
719
+ edit_url = entry.search("link[@rel='edit']")[0]["href"]
720
+ xml << <<-EOS
721
+ <entry>
722
+ <batch:id>#{h(row)},#{h(col)}</batch:id>
723
+ <batch:operation type="update"/>
724
+ <id>#{h(id)}</id>
725
+ <link rel="edit" type="application/atom+xml"
726
+ href="#{h(edit_url)}"/>
727
+ <gs:cell row="#{h(row)}" col="#{h(col)}" inputValue="#{h(value)}"/>
728
+ </entry>
729
+ EOS
730
+ end
731
+ xml << <<-"EOS"
732
+ </feed>
733
+ EOS
734
+
735
+ result = @session.request(:post, "#{@cells_feed_url}/batch", :data => xml)
736
+ for entry in result.search("atom:entry")
737
+ interrupted = entry.search("batch:interrupted")[0]
738
+ if interrupted
739
+ raise(GoogleSpreadsheet::Error, "Update has failed: %s" %
740
+ interrupted["reason"])
741
+ end
742
+ if !(entry.search("batch:status")[0]["code"] =~ /^2/)
743
+ raise(GoogleSpreadsheet::Error, "Updating cell %s has failed: %s" %
744
+ [entry.search("atom:id").text, entry.search("batch:status")[0]["reason"]])
745
+ end
746
+ end
747
+
748
+ end
749
+
750
+ @modified.clear()
751
+ sent = true
752
+
753
+ end
754
+ return sent
755
+ end
756
+
757
+ # Calls save() and reload().
758
+ def synchronize()
759
+ save()
760
+ reload()
761
+ end
762
+
763
+ # Deletes this worksheet. Deletion takes effect right away without calling save().
764
+ def delete()
765
+ ws_doc = @session.request(:get, self.worksheet_feed_url)
766
+ edit_url = ws_doc.search("link[@rel='edit']")[0]["href"]
767
+ @session.request(:delete, edit_url)
768
+ end
769
+
770
+ # Returns true if you have changes made by []= which haven't been saved.
771
+ def dirty?
772
+ return !@modified.empty?
773
+ end
774
+
775
+ # Creates table for the worksheet and returns GoogleSpreadsheet::Table.
776
+ # See this document for details:
777
+ # http://code.google.com/intl/en/apis/spreadsheets/docs/3.0/developers_guide_protocol.html#TableFeeds
778
+ def add_table(table_title, summary, columns)
779
+ column_xml = ""
780
+ columns.each do |index, name|
781
+ column_xml += "<gs:column index='#{h(index)}' name='#{h(name)}'/>\n"
782
+ end
783
+
784
+ xml = <<-"EOS"
785
+ <entry xmlns="http://www.w3.org/2005/Atom"
786
+ xmlns:gs="http://schemas.google.com/spreadsheets/2006">
787
+ <title type='text'>#{h(table_title)}</title>
788
+ <summary type='text'>#{h(summary)}</summary>
789
+ <gs:worksheet name='#{h(self.title)}' />
790
+ <gs:header row='1' />
791
+ <gs:data numRows='0' startRow='2'>
792
+ #{column_xml}
793
+ </gs:data>
794
+ </entry>
795
+ EOS
796
+
797
+ result = @session.request(:post, self.spreadsheet.tables_feed_url, :data => xml)
798
+ return Table.new(@session, result)
799
+ end
800
+
801
+ # Returns list of tables for the workwheet.
802
+ def tables
803
+ return self.spreadsheet.tables.select(){ |t| t.worksheet_title == self.title }
804
+ end
805
+
806
+ end
807
+
808
+
809
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sprout-google-spreadsheet-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - sprout
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-12 00:00:00 +05:45
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hpricot
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0.3"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: oauth
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.6
34
+ version:
35
+ description: This is a library to read/write Google Spreadsheet using oauth and client login.
36
+ email:
37
+ - sprout+github@gmail.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - README.rdoc
44
+ files:
45
+ - README.rdoc
46
+ - lib/google_spreadsheet.rb
47
+ has_rdoc: true
48
+ homepage: http://github.com/sprout/google-spreadsheet-ruby/tree/master
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --main
54
+ - README.rdoc
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 2
75
+ summary: This is a library to read/write Google Spreadsheet using oauth and client login.
76
+ test_files: []
77
+