From bee5d8a86404d234cb2f9cbcd1872a943e537199 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Fri, 20 May 2022 04:48:21 -0400 Subject: [PATCH] gsheet --- .../04-getting-started/03-demos/01-gsheet.md | 562 ++++++++++++++++++ .../03-demos/_category_.json | 4 + .../{03-demos.md => 03-demos/index.md} | 4 +- docz/docs/04-getting-started/_category_.json | 1 + docz/docs/06-solutions/_category_.json | 1 + 5 files changed, 570 insertions(+), 2 deletions(-) create mode 100644 docz/docs/04-getting-started/03-demos/01-gsheet.md create mode 100644 docz/docs/04-getting-started/03-demos/_category_.json rename docz/docs/04-getting-started/{03-demos.md => 03-demos/index.md} (96%) diff --git a/docz/docs/04-getting-started/03-demos/01-gsheet.md b/docz/docs/04-getting-started/03-demos/01-gsheet.md new file mode 100644 index 0000000..563a466 --- /dev/null +++ b/docz/docs/04-getting-started/03-demos/01-gsheet.md @@ -0,0 +1,562 @@ +--- +sidebar_position: 2 +--- + +# Google Sheets + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This demo uses [`node-google-spreadsheet`](https://theoephraim.github.io/node-google-spreadsheet) +to interact with Google Sheets v4 API. + +Code that does not directly relate to SheetJS APIs are tucked away. Click on +the "click to show" blocks to see the code snippets. + +## Initial Configuration + +Install the dependencies: + +```bash +npm i https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz google-spreadsheet@3.3.0 +``` + +The library README has a [guide](https://theoephraim.github.io/node-google-spreadsheet/#/getting-started/authentication) +for configuring a service worker with write access to the document. Following +the service worker guide, the JSON key should be saved to `key.json`. + +The following helper function attempts to authenticate and access the specified +sheet by ID. The code should be copied and saved to `common.js`: + +
Code (click to show) + +```js title=common.js +const fs = require("fs"); +const { GoogleSpreadsheet } = require('google-spreadsheet'); + +module.exports = async(ID) => { + /* get credentials */ + const creds = JSON.parse(fs.readFileSync('key.json')); + + /* initialize sheet and authenticate */ + const doc = new GoogleSpreadsheet(ID); + await doc.useServiceAccountAuth(creds); + await doc.loadInfo(); + return doc; +} +``` + +
+ +## Exporting Document Data to a File + +The goal is to create an XLSB export from a Google Sheet. Google Sheets does +not natively support the XLSB format. SheetJS fills the gap. [The last subsection](#how-to-run-export-example) includes detailed instructions for running locally. + +### Connecting to the Document + +This uses the `common.js` helper from above: + +
Code (click to show) + +```js +/* Connect to Google Sheet */ +const ID = ""; +const doc = await require("./common")(ID); +``` + +
+ +### Creating a New Workbook + +`XLSX.utils.book_new()` creates an empty workbook with no worksheets: + +```js +/* create a blank workbook */ +const wb = XLSX.utils.book_new(); +``` + +### Looping across the Document + + +`doc.sheetsByIndex` is an array of worksheets in the Google Sheet Document. + +
Code (click to show) + +```js +/* Loop across the Document sheets */ +for(let i = 0; i < doc.sheetsByIndex.length; ++i) { + const sheet = doc.sheetsByIndex[i]; + /* Get the worksheet name */ + const name = sheet.title; + /* ... */ +} +``` + +
+ +### Convert a Google Sheets sheet to a SheetJS Worksheet + +The idea is to extract the raw data from the Google Sheet headers and combine +with the raw data rows to produce a large array of arrays. + +
Code (click to show) + +```js + /* get the header and data rows */ + await sheet.loadHeaderRow(); + const header = sheet.headerValues; + const rows = await sheet.getRows(); + + /* construct the array of arrays */ + const aoa = [header].concat(rows.map(r => r._rawData)); +``` + +
+ +This can be converted to a SheetJS worksheet using `XLSX.utils.aoa_to_sheet`: + + +```js + /* generate a SheetJS Worksheet */ + const ws = XLSX.utils.aoa_to_sheet(aoa); +``` + +`XLSX.utils.book_append_sheet` will add the worksheet to the workbook: + +```js + /* add to workbook */ + XLSX.utils.book_append_sheet(wb, ws, name); +``` + +### Generating an XLSB file + +`XLSX.writeFile` will write a file in the filesystem: + +```js +/* write to SheetJS.xlsb */ +XLSX.writeFile(wb, "SheetJS.xlsb"); +``` + +### How to Run Export Example + +
How to run locally (click to show) + +0) Follow the [Authentication and Service Account](https://theoephraim.github.io/node-google-spreadsheet/#/getting-started/authentication) +instructions. At the end, you should have + +- Created a project and enabled the Sheets API +- Created a service account with a JSON key + +Move the generated JSON key to `key.json` in your project folder. + +1) Create a new Google Sheet and share with the generated service account. It +should be granted the "Editor" role + +2) Install the dependencies: + +``` +npm i https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz google-spreadsheet@3.3.0 +``` + +2) Save the following snippet to `common.js`: + +```js title=common.js +const fs = require("fs"); +const { GoogleSpreadsheet } = require('google-spreadsheet'); + +module.exports = async(ID) => { + /* get credentials */ + const creds = JSON.parse(fs.readFileSync('key.json')); + + /* initialize sheet and authenticate */ + const doc = new GoogleSpreadsheet(ID); + await doc.useServiceAccountAuth(creds); + await doc.loadInfo(); + return doc; +} +``` + +3) Save the following snippet to `pull.js`: + +```js title=pull.js +const XLSX = require("xlsx"); + +/* create a blank workbook */ +const wb = XLSX.utils.book_new(); + +const init = require("./common"); +const ID = ""; + +(async() => { + + const doc = await init(ID); + + for(let i = 0; i < doc.sheetsByIndex.length; ++i) { + const sheet = doc.sheetsByIndex[i]; + const name = sheet.title; + + /* get the header and data rows */ + await sheet.loadHeaderRow(); + const header = sheet.headerValues; + const rows = await sheet.getRows(); + const aoa = [header].concat(rows.map(r => r._rawData)); + + /* generate a SheetJS Worksheet */ + const ws = XLSX.utils.aoa_to_sheet(aoa); + + /* add to workbook */ + XLSX.utils.book_append_sheet(wb, ws, name); + } + + /* write to SheetJS.xlsb */ + XLSX.writeFile(wb, "SheetJS.xlsb"); + +})(); +``` + +4) Replace `` with the ID of the actual document. + +5) Run `node pull.js` once. It will create `SheetJS.xlsb`. + +6) Open `SheetJS.xlsb` and confirm the contents are the same as Google Sheets. + +7) Change some cells in the Google Sheets Document. + +8) Run `node pull.js` again and reopen `SheetJS.xlsb` to confirm value changes. + +
+ +## Updating a Document from a Local File + +The goal is to refresh a Google Sheet based on a local file. The problem can +be broken down into a few steps. [The last subsection](#how-to-run-update-example) +includes detailed instructions for running locally. + +### Reading the Workbook File + +`XLSX.readFile` can read files from the filesystem. The following line reads +`sheetjs.xlsx` from the current directory: + +```js +const XLSX = require("xlsx"); +const wb = XLSX.readFile("sheetjs.xlsx"); +``` + +### Connecting to the Document + +This uses the `common.js` helper from above: + +
Code (click to show) + +```js +/* Connect to Google Sheet */ +const ID = ""; +const doc = await require("./common")(ID); +``` + +
+ +### Clearing the Document + +Google Sheets does not allow users to delete every worksheet. The snippet +deletes every worksheet after the first, then clears the first worksheet. + +
Code (click to show) + +```js +/* clear workbook */ +{ + /* delete all sheets after the first sheet */ + const old_sheets = doc.sheetsByIndex; + for(let i = 1; i < old_sheets.length; ++i) { + await old_sheets[i].delete(); + } + /* clear first worksheet */ + old_sheets[0].clear(); +} +``` + +
+ +### Update First Worksheet + +In the SheetJS workbook object, worksheet names are stored in the `SheetNames` +property. The first worksheet name is `wb.SheetNames[0]`: + +```js +const name = wb.SheetNames[0]; +``` + +The `Sheets` property is an object whose keys are sheet names and whose values +are worksheet objects. + +```js +const ws = wb.Sheets[name]; +``` + +In the Google Sheet, `doc.sheetsByIndex[0]` is a reference to the first sheet: + +```js +const sheet = doc.sheetsByIndex[0]; +``` + +#### Update Sheet Name + +The worksheet name is assigned by using the `updateProperties` method. The +desired sheet name is the name of the first worksheet from the file. + +```js +/* update worksheet name */ +await sheet.updateProperties({title: name}); +``` + +#### Update Worksheet Data + +`sheet.addRows` reads an Array of Arrays of values. `XLSX.utils.sheet_to_json` +can generate this exact shape with the option `header: 1`. Unfortunately +Google Sheets requires at least one "Header Row". This can be implemented by +converting the entire worksheet to an Array of Arrays and setting the header +row to the first row of the result: + +```js +/* generate array of arrays from the first worksheet */ +const aoa = XLSX.utils.sheet_to_json(ws, {header: 1}); + +/* set document header row to first row of the AOA */ +await sheet.setHeaderRow(aoa[0]); + +/* add the remaining rows */ +await sheet.addRows(aoa.slice(1)); +``` + +### Add the Other Worksheets + +Each name in the SheetJS Workbook `SheetNames` array maps to a worksheet. The +loop over the remaining worksheet names looks like + +```js +for(let i = 1; i < wb.SheetNames.length; ++i) { + /* wb.SheetNames[i] is the sheet name */ + const name = wb.SheetNames[i]; + /* wb.Sheets[name] is the worksheet object */ + const ws = wb.Sheets[name]; + /* ... */ +} +``` + +#### Appending a Worksheet to the Document + +`doc.addSheet` accepts a properties object that includes the worksheet name: + +```js + const sheet = await doc.addSheet({title: name}); +``` + +This creates a new worksheet, sets the tab name, and returns a reference to the +created worksheet. + +#### Update Worksheet Data + +This is identical to the first worksheet code: + +```js + /* generate array of arrays from the first worksheet */ + const aoa = XLSX.utils.sheet_to_json(ws, {header: 1}); + + /* set document header row to first row of the AOA */ + await sheet.setHeaderRow(aoa[0]); + + /* add the remaining rows */ + await sheet.addRows(aoa.slice(1)); +``` + +### How to Run Update Example + +
How to run locally (click to show) + +0) Follow the [Authentication and Service Account](https://theoephraim.github.io/node-google-spreadsheet/#/getting-started/authentication) +instructions. At the end, you should have + +- Created a project and enabled the Sheets API +- Created a service account with a JSON key + +Move the generated JSON key to `key.json` in your project folder. + +1) Create a new Google Sheet and share with the generated service account. It +should be granted the "Editor" role + +2) Install the dependencies: + +``` +npm i https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz google-spreadsheet@3.3.0 +``` + +2) Save the following snippet to `common.js`: + +```js title=common.js +const fs = require("fs"); +const { GoogleSpreadsheet } = require('google-spreadsheet'); + +module.exports = async(ID) => { + /* get credentials */ + const creds = JSON.parse(fs.readFileSync('key.json')); + + /* initialize sheet and authenticate */ + const doc = new GoogleSpreadsheet(ID); + await doc.useServiceAccountAuth(creds); + await doc.loadInfo(); + return doc; +} +``` + +3) Save the following snippet to `push.js`: + +```js title=push.js +const XLSX = require("xlsx"); +const fs = require("fs"); +/* create dummy worksheet if `sheetjs.xlsx` does not exist */ +if(!fs.existsSync("sheetjs.xlsx")) { + const wb = XLSX.utils.book_new(); + const ws1 = XLSX.utils.aoa_to_sheet([["a","b","c"],[1,2,3]]); XLSX.utils.book_append_sheet(wb, ws1, "Sheet1"); + const ws2 = XLSX.utils.aoa_to_sheet([["a","b","c"],[4,5,6]]); XLSX.utils.book_append_sheet(wb, ws2, "Sheet2"); + XLSX.writeFile(wb, "sheetjs.xlsx"); +} +/* read and parse sheetjs.xlsx */ +const wb = XLSX.readFile("sheetjs.xlsx"); + +const init = require("./common"); +const ID = ""; + +(async() => { + + const doc = await init(ID); + + /* clear workbook */ + { + /* delete all sheets after the first sheet */ + const old_sheets = doc.sheetsByIndex; + for(let i = 1; i < old_sheets.length; ++i) { + await old_sheets[i].delete(); + } + /* clear first worksheet */ + old_sheets[0].clear(); + } + + /* write worksheets */ + { + const name = wb.SheetNames[0]; + const ws = wb.Sheets[name]; + /* first worksheet already exists */ + const sheet = doc.sheetsByIndex[0]; + + /* update worksheet name */ + await sheet.updateProperties({title: name}); + + /* generate array of arrays from the first worksheet */ + const aoa = XLSX.utils.sheet_to_json(ws, {header: 1}); + + /* set document header row to first row of the AOA */ + await sheet.setHeaderRow(aoa[0]) + + /* add the remaining rows */ + await sheet.addRows(aoa.slice(1)); + + /* the other worksheets must be created manually */ + for(let i = 1; i < wb.SheetNames.length; ++i) { + const name = wb.SheetNames[i]; + const ws = wb.Sheets[name]; + + const sheet = await doc.addSheet({title: name}); + const aoa = XLSX.utils.sheet_to_json(ws, {header: 1}); + await sheet.setHeaderRow(aoa[0]) + await sheet.addRows(aoa.slice(1)); + } + } + +})(); +``` + +4) Replace `` with the ID of the actual document. + +5) Run `node push.js` once. It will create `sheetjs.xlsx` and update the sheet. + +6) Edit `sheetjs.xlsx` with some new data + +7) Run `node push.js` again and watch the Google Sheet update! + +
+ +## Using the Raw File Exports + +`node-google-spreadsheet` can download the XLSX or ODS export of the document. +The functions return NodeJS `Buffer` data that can be parsed using SheetJS. + +
Sample Code (click to show) + +SheetJS can read data from XLSX files and ODS files. This example prints the +worksheet names and CSV exports of each sheet. + + + + +```js +const XLSX = require("xlsx"); + +/* Connect to Google Sheet */ +const ID = ""; +const doc = await require("./common")(ID); + +/* Get XLSX export */ +const buf = await doc.downloadAsXLSX(); + +/* Parse with SheetJS */ +const wb = XLSX.read(buf); + +/* Loop over the worksheet names */ +wb.SheetNames.forEach(name => { + /* Print the name to the console */ + console.log(name); + + /* Get the corresponding worksheet object */ + const sheet = wb.Sheets[name]; + + /* Print a CSV export of the worksheet */ + console.log(XLSX.utils.sheet_to_csv(sheet)); +}); +``` + + + + + +```js +const XLSX = require("xlsx"); + +/* Connect to Google Sheet */ +const ID = ""; +const doc = await require("./common")(ID); + +/* Get XLSX export */ +const buf = await doc.downloadAsODS(); + +/* Parse with SheetJS */ +const wb = XLSX.read(buf); + +/* Loop over the worksheet names */ +wb.SheetNames.forEach(name => { + /* Print the name to the console */ + console.log(name); + + /* Get the corresponding worksheet object */ + const sheet = wb.Sheets[name]; + + /* Print a CSV export of the worksheet */ + console.log(XLSX.utils.sheet_to_csv(sheet)); +}); +``` + + + + +
\ No newline at end of file diff --git a/docz/docs/04-getting-started/03-demos/_category_.json b/docz/docs/04-getting-started/03-demos/_category_.json new file mode 100644 index 0000000..29564b6 --- /dev/null +++ b/docz/docs/04-getting-started/03-demos/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Demos", + "position": 3 +} \ No newline at end of file diff --git a/docz/docs/04-getting-started/03-demos.md b/docz/docs/04-getting-started/03-demos/index.md similarity index 96% rename from docz/docs/04-getting-started/03-demos.md rename to docz/docs/04-getting-started/03-demos/index.md index 7e544c9..888f974 100644 --- a/docz/docs/04-getting-started/03-demos.md +++ b/docz/docs/04-getting-started/03-demos/index.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 1 hide_table_of_contents: true --- @@ -32,7 +32,7 @@ The demo projects include small runnable examples and short explainers. - [`Electron`](https://github.com/SheetJS/SheetJS/tree/master/demos/electron/) - [`NW.js`](https://github.com/SheetJS/SheetJS/tree/master/demos/nwjs/) - [`Chrome / Chromium Extension`](https://github.com/SheetJS/SheetJS/tree/master/demos/chrome/) -- [`Google Sheet export`](https://github.com/SheetJS/SheetJS/tree/master/demos/google-sheet/) +- [`Google Sheets API`](./gsheet) - [`ExtendScript for Adobe Apps`](https://github.com/SheetJS/SheetJS/tree/master/demos/extendscript/) - [`Headless Browsers`](https://github.com/SheetJS/SheetJS/tree/master/demos/headless/) - [`Other JavaScript Engines`](https://github.com/SheetJS/SheetJS/tree/master/demos/altjs/) diff --git a/docz/docs/04-getting-started/_category_.json b/docz/docs/04-getting-started/_category_.json index 4bd4025..8103e63 100644 --- a/docz/docs/04-getting-started/_category_.json +++ b/docz/docs/04-getting-started/_category_.json @@ -1,4 +1,5 @@ { "label": "Getting Started", + "collapsed": false, "position": 4 } diff --git a/docz/docs/06-solutions/_category_.json b/docz/docs/06-solutions/_category_.json index aa99f52..9cbca82 100644 --- a/docz/docs/06-solutions/_category_.json +++ b/docz/docs/06-solutions/_category_.json @@ -1,4 +1,5 @@ { "label": "Common Use Cases", + "collapsed": false, "position": 6 }