This commit is contained in:
SheetJS 2023-06-19 21:21:34 -04:00
parent efc57df123
commit edaa2e6b5e
14 changed files with 756 additions and 165 deletions

@ -33,3 +33,6 @@ Specific pages can load scripts using the `head` component:
</head>
```
## Other Notes
`src/theme/Admonition` was swizzled from 2.4.1 to address Docusaurus issue 8568

@ -22,10 +22,25 @@ Most scenarios involving spreadsheets and data can be divided into 5 parts:
locally. Data can be presented to users in an HTML TABLE or data grid.
A common problem involves generating a valid spreadsheet export from data stored
in an HTML table. In this example, an HTML TABLE on the page will be scraped,
a row will be added to the bottom with the date of the report, and a new file
will be generated and downloaded locally. `XLSX.writeFile` takes care of
packaging the data and attempting a local download:
in an HTML table.
```mermaid
flowchart LR
server[(Backend\nServer)]
html{{HTML\nTABLE}}
wb(((SheetJS\nWorkbook)))
wb2(((Modified\nWorkbook)))
file[(workbook\nfile)]
server --> |"Get Table (1)\n."| html
html --> |"Parse Table (2)\n`table_to_book`"| wb
wb --> |"Add data (3)\n`sheet_add_aoa`"| wb2
wb2 --> |"Export file (4,5)\n`writeFile`"| file
```
In this example, an HTML TABLE on the page will be scraped, a row will be added
to the bottom with the date of the report, and a new file will be generated and
downloaded locally. `XLSX.writeFile` takes care of packaging the data and
attempting a local download:
```js
// Acquire Data (reference to the HTML table)
@ -54,6 +69,9 @@ Utility functions help with step 3.
## Highlights
["Demos"](/docs/demos) describes special deployments using SheetJS in tandem with
other tools and libraries.
["Data Import"](/docs/solutions/input) describes solutions for common data import
scenarios.

