From 10805cd4b3807a2cb122e23db30cdb09ea7fd7d4 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Sun, 9 Oct 2022 21:42:23 -0400 Subject: [PATCH] gsheet --- .spelling | 1 + docz/docs/03-demos/25-gsheet.md | 465 ++++++++++++++------------------ 2 files changed, 205 insertions(+), 261 deletions(-) diff --git a/.spelling b/.spelling index a328cad..2a16e2d 100644 --- a/.spelling +++ b/.spelling @@ -153,6 +153,7 @@ Fastify FileReader GBK GatsbyJS +GitLab Goja HTML HTML5 diff --git a/docz/docs/03-demos/25-gsheet.md b/docz/docs/03-demos/25-gsheet.md index 9754356..0a746eb 100644 --- a/docz/docs/03-demos/25-gsheet.md +++ b/docz/docs/03-demos/25-gsheet.md @@ -8,8 +8,12 @@ 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. +:::caution + +Google Sheets deprecates APIs quickly and there is no guarantee that the +referenced API version will be available in the future. + +::: ## Initial Configuration @@ -46,97 +50,10 @@ module.exports = async(ID) => { -## Exporting Document Data to a File +## Export Document Data 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 file system: - -```js -/* write to SheetJS.xlsb */ -XLSX.writeFile(wb, "SheetJS.xlsb"); -``` - -### How to Run Export Example +not natively support the XLSB format. SheetJS fills the gap.
How to run locally (click to show) @@ -225,151 +142,56 @@ const ID = "";
-## Updating a Document from a Local File +### Convert a Single Sheet -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 file system. The following line reads -`sheetjs.xlsx` from the current directory: +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. ```js -const XLSX = require("xlsx"); -const wb = XLSX.readFile("sheetjs.xlsx"); +async function wb_append_sheet(sheet, name, wb) { + /* 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)); + + /* generate a SheetJS Worksheet */ + const ws = XLSX.utils.aoa_to_sheet(aoa); + + /* add to workbook */ + XLSX.utils.book_append_sheet(wb, ws, name); +} ``` -### Connecting to the Document +### Convert a Workbook -This uses the `common.js` helper from above: - -
Code (click to show) +`doc.sheetsByIndex` is an array of worksheets in the Google Sheet Document. By +looping across the sheets, the entire workbook can be written: ```js -/* Connect to Google Sheet */ -const ID = ""; -const doc = await require("./common")(ID); -``` +async function doc_to_wb(doc) { + /* Create a new workbook object */ + const wb = XLSX.utils.book_new(); -
+ /* 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; -### 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(); + /* Add sheet to workbook */ + await add_sheet_to_wb(sheet, name, wb); } - /* clear first worksheet */ - old_sheets[0].clear(); + + return wb; } ``` -
+## Update Document Data -### 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 +The goal is to refresh a Google Sheet based on a local file.
How to run locally (click to show) @@ -485,15 +307,132 @@ const ID = "";
-## Using the Raw File Exports +### Clear the Document + +Google Sheets does not allow users to delete every worksheet. This function +deletes every worksheet after the first, then clears the first worksheet: + +```js +/* clear google sheets doc */ +async function doc_clear(doc) { + /* 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 Sheet + +There are two steps: "update worksheet name" and "update worksheet data": + +#### 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 +async function doc_update_first_sheet_name(doc, wb) { + /* get first worksheet name */ + const wsname = wb.SheetNames[0]; + + /* get first gsheet */ + const sheet = doc.sheetsByIndex[0]; + + /* update worksheet name */ + await sheet.updateProperties({title: wsname}); +} +``` + +#### Update Sheet 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 +async function doc_update_first_sheet_data(doc, wb) { + /* get first worksheet */ + const ws = wb.Sheets[wb.SheetNames[0]]; + /* generate array of arrays from the first worksheet */ + const aoa = XLSX.utils.sheet_to_json(ws, {header: 1}); + + /* get first gsheet */ + const sheet = doc.sheetsByIndex[0]; + /* 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)); +} +``` + +### Append Remaining Worksheets + +Each name in the SheetJS Workbook `SheetNames` array maps to a worksheet. The +list of names not including the first sheet is `wb.SheetNames.slice(1)`. + +There are two steps for each sheet: "create new sheet" and "load data". + +Due to JavaScript `async` idiosyncrasies, a plain `for` loop must be used: + +```js +async function doc_append_remaining_sheets(doc, wb) { + const names = wb.SheetNames.slice(1); + + /* loop across names */ + for(let i = 0; i < names.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]; + + /* create new google sheet */ + const sheet = await doc_add_new_sheet(doc, name); + /* load sheet with data */ + await sheet_load_from_ws(sheet, ws); + } +} +``` + +#### Add a New Worksheet + +`doc.addSheet` accepts a properties object that includes the worksheet name: + +```js +async function doc_add_new_sheet(doc, name) { + return 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 + +```js +async function sheet_load_from_ws(sheet, ws) { + /* 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)); +} +``` + +## 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. +This example prints the worksheet names and CSV exports of each sheet. @@ -501,27 +440,30 @@ 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); +(async() => { + /* Connect to Google Sheet */ + const ID = ""; + const doc = await require("./common")(ID); -/* Get XLSX export */ -const buf = await doc.downloadAsXLSX(); + /* Get file export */ + // highlight-next-line + const buf = await doc.downloadAsXLSX(); -/* Parse with SheetJS */ -const wb = XLSX.read(buf); + /* 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); + /* 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]; + /* 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)); -}); + /* Print a CSV export of the worksheet */ + console.log(XLSX.utils.sheet_to_csv(sheet)); + }); +})(); ``` @@ -531,30 +473,31 @@ wb.SheetNames.forEach(name => { ```js const XLSX = require("xlsx"); -/* Connect to Google Sheet */ -const ID = ""; -const doc = await require("./common")(ID); +(async() => { + /* Connect to Google Sheet */ + const ID = ""; + const doc = await require("./common")(ID); -/* Get XLSX export */ -const buf = await doc.downloadAsODS(); + /* Get file export */ + // highlight-next-line + const buf = await doc.downloadAsODS(); -/* Parse with SheetJS */ -const wb = XLSX.read(buf); + /* 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); + /* 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]; + /* 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)); -}); + /* Print a CSV export of the worksheet */ + console.log(XLSX.utils.sheet_to_csv(sheet)); + }); +})(); ``` - - -
\ No newline at end of file + \ No newline at end of file