This commit is contained in:
SheetJS 2023-10-03 18:58:43 -04:00
parent 5bce4b2705
commit 62641b5da6
6 changed files with 328 additions and 4 deletions

@ -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:
/* credentials */

@ -21,7 +21,7 @@ data from opaque spreadsheets and parse the data from Mathematica.
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.

@ -21,7 +21,7 @@ spreadsheets into simple XLSX files for MATLAB.
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.

@ -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]( is a numeric computing
platform. It offers a robust C-based extension system.
[SheetJS]( 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.
flowchart LR
ofile[(workbook\nXLSB file)]
nfile[(clean file\nXLSX)]
ofile --> |Maple Extension\nSheetJS + Duktape| nfile
nfile --> |ExcelTools\nImport|data
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:
var wb =, {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:
flowchart LR
subgraph JS Operations
ojbuf[(Buffer\nFile Bytes)]
njbuf[(Buffer\nXLSX bytes)]
nbuf[(New file\nbytes)]
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`:
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`:
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:
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:
curl -LO
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.
<CodeBlock language="bash">{`\
curl -LO${current}/package/dist/shim.min.js
curl -LO${current}/package/dist/xlsx.full.min.js
curl -LO`}
6) Still within WSL, download the extension C code
curl -LO
7) Exit WSL by running `exit`. The window will return to the command prompt.
8) Build the extension DLL:
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:
11) Load the `SheetToXLSX` method from the extension:
12) Read the `pres.numbers` test file:
The result will show the data from `pres.numbers`
![Maple Screenshot](pathname:///maple/maple.png)
[^1]: See ["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)"]( in the Maple documentation.

docz/static/maple/maple.png Normal file

Binary file not shown.


Width:  |  Height:  |  Size: 12 KiB

@ -0,0 +1,115 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "maplec.h"
#include "duktape.h"
/* --- EXPORT_DECL macro from official example --- */
#if !defined(EXPORT_DECL)
#ifdef _MSC_VER
#define EXPORT_DECL __declspec(dllexport)
/* --- 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);
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);
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_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 =, 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");