demo refresh

This commit is contained in:
SheetJS 2025-01-05 21:51:20 -05:00
parent a488cdab0f
commit 84b839627e
85 changed files with 1113 additions and 647 deletions

@ -190,7 +190,6 @@ ExecJS
ExpressJS
ExtendScript
FastifyJS
Fastmail
FerretDB
FileReader
FileReaderSync

@ -79,7 +79,7 @@
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"> </Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/mobile/capacitor"><Data ss:Type="String">CapacitorJS</Data></Cell>
@ -90,7 +90,7 @@
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"> </Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/mobile/ionic"><Data ss:Type="String">Ionic</Data></Cell>

@ -123,7 +123,7 @@ This demo was last tested in the following deployments:
|:-------------|:---------|:-----------|
| `darwin-x64` | `1.1.39` | 2024-12-17 |
| `darwin-arm` | `1.1.10` | 2024-09-22 |
| `win11-x64` | `1.1.22` | 2024-08-11 |
| `win11-x64` | `1.1.42` | 2024-12-22 |
| `win11-arm` | `1.1.40` | 2024-12-19 |
| `linux-x64` | `1.1.40` | 2024-12-19 |
| `linux-arm` | `1.1.40` | 2024-12-19 |
@ -146,11 +146,11 @@ The PowerShell file redirect will use the `UTF-16 LE` encoding. Bun does not
support the encoding and will fail to install the package:
```
bun add v1.1.22-canary.96 (df33f2b2)
1 | <20><>{}
bun add v1.1.42 (50eec002)
1 | <20><>
^
error: Unexpected <20><>
at <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:1:1
```
The file must be resaved in UTF8 (without BOM) or ASCII.

@ -489,8 +489,9 @@ function SheetJSAoAFilled() {
### Select Data Rows
At this point, each data row will have the year in column `A` and dollar value
in column `C`. The year will be between 2007 and 2024 and the value will be
positive. The following function tests a data row:
in column `C`. The year (first value in the row) will be between 2007 and 2024.
The value (third value) will be positive. The following function tests a row
against the requirements:
```js
const is_valid_row = r =>

@ -19,7 +19,12 @@ This demo covers details elided in the official DanfoJS documentation.
:::note Tested Deployments
This example was last tested on 2024 April 25 against DanfoJS 1.1.2.
This demo was tested in the following deployments:
| Platform | Version | Date |
|:------------|:--------|:-----------|
| Chrome 131 | `1.1.2` | 2025-01-01 |
| Safari 18.2 | `1.1.2` | 2025-01-01 |
:::
@ -207,15 +212,23 @@ The following example exports a sample dataframe to a XLSX spreadsheet.
```jsx live
function DanfoToExcel() {
if(typeof dfd === "undefined") return (<b>RELOAD THIS PAGE</b>);
/* sample dataframe */
const df = new dfd.DataFrame([{Sheet:1,JS:2},{Sheet:3,JS:4}]);
return ( <><button onClick={async() => {
/* dfd.toExcel calls the SheetJS `writeFile` method */
dfd.toExcel(df, {fileName: "SheetJSDanfoJS.xlsx", writingOptions: {
compression: true
}});
}}>Click to Export</button><pre>{"Data:\n"+df.head()}</pre></> );
const [df, setDF] = React.useState({});
React.useEffect(() => {
/* sample dataframe */
setDF(new dfd.DataFrame([{Sheet:1,JS:2},{Sheet:3,JS:4}]));
}, []);
if(!df.head) return ;
return !df.head ? ( <b>RELOAD THIS PAGE</b>) : ( <>
<button onClick={async() => {
/* dfd.toExcel calls the SheetJS `writeFile` method */
dfd.toExcel(df, {fileName: "SheetJSDanfoJS.xlsx", writingOptions: {
compression: true
}});
}}>Click to Export</button>
<pre>{"Data:\n"+df.head()}</pre>
</> );
}
```

@ -40,11 +40,11 @@ This demo was tested in the following deployments:
| Architecture | JS Engine | Pandas | Python | Date |
|:-------------|:----------------|:-------|:-------|:-----------|
| `darwin-x64` | Duktape `2.7.0` | 2.2.1 | 3.12.2 | 2024-03-15 |
| `darwin-x64` | Duktape `2.7.0` | 2.2.3 | 3.13.1 | 2024-12-31 |
| `darwin-arm` | Duktape `2.7.0` | 2.2.2 | 3.12.3 | 2024-06-30 |
| `win11-x64` | Duktape `2.7.0` | 2.2.3 | 3.11.8 | 2024-12-21 |
| `win11-arm` | Duktape `2.7.0` | 2.2.2 | 3.11.5 | 2024-06-20 |
| `linux-x64` | Duktape `2.7.0` | 1.5.3 | 3.11.3 | 2024-03-21 |
| `linux-x64` | Duktape `2.7.0` | 1.5.3 | 3.11.7 | 2025-01-01 |
| `linux-arm` | Duktape `2.7.0` | 1.5.3 | 3.11.2 | 2024-06-20 |
:::
@ -519,11 +519,11 @@ This demo was tested in the following deployments:
| Architecture | JS Engine | Polars | Python | Date |
|:-------------|:----------------|:--------|:-------|:-----------|
| `darwin-x64` | Duktape `2.7.0` | 0.20.15 | 3.12.2 | 2024-03-15 |
| `darwin-x64` | Duktape `2.7.0` | 1.18.0 | 3.13.1 | 2024-12-31 |
| `darwin-arm` | Duktape `2.7.0` | 0.20.31 | 3.12.3 | 2024-06-30 |
| `win11-x64` | Duktape `2.7.0` | 1.17.1 | 3.11.8 | 2024-12-21 |
| `win11-arm` | Duktape `2.7.0` | 0.20.31 | 3.11.5 | 2024-06-20 |
| `linux-x64` | Duktape `2.7.0` | 0.20.16 | 3.11.3 | 2024-03-21 |
| `linux-x64` | Duktape `2.7.0` | 1.18.0 | 3.11.7 | 2025-01-01 |
| `linux-arm` | Duktape `2.7.0` | 0.20.31 | 3.11.2 | 2024-06-20 |
:::

@ -753,7 +753,7 @@ This demo was tested in the following environments:
| ReactJS | CRA | Date |
|:---------|:--------|:-----------|
| `18.2.0` | `5.0.1` | 2024-03-13 |
| `18.2.0` | `5.0.1` | 2024-12-31 |
:::

@ -425,8 +425,10 @@ This demo was tested in the following environments:
| Angular | Date |
|:----------|:-----------|
| `17.3.0` | 2024-03-13 |
| `16.2.12` | 2024-03-13 |
| `19.0.5` | 2025-01-03 |
| `18.2.13` | 2025-01-03 |
| `17.3.12` | 2025-01-03 |
| `16.2.12` | 2025-01-03 |
:::
@ -439,16 +441,16 @@ npx @angular/cli analytics disable -g
1) Create a new project:
```bash
npx @angular/cli@17.3.0 new --minimal --defaults --no-interactive sheetjs-angular
npx @angular/cli@19 new --minimal --defaults --no-interactive sheetjs-angular
```
:::note pass
The `@angular/cli` version controls the project version of Angular. For example,
the following command uses Angular 16.2.12:
the following command uses Angular 16:
```bash
npx @angular/cli@16.2.12 new --minimal --defaults --no-interactive sheetjs-angular
npx @angular/cli@16 new --minimal --defaults --no-interactive sheetjs-angular
```
:::
@ -619,8 +621,10 @@ This demo was tested in the following environments:
| Angular | Date |
|:----------|:-----------|
| `17.3.0` | 2024-03-13 |
| `16.2.12` | 2024-03-13 |
| `19.0.5` | 2025-01-03 |
| `18.2.13` | 2025-01-03 |
| `17.3.12` | 2025-01-03 |
| `16.2.12` | 2025-01-03 |
:::
@ -633,16 +637,16 @@ npx @angular/cli analytics disable -g
1) Create a new project:
```bash
npx @angular/cli@17.3.0 new --minimal --defaults --no-interactive sheetjs-angular
npx @angular/cli@19 new --minimal --defaults --no-interactive sheetjs-angular
```
:::note pass
The `@angular/cli` version controls the project version of Angular. For example,
the following command uses Angular 16.2.12:
the following command uses Angular 16:
```bash
npx @angular/cli@16.2.12 new --minimal --defaults --no-interactive sheetjs-angular
npx @angular/cli@16 new --minimal --defaults --no-interactive sheetjs-angular
```
:::

@ -412,7 +412,7 @@ This demo was tested in the following environments:
| VueJS | NuxtJS | Date |
|:---------|:---------|:-----------|
| `3.4.21` | `3.11.1` | 2024-03-21 |
| `3.5.13` | `3.15.0` | 2025-01-02 |
:::
@ -520,7 +520,7 @@ This demo was tested in the following environments:
| VueJS | ViteJS | Date |
|:---------|:--------|:-----------|
| `3.4.21` | `5.2.2` | 2024-03-21 |
| `3.5.13` | `6.0.7` | 2025-01-02 |
:::
@ -573,7 +573,7 @@ This demo was tested in the following environments:
| VueJS | NuxtJS | Date |
|:---------|:---------|:-----------|
| `3.4.21` | `3.11.1` | 2024-03-21 |
| `3.5.13` | `3.15.0` | 2025-01-02 |
:::

@ -1,6 +1,6 @@
---
title: Sheets in OpenUI5 Sites
sidebar_label: OpenUI5
title: Sheets in UI5 Sites
sidebar_label: OpenUI5 / SAPUI5
description: Build enterprise-grade applications with OpenUI5. Seamlessly integrate spreadsheets into your app using SheetJS. Bring Excel-powered workflows and data to the modern web.
pagination_prev: demos/index
pagination_next: demos/grid/index
@ -12,7 +12,8 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
[OpenUI5](https://openui5.org/) is a JavaScript framework for building enterprise-ready web applications.
[OpenUI5](https://openui5.org/) is a JavaScript framework for building
enterprise-ready web applications. It is compatible with the SAPUI5 framework.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
@ -21,19 +22,84 @@ This demo uses OpenUI5 and SheetJS to process and generate a spreadsheets. We'll
explore how to load SheetJS in an OpenUI5 app and handle data binding with the
Model-View-Controller pattern.
:::info pass
[Docs Issue #20](https://git.sheetjs.com/sheetjs/docs.sheetjs.com/issues/20)
includes a complete example starting from the OpenUI5 "Worklist App Tutorial".
:::
## Installation
[The "Frameworks" section](/docs/getting-started/installation/frameworks) covers
installation with Yarn and other package managers.
SheetJS libraries conform to the UI5 ECMAScript limitations[^1]. SheetJS
libraries can be loaded in a UI5 site at different points in the app lifecycle.
The library should be loaded via script tag in your HTML:
**HTML**
<CodeBlock language="html" value="html">
{`<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
UI5 is typically loaded in a `SCRIPT` tag in `webapp/index.html`. Similarly,
[SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
can be loaded in the same HTML page:
<CodeBlock language="html" value="html">{`\
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
`}
</CodeBlock>
This will expose the `XLSX` global object which contains all the necessary methods.
This will expose the `XLSX` global object, which includes the functions listed
in the ["API Reference"](/docs/api/) section of the documentation.
:::caution pass
The SheetJS Standalone script must be loaded before the UI5 bootstrap script:
<CodeBlock language="html" value="html" title="webapp/index.html">{`\
<head>
<meta charset="utf-8">
<title>UI5 Walkthrough</title>
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
...(other attributes)...
>
</script>
</head>
`}
</CodeBlock>
:::
**UI5 Module**
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
comply with AMD `define` semantics. They support `sap.ui.define` out of the box.
If the SheetJS Standalone script is saved to `webapp/xlsx.full.min.js`, the base
script `webapp/index.js` can load the library at `./xlsx.full.min`.
```js title="webapp/index.js"
sap.ui.define([
// highlight-next-line
"./xlsx.full.min", // relative path to script, without the file extension
/* ... other libraries ... */
], function(
// highlight-next-line
_XLSX // !! NOTE: this is not XLSX! A different variable name must be used
/* ... variables for the other libraries ... */,
) {
// highlight-next-line
alert(XLSX.version); // use XLSX in the callback
});
```
:::info pass
In some deployments, the function argument was `undefined`.
The standalone scripts add `window.XLSX`, so it is recommended to use `_XLSX`
in the function arguments and access the library with `XLSX` in the callback.
:::
## Internal State
@ -70,7 +136,7 @@ object for each row, using the values in the first rows as keys:
</td></tr></tbody></table>
The OpenUI5 `JSONModel`[^1] is a client-side model implementation that stores data as JSON.
The OpenUI5 `JSONModel`[^2] is a client-side model implementation that stores data as JSON.
Here's a basic example of initializing a model, with a more complete implementation shown later:
<Tabs groupId="lang">
@ -132,7 +198,7 @@ _loadExcelFile: async function () {
#### Rendering Data
In OpenUI5, the `Model-View-Controller`[^2] pattern is used to organize code and separate concerns.
In OpenUI5, the `Model-View-Controller`[^3] pattern is used to organize code and separate concerns.
The view defines the UI structure, the controller handles the logic, and the model manages the data.
```xml title="Example XML for displayin array of objects"
@ -360,14 +426,14 @@ will generate a workbook that can be opened in a spreadsheet editor.
The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells[^3] well!
However, this does not handle merge cells[^4] well!
The [`sheet_to_html`](/docs/api/utilities/html#html-table-output) function generates HTML that is aware of merges and other worksheet
features. Using OpenUI5's `core:HTML`[^4] control, we can render this HTML directly. During export, we extract the table element from the
features. Using OpenUI5's `core:HTML`[^5] control, we can render this HTML directly. During export, we extract the table element from the
rendered HTML and use [`table_to_book`](/docs/api/utilities/html#html-table-input) to create a workbook that maintains all the worksheet
features.
In this example, the component directly renders the HTML table in the model through OpenUI5's `core:HTML`[^4] control. For export, we extract
In this example, the component directly renders the HTML table in the model through OpenUI5's `core:HTML`[^5] control. For export, we extract
the inner table from the rendered HTML using `getElementsByTagName("table")[1]`, then pass it to [`table_to_book`](/docs/api/utilities/html#html-table-input)
to create a workbook that preserves all features.
@ -513,7 +579,8 @@ will generate a workbook that can be opened in a spreadsheet editor.
</details>
[^1]: See [`JSONModel`](https://sdk.openui5.org/1.38.62/docs/api/symbols/sap.ui.model.json.JSONModel.html) in the OpenUI5 documentation.
[^2]: See OpenUI5's [MVC Documentation](https://sdk.openui5.org/topic/91f233476f4d1014b6dd926db0e91070) for detailed explanation of the pattern implementation.
[^3]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.
[^4]: See [`core:HTML`](https://sdk.openui5.org/1.38.62/docs/api/symbols/sap.ui.core.HTML.html) in the OpenUI5 documentation.
[^1]: See ["ECMAScript Support"](https://sdk.openui5.org/topic/0cb44d7a147640a0890cefa5fd7c7f8e.html#loio0cb44d7a147640a0890cefa5fd7c7f8e/section_UI5Mod) for more details about OpenUI5 compatibility.
[^2]: See [`JSONModel`](https://sdk.openui5.org/1.38.62/docs/api/symbols/sap.ui.model.json.JSONModel.html) in the OpenUI5 documentation.
[^3]: See OpenUI5's [MVC Documentation](https://sdk.openui5.org/topic/91f233476f4d1014b6dd926db0e91070) for detailed explanation of the pattern implementation.
[^4]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.
[^5]: See [`core:HTML`](https://sdk.openui5.org/1.38.62/docs/api/symbols/sap.ui.core.HTML.html) in the OpenUI5 documentation.

@ -41,10 +41,10 @@ This demo was tested in the following environments:
| Version | Date | Required Workarounds |
|:---------|:-----------|:------------------------------------|
| `2.7.0` | 2024-03-16 | Import `xlsx/dist/xlsx.full.min.js` |
| `3.12.0` | 2024-03-16 | Import `xlsx/dist/xlsx.full.min.js` |
| `4.47.0` | 2024-03-16 | Downgrade NodeJS (tested v16.20.2) |
| `5.90.3` | 2024-03-16 | |
| `5.97.1` | 2025-01-03 | |
| `4.47.0` | 2025-01-03 | |
| `3.12.0` | 2025-01-03 | Import `xlsx/dist/xlsx.full.min.js` |
| `2.7.0` | 2025-01-03 | Import `xlsx/dist/xlsx.full.min.js` |
:::
@ -269,15 +269,16 @@ version above 4.0 can be pinned by locally installing webpack and the CLI tool.
**Webpack 4.x**
:::info pass
:::note pass
Webpack 4 is incompatible with Node 18+. It will elicit the following error:
Some Webpack 4 versions are incompatible with Node 18+. They will elicit the
following error:
```
Error: error:0308010C:digital envelope routines::unsupported
```
When this demo was last tested, NodeJS was locally downgraded to 16.20.2
In some demo tests, NodeJS was locally downgraded to 16.20.2
:::

@ -46,11 +46,11 @@ This demo was tested in the following environments:
| Version | Platform | Date |
|:----------|:---------|:-----------|
| `0.19.47` | NodeJS | 2024-03-31 |
| `0.20.16` | Browser | 2024-03-31 |
| `0.20.19` | NodeJS | 2024-03-31 |
| `0.21.6` | NodeJS | 2024-03-31 |
| `6.14.3` | NodeJS | 2024-03-31 |
| `0.19.47` | NodeJS | 2025-01-03 |
| `0.20.16` | Browser | 2025-01-03 |
| `0.20.19` | NodeJS | 2025-03-03 |
| `0.21.6` | NodeJS | 2025-03-03 |
| `6.15.1` | NodeJS | 2025-01-03 |
:::
@ -203,7 +203,7 @@ npm init -y
1) Install the dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz systemjs@6.14.3`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz systemjs@6.15.1`}
</CodeBlock>
2) Download [`SheetJSystem.js`](pathname:///systemjs/SheetJSystem.js) and move

@ -34,10 +34,10 @@ This demo was tested in the following environments:
| Version | Date |
|:---------|:-----------|
| `4.13.0` | 2024-03-25 |
| `3.29.4` | 2024-03-25 |
| `2.79.1` | 2024-03-25 |
| `1.32.1` | 2024-03-25 |
| `4.29.1` | 2025-01-03 |
| `3.29.5` | 2025-01-03 |
| `2.79.1` | 2025-01-03 |
| `1.32.1` | 2025-01-03 |
:::

@ -34,8 +34,8 @@ This demo was tested in the following environments:
| Version | Date |
|:---------|:-----------|
| `2.12.0` | 2024-06-08 |
| `1.12.4` | 2024-06-08 |
| `2.13.3` | 2024-12-31 |
| `1.12.3` | 2024-12-31 |
:::

@ -22,8 +22,12 @@ The ["Complete Example"](#complete-example) section includes a complete server.
:::note Tested Deployments
This demo was tested on 2024 March 11 using `express-formidable@1.2.0` and
ExpressJS `4.18.3`
This demo was tested in the following deployments:
| Version | Date |
|:---------|:-----------|
| `4.21.2` | 2025-01-03 |
| `5.0.1` | 2025-01-03 |
:::
@ -136,21 +140,23 @@ app.get('/download', function(req, res) {
res.status(200).end(buf);
// highlight-end
});
app.listen(+process.env.PORT||3000);
app.listen(+process.env.PORT||3000, function() { console.log("Ready to go"); });
```
2) Install dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz express@4.18.3 express-formidable@1.2.0`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz express@4.21.2 express-formidable@1.2.0`}
</CodeBlock>
3) Start server (note: it will not print anything to console when running)
3) Start server
```bash
node SheetJSExpressCSV.js
```
Once the server starts, the process will print `Ready to go`.
4) Test POST requests using https://docs.sheetjs.com/pres.numbers . The commands
should be run in a new terminal window:

