├── README.md ├── lib └── spreadsheets │ ├── Spreadsheets.js │ ├── action9.js │ ├── chain.js │ ├── core.js │ └── fork.js ├── package.json └── xml ├── batchcellentry.xml ├── batchfeed.xml ├── cellentry.xml ├── docentry.xml └── worksheetentry.xml /README.md: -------------------------------------------------------------------------------- 1 | node-spreadsheets 2 | = 3 | 4 | An adaptor for Google Spreadsheets API. 5 | 6 | Install 7 | - 8 | 9 | npm install spreadsheets 10 | 11 | 12 | Usage 13 | - 14 | 15 | require `spreadsheets` 16 | 17 | ```js 18 | var authenticate = require("spreadsheets"); 19 | 20 | ``` 21 | 22 | authenticate with Google Client Login & get your spreadsheets list 23 | 24 | ```js 25 | authenticate({ 26 | Email: your email for google account, 27 | Passwd: password 28 | 29 | }, function(err, spreadsheets) { 30 | if(err) 31 | // handle error 32 | spreadsheets.list(function(err, list) { 33 | if(err) 34 | // handle error 35 | ........ 36 | }); 37 | }); 38 | ``` 39 | 40 | choose a spreadsheet and a worksheet 41 | 42 | ```js 43 | var spreadsheet = list[ spreadsheet key ]; 44 | spreadsheet.worksheet(function(err, sheets) { 45 | if(err)) 46 | // handle error 47 | var worksheet = sheets[ *worksheet key* ]; 48 | }); 49 | ``` 50 | 51 | get cells and change value 52 | 53 | ```js 54 | worksheet.cell({ 55 | "min-row": 2, 56 | "min-col": 4, 57 | "max-col": 4 58 | 59 | }, function(err, cells) { 60 | if(err) 61 | // handle error 62 | var cell = cells[ cell key ]; // for example R3C4 63 | 64 | cell.changeValue("any value", function(err) { 65 | if(err) 66 | // handle error 67 | ...... 68 | }); 69 | }); 70 | ``` 71 | 72 | change styles 73 | 74 | ```js 75 | worksheet.style({ 76 | scol: 2, 77 | ecol: 3, 78 | srow: 2, 79 | erow: 4 80 | 81 | }, function(err, style) { 82 | if(err) 83 | // handle error 84 | style.color("#ff0000", function() {}); 85 | style.bgColor("#ff0000", function() {}); 86 | style.fontsize("10pt", function() {}); 87 | style.align("right", function() {}); 88 | }); 89 | ``` 90 | 91 | Dependency 92 | = 93 | * [node-jQuery](https://github.com/praized/node-jquery) 94 | 95 | -------------------------------------------------------------------------------- /lib/spreadsheets/Spreadsheets.js: -------------------------------------------------------------------------------- 1 | /***/ 2 | 3 | var fs = require("fs"), querystring = require('querystring'), url = require("url"); 4 | var path = require("path"), $ = require("jquery"); 5 | var core = require("./core"), chain = require("./chain"), fork = require("./fork"); 6 | 7 | // extend jQuery 8 | (function($) { 9 | $.fn.outerXml = function() { 10 | var xml = this.wrap("").parent().html(); 11 | xml = xml.replace(/(]+>)/g, "$1"); 12 | return xml; 13 | }; 14 | })($); 15 | 16 | module.exports = exports = spreadsheets; 17 | 18 | function spreadsheets(auth, callback) { 19 | if(auth && $.isFunction(callback)) 20 | clientLogin.call(this, auth, function(err, _auth) { 21 | var sheet = !err && Spreadsheets(_auth); 22 | callback(err, sheet); 23 | }); 24 | } 25 | 26 | function clientLogin(auth, callback) { 27 | var self = this, _auth; 28 | 29 | fork.call(self, function(cb) { 30 | _clientLogin("wise", cb); 31 | 32 | }, function(cb) { 33 | _clientLogin("writely", cb); 34 | 35 | }, function(err, args) { 36 | if(!err) { 37 | _auth = {}, _auth["wise"] = {}, _auth["writely"] = {}; 38 | _auth["wise"].Authorization = "GoogleLogin auth=" + args[0].Auth; 39 | _auth["writely"].Authorization = "GoogleLogin auth=" + args[1].Auth; 40 | } 41 | callback.call(self, err, _auth); 42 | 43 | }); 44 | 45 | function _clientLogin(service, _callback) { 46 | chain.call(self, function(next) { 47 | var data = querystring.stringify({ 48 | accountType: "GOOGLE", 49 | service: service, 50 | Email: auth.Email, 51 | Passwd: auth.Passwd 52 | }); 53 | core.authenticate.call(self, null, data, next); 54 | 55 | }, function(info, next) { 56 | var auth = {}; 57 | var i, line = info.split(/\n/); 58 | for (i = line.length; i--;) 59 | if(line[i]) { 60 | var kv = line[i].split("="); 61 | auth[kv[0]] = kv[1]; 62 | } 63 | // _auth["wise"].Authorization = "GoogleLogin auth=" + auth.Auth; 64 | next.call(self, null, auth); 65 | 66 | }, _callback); 67 | } 68 | 69 | }; 70 | 71 | function Spreadsheets(auth) { 72 | var self = this; 73 | if(!(self instanceof Spreadsheets)) 74 | return new Spreadsheets(auth); 75 | self._auth = auth, self._list = {}; 76 | } 77 | 78 | Spreadsheets.prototype.list = function(callback) { 79 | var self = this, opt = { 80 | headers: self._auth["wise"] 81 | }; 82 | chain.call(self, function(next) { 83 | core.list.call(self, opt, "", next); 84 | 85 | }, function(xml, next) { 86 | var $entries = $("entry", xml), list = self._list = {}; 87 | $entries.each(function() { 88 | var $this = $(this), $id = $("id", $this); 89 | var sp = $id.text().split(/\//), key = sp[sp.length - 1]; 90 | var ss = Spreadsheet(key, self._auth); 91 | ss._xml = $this.outerXml(); 92 | list[key] = ss; 93 | }); 94 | next.call(null, null, list); 95 | 96 | }, callback); 97 | }; 98 | 99 | Spreadsheets.prototype.createSpreadsheet = function(settings, callback) { 100 | if($.isFunction(settings)) 101 | callback = settings, settings = {}; 102 | var self = this, opt = { 103 | headers: self._auth["writely"] 104 | }; 105 | 106 | chain.call(self, function(next) { 107 | var xml = $(fs.readFileSync("xml/docentry.xml").toString()); 108 | $("title", xml).text(settings.title || "untitled"); 109 | var data = xml.outerXml(); 110 | core.createSpreadsheet.call(self, opt, data, next); 111 | 112 | }, function(xml, next) { 113 | var $xml = $(xml); 114 | var id = $("id", $xml).text(); 115 | var sp = id.split(/spreadsheet%3A/), key = sp[1]; 116 | var ss = Spreadsheet(key, self._auth); 117 | ss._xml = $xml.outerXml(); 118 | self._list[key] = ss; 119 | 120 | next.call(self, null, ss); 121 | 122 | }, callback); 123 | }; 124 | 125 | Spreadsheets.prototype.downloadSpreadsheet = function(key, format, out, callback) { 126 | var self = this; 127 | 128 | chain.call(self, function(next) { 129 | self.getSpreadsheet(key, next); 130 | 131 | }, function(ss, next) { 132 | ss.download(format, out, next); 133 | 134 | }, callback); 135 | }; 136 | 137 | Spreadsheets.prototype.moveToTrash = function(key, callback) { 138 | var self = this; 139 | 140 | chain.call(self, function(next) { 141 | self.getSpreadsheet(key, next); 142 | 143 | }, function(ss, next) { 144 | ss.trash(next); 145 | 146 | }, callback); 147 | }; 148 | 149 | Spreadsheets.prototype.deleteSpreadsheet = function(key, callback) { 150 | var self = this; 151 | 152 | chain.call(self, function(next) { 153 | self.getSpreadsheet(key, next); 154 | 155 | }, function(ss, next) { 156 | ss["delete"](next); 157 | 158 | }, callback); 159 | }; 160 | 161 | Spreadsheets.prototype.getSpreadsheet = function(key, callback) { 162 | var self = this, list = self._list, opt = { 163 | path: "/feeds/default/private/full/" + key, 164 | headers: self._auth["writely"] 165 | }; 166 | 167 | chain.call(self, function(next) { 168 | key in list ? next.call(null, null, list[key]): core.getSpreadsheet.call(self, opt, "", next); 169 | 170 | }, function(xml, next) { 171 | var ss = Spreadsheet(key, self._auth); 172 | ss._xml = xml; 173 | next.call(self, null, ss); 174 | 175 | }, callback); 176 | }; 177 | 178 | /** 179 | * Spreadsheet 180 | */ 181 | function Spreadsheet(key, auth) { 182 | var self = this; 183 | if(!(self instanceof Spreadsheet)) 184 | return new Spreadsheet(key, auth); 185 | self._key = key, self._auth = auth["wise"], self._docauth = auth["writely"]; 186 | self._list = {}, self._titles = {}; 187 | } 188 | 189 | Spreadsheet.prototype.worksheet = function(callback) { 190 | var self = this, opt = { 191 | path: "/feeds/worksheets/" + self._key + "/private/full", 192 | headers: self._auth 193 | }; 194 | 195 | chain.call(self, function(next) { 196 | core.worksheet.call(self, opt, "", next); 197 | 198 | }, function(xml, next) { 199 | var $entries = $("entry", xml); 200 | var list = self._list = {}, titles = self._titles = {}; 201 | $entries.each(function() { 202 | var $this = $(this); 203 | var id = $("id", $this), name = $("title", $this).text(); 204 | var sp = id.text().split(/\//), key = sp[sp.length - 1]; 205 | var ws = Worksheet(key, name, self); 206 | ws._xml = $this.outerXml(); 207 | // list[key] = ws; 208 | // titles[name] = ws; 209 | }); 210 | next.call(self, null, list); 211 | 212 | }, callback); 213 | }; 214 | 215 | Spreadsheet.prototype.addWorksheet = function(settings, callback) { 216 | if($.isFunction(settings)) 217 | callback = settings, settings = {}; 218 | var self = this, opt = { 219 | path: "/feeds/worksheets/" + self._key + "/private/full", 220 | headers: self._auth 221 | }; 222 | var xml = $(fs.readFileSync("xml/worksheetentry.xml").toString());// TODO 223 | // async 224 | $("title", xml).text(settings.title || "untitled"); 225 | $("gs\\:rowCount", xml).text(settings.row || 100); 226 | $("gs\\:colCount", xml).text(settings.col || 20); 227 | var data = xml.outerXml(); 228 | // TODO Any bug depending on jquery will lurk... 229 | data = data.replace(/count/g, "Count"); 230 | 231 | chain.call(self, function(next) { 232 | core.addWorksheet.call(self, opt, data, next); 233 | 234 | }, function(xml, next) { 235 | var $xml = $(xml); 236 | var id = $("id", $xml).text(), name = $("title", $xml).text(); 237 | var sp = id.split(/\//), key = sp[sp.length - 1]; 238 | var ws = self._list[key] = Worksheet(key, name, self); 239 | ws._xml = $xml.outerXml(); 240 | next.call(self, null, ws); 241 | 242 | }, callback); 243 | }; 244 | 245 | /** 246 | * format values: [xls, csv, pdf, ods, tsv, html] 247 | */ 248 | Spreadsheet.prototype.download = function(format, out, callback) { 249 | if("function" === typeof format) 250 | callback = format, format = "xls", out = "."; 251 | 252 | else if("function" === typeof out) 253 | callback = out, out = "."; 254 | 255 | var self = this; 256 | var $content = $("content", self._xml).eq(0), src = $content.attr("src"); 257 | var _url = url.parse(src); 258 | 259 | if("string" === typeof out) 260 | out = _create(out); 261 | 262 | var opt = { 263 | host: _url["hostname"], 264 | path: _url["pathname"] + "?" + _url["query"] + "&exportFormat=" + format, 265 | headers: self._auth, 266 | outputStream: out 267 | }; 268 | core.download.call(self, opt, "", callback); 269 | 270 | function _create(pathname) { 271 | if(path.existsSync(pathname)) 272 | if(fs.statSync(pathname).isDirectory()) 273 | return _create(path.join(pathname, $("title", self._xml).text() + "." + format)); 274 | else { 275 | var s, splits = pathname.split(/\./), len = splits.length; 276 | var index = 2 <= len ? len - 2: 0, fname = splits[index]; 277 | if(s = fname.match(/\((\d+)\)$/)) 278 | splits[index] = fname.replace(/\(\d+\)$/, "(" + (+s[1] + 1) + ")"); 279 | else 280 | splits[index] += "(1)"; 281 | return _create(splits.join(".")); 282 | } 283 | else 284 | return fs.createWriteStream(pathname); 285 | } 286 | }; 287 | 288 | Spreadsheet.prototype.trash = function(callback) { 289 | _delete.call(this, false, callback); 290 | }; 291 | 292 | Spreadsheet.prototype["delete"] = function(callback) { 293 | _delete.call(this, true, callback); 294 | }; 295 | 296 | function _delete(flg, callback) { 297 | var self = this, key = self._key; 298 | var opt = { 299 | path: "/feeds/default/private/full/" + key + "?delete=" + flg, 300 | headers: $.extend({ 301 | "If-None-Match": "W/\"" + key + ".\"" 302 | }, self._docauth) 303 | }; 304 | core.deleteSpreadsheet.call(self, opt, "", callback); 305 | } 306 | 307 | /** 308 | * Worksheet 309 | */ 310 | function Worksheet(key, title, spreadsheet) { 311 | var self = this; 312 | if(!(self instanceof Worksheet)) 313 | return new Worksheet(key, title, spreadsheet); 314 | self._key = key, self._title = title, self._parent = spreadsheet; 315 | spreadsheet._list[key] = spreadsheet._titles[title] = self; 316 | } 317 | 318 | Worksheet.prototype.row = function(callback) { 319 | var self = this, opt = { 320 | path: "/feeds/list/" + self._parent._key + "/" + self._key 321 | + "/private/full", 322 | headers: self._parent._auth 323 | }; 324 | 325 | chain.call(self, function(next) { 326 | core.row.call(self, opt, "", next); 327 | 328 | }, function(xml, next) { 329 | var $entries = $("entry", xml), list = self._list = {}; 330 | $entries.each(function() { 331 | var $this = $(this), $id = $("id", $this); 332 | var sp = $id.text().split(/\//), key = sp[sp.length - 1]; 333 | var row = Row(key, self); 334 | row._xml = $this.outerXml(); 335 | list[key] = row; 336 | }); 337 | next.call(self, null, list); 338 | 339 | }, callback); 340 | }; 341 | 342 | Worksheet.prototype.cell = function(query, callback) { 343 | if($.isFunction(query)) 344 | callback = query, query = ""; 345 | if(typeof query !== "string" && !(query instanceof String)) 346 | query = querystring.stringify(query); 347 | if(!!query && /^[^\?]/.test(query)) 348 | query = "?" + query; 349 | var self = this, opt = { 350 | path: "/feeds/cells/" + self._parent._key + "/" + self._key 351 | + "/private/full" + query, 352 | headers: self._parent._auth 353 | }; 354 | 355 | chain.call(self, function(next) { 356 | core.cell.call(self, opt, "", next); 357 | 358 | }, function(xml, next) { 359 | var $entries = $("entry", xml), list = self._list = self._list || {}; 360 | $entries.each(function() { 361 | var $this = $(this), $id = $("id", $this); 362 | var name = $("title", $this).text(), val = $("content", $this).text(); 363 | var sp = $id.text().split(/\//), key = sp[sp.length - 1]; 364 | var cell = Cell(key, name, val, self); 365 | cell._xml = $this.outerXml(); 366 | list[key] = cell; 367 | }); 368 | next.call(self, null, list); 369 | 370 | }, callback); 371 | }; 372 | 373 | Worksheet.prototype.getCell = function(key, callback) { 374 | var self = this; 375 | var cell = Cell(key, null, null, self); 376 | cell._get(callback); 377 | }; 378 | 379 | var Style = require("./action9"); 380 | Worksheet.prototype.style = function(range, callback) { 381 | var self = this; 382 | callback.call(self, null, Style(range, self)); 383 | }; 384 | 385 | /** 386 | * vals = [[row, col, inputValue], ...] 387 | */ 388 | Worksheet.prototype.updateCells = function(vals, callback) { 389 | var self = this, p = self._parent; 390 | var path = "/feeds/cells/" + p._key + "/" + self._key + "/private/full"; 391 | var id = "https://spreadsheets.google.com" + path; 392 | 393 | var $feed; 394 | chain.call(self, function(next) { 395 | fs.readFile("xml/batchfeed.xml", "utf8", next); 396 | }, function(buf, next) { 397 | $feed = $(buf.toString()); 398 | $("id", $feed).text(id); 399 | fs.readFile("xml/batchcellentry.xml", "utf8", next); 400 | }, function(buf, next) { 401 | var xml = buf.toString(), opt = { 402 | path: path + "/batch", 403 | headers: $.extend({ 404 | "If-None-Match": "W/\"" + p._key + ".\"" 405 | }, p._auth) 406 | }, i, newCells = vals; 407 | for (i = newCells.length; i--;) { 408 | var $entry = $(xml), cell = newCells[i]; 409 | var _id = id + "/R" + cell[0] + "C" + cell[1]; 410 | $("batch\\:id", $entry).text("A" + i); 411 | $("title", $entry).text("A" + i); 412 | $("id", $entry).text(_id); 413 | $("link", $entry).attr({ 414 | href: _id + "/1" 415 | }); 416 | $("gs\\:cell", $entry).attr({ 417 | row: cell[0], 418 | col: cell[1], 419 | inputValue: cell[2] 420 | }); 421 | $feed.append($entry); 422 | } 423 | core.updateCells.call(self, opt, $feed.outerXml(), next); 424 | }, function(xml, next) { 425 | var err = /error/i.test(xml) ? new Error(xml): null; 426 | next.call(self, err); 427 | 428 | }, callback); 429 | }; 430 | 431 | /** 432 | * TODO Row 433 | */ 434 | function Row(key, worksheet) { 435 | var self = this; 436 | if(!(self instanceof Row)) 437 | return new Row(key, worksheet); 438 | self._key = key; 439 | self._parent = worksheet; 440 | } 441 | 442 | /** 443 | * Cell 444 | */ 445 | function Cell(key, title, val, worksheet) { 446 | var self = this; 447 | if(!(self instanceof Cell)) 448 | return new Cell(key, worksheet); 449 | var rc = (new RegExp("^R(\\d+)C(\\d+)$")).exec(key); 450 | self._key = key, self._row = +rc[1], self._col = +rc[2]; 451 | self._title = title, self._value = val, self._parent = worksheet; 452 | } 453 | 454 | Cell.prototype.style = function(callback) { 455 | var self = this, range = {}; 456 | range.scol = range.ecol = self._col; 457 | range.srow = range.erow = self._row; 458 | self._parent.style(range, callback); 459 | }; 460 | 461 | Cell.prototype.changeValue = function(val, callback) { 462 | var self = this, p = self._parent, gp = p._parent; 463 | var path = "/feeds/cells/" + gp._key + "/" + p._key + "/private/full/" 464 | + self._key; 465 | 466 | chain.call(self, function(next) { 467 | fs.readFile("xml/cellentry.xml", "utf8", next); 468 | }, function(buf, next) { 469 | var $xml = $(buf.toString()); 470 | var id = "https://spreadsheets.google.com" + path; 471 | $("id", $xml).text(id); 472 | $("link", $xml).attr({ 473 | href: id 474 | }); 475 | $("gs\\:cell", $xml).attr({ 476 | row: self._row, 477 | col: self._col, 478 | inputValue: val 479 | }); 480 | var data = $xml.outerXml(), opt = { 481 | path: path, 482 | headers: $.extend({ 483 | "If-None-Match": "W/\"" + gp._key + ".\"" 484 | }, gp._auth) 485 | }; 486 | core.changeCell.call(self, opt, data, next); 487 | 488 | }, function(xml, next) { 489 | self._xml = $(xml).outerXml(); 490 | next.call(self, null, self); 491 | 492 | }, callback); 493 | }; 494 | 495 | Cell.prototype.getValue = function(callback) { 496 | var self = this, p = self._parent, key = self._key; 497 | chain.call(self, self._get, function(cell, next) { 498 | callback.call(self, null, cell ? cell._value: null); 499 | }); 500 | }; 501 | 502 | Cell.prototype._get = function(callback) { 503 | var self = this, p = self._parent; 504 | var query = {}; 505 | chain.call(self, function(next) { 506 | query["min-row"] = query["max-row"] = self._row; 507 | query["min-col"] = query["max-col"] = self._col; 508 | p.cell(query, next); 509 | 510 | }, function(cells) { 511 | var key = self._key, cell = cells[key] = cells[key] 512 | || Cell(key, null, null, p); 513 | p._list[key] = cell; 514 | callback.call(self, null, cell); 515 | }); 516 | }; 517 | -------------------------------------------------------------------------------- /lib/spreadsheets/action9.js: -------------------------------------------------------------------------------- 1 | /***/ 2 | 3 | var querystring = require("querystring"), core = require("./core"), chain = require("./chain"); 4 | 5 | module.exports = exports = Style; 6 | 7 | function Style(range, worksheet) { 8 | var self = this; 9 | if(!(self instanceof Style)) 10 | return new Style(range, worksheet); 11 | self.range(range), self._ws = worksheet, self._id = worksheet._id; 12 | self._gid = worksheet._gid, self._auth = worksheet._parent._auth; 13 | } 14 | 15 | Style.FONT = { 16 | Normal: "arial,sans,sans-serif", 17 | "Normal/serif": "times new roman,serif", 18 | CourierNew: "courier new,monospace", 19 | Georgia: "georgia", 20 | TrebuchetMS: "trebuchet ms", 21 | Verdana: "verdana" 22 | }; 23 | Style.TEXT = ["underline", "line-through", "none"]; 24 | 25 | var fn, fns = { 26 | format: 8, // TODO 27 | font: 9, // Stryle.FONT 28 | fontsize: 10, // 6pt 29 | bold: 11, // bold or normal 30 | italic: 12, // italic or normal 31 | line: 13, // underline, line-through or none 32 | wrap: 14, // nowrap or normal 33 | align: 15, // left/center/right 34 | valign: 16, // top/middle/bottom 35 | color: 17, // #ff0000 36 | bgColor: 31, // #ffffff 37 | drawLines: 32, // 111111 top/left/bottom/right/holizontal/vertical 38 | fixRow: 40, 39 | rowHeight: 41, // number 40 | columnWidth: 42, // number 41 | joinCells: 43, // null 42 | fixColumn: 45, 43 | insertAbove: 61, // number 44 | insertLeft: 63, // number 45 | insertBelow: 68, // number 46 | insertRight: 69, // number 47 | rangeNames: 91, 48 | comment: 501, 49 | uncomment: 502 50 | }; 51 | 52 | for (fn in fns) 53 | Style.prototype[fn] = reflect(fns[fn]); 54 | 55 | function reflect(num) { 56 | return function(val, callback) { 57 | _query.call(this, num, val, callback); 58 | }; 59 | } 60 | 61 | Style.prototype.initialize = function(callback) { 62 | var self = this; 63 | 64 | if(self._id && self._gid) 65 | return callback.call(self, null); 66 | 67 | var key = self._ws._parent._key, auth = self._auth, titles = self._ws._parent._titles; 68 | 69 | chain.call(self, function(next) { 70 | var opt = { 71 | path: "/spreadsheet/ccc?&key=" + key, 72 | headers: auth 73 | }; 74 | core.ccc.call(self, opt, "", next); 75 | 76 | }, function(info, next) { 77 | var ser = (new RegExp(/*key + */"[\\w\\-]+\\.\\d+\\.\\d+")).exec(info); 78 | self._id = self._ws._id = ser[0]; 79 | var i, s = (new RegExp("structure: \\({action:0, a:(\\[[^\\]]*\\])")) 80 | .exec(info)[1]; 81 | var sheets = JSON.parse(s); 82 | for (i = sheets.length; i--;) 83 | if(sheets[i]["g"]in titles) 84 | titles[sheets[i]["g"]]._gid = sheets[i]["i"]; 85 | self._gid = self._ws._gid; 86 | next.call(self, null); 87 | 88 | }, callback); 89 | 90 | }; 91 | 92 | /** 93 | * @deprecated 94 | * @param range 95 | */ 96 | Style.prototype.range = function(range) { 97 | this._range = range; 98 | }; 99 | 100 | function _query(num, val, callback) { 101 | var self = this; 102 | 103 | var fncs = [self.initialize, function(next) { 104 | var r = self._range; 105 | r.action = 9; 106 | r.atyp = num; 107 | r.gid = self._gid; 108 | r.v = val; 109 | var data = querystring.stringify(r); 110 | var opt = { 111 | path: "/spreadsheet/edit/action9?&id=" + self._id, 112 | headers: self._auth 113 | }; 114 | core.action9.call(self, opt, data, next); 115 | }, callback]; 116 | chain.apply(self, fncs); 117 | } 118 | 119 | // var ex = {}; 120 | // 121 | // ex[8] = function format() { 122 | // // v=%24%23%2C%23%230.00 123 | // // v=0.00%25 124 | // }; 125 | -------------------------------------------------------------------------------- /lib/spreadsheets/chain.js: -------------------------------------------------------------------------------- 1 | /***/ 2 | 3 | module.exports = function() { 4 | var self = this; 5 | var actors = Array.prototype.slice.call(arguments); 6 | next(); 7 | function next(err) { 8 | try { 9 | if(err) 10 | return actors.pop().call(self, err); 11 | var actor = actors.shift(); 12 | var args = Array.prototype.slice.call(arguments); 13 | if(actors.length > 0) { 14 | args = args.slice(1).concat(next); 15 | } 16 | actor.apply(self, args); 17 | } catch (error) { 18 | actors.length === 0 ? actor.call(self, error): next(error); 19 | } 20 | } 21 | }; -------------------------------------------------------------------------------- /lib/spreadsheets/core.js: -------------------------------------------------------------------------------- 1 | /***/ 2 | var http = require("http"), https = require("https"), querystring = require("querystring"); 3 | var $ = require("jquery"); 4 | 5 | var VERSION = "3.0"; 6 | 7 | var core = { 8 | _default: { 9 | opt: function(opt) { 10 | return opt; 11 | }, 12 | success: 200 13 | }, 14 | authenticate: { 15 | opt: function() { 16 | return { 17 | host: "www.google.com", 18 | path: "/accounts/ClientLogin", 19 | method: "POST", 20 | headers: { 21 | "Content-Type": "application/x-www-form-urlencoded" 22 | } 23 | }; 24 | }, 25 | success: 200 26 | }, 27 | 28 | list: { 29 | opt: function(opt) { 30 | return $.extend(true, { 31 | host: "spreadsheets.google.com", 32 | path: "/feeds/spreadsheets/private/full", 33 | method: "GET", 34 | headers: { 35 | "Content-Type": "application/x-www-form-urlencoded", 36 | "GDATA-Version": VERSION 37 | } 38 | }, opt); 39 | }, 40 | success: 200 41 | }, 42 | 43 | worksheet: { 44 | opt: function(opt) { 45 | return $.extend(true, { 46 | host: "spreadsheets.google.com", 47 | method: "GET", 48 | headers: { 49 | "Content-Type": "application/x-www-form-urlencoded", 50 | "GDATA-Version": VERSION 51 | } 52 | }, opt); 53 | }, 54 | success: 200 55 | }, 56 | 57 | addWorksheet: { 58 | opt: function(opt) { 59 | return $.extend(true, { 60 | host: "spreadsheets.google.com", 61 | method: "POST", 62 | headers: { 63 | "Content-Type": "application/atom+xml", 64 | "GDATA-Version": VERSION 65 | } 66 | }, opt); 67 | }, 68 | success: 201 69 | }, 70 | 71 | changeCell: { 72 | opt: function(opt) { 73 | return $.extend(true, { 74 | host: "spreadsheets.google.com", 75 | method: "PUT", 76 | headers: { 77 | "Content-Type": "application/atom+xml", 78 | "GDATA-Version": VERSION 79 | } 80 | }, opt); 81 | }, 82 | success: 200 83 | }, 84 | 85 | updateCells: { 86 | opt: function(opt) { 87 | return $.extend(true, { 88 | host: "spreadsheets.google.com", 89 | method: "POST", 90 | headers: { 91 | "Content-Type": "application/atom+xml", 92 | "GDATA-Version": VERSION 93 | } 94 | }, opt); 95 | }, 96 | success: 200 97 | }, 98 | 99 | deleteWorksheet: { 100 | opt: function(opt) { 101 | return $.extend(true, { 102 | hots: "spreadsheets.google.com", 103 | method: "DELETE", 104 | headers: { 105 | "Content-Type": "application/x-www-form-urlencoded", 106 | "GDATA-Version": VERSION 107 | } 108 | }, opt); 109 | }, 110 | success: 200 111 | // TODO 112 | }, 113 | 114 | cell: { 115 | opt: function(opt) { 116 | return $.extend(true, { 117 | host: "spreadsheets.google.com", 118 | method: "GET", 119 | headers: { 120 | "Content-Type": "application/x-www-form-urlencoded", 121 | "GDATA-Version": VERSION 122 | } 123 | }, opt); 124 | }, 125 | success: 200 126 | }, 127 | 128 | row: { 129 | opt: function(opt) { 130 | return $.extend(true, { 131 | host: "spreadsheets.google.com", 132 | method: "GET", 133 | headers: { 134 | "Content-Type": "application/x-www-form-urlencoded", 135 | "GDATA-Version": VERSION 136 | } 137 | }, opt); 138 | }, 139 | success: 200 140 | }, 141 | 142 | ccc: { 143 | opt: function(opt) { 144 | return $.extend(true, { 145 | host: "docs.google.com", 146 | method: "GET", 147 | headers: { 148 | "X-Same-Domain": "trix", 149 | "Content-Type": "application/x-www-form-urlencoded", 150 | // "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) 151 | // AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.10 152 | // Safari/535.1", 153 | "User-Agent": "AppleWebKit/535.1" 154 | } 155 | }, opt); 156 | }, 157 | success: 200 158 | }, 159 | 160 | action9: { 161 | opt: function(opt) { 162 | return $.extend(true, { 163 | host: "docs.google.com", 164 | method: "POST", 165 | headers: { 166 | "X-Same-Domain": "trix", 167 | "Content-Type": "application/x-www-form-urlencoded" 168 | } 169 | }, opt); 170 | }, 171 | success: 200 172 | }, 173 | 174 | createSpreadsheet: { 175 | opt: function(opt) { 176 | return $.extend(true, { 177 | host: "docs.google.com", 178 | path: "/feeds/default/private/full", 179 | method: "POST", 180 | headers: { 181 | "Content-Type": "application/atom+xml", 182 | "GDATA-Version": VERSION 183 | } 184 | }, opt); 185 | }, 186 | success: 201 187 | }, 188 | 189 | getSpreadsheet: { 190 | opt: function(opt) { 191 | return $.extend(true, { 192 | host: "docs.google.com", 193 | method: "GET", 194 | headers: { 195 | "Content-Type": "application/atom+xml", 196 | "GDATA-Version": VERSION 197 | } 198 | }, opt); 199 | }, 200 | success: 200 201 | }, 202 | 203 | download: { 204 | opt: function(opt) { 205 | return $.extend(true, { 206 | method: "GET", 207 | headers: { 208 | "Content-Type": "application/atom+xml", 209 | "GDATA-Version": VERSION 210 | } 211 | }, opt); 212 | }, 213 | success: 200 214 | }, 215 | 216 | deleteSpreadsheet: { 217 | opt: function(opt) { 218 | return $.extend(true, { 219 | method: "DELETE", 220 | host: "docs.google.com", 221 | headers: { 222 | "Content-Type": "application/atom+xml", 223 | "GDATA-Version": VERSION 224 | } 225 | }, opt); 226 | }, 227 | success: 200 228 | } 229 | }; 230 | 231 | var fn; 232 | for (fn in core) { 233 | exports[fn] = _fn(core[fn]); 234 | } 235 | 236 | function _fn(settings) { 237 | return function(opt, data, callback) { 238 | var self = this; 239 | request.call(self, settings.opt(opt), data, 240 | function(err, info) { 241 | if(info && info.statusCode !== settings.success) 242 | err = new Error(info.statusCode + " " + info.status + "\n" 243 | + info.data); 244 | if(err) 245 | return callback.call(self, err); 246 | callback.call(self, null, info.data); 247 | }); 248 | }; 249 | } 250 | 251 | function request(options, data, callback) { 252 | // console.log(options); 253 | // console.log(data); 254 | var os = options["outputStream"]; 255 | delete options["outputStream"]; 256 | 257 | var self = this, req = https.request(options); 258 | 259 | req.on("response", function(response) { 260 | var info = ""; 261 | 262 | response.on("end", function() { 263 | var res = this, ret = {}; 264 | ret.statusCode = res.statusCode; 265 | ret.status = http.STATUS_CODES[res.statusCode]; 266 | ret.data = info; 267 | callback.call(self, null, ret); 268 | }); 269 | response.on("close", function() { 270 | response.emit("end"); 271 | }); 272 | 273 | if(os) 274 | return response.pipe(os); 275 | 276 | response.setEncoding("utf8"); 277 | 278 | response.on("data", function(data) { 279 | info += data; 280 | }); 281 | }); 282 | 283 | req.on("error", function(err) { 284 | callback.call(self, err); 285 | }); 286 | 287 | req.end(data); 288 | }; 289 | 290 | -------------------------------------------------------------------------------- /lib/spreadsheets/fork.js: -------------------------------------------------------------------------------- 1 | /***/ 2 | var events = require("events"); 3 | 4 | module.exports = function() { 5 | var self = this; 6 | var i, len, actors = Array.prototype.slice.call(arguments); 7 | var _ret = [], _cb = cb = actors.pop(); 8 | var evt = new events.EventEmitter(); 9 | 10 | evt.on("end", function(info, index) { 11 | _ret[index] = info; 12 | _cb = _cb.call(self, null, _ret); 13 | }); 14 | evt.on("error", function(err) { 15 | cb.call(self, err); 16 | }); 17 | 18 | try { 19 | for (i = 0, len = actors.length; i < len; i++) { 20 | _cb = wrap(_cb); 21 | process.nextTick(act(actors[i], i)); 22 | } 23 | _cb = _cb.call(self, null, null); 24 | } catch (err) { 25 | _finally.call(self, err); 26 | } 27 | 28 | function act(actor, index) { 29 | return function() { 30 | try { 31 | var length = actor.length, args = []; 32 | if(length === 0) 33 | return evt.emit("end", actor.apply(self, args), index); 34 | args[length - 1] = function(err, info) { 35 | if(err) 36 | return evt.emit("error", err); 37 | evt.emit("end", info, index); 38 | }; 39 | actor.apply(self, args); 40 | } catch (err) { 41 | evt.emit("error", err); 42 | } 43 | }; 44 | } 45 | 46 | function wrap(fnc) { 47 | return function() { 48 | return fnc; 49 | }; 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spreadsheets", 3 | "description": "A node.js client for Google Spreadsheets API", 4 | "version": "0.1.0", 5 | "author": "EastCloud ", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://EastCloud@github.com/EastCloud/node-spreadsheets.git" 9 | }, 10 | "main": "./lib/spreadsheets/Spreadsheets", 11 | "engines": { 12 | "node": ">=0.4.0" 13 | }, 14 | "licenses": [{ 15 | "type": "Apache License, Version 2.0", 16 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 17 | }] 18 | } -------------------------------------------------------------------------------- /xml/batchcellentry.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /xml/batchfeed.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /xml/cellentry.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /xml/docentry.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /xml/worksheetentry.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | <gs:rowCount/> 5 | <gs:colCount/> 6 | </entry> 7 | 8 | --------------------------------------------------------------------------------