sprout-google-spreadsheet-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+