diff --git a/README.md b/README.md index 50d7bda..1f72139 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,12 @@ do not work in MDX v2. Instead, string literals and concatenation must be used: {"Foo" + current + ""} ``` +**Tables** + +MDX inconsistently requires different indentation levels for `TD` / `TH`, `TR`, +`THEAD` / `TBODY` / `TFOOT`, and `TABLE` tags. Unconventional indentation is +intentional. + diff --git a/docz/docs/03-demos/02-frontend/08-dojo.md b/docz/docs/03-demos/02-frontend/08-dojo.md index 434f7b1..66da80d 100644 --- a/docz/docs/03-demos/02-frontend/08-dojo.md +++ b/docz/docs/03-demos/02-frontend/08-dojo.md @@ -72,7 +72,11 @@ The script https://docs.sheetjs.com/dojo/dojo.js was fetched from the official :::note Tested Deployments -The demos were last tested on 2023-12-04. +This demo was tested in the following environments: + +| Platform | Date | +|:-------------|:-----------| +| Chromium 125 | 2024-06-08 | Demos exclusively using Dojo Core were tested using Dojo Toolkit `1.17.3`. diff --git a/docz/docs/03-demos/02-frontend/19-bundler/20-parcel.md b/docz/docs/03-demos/02-frontend/19-bundler/20-parcel.md index 62529dd..f764f89 100644 --- a/docz/docs/03-demos/02-frontend/19-bundler/20-parcel.md +++ b/docz/docs/03-demos/02-frontend/19-bundler/20-parcel.md @@ -34,8 +34,8 @@ This demo was tested in the following environments: | Version | Date | |:---------|:-----------| -| `2.10.3` | 2023-12-04 | -| `1.12.3` | 2023-12-04 | +| `2.12.0` | 2024-06-08 | +| `1.12.4` | 2024-06-08 | ::: @@ -135,7 +135,7 @@ main `index.html` page should load the `index.js` script:

SheetJS export demo

- + ``` @@ -208,7 +208,7 @@ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} 3) Run the ParcelJS development server: ```bash -npx -y parcel@2.10.3 index.html +npx -y parcel index.html ``` The process will print a URL: @@ -231,7 +231,7 @@ a web browser and click the "Click to Export!" button to generate a file. 6) Build the production site: ```bash -npx -y parcel@2.10.0 build index.html +npx -y parcel build index.html ``` The production site will be stored in the `dist` folder diff --git a/docz/docs/03-demos/30-cloud/21-gsheet.md b/docz/docs/03-demos/30-cloud/21-gsheet.md index eb2800e..08c16e3 100644 --- a/docz/docs/03-demos/30-cloud/21-gsheet.md +++ b/docz/docs/03-demos/30-cloud/21-gsheet.md @@ -33,8 +33,8 @@ libraries and written to a Google Sheets Document - "Exporting data": Data in Google Sheets will be pulled into arrays of objects. A workbook will be assembled and exported to Excel Binary workbooks (XLSB). -- "Exporting files": SheetJS libraries will read XLSX and ODS files exported by -Google Sheets and generate CSV rows from every worksheet. +- "Exporting files": SheetJS libraries will read XLSX files exported by Google +Sheets and generate CSV rows from every worksheet. :::danger pass @@ -53,10 +53,8 @@ referenced APIs will be available in the future. ## Integration Details -This demo uses the following NodeJS modules: - -- `google-auth-library`[^1] simplifies authentication with Google APIs -- `node-google-spreadsheet`[^2] interacts with Google Sheets v4 API +This demo uses the Sheets v4 and Drive v3 APIs through the official `googleapis` +connector module. :::info Initial Setup @@ -65,6 +63,31 @@ for an account. The [Complete Example](#complete-example) covers the process. ::: +### Document Duality + +Each Google Sheets document is identified with a unique ID. This ID can be found +from the Google Sheets edit URL. + +The edit URL starts with `https://docs.google.com/spreadsheets/d/` and includes +`/edit`. The ID is the string of characters between the slashes. For example: + +``` +https://docs.google.com/spreadsheets/d/a_long_string_of_characters/edit#gid=0 + |^^^^^^^^^^^^^^^^^^^^^^^^^^^|--- ID +``` + +The same ID is used in Google Drive operations. + +The following operations are covered in this demo: + +| Operation | API | +|:------------------------------|:-------| +| Create Google Sheets Document | Sheets | +| Add and Remove worksheets | Sheets | +| Modify data in worksheets | Sheets | +| Share Sheets with other users | Drive | +| Generate raw file exports | Drive | + ### Authentication It is strongly recommended to use a service account for Google API operations. @@ -75,52 +98,44 @@ The generated JSON key file includes `client_email` and `private_key` fields. These fields can be used in JWT authentication: ```js title="JWT Authentication using a JSON key file" -import { JWT } from 'google-auth-library' -import { GoogleSpreadsheet } from 'google-spreadsheet'; +import { google } from "googleapis"; // adjust the path to the actual key file. // highlight-next-line import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; -const jwt = new JWT({ +/* connect to google services */ +const jwt = new google.auth.JWT({ email: creds.client_email, key: creds.private_key, scopes: [ - 'https://www.googleapis.com/auth/spreadsheets', - 'https://www.googleapis.com/auth/drive.file', + 'https://www.googleapis.com/auth/spreadsheets', // Google Sheets + 'https://www.googleapis.com/auth/drive.file', // Google Drive ] }); ``` -### Connecting to Documents +### Connecting to Services -To connect to existing documents, the document ID must be specified. This ID can -be found from the edit URL. +The `google` named export includes special methods to connect to various APIs. -The edit URL starts with `https://docs.google.com/spreadsheets/d/` and includes -`/edit`. The ID is the string of characters between the slashes. For example: +#### Google Sheets -``` -https://docs.google.com/spreadsheets/d/a_long_string_of_characters/edit#gid=0 - |^^^^^^^^^^^^^^^^^^^^^^^^^^^|--- ID +```js +const sheets = google.sheets({ version: "v4", auth: jwt }); ``` -The `GoogleSpreadsheet` constructor accepts a document ID and auth object: +`google.sheets` takes an options argument that includes API version number and +authentication details. -```js title="Connect to a document" -const doc = new GoogleSpreadsheet('DOCUMENT_ID', jwt); -await doc.loadInfo(); +#### Google Drive + +```js +const drive = google.drive({ version: "v3", auth: jwt }); ``` -### Creating New Documents - -The `createNewSpreadsheetDocument` makes a request to create a new document. It -is strongly recommended to create a blank sheet. - -```js title="Create a new document with blank sheet" -const doc = await GoogleSpreadsheet.createNewSpreadsheetDocument(jwt, { title: 'Document Title' }); -const newSheet = await doc.addSheet({ title: 'Sheet Title' }); -``` +`google.drive` takes an options argument that includes API version number and +authentication details. ### Array of Arrays @@ -130,8 +145,8 @@ row arrays, and each row array includes data. SheetJS provides methods for working with Arrays of Arrays: -- `aoa_to_sheet`[^3] creates SheetJS worksheet objects from arrays of arrays -- `sheet_to_json`[^4] can generate arrays of arrays from SheetJS worksheets +- `aoa_to_sheet`[^1] creates SheetJS worksheet objects from arrays of arrays +- `sheet_to_json`[^2] can generate arrays of arrays from SheetJS worksheets ## Export Document Data @@ -140,205 +155,274 @@ not natively support the XLSB format. SheetJS fills the gap. ### Convert a Single Sheet -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. +`sheets.spreadsheets.values.get` returns data from an existing Google Sheet. The +method expects a range. Passing the sheet name as the title will pull all rows. + +If successful, the response object will have a `data` property. It will be an +object with a `values` property. The values will be represented as an Array of +Arrays of values. This array of arrays can be converted to a SheetJS sheet: ```js -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(); +async function gsheet_ws_to_sheetjs_ws(id, sheet_name) { + /* get values */ + const res = await sheets.spreadsheets.values.get({ + spreadsheetId: id, + range: `'${sheet_name}'` + }); + const values = res.data.values; - /* 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); + /* create SheetJS worksheet */ + const ws = XLSX.utils.aoa_to_sheet(values); + return ws; } ``` ### Convert a Workbook -`doc.sheetsByIndex` is an array of worksheets in the Google Sheet Document. By -looping across the sheets, the entire workbook can be written: +`sheets.spreadsheets.get` returns metadata about the Google Sheets document. In +the result object, the `data` property is an object which has a `sheets` +property. The value of the `sheets` property is an array of sheet objects. + +The SheetJS `book_new` [^3] method creates blank SheetJS workbook objects. The +`book_append_sheet` [^4] method adds SheetJS worksheet objects to the workbook. + +By looping across the sheets, the entire workbook can be converted: ```js -async function doc_to_wb(doc) { +async function gsheet_doc_to_sheetjs_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; + /* Get metadata */ + const wsheet = await sheets.spreadsheets.get({spreadsheetId: id}); - /* Add sheet to workbook */ - await add_sheet_to_wb(sheet, name, wb); + /* Loop across the Document sheets */ + for(let sheet of wsheet.data.sheets) { + /* Get the worksheet name */ + const name = sheet.properties.title; + + /* Convert Google Docs sheet to SheetJS worksheet */ + const ws = await gsheet_ws_to_sheetjs_ws(id, name); + + /* Append worksheet to workbook */ + XLSX.utils.book_append_sheet(wb, ws, name); } return wb; } ``` +This method returns a SheetJS workbook object that can be exported with the +`writeFile` and `write` methods.[^5] + ## Update Document Data The goal is to import data from a NUMBERS file to Google Sheets. Google Sheets does not natively support the NUMBERS format. SheetJS fills the gap. -### Clear the Document +### Create New Document -Google Sheets does not allow users to delete every worksheet. This function -deletes every worksheet after the first, then clears the first worksheet: +`sheets.spreadsheets.create` creates a new Google Sheets document. It can accept +a document title. It will generate a new workbook with a blank "Sheet1" sheet. +The response includes the document ID for use in subsequent operations: -```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); +```js title="Create a new document with blank sheet" +const res = await sheets.spreadsheets.create({ + requestBody: { + properties: { + /* Document Title */ + title: "SheetJS Test" + } } +}); +const id = res.data.spreadsheetId; +``` + +:::caution pass + +When using a service worker, the main account does not have access to the new +document by default. The document has to be shared with the main account using +the Drive API: + +```js title="Sharing the generated sheet with the main account" +await drive.permissions.create({ + fileId: id, // this ID was returned in the response to the create request + fields: "id", + requestBody: { + type: "user", + role: "writer", + emailAddress: "YOUR_ADDRESS@gmail.com" // main address + } +}); +``` + +::: + +### Delete Non-Initial Sheets + +Google Sheets does not allow users to delete every worksheet. + +The recommended approach involves deleting every worksheet after the first. + +The delete operation requires a unique identifier for a sheet within the Google +Sheets document. These IDs are found in the `sheets.spreadsheets.get` response. + +The following snippet performs one bulk operation using `batchUpdate`: + +```js title="Deleting non-initial sheets" +/* get existing sheets */ +const wsheet = await sheets.spreadsheets.get({spreadsheetId: id}); + +/* remove all sheets after the first */ +if(wsheet.data.sheets.length > 1) await sheets.spreadsheets.batchUpdate({ + spreadsheetId: id, + requestBody: { requests: wsheet.data.sheets.slice(1).map(s => ({ + deleteSheet: { + sheetId: s.properties.sheetId + } + }))} +}); +``` + +### Rename First Sheet + +The first sheet must be renamed so that the append operations do not collide +with the legacy name. Since most SheetJS-supported file formats and most +spreadsheet applications limit worksheet name lengths to 32 characters, it is +safe to set a name that exceeds 33 characters. + +The `updateSheetProperties` update method can rename individual sheets: + +```js title="Rename legacy first sheet" +/* rename first worksheet to avoid collisions */ +await sheets.spreadsheets.batchUpdate({ + spreadsheetId: id, + requestBody: { requests: [{ + updateSheetProperties: { + fields: "title", + properties: { + sheetId: wsheet.data.sheets[0].properties.sheetId, + // the new title is 34 characters, to be exact + title: "thistitleisatleast33characterslong" + } + } + }]} +}); +``` + +### Append Worksheets + +:::note pass + +The [`read` and `readFile` methods](/docs/api/parse-options) generate SheetJS +workbook objects from existing worksheet files. + +::: + +Starting from a SheetJS workbook, the `SheetNames` property[^6] is an array of +worksheet names and the `Sheets` property is an object that maps sheet names to +worksheet objects. + +Looping over the worksheet names, there are two steps to appending a sheet: + +1) "Append a blank worksheet": The `addSheet` request, submitted through the +`sheets.spreadsheets.batchUpdate` method, accepts a new title and creates a new +worksheet. The new worksheet will be added at the end. + +2) "Write data to the new sheet": The SheetJS `sheet_to_json` method with the +option `header: 1`[^7] will generate an array of arrays of data. This structure +is compatible with the `sheets.spreadsheets.values.update` operation. + +The following snippet pushes all worksheets from a SheetJS workbook into a +Google Sheets document: + +```js title="Push data from a SheetJS workbook to a Google Sheets Document" +/* add sheets from file */ +for(let name of wb.SheetNames) { + /* (1) Create a new Google Sheets sheet */ + await sheets.spreadsheets.batchUpdate({ + spreadsheetId: id, + requestBody: { requests: [ + /* add new sheet */ + { addSheet: { properties: { title: name } } }, + ] } + }); + + /* (2) Push data */ + const aoa = XLSX.utils.sheet_to_json(wb.Sheets[name], {header:1}); + await sheets.spreadsheets.values.update({ + spreadsheetId: id, + range: `'${name}'!A1`, + valueInputOption: "USER_ENTERED", + resource: { values: aoa } + }); } ``` -#### Add a New Worksheet +### Delete Initial Sheet -`doc.addSheet` accepts a properties object that includes the worksheet name: +After adding new worksheets, the final step involves removing the initial sheet. -```js -async function doc_add_new_sheet(doc, name) { - return await doc.addSheet({title: name}); -} -``` +The initial sheet ID can be pulled from the worksheet metadata fetched when the +[non-initial sheets were removed](#delete-non-initial-sheets): -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)); -} +```js title="Deleting the Initial sheet" +/* remove first sheet */ +await sheets.spreadsheets.batchUpdate({ + spreadsheetId: id, + requestBody: { requests: [ + /* remove old first sheet */ + { deleteSheet: { sheetId: wsheet.data.sheets[0].properties.sheetId } } + ] } +}); ``` ## Raw File Exports -In the web interface, Google Sheets can export documents to `XLSX` or `ODS`. The -NodeJS library includes similar methods to perform the download[^5]: +In the web interface, Google Sheets can export documents to `XLSX` or `ODS`. -| Format | Google Sheets Description | Method | -|:-------|:--------------------------|:-----------------| -| XLSX | Microsoft Excel (.xlsx) | `downloadAsXLSX` | -| ODS | OpenDocument (.ods) | `downloadAsODS` | +Raw file exports are exposed through the `files.export` method in the Drive API: -The functions resolve to `Buffer` data. The `Buffer` objects can be parsed using -the SheetJS `read`[^6] method: +```js title="Export XLSX workbook" +const drive = google.drive({ version: "v3", auth: jwt }); + +/* Request XLSX export */ +const file = await drive.files.export({ + /* XLSX MIME type */ + mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + fileId: id +}); +``` + +The response object has a `data` field whose value will be a `Blob` object. Data +can be pulled into an `ArrayBuffer` and passed to the SheetJS `read`[^8] method: ```js -/* download XLSX */ -const ab = await doc.downloadAsXLSX(); +/* Obtain ArrayBuffer */ +const ab = await file.data.arrayBuffer(); -/* parse */ +/* Parse */ const wb = XLSX.read(buf); ``` -At this point `wb` is a SheetJS workbook object[^7]. +At this point `wb` is a SheetJS workbook object[^9]. ## Complete Example :::note Tested Deployments -This demo was last tested on 2023 September 17 using `google-auth-library` for -authentication (`v8.9.0`) and `google-spreadsheet` for API access (`v4.1.0`). +This demo was last tested on 2024 June 08 using `googleapis` version `140.0.0`. +The demo uses Sheets v4 and Drive v3 APIs. + +::: + +:::caution pass + +**The Google Cloud web interface changes frequently!** + +The screenshots and detailed descriptions may be out of date. Please report any +issues [to the docs repo](https://git.sheetjs.com/sheetjs/docs.sheetjs.com/) or +reach out to [the SheetJS Discord server](https://sheetjs.com/chat). ::: @@ -352,8 +436,11 @@ A valid phone number (for SMS verification) may be required. ::: -1) Open https://console.cloud.google.com in a web browser. Review the Google -Cloud Platform Terms of Service. +1) Open https://console.cloud.google.com in a web browser. + +If this is the first time accessing Google Cloud resources, a terms of service +modal will be displayed. Review the Google Cloud Platform Terms of Service by +clicking the "Google Cloud Platform Terms of Service" link. :::danger pass @@ -361,17 +448,34 @@ You must agree to the Google Cloud Platform Terms of Service to use the APIs. ::: +Check the box under "Terms of Service" and click "AGREE AND CONTINUE". + ### Project Setup -2) Create a new Project. +:::info pass -If the account does not have an existing project, click "CREATE PROJECT" +The goal of this section is to create a new project. -If the account has an existing project, click the project selector (`:·` icon) -and click "NEW PROJECT" in the modal. +::: + +2) Open the Project Selector. + +In the top bar, between the "Google Cloud" logo and the search bar, there will +be a selection box. Click the `▼` icon to show the modal. + +![Project Selector](pathname:///gsheet/selector.png) + +If the selection box is missing, expand the browser window. + +3) Click "NEW PROJECT" in the top right corner of the modal. + +4) In the New Project screen, enter "SheetJS Test" in the Project name textbox +and select "No organization" in the Location box. Click "CREATE". + +A notification will confirm that the project was created: + +![Project notification](pathname:///gsheet/notification.png) -3) In the New Project screen, enter "SheetJS Test" in the Project name textbox -and select "No organization" in the Location box. Click "CREATE" ### API Setup @@ -381,23 +485,34 @@ The goal of this section is to enable Google Sheets API and Google Drive API. ::: -4) Click the Project Selector (`:·` icon) and select "SheetJS Test" +5) Open the Project Selector (`▼` icon) and select "SheetJS Test" -5) In the left sidebar, click "Enabled APIs and services". +6) In the search bar, type "Enabled" and select "Enabled APIs & services". This +item will be in the "PRODUCTS & PAGES" part of the search results. -6) Near the top of the page, click "+ ENABLE APIS AND SERVICES". +#### Enable Google Sheets API -7) In the search bar near the middle of the page, type "Sheets" and look for -"Google Sheets API". Click the card +7) Near the top of the page, click "+ ENABLE APIS AND SERVICES". -8) In the Product Details screen, click the blue "ENABLE" button. +8) In the search bar near the middle of the page (not the search bar at the top), +type "Sheets" and press Enter. -9) Click the left arrow (`<-`) next to API/Service details. +In the results page, look for "Google Sheets API". Click the card -10) In the search bar near the middle of the page, type "Drive" and look for -"Google Drive API". Click the card. +9) In the Product Details screen, click the blue "ENABLE" button. -11) In the Product Details screen, click the blue "ENABLE" button. +10) Click the left arrow (`<-`) next to "API/Service details". + +#### Enable Google Drive API + +11) Near the top of the page, click "+ ENABLE APIS AND SERVICES". + +12) In the search bar near the middle of the page (not the search bar at the top), +type "Drive" and press Enter. + +In the results page, look for "Google Drive API". Click the card + +13) In the Product Details screen, click the blue "ENABLE" button. ### Service Account Setup @@ -407,19 +522,21 @@ The goal of this section is to create a service account and generate a JSON key. ::: +14) Go to https://console.cloud.google.com or click the "Google Cloud" image in +the top bar. + #### Create Service Account -12) Go to https://console.cloud.google.com. +15) Click the Project Selector (`:·` icon) and select "SheetJS Test". -13) Click the Project Selector (`:·` icon) and select "SheetJS Test". +16) In the search bar, type "Credentials" and select the "Credentials" item with +subtitle "APIs & Services". This item will be in the "PRODUCTS & PAGES" group: -14) Click "Dashboard". +![Credentials](pathname:///gsheet/creds.png) -15) In the left sidebar, hover over "APIs and Services" and select "Credentials" +17) Click "+ CREATE CREDENTIALS". In the dropdown, select "Service Account" -16) Click "+ CREATE CREDENTIALS". In the dropdown, select "Service Account" - -17) Enter "SheetJService" for Service account name. Click "CREATE AND CONTINUE" +18) Enter "SheetJService" for Service account name. Click "CREATE AND CONTINUE" :::note pass @@ -427,23 +544,22 @@ The Service account ID is generated automatically. ::: -18) In Step 2 "Grant this service account access to project", click CONTINUE +19) In Step 2 "Grant this service account access to project", click CONTINUE -19) In Step 3 click "DONE". You will be taken back to the credentials screen +20) In Step 3 click "DONE". You will be taken back to the credentials screen #### Create JSON Key -20) Look for "SheetJService" in the "Service Accounts" table and click the email -address in the row - -21) Click the email address of the account in the "Service Accounts" table. +21) Look for "SheetJService" in the "Service Accounts" table and click the email +address in the row. 22) Click "KEYS" in the horizontal bar near the top of the page. 23) Click "ADD KEY" and select "Create new key" in the dropdown. -24) In the popup, select the "JSON" radio button and click "CREATE". The page -will download a JSON file. +24) In the popup, select the "JSON" radio button and click "CREATE". + +The page will download a JSON file. If prompted, allow the download. 25) Click "CLOSE" @@ -469,35 +585,27 @@ npm init -y 28) Install dependencies: {`\ -npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz google-spreadsheet google-auth-library`} +npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz googleapis`} -29) Save the following script to `init.mjs`: +29) Download [`init.mjs`](pathname:///gsheet/init.mjs): -```js title="init.mjs" -import { JWT } from 'google-auth-library' -import { GoogleSpreadsheet } from 'google-spreadsheet'; +```bash +curl -LO https://docs.sheetjs.com/gsheet/init.mjs +``` +Edit the marked lines near the top of the file: + +```js title="init.mjs (edit highlighted lines)" +/* Change this import statement to point to the credentials JSON file */ // highlight-next-line import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; -const jwt = new JWT({ - email: creds.client_email, - key: creds.private_key, - scopes: [ - 'https://www.googleapis.com/auth/spreadsheets', - 'https://www.googleapis.com/auth/drive.file', - ] -}); - -const doc = await GoogleSpreadsheet.createNewSpreadsheetDocument(jwt, { title: 'test from NodeJS' }); -const newSheet = await doc.addSheet({ title: 'SheetJSTest' }); +/* Change this to the primary account address, NOT THE SERVICE ACCOUNT */ // highlight-next-line -await doc.share('YOUR_ADDRESS@gmail.com'); +const acct = "YOUR_ADDRESS@gmail.com"; ``` -Edit the highlighted lines as follows: - - `'./sheetjs-test-726272627262.json'` should be replaced with the name of the JSON file in step 27. The `./` prefix is required! @@ -510,10 +618,63 @@ address from step 0. node init.mjs ``` -31) Sign into Google Sheets. A shared document "test from NodeJS" should be +The script will print three lines: + +``` +Created Google Workbook a-long-string-of-characters +Created Google Worksheets "SheetJS1" and "SheetJS2" +Shared a-long-string-of-characters with YOUR_ACCOUNT@gmail.com +``` + +The long string of characters after "Created Google Workbook" is the ID. Take +note of this ID. + +31) Sign into Google Sheets. A shared document "SheetJS Test" should be displayed in the table. It will be owned by the service account. -32) Open the shared document from step 31. +32) Open the shared document from step 31 and confirm that the document has two +worksheets named "SheetJS1" and "SheetJS2". + +Confirm the worksheet data matches the following screenshots: + + + + + + + + + + + +
SheetDataScreenshot
SheetJS1 + +```js +[ + [ "Sheet", "JS" ], + [ 72, 62 ] +] +``` + + + +![SheetJS1 data](pathname:///gsheet/SheetJS1.png) + +
SheetJS2 + +```js +[ + [ "Area Code", "Part 1", "Part 2" ], + [ 201, 867, 5309 ], + [ 281, 330, 8004 ] +] +``` + + + +![SheetJS2 data](pathname:///gsheet/SheetJS2.png) + +
33) Copy the URL and extract the document ID. @@ -527,6 +688,8 @@ https://docs.google.com/spreadsheets/d/a_long_string_of_characters/edit#gid=0 The ID is a long string of letters and numbers and underscore characters (`_`) just before the `/edit` part of the URL. +Confirm that this ID matches the ID printed in step 30. + ### Load Data from NUMBERS :::info pass @@ -542,83 +705,26 @@ NUMBERS file. curl -LO https://docs.sheetjs.com/pres.numbers ``` -35) Save the following script to `load.mjs`: +35) Download [`load.mjs`](pathname:///gsheet/load.mjs): -```js title="load.mjs" -import { JWT } from 'google-auth-library' -import { GoogleSpreadsheet } from 'google-spreadsheet'; - -import { set_fs, readFile, utils } from 'xlsx'; -import * as fs from 'fs'; -set_fs(fs); - -// highlight-next-line -import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; - -const jwt = new JWT({ - email: creds.client_email, - key: creds.private_key, - scopes: [ - 'https://www.googleapis.com/auth/spreadsheets', - 'https://www.googleapis.com/auth/drive.file', - ] -}); - -// highlight-next-line -const doc = new GoogleSpreadsheet('DOCUMENT_ID', jwt); -await doc.loadInfo(); - -const wb = readFile("pres.numbers"); - -/* 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 = 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 = utils.sheet_to_json(ws, {header: 1}); - await sheet.setHeaderRow(aoa[0]) - await sheet.addRows(aoa.slice(1)); - } -} +```bash +curl -LO https://docs.sheetjs.com/gsheet/load.mjs ``` -Edit the highlighted lines as follows: +Edit the marked lines near the top of the file: + +```js title="load.mjs (edit highlighted lines)" +/* Change this import statement to point to the credentials JSON file */ +import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; + +/* Change this to the spreadsheet ID */ +const id = "SOME-SPREADSHEETJS-ID"; +``` - `'./sheetjs-test-726272627262.json'` should be replaced with the name of the JSON file in step 27. The `./` prefix is required! -- `'DOCUMENT_ID'` should be replaced with the Document ID from step 33. +- `"SOME-SPREADSHEETJS-ID"` should be replaced with the Document ID from step 33. 36) Run the script: @@ -626,7 +732,7 @@ JSON file in step 27. The `./` prefix is required! node load.mjs ``` -37) Sign into Google Sheets and open the "test from NodeJS" shared document. It +37) Sign into Google Sheets and open the "SheetJS Test" shared document. It should show a list of Presidents, matching the contents of the test file. ### Export Data to XLSB @@ -637,61 +743,26 @@ The goal of this section is to export the raw data from Google Sheets to XLSB. ::: -38) Save the following script to `dump.mjs`: +38) Download [`dump.mjs`](pathname:///gsheet/dump.mjs): -```js title="dump.mjs" -import { JWT } from 'google-auth-library' -import { GoogleSpreadsheet } from 'google-spreadsheet'; - -import { set_fs, writeFile, utils } from 'xlsx'; -import * as fs from 'fs'; -set_fs(fs); - -// highlight-next-line -import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; - -const jwt = new JWT({ - email: creds.client_email, - key: creds.private_key, - scopes: [ - 'https://www.googleapis.com/auth/spreadsheets', - 'https://www.googleapis.com/auth/drive.file', - ] -}); - -// highlight-next-line -const doc = new GoogleSpreadsheet('DOCUMENT_ID', jwt); -await doc.loadInfo(); - -const wb = utils.book_new(); - -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 = utils.aoa_to_sheet(aoa); - - /* add to workbook */ - utils.book_append_sheet(wb, ws, name); -} - -/* write to SheetJS.xlsb */ -writeFile(wb, "SheetJS.xlsb"); +```bash +curl -LO https://docs.sheetjs.com/gsheet/dump.mjs ``` -Edit the highlighted lines as follows: +Edit the marked lines near the top of the file: + +```js title="dump.mjs (edit highlighted lines)" +/* Change this import statement to point to the credentials JSON file */ +import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; + +/* Change this to the spreadsheet ID */ +const id = "SOME-SPREADSHEETJS-ID"; +``` - `'./sheetjs-test-726272627262.json'` should be replaced with the name of the JSON file in step 27. The `./` prefix is required! -- `'DOCUMENT_ID'` should be replaced with the Document ID from step 33. +- `"SOME-SPREADSHEETJS-ID"` should be replaced with the Document ID from step 33. 39) Run the script: @@ -699,11 +770,8 @@ JSON file in step 27. The `./` prefix is required! node dump.mjs ``` -The script should create a file `SheetJS.xlsb` in the project folder. This file -can be opened in Excel - -40) Sign into Google Sheets and open the "test from NodeJS" shared document. It -should show a list of Presidents, matching the contents of the test file. +The script should create a file `SheetJSExport.xlsb` in the project folder. This +file can be opened in Excel. ### Export Raw Files @@ -714,74 +782,63 @@ CSV files for each worksheet. ::: -41) Sign into Google Sheets and open the "test from NodeJS" shared document. +40) Sign into Google Sheets and open the "SheetJS Test" shared document. -42) Click the Plus (`+`) icon in the lower left corner to create a new worksheet. +41) Click the Plus (`+`) icon in the lower left corner to create a new worksheet. -43) In the new worksheet, set cell A1 to the formula `=SEQUENCE(3,5)`. This will +42) In the new worksheet, set cell A1 to the formula `=SEQUENCE(3,5)`. This will assign a grid of values -44) Save the following script to `raw.mjs`: +43) Download [`raw.mjs`](pathname:///gsheet/raw.mjs): -```js title="raw.mjs" -import { JWT } from 'google-auth-library' -import { GoogleSpreadsheet } from 'google-spreadsheet'; - -import { read, utils } from 'xlsx'; - -// highlight-next-line -import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; - -const jwt = new JWT({ - email: creds.client_email, - key: creds.private_key, - scopes: [ - 'https://www.googleapis.com/auth/spreadsheets', - 'https://www.googleapis.com/auth/drive.file', - ] -}); - -// highlight-next-line -const doc = new GoogleSpreadsheet('DOCUMENT_ID', jwt); -await doc.loadInfo(); - -const buf = await doc.downloadAsXLSX(); - -/* Parse with SheetJS */ -const wb = 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(utils.sheet_to_csv(sheet)); -}); +```bash +curl -LO https://docs.sheetjs.com/gsheet/raw.mjs ``` -Edit the highlighted lines as follows: +Edit the marked lines near the top of the file: + +```js title="raw.mjs (edit highlighted lines)" +/* Change this import statement to point to the credentials JSON file */ +import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; + +/* Change this to the spreadsheet ID */ +const id = "SOME-SPREADSHEETJS-ID"; +``` - `'./sheetjs-test-726272627262.json'` should be replaced with the name of the JSON file in step 27. The `./` prefix is required! -- `'DOCUMENT_ID'` should be replaced with the Document ID from step 33. +- `"SOME-SPREADSHEETJS-ID"` should be replaced with the Document ID from step 33. -45) Run the script: +44) Run the script: ```bash node raw.mjs ``` -The script will display the sheet names and CSV rows from both worksheets. +The script will display the sheet names and CSV rows from both worksheets: -[^1]: The package name is [`google-auth-library`](https://www.npmjs.com/package/google-auth-library) -[^2]: The project name is [`node-google-spreadsheet`](https://github.com/theoephraim/node-google-spreadsheet) but the module name is `google-spreadsheet`. -[^3]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) -[^4]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) -[^5]: See ["Exporting Data"](https://theoephraim.github.io/node-google-spreadsheet/#/guides/exports) in the `node-google-spreadsheet` documentation -[^6]: See [`read` in "Reading Files"](/docs/api/parse-options) -[^7]: See ["Workbook Object"](/docs/csf/book) for a description of the workbook object or ["API Reference"](/docs/api) for various methods to work with workbook and sheet objects. +``` +#### Sheet1 +Name,Index +Bill Clinton,42 +GeorgeW Bush,43 +Barack Obama,44 +Donald Trump,45 +Joseph Biden,46 + +#### Sheet14 +1,2,3,4,5 +6,7,8,9,10 +11,12,13,14,15 +``` + +[^1]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) +[^2]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) +[^3]: See [`book_new` in "Utilities"](/docs/api/utilities/wb) +[^4]: See [`book_append_sheet` in "Utilities"](/docs/api/utilities/wb) +[^5]: See [`writeFile` in "Writing Files"](/docs/api/write-options) +[^6]: See ["Workbook Object"](/docs/csf/book) for more details. +[^7]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) +[^8]: See [`read` in "Reading Files"](/docs/api/parse-options) +[^9]: See ["Workbook Object"](/docs/csf/book) for a description of the workbook object or ["API Reference"](/docs/api) for various methods to work with workbook and sheet objects. diff --git a/docz/docs/03-demos/32-extensions/12-maple.md b/docz/docs/03-demos/32-extensions/12-maple.md index b097332..be7f0d6 100644 --- a/docz/docs/03-demos/32-extensions/12-maple.md +++ b/docz/docs/03-demos/32-extensions/12-maple.md @@ -32,7 +32,12 @@ flowchart LR :::note Tested Deployments -This demo was last tested by SheetJS users on 2024-04-25 in Maple 2024. +This demo was tested by SheetJS users in the following deployments: + +| Architecture | Version | Date | +|:-------------|:--------|:-----------| +| `darwin-x64` | 2024 | 2024-04-25 | +| `win10-x64` | 2024 | 2024-04-25 | ::: diff --git a/docz/static/gsheet/SheetJS1.png b/docz/static/gsheet/SheetJS1.png new file mode 100644 index 0000000..01c3266 Binary files /dev/null and b/docz/static/gsheet/SheetJS1.png differ diff --git a/docz/static/gsheet/SheetJS2.png b/docz/static/gsheet/SheetJS2.png new file mode 100644 index 0000000..d0553f9 Binary files /dev/null and b/docz/static/gsheet/SheetJS2.png differ diff --git a/docz/static/gsheet/creds.png b/docz/static/gsheet/creds.png new file mode 100644 index 0000000..f04edee Binary files /dev/null and b/docz/static/gsheet/creds.png differ diff --git a/docz/static/gsheet/dump.mjs b/docz/static/gsheet/dump.mjs new file mode 100644 index 0000000..f850f3b --- /dev/null +++ b/docz/static/gsheet/dump.mjs @@ -0,0 +1,39 @@ +import { google } from "googleapis"; + +import { set_fs, writeFile, utils } from 'xlsx'; +import * as fs from 'fs'; +set_fs(fs); + +/* Change this import statement to point to the credentials JSON file */ +import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; + +/* Change this to the spreadsheet ID */ +const id = "SOME-SPREADSHEETJS-ID"; + +/* connect to google services */ +const jwt = new google.auth.JWT({ + email: creds.client_email, + key: creds.private_key, + scopes: [ + 'https://www.googleapis.com/auth/spreadsheets', + 'https://www.googleapis.com/auth/drive.file', + ] +}); + +const sheets = google.sheets({ version: "v4", auth: jwt }); + +/* get existing sheets */ +const wsheet = await sheets.spreadsheets.get({spreadsheetId: id}); + +/* create a workbook */ +const wb = utils.book_new(); + +for(let sheet of wsheet.data.sheets) { + const name = sheet.properties.title; + const res = await sheets.spreadsheets.values.get({ + spreadsheetId: id, + range: `'${name}'` + }); + utils.book_append_sheet(wb, utils.aoa_to_sheet(res.data.values), name); +} +writeFile(wb, "SheetJSExport.xlsb"); diff --git a/docz/static/gsheet/init.mjs b/docz/static/gsheet/init.mjs new file mode 100644 index 0000000..13f6ae5 --- /dev/null +++ b/docz/static/gsheet/init.mjs @@ -0,0 +1,87 @@ +import { google } from "googleapis"; + +/* Change this import statement to point to the credentials JSON file */ +import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; + +/* Change this to the primary account address, NOT THE SERVICE ACCOUNT */ +const acct = "YOUR_ADDRESS@gmail.com"; + +/* connect to google services */ +const jwt = new google.auth.JWT({ + email: creds.client_email, + key: creds.private_key, + scopes: [ + 'https://www.googleapis.com/auth/spreadsheets', + 'https://www.googleapis.com/auth/drive.file', + ] +}); + +const sheets = google.sheets({ version: "v4", auth: jwt }); +const drive = google.drive({version: "v3", auth: jwt }); + +/* create new google workbook */ +const [id, sheet0id] = await (async() => { + const res = await sheets.spreadsheets.create({ + requestBody: { + properties: { + title: "SheetJS Test" + } + } + }); + const id = res.data.spreadsheetId; + const sheet0id = res.data.sheets[0].properties.sheetId; + return [id, sheet0id]; +})(); +console.log(`Created Google Workbook ${id}`); + +/* create new google worksheet and delete initial sheet */ +const [sheet1id, sheet2id] = await (async() => { + const res = await sheets.spreadsheets.batchUpdate({ + spreadsheetId: id, + requestBody: { requests: [ + /* add SheetJS1 */ + { addSheet: { properties: { title: "SheetJS1" } } }, + /* add SheetJS2 */ + { addSheet: { properties: { title: "SheetJS2" } } }, + /* remove default sheet */ + { deleteSheet: { sheetId: sheet0id } }, + ] } + }); + console.log(`Created Google Worksheets "SheetJS1" and "SheetJS2"`); + return res.data.replies.slice(0,2).map(r => r.addSheet.properties.sheetId); +})(); + +await sheets.spreadsheets.values.update({ + spreadsheetId: id, + range: "SheetJS1!A1", + valueInputOption: "USER_ENTERED", + resource: { values: [ + ["Sheet", "JS"], + [72, 62] + ]} +}); + +await sheets.spreadsheets.values.update({ + spreadsheetId: id, + range: "SheetJS2!A1", + valueInputOption: "USER_ENTERED", + resource: { values: [ + ["Area Code", "Part 1", "Part 2"], + [201, 867, 5309], + [281, 330, 8004], + ]} +}); + +/* Share new Document with the primary account */ +try { + await drive.permissions.create({ + fileId: id, + fields: "id", + requestBody: { + type: "user", + role: "writer", + emailAddress: acct + } + }); + console.log(`Shared ${id} with ${acct}`); +} catch(e) { console.log(e); } diff --git a/docz/static/gsheet/load.mjs b/docz/static/gsheet/load.mjs new file mode 100644 index 0000000..2d5c14d --- /dev/null +++ b/docz/static/gsheet/load.mjs @@ -0,0 +1,87 @@ +import { google } from "googleapis"; + +import { set_fs, readFile, utils } from 'xlsx'; +import * as fs from 'fs'; +set_fs(fs); + +/* Change this import statement to point to the credentials JSON file */ +import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; + +/* Change this to the spreadsheet ID */ +const id = "SOME-SPREADSHEETJS-ID"; + +/* connect to google services */ +const jwt = new google.auth.JWT({ + email: creds.client_email, + key: creds.private_key, + scopes: [ + 'https://www.googleapis.com/auth/spreadsheets', + 'https://www.googleapis.com/auth/drive.file', + ] +}); + +const sheets = google.sheets({ version: "v4", auth: jwt }); + +/* get existing sheets */ +const wsheet = await sheets.spreadsheets.get({spreadsheetId: id}); + +/* remove all sheets after the first */ +if(wsheet.data.sheets.length > 1) await sheets.spreadsheets.batchUpdate({ + spreadsheetId: id, + requestBody: { requests: + wsheet.data.sheets.slice(1).map(s => ({ + deleteSheet: { + sheetId: s.properties.sheetId + } + })) + } +}); + +/* read file */ +const wb = readFile("pres.numbers"); + +/* rename first worksheet to avoid collisions */ +const props0 = wsheet.data.sheets[0].properties; +if(wb.SheetNames.map(n => n.toLowerCase()).includes(props0.title.toLowerCase())) { + await sheets.spreadsheets.batchUpdate({ + spreadsheetId: id, + requestBody: { requests: [{ + updateSheetProperties: { + fields: "title", + properties: { + sheetId: props0.sheetId, + title: "thistitleisatleast33characterslong" + } + } + }]} + }); + console.log(`renamed "${props0.title}" to "thistitleisatleast33characterslong"`); +} + +/* add sheets from file */ +for(let name of wb.SheetNames) { + const aoa = utils.sheet_to_json(wb.Sheets[name], {header:1}); + await sheets.spreadsheets.batchUpdate({ + spreadsheetId: id, + requestBody: { requests: [ + /* add new sheet */ + { addSheet: { properties: { title: name } } }, + ] } + }); + await sheets.spreadsheets.values.update({ + spreadsheetId: id, + range: `'${name}'!A1`, + valueInputOption: "USER_ENTERED", + resource: { values: aoa } + }); + console.log(`Created Google Worksheet "${name}"`); +} + +/* remove first sheet */ +const res = await sheets.spreadsheets.batchUpdate({ + spreadsheetId: id, + requestBody: { requests: [ + /* remove old first sheet */ + { deleteSheet: { sheetId: wsheet.data.sheets[0].properties.sheetId } } + ] } +}); diff --git a/docz/static/gsheet/notification.png b/docz/static/gsheet/notification.png new file mode 100644 index 0000000..24b37b6 Binary files /dev/null and b/docz/static/gsheet/notification.png differ diff --git a/docz/static/gsheet/raw.mjs b/docz/static/gsheet/raw.mjs new file mode 100644 index 0000000..440d565 --- /dev/null +++ b/docz/static/gsheet/raw.mjs @@ -0,0 +1,38 @@ +import { google } from "googleapis"; + +import { read, utils } from 'xlsx'; + +/* Change this import statement to point to the credentials JSON file */ +import creds from './sheetjs-test-726272627262.json' assert { type: "json" }; + +/* Change this to the spreadsheet ID */ +const id = "SOME-SPREADSHEETJS-ID"; + +/* connect to google services */ +const jwt = new google.auth.JWT({ + email: creds.client_email, + key: creds.private_key, + scopes: [ + 'https://www.googleapis.com/auth/spreadsheets', + 'https://www.googleapis.com/auth/drive.file', + ] +}); + +const drive = google.drive({ version: "v3", auth: jwt }); + +/* get XLSX export */ +const file = await drive.files.export({ + mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + fileId: id +}); +const ab = await file.data.arrayBuffer(); + +/* parse with SheetJS */ +const wb = read(ab); + +/* print CSV data from each worksheet */ +wb.SheetNames.forEach(n => { + console.log(`#### ${n}`); + console.log(utils.sheet_to_csv(wb.Sheets[n])); + console.log(""); +}) \ No newline at end of file diff --git a/docz/static/gsheet/selector.png b/docz/static/gsheet/selector.png new file mode 100644 index 0000000..90c35a5 Binary files /dev/null and b/docz/static/gsheet/selector.png differ diff --git a/tests/bundler-parcel.sh b/tests/bundler-parcel.sh new file mode 100755 index 0000000..947022a --- /dev/null +++ b/tests/bundler-parcel.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# https://docs.sheetjs.com/docs/demos/frontend/bundler/parcel +cd /tmp +rm -rf sheetjs-parceljs + +mkdir sheetjs-parceljs +cd sheetjs-parceljs +npm init -y + +npm i --save https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz + +cat >index.html < +

SheetJS export demo

+ + + +EOF + +cat >index.js < { + /* fetch JSON data and parse */ + const url = "https://docs.sheetjs.com/executive.json"; + const raw_data = await (await fetch(url)).json(); + + /* filter for the Presidents */ + const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez")); + + /* sort by first presidential term */ + prez.forEach(row => row.start = row.terms.find(term => term.type === "prez").start); + prez.sort((l,r) => l.start.localeCompare(r.start)); + + /* flatten objects */ + const rows = prez.map(row => ({ + name: row.name.first + " " + row.name.last, + birthday: row.bio.birthday + })); + + /* generate worksheet and workbook */ + const worksheet = utils.json_to_sheet(rows); + const workbook = utils.book_new(); + utils.book_append_sheet(workbook, worksheet, "Dates"); + + /* fix headers */ + utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" }); + + /* calculate column width */ + const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10); + worksheet["!cols"] = [ { wch: max_width } ]; + + /* create an XLSX file and try to save to Presidents.xlsx */ + writeFileXLSX(workbook, "Presidents.xlsx"); +}; +EOF + +cat >test.js < { + await new Promise((res,rej) => setTimeout(res, 1000)); + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + page.on("console", msg => console.log("PAGE LOG:", msg.text())); + await page.setViewport({width: 1920, height: 1080}); + const client = await page.target().createCDPSession(); + await client.send('Browser.setDownloadBehavior', { + behavior: 'allow', + downloadPath: require("path").resolve('./') + }); + page.on('request', req => console.log(req.url())); + await page.goto('http://localhost:7262/'); + await new Promise((res,rej) => setTimeout(res, 1000)); + await page.click("#xport"); + await new Promise((res,rej) => setTimeout(res, 1000)); + await browser.close(); + process.exit(); +}); +EOF + +node -e 'var pjson = JSON.parse(fs.readFileSync("./package.json")); console.log(pjson); delete pjson.main; fs.writeFileSync("package.json", JSON.stringify(pjson))' + +for n in 1.12.4 2.12.0; do + npx -y parcel build index.html + node test.js + npx -y xlsx-cli Presidents.xlsx | head -n 3 + rm -f Presidents.xlsx +done