diff --git a/docz/docs/03-demos/09-cloud/11-aws.md b/docz/docs/03-demos/09-cloud/11-aws.md index 01bb6be1..647b2b8d 100644 --- a/docz/docs/03-demos/09-cloud/11-aws.md +++ b/docz/docs/03-demos/09-cloud/11-aws.md @@ -354,8 +354,8 @@ required in NodeJS scripts. ### Connecting to S3 -The `AWS` module includes a function `S3` that performs the connection. Access -keys for an IAM user[^9] must be used: +The `aws-sdk` module includes a function `S3` that performs the connection. +Access keys for an IAM user[^9] must be used: ```js /* credentials */ diff --git a/docz/docs/03-demos/32-extensions/09-mathematica.md b/docz/docs/03-demos/32-extensions/09-mathematica.md index ce559aab..a01f484b 100644 --- a/docz/docs/03-demos/32-extensions/09-mathematica.md +++ b/docz/docs/03-demos/32-extensions/09-mathematica.md @@ -21,7 +21,7 @@ data from opaque spreadsheets and parse the data from Mathematica. :::note -This demo was last tested in 2023 August 21 in Mathematica 13.2.1. +This demo was last tested by SheetJS users on 2023 August 21 in Mathematica 13. ::: diff --git a/docz/docs/03-demos/32-extensions/11-matlab.md b/docz/docs/03-demos/32-extensions/11-matlab.md index e7307391..8acba17c 100644 --- a/docz/docs/03-demos/32-extensions/11-matlab.md +++ b/docz/docs/03-demos/32-extensions/11-matlab.md @@ -21,7 +21,7 @@ spreadsheets into simple XLSX files for MATLAB. :::note -This demo was last tested in 2023 September 12 in MATLAB R2023a. +This demo was last tested by SheetJS users on 2023 September 12 in MATLAB R2023a. ::: diff --git a/docz/docs/03-demos/32-extensions/12-maple.md b/docz/docs/03-demos/32-extensions/12-maple.md new file mode 100644 index 00000000..f63f2f7e --- /dev/null +++ b/docz/docs/03-demos/32-extensions/12-maple.md @@ -0,0 +1,209 @@ +--- +title: Modern Spreadsheets in Maple +sidebar_label: Maple +pagination_prev: demos/cloud/index +pagination_next: demos/bigdata/index +--- + +import current from '/version.js'; +import CodeBlock from '@theme/CodeBlock'; + +[Maple](https://www.maplesoft.com/products/Maple/) is a numeric computing +platform. It offers a robust C-based extension system. + +[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing +data from spreadsheets. + +This demo uses SheetJS to pull data from a spreadsheet for further analysis +within Maple. We'll create a Maple native extension that loads the +[Duktape](/docs/demos/engines/duktape) JavaScript engine and uses the SheetJS +library to read data from spreadsheets and converts to a Maple-friendly format. + +```mermaid +flowchart LR + ofile[(workbook\nXLSB file)] + nfile[(clean file\nXLSX)] + data[[Maple\nTable]] + ofile --> |Maple Extension\nSheetJS + Duktape| nfile + nfile --> |ExcelTools\nImport|data +``` + +:::note + +This demo was last tested by SheetJS users on 2023 October 3 in Maple 2023. + +::: + +:::info pass + +Maple has limited support for processing spreadsheets through the `ExcelTools` +package[^1]. At the time of writing, it lacked support for XLSB, NUMBERS, and +other common spreadsheet formats. + +SheetJS libraries help fill the gap by normalizing spreadsheets to a form that +Maple can understand. + +::: + +## Integration Details + +The current recommendation involves a native plugin that reads arbitrary files +and generates clean XLSX files that Maple can import. + +The extension function ultimately pairs the SheetJS `read`[^2] and `write`[^3] +methods to read data from the old file and write a new file: + +```js +var wb = XLSX.read(original_file_data, {type: "buffer"}); +var new_file_data = XLSX.write(wb, {type: "array", bookType: "xlsx"}); +``` + +The extension function will receive a file name and perform the following steps: + +```mermaid +flowchart LR + ofile{{File\nName}} + subgraph JS Operations + ojbuf[(Buffer\nFile Bytes)] + wb(((SheetJS\nWorkbook))) + njbuf[(Buffer\nXLSX bytes)] + end + obuf[(File\nbytes)] + nbuf[(New file\nbytes)] + nfile[(XLSX\nFile)] + ofile --> |C\nRead File| obuf + obuf --> |Duktape\nBuffer Ops| ojbuf + ojbuf --> |SheetJS\n`read`| wb + wb --> |SheetJS\n`write`| njbuf + njbuf --> |Duktape\nBuffer Ops| nbuf + nbuf --> |C\nWrite File| nfile +``` + +### C Extensions + +Maple C extensions are shared libraries or DLLs that use special Maple methods +for parsing arguments and returning values. + +To simplify the flow, the new function will take one argument (the original file +name) and return one value (the new file name). + +The official documentation has a comprehensive list[^4] of methods. For this +demo, the following methods are used: + +- `MapleNumArgs` and `IsMapleString` are used in argument validation. The demo +function will raise a Maple exception if no file name is specified. + +- `MapleRaiseError` and `MapleRaiseError2` programmatically raise errors. + +- `MapleToString` and `ToMapleString` convert between Maple and C strings. + +### Duktape JS Engine + +This demo uses the [Duktape JavaScript engine](/docs/demos/engines/duktape). The +SheetJS + Duktape demo covers engine integration details in more detail. + +The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone) +can be loaded in Duktape by reading the source from the filesystem. + +## Complete Demo + +:::info pass + +This demo was tested in Windows x64. The path names and build commands will +differ in other platforms and operating systems. + +::: + +The [`sheetjs-maple.c`](pathname:///maple/sheetjs-maple.c) extension exports the +`SheetToXLSX` Maple method. It takes a file name argument, parses the specified +file, exports data to `sheetjsw.xlsx` and returns the string `"sheetjsw.xlsx"`. + +This can be chained with `Import` from `ExcelTools`: + +```maple +with(ExcelTools); +Import(SheetToXLSX("pres.numbers")) +``` + +0) Ensure "Windows Subsystem for Linux" (WSL) and Visual Studio are installed. + +1) Open a new "x64 Native Tools Command Prompt" window and create a project +folder `c:\sheetjs-maple`: + +```powershell +cd c:\ +mkdir sheetjs-maple +cd sheetjs-maple +``` + +2) Copy the headers and `lib` files from the Maple folder to the project folder. +For example, using Maple 2023 on Windows x64: + +```powershell +copy "C:\Program Files\Maple 2023\extern\include\"*.h . +copy "c:\Program Files\Maple 2023\bin.x86_64_WINDOWS"\*.lib . +``` + +3) Run `bash` to enter WSL + +4) Within WSL, install Duktape: + +```bash +curl -LO https://duktape.org/duktape-2.7.0.tar.xz +tar -xJf duktape-2.7.0.tar.xz +mv duktape-2.7.0/src/*.{c,h} . +``` + +5) Still within WSL, download SheetJS scripts and the test file. + +{`\ +curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js +curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js +curl -LO https://sheetjs.com/pres.numbers`} + + +6) Still within WSL, download the extension C code + +```bash +curl -LO https://docs.sheetjs.com/maple/sheetjs-maple.c +``` + +7) Exit WSL by running `exit`. The window will return to the command prompt. + +8) Build the extension DLL: + +```powershell +cl -Gz sheetjs-maple.c duktape.c /EHsc -link -dll -out:sheetjs-maple.dll maplec.lib +``` + +9) Close and re-open Maple, then create a new Maple Worksheet or Document + +10) Run the following command in Maple to change the working directory: + +```maple +currentdir("c:\\sheetjs-maple"); +``` + +11) Load the `SheetToXLSX` method from the extension: + +```maple +with(ExternalCalling): +dll:=ExternalLibraryName("sheetjs-maple"): +SheetToXLSX:=DefineExternal("SheetToXLSX",dll): +``` + +12) Read the `pres.numbers` test file: + +```maple +with(ExcelTools); +Import(SheetToXLSX("pres.numbers")) +``` + +The result will show the data from `pres.numbers` + +![Maple Screenshot](pathname:///maple/maple.png) + +[^1]: See ["ExcelTools"](https://www.maplesoft.com/support/help/Maple/view.aspx?path=ExcelTools) in the Maple documentation. +[^2]: See [`read` in "Reading Files"](/docs/api/parse-options) +[^3]: See [`write` in "Writing Files"](/docs/api/write-options) +[^4]: See ["C OpenMaple and ExternalCalling Application Program Interface (API)"](https://www.maplesoft.com/support/help/maple/view.aspx?path=OpenMaple%2FC%2FAPI) in the Maple documentation. \ No newline at end of file diff --git a/docz/static/maple/maple.png b/docz/static/maple/maple.png new file mode 100644 index 00000000..3234b655 Binary files /dev/null and b/docz/static/maple/maple.png differ diff --git a/docz/static/maple/sheetjs-maple.c b/docz/static/maple/sheetjs-maple.c new file mode 100644 index 00000000..e49ced14 --- /dev/null +++ b/docz/static/maple/sheetjs-maple.c @@ -0,0 +1,115 @@ +#include +#include +#include +#include "maplec.h" +#include "duktape.h" + +/* --- EXPORT_DECL macro from official example --- */ + +#if !defined(EXPORT_DECL) +#ifdef _MSC_VER +#define EXPORT_DECL __declspec(dllexport) +#else +#define EXPORT_DECL +#endif +#endif + +/* --- the SheetJS + Duktape demo cover these machinations --- */ + +#define FAIL_LOAD { \ + duk_push_undefined(ctx); \ + perror("Error in load_file"); \ + return 1; \ +} + +static char *read_file(const char *filename, size_t *sz) { + FILE *f = fopen(filename, "rb"); + if(!f) return NULL; + long fsize; { fseek(f, 0, SEEK_END); fsize = ftell(f); fseek(f, 0, SEEK_SET); } + char *buf = (char *)malloc(fsize * sizeof(char)); + *sz = fread((void *) buf, 1, fsize, f); + fclose(f); + return buf; +} + +static duk_int_t eval_file(duk_context *ctx, const char *filename) { + size_t len; char *buf = read_file(filename, &len); + if(!buf) FAIL_LOAD + + duk_push_lstring(ctx, (const char *)buf, (duk_size_t)len); + duk_int_t retval = duk_peval(ctx); + duk_pop(ctx); + return retval; +} + +static duk_int_t load_file(duk_context *ctx, const char *filename, const char *var) { + size_t len; char *buf = read_file(filename, &len); + if(!buf) FAIL_LOAD + + duk_push_external_buffer(ctx); + duk_config_buffer(ctx, -1, buf, len); + duk_put_global_string(ctx, var); + return 0; +} + +static duk_int_t save_file(duk_context *ctx, const char *filename, const char *var) { + duk_get_global_string(ctx, var); + duk_size_t sz; + char *buf = (char *)duk_get_buffer_data(ctx, -1, &sz); + + if(!buf) return 1; + FILE *f = fopen(filename, "wb"); fwrite(buf, 1, sz, f); fclose(f); + return 0; +} + +#define FAIL_DUK(cmd) { \ + const char *errmsg = duk_safe_to_string(ctx, -1); \ + duk_destroy_heap(ctx); \ + MapleRaiseError2(kv, "error in %1 : %2", ToMapleString(kv, cmd), ToMapleString(kv, errmsg)); \ + return NULL; \ +} + +#define DOIT(cmd) duk_eval_string_noresult(ctx, cmd); + +/* SheetToXLSX function */ +EXPORT_DECL ALGEB M_DECL SheetToXLSX( MKernelVector kv, ALGEB *args ) { + duk_int_t res = 0; + + /* get filename */ + if(MapleNumArgs(kv, (ALGEB)args) != 1) { + MapleRaiseError(kv, "must specify a filename"); + return NULL; + } + if(!IsMapleString(kv, args[1])) { + MapleRaiseError(kv, "filename must be a string"); + return NULL; + } + const char *filename = MapleToString(kv, args[1]); + + /* initialize duktape */ + duk_context *ctx = duk_create_heap_default(); + /* duktape does not expose a standard "global" by default */ + DOIT("var global = (function(){ return this; }).call(null);"); + + /* load SheetJS library */ + res = eval_file(ctx, "shim.min.js"); + if(res != 0) FAIL_DUK("shim load") + res = eval_file(ctx, "xlsx.full.min.js"); + if(res != 0) FAIL_DUK("library load") + + /* read file */ + res = load_file(ctx, filename, "buf"); + if(res != 0) FAIL_DUK("file load") + printf("Loaded file %s\n", filename); + + /* parse workbook and write to XLSX */ + DOIT("wb = XLSX.read(buf.slice(0, buf.length), {type:'buffer'});"); + DOIT("newbuf = (XLSX.write(wb, {type:'array', bookType:'xlsx'}));");\ + + /* write file */ + res = save_file(ctx, "sheetjsw.xlsx", "newbuf");\ + if(res != 0) FAIL_DUK("save sheetjsw.xlsx") + + /* return filename */ + return ToMapleString(kv, "sheetjsw.xlsx"); +}