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 | 5020015 | 2025-03-31 | 
| darwin-arm | d2d30df | 2025-02-13 | 
| win11-x64 | 5020015 | 2025-04-23 | 
| win11-arm | 5020015 | 2025-02-23 | 
| linux-x64 | 5020015 | 2025-04-21 | 
| linux-arm | 5020015 | 2025-02-15 | 
Windows 测试在 WSL 中运行。
¥The Windows tests were run in WSL.
集成详情
¥Integration Details
官方 JerryScript 文档和示例已过时。此解释已针对最新版本(提交 5020015)进行了验证。
¥The official JerryScript documentation and examples are out of date. This
explanation was verified against the latest release (commit 5020015).
初始化 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_parsewill parse the script
- 
jerry_run将运行解析后的脚本对象¥ jerry_runwill 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 thisvariable (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 SheetJSJerrydirectory:
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.cinto 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 jerryscriptcloned 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.jscreates aglobalvariable 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.jswill callXLSX.readandXLSX.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 jerrystandalone 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 和文本"