--- 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 --- import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; [QuickJS](https://bellard.org/quickjs/) is an embeddable JS engine written in C. It has built-in support for reading and writing file data stored in memory. [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing data from spreadsheets. This demo uses QuickJS and SheetJS to pull data from a spreadsheet and print CSV rows. We'll explore how to load SheetJS in a QuickJS context and process spreadsheets from C programs. The ["Integration Example"](#integration-example) section includes a complete command-line tool for reading data from files. ## Integration Details :::note pass Many QuickJS functions are not documented. The explanation was verified against the latest release (commit `6a89d7c`). ::: ### Initialize QuickJS Most QuickJS API functions interact with a `JSContext` object[^1], which is normally created with `JS_NewRuntime` and `JS_NewContext`: ```c #include "quickjs.h" /* initialize context */ JSRuntime *rt = JS_NewRuntime(); JSContext *ctx = JS_NewContext(rt); ``` QuickJS provides a `global` object through `JS_GetGlobalObject`: ```c /* obtain reference to global object */ JSValue global = JS_GetGlobalObject(ctx); ```
Cleanup (click to show) Once finished, programs are expected to cleanup by using `JS_FreeValue` to free values, `JS_FreeContext` to free the context pointer, and `JS_FreeRuntime` to free the runtime: ```c /* global is a JSValue */ JS_FreeValue(ctx, global); /* cleanup */ JS_FreeContext(ctx); JS_FreeRuntime(rt); ``` The [Integration Example](#integration-example) frees JS values after use.
### Load SheetJS Scripts [SheetJS Standalone scripts](/docs/getting-started/installation/standalone) can be loaded and executed in QuickJS. The main library can be loaded by reading the script from the file system and evaluating in the QuickJS context using `JS_Eval`: ```c 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; } // ... { /* Read `xlsx.full.min.js` from the filesystem */ size_t len; char *buf = read_file("xlsx.full.min.js", &len); /* evaluate from the QuickJS context */ JS_Eval(ctx, buf, len, "", 0); /* Free the file buffer */ free(buf); } ``` If the library is loaded, `XLSX.version` will be a string. This string can be pulled into the main C program. 1) Get the `XLSX` property of the global object using `JS_GetPropertyStr`: ```c /* obtain reference to the XLSX object */ JSValue XLSX = JS_GetPropertyStr(ctx, global, "XLSX"); ``` 2) Get the `version` property of the `XLSX` object using `JS_GetPropertyStr`: ```c /* obtain reference to `XLSX.version` */ JSValue version = JS_GetPropertyStr(ctx, XLSX, "version"); ``` 3) Pull the string into C code with `JS_ToCStringLen`: ```c /* pull the version string into C */ size_t vlen; const char *vers = JS_ToCStringLen(ctx, &vlen, version); printf("Version: %s\n", vers); ``` ### Reading Files `JS_NewArrayBuffer` can generate an `ArrayBuffer` from a C byte array. The function signature expects `uint8_t *` instead of `char *`: ```c /* read file */ size_t dlen; uint8_t * dbuf = (uint8_t *)read_file("pres.numbers", &dlen); /* load data into array buffer */ JSValue ab = JS_NewArrayBuffer(ctx, dbuf, dlen, NULL, NULL, 0); ``` The `ArrayBuffer` will be parsed with the SheetJS `read` method[^2]. The CSV row data will be generated with `sheet_to_csv`[^3]. #### Parse the ArrayBuffer :::note pass The goal is to run the equivalent of the following JavaScript code: ```js /* `ab` is the `ArrayBuffer` from the previous step */ var wb = XLSX.read(ab); ``` ::: 1) Get the `XLSX` property of the global object and the `read` property of `XLSX`: ```c /* obtain reference to XLSX.read */ JSValue XLSX = JS_GetPropertyStr(ctx, global, "XLSX"); JSValue XLSX_read = JS_GetPropertyStr(ctx, XLSX, "read"); ``` 2) Create an array of arguments to pass to the function. In this case, the `read` function will be called with one argument (`ArrayBuffer` data): ```c /* prepare arguments */ JSValue args[] = { ab }; ``` 3) Use `JS_Call` to call the function with the arguments: ```c /* call XLSX.read(ab) */ JSValue wb = JS_Call(ctx, XLSX_read, XLSX, 1, args); ``` #### Get First Worksheet :::note pass The goal is to get the first worksheet. In JavaScript, the `SheetNames` property of the workbook is an array of strings and the `Sheets` property holds worksheet objects[^4]. The desired action looks like: ```js /* `wb` is the workbook from the previous step */ var wsname = wb.SheetNames[0]; var ws = wb.Sheets[wsname]; ``` ::: 4) Pull `wb.SheetNames[0]` into a C string using `JS_GetPropertyStr`: ```c /* get `wb.SheetNames[0]` */ JSValue SheetNames = JS_GetPropertyStr(ctx, wb, "SheetNames"); JSValue Sheet1 = JS_GetPropertyStr(ctx, SheetNames, "0"); /* pull first sheet name into C code */ size_t wslen; const char *wsname = JS_ToCStringLen(ctx, &wslen, Sheet1); ``` 5) Get the worksheet object: ```c /* get wb.Sheets[wsname] */ JSValue Sheets = JS_GetPropertyStr(ctx, wb, "Sheets"); JSValue ws = JS_GetPropertyStr(ctx, Sheets, wsname); ``` #### Convert to CSV :::note pass The goal is to call `sheet_to_csv`[^5] and pull the result into C code: ```js /* `ws` is the worksheet from the previous step */ var csv = XLSX.utils.sheet_to_csv(ws); ``` ::: 6) Create a references to `XLSX.utils` and `XLSX.utils.sheet_to_csv`: ```c /* obtain reference to XLSX.utils.sheet_to_csv */ JSValue utils = JS_GetPropertyStr(ctx, XLSX, "utils"); JSValue sheet_to_csv = JS_GetPropertyStr(ctx, utils, "sheet_to_csv"); ``` 7) Create arguments array: ```c /* prepare arguments */ JSValue args[] = { ws }; ``` 8) Use `JS_Call` to call the function and use `JS_ToCStringLen` to pull the CSV: ```c JSValue csv = JS_Call(ctx, sheet_to_csv, utils, 1, args); size_t csvlen; const char *csvstr = JS_ToCStringLen(ctx, &csvlen, csv); ``` At this point, `csvstr` is a C string that can be printed to standard output. ## Complete Example The "Integration Example" covers a traditional integration in a C application, while the "CLI Test" demonstrates other concepts using the `quickjs` CLI tool. ### Integration Example :::note Tested Deployments This demo was tested in the following deployments: | Architecture | Git Commit | Date | |:-------------|:-----------|:-----------| | `darwin-x64` | `6a89d7c` | 2024-03-15 | | `darwin-arm` | `2788d71` | 2023-10-18 | | `win10-x64` | `9e561d5` | 2024-03-04 | | `win11-arm` | `03cc5ec` | 2023-12-01 | | `linux-x64` | `3b45d15` | 2024-04-25 | | `linux-arm` | `03cc5ec` | 2023-12-01 | When the demo was tested, `3b45d15` was the HEAD commit on the `master` branch. ::: :::caution pass QuickJS does not officially support Windows. The `win10-x64` and `win11-arm` tests were run entirely within Windows Subsystem for Linux. ::: 0) Build `libquickjs.a`: ```bash git clone https://github.com/bellard/quickjs cd quickjs git checkout 3b45d15 make cd .. ``` 1) Copy `libquickjs.a` and `quickjs.h` into the working directory: ```bash cp quickjs/libquickjs.a . cp quickjs/quickjs.h . ``` 2) Download [`sheetjs.quick.c`](pathname:///quickjs/sheetjs.quick.c): ```bash curl -LO https://docs.sheetjs.com/quickjs/sheetjs.quick.c ``` 3) Build the sample application: ```bash gcc -o sheetjs.quick -Wall sheetjs.quick.c libquickjs.a -lm ``` This program tries to parse the file specified by the first argument 4) Download the SheetJS Standalone script and test file. Save both files in the project directory: {`\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://docs.sheetjs.com/pres.numbers`} 5) Run the test program: ```bash ./sheetjs.quick pres.numbers ``` If successful, the program will print the library version number, file size, first worksheet name, and the contents of the first sheet as CSV rows. ### CLI Test :::note Tested Deployments This demo was tested in the following environments: | Git Commit | Date | |:-----------|:-----------| | `3b45d15` | 2024-04-25 | When the demo was tested, `3b45d15` was the HEAD commit on the `master` branch. ::: 0) Build the `qjs` command line utility from source: ```bash git clone https://github.com/bellard/quickjs cd quickjs git checkout 9e561d5 make cd .. cp quickjs/qjs . ``` 1) Download the SheetJS Standalone script and the test file. Save both files in the project directory: {`\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://docs.sheetjs.com/pres.numbers`} 2) Download [`SheetJSQuick.js`](pathname:///quickjs/SheetJSQuick.js) ```bash curl -LO https://docs.sheetjs.com/quickjs/SheetJSQuick.js ``` 3) Test the program: ```bash ./qjs SheetJSQuick.js ``` If successful, the script will print CSV rows and generate `SheetJSQuick.xlsx`. The generated file can be opened in Excel or another spreadsheet editor. [^1]: See ["Runtime and Contexts"](https://bellard.org/quickjs/quickjs.html#Runtime-and-contexts) in the QuickJS documentation [^2]: See [`read` in "Reading Files"](/docs/api/parse-options) [^3]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) [^4]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) [^5]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)