diff --git a/docz/docs/03-demos/32-extensions/09-mathematica.md b/docz/docs/03-demos/32-extensions/09-mathematica.md
index 9511a93..7579ac3 100644
--- a/docz/docs/03-demos/32-extensions/09-mathematica.md
+++ b/docz/docs/03-demos/32-extensions/09-mathematica.md
@@ -25,7 +25,12 @@ data from opaque spreadsheets and parse the data from Mathematica.
:::note Tested Deployments
-This demo was last tested by SheetJS users on 2023 November 04 in Mathematica 13.
+This demo was tested by SheetJS users in the following deployments:
+
+| Architecture | Version | Date |
+|:-------------|:--------|:-----------|
+| `darwin-x64` | `14.0` | 2024-06-05 |
+| `win10-x64` | `14.0` | 2024-06-05 |
:::
@@ -81,7 +86,7 @@ Dataset object assuming one header row.
-```mathematica title="Complete Function"
+```mathematica title="SheetJSImportFileEE"
(* Import file stored in the Documents folder (e.g. C:\Users\Me\Documents) *)
SheetJSImportFileEE[filename_]:=Module[{csv}, (
(* This was required in local testing *)
@@ -107,7 +112,7 @@ SheetJSImportFileEE[filename_]:=Module[{csv}, (
-```mathematica title="Complete Function"
+```mathematica title="SheetJSImportFileEE"
(* Import file stored in the Documents folder (e.g. C:\Users\Me\Documents) *)
SheetJSImportFileEE[filename_]:=Module[{csv}, (
(* This was required in local testing *)
@@ -133,70 +138,6 @@ SheetJSImportFileEE[filename_]:=Module[{csv}, (
-
- How to run the example (click to hide)
-
-:::note Tested Deployments
-
-This example was last tested on 2023 November 04 with Mathematica 13.3.
-
-:::
-
-0) Install NodeJS. When the demo was tested, version `20.9.0` was installed.
-
-1) Install dependencies in the Home folder (`~` or `$HOME` or `%HOMEPATH%`):
-
-{`\
-npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz zeromq@6.0.0-beta.17`}
-
-
-2) Open a new Mathematica Notebook and register NodeJS. When the example was
-tested in Windows, the commands were:
-
-
-
-
-```mathematica
-RegisterExternalEvaluator["NodeJS","/usr/local/bin/node"]
-FindExternalEvaluators["NodeJS"]
-```
-
-
-
-
-```mathematica
-RegisterExternalEvaluator["NodeJS","C:\\Program Files\\nodejs\\node.exe"]
-FindExternalEvaluators["NodeJS"]
-```
-
-
-
-
-The second argument to `RegisterExternalEvaluator` should be the path to the
-`node` or `node.exe` binary.
-
-If NodeJS is registered, the value in the "Registered" column will be "True".
-
-4) To determine the base folder, run `require("process").cwd()` from NodeJS:
-
-```mathematica
-ExternalEvaluate["NodeJS", "require('process').cwd()"]
-```
-
-5) Download [`pres.numbers`](https://docs.sheetjs.com/pres.numbers) and move
-the file to the base folder as shown in the previous step.
-
-6) Copy and evaluate the "Complete Function" in the previous codeblock.
-
-7) Run the function and confirm the result is a proper Dataset:
-
-```mathematica
-SheetJSImportFileEE["pres.numbers"]
-```
-
-![SheetJSImportFileEE result](pathname:///mathematica/SheetJSImportFileEE.png)
-
-
### Command-Line Tools
@@ -236,31 +177,177 @@ flowchart LR
## Complete Demo
-1) Create the standalone `xlsx-cli` binary[^14]:
+This demo tests the NodeJS external engine and dedicated command line tools.
+
+### NodeJS Engine
+
+0) Install NodeJS. When the demo was tested, version `20.14.0` was installed.
+
+1) Install dependencies in the Home folder (`~` or `$HOME` or `%HOMEPATH%`):
+
+{`\
+npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz zeromq@6.0.0-beta.19`}
+
+
+2) Open a new Mathematica Notebook and register NodeJS. When the example was
+tested in Windows, the commands were:
+
+
+
+
+```mathematica
+RegisterExternalEvaluator["NodeJS","/usr/local/bin/node"]
+FindExternalEvaluators["NodeJS"]
+```
+
+The second argument to `RegisterExternalEvaluator` should be the path to the
+`node` program, which can be found by running the following command in a new
+terminal window:
+
+```bash
+which node
+```
+
+
+
+
+```mathematica
+RegisterExternalEvaluator["NodeJS","C:\\Program Files\\nodejs\\node.exe"]
+FindExternalEvaluators["NodeJS"]
+```
+
+The second argument to `RegisterExternalEvaluator` should be the path to the
+`node.exe` program, which can be found by running the following command in a new
+PowerShell window:
+
+```powershell
+Get-Command node.exe
+```
+
+
+
+
+If NodeJS is registered, the value in the "Registered" column will be "True".
+
+4) To determine the base folder, run `require("process").cwd()` from NodeJS:
+
+```mathematica
+ExternalEvaluate["NodeJS", "require('process').cwd()"]
+```
+
+5) Download [`pres.numbers`](https://docs.sheetjs.com/pres.numbers) and move
+the file to the base folder as shown in the previous step.
+
+3) Copy, but do not run, the following snippet into the running notebook:
+
+
+
+
+```mathematica title="SheetJSImportFileEE"
+(* Import file stored in the Documents folder (e.g. C:\Users\Me\Documents) *)
+SheetJSImportFileEE[filename_]:=Module[{csv}, (
+ (* This was required in local testing *)
+ /* highlight-next-line */
+ RegisterExternalEvaluator["NodeJS","/usr/local/bin/node"];
+
+ (* Generate CSV from first sheet *)
+ csv:=ExternalEvaluate["NodeJS", StringJoin[
+ (* module installed in home directory *)
+ "var XLSX = require('xlsx');",
+ (* read specified filename *)
+ "var wb = XLSX.readFile('",filename,"');",
+ (* grab first worksheet *)
+ "var ws = wb.Sheets[wb.SheetNames[0]];",
+ (* convert to CSV *)
+ "XLSX.utils.sheet_to_csv(ws)"
+ ]];
+
+ (* Parse CSV into a dataset *)
+ Return[ImportString[csv, "Dataset", "HeaderLines"->1]];
+)]
+```
+
+
+
+
+```mathematica title="SheetJSImportFileEE"
+(* Import file stored in the Documents folder (e.g. C:\Users\Me\Documents) *)
+SheetJSImportFileEE[filename_]:=Module[{csv}, (
+ (* This was required in local testing *)
+ /* highlight-next-line */
+ RegisterExternalEvaluator["NodeJS","C:\\Program Files\\nodejs\\node.exe"];
+
+ (* Generate CSV from first sheet *)
+ csv:=ExternalEvaluate["NodeJS", StringJoin[
+ (* module installed in home directory *)
+ "var XLSX = require('xlsx');",
+ (* read specified filename *)
+ "var wb = XLSX.readFile('",filename,"');",
+ (* grab first worksheet *)
+ "var ws = wb.Sheets[wb.SheetNames[0]];",
+ (* convert to CSV *)
+ "XLSX.utils.sheet_to_csv(ws)"
+ ]];
+
+ (* Parse CSV into a dataset *)
+ Return[ImportString[csv, "Dataset", "HeaderLines"->1]];
+)]
+```
+
+
+
+
+After pasting, edit the highlighted line to reflect the path of the `node` or
+`node.exe` binary. This path was discovered in Step 2.
+
+After editing the snippet, run the expression.
+
+7) Run the function and confirm the result is a proper Dataset:
+
+```mathematica
+SheetJSImportFileEE["pres.numbers"]
+```
+
+![SheetJSImportFileEE result](pathname:///mathematica/SheetJSImportFileEE.png)
+
+### Standalone Binary
+
+8) Create the standalone `xlsx-cli` binary[^14]. The commands should be run in a
+Terminal or PowerShell window:
{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz exit-on-epipe commander@2
curl -LO https://docs.sheetjs.com/cli/xlsx-cli.js
-npx nexe -t 14.15.3 xlsx-cli.js`}
+npx -y nexe -t 14.15.3 xlsx-cli.js`}
-2) Move the generated `xlsx-cli` to a fixed location in `/usr/local/bin`:
+9) Move the generated `xlsx-cli` to a fixed location in `/usr/local/bin`:
```bash
mkdir -p /usr/local/bin
mv xlsx-cli /usr/local/bin/
```
+:::note pass
+
+If there are permission errors, the command should be run with the root user:
+
+```bash
+sudo mv xlsx-cli /usr/local/bin/
+```
+
+:::
+
-2) Find the current directory:
+9) Find the current directory:
```bash
-cd
+pwd
```
The generated binary will be `xlsx-cli.exe` in the displayed path.
@@ -270,9 +357,12 @@ The generated binary will be `xlsx-cli.exe` in the displayed path.
### Reading a Local File
-3) In a new Mathematica notebook, run the following snippet:
+
+
-```mathematica
+10) In a new Mathematica notebook, run the following snippet:
+
+```mathematica title="SheetJSImportFile"
SheetJSImportFile[x_] := ImportString[Block[{Print}, ExternalEvaluate[
"Shell" -> "StandardOutput",
// highlight-next-line
@@ -280,17 +370,24 @@ SheetJSImportFile[x_] := ImportString[Block[{Print}, ExternalEvaluate[
]], "Dataset", "HeaderLines" -> 1]
```
-
-
-
+10) In a new Mathematica notebook, copy but do not run the following snippet:
+
+```mathematica title="SheetJSImportFile"
+SheetJSImportFile[x_] := ImportString[Block[{Print}, ExternalEvaluate[
+ "Shell" -> "StandardOutput",
+ // highlight-next-line
+ "/usr/local/bin/xlsx-cli " <> x
+]], "Dataset", "HeaderLines" -> 1]
+```
+
Change `/usr/local/bin/xlsx-cli` in the string to the path to the generated
`xlsx-cli.exe` binary. For example, if the path in step 2 was
`C:\Users\Me\Documents\`, then the code should be:
-```mathematica
+```mathematica title="SheetJSImportFile"
SheetJSImportFile[x_] := ImportString[Block[{Print}, ExternalEvaluate[
"Shell" -> "StandardOutput",
// highlight-next-line
@@ -298,25 +395,58 @@ SheetJSImportFile[x_] := ImportString[Block[{Print}, ExternalEvaluate[
]], "Dataset", "HeaderLines" -> 1]
```
-The `\` characters must be doubled.
+:::info pass
+
+Mathematica requires the `\` characters must be doubled.
+
+:::
+
+After making the change, run the snippet.
-4) Download https://docs.sheetjs.com/pres.numbers and save to Downloads folder:
+11) Download https://docs.sheetjs.com/pres.numbers and save to Downloads folder:
```bash
cd ~/Downloads/
curl -LO https://docs.sheetjs.com/pres.numbers
```
-5) In the Mathematica notebook, run the new function. If the file was saved to
+12) In the Mathematica notebook, run the new function. If the file was saved to
the Downloads folder, the path will be `"~/Downloads/pres.numbers"` in macOS:
+
+
+
```mathematica
data = SheetJSImportFile["~/Downloads/pres.numbers"]
```
+
+
+
+On Windows, the absolute path to the file must be used. To find this path, run
+the following commands in PowerShell:
+
+```powershell
+cd $HOME\Downloads
+pwd
+```
+
+Append `\\pres.numbers` to the displayed path. For example, if the path was
+`C:\Users\Me\Downloads`, the command will be
+
+```mathematica
+data = SheetJSImportFile["C:\\Users\\Me\\Downloads\\pres.numbers"]
+```
+
+The `\` characters must be doubled.
+
+
+
+
+
The result should be displayed in a concise table.
![SheetJSImportFile result](pathname:///mathematica/SheetJSImportFile.png)
@@ -326,9 +456,9 @@ The result should be displayed in a concise table.
`FetchURL`[^15] downloads a file from a specified URL and returns a path to the
file. This function will be wrapped in a new function called `SheetJSImportURL`.
-6) In the same notebook, run the following:
+13) In the same notebook, run the following:
-```mathematica
+```mathematica title="SheetJSImportURL"
Needs["Utilities`URLTools`"];
SheetJSImportURL[x_] := Module[{path},(
path = FetchURL[x];
@@ -336,7 +466,7 @@ SheetJSImportURL[x_] := Module[{path},(
)];
```
-7) Test by downloading the test file in the notebook:
+14) Test by downloading the test file in the notebook:
```mathematica
data = SheetJSImportURL["https://docs.sheetjs.com/pres.numbers"]