@ -22,7 +22,13 @@ The ["Complete Example"](#complete-example) section includes a complete server.
:::note Tested Deployments
This demo was tested on 2024 March 11 using NestJS `10.3.3`.
This demo was tested in the following deployments:
| NestJS | Date |
|:----------|:-------------|
| `10.4.15` | `2024-12-22` |
| `9.4.3` | `2024-12-22` |
| `8.4.7` | `2024-12-22` |
:::
@ -124,10 +130,23 @@ npx @nestjs/cli@latest new -p npm sheetjs-nest
cd sheetjs-nest
```
2) Install the `@types/multer` package as a development dependency:
:::info pass
The NestJS CLI generates a project using the latest version of NestJS. To force
an older version, install older versions of dependencies in the `@nestjs` scope.
For example, the following command rolls back to NestJS major version 8:
```bash
npm i --save-dev @types/multer
npm install --save @nestjs/cli@8.x @nestjs/common@8.x @nestjs/core@8.x @nestjs/platform-express@8.x @nestjs/schematics@8.x @nestjs/testing@8.x --force
```
:::
2) Install the `@types/multer` package as a dependency:
```bash
npm i --save @types/multer
```
3) Install the SheetJS library:
@ -187,7 +206,7 @@ npx @nestjs/cli start
:::note pass
In the most recent test, the process failed with a message referencing `Multer`:
In some tests, the process failed with a message referencing `Multer`:
```
src/sheetjs/sheetjs.controller.ts:9:54 - error TS2694: Namespace 'global.Express' has no exported member 'Multer'.
@ -203,7 +222,7 @@ This error indicates that `@types/multer` is not available.
The recommended fix is to install `@types/multer` again:
```bash
npm i --save-dev @types/multer
npm i --save @types/multer
npx @nestjs/cli start
```

@ -21,7 +21,12 @@ The ["Complete Example"](#complete-example) section includes a complete server.
:::note Tested Deployments
This demo was verified on 2024 March 11 using `fastify@4.26.2`
This demo was tested in the following deployments:
| Version | Date |
|:---------|:-----------|
| `4.29.0` | 2025-01-02 |
| `5.2.0` | 2025-01-02 |
:::
@ -157,7 +162,7 @@ fastify.listen({port: process.env.PORT || 3000}, (err, addr) => { if(err) throw
1) Install dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz fastify@4.26.2 @fastify/multipart@8.1.0`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz fastify@4.29.0 @fastify/multipart@8`}
</CodeBlock>
2) Start server

@ -34,7 +34,13 @@ the file can be downloaded or previewed in the browser.
:::note Tested Deployments
This demo was last tested on 2024 March 11 against `pst-extractor` 1.9.0
This demo was tested in the following deployments:
| Platform | Version | Date |
|:------------|:---------|:-----------|
| Chrome 131 | `1.9.0` | 2024-12-22 |
| NodeJS 20 | `1.10.0` | 2024-12-22 |
| BunJS 1.1 | `1.10.0` | 2024-12-22 |
:::

@ -32,53 +32,25 @@ before integrating with important inboxes or accounts.
:::danger pass
It is strongly advised to use a test email address before using an important
address. One small mistake could erase decades of messages or result in a block
address. One small mistake could erase decades of messages or result in a block
or ban from Google services.
:::
### App Passwords
Many email providers (including Fastmail, GMail, and Yahoo Mail) require "app
passwords" or passwords for "less secure apps". Attempting to connect and send
using the account password will throw errors.
Many email providers (including GMail and Yahoo Mail) require "app passwords" or
passwords for "less secure apps". Attempting to connect and send using the
account password will throw errors.
### Test Account
It is strongly recommended to first test with an independent service provider.
#### Fastmail
This demo will start with a free 30-day trial of Fastmail. At the time the demo
was last tested, no payment details were required.
:::caution pass
A valid phone number (for SMS verification) was required.
:::
0) Create a new Fastmail email account and verify with a mobile number.
_Create App Password_
1) Open the settings screen (click on the icon in the top-left corner of the
screen and select "Settings").
2) Select "Privacy & Security" in the left pane, then click "Integrations" near
the top of the main page. Click "New app password".
3) Select any name in the top drop-down (the default "iPhone" can be used). In
the second drop-down, select "Mail (IMAP/POP/SMTP)". Click "Generate password".
A new password will be displayed. This is the app password that will be used in
the demo script. **Copy the displayed password or write it down.**
It is strongly recommended to first test with a separate account.
#### Gmail
This demo will start with a free Gmail account. At the time the demo was last
tested, no payment details were required.
This demo will start with a free Gmail account. When the demo was last tested,
no payment details were required.
:::caution pass
@ -98,7 +70,8 @@ _Create App Password_
4) Click "2-Step Verification"
5) Click the right arrow (`>`) next to "App passwords".
5) Click the right arrow (`>`) next to "App passwords". If that option is not
available, open https://myaccount.google.com/apppasswords
6) Type a name ("SheetJS Test") and click "Create".
@ -152,8 +125,7 @@ This demo was tested in the following deployments:
| Email Provider | Date | Library | Version |
|:---------------|:-----------|:-------------|:---------|
| `gmail.com` | 2024-03-11 | `nodemailer` | `6.9.12` |
| `fastmail.com` | 2024-03-11 | `nodemailer` | `6.9.12` |
| `gmail.com` | 2025-01-05 | `nodemailer` | `6.9.16` |
:::
@ -175,7 +147,7 @@ const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
// highlight-next-line
service: 'fastmail',
service: 'gmail',
auth: {
// highlight-start
user: '**',
@ -210,7 +182,7 @@ transporter.sendMail(mailOptions, function (err, info) {
3) Edit `SheetJSend.js` and replace the highlighted lines:
- `service: 'fastmail',` the value should be one of the supported providers[^1]
- `service: 'gmail',` the value should be one of the supported providers[^1]
- `user: "**",` the value should be the sender email address
- `pass: "**"` the value should be the app password from earlier
- `from: "**",` the value should be the sender email address
@ -300,8 +272,7 @@ This demo was tested in the following deployments:
| Email Provider | Date | Library | Version |
|:---------------|:-----------|:-----------|:----------|
| `gmail.com` | 2024-03-11 | `imapflow` | `1.0.156` |
| `fastmail.com` | 2024-03-11 | `imapflow` | `1.0.156` |
| `gmail.com` | 2025-01-05 | `imapflow` | `1.0.172` |
:::
@ -312,7 +283,7 @@ This demo was tested in the following deployments:
<CodeBlock language="bash">{`\
mkdir sheetjs-recv
cd sheetjs-recv
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz imapflow@1.0.156`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz imapflow@1.0.172`}
</CodeBlock>
2) Save the following script to `SheetJSIMAP.js`:
@ -323,7 +294,7 @@ const { ImapFlow } = require('imapflow');
const client = new ImapFlow({
// highlight-next-line
host: 'imap.fastmail.com',
host: 'imap.gmail.com',
port: 993, secure: true, logger: false,
auth: {
// highlight-start
@ -371,18 +342,19 @@ const concat_RS = (stream) => new Promise((res, rej) => {
- `user: "**",` the value should be the account address
- `pass: "**"` the value should be the app password from earlier
- `host: 'imap.fastmail.com',` the value should be the host name:
- `host: 'imap.gmail.com',` the value should be the host name:
| Service | `host` value |
|:---------------|:--------------------|
| `gmail.com` | `imap.gmail.com` |
| `fastmail.com` | `imap.fastmail.com` |
4) Download https://docs.sheetjs.com/pres.numbers . Using a different account,
send an email to the test account and attach the file. At the end of this step,
the test account should have an email in the inbox that has an attachment.
4) Download https://docs.sheetjs.com/pres.numbers .
5) Run the script:
5) Send an email to the test account and attach `pres.numbers` from step 4.
6) Wait until the test account receives the email.
7) Run the script:
```bash
node SheetJSIMAP.js

@ -25,6 +25,17 @@ not generally support passing objects between the browser context and the
automation script, so the file data must be generated in the browser context
and sent back to the automation script for saving in the file system.
This demo exports data from https://sheetjs.com/demos/table.
:::note pass
It is also possible to parse files from the browser context, but parsing from
the automation context is more efficient and strongly recommended.
:::
#### Key Steps
```mermaid
sequenceDiagram
autonumber off
@ -50,39 +61,23 @@ sequenceDiagram
end
```
<details open>
<summary><b>Key Steps</b> (click to hide)</summary>
1) Launch the headless browser and load the target site.
2) Add the standalone SheetJS build to the page in a `SCRIPT` tag.
3) Add a script to the page (in the browser context) that will:
- Make a workbook object from the first table using `XLSX.utils.table_to_book`
- Generate the bytes for an XLSB file using `XLSX.write`
- Make a SheetJS workbook object[^1] from the first table using the SheetJS
`table_to_book`[^2] method.
- Generate the bytes for an XLSB file using the SheetJS `write`[^3] method.
- Send the bytes back to the automation script
4) When the automation context receives data, save to a file
</details>
This demo exports data from https://sheetjs.com/demos/table.
:::note pass
It is also possible to parse files from the browser context, but parsing from
the automation context is more efficient and strongly recommended.
:::
## Puppeteer
[Puppeteer](https://pptr.dev/) enables headless Chromium automation for NodeJS.
Releases ship with an installer script that installs a headless browser.
<Tabs>
<TabItem value="nodejs" label="NodeJS">
[Puppeteer](https://pptr.dev/) enables headless Chromium automation for NodeJS
and BunJS. Releases ship with a script that installs a headless browser.
Binary strings are the favored data type. They can be safely passed from the
browser context to the automation script. NodeJS provides an API to write
@ -131,116 +126,61 @@ const puppeteer = require('puppeteer');
:::note Tested Deployments
This demo was last tested on 2024 June 24 against Puppeteer 22.12.0.
This demo was tested in the following deployments:
| Puppeteer | Date |
|:----------|:-----------|
| `23.11.1` | 2024-12-31 |
| `22.15.0` | 2024-12-31 |
| `21.11.0` | 2024-12-31 |
| `20.9.0` | 2024-12-31 |
| `15.5.0` | 2024-12-31 |
| `10.4.0` | 2024-12-31 |
:::
1) Install SheetJS and Puppeteer:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz puppeteer@22.12.0`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz puppeteer@23.11.1`}
</CodeBlock>
</TabItem>
<TabItem value="bunjs" label="BunJS">
<CodeBlock language="bash">{`\
bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz puppeteer@23.11.1`}
</CodeBlock>
</TabItem>
</Tabs>
2) Save the `SheetJSPuppeteer.js` code snippet to `SheetJSPuppeteer.js`.
3) Run the script:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
```bash
node SheetJSPuppeteer.js
```
When the script finishes, the file `SheetJSPuppeteer.xlsb` will be created.
This file can be opened with Excel.
</TabItem>
<TabItem value="deno" label="Deno">
:::caution pass
Deno Puppeteer is a fork. It is not officially supported by the Puppeteer team.
:::
Base64 strings are the favored data type. They can be safely passed from the
browser context to the automation script. Deno can decode the Base64 strings
and write the decoded `Uint8Array` data to file with `Deno.writeFileSync`
The key steps are commented below:
<CodeBlock language="ts" title="SheetJSPuppeteer.ts">{`\
import puppeteer from "https://deno.land/x/puppeteer@16.2.0/mod.ts";
import { decode } from "https://deno.land/std/encoding/base64.ts"
\n\
/* (1) Load the target page */
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});
await page.goto('https://sheetjs.com/demos/table');
\n\
/* (2) Load the standalone SheetJS build from the CDN */
await page.addScriptTag({ url: 'https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js' });
\n\
/* (3) Run the snippet in browser and return data */
const b64 = await page.evaluate(() => {
/* NOTE: this function will be evaluated in the browser context.
\`page\`, \`fs\` and \`puppeteer\` are not available.
\`XLSX\` will be available thanks to step 2 */
\n\
/* find first table */
var table = document.body.getElementsByTagName('table')[0];
\n\
/* call table_to_book on first table */
var wb = XLSX.utils.table_to_book(table);
\n\
/* generate XLSB and return binary string */
return XLSX.write(wb, {type: "base64", bookType: "xlsb"});
});
/* (4) write data to file */
Deno.writeFileSync("SheetJSPuppeteer.xlsb", decode(b64));
\n\
await browser.close();`}
</CodeBlock>
**Demo**
:::note Tested Deployments
This demo was last tested on 2024 June 24 against `deno-puppeteer` 16.2.0.
:::
1) Install `deno-puppeteer`:
<TabItem value="bunjs" label="BunJS">
```bash
env PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts
bun SheetJSPuppeteer.js
```
:::note pass
In PowerShell, the environment variable should be set separately:
```powershell
[Environment]::SetEnvironmentVariable('PUPPETEER_PRODUCT', 'chrome')
deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts
```
:::
2) Save the `SheetJSPuppeteer.ts` code snippet to `SheetJSPuppeteer.ts`.
3) Run the script:
```bash
deno run -A --unstable SheetJSPuppeteer.ts
```
When the script finishes, the file `SheetJSPuppeteer.xlsb` will be created.
This file can be opened with Excel.
</TabItem>
</Tabs>
When the script finishes, the file `SheetJSPuppeteer.xlsb` will be created.
This file can be opened with a spreadsheet editor that supports XLSB workbooks.
## Playwright
@ -294,26 +234,56 @@ const { webkit } = require('playwright'); // import desired browser
:::note Tested Deployments
This demo was last tested on 2024 June 24 against Playwright 1.45.0.
This demo was tested in the following deployments:
| Playwright | Date |
|:-----------|:-----------|
| `1.49.1` | 2024-12-31 |
:::
1) Install SheetJS and Playwright:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz playwright@1.45.0`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz playwright@1.49.1`}
</CodeBlock>
</TabItem>
<TabItem value="bunjs" label="BunJS">
<CodeBlock language="bash">{`\
bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz playwright@1.49.1`}
</CodeBlock>
</TabItem>
</Tabs>
2) Save the `SheetJSPlaywright.js` code snippet to `SheetJSPlaywright.js`.
3) Run the script
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
```bash
node SheetJSPlaywright.js
```
</TabItem>
<TabItem value="bunjs" label="BunJS">
```bash
bun SheetJSPlaywright.js
```
</TabItem>
</Tabs>
When the script finishes, the file `SheetJSPlaywright.xlsb` will be created.
This file can be opened with Excel.
This file can be opened with a spreadsheet editor that supports XLSB workbooks.
:::caution pass
@ -456,3 +426,7 @@ env OPENSSL_CONF=/dev/null QT_QPA_PLATFORM=phantom ./phantomjs-2.1.1-linux-x86_6
:::
</details>
[^1]: See ["Workbook Object"](/docs/csf/book) for more details about the SheetJS workbook object.
[^2]: See [`table_to_book` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet)
[^3]: See [`write` in "Writing Files"](/docs/api/write-options)