@ -94,14 +94,22 @@ export default defineConfig({
```
In frontend code, the loader will look for all modules with a `?sheetjs`
query string. The default export is an array of row objects:
query string. The default export is an array of row objects.
The following example script displays the data in a table:
```js title="main.js"
import data from './data.xlsx?sheetjs';
import data from './data/pres.xlsx?sheetjs';
document.querySelector('#app').innerHTML = `<div><pre>
${data.map(row => JSON.stringify(row)).join("\n")}
</pre></div>`;
document.querySelector('#app').innerHTML = `<table>
<thead><tr><th>Name</th><th>Index</th></tr></thead>
<tbody>
${data.map(row => `<tr>
<td>${row.Name}</td>
<td>${row.Index}</td>
</tr>`).join("\n")}
</tbody>
</table>`;
```
### Base64 Plugin
@ -155,17 +163,23 @@ When importing using the `b64` query, the raw Base64 string will be exposed.
```js title="main.js"
import { read, utils } from "xlsx";
/* reference workbook */
/* import workbook data */
import b64 from './data.xlsx?b64';
/* parse workbook and export first sheet to CSV */
/* parse workbook and pull data from the first worksheet */
const wb = read(b64, { type: "base64" });
const wsname = wb.SheetNames[0];
const csv = utils.sheet_to_csv(wb.Sheets[wsname]);
const data = utils.sheet_to_json(wb.Sheets[wsname]);
document.querySelector('#app').innerHTML = `<div><pre>
<b>${wsname}</b>
${csv}
</pre></div>`;
document.querySelector('#app').innerHTML = `<table>
<thead><tr><th>Name</th><th>Index</th></tr></thead>
<tbody>
${data.map(row => `<tr>
<td>${row.Name}</td>
<td>${row.Index}</td>
</tr>`).join("\n")}
</tbody>
</table>`;
```
## Complete Demo
@ -278,7 +292,7 @@ source. The code will reference some script like `/assets/index-HASH.js`.
Open that script. Searching for `Bill Clinton` reveals the following:
```
JSON.parse('[{"Name":"Bill Clinton","Index":42}
{"Name":"Bill Clinton","Index":42}
```
Searching for `BESSELJ` should reveal no results. The SheetJS scripts are not
@ -350,9 +364,9 @@ The SheetJS library is embedded in the final site.
[^1]: See ["Using Plugins"](https://vitejs.dev/guide/using-plugins.html) in the ViteJS documentation.
[^2]: See ["Static Asset Handling"](https://vitejs.dev/guide/assets.html) in the ViteJS documentation.
[^3]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/#array-output)
[^4]: See [`read` in "Parsing Options"](/docs/api/parse-options)
[^5]: See [`read` in "Parsing Options"](/docs/api/parse-options)
[^6]: See [the "base64" type in "Parsing Options"](/docs/api/parse-options#input-type)
[^3]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^4]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^5]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^6]: See [the "base64" type in "Reading Files"](/docs/api/parse-options#input-type)
[^7]: See [`SheetJS/sheetjs-vite`](https://git.sheetjs.com/sheetjs/sheetjs-vite/) on the SheetJS git server.
[^8]: See ["Server-Side Rendering"](https://vitejs.dev/guide/ssr.html) in the ViteJS documentation.

@ -1,5 +1,7 @@
---
title: Wails
sidebar_label: Wails
description: Build data-intensive desktop apps using Wails. Seamlessly integrate spreadsheets into your app using SheetJS. Modernize Excel-powered business processes with confidence.
pagination_prev: demos/mobile/index
pagination_next: demos/data/index
sidebar_position: 3
@ -7,18 +9,28 @@ sidebar_custom_props:
summary: Webview + Go Backend
---
# Spreadsheet-Powered Wails Apps
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported
from JavaScript code.
[Wails](https://wails.io/) is a modern toolkit for building desktop apps. Wails
apps pair a Go-powered backend with a JavaScript-powered frontend[^1].
The "Complete Example" creates an app that looks like the screenshot:
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses Wails and SheetJS to pull data from a spreadsheet and display the
data in the app. We'll explore how to load SheetJS in a Wails app and exchange
file data between the JavaScript frontend and Go backend.
The ["Complete Example"](#complete-example) section covers a complete desktop
app to read and write workbooks. The app will look like the screenshots below:
<table><thead><tr>
<th><a href="#complete-example">Win10</a></th>
<th><a href="#complete-example">Windows</a></th>
<th><a href="#complete-example">macOS</a></th>
<th><a href="#complete-example">Linux</a></th>
</tr></thead><tbody><tr><td>
@ -35,49 +47,71 @@ The "Complete Example" creates an app that looks like the screenshot:
</td></tr></tbody></table>
## Integration Details
:::info
All operations must be run from Go code. This example passes Base64 strings.
:::caution
Wails currently does not provide the equivalent of NodeJS `fs` module. All raw
file operations must be performed in Go code.
The HTML File Input Element does not show a file picker. This is a known bug.
The demo works around the issue by showing pickers in Go code.
This demo assumes some familiarity with JavaScript and with Go. If you would
prefer a pure JavaScript solution, the [Electron](/docs/demos/desktop/electron)
platform provides many native features out of the box.
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
installed in the `frontend` folder and imported in frontend scripts.
:::caution
Wails currently does not provide the equivalent of NodeJS `fs` module.
Reading and writing raw file data must be implemented in native Go code.
:::
This demo includes native Go code for showing dialogs and reading and writing
files. When sending data between Go and JavaScript code, the raw files are
encoded as Base64 strings.
### Reading Files
The file picker and reading operations can be combined in one Go function.
When the user clicks the "Import File" button, the frontend tells the Go backend
to read data. The user will be presented with a file picker to select a file to
read. The Go backend will read the data, encode as a Base64 string, and send the
result to the frontend.
The frontend will parse the data using the SheetJS `read` method[^2], generate
HTML tables with `sheet_to_html`[^3], and display the tables on the frontend.
The following diagram summarizes the steps:
```mermaid
sequenceDiagram
autonumber
actor User
participant JS
participant Go
participant JS as Frontend (JS)
participant Go as Backend (Go)
User->>JS: click button
JS->>Go: ask for data
Note over Go: Show Open Dialog
Note over Go: Read File Bytes
Note over Go: Generate Base64
Go->>JS: return data
Note over JS: Parse data
Note over JS: Display Table
Note over JS: Parse Data<br/>`read`
Note over JS: Display Table<br/>`sheet_to_html`
JS->>User: app shows data
```
#### Go
The Wails runtime provides the cross-platform `OpenFileDialog` function[^4] to
show a file picker. The Go standard library provides methods for reading data
from the selected file[^5] and encoding in a Base64 string[^6]
```go
import (
"context"
// highlight-start
"encoding/base64"
"io/ioutil"
"os"
"github.com/wailsapp/wails/v2/pkg/runtime"
// highlight-end
)
@ -98,7 +132,7 @@ func (a *App) ReadFile() string {
})
if err != nil { return "" } // The demo app shows an error message
// highlight-next-line
data, err := ioutil.ReadFile(selection)
data, err := os.ReadFile(selection)
if err != nil { return "" } // The demo app shows an error message
// highlight-next-line
return base64.StdEncoding.EncodeToString(data)
@ -107,7 +141,8 @@ func (a *App) ReadFile() string {
#### JS
Wails will automatically create bindings for use in JS:
Wails will automatically create bindings for use in JS. The `App` binding module
will export the function `ReadFile`.
```js title="frontend/src/App.svelte"
import { read, utils } from 'xlsx';
@ -115,38 +150,53 @@ import { ReadFile } from '../wailsjs/go/main/App';
async function importFile(evt) {
// highlight-start
/* call the native Go function and receive a base64 string */
const b64 = await ReadFile();
/* parse the base64 string with SheetJS */
const wb = read(b64, { type: "base64" });
// highlight-end
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
html = utils.sheet_to_html(ws); // generate HTML and update state
return utils.sheet_to_html(ws); // generate HTML table
}
```
### Writing Files
There is a multi-part dance since the library needs the file extension.
:::info
1) Show the save file picker in Go, pass back to JS
The SheetJS `write` method[^7] can write spreadsheets in a number of formats[^8]
including XLSX, XLSB, XLS, and NUMBERS. It expects a `bookType` option. This
means the frontend needs to know the output file name before creating the file.
2) Generate the file data in JS, pass the data back to Go
:::
3) Write to file in Go
When the user clicks the "Export File" button, the frontend asks the Go backend
for the output filename and path. The user will be presented with a file picker
to select the output folder and workbook type. The backend will send the name
to the frontend.
The frontend will generate a workbook object from the table using the SheetJS
`table_to_book` method[^9]. The SheetJS `write` method[^10] will generate a
Base64 string from the data.
The frontend will send the Base64 string to the backend. The backend will write
the data to a file in the selected folder.
```mermaid
sequenceDiagram
autonumber
actor User
participant JS
participant Go
participant JS as Frontend (JS)
participant Go as Backend (Go)
User->>JS: click button
JS->>Go: ask for path
Note over Go: Show Save Dialog
Go->>JS: path to save file
Note over JS: write workbook
Note over JS: Read from Table<br/>`table_to_book`
Note over JS: Write Workbook<br/>`write`
JS->>Go: base64-encoded bytes
Note over Go: decode data
Note over Go: write to file
Note over Go: Decode Data
Note over Go: Write to File
Go->>JS: write finished
JS->>User: alert
```
@ -155,7 +205,8 @@ sequenceDiagram
Two Go functions will be exposed.
- `SaveFile` will show the file picker and return the path:
- `SaveFile` will show the file picker and return the path. It will use the
cross-platform `SaveFileDialog` function[^11].
```go
import (
@ -183,14 +234,16 @@ func (a *App) SaveFile() string {
}
```
- `WriteFile` performs the file write given a Base64 string and file path:
- `WriteFile` performs the file write given a Base64 string and file path. The
Go standard library provides methods for decoding Base64 strings[^12] and
writing data to the filesystem[^13]
```go
import (
"context"
// highlight-start
"encoding/base64"
"io/ioutil"
"os"
// highlight-end
)
@ -201,29 +254,32 @@ type App struct {
func (a *App) WriteFile(b64 string, path string) {
// highlight-start
buf, _ := base64.StdEncoding.DecodeString(b64);
_ = ioutil.WriteFile(path, buf, 0644);
_ = os.WriteFile(path, buf, 0644);
// highlight-end
}
```
#### JS
Wails will automatically create bindings for use in JS:
Wails will automatically create bindings for use in JS. The `App` binding module
will export the functions `SaveFile` and `WriteFile`:
```js
```js title="frontend/src/App.svelte"
import { utils, write } from 'xlsx';
import { SaveFile, WriteFile } from '../wailsjs/go/main/App';
async function exportFile(wb) {
async function exportFile(table_element) {
/* generate workbook */
const elt = tbl.getElementsByTagName("TABLE")[0];
const wb = utils.table_to_book(elt);
const wb = utils.table_to_book(table_element);
/* show save picker and get path */
const path = await SaveFile();
/* generate base64 string based on the path */
const b64 = write(wb, { bookType: path.slice(path.lastIndexOf(".")+1), type: "base64" });
/* get the file extension -> bookType */
const bookType = path.slice(path.lastIndexOf(".")+1);
/* generate base64 string */
const b64 = write(wb, { bookType: bookType, type: "base64" });
/* write to file */
await WriteFile(b64, path);
@ -239,7 +295,44 @@ the Svelte TypeScript starter.
:::
0) [Read Wails "Getting Started" guide and install dependencies.](https://wails.io/docs/gettingstarted/installation)
0) Read the Wails "Getting Started" guide[^14] and install dependencies.
<details><summary><b>Installation Notes</b> (click to show)</summary>
Wails will require:
- A recent version of [Go](https://go.dev/doc/install).
- The "LTS" version of [NodeJS](https://nodejs.org/en/download).
After installing both, run the following command to install Wails:
```bash
go install github.com/wailsapp/wails/v2/cmd/wails@latest
```
Once that finishes, run the following command in a new terminal window:
```bash
wails doctor
```
The output will include a `# Diagnosis` section. It should display:
```
# Diagnosis
Your system is ready for Wails development!
```
If a required dependency is missing, it will be displayed.
:::note
None of the optional packages are required for building and running this demo.
:::
</details>
1) Create a new Wails app:
@ -280,3 +373,18 @@ wails build
```
At the end, it will print the path to the generated program. Run the program!
[^1]: See ["How does it Work?"](https://wails.io/docs/howdoesitwork) in the Wails documentation.
[^2]: See [`read` in "Parsing Options"](/docs/api/parse-options)
[^3]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output)
[^4]: See [`OpenFileDialog`](https://wails.io/docs/reference/runtime/dialog#openfiledialog) in the Wails documentation.
[^5]: See [`ReadFile`](https://pkg.go.dev/os#ReadFile) in the Go documentation
[^6]: See [`EncodeToString`](https://pkg.go.dev/encoding/base64#Encoding.EncodeToString) in the Go documentation
[^7]: See [`write` in "Writing Files"](/docs/api/write-options)
[^8]: See ["Supported Output Formats" type in "Writing Files"](/docs/api/write-options#supported-output-formats)
[^9]: See ["HTML Table Input" in "Utilities"](/docs/api/utilities/html#create-new-sheet)
[^10]: See [`write` in "Writing Files"](/docs/api/write-options)
[^11]: See [`SaveFileDialog`](https://wails.io/docs/reference/runtime/dialog#savefiledialog) in the Wails documentation.
[^12]: See [`DecodeString`](https://pkg.go.dev/encoding/base64#Encoding.DecodeString) in the Go documentation
[^13]: See [`WriteFile`](https://pkg.go.dev/os#WriteFile) in the Go documentation
[^14]: See ["Installation"](https://wails.io/docs/gettingstarted/installation) in the Wails documentation.

@ -1,36 +1,66 @@
---
title: C + 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';
QuickJS is an embeddable JS engine written in C. It provides a separate set of
functions for interacting with the filesystem and the global object. It can run
the standalone browser scripts.
[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.
The [Standalone scripts](/docs/getting-started/installation/standalone) can be
parsed and evaluated in a QuickJS context.
[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
_Initialize QuickJS_
:::note
Many QuickJS functions are not documented. The explanation was verified against
the latest release (version `2021-03-27`, commit `2788d71`).
:::
### 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
/* initialize */
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
/* obtain reference to global object */
JSValue global = JS_GetGlobalObject(ctx);
```
/* DO WORK HERE */
<details><summary><b>Cleanup</b> (click to show)</summary>
/* free after use */
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 */
@ -38,27 +68,23 @@ JS_FreeContext(ctx);
JS_FreeRuntime(rt);
```
:::warning
The [Integration Example](#integration-example) frees JS values after use.
All values must be freed with `JS_FreeValue` before calling `JS_FreeContext`!
</details>
`JS_IsException` should be used for validation.
### Load SheetJS Scripts
Cleanup and validation code is omitted from the discussion. The integration
example shows structured validation and controlled memory usage.
:::
_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:
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); fsee (f, 0, SEEK_SET); }
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);
@ -66,22 +92,37 @@ static char *read_file(const char *filename, size_t *sz) {
}
// ...
/* load library */
{
/* 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, "<input>", 0);
/* Free the file buffer */
free(buf);
}
```
To confirm the library is loaded, `XLSX.version` can be inspected:
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");
```
/* print version */
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);
```
@ -97,16 +138,119 @@ 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);
```
/* obtain reference to the XLSX object */
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");
/* call XLSX.read(ab) */
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,
@ -124,6 +268,8 @@ This demo was tested in the following deployments:
| `darwin-arm` | `2788d71` | 2023-06-05 |
| `linux-x64` | `2788d71` | 2023-06-02 |
Git commit `2788d71` corresponds to the latest release (`2021-03-27`)
:::
0) Build `libquickjs.a`:
@ -171,7 +317,7 @@ curl -LO https://sheetjs.com/pres.numbers`}
5) Run the test program:
```
```bash
./sheetjs.quick pres.numbers
```
@ -215,3 +361,9 @@ quickjs SheetJSQuick.js
If successful, the script will generate `SheetJSQuick.xlsx`.
[^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)

@ -31,7 +31,22 @@ 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:
synthesized from JS code in the runtime.
:::note pass
The JavaScript code is shown below:
```js
/* 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); } };
```
:::
The code can be stored in a C string and evaluated after creating a runtime:
```cpp
auto src = std::make_shared<facebook::jsi::StringBuffer>(
@ -96,7 +111,7 @@ Hermes supports `ArrayBuffer` but has no simple helper to read raw memory.
Libraries are expected to implement `MutableBuffer`:
```cpp
/* ArrayBuffer constructor expects MutableBuffer*/
/* ArrayBuffer constructor expects MutableBuffer */
class CMutableBuffer : public facebook::jsi::MutableBuffer {
public:
CMutableBuffer(uint8_t *data, size_t size) : buf(data), sz(size) {}
@ -117,8 +132,10 @@ class CMutableBuffer : public facebook::jsi::MutableBuffer {
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++.
> _JS Stub function_
>
:::note pass
The stub function will be passed an `ArrayBuffer` object:
```js
function(buf) {
/* `buf` will be an ArrayBuffer */
@ -127,6 +144,8 @@ function(buf) {
}
```
:::
_C++ integration code_
```cpp

@ -3,19 +3,102 @@ sidebar_position: 1
title: Addresses and Ranges
---
export const g = {style: {backgroundColor:"green"}};
Each cell in a worksheet has a unique address which specifies the row and the
column that include the cell.
## Basic Concepts
### Rows
Spreadsheet applications typically display ordinal row numbers, where `1` is the
first row, `2` is the second row, etc. The numbering starts at `1`.
SheetJS follows JavaScript counting conventions, where `0` is the first row, `1`
is the second row, etc. The numbering starts at `0`.
The following table lists some example row labels:
| Ordinal | Row Label | SheetJS |
|:----------|----------:|----------:|
| First | `1` | `0` |
| Second | `2` | `1` |
| 26th | `26` | `25` |
| 420th | `420` | `419` |
| 7262nd | `7262` | `7261` |
| 1048576th | `1048576` | `1048575` |
### Columns
Spreadsheet applications typically use letters to represent columns.
The first column is `A`, the second column is `B`, and the 26th column is `Z`.
After `Z`, the next column is `AA` and counting continues through `AZ`. After
`AZ`, the count continues with `BA`. After `ZZ`, the count continues with `AAA`.
Some sample values, along with SheetJS column indices, are listed below:
| Ordinal | Column Label | SheetJS |
|:--------|:-------------|--------:|
| First | `A` | `0` |
| Second | `B` | `1` |
| 26th | `Z` | `25` |
| 27th | `AA` | `26` |
| 420th | `PD` | `419` |
| 702nd | `ZZ` | `701` |
| 703rd | `AAA` | `702` |
| 7262nd | `JSH` | `7261` |
| 16384th | `XFD` | `16383` |
## Cell Addresses
### A1-Style
A1-Style is the default address style in Lotus 1-2-3 and Excel.
A cell address is the concatenation of column label and row label.
For example, the cell in the third column and fourth row is `C4`, concatenating
the third column label (`C`) and the fourth row label (`4`)
### SheetJS Cell Address
Cell address objects are stored as `{c:C, r:R}` where `C` and `R` are 0-indexed
column and row numbers, respectively. For example, the cell address `B5` is
represented by the object `{c:1, r:4}`.
## Cell Ranges
### A1-Style
A cell range is represented as the top-left cell of the range, followed by `:`,
followed by the bottom-right cell of the range. For example, the range `"C2:D4"`
includes the 6 green cells in the following table:
<table><tbody>
<tr><th> </th><th>A</th><th>B</th><th>C</th><th>D</th><th>E</th></tr>
<tr><th>1</th><td> </td><td> </td><td> </td><td> </td><td> </td></tr>
<tr><th>2</th><td> </td><td> </td><td {...g}></td><td {...g}></td><td> </td></tr>
<tr><th>3</th><td> </td><td> </td><td {...g}></td><td {...g}></td><td> </td></tr>
<tr><th>4</th><td> </td><td> </td><td {...g}></td><td {...g}></td><td> </td></tr>
<tr><th>5</th><td> </td><td> </td><td> </td><td> </td><td> </td></tr>
</tbody></table>
A column range is represented by the left-most column, followed by `:`, followed
by the right-most column. For example, the range `C:D` represents the third and
fourth columns.
A row range is represented by the top-most row, followed by `:`, followed by the
bottom-most column. For example, `2:4` represents the second/third/fourth rows.
### SheetJS Range
Cell range objects are stored as `{s:S, e:E}` where `S` is the first cell and
`E` is the last cell in the range. The ranges are inclusive. For example, the
range `A3:B7` is represented by the object `{s:{c:0, r:2}, e:{c:1, r:6}}`.
### Column and Row Ranges
#### Column and Row Ranges
A column range (spanning every row) is represented with the starting row `0` and
the ending row `1048575`:
@ -33,54 +116,9 @@ the ending col `16383`:
{ s: { c: 0, r: 1 }, e: { c: 16383, r: 2 } } // 2:3
```
# Common Spreadsheet Address Styles
## Utilities
## A1-Style
A1-Style is the default address style in Lotus 1-2-3 and Excel.
Columns are specified with letters, counting from `A` to `Z`, then `AA` to `ZZ`,
then `AAA`. Some sample values, along with SheetJS column indices, are listed:
| Ordinal | `A1` | SheetJS |
|:--------|:--------|--------:|
| First | `A` | `0` |
| Second | `B` | `1` |
| 26th | `Z` | `25` |
| 27th | `AA` | `26` |
| 702nd | `ZZ` | `701` |
| 703rd | `AAA` | `702` |
| 16384th | `XFD` | `16383` |
Rows are specified with numbers, starting from `1` for the first row. SheetJS
APIs that take row indices start from `0` (ECMAScript convention).
A cell address is the concatenation of column text and row number. For example,
the cell in the third column and fourth row is "C4".
A cell range is represented as the top-left cell of the range, followed by `:`,
followed by the bottom-right cell of the range. For example, the range `"C2:D4"`
includes 6 cells marked with ▒ in the table below:
<table><tbody>
<tr><th> </th><th>A</th><th>B</th><th>C</th><th>D</th><th>E</th></tr>
<tr><th>1</th><td> </td><td> </td><td> </td><td> </td><td> </td></tr>
<tr><th>2</th><td> </td><td> </td><td></td><td></td><td> </td></tr>
<tr><th>3</th><td> </td><td> </td><td></td><td></td><td> </td></tr>
<tr><th>4</th><td> </td><td> </td><td></td><td></td><td> </td></tr>
<tr><th>5</th><td> </td><td> </td><td> </td><td> </td><td> </td></tr>
</tbody></table>
A column range is represented by the left-most column, followed by `:`, followed
by the right-most column. For example, the range `C:D` represents the third and
fourth columns.
A row range is represented by the top-most row, followed by `:`, followed by the
bottom-most column. For example, `2:4` represents the second/third/fourth rows.
### Utilities
#### Column Names
### Column Names
_Get the SheetJS index from an A1-Style column_
@ -98,7 +136,7 @@ var col_name = XLSX.utils.encode_col(3);
The argument is expected to be a SheetJS column (non-negative integer).
#### Row Names
### Row Names
_Get the SheetJS index from an A1-Style row_
@ -116,7 +154,7 @@ var row_name = XLSX.utils.encode_row(3);
The argument is expected to be a SheetJS column (non-negative integer).
#### Cell Addresses
### Cell Addresses
_Generate a SheetJS cell address from an A1-Style address string_
@ -134,7 +172,7 @@ var a1_addr = XLSX.utils.encode_cell({r:1, c:0});
The argument is expected to be a SheetJS cell address
#### Cell Ranges
### Cell Ranges
_Generate a SheetJS cell range from an A1-Style range string_

@ -104,6 +104,7 @@ Read functions attempt to populate all three properties. Write functions will
try to cycle specified values to the desired type. In order to avoid potential
conflicts, manipulation should delete the other properties first. For example,
when changing the pixel width, delete the `wch` and `width` properties.
</details>
<details>
@ -222,10 +223,6 @@ function Visibility(props) {
</tr>))}</tbody></table>);
}
```
</details>

@ -7,9 +7,12 @@ title: Common Spreadsheet Format
import DocCardList from '@theme/DocCardList';
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
The "Common Spreadsheet Format" is the object model used by SheetJS. This
section covers the JS representation of workbooks, worksheets, cells, ranges,
addresses and other features.
The "Common Spreadsheet Format" is the object model used by SheetJS. The library
[includes a number of API functions](/docs/api) for common operations, but some
features are only accessible by inspecting and modifying the objects directly.
This section covers the JS representation of workbooks, worksheets, cells,
ranges, addresses and other features.
### Contents

@ -4,15 +4,26 @@ sidebar_position: 5
title: API Reference
---
## Interface Summary
import current from '/version.js';
`XLSX` is the exposed variable in the browser and the exported node variable
This section lists the functions defined in the library.
`XLSX.version` is the version of the library (added by the build script).
Using the ["Standalone" scripts](/docs/getting-started/installation/standalone),
`XLSX` is added to the `window` or other `global` object.
`XLSX.SSF` is an embedded version of the [format library](https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/packages/ssf).
Using the ["NodeJS" module](/docs/getting-started/installation/nodejs), the
`XLSX` variable refers to the CommonJS export:
`XLSX.CFB` is an embedded version of the [container library](https://git.sheetjs.com/sheetjs/js-cfb).
```js
var XLSX = require("xlsx");
```
Using [a framework](/docs/getting-started/installation/frameworks), the `XLSX`
variable refers to the glob import:
```js
import * as XLSX from "xlsx";
```
## Parsing functions
@ -127,4 +138,20 @@ for different languages in XLS or text parsing.
provides NodeJS ESM support for `XLSX.readFile` and `XLSX.writeFile`.
`XLSX.utils.set_readable` supplies a NodeJS `stream.Readable` constructor. This
provides NodeJS ESM support for the streaming operations.
provides NodeJS ESM support for the streaming operations.
ESM helper functions are described in the ["NodeJS" Installation section](/docs/getting-started/installation/nodejs)
## Miscellaneous
`XLSX.version` is the version of the library.
:::note pass
<p>The current version is <code>{current}</code></p>
:::
`XLSX.SSF` is an embedded version of the [format library](https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/packages/ssf).
`XLSX.CFB` is an embedded version of the [container library](https://git.sheetjs.com/sheetjs/js-cfb).

@ -34,6 +34,7 @@ const config = {
({
docs: {
sidebarPath: require.resolve('./sidebars.js'),
showLastUpdateTime: true,
editUrl: 'https://git.sheetjs.com/sheetjs/docs.sheetjs.com/src/branch/master/docz',
},
//blog: {

@ -0,0 +1,180 @@
import React from 'react';
import clsx from 'clsx';
import {ThemeClassNames} from '@docusaurus/theme-common';
import Translate from '@docusaurus/Translate';
import styles from './styles.module.css';
function NoteIcon() {
return (
<svg viewBox="0 0 14 16">
<path
fillRule="evenodd"
d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"
/>
</svg>
);
}
function TipIcon() {
return (
<svg viewBox="0 0 12 16">
<path
fillRule="evenodd"
d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"
/>
</svg>
);
}
function DangerIcon() {
return (
<svg viewBox="0 0 12 16">
<path
fillRule="evenodd"
d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"
/>
</svg>
);
}
function InfoIcon() {
return (
<svg viewBox="0 0 14 16">
<path
fillRule="evenodd"
d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"
/>
</svg>
);
}
function CautionIcon() {
return (
<svg viewBox="0 0 16 16">
<path
fillRule="evenodd"
d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"
/>
</svg>
);
}
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
const AdmonitionConfigs = {
note: {
infimaClassName: 'secondary',
iconComponent: NoteIcon,
label: (
<Translate
id="theme.admonition.note"
description="The default label used for the Note admonition (:::note)">
note
</Translate>
),
},
tip: {
infimaClassName: 'success',
iconComponent: TipIcon,
label: (
<Translate
id="theme.admonition.tip"
description="The default label used for the Tip admonition (:::tip)">
tip
</Translate>
),
},
danger: {
infimaClassName: 'danger',
iconComponent: DangerIcon,
label: (
<Translate
id="theme.admonition.danger"
description="The default label used for the Danger admonition (:::danger)">
danger
</Translate>
),
},
info: {
infimaClassName: 'info',
iconComponent: InfoIcon,
label: (
<Translate
id="theme.admonition.info"
description="The default label used for the Info admonition (:::info)">
info
</Translate>
),
},
caution: {
infimaClassName: 'warning',
iconComponent: CautionIcon,
label: (
<Translate
id="theme.admonition.caution"
description="The default label used for the Caution admonition (:::caution)">
caution
</Translate>
),
},
};
// Legacy aliases, undocumented but kept for retro-compatibility
const aliases = {
secondary: 'note',
important: 'info',
success: 'tip',
warning: 'danger',
};
function getAdmonitionConfig(unsafeType) {
const type = aliases[unsafeType] ?? unsafeType;
const config = AdmonitionConfigs[type];
if (config) {
return config;
}
console.warn(
`No admonition config found for admonition type "${type}". Using Info as fallback.`,
);
return AdmonitionConfigs.info;
}
// Workaround because it's difficult in MDX v1 to provide a MDX title as props
// See https://github.com/facebook/docusaurus/pull/7152#issuecomment-1145779682
function extractMDXAdmonitionTitle(children) {
const items = React.Children.toArray(children);
const mdxAdmonitionTitle = items.find(
(item) =>
React.isValidElement(item) &&
item.props?.mdxType === 'mdxAdmonitionTitle',
);
const rest = <>{items.filter((item) => item !== mdxAdmonitionTitle)}</>;
return {
mdxAdmonitionTitle,
rest,
};
}
function processAdmonitionProps(props) {
const {mdxAdmonitionTitle, rest} = extractMDXAdmonitionTitle(props.children);
return {
...props,
title: props.title ?? mdxAdmonitionTitle,
children: rest,
};
}
export default function Admonition(props) {
const {children, type, title, icon: iconProp} = processAdmonitionProps(props);
const typeConfig = getAdmonitionConfig(type);
const titleLabel = title ?? typeConfig.label;
const {iconComponent: IconComponent} = typeConfig;
const icon = iconProp ?? <IconComponent />;
return (
<div
className={clsx(
ThemeClassNames.common.admonition,
ThemeClassNames.common.admonitionType(props.type),
'alert',
`alert--${typeConfig.infimaClassName}`,
styles.admonition,
)}>
{titleLabel == "pass" ? void 0 : (
<div className={styles.admonitionHeading}>
<span className={styles.admonitionIcon}>{icon}</span>
{titleLabel}
</div>
)}
<div className={styles.admonitionContent}>{children}</div>
</div>
);
}
/* See docusaurus issue 8568 -- this was swizzled against 2.4.1 */

@ -0,0 +1,31 @@
.admonition {
margin-bottom: 1em;
}
.admonitionHeading {
font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) /
var(--ifm-heading-line-height) var(--ifm-heading-font-family);
text-transform: uppercase;
margin-bottom: 0.3rem;
}
.admonitionHeading code {
text-transform: none;
}
.admonitionIcon {
display: inline-block;
vertical-align: middle;
margin-right: 0.4em;
}
.admonitionIcon svg {
display: inline-block;
height: 1.6em;
width: 1.6em;
fill: var(--ifm-alert-foreground-color);
}
.admonitionContent > :last-child {
margin-bottom: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB