This commit is contained in:
SheetJS 2022-10-09 21:42:23 -04:00
parent da2b8885a4
commit 10805cd4b3
2 changed files with 205 additions and 261 deletions

@ -153,6 +153,7 @@ Fastify
FileReader
GBK
GatsbyJS
GitLab
Goja
HTML
HTML5

@ -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) => {
</details>
## 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:
<details><summary><b>Code</b> (click to show)</summary>
```js
/* Connect to Google Sheet */
const ID = "<google sheet id>";
const doc = await require("./common")(ID);
```
</details>
### 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.
<details><summary><b>Code</b> (click to show)</summary>
```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;
/* ... */
}
```
</details>
### 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.
<details><summary><b>Code</b> (click to show)</summary>
```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));
```
</details>
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.
<details><summary><b>How to run locally</b> (click to show)</summary>
@ -225,151 +142,56 @@ const ID = "<google sheet ID>";
</details>
## 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:
<details><summary><b>Code</b> (click to show)</summary>
`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 = "<google sheet id>";
const doc = await require("./common")(ID);
```
async function doc_to_wb(doc) {
/* Create a new workbook object */
const wb = XLSX.utils.book_new();
</details>
/* 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.
<details><summary><b>Code</b> (click to show)</summary>
```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;
}
```
</details>
## 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.
<details><summary><b>How to run locally</b> (click to show)</summary>
@ -485,15 +307,132 @@ const ID = "<google sheet ID>";
</details>
## 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.
<details><summary><b>Sample Code</b> (click to show)</summary>
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.
<Tabs>
<TabItem value="xlsx" label="XLSX">
@ -501,27 +440,30 @@ worksheet names and CSV exports of each sheet.
```js
const XLSX = require("xlsx");
/* Connect to Google Sheet */
const ID = "<google sheet id>";
const doc = await require("./common")(ID);
(async() => {
/* Connect to Google Sheet */
const ID = "<google sheet 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));
});
})();
```
</TabItem>
@ -531,30 +473,31 @@ wb.SheetNames.forEach(name => {
```js
const XLSX = require("xlsx");
/* Connect to Google Sheet */
const ID = "<google sheet id>";
const doc = await require("./common")(ID);
(async() => {
/* Connect to Google Sheet */
const ID = "<google sheet 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));
});
})();
```
</TabItem>
</Tabs>
</details>
</Tabs>