diff --git a/docz/docs/03-demos/35-server.md b/docz/docs/03-demos/35-server.md
index 6d450ef..3362d64 100644
--- a/docz/docs/03-demos/35-server.md
+++ b/docz/docs/03-demos/35-server.md
@@ -115,7 +115,7 @@ with `Response#send`:
```ts
// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
-import { read, utils } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
+import { utils, write } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
diff --git a/docz/docs/03-demos/45-git.md b/docz/docs/03-demos/45-git.md
new file mode 100644
index 0000000..2f20aef
--- /dev/null
+++ b/docz/docs/03-demos/45-git.md
@@ -0,0 +1,329 @@
+---
+title: Data in Version Control
+---
+
+Git is a popular system for organizing a historical record of source code and
+changes. Git can also store and track binary data artifacts, but data tools
+are more effective in processing data stored in plain text formats like CSV.
+
+Many official data releases by governments and organizations include XLSX or
+XLS files. SheetJS trivializes the conversion to CSV. For example, in NodeJS:
+
+```js
+const XLSX = require("xlsx");
+
+(async() => {
+ /* Download Data */
+ const f = await fetch("https://sheetjs.com/pres.xlsx");
+ const data = await f.arrayBuffer();
+
+ /* Parse workbook */
+ // highlight-next-line
+ const wb = XLSX.read(data);
+
+ /* Convert first worksheet to CSV */
+ const ws = wb.Sheets[wb.SheetNames[0]];
+ // highlight-next-line
+ const csv = XLSX.utils.sheet_to_csv(ws);
+ console.log(csv);
+})();
+```
+
+GitHub's ["Flat Data"](https://githubnext.com/projects/flat-data/) project
+explores storing and comparing versions of structured CSV and JSON data. The
+official ["Excel to CSV"](https://github.com/githubocto/flat-demo-xlsx) example
+uses SheetJS under the hood to generate CSV data from an XLSX file.
+
+This demo covers implementation details elided in the official writeup.
+
+## Flat Data
+
+As a project from the company, the entire lifecycle uses GitHub offerings:
+
+- GitHub offers free hosting for Git repositories
+- GitHub Actions provide the main engine for running tasks at regular intervals
+- `githubocto/flat` Action to help fetch data and automate post-processing
+- `flat-postprocessing` Post-processing helper functions and examples
+- "Flat Viewer": Web viewer for structured CSV and JSON data on GitHub
+
+:::caution
+
+A GitHub account is required. At the time of writing (2022 August 29), free
+GitHub accounts have no Actions usage limits for public repositories.
+
+Using private GitHub repositories is not recommended because the Flat Viewer
+cannot access private repositories.
+
+:::
+
+
+### Data Source
+
+Any publicly available spreadsheet can be a valid data source. The process will
+fetch the data on specified intervals or events.
+
+This demo endpoint generates XLSX files.
+
+Server Details (click to show)
+
+This demo is hosted on Deno Deploy.
+
+```ts
+// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
+import { utils, writeXLSX } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
+
+import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
+
+const rand = (x:number, n = 10): number => ((x + n * (Math.random() - 0.5) + 10)|0)%10;
+
+class HomeResource extends Drash.Resource {
+ public paths = ["/"];
+
+ // see https://github.com/drashland/drash/issues/194
+ public OPTIONS(request: Drash.Request, response: Drash.Response) {
+ const allHttpMethods: string[] = [ "GET", "POST", "PUT", "DELETE" ];
+ response.headers.set("Allow", allHttpMethods.join()); // Use this
+ response.headers.set("Access-Control-Allow-Methods", allHttpMethods.join()); // or this
+ response.headers.set("access-control-allow-origin", "*");
+ response.status_code = 204;
+ return response;
+ }
+
+ public GET(request: Drash.Request, response: Drash.Response): void {
+ // create a workbook with some random data
+ let data: any[][] = [ "ABCDEFG".split("") ];
+ for(let i = 0; i < 10; ++i) data = data.concat([
+ [5,4,3,3,7,9,5].map(v => rand(v)),
+ [5,4,3,3,7,9,5].map(v => rand(v, 8)),
+ [5,4,3,3,7,9,5].map(v => rand(v, 6)),
+ [5,4,3,3,7,9,5].map(v => rand(v, 4)),
+ [5,4,3,3,7,9,5].map(v => rand(v, 2)),
+ [5,4,3,3,7,9,5].map(v => rand(v, 0))
+ ]);
+ const ws = utils.aoa_to_sheet(data);
+ const wb = utils.book_new(); utils.book_append_sheet(wb, ws, "data");
+ // write the workbook to XLSX as a Uint8Array
+ const file = writeXLSX(wb, { type: "buffer"});
+ // set headers
+ response.headers.set("Content-Disposition", 'attachment; filename="LiveSheet.xlsx"');
+ // send data
+ return response.send("application/vnd.ms-excel", file);
+ }
+}
+
+// Create and run your server
+const server = new Drash.Server({
+ hostname: "",
+ port: 3000,
+ protocol: "http",
+ resources: [ HomeResource ],
+});
+
+server.run();
+
+console.log(`Server running at ${server.address}.`);
+```
+
+
+
+
+### Action
+
+The `githubocto/flat` action can be added as a step in a workflow:
+
+```yaml
+ - name: Fetch data
+ uses: githubocto/flat@v3
+ with:
+ http_url: https://livesheet.deno.dev/
+ downloaded_filename: data.xlsx
+ postprocess: ./postprocess.ts
+```
+
+The `http_url` will be fetched and saved to `downloaded_filename` in the repo.
+This can be approximated with the following command:
+
+```bash
+curl -L -o data.xlsx https://livesheet.deno.dev/
+```
+
+After saving, the `postprocess` script will be run. When a `.ts` file is the
+script, it will run the script in the Deno runtime. The `postprocess` script is
+expected to read the downloaded file and create or overwrite files in the repo.
+This can be approximated with the following command:
+
+```bash
+deno run -A ./postprocess.ts data.xlsx
+```
+
+The action will then compare the contents of the repo, creating a new commit if
+the source data or artifacts from the `postprocess` script changed.
+
+
+### Post-Processing Data
+
+:::caution
+
+The `flat-postprocessing` library includes a number of utilities for different
+data formats. The `readXLSX` helper uses SheetJS under the hood.
+
+The library uses an older version of the SheetJS library. To use the latest
+releases, the examples import from the SheetJS CDN:
+
+```ts
+// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
+import * as XLSX from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
+```
+
+:::
+
+#### Post-Process Script
+
+The first argument to the post-processing script is the filename. The file can
+be read with `XLSX.readFile` directly. On the export side, `writeCSV` from the
+`flat` library can write data generated from `XLSX.utils.sheet_to_csv`:
+
+```ts title="postprocess.ts"
+import { writeCSV } from "https://deno.land/x/flat/mod.ts";
+// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
+import * as XLSX from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
+/* load the codepage support library for extended support with older formats */
+import * as cptable from 'https://cdn.sheetjs.com/xlsx-latest/package/dist/cpexcel.full.mjs';
+XLSX.set_cptable(cptable);
+
+/* get the file path for the downloaded file and generate the CSV path */
+const in_file = Deno.args[0];
+const out_file = in_file.replace(/.xlsx$/, ".csv");
+
+/* read file */
+// highlight-next-line
+const workbook = XLSX.readFile(in_file);
+
+/* generate CSV from first worksheet */
+const first_sheet = workbook.Sheets[workbook.SheetNames[0]];
+// highlight-next-line
+const csv = XLSX.utils.sheet_to_csv(first_sheet);
+
+/* write CSV */
+// highlight-next-line
+await writeCSV(out_file, csv);
+```
+
+
+## Complete Example
+
+:::note
+
+This was tested on 2022 August 29 using the GitHub UI.
+
+:::
+
+Complete Example (click to show)
+
+0) Create a free GitHub account or sign into the GitHub web interface.
+
+1) Create a new repository (click the "+" icon in the upper-right corner).
+
+- When prompted, enter a repository name of your choosing.
+- Ensure "Public" is selected
+- Check "Add a README file"
+- Click "Create repository" at the bottom.
+
+You will be redirected to the new project.
+
+2) In the browser URL bar, change "github.com" to "github.dev". For example, if
+ the URL was originally `https://github.com/SheetJS/flat-sheet` , the new URL
+ should be `https://github.dev/SheetJS/flat-sheet` . Press Enter.
+
+3) In the left "EXPLORER" panel, double-click just below README.md. A text box
+ will appear just above README. Type `postprocess.ts` and press Enter.
+
+ The main panel will show a `postprocess.ts` tab. Copy the following code to
+ the main editor window:
+
+```ts title="postprocess.ts"
+import { writeCSV } from "https://deno.land/x/flat/mod.ts";
+// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
+import * as XLSX from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
+/* load the codepage support library for extended support with older formats */
+import * as cptable from 'https://cdn.sheetjs.com/xlsx-latest/package/dist/cpexcel.full.mjs';
+XLSX.set_cptable(cptable);
+
+/* get the file path for the downloaded file and generate the CSV path */
+const in_file = Deno.args[0];
+const out_file = in_file.replace(/.xlsx$/, ".csv");
+
+/* read file */
+const workbook = XLSX.readFile(in_file);
+
+/* generate CSV */
+const first_sheet = workbook.Sheets[workbook.SheetNames[0]];
+const csv = XLSX.utils.sheet_to_csv(first_sheet);
+
+/* write CSV */
+await writeCSV(out_file, csv);
+```
+
+
+4) In the left "EXPLORER" panel, double-click just below README.md. A text box
+ will appear. Type `.github/workflows/data.yaml` and press Enter.
+
+ Copy the following code into the main area. It will create an action that
+ runs roughly once an hour:
+
+```yaml title=".github/workflows/data.yaml"
+name: flatsheet
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 * * * *'
+
+jobs:
+ scheduled:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Setup deno
+ uses: denoland/setup-deno@main
+ with:
+ deno-version: v1.x
+ - name: Check out repo
+ uses: actions/checkout@v2
+ - name: Fetch data
+ uses: githubocto/flat@v3
+ with:
+ http_url: https://livesheet.deno.dev/
+ downloaded_filename: data.xlsx
+ postprocess: ./postprocess.ts
+```
+
+5) Click on the source control icon (a little blue circle with the number 2).
+ In the left panel, select Message box, type `init` and press `Ctrl+Enter` on
+ Windows (`Command+Enter` on Mac).
+
+6) Click the `☰` icon and click "Go to Repository" to return to the repo page.
+
+7) Click "Actions" to see the workflows. In the left column, click `flatsheet`.
+
+ This is the page for the action. Every time the action is run, a new entry
+ will be added to the list.
+
+ Click "Run workflow", then click the "Run workflow" button in the popup.
+ This will start a new run. After about 30 seconds, a new row should show up
+ in the main area. The icon should be a white `✓` in a green circle.
+
+8) Click "Code" to return to the main view. It should have a file listing that
+ includes `data.xlsx` (downloaded file) and `data.csv` (generated data)
+
+ Now repeat step 7 to run the action a second time. Click "Code" again.
+
+9) Go to the URL bar and change "github.com" to "flatgithub.com". For example,
+ if the URL was originally `https://github.com/SheetJS/flat-sheet` , the new
+ URL should be `https://flatgithub.com/SheetJS/flat-sheet` . Press Enter.
+
+ You will see the "Flat Viewer". In the top bar, the "Commit" option allows
+ for switching to an older version of the data.
+
+ The update process will run once an hour. If you return in a few hours and
+ refresh the page, there should be more commits in the selection list.
+
\ No newline at end of file
diff --git a/docz/docs/07-csf/07-features/01-formulae.md b/docz/docs/07-csf/07-features/01-formulae.md
index d689f8e..b5f7bb2 100644
--- a/docz/docs/07-csf/07-features/01-formulae.md
+++ b/docz/docs/07-csf/07-features/01-formulae.md
@@ -130,10 +130,14 @@ SheetJS formulae omit the `=`.
["A1-Style"](../general#a1-style) describes A1-Style in more detail.
+Live Example (click to hide)
+
For example, consider [this test file](pathname:///files/concat.xlsx):
![Screenshot](pathname:///files/concat.png)
+The following code block fetches the file, parses and prints info on cell `D1`:
+
```jsx live
/* The live editor requires this function wrapper */
function ConcatFormula(props) {
@@ -162,6 +166,7 @@ VALUE (TYPE): "${v}" ("${t}")\n\
}
```
+
## Single-Cell Formulae
@@ -187,7 +192,7 @@ var worksheet = XLSX.utils.aoa_to_sheet([
]);
```
-Live Example (click to show)
+Live Example (click to hide)
```jsx live
/* The live editor requires this function wrapper */
@@ -270,6 +275,47 @@ Utilities and writers are expected to check for the presence of a `F` field and
ignore any possible formula element `f` in cells other than the starting cell.
They are not expected to perform validation of the formulae!
+Live Example (click to show)
+
+```jsx live
+/* The live editor requires this function wrapper */
+function ExportArrayFormulae(props) {
+
+ /* Callback invoked when the button is clicked */
+ const xport = React.useCallback(() => {
+ /* Starting data */
+ var D = [
+ [ "A", "B", , "Cell AF", "Expected", , "Range AF", "Expected" ],
+ [ 1, 2 ],
+ [ 3, 4 ],
+ [ 5, 6 ]
+ ];
+ /* Add expected values */
+ let sum = 0;
+ for(let i = 1; i < D.length; ++i) sum += (D[i][7] = D[i][0] * D[i][1]);
+ D[1][4] = sum;
+
+ /* Create worksheet */
+ var ws = XLSX.utils.aoa_to_sheet(D);
+
+ /* D2 single-cell array formula */
+ XLSX.utils.sheet_set_array_formula(ws, "D2", "SUM(A2:A4*B2:B4)");
+
+ /* G2:G4 range array formula */
+ XLSX.utils.sheet_set_array_formula(ws, "G2:G4", "A2:A4*B2:B4");
+
+ /* Export to file (start a download) */
+ var wb = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
+ XLSX.writeFile(wb, "SheetJSArrayFormulae.xlsx");
+ });
+
+ return ();
+}
+```
+
+
+
### Dynamic Array Formulae
@@ -294,6 +340,43 @@ XLSX.utils.sheet_set_array_formula(worksheet, "C1", "_xlfn.UNIQUE(A1:A3)", 1);
worksheet["C1"] = { t: "s", f: "_xlfn.UNIQUE(A1:A3)", F:"C1", D: 1 }; // dynamic
```
+Live Example (click to show)
+
+```jsx live
+/* The live editor requires this function wrapper */
+function ExportDynamicArrayFormulae(props) {
+
+ /* Callback invoked when the button is clicked */
+ const xport = React.useCallback(() => {
+ /* Starting data */
+ var D = [
+ [ "A", , "Static", "Expected", , "Dynamic", "Expected" ],
+ [ 1, , , 1 , , , 1 ],
+ [ 2, , , , , , 2 ],
+ [ 1, ]
+ ];
+
+ /* Create worksheet */
+ var ws = XLSX.utils.aoa_to_sheet(D);
+
+ /* C2 static formula */
+ XLSX.utils.sheet_set_array_formula(ws, "C2", "_xlfn.UNIQUE(A2:A4)");
+
+ /* F2 dynamic formula */
+ XLSX.utils.sheet_set_array_formula(ws, "F2", "_xlfn.UNIQUE(A2:A4)", 1);
+
+ /* Export to file (start a download) */
+ var wb = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
+ XLSX.writeFile(wb, "SheetJSDynamicFormulae.xlsx");
+ });
+
+ return ();
+}
+```
+
+
+
## Localization
SheetJS operates at the file level. Excel stores formula expressions using the
diff --git a/docz/docs/07-csf/07-features/02-hyperlinks.md b/docz/docs/07-csf/07-features/02-hyperlinks.md
index 3205cb3..83f9c07 100644
--- a/docz/docs/07-csf/07-features/02-hyperlinks.md
+++ b/docz/docs/07-csf/07-features/02-hyperlinks.md
@@ -28,6 +28,34 @@ Note that Excel does not automatically style hyperlinks. They will be displayed
using default style. SheetJS Pro Basic
extends this export with support for hyperlink styling.
+Live Example (click to show)
+
+```jsx live
+/* The live editor requires this function wrapper */
+function ExportSimpleLink(props) {
+
+ /* Callback invoked when the button is clicked */
+ const xport = React.useCallback(() => {
+ /* Create worksheet */
+ var ws = XLSX.utils.aoa_to_sheet([ [ "Link", "No Link" ] ]);
+ /* Add link */
+ ws["A1"].l = {
+ Target: "https://sheetjs.com",
+ Tooltip: "Find us @ SheetJS.com!"
+ };
+
+ /* Export to file (start a download) */
+ var wb = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
+ XLSX.writeFile(wb, "SheetJSSimpleLink.xlsx");
+ });
+
+ return ();
+}
+```
+
+
+
## Remote Links
HTTP / HTTPS links can be used directly:
@@ -93,7 +121,7 @@ function ExportHyperlink(props) {
const wb = XLSX.utils.table_to_book(table);
/* Export to file (start a download) */
- XLSX.writeFile(wb, "SheetJSHyperlink1.xlsx");
+ XLSX.writeFile(wb, "SheetJSHTMLHyperlink.xlsx");
});
return (<>
diff --git a/docz/docs/09-miscellany/02-errors.md b/docz/docs/09-miscellany/02-errors.md
index f3a8649..39c6f9b 100644
--- a/docz/docs/09-miscellany/02-errors.md
+++ b/docz/docs/09-miscellany/02-errors.md
@@ -23,7 +23,7 @@ workarounds and solutions!
#### SCRIPT5022: DataCloneError
-IE10 does not properly support Transferables.
+IE10 does not properly support `Transferable`.
#### Object doesn't support property or method 'slice'
@@ -59,7 +59,7 @@ Old versions of the library did not automatically detect `ArrayBuffer` objects.
:::warning Legacy workaround
-This soltion is not recommended for production deployments. Native support
+This solution is not recommended for production deployments. Native support
for `ArrayBuffer` was added in library version `0.9.9`.
:::