@ -16,7 +16,12 @@ With a familiar UI, `x-spreadsheet` is an excellent choice for a modern editor.
:::note Tested Deployments
This demo was last verified on 2024 April 25.
This demo was tested in the following environments:
| Browser | Date |
|:------------|:-----------|
| Chrome 131 | 2024-12-31 |
| Safari 18.2 | 2024-12-31 |
:::

@ -15,7 +15,12 @@ with a straightforward API.
:::note Tested Deployments
This demo was last verified on 2024 April 25.
This demo was tested in the following environments:
| Browser | Date |
|:------------|:-----------|
| Chrome 131 | 2024-12-31 |
| Safari 18.2 | 2024-12-31 |
:::

@ -133,8 +133,8 @@ This demo was tested in the following environments:
| Lume | Date |
|:---------|:-----------|
| `1.19.4` | 2024-03-16 |
| `2.1.2` | 2024-03-16 |
| `1.19.4` | 2025-01-02 |
| `2.4.3` | 2025-01-02 |
This example uses the Nunjucks template format. Lume plugins support additional
template formats, including Markdown and JSX.
@ -150,15 +150,17 @@ template formats, including Markdown and JSX.
```bash
mkdir -p sheetjs-lume
cd sheetjs-lume
deno run -Ar https://deno.land/x/lume@v2.1.2/init.ts
deno run -Ar https://deno.land/x/lume@v2.4.3/init.ts
```
When prompted, enter the following options:
When prompted, enter the following options. The initialization script has
changed over time, so not all questions will be asked.
- `What kind of setup do you want?`: select `Basic + plugins`
- `Choose the configuration file format`: select `_config.ts`
- `Do you want to install some plugins now?`: select `Yes`
- `Select the plugins to install`: select `sheets` and `nunjucks`
- `Do you want to setup a CMS?`: select `Maybe later`
- `Do you want to setup a CMS?`: select `No` or `Maybe later`
The project will be configured and modules will be installed.
@ -226,7 +228,7 @@ deno task lume
This will create a static site in the `_site` folder
7) Test the generated site by starting a web server:
8) Test the generated site by starting a web server:
```bash
npx http-server _site

@ -180,7 +180,7 @@ This demo was tested in the following environments:
| GatsbyJS | Date |
|:---------|:-----------|
| `5.13.4` | 2024-05-04 |
| `4.25.8` | 2024-03-27 |
| `4.25.8` | 2025-01-02 |
:::
@ -221,8 +221,8 @@ npx gatsby new sheetjs-gatsby
For older Gatsby versions, the project must be built from the starter project.
For GatsbyJS 4, the starter commit is `6bc4466090845f20650117b3d27e68e6e46dc8d5`
and the steps are shown below:
The starter commit for GatsbyJS 4 is `6bc4466090845f20650117b3d27e68e6e46dc8d5`.
This version of the starter can be fetched with `git`:
```bash
git clone https://github.com/gatsbyjs/gatsby-starter-default sheetjs-gatsby
@ -234,7 +234,7 @@ cd ..
:::
2) Follow the on-screen instructions for starting the local development server:
2) Start the local development server:
```bash
cd sheetjs-gatsby
@ -257,7 +257,7 @@ Open a web browser to the displayed URL (typically `http://localhost:8000/`)
`}
</CodeBlock>
4) Install the library and plugins:
4) Install the SheetJS library and GatsbyJS plugins:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
@ -329,14 +329,18 @@ If the `plugins` array exists, the two plugins should be added at the beginning:
:::
Stop and restart the development server process (`npm run develop`).
7) Stop and restart the development server process:
```bash
npm run develop
```
### GraphiQL test
7) Open the GraphiQL editor. The output of the previous step displayed the URL
(typically `http://localhost:8000/___graphql` )
8) Open the GraphiQL editor in a web browser. The output of the previous step
displayed the URL (typically `http://localhost:8000/___graphql` ).
There is an editor in the left pane. Paste the following query into the editor:
9) Paste the following query into the code editor:
```graphql title="GraphQL Query (paste into editor)"
{
@ -358,7 +362,7 @@ Press the Execute Query button (`▶`) and data should show up in the right pane
<details>
<summary><b>Sample Output</b> (click to show)</summary>
In GatsbyJS version `5.13.4`, the raw output was:
In GatsbyJS versions `5.13.4` and `4.25.8`, the raw output was:
```json title="GraphQL query result from GatsbyJS 5.13.4"
{
@ -406,7 +410,7 @@ In GatsbyJS version `5.13.4`, the raw output was:
### React page
8) Create a new file `src/pages/pres.js` that uses the query and displays the result:
10) Create a new page `src/pages/pres.js` that displays the raw query result:
```jsx title="src/pages/pres.js (create new file)"
import { graphql } from "gatsby"
@ -429,8 +433,8 @@ const PageComponent = ({data}) => {
export default PageComponent;
```
After saving the file, access `http://localhost:8000/pres` in the browser. The
displayed JSON is the data that the component receives:
11) After saving the file, access `http://localhost:8000/pres` in the browser.
The displayed JSON is the data that the component receives:
```js title="Expected contents of /pres"
{
@ -445,7 +449,7 @@ displayed JSON is the data that the component receives:
// ....
```
9) Change `PageComponent` to display a table based on the data:
12) Change `PageComponent` to display a table based on the data:
```jsx title="src/pages/pres.js (replace PageComponent)"
import { graphql } from "gatsby"
@ -484,7 +488,7 @@ Going back to the browser, `http://localhost:8000/pres` will show a table:
### Live refresh
10) Open the file `src/data/pres.xlsx` in Excel or another spreadsheet editor.
13) Open the file `src/data/pres.xlsx` in Excel or another spreadsheet editor.
Add a new row at the end of the file, setting cell `A7` to "SheetJS Dev" and
cell `B7` to `47`. The sheet should look like the following screenshot:
@ -496,7 +500,7 @@ Save the file and observe that the table has refreshed with the new data:
### Static site
11) Stop the development server and build the site:
14) Stop the development server and build the site:
```bash
npm run build
@ -527,7 +531,7 @@ Pages
The generated page will be placed in `public/pres/index.html`.
12) Open `public/pres/index.html` with a text editor and search for "SheetJS".
15) Open `public/pres/index.html` with a text editor and search for "SheetJS".
There will be a HTML row:
```html title="public/pres/index.html (Expected contents)"

@ -184,7 +184,11 @@ document.body.appendChild(elt);
:::note Tested Deployments
This demo was last tested on 2024 April 06 against Webpack 5.91.0
This demo was tested in the following deployments:
| Version | Date |
|:---------|:-----------|
| `5.91.0` | 2024-04-06 |
:::

@ -114,10 +114,10 @@ accessed using the variable `pres` in a template:
This demo was tested in the following environments:
| Eleventy | Date |
|:----------------|:-----------|
| `2.0.1` | 2024-03-15 |
| `3.0.0-alpha.5` | 2024-03-15 |
| Eleventy | Date |
|:---------|:-----------|
| `2.0.1` | 2024-12-23 |
| `3.0.0` | 2024-12-23 |
:::
@ -134,17 +134,17 @@ npm init -y
2) Install Eleventy and SheetJS libraries:
<Tabs groupId="11ty">
<TabItem value="2" label="Stable">
<TabItem value="2" label="2.x">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @11ty/eleventy@2.0.1`}
</CodeBlock>
</TabItem>
<TabItem value="3" label="Alpha">
<TabItem value="3" label="3.x">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @11ty/eleventy@3.0.0-alpha.5`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @11ty/eleventy@3.0.0`}
</CodeBlock>
</TabItem>

@ -226,19 +226,19 @@ This demo was tested in the following environments:
| OS | Device | RN | Date |
|:-----------|:------------------|:---------|:-----------|
| iOS 15.1 | iPhone 12 Pro Max | `0.73.6` | 2024-03-13 |
| Android 29 | NVIDIA Shield | `0.73.6` | 2024-03-13 |
| iOS 15.6 | iPhone 13 Pro Max | `0.76.5` | 2025-01-05 |
| Android 34 | NVIDIA Shield | `0.76.5` | 2025-01-05 |
**Simulators**
| OS | Device | RN | Dev Platform | Date |
|:-----------|:--------------------|:---------|:-------------|:-----------|
| Android 34 | Pixel 3a | `0.73.6` | `darwin-x64` | 2024-03-13 |
| iOS 17.4 | iPhone 15 Pro Max | `0.73.6` | `darwin-x64` | 2024-03-13 |
| Android 34 | Pixel 3a | `0.74.2` | `darwin-arm` | 2024-06-20 |
| iOS 17.5 | iPhone SE (3rd gen) | `0.74.2` | `darwin-arm` | 2024-06-20 |
| Android 34 | Pixel 3a | `0.76.5` | `darwin-x64` | 2024-12-31 |
| iOS 18.2 | iPhone 16 Pro | `0.76.5` | `darwin-x64` | 2024-12-31 |
| Android 34 | Pixel 3a | `0.76.5` | `darwin-arm` | 2025-01-05 |
| iOS 18.2 | iPhone 16 Pro | `0.76.5` | `darwin-arm` | 2025-01-05 |
| Android 35 | Pixel 9 | `0.76.5` | `win11-x64` | 2024-12-22 |
| Android 34 | Pixel 3a | `0.73.7` | `linux-x64` | 2024-04-29 |
| Android 35 | Pixel 9 | `0.76.5` | `linux-x64` | 2025-01-02 |
:::
@ -280,6 +280,8 @@ export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
npx -y @react-native-community/cli@15 init SheetJSRNFetch --version="0.76.5"
```
On macOS, if prompted to install `CocoaPods`, press <kbd>Y</kbd>
:::info pass
Older versions of this demo used the `react-native` package. The `init` command
@ -481,6 +483,12 @@ If the device asks to allow USB debugging, tap "Allow".
npx react-native run-android
```
:::note pass
Depending on the device, it may take up to 10 seconds for app to update.
:::
**iOS Device Testing**
13) Connect an iOS device using a USB cable.
@ -494,7 +502,7 @@ If the device asks to trust the computer, tap "Trust" and enter the passcode.
<details open>
<summary><b>Enabling Code Signing</b> (click to show)</summary>
These instructions were verified against Xcode 15.3.
These instructions were verified against Xcode 16.2.
A) Open the included iOS workspace in Xcode:
@ -526,6 +534,14 @@ brew install ios-deploy
npx react-native run-ios
```
:::info pass
> "SheetJSRNFetch" would like to find and connect to devices on your local network.
Local network access is not required for the demo. Select "Don't Allow".
:::
:::caution pass
In some tests, the app failed to run on the device due to "Untrusted Developer":
@ -534,7 +550,7 @@ In some tests, the app failed to run on the device due to "Untrusted Developer":
Your device management settings do not allow apps from developer ... on this iPhone. You can allow using these apps in Settings.
```
These instructions were verified against iOS 15.1.
These instructions were verified against iOS 15.6.
A) Open the Settings app and select "General" > "VPN & Device Management".
@ -1027,17 +1043,19 @@ This demo was tested in the following environments:
| OS | Device | RN | Date |
|:-----------|:------------------|:---------|:-----------|
| iOS 15.5 | iPhone 13 Pro Max | `0.73.6` | 2024-03-31 |
| Android 29 | NVIDIA Shield | `0.73.6` | 2024-03-31 |
| iOS 15.6 | iPhone 13 Pro Max | `0.76.5` | 2025-01-05 |
| Android 34 | NVIDIA Shield | `0.76.5` | 2025-01-05 |
**Simulators**
| OS | Device | RN | Dev Platform | Date |
|:-----------|:------------------|:---------|:-------------|:-----------|
| Android 34 | Pixel 3a | `0.73.6` | `darwin-x64` | 2024-03-31 |
| iOS 17.4 | iPhone 15 Pro Max | `0.73.6` | `darwin-x64` | 2024-03-31 |
| Android 35 | Pixel 9 | `0.76.5` | `win11-x64` | 2024-03-31 |
| Android 34 | Pixel 3a | `0.73.6` | `linux-x64` | 2024-03-31 |
| Android 34 | Pixel 3a | `0.76.5` | `darwin-x64` | 2024-12-31 |
| iOS 18.2 | iPhone 16 Pro | `0.76.5` | `darwin-x64` | 2024-12-31 |
| Android 34 | Pixel 3a | `0.76.5` | `darwin-arm` | 2025-01-05 |
| iOS 18.2 | iPhone 16 Pro | `0.76.5` | `darwin-arm` | 2025-01-05 |
| Android 35 | Pixel 9 | `0.76.5` | `win11-x64` | 2024-12-22 |
| Android 35 | Pixel 9 | `0.76.5` | `linux-x64` | 2025-01-02 |
:::
@ -1060,7 +1078,7 @@ This example tries to separate the library-specific functions.
npx -y @react-native-community/cli@15 init SheetJSRN --version="0.76.5"
```
On macOS, if prompted to install `CocoaPods`, press `y`.
On macOS, if prompted to install `CocoaPods`, press <kbd>Y</kbd>
2) Install shared dependencies:

@ -66,6 +66,7 @@ This demo was tested in the following environments:
| Android 34 | Pixel 3a | `8.7.2` | `darwin-arm` | 2024-06-09 |
| iOS 17.5 | iPhone SE (3rd gen) | `8.7.2` | `darwin-arm` | 2024-06-09 |
| Android 35 | Pixel 9 | `8.8.3` | `win11-x64` | 2024-12-21 |
| Android 35 | Pixel 9 | `8.8.3` | `linux-x64` | 2025-01-02 |
:::

@ -56,6 +56,7 @@ This demo was tested in the following environments:
| Android 34 | Pixel 3a | `6.0.0` / `6.0.0` | `darwin-arm` | 2024-06-02 |
| iOS 17.5 | iPhone 15 Pro Max | `6.0.0` / `6.0.0` | `darwin-arm` | 2024-06-02 |
| Android 35 | Pixel 9 | `6.2.0` / `6.0.2` | `win11-x64` | 2024-12-21 |
| Android 35 | Pixel 9 | `6.2.0` / `6.0.2` | `linux-x64` | 2025-01-02 |
:::

