google-spreadsheet-ruby 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,298 +1,4 @@
1
1
  # Author: Hiroshi Ichikawa <http://gimite.net/>
2
2
  # The license of this source is "New BSD Licence"
3
3
 
4
- require "google_spreadsheet/util"
5
- require "google_spreadsheet/error"
6
- require "google_spreadsheet/worksheet"
7
- require "google_spreadsheet/table"
8
- require "google_spreadsheet/acl"
9
-
10
-
11
- module GoogleSpreadsheet
12
-
13
- # A spreadsheet.
14
- #
15
- # Use methods in GoogleSpreadsheet::Session to get GoogleSpreadsheet::Spreadsheet object.
16
- class Spreadsheet
17
-
18
- include(Util)
19
-
20
- SUPPORTED_EXPORT_FORMAT = Set.new(["xls", "csv", "pdf", "ods", "tsv", "html"])
21
-
22
- def initialize(session, worksheets_feed_url, title = nil) #:nodoc:
23
- @session = session
24
- @worksheets_feed_url = worksheets_feed_url
25
- @title = title
26
- @acl = nil
27
- end
28
-
29
- # URL of worksheet-based feed of the spreadsheet.
30
- attr_reader(:worksheets_feed_url)
31
-
32
- # Title of the spreadsheet.
33
- #
34
- # Set params[:reload] to true to force reloading the title.
35
- def title(params = {})
36
- if !@title || params[:reload]
37
- @title = spreadsheet_feed_entry(params).css("title").text
38
- end
39
- return @title
40
- end
41
-
42
- # Key of the spreadsheet.
43
- def key
44
- if !(@worksheets_feed_url =~
45
- %r{^https?://spreadsheets.google.com/feeds/worksheets/(.*)/private/.*$})
46
- raise(GoogleSpreadsheet::Error,
47
- "Worksheets feed URL is in unknown format: #{@worksheets_feed_url}")
48
- end
49
- return $1
50
- end
51
-
52
- # Spreadsheet feed URL of the spreadsheet.
53
- def spreadsheet_feed_url
54
- return "https://spreadsheets.google.com/feeds/spreadsheets/private/full/#{self.key}"
55
- end
56
-
57
- # URL which you can open the spreadsheet in a Web browser with.
58
- #
59
- # e.g. "http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg"
60
- def human_url
61
- # Uses Document feed because Spreadsheet feed returns wrong URL for Apps account.
62
- return self.document_feed_entry.css("link[rel='alternate']")[0]["href"]
63
- end
64
-
65
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
66
- # March 2012.
67
- #
68
- # Tables feed URL of the spreadsheet.
69
- def tables_feed_url
70
- warn(
71
- "DEPRECATED: Google Spreadsheet Table and Record feeds are deprecated and they " +
72
- "will not be available after March 2012.")
73
- return "https://spreadsheets.google.com/feeds/#{self.key}/tables"
74
- end
75
-
76
- # URL of feed used in document list feed API.
77
- def document_feed_url
78
- return "https://docs.google.com/feeds/documents/private/full/spreadsheet%3A#{self.key}"
79
- end
80
-
81
- # ACL feed URL of the spreadsheet.
82
- def acl_feed_url
83
- orig_acl_feed_url = document_feed_entry.css(
84
- "gd|feedLink[rel='http://schemas.google.com/acl/2007#accessControlList']")[0]["href"]
85
- case orig_acl_feed_url
86
- when %r{^https?://docs.google.com/feeds/default/private/full/.*/acl$}
87
- return orig_acl_feed_url
88
- when %r{^https?://docs.google.com/feeds/acl/private/full/([^\?]*)(\?.*)?$}
89
- # URL of old API version. Converts to v3 URL.
90
- return "https://docs.google.com/feeds/default/private/full/#{$1}/acl"
91
- else
92
- raise(GoogleSpreadsheet::Error,
93
- "ACL feed URL is in unknown format: #{orig_acl_feed_url}")
94
- end
95
- end
96
-
97
- # <entry> element of spreadsheet feed as Nokogiri::XML::Element.
98
- #
99
- # Set params[:reload] to true to force reloading the feed.
100
- def spreadsheet_feed_entry(params = {})
101
- if !@spreadsheet_feed_entry || params[:reload]
102
- @spreadsheet_feed_entry =
103
- @session.request(:get, self.spreadsheet_feed_url).css("entry")[0]
104
- end
105
- return @spreadsheet_feed_entry
106
- end
107
-
108
- # <entry> element of document list feed as Nokogiri::XML::Element.
109
- #
110
- # Set params[:reload] to true to force reloading the feed.
111
- def document_feed_entry(params = {})
112
- if !@document_feed_entry || params[:reload]
113
- @document_feed_entry =
114
- @session.request(:get, self.document_feed_url, :auth => :writely).css("entry")[0]
115
- end
116
- return @document_feed_entry
117
- end
118
-
119
- # Creates copy of this spreadsheet with the given title.
120
- def duplicate(new_title = nil)
121
- new_title ||= (self.title ? "Copy of " + self.title : "Untitled")
122
- post_url = "https://docs.google.com/feeds/default/private/full/"
123
- header = {"GData-Version" => "3.0", "Content-Type" => "application/atom+xml"}
124
- xml = <<-"EOS"
125
- <entry xmlns='http://www.w3.org/2005/Atom'>
126
- <id>#{h(self.document_feed_url)}</id>
127
- <title>#{h(new_title)}</title>
128
- </entry>
129
- EOS
130
- doc = @session.request(
131
- :post, post_url, :data => xml, :header => header, :auth => :writely)
132
- ss_url = doc.css(
133
- "link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
134
- return Spreadsheet.new(@session, ss_url, new_title)
135
- end
136
-
137
- # If +permanent+ is +false+, moves the spreadsheet to the trash.
138
- # If +permanent+ is +true+, deletes the spreadsheet permanently.
139
- def delete(permanent = false)
140
- @session.request(:delete,
141
- self.document_feed_url + (permanent ? "?delete=true" : ""),
142
- :auth => :writely, :header => {"If-Match" => "*"})
143
- end
144
-
145
- # Renames title of the spreadsheet.
146
- def rename(title)
147
- doc = @session.request(:get, self.document_feed_url, :auth => :writely)
148
- edit_url = doc.css("link[rel='edit']").first["href"]
149
- xml = <<-"EOS"
150
- <atom:entry
151
- xmlns:atom="http://www.w3.org/2005/Atom"
152
- xmlns:docs="http://schemas.google.com/docs/2007">
153
- <atom:category
154
- scheme="http://schemas.google.com/g/2005#kind"
155
- term="http://schemas.google.com/docs/2007#spreadsheet" label="spreadsheet"/>
156
- <atom:title>#{h(title)}</atom:title>
157
- </atom:entry>
158
- EOS
159
-
160
- @session.request(:put, edit_url, :data => xml, :auth => :writely)
161
- end
162
-
163
- alias title= rename
164
-
165
- # Exports the spreadsheet in +format+ and returns it as String.
166
- #
167
- # +format+ can be either "xls", "csv", "pdf", "ods", "tsv" or "html".
168
- # In format such as "csv", only the worksheet specified with +worksheet_index+ is
169
- # exported.
170
- def export_as_string(format, worksheet_index = nil)
171
- gid_param = worksheet_index ? "&gid=#{worksheet_index}" : ""
172
- url =
173
- "https://spreadsheets.google.com/feeds/download/spreadsheets/Export" +
174
- "?key=#{key}&exportFormat=#{format}#{gid_param}"
175
- return @session.request(:get, url, :response_type => :raw)
176
- end
177
-
178
- # Exports the spreadsheet in +format+ as a local file.
179
- #
180
- # +format+ can be either "xls", "csv", "pdf", "ods", "tsv" or "html".
181
- # If +format+ is nil, it is guessed from the file name.
182
- # In format such as "csv", only the worksheet specified with +worksheet_index+ is exported.
183
- #
184
- # e.g.
185
- # spreadsheet.export_as_file("hoge.ods")
186
- # spreadsheet.export_as_file("hoge.csv", nil, 0)
187
- def export_as_file(local_path, format = nil, worksheet_index = nil)
188
- if !format
189
- format = File.extname(local_path).gsub(/^\./, "")
190
- if !SUPPORTED_EXPORT_FORMAT.include?(format)
191
- raise(ArgumentError,
192
- ("Cannot guess format from the file name: %s\n" +
193
- "Specify format argument explicitly.") %
194
- local_path)
195
- end
196
- end
197
- open(local_path, "wb") do |f|
198
- f.write(export_as_string(format, worksheet_index))
199
- end
200
- end
201
-
202
- # Returns worksheets of the spreadsheet as array of GoogleSpreadsheet::Worksheet.
203
- def worksheets
204
- doc = @session.request(:get, @worksheets_feed_url)
205
- if doc.root.name != "feed"
206
- raise(GoogleSpreadsheet::Error,
207
- "%s doesn't look like a worksheets feed URL because its root is not <feed>." %
208
- @worksheets_feed_url)
209
- end
210
- result = []
211
- doc.css("entry").each() do |entry|
212
- title = entry.css("title").text
213
- url = entry.css(
214
- "link[rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"]
215
- result.push(Worksheet.new(@session, self, url, title))
216
- end
217
- return result.freeze()
218
- end
219
-
220
- # Returns a GoogleSpreadsheet::Worksheet with the given title in the spreadsheet.
221
- #
222
- # Returns nil if not found. Returns the first one when multiple worksheets with the
223
- # title are found.
224
- def worksheet_by_title(title)
225
- return self.worksheets.find(){ |ws| ws.title == title }
226
- end
227
-
228
- # Adds a new worksheet to the spreadsheet. Returns added GoogleSpreadsheet::Worksheet.
229
- def add_worksheet(title, max_rows = 100, max_cols = 20)
230
- xml = <<-"EOS"
231
- <entry xmlns='http://www.w3.org/2005/Atom'
232
- xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
233
- <title>#{h(title)}</title>
234
- <gs:rowCount>#{h(max_rows)}</gs:rowCount>
235
- <gs:colCount>#{h(max_cols)}</gs:colCount>
236
- </entry>
237
- EOS
238
- doc = @session.request(:post, @worksheets_feed_url, :data => xml)
239
- url = doc.css(
240
- "link[rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"]
241
- return Worksheet.new(@session, self, url, title)
242
- end
243
-
244
- # Returns GoogleSpreadsheet::Acl object for the spreadsheet.
245
- #
246
- # With the object, you can see and modify people who can access the spreadsheet.
247
- # Modifications take effect immediately.
248
- #
249
- # Set params[:reload] to true to force reloading the title.
250
- #
251
- # e.g.
252
- # # Dumps people who have access:
253
- # for entry in spreadsheet.acl
254
- # p [entry.scope_type, entry.scope, entry.role]
255
- # # => e.g. ["user", "example1@gmail.com", "owner"]
256
- # end
257
- #
258
- # # Shares the spreadsheet with new people:
259
- # # NOTE: This sends email to the new people.
260
- # spreadsheet.acl.push(
261
- # {:scope_type => "user", :scope => "example2@gmail.com", :role => "reader"})
262
- # spreadsheet.acl.push(
263
- # {:scope_type => "user", :scope => "example3@gmail.com", :role => "writer"})
264
- #
265
- # # Changes the role of a person:
266
- # spreadsheet.acl[1].role = "writer"
267
- #
268
- # # Deletes an ACL entry:
269
- # spreadsheet.acl.delete(spreadsheet.acl[1])
270
-
271
- def acl(params = {})
272
- if !@acl || params[:reload]
273
- @acl = Acl.new(@session, self.acl_feed_url)
274
- end
275
- return @acl
276
- end
277
-
278
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
279
- # March 2012.
280
- #
281
- # Returns list of tables in the spreadsheet.
282
- def tables
283
- warn(
284
- "DEPRECATED: Google Spreadsheet Table and Record feeds are deprecated and they " +
285
- "will not be available after March 2012.")
286
- doc = @session.request(:get, self.tables_feed_url)
287
- return doc.css("entry").map(){ |e| Table.new(@session, e) }.freeze()
288
- end
289
-
290
- def inspect
291
- fields = {:worksheets_feed_url => self.worksheets_feed_url}
292
- fields[:title] = @title if @title
293
- return "\#<%p %s>" % [self.class, fields.map(){ |k, v| "%s=%p" % [k, v] }.join(", ")]
294
- end
295
-
296
- end
297
-
298
- end
4
+ require "google_spreadsheet"
@@ -1,60 +1,4 @@
1
1
  # Author: Hiroshi Ichikawa <http://gimite.net/>
2
2
  # The license of this source is "New BSD Licence"
3
3
 
4
- require "google_spreadsheet/util"
5
- require "google_spreadsheet/error"
6
- require "google_spreadsheet/record"
7
-
8
-
9
- module GoogleSpreadsheet
10
-
11
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
12
- # March 2012.
13
- #
14
- # Use GoogleSpreadsheet::Worksheet#add_table to create table.
15
- # Use GoogleSpreadsheet::Worksheet#tables to get GoogleSpreadsheet::Table objects.
16
- class Table
17
-
18
- include(Util)
19
-
20
- def initialize(session, entry) #:nodoc:
21
- @columns = {}
22
- @worksheet_title = entry.css("gs|worksheet")[0]["name"]
23
- @records_url = entry.css("content")[0]["src"]
24
- @edit_url = entry.css("link[rel='edit']")[0]["href"]
25
- @session = session
26
- end
27
-
28
- # Title of the worksheet the table belongs to.
29
- attr_reader(:worksheet_title)
30
-
31
- # Adds a record.
32
- def add_record(values)
33
- fields = ""
34
- values.each() do |name, value|
35
- fields += "<gs:field name='#{h(name)}'>#{h(value)}</gs:field>"
36
- end
37
- xml =<<-EOS
38
- <entry
39
- xmlns="http://www.w3.org/2005/Atom"
40
- xmlns:gs="http://schemas.google.com/spreadsheets/2006">
41
- #{fields}
42
- </entry>
43
- EOS
44
- @session.request(:post, @records_url, :data => xml)
45
- end
46
-
47
- # Returns records in the table.
48
- def records
49
- doc = @session.request(:get, @records_url)
50
- return doc.css("entry").map(){ |e| Record.new(@session, e) }
51
- end
52
-
53
- # Deletes this table. Deletion takes effect right away without calling save().
54
- def delete
55
- @session.request(:delete, @edit_url, :header => {"If-Match" => "*"})
56
- end
57
-
58
- end
59
-
60
- end
4
+ require "google_spreadsheet"
@@ -1,30 +1,4 @@
1
1
  # Author: Hiroshi Ichikawa <http://gimite.net/>
2
2
  # The license of this source is "New BSD Licence"
3
3
 
4
- require "cgi"
5
-
6
-
7
- module GoogleSpreadsheet
8
-
9
- module Util #:nodoc:
10
-
11
- module_function
12
-
13
- def encode_query(params)
14
- return params.map(){ |k, v| CGI.escape(k) + "=" + CGI.escape(v) }.join("&")
15
- end
16
-
17
- def concat_url(url, piece)
18
- (url_base, url_query) = url.split(/\?/, 2)
19
- (piece_base, piece_query) = piece.split(/\?/, 2)
20
- result_query = [url_query, piece_query].select(){ |s| s && !s.empty? }.join("&")
21
- return url_base + piece_base + (result_query.empty? ? "" : "?#{result_query}")
22
- end
23
-
24
- def h(str)
25
- return CGI.escapeHTML(str.to_s())
26
- end
27
-
28
- end
29
-
30
- end
4
+ require "google_spreadsheet"
@@ -1,445 +1,4 @@
1
1
  # Author: Hiroshi Ichikawa <http://gimite.net/>
2
2
  # The license of this source is "New BSD Licence"
3
3
 
4
- require "set"
5
-
6
- require "google_spreadsheet/util"
7
- require "google_spreadsheet/error"
8
- require "google_spreadsheet/table"
9
- require "google_spreadsheet/list"
10
-
11
-
12
- module GoogleSpreadsheet
13
-
14
- # A worksheet (i.e. a tab) in a spreadsheet.
15
- # Use GoogleSpreadsheet::Spreadsheet#worksheets to get GoogleSpreadsheet::Worksheet object.
16
- class Worksheet
17
-
18
- include(Util)
19
-
20
- def initialize(session, spreadsheet, cells_feed_url, title = nil) #:nodoc:
21
-
22
- @session = session
23
- @spreadsheet = spreadsheet
24
- @cells_feed_url = cells_feed_url
25
- @title = title
26
-
27
- @cells = nil
28
- @input_values = nil
29
- @modified = Set.new()
30
- @list = nil
31
-
32
- end
33
-
34
- # URL of cell-based feed of the worksheet.
35
- attr_reader(:cells_feed_url)
36
-
37
- # URL of worksheet feed URL of the worksheet.
38
- def worksheet_feed_url
39
- # I don't know good way to get worksheet feed URL from cells feed URL.
40
- # Probably it would be cleaner to keep worksheet feed URL and get cells feed URL
41
- # from it.
42
- if !(@cells_feed_url =~
43
- %r{^https?://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full((\?.*)?)$})
44
- raise(GoogleSpreadsheet::Error,
45
- "Cells feed URL is in unknown format: #{@cells_feed_url}")
46
- end
47
- return "https://spreadsheets.google.com/feeds/worksheets/#{$1}/private/full/#{$2}#{$3}"
48
- end
49
-
50
- # GoogleSpreadsheet::Spreadsheet which this worksheet belongs to.
51
- def spreadsheet
52
- if !@spreadsheet
53
- if !(@cells_feed_url =~
54
- %r{^https?://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full(\?.*)?$})
55
- raise(GoogleSpreadsheet::Error,
56
- "Cells feed URL is in unknown format: #{@cells_feed_url}")
57
- end
58
- @spreadsheet = @session.spreadsheet_by_key($1)
59
- end
60
- return @spreadsheet
61
- end
62
-
63
- # Returns content of the cell as String. Arguments must be either
64
- # (row number, column number) or cell name. Top-left cell is [1, 1].
65
- #
66
- # e.g.
67
- # worksheet[2, 1] #=> "hoge"
68
- # worksheet["A2"] #=> "hoge"
69
- def [](*args)
70
- (row, col) = parse_cell_args(args)
71
- return self.cells[[row, col]] || ""
72
- end
73
-
74
- # Updates content of the cell.
75
- # Arguments in the bracket must be either (row number, column number) or cell name.
76
- # Note that update is not sent to the server until you call save().
77
- # Top-left cell is [1, 1].
78
- #
79
- # e.g.
80
- # worksheet[2, 1] = "hoge"
81
- # worksheet["A2"] = "hoge"
82
- # worksheet[1, 3] = "=A1+B1"
83
- def []=(*args)
84
- (row, col) = parse_cell_args(args[0...-1])
85
- value = args[-1].to_s()
86
- reload() if !@cells
87
- @cells[[row, col]] = value
88
- @input_values[[row, col]] = value
89
- @modified.add([row, col])
90
- self.max_rows = row if row > @max_rows
91
- self.max_cols = col if col > @max_cols
92
- end
93
-
94
- # Updates cells in a rectangle area by a two-dimensional Array.
95
- # +top_row+ and +left_col+ specifies the top-left corner of the area.
96
- #
97
- # e.g.
98
- # worksheet.update_cells(2, 3, [["1", "2"], ["3", "4"]])
99
- def update_cells(top_row, left_col, darray)
100
- darray.each_with_index() do |array, y|
101
- array.each_with_index() do |value, x|
102
- self[top_row + y, left_col + x] = value
103
- end
104
- end
105
- end
106
-
107
- # Returns the value or the formula of the cell. Arguments must be either
108
- # (row number, column number) or cell name. Top-left cell is [1, 1].
109
- #
110
- # If user input "=A1+B1" to cell [1, 3]:
111
- # worksheet[1, 3] #=> "3" for example
112
- # worksheet.input_value(1, 3) #=> "=RC[-2]+RC[-1]"
113
- def input_value(*args)
114
- (row, col) = parse_cell_args(args)
115
- reload() if !@cells
116
- return @input_values[[row, col]] || ""
117
- end
118
-
119
- # Row number of the bottom-most non-empty row.
120
- def num_rows
121
- reload() if !@cells
122
- return @input_values.select(){ |(r, c), v| !v.empty? }.map(){ |(r, c), v| r }.max || 0
123
- end
124
-
125
- # Column number of the right-most non-empty column.
126
- def num_cols
127
- reload() if !@cells
128
- return @input_values.select(){ |(r, c), v| !v.empty? }.map(){ |(r, c), v| c }.max || 0
129
- end
130
-
131
- # Number of rows including empty rows.
132
- def max_rows
133
- reload() if !@cells
134
- return @max_rows
135
- end
136
-
137
- # Updates number of rows.
138
- # Note that update is not sent to the server until you call save().
139
- def max_rows=(rows)
140
- reload() if !@cells
141
- @max_rows = rows
142
- @meta_modified = true
143
- end
144
-
145
- # Number of columns including empty columns.
146
- def max_cols
147
- reload() if !@cells
148
- return @max_cols
149
- end
150
-
151
- # Updates number of columns.
152
- # Note that update is not sent to the server until you call save().
153
- def max_cols=(cols)
154
- reload() if !@cells
155
- @max_cols = cols
156
- @meta_modified = true
157
- end
158
-
159
- # Title of the worksheet (shown as tab label in Web interface).
160
- def title
161
- reload() if !@title
162
- return @title
163
- end
164
-
165
- # Updates title of the worksheet.
166
- # Note that update is not sent to the server until you call save().
167
- def title=(title)
168
- reload() if !@cells
169
- @title = title
170
- @meta_modified = true
171
- end
172
-
173
- def cells #:nodoc:
174
- reload() if !@cells
175
- return @cells
176
- end
177
-
178
- # An array of spreadsheet rows. Each row contains an array of
179
- # columns. Note that resulting array is 0-origin so
180
- # worksheet.rows[0][0] == worksheet[1, 1].
181
- def rows(skip = 0)
182
- nc = self.num_cols
183
- result = ((1 + skip)..self.num_rows).map() do |row|
184
- (1..nc).map(){ |col| self[row, col] }.freeze()
185
- end
186
- return result.freeze()
187
- end
188
-
189
- # Reloads content of the worksheets from the server.
190
- # Note that changes you made by []= etc. is discarded if you haven't called save().
191
- def reload()
192
-
193
- doc = @session.request(:get, @cells_feed_url)
194
- @max_rows = doc.css("gs|rowCount").text.to_i()
195
- @max_cols = doc.css("gs|colCount").text.to_i()
196
- @title = doc.css("feed > title")[0].text
197
-
198
- @cells = {}
199
- @input_values = {}
200
- doc.css("feed > entry").each() do |entry|
201
- cell = entry.css("gs|cell")[0]
202
- row = cell["row"].to_i()
203
- col = cell["col"].to_i()
204
- @cells[[row, col]] = cell.inner_text
205
- @input_values[[row, col]] = cell["inputValue"]
206
- end
207
- @modified.clear()
208
- @meta_modified = false
209
- return true
210
-
211
- end
212
-
213
- # Saves your changes made by []=, etc. to the server.
214
- def save()
215
-
216
- sent = false
217
-
218
- if @meta_modified
219
-
220
- ws_doc = @session.request(:get, self.worksheet_feed_url)
221
- edit_url = ws_doc.css("link[rel='edit']")[0]["href"]
222
- xml = <<-"EOS"
223
- <entry xmlns='http://www.w3.org/2005/Atom'
224
- xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
225
- <title>#{h(self.title)}</title>
226
- <gs:rowCount>#{h(self.max_rows)}</gs:rowCount>
227
- <gs:colCount>#{h(self.max_cols)}</gs:colCount>
228
- </entry>
229
- EOS
230
-
231
- @session.request(:put, edit_url, :data => xml)
232
-
233
- @meta_modified = false
234
- sent = true
235
-
236
- end
237
-
238
- if !@modified.empty?
239
-
240
- # Gets id and edit URL for each cell.
241
- # Note that return-empty=true is required to get those info for empty cells.
242
- cell_entries = {}
243
- rows = @modified.map(){ |r, c| r }
244
- cols = @modified.map(){ |r, c| c }
245
- url = concat_url(@cells_feed_url,
246
- "?return-empty=true&min-row=#{rows.min}&max-row=#{rows.max}" +
247
- "&min-col=#{cols.min}&max-col=#{cols.max}")
248
- doc = @session.request(:get, url)
249
-
250
- doc.css("entry").each() do |entry|
251
- row = entry.css("gs|cell")[0]["row"].to_i()
252
- col = entry.css("gs|cell")[0]["col"].to_i()
253
- cell_entries[[row, col]] = entry
254
- end
255
-
256
- # Updates cell values using batch operation.
257
- # If the data is large, we split it into multiple operations, otherwise batch may fail.
258
- @modified.each_slice(250) do |chunk|
259
-
260
- xml = <<-EOS
261
- <feed xmlns="http://www.w3.org/2005/Atom"
262
- xmlns:batch="http://schemas.google.com/gdata/batch"
263
- xmlns:gs="http://schemas.google.com/spreadsheets/2006">
264
- <id>#{h(@cells_feed_url)}</id>
265
- EOS
266
- for row, col in chunk
267
- value = @cells[[row, col]]
268
- entry = cell_entries[[row, col]]
269
- id = entry.css("id").text
270
- edit_url = entry.css("link[rel='edit']")[0]["href"]
271
- xml << <<-EOS
272
- <entry>
273
- <batch:id>#{h(row)},#{h(col)}</batch:id>
274
- <batch:operation type="update"/>
275
- <id>#{h(id)}</id>
276
- <link rel="edit" type="application/atom+xml"
277
- href="#{h(edit_url)}"/>
278
- <gs:cell row="#{h(row)}" col="#{h(col)}" inputValue="#{h(value)}"/>
279
- </entry>
280
- EOS
281
- end
282
- xml << <<-"EOS"
283
- </feed>
284
- EOS
285
-
286
- batch_url = concat_url(@cells_feed_url, "/batch")
287
- result = @session.request(:post, batch_url, :data => xml)
288
- result.css("atom|entry").each() do |entry|
289
- interrupted = entry.css("batch|interrupted")[0]
290
- if interrupted
291
- raise(GoogleSpreadsheet::Error, "Update has failed: %s" %
292
- interrupted["reason"])
293
- end
294
- if !(entry.css("batch|status").first["code"] =~ /^2/)
295
- raise(GoogleSpreadsheet::Error, "Updating cell %s has failed: %s" %
296
- [entry.css("atom|id").text, entry.css("batch|status")[0]["reason"]])
297
- end
298
- end
299
-
300
- end
301
-
302
- @modified.clear()
303
- sent = true
304
-
305
- end
306
-
307
- return sent
308
-
309
- end
310
-
311
- # Calls save() and reload().
312
- def synchronize()
313
- save()
314
- reload()
315
- end
316
-
317
- # Deletes this worksheet. Deletion takes effect right away without calling save().
318
- def delete()
319
- ws_doc = @session.request(:get, self.worksheet_feed_url)
320
- edit_url = ws_doc.css("link[rel='edit']")[0]["href"]
321
- @session.request(:delete, edit_url)
322
- end
323
-
324
- # Returns true if you have changes made by []= which haven't been saved.
325
- def dirty?
326
- return !@modified.empty?
327
- end
328
-
329
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
330
- # March 2012.
331
- #
332
- # Creates table for the worksheet and returns GoogleSpreadsheet::Table.
333
- # See this document for details:
334
- # http://code.google.com/intl/en/apis/spreadsheets/docs/3.0/developers_guide_protocol.html#TableFeeds
335
- def add_table(table_title, summary, columns, options)
336
-
337
- warn(
338
- "DEPRECATED: Google Spreadsheet Table and Record feeds are deprecated and they " +
339
- "will not be available after March 2012.")
340
- default_options = { :header_row => 1, :num_rows => 0, :start_row => 2}
341
- options = default_options.merge(options)
342
-
343
- column_xml = ""
344
- columns.each() do |index, name|
345
- column_xml += "<gs:column index='#{h(index)}' name='#{h(name)}'/>\n"
346
- end
347
-
348
- xml = <<-"EOS"
349
- <entry xmlns="http://www.w3.org/2005/Atom"
350
- xmlns:gs="http://schemas.google.com/spreadsheets/2006">
351
- <title type='text'>#{h(table_title)}</title>
352
- <summary type='text'>#{h(summary)}</summary>
353
- <gs:worksheet name='#{h(self.title)}' />
354
- <gs:header row='#{options[:header_row]}' />
355
- <gs:data numRows='#{options[:num_rows]}' startRow='#{options[:start_row]}'>
356
- #{column_xml}
357
- </gs:data>
358
- </entry>
359
- EOS
360
-
361
- result = @session.request(:post, self.spreadsheet.tables_feed_url, :data => xml)
362
- return Table.new(@session, result)
363
-
364
- end
365
-
366
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
367
- # March 2012.
368
- #
369
- # Returns list of tables for the workwheet.
370
- def tables
371
- warn(
372
- "DEPRECATED: Google Spreadsheet Table and Record feeds are deprecated and they " +
373
- "will not be available after March 2012.")
374
- return self.spreadsheet.tables.select(){ |t| t.worksheet_title == self.title }
375
- end
376
-
377
- # List feed URL of the worksheet.
378
- def list_feed_url
379
- # Gets the worksheets metafeed.
380
- entry = @session.request(:get, self.worksheet_feed_url)
381
-
382
- # Gets the URL of list-based feed for the given spreadsheet.
383
- return entry.css(
384
- "link[rel='http://schemas.google.com/spreadsheets/2006#listfeed']")[0]["href"]
385
- end
386
-
387
- # Provides access to cells using column names, assuming the first row contains column
388
- # names. Returned object is GoogleSpreadsheet::List which you can use mostly as
389
- # Array of Hash.
390
- #
391
- # e.g. Assuming the first row is ["x", "y"]:
392
- # worksheet.list[0]["x"] #=> "1" # i.e. worksheet[2, 1]
393
- # worksheet.list[0]["y"] #=> "2" # i.e. worksheet[2, 2]
394
- # worksheet.list[1]["x"] = "3" # i.e. worksheet[3, 1] = "3"
395
- # worksheet.list[1]["y"] = "4" # i.e. worksheet[3, 2] = "4"
396
- # worksheet.list.push({"x" => "5", "y" => "6"})
397
- #
398
- # Note that update is not sent to the server until you call save().
399
- def list
400
- return @list ||= List.new(self)
401
- end
402
-
403
- # Returns a [row, col] pair for a cell name string.
404
- # e.g.
405
- # worksheet.cell_name_to_row_col("C2") #=> [2, 3]
406
- def cell_name_to_row_col(cell_name)
407
- if !cell_name.is_a?(String)
408
- raise(ArgumentError, "Cell name must be a string: %p" % cell_name)
409
- end
410
- if !(cell_name.upcase =~ /^([A-Z]+)(\d+)$/)
411
- raise(ArgumentError,
412
- "Cell name must be only letters followed by digits with no spaces in between: %p" %
413
- cell_name)
414
- end
415
- col = 0
416
- $1.each_byte() do |b|
417
- # 0x41: "A"
418
- col = col * 26 + (b - 0x41 + 1)
419
- end
420
- row = $2.to_i()
421
- return [row, col]
422
- end
423
-
424
- def inspect
425
- fields = {:worksheet_feed_url => self.worksheet_feed_url}
426
- fields[:title] = @title if @title
427
- return "\#<%p %s>" % [self.class, fields.map(){ |k, v| "%s=%p" % [k, v] }.join(", ")]
428
- end
429
-
430
- private
431
-
432
- def parse_cell_args(args)
433
- if args.size == 1 && args[0].is_a?(String)
434
- return cell_name_to_row_col(args[0])
435
- elsif args.size == 2 && args[0].is_a?(Integer) && args[1].is_a?(Integer)
436
- return args
437
- else
438
- raise(ArgumentError,
439
- "Arguments must be either one String or two Integer's, but are %p" % args)
440
- end
441
- end
442
-
443
- end
444
-
445
- end
4
+ require "google_spreadsheet"