diff --git a/docz/docs/03-demos/01-frontend/01-react.md b/docz/docs/03-demos/01-frontend/01-react.md
index cbb9a0f..2f89ab3 100644
--- a/docz/docs/03-demos/01-frontend/01-react.md
+++ b/docz/docs/03-demos/01-frontend/01-react.md
@@ -15,7 +15,7 @@ Other demos cover general React deployments, including:
- [Static Site Generation powered by NextJS](/docs/demos/static/nextjs)
- [iOS and Android applications powered by React Native](/docs/demos/mobile/reactnative)
- [Desktop application powered by React Native Windows + macOS](/docs/demos/desktop/reactnative)
-- [React Data Grid UI component](/docs/demos/grid#react-data-grid)
+- [React Data Grid UI component](/docs/demos/grid/rdg)
- [Glide Data Grid UI component](/docs/demos/grid/gdg)
diff --git a/docz/docs/03-demos/02-grid/11-rdg.md b/docz/docs/03-demos/02-grid/11-rdg.md
new file mode 100644
index 0000000..b237260
--- /dev/null
+++ b/docz/docs/03-demos/02-grid/11-rdg.md
@@ -0,0 +1,127 @@
+---
+title: React Datagrid
+pagination_prev: demos/frontend/index
+pagination_next: demos/net/index
+---
+
+:::note
+
+This demo was last tested on 2023 April 18 with `react-data-grid 7.0.0-beta.28`,
+`create-react-app` 5.0.1 and React 18.2.0.
+
+:::
+
+
+The demo creates a site that looks like the screenshot below:
+
+![react-data-grid screenshot](pathname:///rdg/rdg1.png)
+
+## Integration Details
+
+#### Rows and Columns state
+
+`react-data-grid` state consists of an Array of column metadata and an Array of
+row objects. Typically both are defined in state:
+
+```jsx
+import DataGrid, { Column } from "react-data-grid";
+
+export default function App() {
+ const [rows, setRows] = useState([]);
+ const [columns, setColumns] = useState([]);
+
+ return ( );
+}
+```
+
+The most generic data representation is an array of arrays. To sate the grid,
+columns must be objects whose `key` property is the index converted to string:
+
+```ts
+import { WorkSheet, utils } from 'xlsx';
+import { textEditor, Column } from "react-data-grid";
+
+type Row = any[];
+type AOAColumn = Column;
+type RowCol = { rows: Row[]; columns: AOAColumn[]; };
+
+function ws_to_rdg(ws: WorkSheet): RowCol {
+ /* create an array of arrays */
+ const rows = utils.sheet_to_json(ws, { header: 1 });
+
+ /* create column array */
+ const range = utils.decode_range(ws["!ref"]||"A1");
+ const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
+ key: String(i), // RDG will access row["0"], row["1"], etc
+ name: utils.encode_col(i), // the column labels will be A, B, etc
+ editor: textEditor // enable cell editing
+ }));
+
+ return { rows, columns }; // these can be fed to setRows / setColumns
+}
+```
+
+In the other direction, a worksheet can be generated with `aoa_to_sheet`:
+
+```ts
+import { WorkSheet, utils } from 'xlsx';
+
+type Row = any[];
+
+function rdg_to_ws(rows: Row[]): WorkSheet {
+ return utils.aoa_to_sheet(rows);
+}
+```
+
+:::caution
+
+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.
+
+The snippet defines a `arrayify` function that creates arrays if necessary.
+
+```ts
+import { WorkSheet, utils } from 'xlsx';
+
+type Row = any[];
+
+// highlight-start
+function arrayify(rows: any[]): Row[] {
+ return rows.map(row => {
+ var length = Object.keys(row).length;
+ for(; length > 0; --length) if(row[length-1] != null) break;
+ return Array.from({length, ...row});
+ });
+}
+// highlight-end
+
+function rdg_to_ws(rows: Row[]): WorkSheet {
+ return utils.aoa_to_sheet(arrayify(rows));
+}
+```
+
+:::
+
+## Demo
+
+1) Create a new TypeScript `create-react-app` app:
+
+```bash
+npx create-react-app sheetjs-rdg --template typescript
+cd sheetjs-rdg
+```
+
+2) Install dependencies:
+
+```bash
+npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz react-data-grid
+```
+
+3) Download [`App.tsx`](pathname:///rdg/App.tsx) and replace `src/App.tsx`.
+
+```bash
+curl -L -o src/App.tsx https://docs.sheetjs.com/rdg/App.tsx
+```
+
+4) run `npm start`. When you load the page in the browser, it will attempt to
+ fetch and load the data.
diff --git a/docz/docs/03-demos/02-grid/index.md b/docz/docs/03-demos/02-grid/index.md
index 329cce6..124ab4c 100644
--- a/docz/docs/03-demos/02-grid/index.md
+++ b/docz/docs/03-demos/02-grid/index.md
@@ -91,129 +91,7 @@ with other buttons and components on the page.
### React Data Grid
-:::note
-
-This demo was tested against `react-data-grid 7.0.0-beta.15`, React 18.2.0,
-and `create-react-app` 5.0.1 on 2022 August 16.
-
-:::
-
-
-
-#### RDG Demo
-
-
-
-Complete Example (click to show)
-
-1) Create a new TypeScript `create-react-app` app:
-
-```bash
-npx create-react-app sheetjs-cra --template typescript
-cd sheetjs-cra
-```
-
-2) Install dependencies:
-
-```bash
-npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz react-data-grid
-```
-
-3) Download [`App.tsx`](pathname:///rdg/App.tsx) and replace `src/App.tsx`.
-
-4) run `npm start`. When you load the page in the browser, it will attempt to
- fetch and load the data.
-
-The following screenshot was taken from the demo:
-
-![react-data-grid screenshot](pathname:///rdg/rdg1.png)
-
-
-
-#### Rows and Columns state
-
-`react-data-grid` state consists of an Array of column metadata and an Array of
-row objects. Typically both are defined in state:
-
-```jsx
-import DataGrid, { Column } from "react-data-grid";
-
-export default function App() {
- const [rows, setRows] = useState([]);
- const [columns, setColumns] = useState([]);
-
- return ( );
-}
-```
-
-The most generic data representation is an array of arrays. To sate the grid,
-columns must be objects whose `key` property is the index converted to string:
-
-```ts
-import { WorkSheet, utils } from 'xlsx';
-import { textEditor, Column } from "react-data-grid";
-
-type Row = any[];
-type AOAColumn = Column;
-type RowCol = { rows: Row[]; columns: AOAColumn[]; };
-
-function ws_to_rdg(ws: WorkSheet): RowCol {
- /* create an array of arrays */
- const rows = utils.sheet_to_json(ws, { header: 1 });
-
- /* create column array */
- const range = utils.decode_range(ws["!ref"]||"A1");
- const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
- key: String(i), // RDG will access row["0"], row["1"], etc
- name: utils.encode_col(i), // the column labels will be A, B, etc
- editor: textEditor // enable cell editing
- }));
-
- return { rows, columns }; // these can be fed to setRows / setColumns
-}
-```
-
-In the other direction, a worksheet can be generated with `aoa_to_sheet`:
-
-```ts
-import { WorkSheet, utils } from 'xlsx';
-
-type Row = any[];
-
-function rdg_to_ws(rows: Row[]): WorkSheet {
- return utils.aoa_to_sheet(rows);
-}
-```
-
-:::caution
-
-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.
-
-The snippet defines a `arrayify` function that creates arrays if necessary.
-
-```ts
-import { WorkSheet, utils } from 'xlsx';
-
-type Row = any[];
-
-// highlight-start
-function arrayify(rows: any[]): Row[] {
- return rows.map(row => {
- var length = Object.keys(row).length;
- for(; length > 0; --length) if(row[length-1] != null) break;
- return Array.from({length, ...row});
- });
-}
-// highlight-end
-
-function rdg_to_ws(rows: Row[]): WorkSheet {
- return utils.aoa_to_sheet(arrayify(rows));
-}
-```
-
-:::
-
+**[The exposition has been moved to a separate page.](/docs/demos/grid/rdg)**
### Glide Data Grid
@@ -222,7 +100,7 @@ function rdg_to_ws(rows: Row[]): WorkSheet {
### Material UI Data Grid
Material UI Data Grid and React Data Grid share many state patterns and idioms.
-Differences from ["React Data Grid"](#react-data-grid) will be highlighted.
+Differences from ["React Data Grid"](/docs/demos/grid/rdg) will be highlighted.
[A complete example is included below.](#muidg-demo)
diff --git a/docz/docs/03-demos/04-static/01-lume.md b/docz/docs/03-demos/04-static/01-lume.md
index de96587..5ff7837 100644
--- a/docz/docs/03-demos/04-static/01-lume.md
+++ b/docz/docs/03-demos/04-static/01-lume.md
@@ -103,7 +103,7 @@ from the `"Presidents"` sheet:
:::note
-This was tested against `lume v1.15.3` on 2023 March 14.
+This was tested against `lume v1.16.2` on 2023 April 18.
This example uses the Nunjucks template format. Lume plugins support additional
template formats, including Markdown and JSX.
@@ -156,8 +156,16 @@ curl -L -o _data/pres.numbers https://sheetjs.com/pres.numbers
deno task lume --serve
```
-To verify it works, access http://localhost:3000 from your web browser.
-Adding a new row and saving `pres.numbers` should refresh the data
+To verify it works, access http://localhost:3000 from your web browser. Open
+`_data/pres.numbers` and add a new row to the bottom of the sheet. The page will
+refresh and show the new contents.
+
+:::caution
+
+There is a known bug with Deno hot reloading. If the page does not refresh
+automatically, upgrade with `deno upgrade` and restart the development server.
+
+:::
5) Stop the server (press `CTRL+C` in the terminal window) and run
diff --git a/docz/docs/03-demos/07-data/10-sql.md b/docz/docs/03-demos/07-data/10-sql.md
index 3200c23..69bcaef 100644
--- a/docz/docs/03-demos/07-data/10-sql.md
+++ b/docz/docs/03-demos/07-data/10-sql.md
@@ -143,66 +143,7 @@ types and other database minutiae.
**Knex**
-The result of a `SELECT` statement is an array of objects:
-
-```js
-const aoo = await connection.select("*").from("DataTable");
-const worksheet = XLSX.utils.json_to_sheet(aoa);
-```
-
-Knex wraps primitive types when creating a table. `generate_sql` takes a `knex`
-connection object and uses the API:
-
-Generating a Table (click to show)
-
-```js
-// define mapping between determined types and Knex types
-const PG = { "n": "float", "s": "text", "b": "boolean" };
-
-async function generate_sql(knex, ws, wsname) {
-
- // 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(wsname);
- await knex.schema.createTable(wsname, (table) => { hdr.forEach(h => { table[PG[types[h]] || "text"](h); }); });
- for(let i = 0; i < aoo.length; ++i) {
- if(!aoo[i] || !Object.keys(aoo[i]).length) continue;
- try { await knex.insert(aoo[i]).into(wsname); } catch(e) {}
- }
- return knex;
-}
-```
-
-
-
+**[The exposition has been moved to a separate page.](/docs/demos/data/knex)**
### Other SQL Databases
diff --git a/docz/docs/03-demos/07-data/14-knex.md b/docz/docs/03-demos/07-data/14-knex.md
new file mode 100644
index 0000000..3a7cd60
--- /dev/null
+++ b/docz/docs/03-demos/07-data/14-knex.md
@@ -0,0 +1,170 @@
+---
+title: Knex SQL Builder
+pagination_prev: demos/desktop/index
+pagination_next: demos/local/index
+sidebar_custom_props:
+ sql: true
+---
+
+:::note
+
+This demo was last tested on 2023 April 19 with Knex 2.4.2 and `better-sqlite`.
+
+:::
+
+## Integration Details
+
+#### Importing Data
+
+`sheet_to_json` generates an array of objects. An `INSERT` statement can be
+generated from each row object using `knex.insert`:
+
+```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);
+```
+
+#### Exporting Data
+
+The result of a `SELECT` statement is an array of objects:
+
+```js
+const aoo = await knex.select("*").from(table_name);
+const worksheet = XLSX.utils.json_to_sheet(aoo);
+```
+
+## Complete Example
+
+1) Install dependencies:
+
+```bash
+npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz knex better-sqlite3
+```
+
+2) Download the [test file](https://sheetjs.com/pres.numbers)
+
+```bash
+curl -LO https://sheetjs.com/pres.numbers
+```
+
+3) Save the following utility script to `sheetjsknex.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 };
+```
+
+4) Save the following to `SheetJSKnexTest.js`:
+
+```js title="SheetJSKnexTest.js"
+const { ws_to_knex, knex_to_ws } = require("./sheetjsknex");
+const Knex = require('knex');
+
+/* 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:
+
+```bash
+node SheetJSKnexTest.js
+```
+
+The script will generate two artifacts:
+
+`SheetJSKnex.xlsx` can be opened in a spreadsheet app or tested in the terminal:
+
+```bash
+npx xlsx-cli SheetJSKnex.xlsx
+```
+
+`SheetJSKnex.db` can be verified with the `sqlite3` command line tool:
+
+```bash
+sqlite3 SheetJSKnex.db 'select * from Test_Table'
+```
diff --git a/docz/docs/03-demos/09-cloud/09-azure.md b/docz/docs/03-demos/09-cloud/09-azure.md
index 17151b9..236f441 100644
--- a/docz/docs/03-demos/09-cloud/09-azure.md
+++ b/docz/docs/03-demos/09-cloud/09-azure.md
@@ -258,7 +258,7 @@ console.log(utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
```js title="SheetJSWriteToAzure.mjs"
import { BlobServiceClient } from "@azure/storage-blob";
-import { read, utils } from "xlsx";
+import { write, utils } from "xlsx";
/* replace these constants */
const connStr = "";
diff --git a/docz/docs/03-demos/10-extensions/05-osa.md b/docz/docs/03-demos/10-extensions/05-osa.md
index c708e1e..00ebc48 100644
--- a/docz/docs/03-demos/10-extensions/05-osa.md
+++ b/docz/docs/03-demos/10-extensions/05-osa.md
@@ -15,7 +15,7 @@ available as a global. A JS stub can expose methods from AppleScript scripts.
:::note
-This demo was last tested on 2022 April 18 in macOS Monterey.
+This demo was last tested on 2023 April 18 in macOS Monterey.
:::
@@ -218,7 +218,7 @@ extractResult(res)
on getContext()
set UnixPath to POSIX path of ((path to me as text) & "::")
- set libpath to POSIX path of (UnixPath & "xlsx.shim.js")
+ set libpath to POSIX path of (UnixPath & "xlsx.stub.js")
set {src, err} to current application's NSString's stringWithContentsOfFile:libpath encoding:(current application's NSISOLatin1StringEncoding) |error|:(reference)
set lang to current application's OSALanguage's languageForName:"JavaScript"
diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md
index 4f7af16..08219ca 100644
--- a/docz/docs/03-demos/index.md
+++ b/docz/docs/03-demos/index.md
@@ -34,7 +34,7 @@ run in the web browser, demos will include interactive examples.
- [`canvas-datagrid`](/docs/demos/grid/cdg)
- [`x-spreadsheet`](/docs/demos/grid/xs)
-- [`react-data-grid`](/docs/demos/grid#react-data-grid)
+- [`react-data-grid`](/docs/demos/grid/rdg)
- [`glide-data-grid`](/docs/demos/grid/gdg)
- [`vue3-table-lite`](/docs/demos/grid#vue3-table-lite)
- [`angular-ui-grid`](/docs/demos/grid#angular-ui-grid)
@@ -70,6 +70,8 @@ run in the web browser, demos will include interactive examples.
- [`Excel JavaScript API`](/docs/demos/extensions/excelapi)
- [`ExtendScript for Adobe Apps`](/docs/demos/extensions/extendscript)
- [`Chrome and Chromium Extensions`](/docs/demos/extensions/chromium)
+- [`Google Sheets + Apps Script`](/docs/demos/extensions/gsheet)
+- [`AppleScript and OSA`](/docs/demos/extensions/osa)
### Cloud Platforms
@@ -116,10 +118,13 @@ run in the web browser, demos will include interactive examples.
### Other Programming Languages
- [`JavaScript Engines`](/docs/demos/engines)
-- [`Duktape (C)`](/docs/demos/engines/duktape)
+- [`Duktape (C / Perl)`](/docs/demos/engines/duktape)
- [`JavaScriptCore (Swift)`](/docs/demos/engines/jsc)
- [`Rhino (Java)`](/docs/demos/engines/rhino)
+- [`Nashorn (Java)`](/docs/demos/engines/nashorn)
- [`Goja (Go)`](/docs/demos/engines/goja)
+- [`ChakraCore (C++)`](/docs/demos/engines/chakra)
+- [`QuickJS (C)`](/docs/demos/engines/quickjs)
- [`ExecJS (Ruby)`](/docs/demos/engines/rb)
- [`JavaScript::Engine (Perl)`](/docs/demos/engines/perl)
diff --git a/docz/docs/06-solutions/05-output.md b/docz/docs/06-solutions/05-output.md
index e0eac3d..60c1824 100644
--- a/docz/docs/06-solutions/05-output.md
+++ b/docz/docs/06-solutions/05-output.md
@@ -605,7 +605,7 @@ previewing and modifying structured data in the web browser.
-[`react-data-grid`](/docs/demos/grid#react-data-grid) is a data grid built for
+[`react-data-grid`](/docs/demos/grid/rdg) is a data grid built for
React. It uses two properties: `rows` of data objects and `columns` which
describe the columns. The grid API can play nice with an array of arrays.
@@ -616,6 +616,8 @@ import { useEffect, useState } from "react";
import DataGrid from "react-data-grid";
import { read, utils } from "xlsx";
+import 'react-data-grid/lib/styles.css';
+
const url = "https://oss.sheetjs.com/test_files/RkNumber.xls";
export default function App() {
diff --git a/docz/static/rdg/App.tsx b/docz/static/rdg/App.tsx
index 1c87782..2ec3fe3 100644
--- a/docz/static/rdg/App.tsx
+++ b/docz/static/rdg/App.tsx
@@ -2,6 +2,7 @@ import React, { useEffect, useState, ChangeEvent } from "react";
import DataGrid, { textEditor, Column } from "react-data-grid";
import { read, utils, WorkSheet, writeFile } from "xlsx";
+import 'react-data-grid/lib/styles.css';
import './App.css';
type DataSet = { [index: string]: WorkSheet; };