@ -188,11 +188,11 @@ This demo was tested in the following environments:
| OS and Version | Architecture | Electron | Date |
|:---------------|:-------------|:---------|:-----------|
| macOS 14.4 | `darwin-x64` | `29.1.4` | 2024-03-15 |
| macOS 15.2 | `darwin-x64` | `33.2.1` | 2024-12-31 |
| macOS 14.5 | `darwin-arm` | `30.0.8` | 2024-05-28 |
| Windows 11 | `win11-x64` | `31.2.0` | 2024-08-18 |
| Windows 11 | `win11-arm` | `30.0.8` | 2024-05-28 |
| Linux (HoloOS) | `linux-x64` | `29.1.4` | 2024-03-21 |
| Linux (HoloOS) | `linux-x64` | `33.2.1` | 2025-01-02 |
| Linux (Debian) | `linux-arm` | `30.0.8` | 2024-05-28 |
:::
@ -321,8 +321,8 @@ and select `pres.numbers`.
## Electron Breaking Changes
The first version of this demo used Electron 1.7.5. The current demo includes
the required changes for Electron 30.0.8.
The first version of this demo used Electron `1.7.5`. The current demo includes
the required changes for Electron `33.2.1`.
There are no Electron-specific workarounds in the library, but Electron broke
backwards compatibility multiple times. A summary of changes is noted below.
@ -336,6 +336,7 @@ methods have been renamed:
|:-----------------|:---------------------|
| `showOpenDialog` | `showOpenDialogSync` |
| `showSaveDialog` | `showSaveDialogSync` |
**This change was not properly documented!**
Electron 9 and later require the preference `nodeIntegration: true` in order to

@ -113,7 +113,7 @@ This demo was tested in the following environments:
| OS and Version | Architecture | NW.js | Date | Notes |
|:---------------|:-------------|:---------|:-----------|:---------------------|
| macOS 14.3.1 | `darwin-x64` | `0.85.0` | 2024-03-12 | |
| macOS 15.2 | `darwin-x64` | `0.94.0` | 2024-12-31 | |
| macOS 14.5 | `darwin-arm` | `0.88.0` | 2024-05-28 | |
| Windows 11 | `win11-x64` | `0.94.0` | 2024-12-19 | |
| Windows 11 | `win11-arm` | `0.88.0` | 2024-05-28 | |
@ -198,6 +198,7 @@ testing, version `4.11.6` correctly generated the standalone application.
| Architecture | Command |
|:-------------|:--------------------------------------------------------------|
| `darwin-x64` | `open ../out/sheetjs-nwjs.app` |
| `linux-x64` | `../out/sheetjs-nwjs` |
| `win11-x64` | `..\out\sheetjs-nwjs.exe` |

@ -297,11 +297,11 @@ This demo was tested in the following environments:
| OS and Version | Architecture | Wails | Date |
|:---------------|:-------------|:---------|:-----------|
| macOS 14.4 | `darwin-x64` | `v2.8.0` | 2024-03-15 |
| macOS 15.2 | `darwin-x64` | `v2.9.2` | 2024-12-31 |
| macOS 14.5 | `darwin-arm` | `v2.8.2` | 2024-05-28 |
| Windows 11 | `win11-x64` | `v2.9.2` | 2024-12-21 |
| Windows 11 | `win11-arm` | `v2.8.2` | 2024-05-28 |
| Linux (HoloOS) | `linux-x64` | `v2.8.0` | 2024-03-21 |
| Linux (HoloOS) | `linux-x64` | `v2.9.2` | 2025-01-02 |
| Linux (Debian) | `linux-arm` | `v2.8.2` | 2024-05-28 |
:::
@ -358,7 +358,7 @@ None of the optional packages are required for building and running this demo.
On the Steam Deck (HoloOS), some dependencies must be reinstalled:
```bash
sudo pacman -Syu base-devel gtk3 glib2 pango harfbuzz cairo gdk-pixbuf2 atk libsoup
sudo pacman -Syu base-devel gtk3 glib2 pango harfbuzz cairo gdk-pixbuf2 atk libsoup webkit2gtk
```
:::

@ -14,6 +14,7 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
export const c = {style: {color:"cyan"}};
export const y = {style: {color:"gold"}};
export const g = {style: {color:"green"}};
export const B = {style: {fontWeight:"bold"}};
@ -352,11 +353,11 @@ This demo was tested in the following environments:
| OS and Version | Architecture | Tauri | Date |
|:---------------|:-------------|:----------|:-----------|
| macOS 14.4 | `darwin-x64` | `v1.5.11` | 2024-04-20 |
| macOS 15.2 | `darwin-x64` | `v1.6.0` | 2024-12-31 |
| macOS 14.5 | `darwin-arm` | `v1.5.14` | 2024-05-26 |
| Windows 11 | `win11-x64` | `v1.6.0` | 2024-12-21 |
| Windows 11 | `win11-arm` | `v1.5.14` | 2024-05-28 |
| Linux (HoloOS) | `linux-x64` | `v1.5.11` | 2024-03-21 |
| Linux (HoloOS) | `linux-x64` | `v1.6.0` | 2025-01-02 |
| Linux (Debian) | `linux-arm` | `v1.5.14` | 2024-05-28 |
:::
@ -383,15 +384,16 @@ If required dependencies are installed, the output will show a checkmark next to
<pre>
<span {...g}>[✔]</span> <span style={{...y.style,...B.style}}>Environment</span>
{` `}<span {...g}>-</span> <span {...B}>OS</span>: Mac OS 14.4.1 X64
{` `}<span {...c}>-</span> <span {...B}>OS</span>: Mac OS 15.2.0 x86_64 (X64)
{` `}<span {...g}></span> <span {...B}>Xcode Command Line Tools</span>: installed
{` `}<span {...g}></span> <span {...B}>rustc</span>: 1.77.2 (25ef9e3d8 2024-04-09)
{` `}<span {...g}></span> <span {...B}>cargo</span>: 1.77.2 (e52e36006 2024-03-26)
{` `}<span {...g}></span> <span {...B}>rustup</span>: 1.27.0 (bbb9276d2 2024-03-08)
{` `}<span {...g}></span> <span {...B}>rustc</span>: 1.83.0 (90b35a623 2024-11-26)
{` `}<span {...g}></span> <span {...B}>cargo</span>: 1.83.0 (5ffbef321 2024-10-29)
{` `}<span {...g}></span> <span {...B}>rustup</span>: 1.27.1 (54dd3d00f 2024-04-24)
{` `}<span {...g}></span> <span {...B}>Rust toolchain</span>: stable-x86_64-apple-darwin (default)
{` `}<span {...g}>-</span> <span {...B}>node</span>: 20.12.1
{` `}<span {...g}>-</span> <span {...B}>npm</span>: 10.5.0
{` `}<span {...g}>-</span> <span {...B}>bun</span>: 1.1.4
{` `}<span {...c}>-</span> <span {...B}>node</span>: 20.18.0
{` `}<span {...c}>-</span> <span {...B}>npm</span>: 10.8.2
{` `}<span {...c}>-</span> <span {...B}>bun</span>: 1.1.42
{` `}<span {...c}>-</span> <span {...B}>deno</span>: deno 2.1.4
</pre>
:::caution pass

@ -192,11 +192,11 @@ This demo was tested in the following environments:
| OS and Version | Architecture | Server | Client | Date |
|:---------------|:-------------|:---------|:---------|:-----------|
| macOS 14.4 | `darwin-x64` | `5.0.0` | `5.0.1` | 2024-03-15 |
| macOS 15.2 | `darwin-x64` | `5.5.0` | `5.5.0` | 2024-12-31 |
| macOS 14.5 | `darwin-arm` | `5.1.0` | `5.1.0` | 2024-05-25 |
| Windows 11 | `win11-x64` | `5.5.0` | `5.5.0` | 2024-12-20 |
| Windows 11 | `win11-arm` | `5.1.0` | `5.1.1` | 2024-05-28 |
| Linux (HoloOS) | `linux-x64` | `5.0.0` | `5.0.1` | 2024-03-21 |
| Linux (HoloOS) | `linux-x64` | `5.5.0` | `5.5.0` | 2025-01-02 |
| Linux (Debian) | `linux-arm` | `5.1.0` | `5.1.1` | 2024-05-28 |
NeutralinoJS on Windows on ARM generates X64 binaries that run using the X64
@ -211,7 +211,7 @@ the window. Writing files will parse the table into a spreadsheet.
<summary><b>Installation Notes</b> (click to show)</summary>
NeutralinoJS uses `portable-file-dialogs`[^12] to show open and save dialogs. On
Linux, Zenity or KDialog are require.
Linux, a dialog box helper (Zenity or KDialog) must be installed.
The last Debian test was run on a system using LXDE. KDialog is supported but
must be explicitly installed:
@ -413,10 +413,12 @@ Platform-specific programs will be created in the `dist` folder:
| Platform | Path to binary |
|:-------------|:---------------------------------------------|
| `darwin-x64` | `./dist/sheetjs-neu/sheetjs-neu-mac_x64` |
| `darwin-arm` | `./dist/sheetjs-neu/sheetjs-neu-mac_arm64` |
| `win11-x64` | `.\dist\sheetjs-neu\sheetjs-neu-win_x64.exe` |
| `win11-arm` | `.\dist\sheetjs-neu\sheetjs-neu-win_x64.exe` |
| `linux-arm` | `.\dist\sheetjs-neu\sheetjs-neu-linux_arm64` |
| `linux-x64` | `./dist/sheetjs-neu/sheetjs-neu-linux_x64` |
| `linux-arm` | `./dist/sheetjs-neu/sheetjs-neu-linux_arm64` |
Run the generated app and confirm that Presidential data is displayed.

@ -41,7 +41,7 @@ This demo was tested in the following deployments:
| `darwin-arm` | `4.0.0-rc.6` | `18.20.3` | Compiled | 2024-05-25 |
| `win11-x64` | `4.0.0-rc.6` | `14.15.3` | Pre-built | 2024-12-19 |
| `win11-arm` | `4.0.0-rc.6` | `20.10.0` | Compiled | 2024-05-28 |
| `linux-x64` | `4.0.0-rc.4` | `14.15.3` | Pre-built | 2024-03-21 |
| `linux-x64` | `4.0.0-rc.6` | `14.15.3` | Pre-built | 2024-12-31 |
| `linux-arm` | `4.0.0-rc.6` | `18.20.3` | Compiled | 2024-05-26 |
:::

@ -41,7 +41,7 @@ This demo was tested in the following deployments:
| `darwin-arm` | `5.8.1` | `18.5.0` | 2024-05-25 |
| `win11-x64` | `5.8.1` | `18.5.0` | 2024-12-19 |
| `win11-arm` | `5.8.1` | `18.5.0` | 2024-10-25 |
| `linux-x64` | `5.8.1` | `18.5.0` | 2024-03-21 |
| `linux-x64` | `5.8.1` | `18.5.0` | 2024-12-31 |
| `linux-arm` | `5.8.1` | `18.5.0` | 2024-05-26 |
:::

@ -33,7 +33,7 @@ This demo was tested in the following deployments:
| `darwin-x64` | `2.4.0` | `22.2.0` | 2024-05-28 |
| `darwin-arm` | `2.4.3` | `22.2.0` | 2024-05-25 |
| `win11-x64` | `2.4.4` | `16.20.2` | 2024-12-19 |
| `linux-x64` | `2.4.0` | `21.7.1` | 2024-03-21 |
| `linux-x64` | `2.4.4` | `23.5.0` | 2024-12-31 |
| `linux-arm` | `2.4.3` | `20.13.1` | 2024-05-26 |
:::

@ -162,7 +162,7 @@ This demo was tested in the following deployments:
| `darwin-arm` | `22.2.0` | 2024-05-29 |
| `win11-x64` | `20.13.1` | 2024-05-22 |
| `win11-arm` | `20.14.0` | 2024-06-11 |
| `linux-x64` | `20.11.1` | 2024-03-18 |
| `linux-x64` | `22.12.0` | 2025-01-02 |
| `linux-arm` | `20.14.0` | 2024-06-10 |
:::

@ -103,7 +103,7 @@ This demo was last tested in the following deployments:
| `darwin-arm` | `1.43.6` | 2024-05-23 |
| `win11-x64` | `1.43.6` | 2024-05-25 |
| `win11-arm` | `2.0.3` | 2024-10-25 |
| `linux-x64` | `1.41.3` | 2024-03-18 |
| `linux-x64` | `2.1.4` | 2025-01-02 |
| `linux-arm` | `1.43.6` | 2024-05-25 |
:::

