C + JerryScript
JerryScript 是一个轻量级的 JavaScript 引擎。它专为微控制器和类似环境而设计。
¥JerryScript is a lightweight JavaScript engine. It is designed for microcontrollers and similar environments.
SheetJS 是一个用于从电子表格读取和写入数据的 JavaScript 库。
¥SheetJS is a JavaScript library for reading and writing data from spreadsheets.
该演示使用 JerryScript 和 SheetJS 从电子表格中提取数据并打印 CSV 行。我们将探讨如何在 JerryScript 字段中加载 SheetJS 并处理来自 C 程序的电子表格。
¥This demo uses JerryScript and SheetJS to pull data from a spreadsheet and print CSV rows. We'll explore how to load SheetJS in a JerryScript realm and process spreadsheets from C programs.
"集成示例" 部分包括一个完整的命令行工具,用于从文件中读取数据。
¥The "Integration Example" section includes a complete command-line tool for reading data from files.
该演示需要比 JerryScript 部署中通常使用的堆大小大得多的堆大小!在本地测试中,需要以下尺寸:
¥This demo requires a much larger heap size than is normally used in JerryScript deployments! In local testing, the following sizes were needed:
-
8192(8M)用于 https://xlsx.nodejs.cn/pres.xlsx
¥8192 (8M) for https://xlsx.nodejs.cn/pres.xlsx
-
65536(64M)用于 https://xlsx.nodejs.cn/pres.numbers
¥65536 (64M) for https://xlsx.nodejs.cn/pres.numbers
本 demo 在以下环境下进行了测试:
¥This demo was tested in the following environments:
架构 | 提交 | 日期 |
---|---|---|
darwin-x64 | 35465ed | 2024-05-25 |
darwin-arm | 35465ed | 2024-05-25 |
win10-x64 | 47bd5d4 | 2024-04-14 |
win11-arm | 35465ed | 2024-05-25 |
linux-x64 | cefd391 | 2024-03-21 |
linux-arm | 35465ed | 2024-05-25 |
Windows 测试在 WSL 中运行。
¥The Windows tests were run in WSL.
集成详情
¥Integration Details
官方 JerryScript 文档和示例已过时。此解释已针对最新版本(提交 514fa67
)进行了验证。
¥The official JerryScript documentation and examples are out of date. This
explanation was verified against the latest release (commit 514fa67
).
初始化 JerryScript
¥Initialize JerryScript
全局引擎实例可以使用 jerry_init
进行初始化并使用 jerry_cleanup
进行清理:
¥The global engine instance can be initialized with jerry_init
and cleaned up
with jerry_cleanup
:
#include "jerryscript.h"
int main (int argc, char **argv) {
/* Initialize engine */
jerry_init(JERRY_INIT_EMPTY);
// ... use engine methods ...
/* cleanup before exiting */
jerry_cleanup();
return 0;
}
API 方法使用 jerry_value_t
值来表示 JS 值和杂项。可以使用 jerry_value_is_error
来区分代表错误的值。jerry_value_t
值可以通过 jerry_value_free
释放。
¥API methods use jerry_value_t
values to represent JS values and miscellany.
Values representing errors can be distinguished using jerry_value_is_error
.
jerry_value_t
values can be freed with jerry_value_free
.
评估代码
¥Evaluate Code
评估代码涉及两个步骤:
¥Evaluating code involves two steps:
-
jerry_parse
将解析脚本¥
jerry_parse
will parse the script -
jerry_run
将运行解析后的脚本对象¥
jerry_run
will run the parsed script object
jerry_parse
的返回值是 jerry_value_t
值,可以在 jerry_run
之后安全释放。
¥The return value of jerry_parse
is a jerry_value_t
value that can be safely
freed after jerry_run
.
以下 eval_str
函数解析并执行脚本。如果解析失败,该函数将返回解析错误。如果解析成功,该函数将返回执行代码的结果。
¥The following eval_str
function parses and executes scripts. If parsing fails,
the function will return the parsing error. If parsing succeeds, the function
will return the result of executing the code.
jerry_value_t eval_str(const char *code, size_t sz) {
/* try to parse code */
jerry_value_t parsed = jerry_parse(code, sz, NULL);
/* return the parse error if parsing failed */
if(jerry_value_is_error(parsed)) return parsed;
/* run the code */
jerry_value_t out = jerry_run(parsed);
/* free the parsed representation */
jerry_value_free(parsed);
/* return the result */
return out;
}
加载 SheetJS 脚本
¥Load SheetJS Scripts
SheetJS 独立脚本 可以在 JerryScript 中解析并运行。
¥SheetJS Standalone scripts can be parsed and run in JerryScript.
可以使用标准 C 函数从文件系统读取脚本:
¥Scripts can be read from the filesystem using standard C functions:
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) - 1;
fclose(f);
return buf;
}
必须在主库之前评估垫片脚本。这两种情况,读取脚本文件后,前面的 eval_str
函数都可以运行代码:
¥The shim script must be evaluated before the main library. In both cases, after
reading the script file, the previous eval_str
function can run the code:
/* evaluate shim.min.js */
{
size_t sz; const jerry_char_t *script = (jerry_char_t *)read_file("shim.min.js", &sz);
jerry_value_t result = eval_str(script, sz);
if(jerry_value_is_error(result)) { // failed to parse / execute
fprintf(stderr, "Failed to evaluate shim.min.js"); return 1;
}
jerry_value_free(result);
}
/* evaluate xlsx.full.min.js */
{
size_t sz; const jerry_char_t *script = (jerry_char_t *)read_file("xlsx.full.min.js", &sz);
jerry_value_t result = eval_str(script, sz);
if(jerry_value_is_error(result)) { // failed to parse / execute
fprintf(stderr, "Failed to evaluate xlsx.full.min.js"); return 1;
}
jerry_value_free(result);
}
读取文件
¥Reading Files
二进制文件数据可以使用 ArrayBuffer
对象从 C 传递到 JerryScript。
¥Binary file data can be passed from C to JerryScript with ArrayBuffer
objects.
创建 ArrayBuffer
¥Creating ArrayBuffers
jerry_arraybuffer
将生成指定长度的 ArrayBuffer
对象。创建数组后,jerry_arraybuffer_write
将复制数据。
¥jerry_arraybuffer
will generate an ArrayBuffer
object of specified length.
After creating the array, jerry_arraybuffer_write
will copy data.
以下 load_file
函数从文件系统读取文件并将数据加载到 ArrayBuffer
中:
¥The following load_file
function reads a file from the filesystem and loads
the data into an ArrayBuffer
:
static jerry_value_t load_file(const char *filename) {
/* read file */
size_t len; char *buf = read_file(filename, &len);
if(!buf) return 0;
/* create ArrayBuffer */
jerry_value_t out = jerry_arraybuffer(len);
/* copy file data into ArrayBuffer */
jerry_arraybuffer_write(out, 0, (const uint8_t*)buf, len);
return out;
}
该过程可能会失败。结果应使用 jerry_value_is_error
进行测试:
¥The process may fail. The result should be tested with jerry_value_is_error
:
jerry_value_t ab = load_file("pres.xlsx");
if(!ab || jerry_value_is_error(ab)) { // failed to create ArrayBuffer
fprintf(stderr, "Failed to read pres.xlsx"); return 1;
}
创建全局变量
¥Creating Global Variable
ArrayBuffer
对象必须先绑定到变量才能使用。
¥The ArrayBuffer
object must be bound to a variable before it can be used.
目标是将 ArrayBuffer
绑定到全局范围内的 buf
属性。
¥The goal is to bind the ArrayBuffer
to the buf
property in global scope.
-
获取全局
this
变量(使用jerry_current_realm
):¥Get the global
this
variable (usingjerry_current_realm
):
/* get the global variable */
jerry_value_t this = jerry_current_realm();
if(jerry_value_is_error(this)) { // failed to get global object
fprintf(stderr, "Failed to get global object"); return 1;
}
-
为该属性创建 JerryScript 字符串 (
"buf"
):¥Create a JerryScript string (
"buf"
) for the property:
/* create a string "buf" for the property access */
jerry_value_t prop = jerry_string_sz("buf");
if(jerry_value_is_error(this)) { // failed to create "buf"
fprintf(stderr, "Failed to create string"); return 1;
}
-
使用
jerry_object_set
分配属性:¥Assign the property using
jerry_object_set
:
/* set global["buf"] to the ArrayBuffer */
jerry_value_t set = jerry_object_set(this, prop, ab);
if(jerry_value_is_error(set)) { // failed to set property
fprintf(stderr, "Failed to assign ArrayBuffer"); return 1;
}
解析数据
¥Parsing Data
目标是运行与以下 JavaScript 代码等效的内容:
¥The goal is to run the equivalent of the following JavaScript code:
/* `buf` is the `ArrayBuffer` from the previous step */
var wb = XLSX.read(buf);
上一步中的 ArrayBuffer
可在 buf
变量中使用。该 ArrayBuffer
可以传递给 SheetJS read
方法 [^1],该方法将解析原始数据并返回 SheetJS 工作簿对象 [^2]。
¥The ArrayBuffer
from the previous step is available in the buf
variable.
That ArrayBuffer
can be passed to the SheetJS read
method[^1], which will
parse the raw data and return a SheetJS workbook object[^2].
var wb = XLSX.read(buf)
可以存储在字节数组中并直接求值:
¥var wb = XLSX.read(buf)
can be stored in a byte array and evaluated directly:
/* run `var wb = XLSX.read(buf)` */
{
const jerry_char_t code[] = "var wb = XLSX.read(buf);";
jerry_value_t result = eval_str(code, sizeof(code) - 1);
if(jerry_value_is_error(result)) {
fprintf(stderr, "Failed to parse file"); return 1;
}
jerry_value_free(result);
}
生成 CSV
¥Generating CSV
目标是运行与以下 JavaScript 代码等效的内容:
¥The goal is to run the equivalent of the following JavaScript code:
/* `wb` is the workbook from the previous step */
XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])
一个 SheetJS 工作簿对象可以包含多个工作表对象 [^3]。Sheets
属性是一个对象,其键是工作表名称,其值是工作表对象。SheetNames
属性是工作表名称的数组。
¥A SheetJS workbook object can contain multiple sheet objects[^3]. The Sheets
property is an object whose keys are sheet names and whose values are sheet
objects. The SheetNames
property is an array of worksheet names.
第一个工作表名称可以在 wb.SheetNames[0]
处找到。第一个工作表对象可以在 wb.Sheets[wb.SheetNames[0]]
处找到。
¥The first sheet name can be found at wb.SheetNames[0]
. The first sheet object
can be found at wb.Sheets[wb.SheetNames[0]]
.
SheetJS sheet_to_csv
实用函数 [^4] 接受一个工作表对象并生成一个 JS 字符串。
¥The SheetJS sheet_to_csv
utility function[^4] accepts a sheet object and
generates a JS string.
结合所有内容,XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])
基于工作簿 wb
中的第一个工作表生成 CSV 字符串:
¥Combining everything, XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])
generates a CSV string based on the first worksheet in the workbook wb
:
const jerry_char_t code[] = "XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])";
jerry_value_t csv = eval_str(code, sizeof(code) - 1);
if(jerry_value_is_error(result)) { // CSV generation failed
fprintf(stderr, "Failed to generate csv"); return 1;
}
拉取字符串
¥Pulling Strings
JerryScript 公开了编码感知方法,将 JS 字符串提取到 C 中。JERRY_ENCODING_UTF8
编码强制使用 UTF8 解释。
¥JerryScript exposes encoding-aware methods to pull JS strings into C. The
JERRY_ENCODING_UTF8
encoding forces UTF8 interpretations.
jerry_string_size
函数返回存储字符串所需的字节数。分配内存后,jerry_string_to_buffer
将复制数据。以下 pull_str
函数使用 malloc
:
¥The jerry_string_size
function returns the number of bytes required to store
the string. After allocating memory, jerry_string_to_buffer
will copy data.
The following pull_str
function uses malloc
:
char *pull_str(jerry_value_t str, size_t *sz) {
/* determine string size in bytes */
jerry_size_t str_sz = jerry_string_size(str, JERRY_ENCODING_UTF8);
/* allocate memory */
jerry_char_t *buf = (jerry_char_t *)malloc(str_sz + 1);
/* copy from JS string to C byte array */
jerry_string_to_buffer(str, JERRY_ENCODING_UTF8, buf, str_sz + 1);
/* pass back size and return the pointer */
*sz = str_sz;
return (char *)buf;
}
此函数可用于从上一节中提取 csv
值:
¥This function can be used to pull the csv
value from the previous section:
size_t sz; char *buf = pull_str(result, &sz);
printf("%s\n", buf);
完整示例
¥Complete Example
"集成示例" 涵盖了 C 应用中的传统集成,而 "CLI 测试" 使用 jerry
CLI 工具演示了其他概念。
¥The "Integration Example" covers a traditional integration in a C application,
while the "CLI Test" demonstrates other concepts using the jerry
CLI tool.
集成示例
¥Integration Example
Build Dependencies (click to show)
The JerryScript build system requires cmake
.
Debian and WSL additionally require python3
and python-is-python3
packages.
-
创建项目文件夹:
¥Create a project folder:
mkdir SheetJSJerry
cd SheetJSJerry
-
克隆存储库并使用所需选项构建库:
¥Clone the repository and build the library with required options:
git clone --depth=1 https://github.com/jerryscript-project/jerryscript.git
cd jerryscript
python3 tools/build.py --error-messages=ON --logging=ON --mem-heap=8192 --cpointer-32bit=ON
cd ..
-
下载 SheetJS Standalone 脚本、shim 脚本和测试文件。将所有三个文件移动到
SheetJSJerry
目录:¥Download the SheetJS Standalone script, shim script and test file. Move all three files to the
SheetJSJerry
directory:
curl -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/shim.min.js
curl -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js
curl -LO https://xlsx.nodejs.cn/pres.xlsx
-
将
sheetjs.jerry.c
下载到同一文件夹中:¥Download
sheetjs.jerry.c
into the same folder:
curl -LO https://xlsx.nodejs.cn/jerryscript/sheetjs.jerry.c
-
构建示例应用:
¥Build the sample application:
gcc -o sheetjs.jerry -Ijerryscript/jerry-ext/include -Ijerryscript/jerry-math/include -Ijerryscript/jerry-core/include sheetjs.jerry.c -ljerry-core -ljerry-ext -ljerry-port -lm -Ljerryscript/build/lib -Wno-pointer-sign
-
运行测试程序:
¥Run the test program:
./sheetjs.jerry pres.xlsx
如果成功,程序会将第一张表的内容打印为 CSV 行。
¥If successful, the program will print contents of the first sheet as CSV rows.
CLI 测试
¥CLI Test
由于独立二进制文件的限制,该演示将测试文件编码为 Base64 字符串,并将其直接添加到合并脚本中。
¥Due to limitations of the standalone binary, this demo will encode a test file as a Base64 string and directly add it to an amalgamated script.
-
使用所需选项构建库和命令行工具。
¥Build the library and command line tool with required options.
如果 "集成示例" 未经过测试,请运行以下命令:
¥If the "Integration Example" was not tested, run the following commands:
git clone --depth=1 https://github.com/jerryscript-project/jerryscript.git
cd jerryscript
python3 tools/build.py --error-messages=ON --logging=ON --mem-heap=8192 --cpointer-32bit=ON
如果测试了 "集成示例",请进入 jerryscript
文件夹:
¥If the "Integration Example" was tested, enter the jerryscript
folder:
cd jerryscript
-
下载 SheetJS Standalone 脚本、shim 脚本和测试文件。将所有三个文件移至
jerryscript
克隆的存储库目录:¥Download the SheetJS Standalone script, shim script and test file. Move all three files to the
jerryscript
cloned repo directory:
curl -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/shim.min.js
curl -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js
curl -LO https://xlsx.nodejs.cn/pres.xlsx
-
打包测试文件并创建
payload.js
:¥Bundle the test file and create
payload.js
:
node -e "fs.writeFileSync('payload.js', 'var payload = \"' + fs.readFileSync('pres.xlsx').toString('base64') + '\";')"
-
创建支持脚本:
¥Create support scripts:
-
global.js
创建一个global
变量并定义一个假console
:¥
global.js
creates aglobal
variable and defines a fakeconsole
:
var global = (function(){ return this; }).call(null);
var console = { log: function(x) { print(x); } };
-
jerry.js
将调用XLSX.read
和XLSX.utils.sheet_to_csv
:¥
jerry.js
will callXLSX.read
andXLSX.utils.sheet_to_csv
:
var wb = XLSX.read(payload, {type:'base64'});
console.log(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
-
创建合并
xlsx.jerry.js
:¥Create the amalgamation
xlsx.jerry.js
:
cat global.js xlsx.full.min.js payload.js jerry.js > xlsx.jerry.js
最终脚本在加载独立库之前定义了 global
。准备就绪后,它将读取打包的测试数据并将内容打印为 CSV。
¥The final script defines global
before loading the standalone library. Once
ready, it will read the bundled test data and print the contents as CSV.
-
使用
jerry
独立二进制文件运行脚本:¥Run the script using the
jerry
standalone binary:
build/bin/jerry xlsx.jerry.js; echo $?
如果成功,测试文件的内容将显示在 CSV 行中。状态代码 0
将打印在行之后。
¥If successful, the contents of the test file will be displayed in CSV rows. The
status code 0
will be printed after the rows.
[^1]: 见 read
于 "读取文件"
[^2]: 见 "工作簿对象" 于 "SheetJS 数据模型"
¥See "Workbook Object" in "SheetJS Data Model"
[^3]: 见 "Sheet 对象"
¥See "Sheet Objects"
[^4]: 见 sheet_to_csv
于 "CSV 和文本"