google_drive 1.0.4 → 1.0.5

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