@ -36,9 +36,12 @@ This demo was tested in the following environments:
| Postgres | Connector Library | Date |
|:---------|:------------------|:-----------|
| `16.6.1` | `pg` (`8.13.1`) | 2024-12-03 |
| `15.6` | `pg` (`8.11.4`) | 2024-03-31 |
| `14.11` | `pg` (`8.11.4`) | 2024-03-31 |
| `17.2` | `pg` (`8.13.1`) | 2025-01-03 |
| `16.6` | `pg` (`8.13.1`) | 2025-01-03 |
| `15.10` | `pg` (`8.13.1`) | 2025-01-03 |
| `14.15` | `pg` (`8.13.1`) | 2025-01-03 |
| `13.18` | `pg` (`8.13.1`) | 2025-01-03 |
| `12.22` | `pg` (`8.13.1`) | 2025-01-03 |
:::
@ -144,125 +147,125 @@ The `sheet_to_pg_table` function:
```js
/* create table and load data given a worksheet and a PostgreSQL client */
async function sheet_to_pg_table(client, worksheet, tableName) {
if (!worksheet['!ref']) return;
if (!worksheet['!ref']) return;
const range = XLSX.utils.decode_range(worksheet['!ref']);
const range = XLSX.utils.decode_range(worksheet['!ref']);
/* Extract headers from first row, clean names for PostgreSQL */
const headers = [];
/* Extract headers from first row, clean names for PostgreSQL */
const headers = [];
for (let col = range.s.c; col <= range.e.c; col++) {
const cellAddress = XLSX.utils.encode_cell({ r: range.s.r, c: col });
const cell = worksheet[cellAddress];
const headerValue = cell ? String(cell.v).replace(/[^a-zA-Z0-9_]/g, '_') : `column_${col + 1}`;
headers.push(headerValue.toLowerCase());
}
/* Group cell values by column for type deduction */
const columnValues = headers.map(() => []);
for (let row = range.s.r + 1; row <= range.e.r; row++) {
for (let col = range.s.c; col <= range.e.c; col++) {
const cellAddress = XLSX.utils.encode_cell({ r: range.s.r, c: col });
const cell = worksheet[cellAddress];
const headerValue = cell ? String(cell.v).replace(/[^a-zA-Z0-9_]/g, '_') : `column_${col + 1}`;
headers.push(headerValue.toLowerCase());
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });
const cell = worksheet[cellAddress];
columnValues[col].push(cell);
}
}
/* Group cell values by column for type deduction */
const columnValues = headers.map(() => []);
for (let row = range.s.r + 1; row <= range.e.r; row++) {
for (let col = range.s.c; col <= range.e.c; col++) {
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });
const cell = worksheet[cellAddress];
columnValues[col].push(cell);
}
}
/* Deduce PostgreSQL type for each column */
const types = {};
headers.forEach((header, idx) => {
types[header] = deduceType(columnValues[idx]);
});
/* Deduce PostgreSQL type for each column */
const types = {};
headers.forEach((header, idx) => {
types[header] = deduceType(columnValues[idx]);
/* Delete table if it exists in the DB */
await client.query(format('DROP TABLE IF EXISTS %I', tableName));
/* Create table */
const createTableSQL = format(
'CREATE TABLE %I (%s)',
tableName,
headers.map(header => format('%I %s', header, types[header])).join(', ')
);
await client.query(createTableSQL);
/* Insert data row by row */
for (let row = range.s.r + 1; row <= range.e.r; row++) {
const values = headers.map((header, col) => {
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });
const cell = worksheet[cellAddress];
return parseValue(cell, types[header]);
});
/* Delete table if it exists in the DB */
await client.query(format('DROP TABLE IF EXISTS %I', tableName));
/* Create table */
const createTableSQL = format(
'CREATE TABLE %I (%s)',
tableName,
headers.map(header => format('%I %s', header, types[header])).join(', ')
const insertSQL = format(
'INSERT INTO %I (%s) VALUES (%s)',
tableName,
headers.map(h => format('%I', h)).join(', '),
values.map(() => '%L').join(', ')
);
await client.query(createTableSQL);
/* Insert data row by row */
for (let row = range.s.r + 1; row <= range.e.r; row++) {
const values = headers.map((header, col) => {
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });
const cell = worksheet[cellAddress];
return parseValue(cell, types[header]);
});
const insertSQL = format(
'INSERT INTO %I (%s) VALUES (%s)',
tableName,
headers.map(h => format('%I', h)).join(', '),
values.map(() => '%L').join(', ')
);
await client.query(format(insertSQL, ...values));
}
await client.query(format(insertSQL, ...values));
}
}
function deduceType(cells) {
if (!cells || cells.length === 0) return 'text';
if (!cells || cells.length === 0) return 'text';
const nonEmptyCells = cells.filter(cell => cell && cell.v != null);
if (nonEmptyCells.length === 0) return 'text';
const nonEmptyCells = cells.filter(cell => cell && cell.v != null);
if (nonEmptyCells.length === 0) return 'text';
// Check for dates by looking at both cell type and formatted value
const isDateCell = cell => cell?.t === 'd' || (cell?.t === 'n' && cell.w && /\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{4}|\d{2}-[A-Za-z]{3}-\d{4}|[A-Za-z]{3}-\d{2}|\d{1,2}-[A-Za-z]{3}/.test(cell.w));
// Check for dates by looking at both cell type and formatted value
const isDateCell = cell => cell?.t === 'd' || (cell?.t === 'n' && cell.w && /\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{4}|\d{2}-[A-Za-z]{3}-\d{4}|[A-Za-z]{3}-\d{2}|\d{1,2}-[A-Za-z]{3}/.test(cell.w));
if (nonEmptyCells.some(isDateCell)) { return 'date'; }
if (nonEmptyCells.some(isDateCell)) { return 'date'; }
const allBooleans = nonEmptyCells.every(cell => cell.t === 'b');
if (allBooleans) { return 'boolean'; }
const allBooleans = nonEmptyCells.every(cell => cell.t === 'b');
if (allBooleans) { return 'boolean'; }
const allNumbers = nonEmptyCells.every(cell => cell.t === 'n' || (cell.t === 's' && !isNaN(cell.v.replace(/[,$\s%()]/g, ''))));
const allNumbers = nonEmptyCells.every(cell => cell.t === 'n' || (cell.t === 's' && !isNaN(cell.v.replace(/[,$\s%()]/g, ''))));
if (allNumbers) {
const numbers = nonEmptyCells.map(cell => {
if (cell.t === 'n') return cell.v;
return parseFloat(cell.v.replace(/[,$\s%()]/g, ''));
});
if (allNumbers) {
const numbers = nonEmptyCells.map(cell => {
if (cell.t === 'n') return cell.v;
return parseFloat(cell.v.replace(/[,$\s%()]/g, ''));
});
const needsPrecision = numbers.some(num => {
const str = num.toString();
return str.includes('e') ||
(str.includes('.') && str.split('.')[1].length > 6) ||
Math.abs(num) > 1e15;
});
const needsPrecision = numbers.some(num => {
const str = num.toString();
return str.includes('e')
|| (str.includes('.') && str.split('.')[1].length > 6)
|| Math.abs(num) > 1e15;
});
return needsPrecision ? 'numeric' : 'double precision';
}
return 'text';
return needsPrecision ? 'numeric' : 'double precision';
}
return 'text';
}
function parseValue(cell, type) {
if (!cell || cell.v == null) return null;
if (!cell || cell.v == null) return null;
switch (type) {
case 'date':
if (cell.t === 'd') { return cell.v.toISOString().split('T')[0]; }
if (cell.t === 'n') {
const date = new Date((cell.v - 25569) * 86400 * 1000);
return date.toISOString().split('T')[0];
}
return null;
switch (type) {
case 'date':
if (cell.t === 'd') { return cell.v.toISOString().split('T')[0]; }
if (cell.t === 'n') {
const date = new Date((cell.v - 25569) * 86400 * 1000);
return date.toISOString().split('T')[0];
}
return null;
case 'numeric':
case 'double precision':
if (cell.t === 'n') return cell.v;
if (cell.t === 's') {
const cleaned = cell.v.replace(/[,$\s%()]/g, '');
if (!isNaN(cleaned)) return parseFloat(cleaned);
}
return null;
case 'numeric':
case 'double precision':
if (cell.t === 'n') return cell.v;
if (cell.t === 's') {
const cleaned = cell.v.replace(/[,$\s%()]/g, '');
if (!isNaN(cleaned)) return parseFloat(cleaned);
}
return null;
case 'boolean':
return cell.t === 'b' ? cell.v : null;
case 'boolean':
return cell.t === 'b' ? cell.v : null;
default:
return String(cell.v);
}
default:
return String(cell.v);
}
}
```
@ -271,7 +274,7 @@ function parseValue(cell, type) {
## Complete Example
0) Install and start the PostgreSQL server.
0) Install and start the PostgreSQL serve r.
<details>
<summary><b>Installation Notes</b> (click to show)</summary>

@ -1,6 +1,6 @@
---
title: Sheets with MongoDB
sidebar_label: MongoDB / FerretDB
title: Sheets with FerretDB and MongoDB
sidebar_label: FerretDB / MongoDB
pagination_prev: demos/cli/index
pagination_next: demos/local/index
sidebar_custom_props:
@ -11,7 +11,8 @@ import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[MongoDB](https://mongodb.github.io/node-mongodb-native/) is a document-oriented
database engine.
database engine. [FerretDB](https://www.ferretdb.com/) is a truly open source
implementation of the MongoDB wire protocol
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
@ -24,11 +25,11 @@ to add data from spreadsheets into a collection.
This demo was tested in the following environments:
| Server | Connector Library | Date |
|:--------------------|:--------------------|:-----------|
| FerretDB `1.21.0` | `mongodb` (`5.9.2`) | 2024-03-30 |
| MongoDB CE `6.0.15` | `mongodb` (`6.5.0`) | 2024-05-01 |
| MongoDB CE `7.0.8` | `mongodb` (`6.5.0`) | 2024-05-01 |
| Server | Connector Library | Date |
|:--------------------|:---------------------|:-----------|
| FerretDB `1.24.0` | `mongodb` (`6.12.0`) | 2025-01-03 |
| MongoDB CE `6.0.15` | `mongodb` (`6.5.0`) | 2024-05-01 |
| MongoDB CE `7.0.8` | `mongodb` (`6.5.0`) | 2024-05-01 |
:::
@ -123,13 +124,69 @@ If Homebrew is configured to use `/opt/homebrew`, the command is:
</details>
<details>
<summary><b>FerretDB Setup</b> (click to show)</summary>
The official documentation recommends Docker, but it is strongly recommended to
use [`colima`](https://github.com/abiosoft/colima) on MacOS:
```bash
brew install colima docker docker-compose
```
To properly install `docker-compose`, the `colima` process must be run once:
```bash
/opt/homebrew/opt/colima/bin/colima start -f
```
After stopping the process, delete any containers:
```bash
/opt/homebrew/opt/colima/bin/colima delete
```
`config.json` must be edited:
- Homebrew docker plugins folder must be added to `cliPluginsExtraDirs`.
- All `credsStore` settings must be removed.
When the demo was last tested, the following `config.json` was used:
```json title="~/.docker/config.json"
{
"auths": {},
"currentContext": "colima",
/* highlight-start */
"cliPluginsExtraDirs": [
"/opt/homebrew/lib/docker/cli-plugins"
]
/* highlight-end */
}
```
After making the changes, start `colima` again and keep the window open:
```bash
/opt/homebrew/opt/colima/bin/colima start -f
```
Start the FerretDB server in a new terminal window:
```bash
cd /tmp
docker run -d --rm --name ferretdb -p 27017:27017 -e FERRETDB_HANDLER=sqlite ghcr.io/ferretdb/all-in-one
```
</details>
2) Create base project and install the dependencies:
<CodeBlock language="bash">{`\
mkdir sheetjs-mongo
cd sheetjs-mongo
npm init -y
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz mongodb@6.5.0`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz mongodb@6.12.0`}
</CodeBlock>
3) Save the following to `SheetJSMongoCRUD.mjs` (the key step is highlighted):

@ -20,8 +20,8 @@ Each browser demo was tested in the following environments:
| Browser | Date |
|:------------|:-----------|
| Chrome 122 | 2024-03-25 |
| Safari 17.3 | 2024-03-12 |
| Chrome 131 | 2024-12-23 |
| Safari 18.2 | 2024-12-31 |
:::
@ -48,8 +48,8 @@ strings using `JSON.stringify` and store using the row index as a key:
#### Importing Data
Starting from a worksheet, `XLSX.utils.sheet_to_json` generates an array of row
objects. `localStorage.setItem` will store data in Local Storage:
Starting from a worksheet, the SheetJS `sheet_to_json` method[^1] generates an
array of row objects. `localStorage.setItem` will store data in Local Storage:
```js
function sheet_to_localStorage(worksheet) {
@ -65,9 +65,35 @@ function sheet_to_localStorage(worksheet) {
`localStorage.length` returns the total number of entries. A simple `for` loop
can cover the keys (integers from `0` to `localStorage.length - 1` inclusive)
`localStorage.getItem` will load the stringified data from the Local Storage. A
new array of objects can be constructed by using `JSON.parse` and pushing to an
array. `XLSX.utils.json_to_sheet` can create a new worksheet from that array:
`localStorage.getItem` will load the stringified data from the Local Storage.
The following function collects data from `localStorage` to an array of strings:
```js
function localStorage_to_array_of_strings() {
const strings = [];
for(let i = 0; i < localStorage.length; ++i) {
aoo.push(localStorage.getItem(i));
}
return strings;
}
```
Since each entry is a string created using `JSON.stringify`, an object can be
constructed using `JSON.parse`:
```js
function localStorage_to_array_of_objects() {
const objects = [];
for(let i = 0; i < localStorage.length; ++i) {
aoo.push(JSON.parse(localStorage.getItem(i)));
}
return objects;
}
```
The SheetJS `json_to_sheet`[^2] method will create a new worksheet from the
array of objects:
```js
function localStorage_to_sheet() {
@ -173,7 +199,8 @@ objects approach has an ordering. That does not apply to the general case.
:::
In modern browsers, `Object.entries` will generate an array of key/value pairs.
`XLSX.utils.aoa_to_sheet` will interpret that array as a worksheet with 2 cols:
The SheetJS `aoa_to_sheet`[^3] method will interpret that array as a worksheet
with 2 columns (key and value):
```js
function localStorage_to_ws() {
@ -185,8 +212,8 @@ function localStorage_to_ws() {
#### Importing Storage
In the other direction, the worksheet is assumed to store keys in column A and
values in column B. `XLSX.utils.sheet_to_json` with the `header: 1` option
will generate key/value pairs that can be assigned to a storage:
values in column B. The SheetJS `sheet_to_json`[^1] method, with the option
`header: 1`, will generate key/value pairs that can be assigned to a storage:
```js
function ws_to_localStorage(ws) {
@ -239,4 +266,8 @@ function SheetJSRandomStorage() {
}
```
</details>
</details>
[^1]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^2]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
[^3]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)

@ -8,7 +8,7 @@ sidebar_custom_props:
<head>
<script type="text/javascript" src="https://unpkg.com/localforage@1.10.0/dist/localforage.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/dexie@3.2.4/dist/dexie.js"></script>
<script type="text/javascript" src="https://unpkg.com/dexie@4.0.10/dist/dexie.js"></script>
</head>
:::danger pass
@ -43,8 +43,8 @@ This demo was last tested in the following environments:
| Browser | Date | `localForage` |
|:------------|:-----------|:--------------|
| Chrome 122 | 2024-03-21 | 1.10.0 |
| Safari 17.4 | 2024-03-23 | 1.10.0 |
| Chrome 131 | 2024-12-31 | `1.10.0` |
| Safari 18.2 | 2024-12-31 | `1.10.0` |
:::
@ -113,9 +113,10 @@ function SheetJSLocalForage() {
This demo was last tested in the following environments:
| Browser | Date | DexieJS |
|:------------|:-----------|:--------|
| Chrome 122 | 2024-03-21 | 3.2.4 |
| Browser | Date | DexieJS |
|:------------|:-----------|:---------|
| Chrome 131 | 2024-12-31 | `4.0.10` |
| Safari 18.2 | 2024-12-31 | `4.0.10` |
:::

@ -29,7 +29,11 @@ may require some adjustments. The official documentation should be consulted.
:::note Tested Deployments
This demo was last tested on 2024 October 06 using Lightning API version `61.0`.
This demo was tested in the following deployments:
| Lightning API | Date |
|:--------------|:-----------|
| `61.0` | 2024-10-06 |
:::

@ -10,11 +10,12 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
:::note pass
:::info pass
This demo focuses on external data processing. For Google Apps Script custom
functions, [the "Google Sheets" extension demo](/docs/demos/extensions/gsheet)
covers Apps Script integration.
This demo focuses on external data processing.
[The "Google Sheets" extension demo](/docs/demos/extensions/gsheet) covers Apps
Script integration and user-defined functions.
:::

@ -45,10 +45,10 @@ This demo was verified in the following deployments:
| App | Platform | Date |
|:----------|:-------------|:-----------|
| Photoshop | ExtendScript | 2024-03-12 |
| InDesign | ExtendScript | 2024-08-12 |
| InDesign | CEP | 2024-03-12 |
| InDesign | UXP | 2024-03-11 |
| Photoshop | ExtendScript | 2025-01-03 |
| InDesign | ExtendScript | 2025-01-03 |
| InDesign | CEP | 2025-01-03 |
| InDesign | UXP | 2025-01-03 |
:::
@ -112,15 +112,20 @@ author (`activeDocument.info.author`) will be changed accordingly.
<li><a href={`https://docs.sheetjs.com/extendscript/parse.jsx`}><code>parse.jsx</code></a></li>
</ul>
2) Restart Photoshop and open a file (or create a new one)
2) Restart Photoshop.
3) File > Scripts > parse and select the test workbook
3) Create a new file using the default settings.
4) An alert will confirm that the file was read and the author will be changed:
4) In the menu bar, select File > Scripts > parse and select the test workbook
!["Changing Author" popup](pathname:///files/psparse.png)
An alert will confirm that the file was read and the author will be changed:
5) Check the Author field of the document in File > File Info...
!["Changing Author" popup](pathname:///extendscript/psparse.png)
5) In the menu bar, select File > File Info… and select "Basic" in the popup.
The text box next to "Author:" will show `Sh33tJ5`.
!["Author" property in Photoshop](pathname:///extendscript/psauthor.png)
</TabItem>
<TabItem value="indesign" label="InDesign">
@ -133,17 +138,17 @@ the Layers window is "Title") will be set to the name of the first worksheet.
- The data from the first sheet will be added to the "Table Frame" TextFrame.
0) Download the [test workbook](https://docs.sheetjs.com/pres.xlsx) and
[InDesign template](pathname:///extendscript/Template.idml)
0) Download the [`pres.xlsx` test workbook](https://docs.sheetjs.com/pres.xlsx)
and [`Template.idml` InDesign template](pathname:///extendscript/Template.idml)
1) Download the following scripts and move to the scripts directory[^4]:
1) Download the following scripts and move to the scripts directory[^3]:
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.extendscript.js`}><code>xlsx.extendscript.js</code></a></li>
<li><a href={`https://docs.sheetjs.com/extendscript/esidparse.jsx`}><code>esidparse.jsx</code></a></li>
</ul>
2) Open the template
2) Open `Template.idml` in InDesign.
3) Activate the Scripts panel. Expand the "User" folder and double-click
`esidparse` in the list.
@ -187,25 +192,31 @@ selected, the library will create a new workbook with one worksheet. Cell `A1`
will be "Author" and cell `B1` will be the active Photoshop document Author.
The PS author is available as `activeDocument.info.author`.
1) Download the following scripts and move to the scripts directory[^6]:
1) Download the following scripts and move to the scripts directory[^3]:
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.extendscript.js`}><code>xlsx.extendscript.js</code></a></li>
<li><a href={`https://docs.sheetjs.com/extendscript/write.jsx`}><code>write.jsx</code></a></li>
</ul>
2) Restart Photoshop and open a file (or create a new one)
2) Restart Photoshop.
3) File > File Info ... and confirm there is an Author. If not, set to `SheetJS`
3) Create a new file using the default settings.
4) File > Scripts > write and use the popup to select the Documents folder.
Enter `SheetJSPSTest.xlsx` and press "Save"
4) In the menu bar, select File > File Info… and add an Author if it is blank.
5) An alert will confirm that the file was created:
5) In the menu bar, select File > Scripts > write.
!["Created File" popup](pathname:///files/pswrite.png)
Select the Documents folder, enter the file name `SheetJSPSTest.xlsx` and
press "Save".
6) Open the generated `SheetJSPSTest.xlsx` file and compare to Photoshop author
6) Click "OK" in the "Created File" alert:
!["Created File" popup](pathname:///extendscript/pswrite.png)
7) Open the generated `SheetJSPSTest.xlsx` file.
Cell `A1` will be "Author" and cell `B1` will match the Photoshop file author.
</TabItem>
<TabItem value="indesign" label="InDesign">
@ -214,16 +225,16 @@ In this example, the script will show a dialog to select an output file. Once
selected, the library will scan all text frames for table objects. Each table
object will be scanned and a new worksheet will be created.
0) Download the [InDesign document](pathname:///extendscript/Filled.idml)
0) Download the [`Filled.idml` document](pathname:///extendscript/Filled.idml)
1) Download the following scripts and move to the scripts directory[^7]:
1) Download the following scripts and move to the scripts directory[^3]:
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.extendscript.js`}><code>xlsx.extendscript.js</code></a></li>
<li><a href={`https://docs.sheetjs.com/extendscript/esidwrite.jsx`}><code>esidwrite.jsx</code></a></li>
</ul>
2) Open the document.
2) Open `Filled.idml` in InDesign.
3) Activate the Scripts panel. Expand the "User" folder and double-click
`esidwrite` in the list. Use the popup to select the Documents folder. Enter
@ -253,6 +264,8 @@ manifest must include the following flags to enable `cep.fs`:
</CEFCommandLine>
```
#### Unsigned Extensions
:::caution pass
With newer versions of Creative Cloud apps, a special player debug mode must be
@ -318,9 +331,16 @@ If prompted, give administrator privileges.
4) Show the extension (in the menu bar, select Window > Extensions > SheetJS)
:::caution pass
If the extension panel is blank, unsigned extensions must be enabled. See the
["Unsigned Extensions" note](#unsigned-extensions) for more details.
:::
5) In the extension panel, click "Import from file" and select `pres.xlsx`
After "success" popup, the first worksheet should be written to the file.
After "success" popup, the first worksheet should be written to the document.
</TabItem>
</Tabs>
@ -358,7 +378,7 @@ cep.fs.writeFile(fn.data, b64, cep.encoding.Base64);
0) Download [`com.sheetjs.data.zip`](pathname:///extendscript/com.sheetjs.data.zip)
and extract to a `com.sheetjs.data` subdirectory.
1) Move the entire `com.sheetjs.data` folder to the CEP extensions folder[^13]:
1) Move the entire `com.sheetjs.data` folder to the CEP extensions folder[^10]:
If prompted, give administrator privileges.
@ -366,9 +386,17 @@ If prompted, give administrator privileges.
3) Show the extension (in the menu bar, select Window > Extensions > SheetJS)
:::caution pass
If the extension panel is blank, unsigned extensions must be enabled. See the
["Unsigned Extensions" note](#unsigned-extensions) for more details.
:::
4) In the extension panel, click "Export to XLSX" and "Save" in the dialog.
5) A popup will display the path to the generated file. Open the new file.
5) A popup will display the path to the generated file (`SheetJSIDCEP.xlsx` in
the Documents folder). Open the new file.
</TabItem>
</Tabs>
@ -398,7 +426,7 @@ const storage = UXP.storage, ufs = storage.localFileSystem;
The `getFileForOpening` method resolves to a `File` object. Reading the file
with the `binary` format returns an `ArrayBuffer` object that can be parsed
with the SheetJS `read` method[^14]:
with the SheetJS `read` method[^8]:
```js
/* show file picker (single file, no folders) */
@ -415,7 +443,7 @@ const wb = XLSX.read(ab);
<Tabs groupId="ccapp">
<TabItem value="indesign" label="InDesign">
0) Open the "Scripts Panel" folder[^15].
0) Open the "Scripts Panel" folder[^3].
1) Download the following scripts:
@ -450,7 +478,7 @@ If the InDesign version does not support UXP, a tooltip shows a message:
### Writing Files
The SheetJS `write` method[^16], with the option `type: "buffer"`[^17], returns
The SheetJS `write` method[^11], with the option `type: "buffer"`[^12], returns
file data stored in a `Uint8Array`.
The `getFileForSaving` method resolves to a `File` object. The `write` method
@ -474,7 +502,7 @@ await file.write(buf, { data: storage.formats.binary });
<Tabs groupId="ccapp">
<TabItem value="indesign" label="InDesign">
0) Open the "Scripts Panel" folder[^18].
0) Open the "Scripts Panel" folder[^3].
1) Download the following scripts:
@ -501,7 +529,9 @@ Move them to the Scripts Panel folder.
### Scripts Panel
The scripts panel folder is used for ExtendScript and UXP scripts. The location
can be revealed from the relevant applications. For InDesign:
can be revealed from the relevant applications.
**InDesign**
1) Activate Scripts panel (Windows > Utilities > Scripts)
@ -518,6 +548,13 @@ Some versions of InDesign will open the parent "Scripts" folder. If there is a
:::
**Photoshop**
The scripts folder is located in `\Presets\Scripts` within the app folder.
For example, on Windows, the Photoshop 2025 scripts folder is typically
`c:\Program Files\Adobe\Adobe Photoshop 2025\Presets\Scripts`
### CEP Extensions
CEP extension scripts are typically stored in a system-wide folder:
@ -532,18 +569,9 @@ Administrator privileges are usually required for writing to the folder.
[^1]: Historically, Adobe applications were separate entities. Eventually they were bundled in a package called "Creative Suite". It was rebranded to "Creative Cloud" later. As ExtendScript was introduced during the Creative Suite era, this page will use the phrase "Creative Suite".
[^2]: See [`readFile` in "Reading Files"](/docs/api/parse-options)
[^3]: See ["Scripts Panel"](#scripts-panel)
[^4]: See ["Scripts Panel"](#scripts-panel)
[^5]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
[^6]: See ["Scripts Panel"](#scripts-panel)
[^7]: See ["Scripts Panel"](#scripts-panel)
[^8]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^9]: See [the "base64" type in "Reading Files"](/docs/api/parse-options#input-type)
[^10]: See ["CEP Extensions"](#cep-extensions)
[^11]: See [`write` in "Writing Files"](/docs/api/write-options)
[^12]: See ["Supported Output Formats" type in "Writing Files"](/docs/api/write-options#supported-output-formats)
[^13]: See ["CEP Extensions"](#cep-extensions)
[^14]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^15]: See ["Scripts Panel"](#scripts-panel)
[^16]: See [`write` in "Writing Files"](/docs/api/write-options)
[^17]: See ["Supported Output Formats" type in "Writing Files"](/docs/api/write-options#supported-output-formats)
[^18]: See ["Scripts Panel"](#scripts-panel)

@ -21,7 +21,11 @@ tables with a content script and a background script.
:::note Tested Deployments
This demo was last tested on 2024 March 30 against Chrome 122.
This demo was tested in the following deployments:
| Platform | Date |
|:------------|:-----------|
| Chrome 131 | 2025-01-02 |
:::
@ -383,6 +387,14 @@ chrome.bookmarks.getTree(function(res) {
### Table Exporter
:::caution pass
**Due to security restrictions, V2 table export does not work in Chrome 131!**
The Manifest V3 table exporter does work in Chrome 131.
:::
<details open>
<summary><b>Testing</b> (click to hide)</summary>

@ -35,7 +35,12 @@ data into the worksheet.
:::note Tested Deployments
This demo was last tested on 2024 August 11 against Excel 365 (version 2407).
This demo was tested in the following deployments:
| OS and Version | Architecture | Excel | Date |
|:---------------|:-------------|:-----------|:-----------|
| macOS 14.5 | `darwin-arm` | 16.81 | 2024-12-22 |
| Windows 11 | `win11-x64` | 365 (2407) | 2024-08-11 |
:::
@ -125,7 +130,7 @@ export async function extern(url) {
## Complete Demo
0) Clear the functions cache. For the tested version of Excel:
0) Clear the functions cache. For the tested version of Excel for Windows:
- Open File Explorer
- Select the address bar and enter `%LOCALAPPDATA%\Microsoft\Office\16.0\Wef`
@ -143,7 +148,7 @@ after testing is finished.
1) Install [NodeJS LTS](https://nodejs.org/en/download/).
2) After installing NodeJS, launch a new PowerShell window.
2) Launch a new PowerShell (Windows) or Terminal (MacOS) window.
3) Disable telemetry:
@ -220,9 +225,10 @@ element with name `bt:String`. Change the `DefaultValue` attribute to `SHEETJS`:
8) Close the Excel window and the terminal window. Do not save the XLSX file.
9) In the PowerShell window, start the development process again:
9) In the terminal window, start the development process again:
```bash
npm run stop
npm start
```
@ -251,9 +257,10 @@ export function version() {
12) Close the terminal window and the Excel window. Do not save the Excel file.
13) In the PowerShell window, start the development process again:
13) In a new terminal window, start the development process again:
```bash
npm run stop
npm start
```
@ -264,6 +271,33 @@ npm start
This indicates that the SheetJS library has been loaded.
:::info pass
In some MacOS tests, the add-in installed `CONTOSO.VERSION`. To force a refresh
of the manifest:
1) Stop the development process:
```bash
npm run stop
```
2) Close the Excel app by right-clicking Excel in the Dock and selecting "Quit".
3) Restart the development process:
```bash
npm start
```
4) Activate the Task Pane for the addin (click "Show Task Pane" in the ribbon).
5) Hover near the top-right corner of the addin and click the `i` icon.
6) Click "Clear Web Cache" and wait a few moments.
:::
### Fetching Files from the Internet
15) Add the following code snippet to `src\functions\functions.js` and save:
@ -296,9 +330,10 @@ export async function extern(url) {
16) Close the terminal window and the Excel window (do not save the Excel file).
17) In the PowerShell window, start the development process again:
17) In a new terminal window, start the development process again:
```bash
npm run stop
npm start
```

