google-spreadsheet-ruby 0.1.1 → 0.1.2

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 (2) hide show
  1. data/lib/google_spreadsheet.rb +128 -94
  2. metadata +40 -15
@@ -8,7 +8,8 @@ require "open-uri"
8
8
  require "cgi"
9
9
  require "uri"
10
10
  require "rubygems"
11
- require "hpricot"
11
+ require 'nokogiri'
12
+
12
13
  require "oauth"
13
14
  Net::HTTP.version_1_2
14
15
 
@@ -98,14 +99,6 @@ module GoogleSpreadsheet
98
99
  return CGI.escapeHTML(str.to_s())
99
100
  end
100
101
 
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
102
  end
110
103
 
111
104
 
@@ -142,11 +135,8 @@ module GoogleSpreadsheet
142
135
 
143
136
  # Restores session using return value of auth_tokens method of previous session.
144
137
  def initialize(auth_tokens = nil, oauth_token = nil)
145
- if oauth_token
146
- @oauth_token = oauth_token
147
- else
148
- @auth_tokens = auth_tokens
149
- end
138
+ @oauth_token = oauth_token
139
+ @auth_tokens = auth_tokens || {}
150
140
  end
151
141
 
152
142
  # Authenticates with given +mail+ and +password+, and updates current session object
@@ -176,10 +166,11 @@ module GoogleSpreadsheet
176
166
  attr_accessor :on_auth_fail
177
167
 
178
168
  def auth_header(auth) #:nodoc:
179
- if auth == :none
180
- return {}
169
+ token = auth == :none ? nil : @auth_tokens[auth]
170
+ if token
171
+ return {"Authorization" => "GoogleLogin auth=#{token}"}
181
172
  else
182
- return {"Authorization" => "GoogleLogin auth=#{@auth_tokens[auth]}"}
173
+ return {}
183
174
  end
184
175
  end
185
176
 
@@ -194,10 +185,10 @@ module GoogleSpreadsheet
194
185
  query = encode_query(params)
195
186
  doc = request(:get, "https://spreadsheets.google.com/feeds/spreadsheets/private/full?#{query}")
196
187
  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"])
