google_drive 1.0.4 → 1.0.5

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,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