This commit is contained in:
SheetJS 2023-09-23 23:59:48 -04:00
parent 7f366322e7
commit 4fd04640ae
48 changed files with 435 additions and 254 deletions

@ -65,7 +65,7 @@ Snyk security tooling may report errors involving "Prototype Pollution":
Prototype Pollution [Medium Severity][https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926]
```
As noted in the [Snyk report](https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926):
As noted in the [Snyk report](https://web.archive.org/web/20230920204324/https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926):
> The issue is resolved in version 0.19.3

@ -55,7 +55,7 @@ Snyk security tooling may report errors involving "Prototype Pollution":
Prototype Pollution [Medium Severity][https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926]
```
As noted in the [Snyk report](https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926):
As noted in the [Snyk report](https://web.archive.org/web/20230920204324/https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926):
> The issue is resolved in version 0.19.3

@ -50,7 +50,7 @@ Bun supports both "CommonJS" and "ESM" modules.
:::info pass
It is strongly recommended to use CommonJS in Bun.
**It is strongly recommended to use CommonJS in Bun.**
:::

@ -246,7 +246,7 @@ The first presidential term can be found with the following function:
const first_prez_term = prez => prez.terms.find(term => term.type === "prez");
```
:::note
:::note pass
If no element in the array matches the criterion, `Array#find` does not return
a value. In this case, since `prez` was created by filtering for people that
@ -647,10 +647,23 @@ The server process will display a URL (typically `http://127.0.0.1:8080`). Open
Install the dependencies:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="bun" label="Bun">
<CodeBlock language="bash">{`\
bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
</Tabs>
Save the following script to `SheetJSNodeJS.js`:
```js title="SheetJSNodeJS.js"
@ -693,10 +706,23 @@ const XLSX = require("xlsx");
After saving the script, run the script:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
```bash
node SheetJSNodeJS.js
```
</TabItem>
<TabItem value="bun" label="Bun">
```bash
bun run SheetJSNodeJS.js
```
</TabItem>
</Tabs>
This script will write a new file `Presidents.xlsx` in the same folder.
:::caution pass
@ -818,63 +844,6 @@ After saving the script, run the script:
deno run -A SheetJSDeno.ts
```
This script will write a new file `Presidents.xlsx` in the same folder.
</TabItem>
<TabItem value="bun" label="Bun">
<p>Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs`}>https://cdn.sheetjs.com/xlsx-{current}/package/xlsx.mjs</a> to <code>xlsx.mjs</code>:</p>
<CodeBlock language="bash">{`\
curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs`}
</CodeBlock>
Save the following script to `SheetJSBun.js`:
```js title="SheetJSBun.js"
import * as XLSX from './xlsx.mjs';
import * as fs from 'fs';
XLSX.set_fs(fs);
/* fetch JSON data and parse */
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
const prez = raw_data.filter((row) => row.terms.some((term) => term.type === "prez"));
/* sort by first presidential term */
prez.forEach(row => row.start = row.terms.find(term => term.type === "prez").start);
prez.sort((l,r) => l.start.localeCompare(r.start));
/* flatten objects */
const rows = prez.map((row) => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
/* generate worksheet and workbook */
const worksheet = XLSX.utils.json_to_sheet(rows);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
/* fix headers */
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
/* create an XLSX file and try to save to Presidents.xlsx */
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
```
After saving the script, run the script:
```bash
bun SheetJSBun.js
```
This script will write a new file `Presidents.xlsx` in the same folder.
</TabItem>
@ -1054,7 +1023,7 @@ export default App;
<Tabs>
<TabItem value="asim" label="Android">
:::note
:::note pass
The Android demo has been tested in Windows 10 and in macOS.
@ -1169,5 +1138,5 @@ see a preview of the data. The Numbers app can open the file.
[^4]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)
[^5]: See [`book_append_sheet` in "Utilities"](/docs/api/utilities/wb)
[^6]: See [`sheet_add_aoa` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)
[^7]: See ["Row and Column Properties"](/docs/csf/features/#row-and-column-properties)
[^7]: See ["Column Properties"](/docs/csf/features/colprops)
[^8]: See [`writeFile` in "Writing Files"](/docs/api/write-options)

@ -775,10 +775,23 @@ The server process will display a URL (typically `http://127.0.0.1:8080`). Open
Install the dependencies:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="bun" label="Bun">
<CodeBlock language="bash">{`\
bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
</Tabs>
Save the following script to `SheetJSNodeJS.js`:
```js title="SheetJSNodeJS.js"
@ -813,10 +826,23 @@ const XLSX = require("xlsx");
After saving the script, run the script:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
```bash
node SheetJSNodeJS.js
```
</TabItem>
<TabItem value="bun" label="Bun">
```bash
bun run SheetJSNodeJS.js
```
</TabItem>
</Tabs>
This script will print the rows in tab-separated values (TSV) format:
```
@ -985,7 +1011,7 @@ export default App;
<Tabs>
<TabItem value="asim" label="Android">
:::note
:::note pass
The Android demo has been tested in Windows 10 and in macOS.

@ -22,7 +22,7 @@ This demo was last verified on 2023 September 03.
## Live Demo
:::note pass
:::caution pass
Due to CSS conflicts between the data grid and the documentation generator,
features like scrolling may not work as expected.

@ -21,7 +21,7 @@ This demo was last verified on 2023 September 03.
## Live Demo
:::note
:::caution pass
Due to CSS conflicts between the data grid and the documentation generator,
features like scrolling may not work as expected.

@ -75,7 +75,7 @@ function rdg_to_ws(rows: Row[]): WorkSheet {
}
```
:::caution
:::caution pass
When the demo was last refreshed, row array objects were preserved. This was
not the case in a later release. The row arrays must be re-created.

@ -131,7 +131,7 @@ function ws_to_muidg(ws: WorkSheet): RowCol {
In the other direction, a worksheet can be generated with `aoa_to_sheet`:
:::caution
:::caution pass
`x-data-grid` does not properly preserve row array objects, so the row arrays
must be re-created. The snippet defines a `arrayify` function.

@ -11,7 +11,7 @@ Various JavaScript UI components provide a more interactive editing experience.
Most are able to interchange with arrays of arrays or arrays of data objects.
This demo focuses on a few open source data grids.
:::note
:::tip pass
[SheetJS Pro](https://sheetjs.com/pro) offers additional features like styling
and images. The UI tools typically support many of these advanced features.

@ -362,7 +362,7 @@ async function workbook_dl_axios(url) {
This demo uses `axios` to download <https://sheetjs.com/pres.numbers> and show
the data in an HTML table.
:::caution
:::caution pass
If the live demo shows a message
@ -471,7 +471,7 @@ superagent
This demo uses `superagent` to download <https://sheetjs.com/pres.numbers> and
show the data in an HTML table.
:::caution
:::caution pass
If the live demo shows a message

@ -54,7 +54,7 @@ It is strongly recommended to first test with an independent service provider.
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
:::caution pass
A valid phone number (for SMS verification) was required.
@ -105,7 +105,7 @@ const msg = { from: "*", to: "*", subject: "*", text: "*",
}
```
:::caution
:::caution pass
The file name must have the expected extension for the `bookType`!
@ -195,7 +195,7 @@ including `accepted` and `response`. The recipient inbox should receive an email
shortly. The email will include an attachment `SheetJSMailExport.xlsb` which
can be opened in Excel.
:::caution
:::caution pass
The app password must be entered in step 3. If the account password was used,
the mailer will fail with a message that includes:

@ -17,7 +17,7 @@ is a transformer that generates GraphQL nodes for each row of each worksheet.
The plugin is officially supported by the Gatsby team. The plugin documentation
includes examples and more detailed usage instructions.
:::note
:::note pass
`gatsby-transformer-excel` is maintained by the Gatsby core team and all bugs
should be directed to the main Gatsby project. If it is determined to be a bug
@ -25,7 +25,7 @@ in the parsing logic, issues should then be raised with the SheetJS project.
:::
:::caution
:::caution pass
`gatsby-transformer-excel` uses an older version of the library. It can be
overridden through a `package.json` override in the latest versions of NodeJS:

@ -9,10 +9,11 @@ sidebar_custom_props:
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
:::note
:::info pass
This demo covers static asset imports. For processing files in the browser, the
["Bundlers" demo](/docs/demos/frontend/bundler#webpack) includes an example.
["Bundlers" demo](/docs/demos/frontend/bundler#webpack) includes an example of
importing the SheetJS library in a browser script.
:::

@ -314,7 +314,7 @@ The app should now show two buttons at the bottom:
![Quasar Step 6](pathname:///mobile/quasar6.png)
:::caution
:::caution pass
If the app is blank or not refreshing, delete the app and close the simulator,
then restart the development process.

@ -121,7 +121,7 @@ async function importFile() {
}
```
:::note
:::note pass
In older versions of Electron, `showOpenDialog` returned the path directly:
@ -161,7 +161,7 @@ async function exportFile(workbook) {
}
```
:::note
:::note pass
In older versions of Electron, `showSaveDialog` returned the path directly:
@ -208,7 +208,7 @@ The demo project is wired for `electron-forge` to build the standalone binary.
- [`index.html`](pathname:///electron/index.html) : window page
- [`index.js`](pathname:///electron/index.js) : script loaded in render context
:::caution
:::caution pass
Right-click each link and select "Save Link As...". Left-clicking a link will
try to load the page in your browser. The goal is to save the file contents.
@ -302,7 +302,7 @@ the required changes for Electron 23.0.0.
There are no Electron-specific workarounds in the library, but Electron broke
backwards compatibility multiple times. A summary of changes is noted below.
:::caution
:::caution pass
Electron 6.x changed the `dialog` API. Methods like `showSaveDialog` originally
returned an array of strings, but now returns a `Promise`. This change was not

@ -130,7 +130,7 @@ This demo was tested against NW.js 0.78.0 on 2023 July 27.
2) Download [`index.html`](pathname:///nwjs/index.html) into the same folder.
:::caution
:::caution pass
Right-click the link and select "Save Link As...". Left-clicking the link will
try to load the page in your browser. The goal is to save the file contents.

@ -228,7 +228,7 @@ export default App;
## Native Modules
:::caution
:::caution pass
As with the mobile versions of React Native, file operations are not provided
by the base SDK. The examples include native code for both Windows and macOS.
@ -387,7 +387,7 @@ There is no simple standalone executable file at the end of the process.
:::
:::note
:::note pass
React Native Windows supports writing native code in C++ or C#. This demo has
been tested against both application types.
@ -396,7 +396,7 @@ been tested against both application types.
0) Follow the ["Getting Started" guide](https://microsoft.github.io/react-native-windows/docs/getting-started)
:::caution
:::caution pass
At the time of testing, NodeJS `v16` was required. A tool like
[`nvm-windows`](https://github.com/coreybutler/nvm-windows/releases) should be
@ -447,7 +447,7 @@ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
npx react-native run-windows --no-telemetry
```
:::caution
:::caution pass
When the demo was tested in Windows 11, the run step failed with the message:

@ -112,7 +112,7 @@ npx nexe -t 14.15.3 xlsx-cli.js
This generates `xlsx-cli` or `xlsx-cli.exe` depending on platform.
:::caution
:::caution pass
When the demo was tested on `darwin-arm`, the `mac-arm64` pre-built package was
missing. The package must be built from source:

@ -16,7 +16,7 @@ work as-is in WebSQL.
The public demo <https://sheetjs.com/sql> generates a database from workbook.
:::caution
:::caution pass
WebSQL is only supported in Chromium-based browsers including Chrome.

@ -9,33 +9,169 @@ sidebar_custom_props:
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[KnexJS](https://knexjs.org/) is a SQL query builder with support for a number
of SQL dialects.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses KnexJS and SheetJS to exchange data between spreadsheets and SQL
servers. We'll explore how to use save tables from a database to spreadsheets
and how to add data from spreadsheets into a database.
:::note
This demo was last tested on 2023 April 19 with Knex 2.4.2 and `better-sqlite`.
This demo was last tested on 2023 September 23 with Knex 2.5.1. The demo uses
the SQLite backend with the `better-sqlite3` connector.
:::
## Integration Details
#### Importing Data
The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
loaded in NodeJS scripts that use KnexJS.
`sheet_to_json` generates an array of objects. An `INSERT` statement can be
generated from each row object using `knex.insert`:
### Exporting Data
The KnexJS `select` method[^1] creates a `SELECT` query. The return value is a
Promise that resolves to an array of objects.
The SheetJS `json_to_sheet` method[^2] can generate a worksheet object[^3] from
the array of objects:
```js
const aoo = XLSX.utils.sheet_to_json(ws);
for(let i = 0; i < aoo.length; ++i) await knex.insert(aoo[i]).into(table_name);
```
const table_name = "Tabeller1"; // name of table
#### Exporting Data
The result of a `SELECT` statement is an array of objects:
```js
/* fetch all data from specified table */
const aoo = await knex.select("*").from(table_name);
/* generate a SheetJS worksheet object from the data */
const worksheet = XLSX.utils.json_to_sheet(aoo);
```
A workbook object can be built from the worksheet using utility functions[^4].
The workbook can be exported using the SheetJS `writeFile` method[^5]:
```js
/* create a new workbook and add the worksheet */
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, worksheet, "Sheet1");
/* export workbook to XLSX */
XLSX.writeFile(wb, "SheetJSKnexJSExport.xlsx");
```
### Importing Data
The SheetJS `sheet_to_json` function[^6] takes a worksheet object and generates
an array of objects.
The KnexJS `insert` method[^7] creates `INSERT` queries. The return value is a
Promise that resolves when the query is executed:
```js
const table_name = "Blatte1"; // name of table
/* generate an array of arrays from the worksheet */
const aoo = XLSX.utils.sheet_to_json(ws);
/* insert every row into the specified database table */
await knex.insert(aoo).into(table_name);
```
### Creating a Table
The KnexJS Schema Builder supports creating tables with `createTable`[^8] and
dropping tables with `dropTableIfExists`[^9].
The array of objects can be scanned to determine column names and types.
<details><summary><b>Implementation Details</b> (click to show)</summary>
The `aoo_to_knex_table` function:
- scans each row object to determine column names and types
- drops and creates a new table with the determined column names and types
- loads the entire dataset into the new table
```js
/* create table and load data given an array of objects and a Knex connection */
async function aoo_to_knex_table(knex, aoo, table_name) {
/* define types that can be converted (e.g. boolean can be stored in float) */
const T_FLOAT = ["float", "boolean"];
const T_BOOL = ["boolean"];
/* types is a map from column headers to Knex schema column type */
const types = {};
/* names is an ordered list of the column header names */
const names = [];
/* loop across each row object */
aoo.forEach(row =>
/* Object.entries returns a row of [key, value] pairs */
Object.entries(row).forEach(([k,v]) => {
/* If this is first occurrence, mark unknown and append header to names */
if(!types[k]) { types[k] = ""; names.push(k); }
/* skip null and undefined values */
if(v == null) return;
/* check and resolve type */
switch(typeof v) {
/* change type if it is empty or can be stored in a float */
case "number": if(!types[k] || T_FLOAT.includes(types[k])) types[k] = "float"; break;
/* change type if it is empty or can be stored in a boolean */
case "boolean": if(!types[k] || T_BOOL.includes(types[k])) types[k] = "boolean"; break;
/* no other type can hold strings */
case "string": types[k] = "text"; break;
default: types[k] = "text"; break;
}
})
);
/* Delete table if it exists in the DB */
await knex.schema.dropTableIfExists(table_name);
/* use column type info to create table */
await knex.schema.createTable(table_name, (table) => {
names.forEach(h => {
/* call schema function e.g. table.text("Name"); table.float("Index"); */
table[types[h] || "text"](h);
});
});
/* insert each row */
await knex.insert(aoo).into(table_name);
return knex;
}
```
</details>
:::note pass
The `Knex` constructor may display a warning when connecting to SQLite:
```
sqlite does not support inserting default values. Set the `useNullAsDefault` flag to hide this warning. (see docs https://knexjs.org/guide/query-builder.html#insert).
```
That flag should be added to the options argument:
```js
const Knex = require('knex');
let knex = Knex({
client: 'better-sqlite3',
connection: { filename: "SheetJSKnex.db" },
// highlight-next-line
useNullAsDefault: true
});
```
:::
## Complete Example
1) Install dependencies:
@ -50,109 +186,22 @@ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz knex be
curl -LO https://sheetjs.com/pres.numbers
```
3) Save the following utility script to `sheetjsknex.js`:
3) Download [`SheetJSKnexTest.js`](pathname:///knex/SheetJSKnexTest.js):
```js title="sheetjsknex.js"
const XLSX = require("xlsx");
// define mapping between determined types and Knex types
const PG = { "n": "float", "s": "text", "b": "boolean" };
async function ws_to_knex(knex, ws, table_name) {
// generate an array of objects from the data
const aoo = XLSX.utils.sheet_to_json(ws);
// types will map column headers to types, while hdr holds headers in order
const types = {}, hdr = [];
// loop across each row object
aoo.forEach(row =>
// Object.entries returns a row of [key, value] pairs. Loop across those
Object.entries(row).forEach(([k,v]) => {
// If this is first time seeing key, mark unknown and append header array
if(!types[k]) { types[k] = "?"; hdr.push(k); }
// skip null and undefined
if(v == null) return;
// check and resolve type
switch(typeof v) {
case "string": // strings are the broadest type
types[k] = "s"; break;
case "number": // if column is not string, number is the broadest type
if(types[k] != "s") types[k] = "n"; break;
case "boolean": // only mark boolean if column is unknown or boolean
if("?b".includes(types[k])) types[k] = "b"; break;
default: types[k] = "s"; break; // default to string type
}
})
);
await knex.schema.dropTableIfExists(table_name);
// use column type info to create table
await knex.schema.createTable(table_name, (table) => { hdr.forEach(h => { table[PG[types[h]] || "text"](h); }); });
// insert each non-empty row object
for(let i = 0; i < aoo.length; ++i) {
if(!aoo[i] || !Object.keys(aoo[i]).length) continue;
try { await knex.insert(aoo[i]).into(table_name); } catch(e) {}
}
return knex;
}
async function knex_to_ws(knex, table_name) {
const aoo = await knex.select("*").from(table_name);
return XLSX.utils.json_to_sheet(aoo);
}
module.exports = { ws_to_knex, knex_to_ws };
```bash
curl -LO https://docs.sheetjs.com/knex/SheetJSKnexTest.js
```
4) Save the following to `SheetJSKnexTest.js`:
```js title="SheetJSKnexTest.js"
const { ws_to_knex, knex_to_ws } = require("./sheetjsknex");
const Knex = require('knex');
This script will:
- read and parse the test file `pres.numbers`
- create a connection to a SQLite database stored at `SheetJSKnex.db`
- load data from the first worksheet into a table with name `Test_Table`
- disconnect and reconnect to the database
- dump data from the table `Test_Table`
- export the dataset to `SheetJSKnex.xlsx`
/* read file and get first worksheet */
const XLSX = require("xlsx");
const oldwb = XLSX.readFile("pres.numbers");
const wsname = oldwb.SheetNames[0];
const oldws = oldwb.Sheets[wsname];
(async() => {
/* open connection to SheetJSKnex.db */
let knex = Knex({ client: 'better-sqlite3', connection: { filename: "SheetJSKnex.db" } });
try {
/* load data into database and close connection */
await ws_to_knex(knex, oldws, "Test_Table");
} finally { knex.destroy(); }
/* reconnect to SheetJSKnex.db */
knex = Knex({ client: 'better-sqlite3', connection: { filename: "SheetJSKnex.db" } });
try {
/* get data from db */
const aoo = await knex.select("*").from("Test_Table");
/* export to file */
const newws = await knex_to_ws(knex, "Test_Table");
const newwb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(newwb, newws, "Export");
XLSX.writeFile(newwb, "SheetJSKnex.xlsx");
} finally { knex.destroy(); }
})();
```
This script will read `pres.numbers` and load data into a table `Test_Table`.
The SQLite database will be saved to `SheetJSKnex.db`.
After closing the connection, the script will make a new connection and export
data to `SheetJSKnex.xlsx`.
5) Run the script:
4) Run the script:
```bash
node SheetJSKnexTest.js
@ -171,3 +220,13 @@ npx xlsx-cli SheetJSKnex.xlsx
```bash
sqlite3 SheetJSKnex.db 'select * from Test_Table'
```
[^1]: See [`select`](https://knexjs.org/guide/query-builder.html#select) in the KnexJS query builder documentation.
[^2]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
[^3]: See ["Sheet Objects"](/docs/csf/sheet) in "SheetJS Data Model" for more details.
[^4]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`.
[^5]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
[^6]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^7]: See [`insert`](https://knexjs.org/guide/query-builder.html#insert) in the KnexJS query builder documentation.
[^8]: See [`createTable`](https://knexjs.org/guide/schema-builder.html#createtable) in the KnexJS Schema Builder documentation.
[^9]: See [`dropTableIfExists`](https://knexjs.org/guide/schema-builder.html#droptableifexists) in the KnexJS Schema Builder documentation.

@ -59,7 +59,7 @@ INTO XLSX( -- export data to file
</details>
:::caution
:::caution pass
If the live demo shows a message
@ -196,7 +196,7 @@ The `XLSX` "into" target calls `XLSX.writeFile` under the hood:
## NodeJS
:::caution
:::caution pass
`alasql` uses an older version of the library. It can be overridden through a
`package.json` override in the latest versions of NodeJS:

@ -116,7 +116,7 @@ const ws = XLSX.utils.json_to_sheet(aoo);
When a strict schema is needed, the `sheet_to_json` helper function generates
arrays of JS objects that can be scanned to determine the column "types".
:::note
:::note pass
Document databases like MongoDB tend not to require schemas. Arrays of objects
can be used directly without setting up a schema:

@ -390,7 +390,7 @@ the feature in version 86. Safari did not support File System Access API.
:::
:::caution
:::caution pass
When this demo was last tested, Google Chrome did not add an entry to the
"Downloads" list. Nevertheless the actual file was written correctly.

@ -28,7 +28,7 @@ a popular choice for offline storage.
A number of popular wrapper libraries seek to simplify IndexedDB operations.
:::note
:::note pass
The wrapper libraries in this section have been used by SheetJS users in
production sites.

@ -12,7 +12,7 @@ Salesforce apps can use third-party libraries in "Lightning Web Components".
This demo assumes familiarity with Lightning Web Components. Salesforce has a
[detailed introduction.](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_introduction)
:::caution
:::caution pass
Salesforce may change the platform in backwards-incompatible ways, so the demo
may require some adjustments. The official documentation should be consulted.
@ -152,7 +152,7 @@ The easiest approach is to right-click the link and select "Save Link As..."
sfdx force:source:deploy -p force-app -u SALESFORCE@USER.NAME # replace with actual username
```
:::note
:::note pass
The official documentation recommends adding a static resource with a ZIP file.
That approach is not explored in this demo.
@ -207,7 +207,7 @@ export default class SheetComponent extends LightningElement {
## Exporting Data from SF Lists
:::note
:::note pass
There are many different data types and APIs. This demo uses the deprecated
`getListUi` function to pull account data.
@ -381,7 +381,7 @@ The simple export has all of the data:
![Excel Export](pathname:///files/sfxlexport.png)
:::note
:::tip pass
[SheetJS Pro](https://sheetjs.com/pro) offers additional styling options like
cell styling, automatic column width calculations, and frozen rows.

@ -10,7 +10,7 @@ import CodeBlock from '@theme/CodeBlock';
AWS is a Cloud Services platform which includes traditional virtual machine
support, "Serverless Functions", cloud storage and much more.
:::caution
:::caution pass
AWS iterates quickly and there is no guarantee that the referenced services
will be available in the future.

@ -10,7 +10,7 @@ import CodeBlock from '@theme/CodeBlock';
Azure is a Cloud Services platform which includes traditional virtual machine
support, "Serverless Functions", cloud storage and much more.
:::caution
:::caution pass
Azure iterates quickly and there is no guarantee that the referenced services
will be available in the future.

@ -48,7 +48,7 @@ As a project from the company, the entire lifecycle uses GitHub offerings:
- `flat-postprocessing` Post-processing helper functions and examples
- "Flat Viewer": Web viewer for structured CSV and JSON data on GitHub
:::caution
:::caution pass
A GitHub account is required. When the demo was tested, free GitHub accounts had
no Actions usage limits for public repositories.

@ -20,7 +20,7 @@ This breaks web frameworks that use the filesystem in body parsing.
:::
:::caution
:::caution pass
When the demo was last tested, Deno Deploy required a GitHub account.

@ -4,7 +4,7 @@ pagination_prev: demos/cloud/index
pagination_next: demos/bigdata/index
---
:::note
:::note pass
This demo showcases Manifest V2 and Manifest V3 extensions. Chrome Web Store
will not accept new V2 extensions, but these can be sideloaded using the

@ -119,7 +119,7 @@ This demo was tested in the following deployments:
|:-------------|:--------|:-----------|
| `darwin-x64` | `2.7.0` | 2023-07-24 |
| `darwin-arm` | `2.7.0` | 2023-06-05 |
| `linux-x64` | `2.7.0` | 2023-06-02 |
| `linux-x64` | `2.7.0` | 2023-09-22 |
| `linux-arm` | `2.7.0` | 2023-08-30 |
| `win10-x64` | `2.7.0` | 2023-07-24 |

@ -15,7 +15,7 @@ can be parsed and evaluated in a Rhino context.
This demo wraps workbooks and sheets into separate Java classes. The final
result is a JAR.
:::caution
:::caution pass
Rhino does not support Uint8Array, so certain formats like NUMBERS cannot be
parsed or written from Rhino JS code!
@ -24,7 +24,7 @@ parsed or written from Rhino JS code!
## Integration Details
:::note
:::caution pass
Due to code generation errors, optimization must be turned off:

@ -165,6 +165,7 @@ This demo was tested in the following deployments:
|:-------------|:------------------|:-----------|
| `darwin-x64` | `3.0.0-beta-2051` | 2023-09-16 |
| `win10-x64` | `3.0.0-beta-2051` | 2023-09-16 |
| `linux-x64` | `3.0.0-beta-2051` | 2023-09-22 |
:::
@ -337,6 +338,7 @@ tested platforms are listed below:
|:-----------------|:------------|
| Intel Mac | `osx-x64` |
| Windows 10 (x64) | `win10-x64` |
| Linux (x64) | `linux-x64` |
9) Build the standalone application. Replace `$RID` with the real value in:
@ -353,6 +355,15 @@ For Intel Mac, the RID is `osx-x64` and the command is
```bash
dotnet publish -c Release -r osx-x64 --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true
```
</TabItem>
<TabItem value="linux-x64" label="Linux x64">
For x64 Linux, the RID is `linux-x64` and the command is
```bash
dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true
```
</TabItem>
@ -383,6 +394,15 @@ For Intel Mac, the RID is `osx-x64` and the command is:
```bash
cp bin/Release/net6.0/osx-x64/publish/SheetJSJint .
```
</TabItem>
<TabItem value="linux-x64" label="Linux x64">
For x64 Linux, the RID is `linux-x64` and the command is
```bash
cp bin/Release/net6.0/linux-x64/publish/SheetJSJint .
```
</TabItem>
@ -422,5 +442,5 @@ copy .\bin\Release\net6.0\win10-x64\publish\SheetJSJint.exe .
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^3]: See [`write` in "Writing Files"](/docs/api/write-options)
[^4]: See ["Supported Output Formats" in "Writing Files"](/docs/api/write-options#supported-output-formats) for details on `bookType`
[^5]: At the time of writing, <https://dotnet.microsoft.com/en-us/download> is the official endpoint.
[^5]: At the time of writing, <https://dotnet.microsoft.com/en-us/download> is the official endpoint. For Steam Deck Holo and other Arch Linux distributions, the `dotnet-sdk` and `dotnet-runtime` packages should be installed using `pacman`.
[^6]: See [".NET RID Catalog"](https://learn.microsoft.com/en-us/dotnet/core/rid-catalog) in the .NET documentation

@ -26,7 +26,7 @@ command-line tool for reading data from files.
:::info pass
Many Hermes functions are not documented. The explanation was verified against
commit `70af78b`.
commit `49e1930`.
:::
@ -207,7 +207,7 @@ Standard SheetJS operations can pick the first worksheet and generate CSV string
data from the worksheet. Hermes provides methods to convert the JS strings back
to `std::string` objects for further processing in C++.
:::note
:::note pass
It is strongly recommended to create a stub function to perform the entire
workflow in JS code and pass the final result back to C++.
@ -340,7 +340,7 @@ This demo was tested in the following deployments:
|:-------------|:-----------|:-----------|
| `darwin-x64` | `70af78b` | 2023-08-27 |
| `darwin-arm` | `869312f` | 2023-06-05 |
| `linux-x64` | `70af78b` | 2023-08-27 |
| `linux-x64` | `49e1930` | 2023-09-22 |
| `linux-arm` | `70af78b` | 2023-08-27 |
:::

@ -81,7 +81,7 @@ This demo was tested in the following deployments:
gem install execjs
```
:::note
:::note pass
The command may need to be run as an administrator or root user:

@ -40,7 +40,7 @@ JsSetCurrentContext(JS_INVALID_REFERENCE);
JsDisposeRuntime(runtime);
```
:::note
:::note pass
Cleanup and validation code is omitted from the discussion. The integration
example shows structured validation and controlled memory usage.
@ -181,7 +181,7 @@ cd ChakraCore
cd ..
```
:::caution
:::caution pass
When this demo was last tested, the build failed with the message:
@ -216,7 +216,7 @@ cd ..
```
:::caution
:::caution pass
When this demo was last tested, the build failed with the message:
@ -238,7 +238,7 @@ cd ..
</TabItem>
<TabItem value="linux-x64" label="Linux">
:::caution
:::caution pass
When the demo was last tested, ChakraCore JIT was not supported.
@ -297,7 +297,7 @@ curl -L -O https://docs.sheetjs.com/chakra/Makefile
make
```
:::caution
:::caution pass
When this demo was last tested on macOS, the build failed with the message:

@ -125,14 +125,23 @@ generates a C library and a standalone CLI tool.
The simplest way to interact with the engine is to pass Base64 strings.
:::note
:::note pass
This demo was tested in the following deployments:
| Architecture | Commit | Date |
|:-------------|:----------|:-----------|
| `darwin-x64` | `a588e49` | 2023-09-22 |
| `linux-x64` | `a588e49` | 2023-09-22 |
:::note pass
While applications should link against the official libraries, the standalone tool
is useful for verifying functionality.
:::
:::caution
:::caution pass
This demo requires a much larger heap size than is normally used in JerryScript
deployments! In local testing, the following sizes were needed:

@ -143,7 +143,7 @@ import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs'
\n\
const workbook = XLSX.readFile("test.xlsx");`}</CodeBlock>
:::note
:::note pass
Applications reading files must be invoked with the `--allow-read` flag.
@ -369,7 +369,7 @@ const server = new Drash.Server({ hostname: "", port: 7262, protocol: "http",
\n\
server.run();`}</CodeBlock>
:::note
:::note pass
Deno must be run with the `--allow-net` flag to enable network requests:
@ -518,7 +518,7 @@ const data = await (await fetch(url)).arrayBuffer();
/* data is an ArrayBuffer */
const workbook = XLSX.read(data);`}</CodeBlock>
:::note
:::note pass
Deno must be run with the `--allow-net` flag to enable network requests:
@ -557,7 +557,7 @@ req.end();
### Example: Readable Streams
:::caution
:::caution pass
The recommended approach is to buffer streams in memory and process once all of
the data has been collected. A proper streaming parse is technically impossible.

@ -15,7 +15,7 @@ read from data sources and write to data sources.
### Worksheets
:::note
:::note pass
Worksheet names are case-sensitive.
@ -191,7 +191,7 @@ worksheet["!merges"].push({
});
```
:::caution
:::caution pass
This approach does not verify if two merged ranges intersect.

@ -54,7 +54,7 @@ referencing the other export functions.
The second `opts` argument is optional. ["Writing Options"](/docs/api/write-options)
covers the supported properties and behaviors.
:::note
:::note pass
The `writeFile` and `writeFileXLSX` methods uses platform-specific APIs to save
files. The APIs do not generally provide feedback on whether files were created.
@ -218,7 +218,7 @@ import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs'
\n\
XLSX.writeFile(workbook, "test.xlsx");`}</CodeBlock>
:::note
:::note pass
Applications writing files must be invoked with the `--allow-write` flag.
@ -303,7 +303,7 @@ This example focuses on responses to network requests in a server-side platform
like NodeJS. While files can be generated in the web browser, server-side file
generation allows for exact audit trails and has better mobile user support.
:::caution
:::caution pass
Production deployments should use a server framework like ExpressJS. These
snippets use low-level APIs for illustration purposes.
@ -489,7 +489,7 @@ like `XMLHttpRequest` and `fetch` as well as third-party libraries.
<Tabs>
<TabItem value="browser" label="Browser">
:::caution
:::caution pass
Some platforms like Azure and AWS will attempt to parse POST request bodies as
UTF-8 strings before user code can see the data. This will result in corrupt

@ -151,7 +151,7 @@ The following table covers some common formats:
| `A/P` | Meridiem ("A" or "P") |
| `AM/PM` | Meridiem ("AM" or "PM") |
:::note
:::note pass
`m` and `mm` are context-dependent. It is interpreted as "minutes" when the
previous or next date token represents a time (hours or seconds):
@ -278,7 +278,7 @@ function LocalInfo() {
</>)}
```
:::caution
:::caution pass
The timezone information is provided by the JavaScript engine and local settings.
There are outstanding Google Chrome and V8 bugs related to rounded offsets for

@ -34,7 +34,7 @@ support Excel styled comments or Excel legacy notes.
The letter R (R) marks features parsed but not written in the format.
:::note
:::note pass
[SheetJS Pro](https://sheetjs.com/pro) supports comment rich text and styling.
@ -78,6 +78,10 @@ characters may cause issues with other formats.
:::
## Demos
#### Export
<details open><summary><b>Live Export Example</b> (click to hide)</summary>
This example creates a small worksheet with a comment in cell A1:
@ -98,7 +102,9 @@ function SheetJSComments1() {
</details>
<details open><summary><b>Live Import Example</b> (click to hide)</summary>
#### Import
<details><summary><b>Live Import Example</b> (click to show)</summary>
This example displays every comment in the workbook:
@ -149,7 +155,10 @@ cell.c.hidden = true;
cell.c.push({a:"SheetJS", t:"This comment will be hidden"});
```
<details open><summary><b>Live Example</b> (click to hide)</summary>
<details><summary><b>Live Example</b> (click to show)</summary>
The following demo creates a worksheet with two comments. The comment in cell A1
will be visibile and the comment in cell A2 will be hidden.
```jsx live
function SheetJSComments2() {
@ -203,7 +212,7 @@ There is no Active Directory or Office 365 metadata associated with authors.
<details open><summary><b>Live Example</b> (click to hide)</summary>
```jsx live
function SheetJSComments2() {
function SheetJSThreadedComments() {
return ( <button onClick={() => {
var ws = XLSX.utils.aoa_to_sheet([["SheetJS"], [5433795]]);

@ -98,7 +98,7 @@ data from SheetJS worksheet objects.
#### Example Sheet
:::note
:::note pass
The live examples are based on the following worksheet:
@ -284,7 +284,7 @@ default column order is determined by the first appearance of the field using
[UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
:::caution
:::caution pass
All fields from each row will be written! `header` hints at a particular order
but is not exclusive. To remove fields from the export, filter the data source.
@ -431,7 +431,7 @@ XLSX.utils.sheet_add_json(ws, [
], {header: ["A", "B", "C", "D", "E", "F", "G"], skipHeader: true, origin: -1});
```
:::note
:::note pass
If the `header` option is an array, `sheet_add_json` and `sheet_to_json` will
append missing elements.
@ -479,7 +479,7 @@ var aoa = XLSX.utils.sheet_to_json(ws, {header: 1, ...other_opts});
</TabItem>
<TabItem name="TS" value="TypeScript">
:::caution
:::caution pass
TypeScript types are purely informational. They are not included at run time
and do not influence the behavior of the `sheet_to_json` function.

@ -8,7 +8,7 @@ functions (`XLSX.read` and `XLSX.readFile`) can parse HTML strings and the write
functions (`XLSX.write` and `XLSX.writeFile`) can generate HTML strings, the
utility functions in this section can use DOM features.
:::note
:::note pass
SheetJS CE primarily focuses on data and number formatting.
@ -437,7 +437,7 @@ function SheetJSHTMLValueOverride() {
`table_to_book` / `table_to_sheet` / `sheet_add_dom` act on HTML DOM elements.
Traditionally there is no DOM in server-side environments including NodeJS.
:::note
:::note pass
The simplest approach for server-side processing is to automate a headless web
browser. ["Browser Automation"](/docs/demos/net/headless) covers some browsers.

@ -39,7 +39,7 @@ These instructions were tested on the following platforms:
| Platform | Test Date |
|:------------------------------|:-----------|
| Linux (Steam Deck Holo x64) | 2023-07-12 |
| Linux (Steam Deck Holo x64) | 2023-09-22 |
| Linux (Ubuntu 18 AArch64) | 2023-09-07 |
| MacOS 10.13 (x64) | 2023-04-04 |
| MacOS 13.0 (ARM64) | 2023-04-13 |

@ -1,5 +1,5 @@
# Note: The official Hermes documentation includes zero guidance on embedding.
# Tested against commit 70af78ba69391645749b40a3674d7321c4d6177a
# Tested against commit 49e1930dbab6f4c6444d61ca9674cbd5795253fb
MYCC=llvm-g++
POSTAMBLE=-framework CoreFoundation
@ -64,5 +64,5 @@ sheetjs-hermes: sheetjs-hermes.cpp init
.PHONY: init
init:
if [ ! -e hermes ]; then git clone https://github.com/facebook/hermes.git; cd hermes; git checkout 70af78ba69391645749b40a3674d7321c4d6177a; cd ..; fi
if [ ! -e hermes ]; then git clone https://github.com/facebook/hermes.git; cd hermes; git checkout 49e1930dbab6f4c6444d61ca9674cbd5795253fb; cd ..; fi
if [ ! -e build_release ]; then cmake -S hermes -B build_release -G Ninja -DCMAKE_BUILD_TYPE=Release; cmake --build ./build_release; fi

@ -0,0 +1,88 @@
const Knex = require('knex');
const XLSX = require("xlsx");
/* read file and get first worksheet */
const oldwb = XLSX.readFile("pres.numbers");
const oldws = oldwb.Sheets[oldwb.SheetNames[0]];
/* create table and load data given an array of objects and a Knex connection */
async function aoo_to_knex_table(knex, aoo, table_name) {
/* define types that can be converted (e.g. boolean can be stored in float) */
const T_FLOAT = ["float", "boolean"];
const T_BOOL = ["boolean"];
/* types is a map from column headers to Knex schema column type */
const types = {};
/* names is an ordered list of the column header names */
const names = [];
/* loop across each row object */
aoo.forEach(row =>
/* Object.entries returns a row of [key, value] pairs */
Object.entries(row).forEach(([k,v]) => {
/* If this is first occurrence, mark unknown and append header to names */
if(!types[k]) { types[k] = ""; names.push(k); }
/* skip null and undefined values */
if(v == null) return;
/* check and resolve type */
switch(typeof v) {
/* change type if it is empty or can be stored in a float */
case "number": if(!types[k] || T_FLOAT.includes(types[k])) types[k] = "float"; break;
/* change type if it is empty or can be stored in a boolean */
case "boolean": if(!types[k] || T_BOOL.includes(types[k])) types[k] = "boolean"; break;
/* no other type can hold strings */
case "string": types[k] = "text"; break;
default: types[k] = "text"; break;
}
})
);
/* Delete table if it exists in the DB */
await knex.schema.dropTableIfExists(table_name);
/* use column type info to create table */
await knex.schema.createTable(table_name, (table) => {
names.forEach(h => {
/* call schema function e.g. table.text("Name"); table.float("Index"); */
table[types[h] || "text"](h);
});
});
/* insert each row */
await knex.insert(aoo).into(table_name);
return knex;
}
(async() => {
/* open connection to SheetJSKnex.db */
let knex = Knex({ client: 'better-sqlite3', connection: { filename: "SheetJSKnex.db" }, useNullAsDefault: true });
try {
/* generate array of objects from worksheet */
const aoo = XLSX.utils.sheet_to_json(oldws);
/* create table and load data */
await aoo_to_knex_table(knex, aoo, "Test_Table");
} finally {
/* disconnect */
knex.destroy();
}
/* reconnect to SheetJSKnex.db */
knex = Knex({ client: 'better-sqlite3', connection: { filename: "SheetJSKnex.db" }, useNullAsDefault: true });
try {
/* get data from db */
const aoo = await knex.select("*").from("Test_Table");
/* export to file */
const newws = XLSX.utils.json_to_sheet(aoo);
const newwb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(newwb, newws, "Export");
XLSX.writeFile(newwb, "SheetJSKnex.xlsx");
} finally { knex.destroy(); }
})();