From 67294eeeae8d4ba8a354761cfb45aeacea30c293 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Wed, 5 Jun 2024 23:56:24 -0400 Subject: [PATCH] Mathematica Extension demo refresh --- .../03-demos/32-extensions/09-mathematica.md | 298 +++++++++++++----- 1 file changed, 214 insertions(+), 84 deletions(-) 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"]