google-spreadsheet-ruby 0.2.1 → 0.3.0

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.
@@ -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"