This commit is contained in:
SheetJS 2023-08-02 22:49:32 -04:00
parent ba3ffafe47
commit fecf228fed
6 changed files with 228 additions and 63 deletions

View File

@ -1006,7 +1006,7 @@ When the app is loaded, the data will be displayed in rows.
[^4]: See ["Workbook Object"](/docs/csf/book)
[^5]: See ["Workbook Object"](/docs/csf/book)
[^6]: See ["Sheet Objects"](/docs/csf/sheet)
[^7]: See [`sheet_to_csv` in "Utilities"](/docs/api/utilities/csv#csv-output)
[^7]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output)
[^8]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^9]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^10]: See [`!merges` in "Sheet Objects"](/docs/csf/sheet#worksheet-object)

View File

@ -1,13 +1,11 @@
---
title: NetSuite
sidebar_title: NetSuite
title: Spreadsheets in NetSuite SuiteScripts
sidebar_label: NetSuite
description: Automate the NetSuite ERP platform with SuiteScripts. Effortlessly read and write spreadsheets using SheetJS. Modernize Excel-powered business processes with confidence.
pagination_prev: demos/local/index
pagination_next: demos/extensions/index
---
# Spreadsheets in NetSuite SuiteScripts
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
@ -29,6 +27,7 @@ This demo was verified by NetSuite consultants in the following deployments:
| ScheduledScript | 2.1 | 2023-03-09 |
| Restlet | 2.1 | 2023-04-20 |
| Suitelet | 2.1 | 2023-07-21 |
| MapReduceScript | 2.1 | 2023-07-31 |
:::

View File

@ -1,13 +1,11 @@
---
title: C + QuickJS
title: Data Processing with QuickJS
sidebar_label: C + QuickJS
description: Process structured data in C programs. Seamlessly integrate spreadsheets into your program by pairing QuickJS and SheetJS. Supercharge programs with modern data tools.
pagination_prev: demos/bigdata/index
pagination_next: solutions/input
---
# Data Processing with QuickJS
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';

View File

@ -1,5 +1,7 @@
---
title: C++ + Hermes
title: Sharing Sheets with Hermes
sidebar_label: C++ + Hermes
description: Process structured data in C++ programs. Seamlessly integrate spreadsheets into your program by pairing Hermes and SheetJS. Handle the most complex files without breaking a sweat.
pagination_prev: demos/bigdata/index
pagination_next: solutions/input
---
@ -7,62 +9,115 @@ pagination_next: solutions/input
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
Hermes is an embeddable JS engine written in C++. With some light shims, it can
run the standalone browser scripts.
[Hermes](https://hermesengine.dev/) is an embeddable JS engine written in C++.
The [Standalone scripts](/docs/getting-started/installation/standalone) can be
parsed and evaluated in a Hermes context.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
:::caution Here be Dragons
This demo uses Hermes and SheetJS to pull data from a spreadsheet and print CSV
rows. We'll explore how to load SheetJS in a Hermes context and process
spreadsheets from a C++ program.
The ["Integration Example"](#integration-example) section includes a complete
command-line tool for reading data from files.
## Integration Details
:::info pass
Many Hermes functions are not documented. The explanation was verified against
commit `70af78b`.
:::
:::warning pass
The main target for Hermes is React Native. At the time of writing, there was
no official documentation for embedding the Hermes engine in C++ programs.
:::
## Integration Details
### Initialize Hermes
_Initialize Hermes_
The runtime can be initialized in one line:
A Hermes engine instance is created with `facebook::hermes::makeHermesRuntime`:
```cpp
std::unique_ptr<facebook::jsi::Runtime> rt(facebook::hermes::makeHermesRuntime());
```
Hermes does not expose a `console` or `global` variable, but those can be
synthesized from JS code in the runtime.
_Essential Objects_
:::note pass
Hermes does not expose a `console` or `global` variable, but they can be
synthesized from JS code in the runtime:
The JavaScript code is shown below:
- `global` can be obtained from a reference to `this` in an unbound function:
```js
/* create global object */
var global = (function(){ return this; }).call(null);
```
- `console.log` can be constructed from the builtin `print` function:
```js
/* create a fake `console` from the hermes `print` builtin */
var console = { log: function(x) { print(x); } };
```
:::
The code can be stored in a C string and evaluated after creating a runtime:
The code can be stored in a C string and evaluated using `prepareJavascript` to
prepare code and `evaluatePreparedJavascript` to evaluate:
```cpp
auto src = std::make_shared<facebook::jsi::StringBuffer>(
const char *init_code =
/* create global object */
"var global = (function(){ return this; }).call(null);"
/* create a fake `console` from the hermes `print` builtin */
"var console = { log: function(x) { print(x); } };"
);
;
auto src = std::make_shared<facebook::jsi::StringBuffer>(init_code);
auto js = rt->prepareJavaScript(src, std::string("<eval>"));
rt->evaluatePreparedJavaScript(js);
```
_Load SheetJS Scripts_
:::info Exception handling
Standard C++ exception handling patterns are used in Hermes integration code.
The base class for Hermes exceptions is `facebook::jsi::JSIException`:
```cpp
try {
const char *init_code = "...";
auto src = std::make_shared<facebook::jsi::StringBuffer>(init_code);
auto js = rt->prepareJavaScript(src, std::string("<eval>"));
rt->evaluatePreparedJavaScript(js);
} catch (const facebook::jsi::JSIException &e) {
std::cerr << "JavaScript exception: " << e.what() << std::endl;
return 1;
}
```
:::
### Load SheetJS Scripts
[SheetJS Standalone scripts](/docs/getting-started/installation/standalone) can
be parsed and evaluated in a Hermes context.
The main library can be loaded by reading the script from the file system and
evaluating in the Hermes context:
evaluating in the Hermes context.
:::tip pass
There are nonstandard tricks to embed the entire script in the binary. There are
language proposals such as `#embed` (mirroring the same feature in C23).
For simplicity, the examples read the script file from the filesystem.
:::
_Reading scripts from the filesystem_
For the purposes of this demo, the standard C `<stdio.h>` methods are used:
```cpp
static char *read_file(const char *filename, size_t *sz) {
@ -75,6 +130,17 @@ static char *read_file(const char *filename, size_t *sz) {
return buf;
}
// ...
/* read SheetJS library from filesystem */
size_t sz; char *xlsx_full_min_js = read_file("xlsx.full.min.js", &sz);
```
_Hermes Wrapper_
Hermes does not provide a friendly way to prepare JavaScript code stored in a
standard heap-allocated C string. Fortunately a wrapper can be created:
```cpp
/* Unfortunately the library provides no C-friendly Buffer classes */
class CBuffer : public facebook::jsi::Buffer {
public:
@ -89,24 +155,67 @@ class CBuffer : public facebook::jsi::Buffer {
// ...
/* load SheetJS library */
size_t sz; char *xlsx_full_min_js = read_file("xlsx.full.min.js", &sz);
auto src = std::make_shared<CBuffer>(CBuffer((uint8_t *)xlsx_full_min_js, sz));
```
_Evaluating SheetJS Library Code_
The code wrapper can be "prepared" with `prepareJavascript` and "evaluated" with
`evaluatePreparedJavascript`.
The second argument to `preparedJavascript` is a C++ `std::string` that holds
the source URL. Typically a name like `xlsx.full.min.js` helps distinguish
SheetJS library exceptions from other parts of the application.
```cpp
auto js = rt->prepareJavaScript(src, std::string("xlsx.full.min.js"));
rt->evaluatePreparedJavaScript(js);
```
To confirm the library is loaded, `XLSX.version` can be printed to the console:
_Testing_
If the library is loaded, `XLSX.version` will be a string. This string can be
pulled into the main C++ program.
The `evaluatePreparedJavascript` method returns a `facebook::jsi::Value` object
that represents the result:
```cpp
auto src = std::make_shared<facebook::jsi::StringBuffer>(
"console.log('SheetJS Library Version: ' + XLSX.version)"
);
/* evaluate XLSX.version and capture the result */
auto src = std::make_shared<facebook::jsi::StringBuffer>("XLSX.version");
auto js = rt->prepareJavaScript(src, std::string("<eval>"));
rt->evaluatePreparedJavaScript(js);
facebook::jsi::Value jsver = rt->evaluatePreparedJavaScript(js);
```
The `getString` method extracts the string value and returns an internal string
object (`facebook::jsi::String`). Given that string object, the `utf8` method
returns a proper C++ `std::string` that can be printed:
```cpp
/* pull the version string into C++ code and print */
facebook::jsi::String jsstr = jsver.getString(*rt);
std::string cppver = jsstr.utf8(*rt);
std::cout << "SheetJS version " << cppver << std::endl;
```
### Reading Files
Typically C++ code will read files and Hermes will project the data in the JS
engine as an `ArrayBuffer`. SheetJS libraries can parse `ArrayBuffer` data.
Standard SheetJS operations can pick the first worksheet and generate CSV string
data from the worksheet. Hermes provides methods to convert the JS strings back
to `std::string` objects for further processing in C++.
:::note
It is strongly recommended to create a stub function to perform the entire
workflow in JS code and pass the final result back to C++.
:::
_Hermes Wrapper_
Hermes supports `ArrayBuffer` but has no simple helper to read raw memory.
Libraries are expected to implement `MutableBuffer`:
@ -122,15 +231,48 @@ class CMutableBuffer : public facebook::jsi::MutableBuffer {
uint8_t *buf;
size_t sz;
};
// ...
/* load payload as ArrayBuffer */
size_t sz; char *data = read_file(argv[1], &sz);
auto payload = std::make_shared<CMutableBuffer>(CMutableBuffer((uint8_t *)data, sz));
auto ab = facebook::jsi::ArrayBuffer(*rt, payload);
```
It is strongly recommended to create a stub function to perform the entire
workflow in JS code and pass the final result back to C++.
A `facebook::jsi::ArrayBuffer` object can be created using the wrapper:
```cpp
/* load payload as ArrayBuffer */
size_t sz; char *data = read_file("pres.xlsx", &sz);
auto payload = std::make_shared<CMutableBuffer>(CMutableBuffer((uint8_t *)data, sz));
auto ab = facebook::jsi::ArrayBuffer(*rt, payload);
```
_SheetJS Operations_
In this example, the goal is to pull the first worksheet and generate CSV rows.
`XLSX.read`[^1] parses the `ArrayBuffer` and returns a SheetJS workbook object:
```js
var wb = XLSX.read(buf);
```
The `SheetNames` property[^2] is an array of the sheet names in the workbook.
The first sheet name can be obtained with the following JS snippet:
```js
var first_sheet_name = wb.SheetNames[0];
```
The `Sheets` property[^3] is an object whose keys are sheet names and whose
corresponding values are worksheet objects.
```js
var first_sheet = wb.Sheets[first_sheet_name];
```
The `sheet_to_csv` utility function[^4] generates a CSV string from the sheet:
```js
var csv = XLSX.utils.sheet_to_csv(first_sheet);
```
_C++ integration code_
:::note pass
@ -146,24 +288,41 @@ function(buf) {
:::
_C++ integration code_
The result after evaluating the stub is a `facebook::jsi::Value` object:
```cpp
/* define stub function to read and convert first sheet to CSV */
auto src = std::make_shared<facebook::jsi::StringBuffer>(
"(function(buf) {"
"var wb = XLSX.read(buf);"
"return XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]);"
"})"
);
auto js = rt->prepareJavaScript(src, std::string("<eval>"));
auto func = rt->evaluatePreparedJavaScript(js);
/* define stub function to read and convert first sheet to CSV */
auto src = std::make_shared<facebook::jsi::StringBuffer>(
"(function(buf) {"
"var wb = XLSX.read(buf);"
"return XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]);"
"})"
);
auto js = rt->prepareJavaScript(src, std::string("<eval>"));
facebook::jsi::Value funcval = rt->evaluatePreparedJavaScript(js);
```
/* call stub function and capture result */
auto csv = func.asObject(*rt).asFunction(*rt).call(*rt, ab);
To call this function, the opaque `Value` must be converted to a `Function`:
/* interpret as utf8 and print to stdout */
std::string str = csv.getString(*rt).utf8(*rt);
```cpp
facebook::jsi::Function func = func.asObject(*rt).asFunction(*rt);
```
The `Function` exposes a `call` method to perform the function invocation. The
stub accepts an `ArrayBuffer` argument:
```cpp
/* call stub function and capture result */
facebook::jsi::Value csv = func.call(*rt, ab);
```
In the same way the library version string was pulled into C++ code, the CSV
data can be captured using `getString` and `utf8` methods:
```cpp
/* interpret as utf8 */
std::string str = csv.getString(*rt).utf8(*rt);
std::cout << str << std::endl;
```
## Complete Example
@ -179,7 +338,7 @@ This demo was tested in the following deployments:
| Architecture | Git Commit | Date |
|:-------------|:-----------|:-----------|
| `darwin-x64` | `9f8603b` | 2023-07-24 |
| `darwin-x64` | `70af78b` | 2023-08-02 |
| `darwin-arm` | `869312f` | 2023-06-05 |
:::
@ -304,4 +463,9 @@ ready, it will read the bundled test data and print the contents as CSV.
hermes xlsx.hermes.js
```
If successful, the script will print CSV data from the test file
If successful, the script will print CSV data from the test file.
[^1]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^2]: See ["Workbook Object"](/docs/csf/book)
[^3]: See ["Workbook Object"](/docs/csf/book)
[^4]: See [`sheet_to_csv` in "Utilities"](/docs/api/utilities/csv#csv-output)

View File

@ -37,7 +37,7 @@ var global = (function(){ return this; }).call(null);
**Console**
Some engines do not provide a `console` object. `console.log` can be shimmed
using the engine functionality. For example, `hermes` provides `print()`:
using the engine functionality. For example, `hermes`[^1] provides `print()`:
```js
var console = { log: function(x) { print(x); } };
@ -235,3 +235,7 @@ NodeJS and Deno, Adobe UXP and other platforms.
This demo has been moved [to a dedicated page](/docs/demos/engines/v8).
The demo includes examples in C++ and Rust.
The ["Python + Pandas" demo](/docs/demos/engines/pandas) uses V8 with Python.
[^1]: See ["Initialize Hermes"](/docs/demos/engines/hermes#initialize-hermes) in the Hermes demo.

View File

@ -1,10 +1,10 @@
# Note: The official Hermes documentation includes zero guidance on embedding.
# Tested against commit 9f8603b9886c957e0ccead61fe4380616188bbb4
# Tested against commit 70af78ba69391645749b40a3674d7321c4d6177a
.PHONY: doit
doit: sheetjs-hermes
curl -LO https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js
curl -LO https://sheetjs.com/pres.numbers
if [ ! -e xlsx.full.min.js ]; then curl -LO https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js; fi
if [ ! -e pres.numbers ]; then curl -LO https://sheetjs.com/pres.numbers; fi
./sheetjs-hermes pres.numbers
.PHONY: clean
@ -53,5 +53,5 @@ sheetjs-hermes: sheetjs-hermes.cpp init
.PHONY: init
init:
if [ ! -e hermes ]; then git clone https://github.com/facebook/hermes.git; cd hermes; git checkout 9f8603b9886c957e0ccead61fe4380616188bbb4; cd ..; fi
if [ ! -e hermes ]; then git clone https://github.com/facebook/hermes.git; cd hermes; git checkout 70af78ba69391645749b40a3674d7321c4d6177a; cd ..; fi
if [ ! -e build_release ]; then cmake -S hermes -B build_release -G Ninja -DCMAKE_BUILD_TYPE=Release; cmake --build ./build_release; fi