188
+ doc.css("feed > entry").each() do |entry|
189
+ title = entry.css("title").text
190
+ url = entry.css(
191
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
201
192
  result.push(Spreadsheet.new(self, url, title))
202
193
  end
203
194
  return result
@@ -260,68 +251,70 @@ module GoogleSpreadsheet
260
251
  EOS
261
252
 
262
253
  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"])
254
+ ss_url = doc.css(
255
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']").first['href']
265
256
  return Spreadsheet.new(self, ss_url, title)
266
257
  end
267
258
 
268
259
  def request(method, url, params = {}) #:nodoc:
269
260
  # Always uses HTTPS.
270
- uri = URI.parse(url.gsub(%r{^http://}, "https://"))
261
+ url = url.gsub(%r{^http://}, "https://")
271
262
  data = params[:data]
272
263
  auth = params[:auth] || :wise
273
264
  if params[:header]
274
- add_header = params[:header]
265
+ extra_header = params[:header]
266
+ elsif data
267
+ extra_header = {"Content-Type" => "application/atom+xml"}
275
268
  else
276
- add_header = data ? {"Content-Type" => "application/atom+xml"} : {}
269
+ extra_header = {}
277
270
  end
278
271
  response_type = params[:response_type] || :xml
279
272
 
273
+ while true
274
+ response = request_raw(method, url, data, extra_header, auth)
275
+ if response.code == "401" && @on_auth_fail && @on_auth_fail.call()
276
+ next
277
+ end
278
+ if !(response.code =~ /^2/)
279
+ raise(
280
+ response.code == "401" ? AuthenticationError : GoogleSpreadsheet::Error,
281
+ "Response code #{response.code} for #{method} #{url}: " +
282
+ CGI.unescapeHTML(response.body))
283
+ end
284
+ return convert_response(response, response_type)
285
+ end
286
+ end
287
+
288
+ private
289
+
290
+ def request_raw(method, url, data, extra_header, auth)
280
291
  if @oauth_token
281
-
282
292
  if method == :delete || method == :get
283
- response = @oauth_token.__send__(method, url, add_header)
293
+ return @oauth_token.__send__(method, url, extra_header)
284
294
  else
285
- response = @oauth_token.__send__(method, url, data, add_header)
295
+ return @oauth_token.__send__(method, url, data, extra_header)
286
296
  end
287
- return convert_response(response, response_type)
288
-
289
297
  else
290
-
298
+ uri = URI.parse(url)
291
299
  http = Net::HTTP.new(uri.host, uri.port)
292
300
  http.use_ssl = true
293
301
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
294
302
  http.start() do
295
- while true
296
- path = uri.path + (uri.query ? "?#{uri.query}" : "")
297
- header = auth_header(auth).merge(add_header)
298
- if method == :delete || method == :get
299
- response = http.__send__(method, path, header)
300
- else
301
- response = http.__send__(method, path, data, header)
302
- end
303
- if response.code == "401" && @on_auth_fail && @on_auth_fail.call()
304
- next
305
- end
306
- if !(response.code =~ /^2/)
307
- raise(
308
- response.code == "401" ? AuthenticationError : GoogleSpreadsheet::Error,
309
- "Response code #{response.code} for #{method} #{url}: " +
310
- CGI.unescapeHTML(response.body))
311
- end
312
- return convert_response(response, response_type)
303
+ path = uri.path + (uri.query ? "?#{uri.query}" : "")
304
+ header = auth_header(auth).merge(extra_header)
305
+ if method == :delete || method == :get
306
+ return http.__send__(method, path, header)
307
+ else
308
+ return http.__send__(method, path, data, header)
313
309
  end
314
310
  end
315
-
316
311
  end
317
312
  end
318
313
 
319
- private
320
-
321
314
  def convert_response(response, response_type)
322
315
  case response_type
323
316
  when :xml
324
- return Hpricot.XML(response.body)
317
+ return Nokogiri.XML(response.body)
325
318
  when :raw
326
319
  return response.body
327
320
  else
@@ -337,9 +330,10 @@ module GoogleSpreadsheet
337
330
  "service" => auth.to_s(),
338
331
  "source" => "Gimite-RubyGoogleSpreadsheet-1.00",
339
332
  }
333
+ header = {"Content-Type" => "application/x-www-form-urlencoded"}
340
334
  response = request(:post,
341
335
  "https://www.google.com/accounts/ClientLogin",
342
- :data => encode_query(params), :auth => :none, :header => {}, :response_type => :raw)
336
+ :data => encode_query(params), :auth => :none, :header => header, :response_type => :raw)
343
337
  @auth_tokens[auth] = response.slice(/^Auth=(.*)$/, 1)
344
338
  end
345
339
 
@@ -378,12 +372,12 @@ module GoogleSpreadsheet
378
372
  def tables_feed_url
379
373
  return "https://spreadsheets.google.com/feeds/#{self.key}/tables"
380
374
  end
381
-
375
+
382
376
  # URL of feed used in document list feed API.
383
377
  def document_feed_url
384
378
  return "https://docs.google.com/feeds/documents/private/full/spreadsheet%3A#{self.key}"
385
379
  end
386
-
380
+
387
381
  # Creates copy of this spreadsheet with the given name.
388
382
  def duplicate(new_name = nil)
389
383
  new_name ||= (@title ? "Copy of " + @title : "Untitled")
@@ -396,8 +390,8 @@ module GoogleSpreadsheet
396
390
  "Slug" => URI.encode(new_name),
397
391
  }
398
392
  doc = @session.request(:post, url, :data => ods, :auth => :writely, :header => header)
399
- ss_url = as_utf8(doc.search(
400
- "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"])
393
+ ss_url = doc.css(
394
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']").first['href']
401
395
  return Spreadsheet.new(@session, ss_url, title)
402
396
  end
403
397
 
@@ -409,14 +403,32 @@ module GoogleSpreadsheet
409
403
  :auth => :writely, :header => {"If-Match" => "*"})
410
404
  end
411
405
 
406
+ # Renames title of the spreadsheet.
407
+ def rename(title)
408
+ doc = @session.request(:get, self.document_feed_url)
409
+ edit_url = doc.css("link[@rel='edit']").first['href']
410
+ xml = <<-"EOS"
411
+ <atom:entry
412
+ xmlns:atom="http://www.w3.org/2005/Atom"
413
+ xmlns:docs="http://schemas.google.com/docs/2007">
414
+ <atom:category
415
+ scheme="http://schemas.google.com/g/2005#kind"
416
+ term="http://schemas.google.com/docs/2007#spreadsheet" label="spreadsheet"/>
417
+ <atom:title>#{h(title)}</atom:title>
418
+ </atom:entry>
419
+ EOS
420
+
421
+ @session.request(:put, edit_url, :data => xml)
422
+ end
423
+
412
424
  # Returns worksheets of the spreadsheet as array of GoogleSpreadsheet::Worksheet.
413
425
  def worksheets
414
426
  doc = @session.request(:get, @worksheets_feed_url)
415
427
  result = []
416
- for entry in doc.search("entry")
417
- title = as_utf8(entry.search("title").text)
418
- url = as_utf8(entry.search(
419
- "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"])
428
+ doc.css('entry').each() do |entry|
429
+ title = entry.css('title').text
430
+ url = entry.css(
431
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']").first['href']
420
432
  result.push(Worksheet.new(@session, self, url, title))
421
433
  end
422
434
  return result.freeze()
@@ -433,15 +445,15 @@ module GoogleSpreadsheet
433
445
  </entry>
434
446
  EOS
435
447
  doc = @session.request(:post, @worksheets_feed_url, :data => xml)
436
- url = as_utf8(doc.search(
437
- "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"])
448
+ url = doc.css(
449
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']").first['href']
438
450
  return Worksheet.new(@session, self, url, title)
439
451
  end
440
452
 
441
453
  # Returns list of tables in the spreadsheet.
442
454
  def tables
443
455
  doc = @session.request(:get, self.tables_feed_url)
444
- return doc.search("entry").map(){ |e| Table.new(@session, e) }.freeze()
456
+ return doc.css('entry').map(){ |e| Table.new(@session, e) }.freeze()
445
457
  end
446
458
 
447
459
  end
@@ -454,8 +466,9 @@ module GoogleSpreadsheet
454
466
 
455
467
  def initialize(session, entry) #:nodoc:
456
468
  @columns = {}
457
- @worksheet_title = as_utf8(entry.search("gs:worksheet")[0]["name"])
458
- @records_url = as_utf8(entry.search("content")[0]["src"])
469
+ @worksheet_title = entry.css('gs|worksheet').first['name']
470
+ @records_url = entry.css("content")[0]["src"]
471
+ @edit_url = entry.css("link[@rel='edit']")[0]['href']
459
472
  @session = session
460
473
  end
461
474
 
@@ -465,7 +478,7 @@ module GoogleSpreadsheet
465
478
  # Adds a record.
466
479
  def add_record(values)
467
480
  fields = ""
468
- values.each do |name, value|
481
+ values.each() do |name, value|
469
482
  fields += "<gs:field name='#{h(name)}'>#{h(value)}</gs:field>"
470
483
  end
471
484
  xml =<<-EOS
@@ -481,18 +494,24 @@ module GoogleSpreadsheet
481
494
  # Returns records in the table.
482
495
  def records
483
496
  doc = @session.request(:get, @records_url)
484
- return doc.search("entry").map(){ |e| Record.new(@session, e) }
497
+ return doc.css('entry').map(){ |e| Record.new(@session, e) }
498
+ end
499
+
500
+ # Deletes this table. Deletion takes effect right away without calling save().
501
+ def delete
502
+ @session.request(:delete, @edit_url, :header => {"If-Match" => "*"})
485
503
  end
486
504
 
487
505
  end
488
506
 
489
507
  # Use GoogleSpreadsheet::Table#records to get GoogleSpreadsheet::Record objects.
490
508
  class Record < Hash
509
+ include(Util)
491
510
 
492
511
  def initialize(session, entry) #:nodoc:
493
512
  @session = session
494
- for field in entry.search("gs:field")
495
- self[as_utf8(field["name"])] = as_utf8(field.inner_text)
513
+ entry.css('gs|field').each() do |field|
514
+ self[field["name"]] = field.inner_text
496
515
  end
497
516
  end
498
517
 
@@ -652,18 +671,19 @@ module GoogleSpreadsheet
652
671
  # Note that changes you made by []= is discarded if you haven't called save().
653
672
  def reload()
654
673
  doc = @session.request(:get, @cells_feed_url)
655
- @max_rows = doc.search("gs:rowCount").text.to_i()
656
- @max_cols = doc.search("gs:colCount").text.to_i()
657
- @title = as_utf8(doc.search("/feed/title").text)
674
+ @max_rows = doc.css('gs|rowCount').text.to_i
675
+ @max_cols = doc.css('gs|colCount').text.to_i
676
+ @title = doc.css('feed > title')[0].text
658
677
 
659
678
  @cells = {}
660
679
  @input_values = {}
661
- for entry in doc.search("entry")
662
- cell = entry.search("gs:cell")[0]
680
+ doc.css('feed > entry').each() do |entry|
681
+ cell = entry.css('gs|cell').first
663
682
  row = cell["row"].to_i()
664
683
  col = cell["col"].to_i()
665
- @cells[[row, col]] = as_utf8(cell.inner_text)
666
- @input_values[[row, col]] = as_utf8(cell["inputValue"])
684
+ @cells[[row, col]] = cell.inner_text
685
+ @input_values[[row, col]] = cell["inputValue"]
686
+
667
687
  end
668
688
  @modified.clear()
669
689
  @meta_modified = false
@@ -677,7 +697,7 @@ module GoogleSpreadsheet
677
697
  if @meta_modified
678
698
 
679
699
  ws_doc = @session.request(:get, self.worksheet_feed_url)
680
- edit_url = ws_doc.search("link[@rel='edit']")[0]["href"]
700
+ edit_url = ws_doc.css("link[@rel='edit']").first['href']
681
701
  xml = <<-"EOS"
682
702
  <entry xmlns='http://www.w3.org/2005/Atom'
683
703
  xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
@@ -704,9 +724,10 @@ module GoogleSpreadsheet
704
724
  url = "#{@cells_feed_url}?return-empty=true&min-row=#{rows.min}&max-row=#{rows.max}" +
705
725
  "&min-col=#{cols.min}&max-col=#{cols.max}"
706
726
  doc = @session.request(:get, url)
707
- for entry in doc.search("entry")
708
- row = entry.search("gs:cell")[0]["row"].to_i()
709
- col = entry.search("gs:cell")[0]["col"].to_i()
727
+
728
+ doc.css('entry').each() do |entry|
729
+ row = entry.css('gs|cell').first['row'].to_i
730
+ col = entry.css('gs|cell').first['col'].to_i
710
731
  cell_entries[[row, col]] = entry
711
732
  end
712
733
 
@@ -723,8 +744,8 @@ module GoogleSpreadsheet
723
744
  for row, col in chunk
724
745
  value = @cells[[row, col]]
725
746
  entry = cell_entries[[row, col]]
726
- id = entry.search("id").text
727
- edit_url = entry.search("link[@rel='edit']")[0]["href"]
747
+ id = entry.css('id').text
748
+ edit_url = entry.css("link[@rel='edit']").first['href']
728
749
  xml << <<-EOS
729
750
  <entry>
730
751
  <batch:id>#{h(row)},#{h(col)}</batch:id>
@@ -741,15 +762,15 @@ module GoogleSpreadsheet
741
762
  EOS
742
763
 
743
764
  result = @session.request(:post, "#{@cells_feed_url}/batch", :data => xml)
744
- for entry in result.search("atom:entry")
745
- interrupted = entry.search("batch:interrupted")[0]
765
+ result.css('atom|entry').each() do |entry|
766
+ interrupted = entry.css('batch|interrupted').first
746
767
  if interrupted
747
768
  raise(GoogleSpreadsheet::Error, "Update has failed: %s" %
748
769
  interrupted["reason"])
749
770
  end
750
- if !(entry.search("batch:status")[0]["code"] =~ /^2/)
771
+ if !(entry.css('batch|status').first['code'] =~ /^2/)
751
772
  raise(GoogleSpreadsheet::Error, "Updating cell %s has failed: %s" %
752
- [entry.search("atom:id").text, entry.search("batch:status")[0]["reason"]])
773
+ [entry.css('atom|id').text, entry.css('batch|status').first['reason']])
753
774
  end
754
775
  end
755
776
 
@@ -771,7 +792,7 @@ module GoogleSpreadsheet
771
792
  # Deletes this worksheet. Deletion takes effect right away without calling save().
772
793
  def delete()
773
794
  ws_doc = @session.request(:get, self.worksheet_feed_url)
774
- edit_url = ws_doc.search("link[@rel='edit']")[0]["href"]
795
+ edit_url = ws_doc.css("link[@rel='edit']").first['href']
775
796
  @session.request(:delete, edit_url)
776
797
  end
777
798
 
@@ -783,9 +804,12 @@ module GoogleSpreadsheet
783
804
  # Creates table for the worksheet and returns GoogleSpreadsheet::Table.
784
805
  # See this document for details:
785
806
  # http://code.google.com/intl/en/apis/spreadsheets/docs/3.0/developers_guide_protocol.html#TableFeeds
786
- def add_table(table_title, summary, columns)
807
+ def add_table(table_title, summary, columns, options)
808
+ default_options = { :header_row => 1, :num_rows => 0, :start_row => 2}
809
+ options = default_options.merge(options)
810
+
787
811
  column_xml = ""
788
- columns.each do |index, name|
812
+ columns.each() do |index, name|
789
813
  column_xml += "<gs:column index='#{h(index)}' name='#{h(name)}'/>\n"
790
814
  end
791
815
 
@@ -795,8 +819,8 @@ module GoogleSpreadsheet
795
819
  <title type='text'>#{h(table_title)}</title>
796
820
  <summary type='text'>#{h(summary)}</summary>
797
821
  <gs:worksheet name='#{h(self.title)}' />
798
- <gs:header row='1' />
799
- <gs:data numRows='0' startRow='2'>
822
+ <gs:header row='#{options[:header_row]}' />
823
+ <gs:data numRows='#{options[:num_rows]}' startRow='#{options[:start_row]}'>
800
824
  #{column_xml}
801
825
  </gs:data>
802
826
  </entry>
@@ -811,6 +835,16 @@ module GoogleSpreadsheet
811
835
  return self.spreadsheet.tables.select(){ |t| t.worksheet_title == self.title }
812
836
  end
813
837
 
838
+ # List feed URL of the worksheet.
839
+ def list_feed_url
840
+ # Gets the worksheets metafeed.
841
+ entry = @session.request(:get, self.worksheet_feed_url)
842
+
843
+ # Gets the URL of list-based feed for the given spreadsheet.
844
+ return entry.css(
845
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#listfeed']").first['href']
846
+ end
847
+
814
848
  end
815
849
 
816
850
 
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-spreadsheet-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ hash: 31
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 2
10
+ version: 0.1.2
5
11
  platform: ruby
6
12
  authors:
7
13
  - Hiroshi Ichikawa
@@ -9,29 +15,42 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-01-31 00:00:00 +09:00
18
+ date: 2010-09-24 00:00:00 +09:00
13
19
  default_executable:
14
20
  dependencies:
15
21
  - !ruby/object:Gem::Dependency
16
- name: hpricot
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ name: nokogiri
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
20
26
  requirements:
21
27
  - - ">="
22
28
  - !ruby/object:Gem::Version
23
- version: "0.3"
24
- version:
29
+ hash: 113
30
+ segments:
31
+ - 1
32
+ - 4
33
+ - 3
34
+ - 1
35
+ version: 1.4.3.1
36
+ type: :runtime
37
+ version_requirements: *id001
25
38
  - !ruby/object:Gem::Dependency
26
39
  name: oauth
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
30
43
  requirements:
31
44
  - - ">="
32
45
  - !ruby/object:Gem::Version
46
+ hash: 31
47
+ segments:
48
+ - 0
49
+ - 3
50
+ - 6
33
51
  version: 0.3.6
34
- version:
52
+ type: :runtime
53
+ version_requirements: *id002
35
54
  description: This is a library to read/write Google Spreadsheet.
36
55
  email:
37
56
  - gimite+github@gmail.com
@@ -55,21 +74,27 @@ rdoc_options:
55
74
  require_paths:
56
75
  - lib
57
76
  required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
58
78
  requirements:
59
79
  - - ">="
60
80
  - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
61
84
  version: "0"
62
- version:
63
85
  required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
64
87
  requirements:
65
88
  - - ">="
66
89
  - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
67
93
  version: "0"
68
- version:
69
94
  requirements: []
70
95
 
71
96
  rubyforge_project:
72
- rubygems_version: 1.3.5
97
+ rubygems_version: 1.3.7
73
98
  signing_key:
74
99
  specification_version: 2
75
100
  summary: This is a library to read/write Google Spreadsheet.