This commit is contained in:
SheetJS 2023-04-20 20:53:38 -04:00
parent 2e1eed486a
commit 4286792300
7 changed files with 105 additions and 138 deletions

@ -17,40 +17,94 @@ It offers solutions for custom functions as well as task panes.
Excel currently does not provide support for working with Apple Numbers files
and some legacy file formats. SheetJS fills the gap.
This demo creates a new custom function to add much-needed functionality:
This demo creates a new custom function `SHEETJS.EXTERN()` which tries to fetch
an external spreadsheet and insert the data into the worksheet.
- `SHEETJS.EXTERN()` tries to fetch an external spreadsheet and insert the data
into the worksheet.
![`SHEETJS.EXTERN` output](pathname:///xlapi/xlfetch.png)
This demo focuses on the basic mechanics. Advanced topics like Excel Custom
Function parameters are covered in the official Office JavaScript API docs.
SheetJS worksheet metadata and other properties are covered in this doc site.
## Creating a new Add-in
:::note
<details><summary><b>Initial Platform Setup</b> (click to show)</summary>
This demo was last tested on 2023 April 20 against Excel 365 (version 2303)
The tool for generating Office Add-ins uses NodeJS and various libraries.
Install [NodeJS LTS](https://nodejs.org/en/download/). After installing NodeJS,
install dependencies in a new PowerShell window:
:::
:::caution Excel Bugs
There was a binary data bug affecting `fetch` and Excel. It was resolved in
version 2303. It is strongly encouraged to upgrade to the latest version of
Excel 365 before running the demo.
:::
## Integration Details
The [NodeJS module](/docs/getting-started/installation/nodejs) can be imported
in an Excel Custom Functions project.
The [`sheet_to_json`](/docs/api/utilities#json) helper function can generate
arrays of arrays of values based on the worksheet data. Excel custom functions
transparently treat these as Dynamic Arrays.
This example fetches a file, parses the data, and extracts the first worksheet:
```js title="src\functions\functions.js"
var XLSX = require("xlsx");
/**
* Download file and write data
* @customfunction
* @param {string} url URL to fetch and parse
* @returns {any[][]} Worksheet data
*/
async function extern(url) {
try {
/* Fetch Data */
const res = await fetch(url);
/* Get Data */
const ab = await res.arrayBuffer();
/* Parse Data */
var wb = XLSX.read(ab);
/* get and return data */
var ws = wb.Sheets[wb.SheetNames[0]]; // get first worksheet
var aoa = XLSX.utils.sheet_to_json(ws, { header: 1 }); // array of arrays
return aoa;
} catch(e) { return [[e.message || e]]; } // pass error back to Excel
}
```
## Complete Demo
0) Clear the functions cache. For the tested version of Excel:
- Open File Explorer
- Select the address bar and enter `%LOCALAPPDATA%\Microsoft\Office\16.0\Wef`
- Delete `CustomFunctions` and empty Recycle Bin.
1) Install [NodeJS LTS](https://nodejs.org/en/download/).
2) Install dependencies in a new PowerShell window:
```powershell
npm install -g yo bower generator-office
```
</details>
### Creating a new Add-in
<details><summary><b>Creating a new Project</b> (click to show)</summary>
3) Run `yo office` from the command line. It will ask a few questions:
Run `yo office` from the command line. It will ask a few questions.
- "Choose a project type": "Excel Custom Functions Add-in project"
- "Choose a project type": "Excel Custom Functions using a Shared Runtime"
- "Choose a script type": "JavaScript",
- "What do you want to name your add-in?": "SheetJSImport"
The script will create a new project and helpfully print the next steps:
4) Start the dev process:
```powershell
cd SheetJSImport
@ -58,25 +112,33 @@ npm run build
npm start
```
</details>
Running `npm start` will open up a terminal window and a new Excel window with
the loaded add-in. Keep the terminal window open (it can be minimized). When
you make a change, close both the Excel window and the terminal window before
running `npm start` again.
the loaded add-in. Keep the terminal window open.
## Integrating the SheetJS Library
5) In `manifest.xml` , search for `Functions.NameSpace` . There will be an XML
element with name `bt:String`. Change the `DefaultValue` attribute to `SHEETJS`:
The library can be installed like any other NodeJS module:
```xml title="manifest.xml"
<bt:ShortStrings>
// highlight-next-line
<bt:String id="Functions.Namespace" DefaultValue="SHEETJS"/>
<bt:String id="GetStarted.Title" DefaultValue="Get started with your sample add-in!" />
```
6) Close the Excel window and the terminal window, then run `npm start` again.
### Integrating the SheetJS Library
7) Install the SheetJS library in the project
```bash
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
```
To be sure the library is loaded, remove all of the existing functions from
`src\functions\functions.js`. The new contents should be
8) Replace `src\functions\functions.js` with the following:
```js src\functions\functions.js
```js title="src\functions\functions.js"
var XLSX = require("xlsx");
/**
@ -89,132 +151,37 @@ function version() {
}
```
The `manifest.xml` should also be updated to reflect the function namespace.
Search for `Functions.NameSpace` in the file and replace the line with:
```xml
<bt:String id="Functions.Namespace" DefaultValue="SHEETJS"/>
```
After making the change, save the files. Close the terminal window and the
9) After making the change, save the files. Close the terminal window and the
Excel window (do not save the Excel file). Re-run `npm start`.
In the new Excel window, enter the formula `=SHEETJS.VERSION()` in cell `E1`.
You should see something similar to the following screenshot:
10) In the new Excel window, enter the formula `=SHEETJS.VERSION()` in cell
`E1`. You should see something similar to the following screenshot:
![`SHEETJS.VERSION` output](pathname:///files/xlcfversion.png)
![`SHEETJS.VERSION` output](pathname:///xlapi/xlvers.png)
This indicates that the SheetJS library has been loaded.
## Dynamic Arrays and SheetJS Array of Arrays
### Fetching Files from the Internet
The [`sheet_to_json`](/docs/api/utilities#json) helper function can generate
arrays of arrays of values based on the worksheet data. Excel custom functions
transparently treat these as Dynamic Arrays.
## Fetching Files from the Internet
For the next step, we will try to fetch data from an external resource.
<https://sheetjs.com/pres.numbers> is an Apple Numbers file. Excel does not
understand Numbers files and it will not open them.
<details><summary><b>Excel bug related to `fetch`</b> (click to show)</summary>
`fetch` is available to custom functions:
```js
async function extern() {
try {
const url = "https://sheetjs.com/pres.numbers"; // URL to download
const res = await fetch(url); // fetch data
const ab = await res.arrayBuffer(); // get data as an array buffer
// DO SOMETHING WITH THE DATA HERE
} catch(e) { return e; } // pass error back to Excel
}
```
When fetching data, functions typically receive an `ArrayBuffer` which stores
the file data. This is readily parsed with `read`:
```js
var wb = XLSX.read(ab); // parse workbook
```
**This is how it should work**.
At the time of writing, there are outstanding bugs in Excel with raw data.
For the purposes of this demo, a Base64-encoded file will be used. The
workaround involves fetching that Base64 file, getting the text, and parsing
with the [`base64` type:](/docs/api/parse-options#input-type)
```js
async function extern() {
try {
const url = "https://sheetjs.com/pres.numbers.b64"; // URL to download
const res = await fetch(url); // fetch data
const text = await res.text(); // get data as an array buffer
var wb = XLSX.read(text, { type: "base64" });
// DO SOMETHING WITH THE DATA HERE
} catch(e) { return e; } // pass error back to Excel
}
```
Base64-encoded files can be generated with PowerShell:
```powershell
[convert]::ToBase64String([System.IO.File]::ReadAllBytes((Resolve-Path "path\to\file"))) > file.b64
```
</details>
The `.Sheets` property of the workbook object holds all of the worksheets and
the `.SheetNames` property is an array of worksheet names. Picking the first
worksheet is fairly straightforward:
```js
var ws = wb.Sheets[wb.SheetNames[0]]; // get first worksheet
```
This data can be converted to an Array of Arrays in one line:
```js
var aoa = XLSX.utils.sheet_to_json(ws, {header: 1}); // get data as array of arrays
```
To demonstrate the parsing ability, a Base64-encoded version of the file will
be used. This file contains no binary characters and should "just work". Once
the aforementioned Excel bug is fixed, the raw binary files can be used.
This new function should be added to `src\functions\functions.js`:
11) Add the following code snippet to `src\functions\functions.js`:
```js src\functions\functions.js
/**
* Download file and write data
* @customfunction
* @param {string} url URL to fetch and parse
* @returns {any[][]} Worksheet data
*/
async function extern() {
async function extern(url) {
try {
/* URL */
// const url = "https://sheetjs.com/pres.numbers"; // Once Excel bug is fixed
const url = "https://sheetjs.com/pres.numbers.b64"; // workaround
/* Fetch Data */
const res = await fetch(url);
/* Get Data */
// const ab = await res.arrayBuffer(); // Once Excel bug is fixed
const b64 = await res.text(); // workaround
const ab = await res.arrayBuffer();
/* Parse Data */
// var wb = XLSX.read(ab); // Once Excel bug is fixed
var wb = XLSX.read(b64, { type: "base64" }); // workaround
var wb = XLSX.read(ab);
/* get and return data */
var ws = wb.Sheets[wb.SheetNames[0]]; // get first worksheet
@ -224,13 +191,12 @@ async function extern() {
}
```
After making the change, save the files. Close the terminal window and the
12) After making the change, save the files. Close the terminal window and the
Excel window (do not save the Excel file). Re-run `npm start`.
Enter the formula `=SHEETJS.EXTERN()` in cell `D1` and press Enter. Excel
should pull in the data and generate a dynamic array:
![`SHEETJS.VERSION` output](pathname:///files/xlcfextern1.png)
13) Enter the text `https://sheetjs.com/pres.numbers` in cell `D1`. Enter the
formula `=SHEETJS.EXTERN(D1)` in cell `D2` and press Enter. Excel should pull
in the data and generate a dynamic array.
:::note

@ -26,7 +26,7 @@
"prism-react-renderer": "1.3.5",
"react": "17.0.2",
"react-dom": "17.0.2",
"xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz"
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.3.1"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -1,2 +1,3 @@
const current = "0.19.3";
export default current;
//const version = "0.19.3";
import { version } from "xlsx";
export default version;