@ -10,17 +10,20 @@ sidebar_custom_props:
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
:::note pass
:::info pass
This demo focuses on Google Apps Script custom functions. For external data
processing, [the "Google Sheets" cloud data demo](/docs/demos/cloud/gsheet)
covers the API for NodeJS scripts
This demo focuses on Google Apps Script custom functions.
[The "Google Sheets" cloud data demo](/docs/demos/cloud/gsheet) covers NodeJS
APIs for external data processing.
:::
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
can be uploaded into an Apps Script project. Once uploaded, the `XLSX` variable
is available to other scripts in the project.
[Google Sheets](https://google.com/sheets/about/) is a collaborative spreadsheet
service with powerful JavaScript automation and user-defined functions.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
Google Sheets currently does not provide support for working with Apple Numbers
files and some legacy file formats. SheetJS fills the gap.
@ -32,12 +35,20 @@ remote file, parses the contents, and writes data to the sheet:
:::note Tested Deployments
This demo was last tested on 2024 March 11.
This demo was tested in the following deployments:
| Clasp | Date |
|:--------|:-----------|
| `2.4.2` | 2024-12-31 |
:::
## Integration Details
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
can be uploaded into an Apps Script project. Once uploaded, the `XLSX` variable
is available to other scripts in the project.
### Adding the script
The `clasp` command line tool can be used to upload the standalone script:
@ -66,24 +77,55 @@ const response = UrlFetchApp.fetch("https://docs.sheetjs.com/pres.numbers");
const content = response.getContent();
```
The `"array"` type for `XLSX.read` expects an array of *unsigned* bytes.
Fortunately, the content can be corrected with bitwise operations:
The SheetJS `read` method[^1] can read arrays of *unsigned* bytes. Fortunately,
the values in the array can be corrected with bitwise operations:
```js
for(var i = 0; i < content.length; ++i) content[i] &= 0xFF;
```
After converting each signed byte to unsigned byte, the array can be parsed with
the `read` method:
```js
const wb = XLSX.read(content, { type: "array" });
```
### Returning data
`XLSX.utils.sheet_to_json` with the option `header: 1` returns arrays of arrays
that play nice with Google Sheets:
The SheetJS `sheet_to_json` method[^2] with the option `header: 1` returns
arrays of arrays of data[^3]:
```js
const first_worksheet = wb.Sheets[wb.SheetNames[0]];
const aoa = XLSX.utils.sheet_to_json(first_worksheet, {header: 1});
```
Google Sheets will spread arrays of arrays across rows and columns. The `AOA`
function below returns an array that contains two arrays. The screenshot shows
the result of setting cell `A1` to the formula `=AOA()`:
<table>
<thead><tr><th>Custom Function</th><th>Google Sheets</th></tr></thead>
<tbody><tr><td>
```js
function AOA(url) {
return [
["Sheet", "JS"],
[ 72, 62]
];
}
```
</td><td>
![Google Sheets result for AOA function](pathname:///gsheet/aoa-udf.png)
</td></tr></tbody>
</table>
## Complete Demo
This demo creates a function `SHEETJS(url)` that fetches the specified URL,
@ -99,14 +141,33 @@ extracts data from the first worksheet, and writes the data
npx @google/clasp login
```
A browser window should direct to an account selection page. Select the account
from the previous step. In the next page, there will be a title like
A browser window should direct to an account selection page.
> clasp The Apps Script CLI wants to access your Google Account
2) Select the account from step 0.
At the bottom of the screen, click "Allow".
The next page will include the following title:
The terminal window should now state
> Sign in to clasp The Apps Script CLI
![clasp sign-in](pathname:///gsheet/clasp-signin.png)
3) At the bottom of the screen, click "Continue".
4) In the next screen, check every box that mentions "Google Apps Script". When
the demo was last tested, the following were required:
- Create and update Google Apps Script deployments.
- Create and update Google Apps Script projects.
![clasp permissions](pathname:///gsheet/clasp-perms.png)
5) Scroll to the bottom of the screen and click "Continue".
The browser will show the following message:
> Logged in! You may close this page.
The terminal window will show the following message:
```
Authorization successful.
@ -114,22 +175,26 @@ Authorization successful.
### Creating a Sheet
2) Sign into Google Sheets with the same account and create a new blank sheet
6) Sign into Google Sheets with the same account.
3) Open up the apps script window (Extensions > Apps Script)
7) Create a new Blank spreadsheet.
4) Click the gear icon (Project Settings) and copy the Script ID
8) Open the apps script window (Extensions > Apps Script)
![extensions - apps script](pathname:///gsheet/apps-script.png)
9) Click the gear icon (Project Settings) and copy the Script ID
### Cloning the Apps Script
5) In the terminal window, create a new folder for your project:
10) In the terminal window, create a new folder for your project:
```bash
mkdir SheetJSGAS
cd SheetJSGAS
```
6) Clone the Apps Script project. The official command is:
11) Clone the Apps Script project. The official command is:
```bash
npx @google/clasp clone PASTE_YOUR_ID_HERE
@ -141,7 +206,7 @@ and paste in the terminal. Press Enter after pasting the ID.
### Adding the SheetJS Library
7) Download the SheetJS Standalone script and move to the project directory:
12) Download the SheetJS Standalone script and move to the project directory:
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>xlsx.full.min.js</a></li>
@ -151,7 +216,7 @@ and paste in the terminal. Press Enter after pasting the ID.
curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}
</CodeBlock>
8) Push the project to Apps Script:
13) Push the project to Apps Script:
```bash
npx @google/clasp push
@ -185,13 +250,15 @@ After enabling the API, run `npx @google/clasp push` again.
:::
9) Reopen the Google Sheet and Apps Script editor (Extensions > Apps Script).
14) Reopen the Google Sheet and Apps Script editor (Extensions > Apps Script).
In the Files listing, there should be a new entry `xlsx.full.min.gs`
![xlsx.full.min.gs in Apps Script](pathname:///gsheet/xlsx-full-min-gs.png)
### Creating a Custom Function
10) In Apps Script editor, select `Code.gs` and erase the code in the editor.
15) In Apps Script editor, select `Code.gs` and erase the code in the editor.
Replace with the following function:
```js title="Code.gs"
@ -213,12 +280,16 @@ function SHEETJS(url) {
}
```
Click the "Save Project" icon (💾) to save the project.
Click the "Save project to Drive" icon (💾) to save the project.
11) In the Google Sheets window, select cell A1 and enter the formula
16) In the Google Sheets window, select cell A1 and enter the formula
```
=SHEETJS("https://docs.sheetjs.com/pres.numbers")
```
The file will be fetched and the contents will be written to the sheet.
[^1]: See ["Input Type" in "Reading Files"](/docs/api/parse-options#input-type) for more details.
[^2]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^3]: See ["Array of Arrays" in "Utilities"](/docs/api/utilities/array#array-of-arrays) for more details.

@ -34,10 +34,10 @@ flowchart LR
This demo was tested by SheetJS users in the following deployments:
| Architecture | Version | Date |
|:-------------|:--------|:-----------|
| `darwin-x64` | 2024 | 2024-04-25 |
| `win11-x64` | 2024 | 2024-12-19 |
| Architecture | Version | Date |
|:-------------|:---------|:-----------|
| `darwin-x64` | `2024.0` | 2024-04-25 |
| `win11-x64` | `2024.0` | 2024-12-19 |
:::
@ -145,7 +145,10 @@ cd sheetjs-maple
```
2) Copy the headers and `lib` files from the Maple folder to the project folder.
For example, using Maple 2024 on Windows x64:
The headers will be placed in the `extern/include/` folder in the Maple folder.
The `lib` files are placed in a platform-specific `bin` folder.
The following commands apply to x64 versions of Windows:
```powershell
copy "C:\Program Files\Maple 2024\extern\include\"*.h .

@ -193,11 +193,11 @@ Each browser demo was tested in the following environments:
| Browser | Date | Comments |
|:------------|:-----------|:----------------------------------------|
| Chrome 122 | 2024-04-25 | |
| Edge 122 | 2024-03-12 | |
| Safari 17.3 | 2024-03-12 | File System Access API is not supported |
| Brave 1.59 | 2024-03-12 | File System Access API is not supported |
| Firefox 122 | 2024-03-12 | File System Access API is not supported |
| Chrome 131 | 2024-12-31 | |
| Edge 131 | 2024-12-31 | |
| Safari 17.5 | 2024-12-31 | File System Access API is not supported |
| Brave 1.63 | 2024-12-31 | File System Access API is not supported |
| Firefox 133 | 2024-12-31 | File System Access API is not supported |
:::

@ -128,11 +128,11 @@ This demo was tested in the following deployments:
| Architecture | Version | Date |
|:-------------|:--------|:-----------|
| `darwin-x64` | `2.7.0` | 2024-04-04 |
| `darwin-x64` | `2.7.0` | 2024-12-31 |
| `darwin-arm` | `2.7.0` | 2024-05-23 |
| `win11-x64` | `2.7.0` | 2024-12-20 |
| `win11-arm` | `2.7.0` | 2024-05-25 |
| `linux-x64` | `2.7.0` | 2024-03-21 |
| `linux-x64` | `2.7.0` | 2024-12-31 |
| `linux-arm` | `2.7.0` | 2024-05-23 |
:::
@ -404,9 +404,9 @@ This demo was tested in the following deployments:
| Architecture | Version | PHP | Date |
|:-------------|:--------|:---------|:-----------|
| `darwin-x64` | `2.7.0` | `8.3.4` | 2024-03-15 |
| `darwin-x64` | `2.7.0` | `8.4.2` | 2024-12-31 |
| `darwin-arm` | `2.7.0` | `8.3.8` | 2024-06-30 |
| `linux-x64` | `2.7.0` | `8.2.7` | 2024-03-21 |
| `linux-x64` | `2.7.0` | `8.3.3` | 2024-12-31 |
| `linux-arm` | `2.7.0` | `8.2.18` | 2024-05-25 |
:::
@ -419,14 +419,14 @@ This demo was tested in the following deployments:
php --ini
```
The following output is from the last macOS test:
The following output is from the most recent `darwin-x64` test:
```text pass
Configuration File (php.ini) Path: /usr/local/etc/php/8.3
Configuration File (php.ini) Path: /usr/local/etc/php/8.4
// highlight-next-line
Loaded Configuration File: /usr/local/etc/php/8.3/php.ini
Scan for additional .ini files in: /usr/local/etc/php/8.3/conf.d
Additional .ini files parsed: /usr/local/etc/php/8.3/conf.d/ext-opcache.ini
Loaded Configuration File: /usr/local/etc/php/8.4/php.ini
Scan for additional .ini files in: /usr/local/etc/php/8.4/conf.d
Additional .ini files parsed: /usr/local/etc/php/8.4/conf.d/ext-opcache.ini
```
2) Edit the `php.ini` configuration file.
@ -546,14 +546,14 @@ This demo was tested in the following deployments:
| Architecture | Version | Python | Date |
|:-------------|:--------|:---------|:-----------|
| `darwin-x64` | `2.7.0` | `3.12.2` | 2024-03-15 |
| `darwin-x64` | `2.7.0` | `3.13.1` | 2024-12-31 |
| `darwin-arm` | `2.7.0` | `3.12.3` | 2024-06-30 |
| `linux-x64` | `2.7.0` | `3.11.3` | 2024-03-21 |
| `linux-x64` | `2.7.0` | `3.11.7` | 2024-12-31 |
| `linux-arm` | `2.7.0` | `3.11.2` | 2024-05-25 |
:::
0) Ensure `python` is installed and available on the system path.
0) Ensure `python3` is installed and available on the system path.
1) Build the Duktape shared library:
@ -779,11 +779,11 @@ This demo was tested in the following deployments:
| Architecture | Version | Zig | Date |
|:-------------|:--------|:---------|:-----------|
| `darwin-x64` | `2.7.0` | `0.11.0` | 2024-03-10 |
| `darwin-x64` | `2.7.0` | `0.13.0` | 2024-12-31 |
| `darwin-arm` | `2.7.0` | `0.12.0` | 2024-05-23 |
| `win11-x64` | `2.7.0` | `0.13.0` | 2024-12-20 |
| `win11-arm` | `2.7.0` | `0.12.0` | 2024-05-25 |
| `linux-x64` | `2.7.0` | `0.12.0` | 2024-04-25 |
| `linux-x64` | `2.7.0` | `0.13.0` | 2024-12-31 |
| `linux-arm` | `2.7.0` | `0.12.0` | 2024-05-25 |
On Windows, due to incompatibilities between WSL and PowerShell, some commands
@ -1014,15 +1014,26 @@ This demo was tested in the following deployments:
| Architecture | Version | Date |
|:-------------|:--------|:-----------|
| `darwin-x64` | `2.2.0` | 2024-03-15 |
| `darwin-x64` | `2.2.0` | 2024-12-31 |
| `darwin-arm` | `2.2.0` | 2024-06-30 |
| `linux-x64` | `2.2.0` | 2024-03-21 |
| `linux-x64` | `2.2.0` | 2024-12-31 |
| `linux-arm` | `2.2.0` | 2024-05-25 |
:::
0) Ensure `perl` and `cpan` are installed and available on the system path.
:::caution pass
On Arch Linux and HoloOS (Steam Deck), `crypt.h` may be missing. The `libxcrypt`
package must be explicitly installed:
```bash
sudo pacman -S libxcrypt
```
:::
1) Install the `JavaScript::Duktape::XS` library:
```bash

@ -149,7 +149,7 @@ This demo was tested in the following deployments:
| `13.3.228` | `darwin-x64` | macOS 15.1.1 | `clang 16.0.0` | 2024-12-03 |
| `12.7.130` | `darwin-arm` | macOS 14.5 | `clang 15.0.0` | 2024-05-25 |
| `12.7.130` | `win11-x64` | Windows 11 | `CL 19.42.34435` | 2024-12-20 |
| `12.5.48` | `linux-x64` | HoloOS 3.5.17 | `gcc 13.1.1` | 2024-03-21 |
| `12.7.130` | `linux-x64` | HoloOS 3.6.20 | `gcc 13.2.1` | 2025-01-02 |
| `12.7.130` | `linux-arm` | Debian 12 | `gcc 12.2.0` | 2024-05-25 |
:::
@ -965,7 +965,7 @@ This demo was last tested in the following deployments:
|:-------------|:----------|:-----------|
| `darwin-x64` | `0.92.0` | 2024-05-28 |
| `darwin-arm` | `0.92.0` | 2024-05-25 |
| `win11-x64` | `130.0.2` | 2024-03-24 |
| `win11-x64` | `130.0.2` | 2024-12-20 |
| `linux-x64` | `0.91.0` | 2024-04-25 |
| `linux-arm` | `0.92.0` | 2024-05-25 |
@ -1499,7 +1499,7 @@ This demo was last tested in the following deployments:
| `darwin-x64` | `12.6.228.3` | `0.92.0` | 2024-05-28 |
| `darwin-arm` | `12.6.228.3` | `0.92.0` | 2024-12-20 |
| `win11-x64` | `12.6.228.3` | `0.92.0` | 2024-12-20 |
| `linux-x64` | `12.3.219.9` | `0.88.0` | 2024-03-18 |
| `linux-x64` | `12.6.228.3` | `0.92.0` | 2025-01-02 |
| `linux-arm` | `12.6.228.3` | `0.92.0` | 2024-05-26 |
:::

@ -109,7 +109,7 @@ This demo was tested in the following deployments:
| `darwin-arm` | `ccbae20` | `1.22.3` | 2024-05-23 |
| `win11-x64` | `79f3a7e` | `1.23.4` | 2024-12-20 |
| `win11-arm` | `ccbae20` | `1.22.3` | 2024-05-25 |
| `linux-x64` | `e401ed4` | `1.22.1` | 2024-03-21 |
| `linux-x64` | `79f3a7e` | `1.22.0` | 2025-01-02 |
| `linux-arm` | `ccbae20` | `1.19.8` | 2024-05-25 |
At the time of writing, Goja did not have proper version numbers. Versions are

@ -366,7 +366,7 @@ This demo was tested in the following deployments:
|:-------------|:-----------|:-----------|
| `darwin-x64` | `d070c74` | 2024-12-17 |
| `darwin-arm` | `d070c74` | 2024-05-23 |
| `linux-x64` | `d217af8` | 2024-03-21 |
| `linux-x64` | `388376f` | 2024-12-17 |
| `linux-arm` | `d070c74` | 2024-05-25 |
The main Hermes source tree does not have Windows support. The `hermes-windows`
@ -479,10 +479,17 @@ In some tests, the build failed with a message referencing a missing header:
hermes/API/hermes/inspector/chrome/tests/SerialExecutor.cpp:34:16: note: std::runtime_error is defined in header <stdexcept>; did you forget to #include <stdexcept>?
```
```
hermes/lib/SerialExecutor/SerialExecutor.cpp:9:1: note: std::runtime_error is defined in header <stdexcept>; did you forget to #include <stdexcept>?
```
**This error affects the official Hermes releases!**
The fix is to manually add a `#include` statement in the corresponding header
file (`API/hermes/inspector/chrome/tests/SerialExecutor.h` in the repo):
The `#include` statement should be added in the corresponding header file. In
previous tests, the following files elicited errors:
- `hermes/API/hermes/inspector/chrome/tests/SerialExecutor.cpp` errors are fixed
by patching `API/hermes/inspector/chrome/tests/SerialExecutor.h` in the repo:
```c title="hermes/API/hermes/inspector/chrome/tests/SerialExecutor.h (add highlighted line)"
#include <memory>
@ -495,6 +502,26 @@ file (`API/hermes/inspector/chrome/tests/SerialExecutor.h` in the repo):
#include <thread>
```
- `hermes/lib/SerialExecutor/SerialExecutor.cpp` errors are fixed by patching
`include/hermes/SerialExecutor/SerialExecutor.h` in the repo:
```c title="hermes/include/hermes/SerialExecutor/SerialExecutor.h (add highlighted line)"
#include <memory>
#include <mutex>
#if !defined(_WINDOWS) && !defined(__EMSCRIPTEN__)
// highlight-next-line
#include <stdexcept>
#include <pthread.h>
#else
#include <thread>
```
After making the required patch, the build can be resumed by running `cmake`:
```bash
cmake --build ./build_release`
```
:::
5) Build the application:
@ -776,6 +803,7 @@ This demo was tested in the following deployments:
|:-------------|:---------|:-----------|
| `darwin-x64` | `0.13.0` | 2024-12-17 |
| `win11-x64` | `0.13.0` | 2024-12-20 |
| `linux-x64` | `0.13.0` | 2024-12-31 |
:::

@ -91,7 +91,7 @@ This demo was tested in the following deployments:
| `darwin-arm` | `2.6.10` | `2.9.1` | 2024-05-25 |
| `win11-x64` | `3.3.6` | `2.10.0` | 2024-12-20 |
| `win11-arm` | `3.0.2` | `2.9.1` | 2024-05-25 |
| `linux-x64` | `3.0.5` | `2.9.1` | 2024-03-21 |
| `linux-x64` | `3.0.6` | `2.10.0` | 2024-12-31 |
| `linux-arm` | `3.1.2` | `2.9.1` | 2024-05-25 |
When the demo was last tested, there was no official Ruby release for Windows

@ -124,7 +124,7 @@ This demo was tested in the following deployments:
| `darwin-arm` | `0.18.0` | 2024-05-23 |
| `win11-x64` | `0.20.0` | 2024-12-19 |
| `win11-arm` | `0.18.0` | 2024-05-25 |
| `linux-x64` | `0.18.0` | 2024-03-21 |
| `linux-x64` | `0.20.0` | 2024-12-31 |
| `linux-arm` | `0.18.0` | 2024-05-25 |
:::

@ -40,7 +40,7 @@ This demo was tested in the following environments:
| `darwin-arm` | `35465ed` | 2024-05-25 |
| `win11-x64` | `d2d30df` | 2024-12-19 |
| `win11-arm` | `35465ed` | 2024-05-25 |
| `linux-x64` | `cefd391` | 2024-03-21 |
| `linux-x64` | `d2d30df` | 2024-12-31 |
| `linux-arm` | `35465ed` | 2024-05-25 |
The Windows tests were run in WSL.

@ -367,7 +367,7 @@ This,is,a,Test
The test suite is regularly run against a number of modern and legacy browsers
using [Sauce Labs](https://saucelabs.com/).
The following chart shows test results on 2024 October 20 for version 0.20.3:
The following chart shows test results on 2024-10-20 for version `0.20.3`:
[![Build Status](pathname:///test/sheetjs.svg)](https://saucelabs.com/u/sheetjs)

@ -1,9 +1,12 @@
/* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */
/* eslint-env node */
/* vim: set ts=2 ft=javascript: */
var n = "xlsx";
var X = require('xlsx');
try { require('exit-on-epipe'); } catch(e) {}
try { require('exit-on-epipe'); } catch (e) {}
var fs = require('fs'), program;
try { program = require('commander'); } catch(e) {
try { program = require('commander'); } catch (e) {
[
"The `xlsx` command line tool is deprecated in favor of `xlsx-cli`.",
"",
@ -13,9 +16,10 @@ try { program = require('commander'); } catch(e) {
"For older versions of node, explicitly install `xlsx-cli` globally:",
" $ npm i -g xlsx-cli",
" $ xlsx-cli --help"
].forEach(function(m) { console.error(m); });
].forEach(function (m) { console.error(m); });
process.exit(1);
}
program
.version(X.version)
.usage('[options] <file> [sheetname]')
@ -64,6 +68,7 @@ program
.option('--date-format <string>', 'output date format, for example yyyy-mm-dd')
.option('--codepage <cp>', 'default to specified codepage when ambiguous')
.option('--sst', 'generate shared string table for XLS* formats')
.option('-d, --no-dim', 'recalculate worksheet range')
.option('--compress', 'use compression when writing XLSX/M/B and ODS')
.option('--read', 'read but do not generate output')
.option('--book', 'for single-sheet formats, emit a file per worksheet')
@ -72,10 +77,7 @@ program
.option('--sparse', 'sparse mode')
.option('-q, --quiet', 'quiet mode');
program.on('--help', function() {
console.log(' Default output format is CSV');
console.log(' Support email: dev@sheetjs.com');
console.log(' Web Demo: https://oss.sheetjs.com/js-'+n+'/');
program.on('--help', function () {
});
/* flag, bookType, default ext */
@ -98,49 +100,50 @@ var wb_formats_2 = [
program.parse(process.argv);
var filename = '', sheetname = '';
if(program.args[0]) {
if (program.args[0]) {
filename = program.args[0];
if(program.args[1]) sheetname = program.args[1];
if (program.args[1]) sheetname = program.args[1];
}
if(program.sheet) sheetname = program.sheet;
if(program.file) filename = program.file;
if (program.sheet) sheetname = program.sheet;
if (program.file) filename = program.file;
if(!filename) {
if (!filename) {
console.error(n + ": must specify a filename");
process.exit(1);
}
if(!fs.existsSync(filename)) {
if (!fs.existsSync(filename)) {
console.error(n + ": " + filename + ": No such file or directory");
process.exit(2);
}
var opts = {}, wb/*:?Workbook*/;
if(program.listSheets) opts.bookSheets = true;
if(program.sheetRows) opts.sheetRows = program.sheetRows;
if(program.password) opts.password = program.password;
if (program.listSheets) opts.bookSheets = true;
if (program.sheetRows) opts.sheetRows = program.sheetRows;
if (program.password) opts.password = program.password;
if (program.dim != null) opts.nodim = !program.dim;
var seen = false;
function wb_fmt() {
seen = true;
opts.cellFormula = true;
opts.cellNF = true;
opts.xlfn = true;
if(program.output) sheetname = program.output;
if (program.output) sheetname = program.output;
}
function isfmt(m/*:string*/)/*:boolean*/ {
if(!program.output) return false;
if (!program.output) return false;
var t = m.charAt(0) === "." ? m : "." + m;
return program.output.slice(-t.length) === t;
}
workbook_formats.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) { wb_fmt(); } });
wb_formats_2.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) { wb_fmt(); } });
if(seen) {
} else if(program.formulae) opts.cellFormula = true;
workbook_formats.forEach(function (m) { if (program[m[0]] || isfmt(m[0])) { wb_fmt(); } });
wb_formats_2.forEach(function (m) { if (program[m[0]] || isfmt(m[0])) { wb_fmt(); } });
if (seen) {
} else if (program.formulae) opts.cellFormula = true;
else opts.cellFormula = false;
var wopts = ({WTF:opts.WTF, bookSST:program.sst}/*:any*/);
if(program.compress) wopts.compression = true;
var wopts = ({ WTF: opts.WTF, bookSST: program.sst }/*:any*/);
if (program.compress) wopts.compression = true;
if(program.all) {
if (program.all) {
opts.cellFormula = true;
opts.bookVBA = true;
opts.cellNF = true;
@ -153,73 +156,76 @@ if(program.all) {
wopts.sheetStubs = true;
wopts.bookVBA = true;
}
if(program.sparse) opts.dense = false; else opts.dense = true;
if(program.codepage) opts.codepage = +program.codepage;
if(program.dateFormat) opts.dateNF = program.dateFormat;
if (program.sparse) opts.dense = false; else opts.dense = true;
if (program.codepage) opts.codepage = +program.codepage;
if (program.dateFormat) opts.dateNF = program.dateFormat;
if(program.dev) {
if (program.dev) {
opts.WTF = true;
wb = X.readFile(filename, opts);
} else try {
wb = X.readFile(filename, opts);
} catch(e) {
} catch (e) {
var msg = (program.quiet) ? "" : n + ": error parsing ";
msg += filename + ": " + e;
console.error(msg);
process.exit(3);
}
if(program.read) process.exit(0);
if(!wb) { console.error(n + ": error parsing " + filename + ": empty workbook"); process.exit(0); }
/*:: if(!wb) throw new Error("unreachable"); */
if(program.listSheets) {
console.log((wb.SheetNames||[]).join("\n"));
if (program.read) process.exit(0);
if (!wb) { console.error(n + ": error parsing " + filename + ": empty workbook"); process.exit(0); }
/*:: if (!wb) throw new Error("unreachable"); */
if (program.listSheets) {
console.log((wb.SheetNames || []).join("\n"));
process.exit(0);
}
if(program.dump) {
if (program.dump) {
console.log(JSON.stringify(wb));
process.exit(0);
}
if(program.props) {
if(wb) dump_props(wb);
if (program.props) {
if (wb) dump_props(wb);
process.exit(0);
}
/* full workbook formats */
workbook_formats.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) {
workbook_formats.forEach(function (m) {
if (program[m[0]] || isfmt(m[0])) {
wopts.bookType = m[1];
if(wopts.bookType == "numbers") try {
if (wopts.bookType == "numbers") try {
var XLSX_ZAHL = require("xlsx/dist/xlsx.zahl");
wopts.numbers = XLSX_ZAHL;
} catch(e) {}
if(wb) X.writeFile(wb, program.output || sheetname || ((filename || "") + "." + m[2]), wopts);
} catch (e) {}
if (wb) X.writeFile(wb, program.output || sheetname || ((filename || "") + "." + m[2]), wopts);
process.exit(0);
} });
wb_formats_2.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) {
wb_formats_2.forEach(function (m) {
if (program[m[0]] || isfmt(m[0])) {
wopts.bookType = m[1];
if(wb) X.writeFile(wb, program.output || sheetname || ((filename || "") + "." + m[2]), wopts);
if (wb) X.writeFile(wb, program.output || sheetname || ((filename || "") + "." + m[2]), wopts);
process.exit(0);
} });
}
});
var target_sheet = sheetname || '';
if(target_sheet === '') {
if(+program.sheetIndex < (wb.SheetNames||[]).length) target_sheet = wb.SheetNames[+program.sheetIndex];
else target_sheet = (wb.SheetNames||[""])[0];
if (target_sheet === '') {
if (+program.sheetIndex < (wb.SheetNames || []).length) target_sheet = wb.SheetNames[+program.sheetIndex];
else target_sheet = (wb.SheetNames || [""])[0];
}
var ws;
try {
ws = wb.Sheets[target_sheet];
if(!ws) {
if (!ws) {
console.error("Sheet " + target_sheet + " cannot be found");
process.exit(3);
}
} catch(e) {
console.error(n + ": error parsing "+filename+" "+target_sheet+": " + e);
} catch (e) {
console.error(n + ": error parsing " + filename + " " + target_sheet + ": " + e);
process.exit(4);
}
if(!program.quiet && !program.book) console.error(target_sheet);
if (!program.quiet && !program.book) console.error(target_sheet);
/* single worksheet file formats */
[
@ -235,33 +241,35 @@ if(!program.quiet && !program.book) console.error(target_sheet);
['dbf', '.dbf'],
['wk1', '.wk1'],
['dif', '.dif']
].forEach(function(m) { if(program[m[0]] || isfmt(m[1])) {
].forEach(function (m) {
if (program[m[0]] || isfmt(m[1])) {
wopts.bookType = m[0];
if(program.book) {
/*:: if(wb == null) throw new Error("Unreachable"); */
wb.SheetNames.forEach(function(n, i) {
if (program.book) {
/*:: if (wb == null) throw new Error("Unreachable"); */
wb.SheetNames.forEach(function (n, i) {
wopts.sheet = n;
X.writeFile(wb, (program.output || sheetname || filename || "") + m[1] + "." + i, wopts);
});
} else X.writeFile(wb, program.output || sheetname || ((filename || "") + m[1]), wopts);
process.exit(0);
} });
}
});
function outit(o, fn) { if(fn) fs.writeFileSync(fn, o); else console.log(o); }
function outit(o, fn) { if (fn) fs.writeFileSync(fn, o); else console.log(o); }
function doit(cb) {
/*:: if(!wb) throw new Error("unreachable"); */
if(program.book) wb.SheetNames.forEach(function(n, i) {
/*:: if(!wb) throw new Error("unreachable"); */
/*:: if (!wb) throw new Error("unreachable"); */
if (program.book) wb.SheetNames.forEach(function (n, i) {
/*:: if (!wb) throw new Error("unreachable"); */
outit(cb(wb.Sheets[n]), (program.output || sheetname || filename) + "." + i);
});
else outit(cb(ws), program.output);
}
var jso = {};
switch(true) {
switch (true) {
case program.formulae:
doit(function(ws) { return X.utils.sheet_to_formulae(ws).join("\n"); });
doit(function (ws) { return X.utils.sheet_to_formulae(ws).join("\n"); });
break;
case program.arrays: jso.header = 1;
@ -269,33 +277,33 @@ switch(true) {
case program.rawJs: jso.raw = true;
/* falls through */
case program.json:
doit(function(ws) { return JSON.stringify(X.utils.sheet_to_json(ws,jso)); });
doit(function (ws) { return JSON.stringify(X.utils.sheet_to_json(ws, jso)); });
break;
default:
if(!program.book) {
var stream = X.stream.to_csv(ws, {FS:program.fieldSep||",", RS:program.rowSep||"\n"});
if(program.output) stream.pipe(fs.createWriteStream(program.output));
if (!program.book) {
var stream = X.stream.to_csv(ws, { FS: program.fieldSep || ",", RS: program.rowSep || "\n" });
if (program.output) stream.pipe(fs.createWriteStream(program.output));
else stream.pipe(process.stdout);
} else doit(function(ws) { return X.utils.sheet_to_csv(ws,{FS:program.fieldSep, RS:program.rowSep}); });
} else doit(function (ws) { return X.utils.sheet_to_csv(ws, { FS: program.fieldSep, RS: program.rowSep }); });
break;
}
function dump_props(wb/*:Workbook*/) {
var propaoa = [];
if(Object.assign && Object.entries) propaoa = Object.entries(Object.assign({}, wb.Props, wb.Custprops));
if (Object.assign && Object.entries) propaoa = Object.entries(Object.assign({}, wb.Props, wb.Custprops));
else {
var Keys/*:: :Array<string> = []*/, pi;
if(wb.Props) {
if (wb.Props) {
Keys = Object.keys(wb.Props);
for(pi = 0; pi < Keys.length; ++pi) {
if(Object.prototype.hasOwnProperty.call(Keys, Keys[pi])) propaoa.push([Keys[pi], Keys[/*::+*/Keys[pi]]]);
for (pi = 0; pi < Keys.length; ++pi) {
if (Object.prototype.hasOwnProperty.call(Keys, Keys[pi])) propaoa.push([Keys[pi], Keys[/*::+*/Keys[pi]]]);
}
}
if(wb.Custprops) {
if (wb.Custprops) {
Keys = Object.keys(wb.Custprops);
for(pi = 0; pi < Keys.length; ++pi) {
if(Object.prototype.hasOwnProperty.call(Keys, Keys[pi])) propaoa.push([Keys[pi], Keys[/*::+*/Keys[pi]]]);
for (pi = 0; pi < Keys.length; ++pi) {
if (Object.prototype.hasOwnProperty.call(Keys, Keys[pi])) propaoa.push([Keys[pi], Keys[/*::+*/Keys[pi]]]);
}
}
}

@ -15,12 +15,12 @@
"make": "electron-forge make"
},
"devDependencies": {
"@electron-forge/cli": "7.4.0",
"@electron-forge/maker-deb": "7.4.0",
"@electron-forge/maker-rpm": "7.4.0",
"@electron-forge/maker-squirrel": "7.4.0",
"@electron-forge/maker-zip": "7.4.0",
"electron": "31.2.0"
"@electron-forge/cli": "7.6.0",
"@electron-forge/maker-deb": "7.6.0",
"@electron-forge/maker-rpm": "7.6.0",
"@electron-forge/maker-squirrel": "7.6.0",
"@electron-forge/maker-zip": "7.6.0",
"electron": "33.2.1"
},
"config": {
"forge": {

@ -1,5 +1,6 @@
const UXP = require("uxp");
const XLSX = require("./xlsx.full.min.js");
if(typeof app == "undefined") app = require("indesign").app;
const storage = UXP.storage, ufs = storage.localFileSystem;
/* show file picker (single file, no folders) */

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

@ -1,5 +1,6 @@
const UXP = require("uxp");
const XLSX = require("./xlsx.full.min.js");
if(typeof app == "undefined") app = require("indesign").app;
const storage = UXP.storage, ufs = storage.localFileSystem;
function workbook_add_table(wb, table) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@ -1,5 +1,5 @@
# Note: The official Hermes documentation includes zero guidance on embedding.=
# Tested against commit d070c749bcf6fb635579433421a976c29d7a2bb7 on darwin-x64
# Tested against commit 388376f05d0c2c836c8761e372fdfafd9bf077fc on darwin-x64
# History https://git.sheetjs.com/sheetjs/docs.sheetjs.com/commits/branch/master/docz/static/hermes/Makefile
MYCC=llvm-g++
@ -71,5 +71,5 @@ sheetjs-hermes.cpp:
.PHONY: init
init:
if [ ! -e hermes ]; then git clone https://github.com/facebook/hermes.git; cd hermes; git checkout d070c749bcf6fb635579433421a976c29d7a2bb7; cd ..; fi
if [ ! -e hermes ]; then git clone https://github.com/facebook/hermes.git; cd hermes; git checkout 388376f05d0c2c836c8761e372fdfafd9bf077fc; cd ..; fi
if [ ! -e build_release ]; then cmake -S hermes -B build_release -G Ninja -DCMAKE_BUILD_TYPE=Release -DHERMES_BUILD_APPLE_FRAMEWORK=OFF; cmake --build ./build_release; fi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 19 KiB

@ -119,7 +119,7 @@ function export_xlsx() {
var new_wb = xtos(xspr.getData());
/* write file and trigger a download */
XLSX.writeFile(new_wb, 'sheetjs.xlsx', {});
XLSX.writeFile(new_wb, 'SheetJSXSpreadsheetExport.xlsx', {});
}
</script>
<script type="text/javascript">

@ -83,10 +83,13 @@ app.listen(7262, async() => {
});
EOF
npm i --save puppeteer express
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
for n in 1.12.3 2.13.3; do
npm i --save parcel@$n
npx -y parcel@$n build index.html
node test.js
npx -y xlsx-cli Presidents.xlsx | head -n 3
rm -f Presidents.xlsx

33
tests/data-postgres.sh Normal file

@ -0,0 +1,33 @@
#!/bin/bash
# https://docs.sheetjs.com/docs/demos/data/postgresql
## NOTE: these steps are for darwin-arm
mkdir sheetjs-pg
cd sheetjs-pg
npm init -y
npm i --save https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz pg@8.13.1 pg-format@1.0.4
curl -LO https://docs.sheetjs.com/postgresql/SheetJSPG.js
curl -L -O https://docs.sheetjs.com/pres.numbers
for n in 1{2..7}; do
# brew install postgresql@$n
echo $n
# "If you need to have postgresql@$n first in your PATH, run:"
export PATH="/opt/homebrew/opt/postgresql@$n/bin:$PATH"
# "Or, if you don't want/need a background service you can just run:"
nohup env LC_ALL="C" /opt/homebrew/opt/postgresql@$n/bin/postgres -D /opt/homebrew/var/postgresql@$n >/dev/null 2>&1 &
sleep 5
dropdb SheetJSPG
createdb SheetJSPG
node SheetJSPG.js
npx xlsx-cli SheetJSPGExport.xlsx
psql SheetJSPG -c 'SELECT * FROM "Presidents";'
kill $!
done

22
tests/static-eleventy.sh Executable file

@ -0,0 +1,22 @@
#!/bin/bash
# https://docs.sheetjs.com/docs/demos/static/eleventy
# This script builds the Static Site. It does not test HMR!
cd /tmp
rm -rf sheetjs-11ty
mkdir sheetjs-11ty
cd sheetjs-11ty
npm init -y
mkdir _data
curl -Lo _data/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -L -o .eleventy.js https://docs.sheetjs.com/eleventy/_eleventy.js
curl -LO https://docs.sheetjs.com/eleventy/index.njk
for n in 2.0.1 3.0.0; do
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz @11ty/eleventy@$n
npx @11ty/eleventy@$n
echo "Clinton" $(grep Clinton _site/index.html | wc -l) "BESSELJ" $(grep BESSELJ _site/index.html | wc -l) "JS" $(grep -F '.js' _site/index.html | wc -l)
# Expected output: Clinton 1 BESSELJ 0 JS 0
done