This commit is contained in:
SheetJS 2023-05-02 23:40:40 -04:00
parent 775e72c345
commit 32021ab7ed
26 changed files with 598 additions and 381 deletions

@ -267,46 +267,46 @@ hosted (no `file:///` access).
<https://sheetjs.com/pres.html> is a hosted version of the page.
```html
<CodeBlock language="html" title="snippet.html">{`\
<body>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
<script>
(async() => {
/* fetch JSON data and parse */
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
\n\
/* filter for the Presidents */
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
\n\
/* sort by first presidential term */
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
prez.sort((l,r) => l.start.localeCompare(r.start));
\n\
/* flatten objects */
const rows = prez.map(row => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
\n\
/* generate worksheet and workbook */
const worksheet = XLSX.utils.json_to_sheet(rows);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
\n\
/* fix headers */
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
\n\
/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
\n\
/* create an XLSX file and try to save to Presidents.xlsx */
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
})();
</script>
<body>
```
</body>`}
</CodeBlock>
</TabItem>
<TabItem value="nodejs" label="Command-Line (NodeJS)">
@ -423,50 +423,49 @@ const axios = require("axios");
<Tabs>
<TabItem value="deno" label="Deno">
Save the following script to `snippet.ts` and run with
`deno run --allow-net --allow-write snippet.ts`:
```js title="snippet.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';
Save the following script to `snippet.ts` and run `deno run -A snippet.ts`:
<CodeBlock language="ts" title="snippet.ts">{`\
// @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts"
import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
\n\
/* fetch JSON data and parse */
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
\n\
/* filter for the Presidents */
const prez = raw_data.filter((row: any) => row.terms.some((term: any) => term.type === "prez"));
\n\
/* sort by first presidential term */
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
prez.sort((l,r) => l.start.localeCompare(r.start));
\n\
/* flatten objects */
const rows = prez.map((row: any) => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
\n\
/* generate worksheet and workbook */
const worksheet = XLSX.utils.json_to_sheet(rows);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
\n\
/* fix headers */
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
\n\
/* calculate column width */
const max_width = rows.reduce((w: number, r: any) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
\n\
/* create an XLSX file and try to save to Presidents.xlsx */
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
```
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });`}
</CodeBlock>
</TabItem>
<TabItem value="bun" label="Bun">
Download <https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs> to `xlsx.mjs`
<div>Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs`}>https://cdn.sheetjs.com/xlsx-{current}/package/xlsx.mjs</a> to <code>xlsx.mjs</code></div>
Save the following script to `snippet.js` and run with `bun snippet.js`:
@ -519,46 +518,46 @@ XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
Save the following script to `snippet.html`:
```html title="snippet.html"
<CodeBlock language="html" title="snippet.html">{`\
<body>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
<script>
(async() => {
/* fetch JSON data and parse */
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
\n\
/* filter for the Presidents */
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
\n\
/* sort by first presidential term */
prez.forEach(prez => prez.start = prez.terms.find(term => term.type === "prez").start);
prez.sort((l,r) => l.start.localeCompare(r.start));
\n\
/* flatten objects */
const rows = prez.map(row => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
\n\
/* generate worksheet and workbook */
const worksheet = XLSX.utils.json_to_sheet(rows);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
\n\
/* fix headers */
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
\n\
/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
\n\
/* create an XLSX file and try to save to Presidents.xlsx */
XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
})();
</script>
<body>
```
</body>`}
</CodeBlock>
Save the following to `package.json`:

@ -602,7 +602,7 @@ npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
npm start
```
The traditional site URL is http://localhost:4200/ . Open the page with a web
The traditional site URL is `http://localhost:4200/` . Open the page with a web
browser and open the console. In the "Elements" tab, the `app-root` element
will have an `ng-version` attribute.

@ -5,6 +5,9 @@ pagination_next: demos/grid/index
sidebar_position: 7
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
:::warning
This demo is for the legacy AngularJS framework (version 1).
@ -162,18 +165,18 @@ $scope.exportSheetJS = function() {
1) Save the following to `index.html`:
```html title="index.html"
<CodeBlock language="html" title="index.html">{`\
<!DOCTYPE html>
<html ng-app="s5s">
<head>
<title>SheetJS + AngularJS</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
</head>
<body>
<h3><a href="https://sheetjs.com">SheetJS + AngularJS demo</a></h3>
\n\
<div ng-controller="sheetjs">
<button ng-click="exportSheetJS()">Export Table</button>
<table id="s5s-table">
@ -184,7 +187,7 @@ $scope.exportSheetJS = function() {
</tr>
</table>
</div>
\n\
<script>
var app = angular.module('s5s', []);
app.controller('sheetjs', function($scope, $http) {
@ -206,8 +209,8 @@ app.controller('sheetjs', function($scope, $http) {
});
</script>
</body>
</html>
```
</html>`}
</CodeBlock>
2) Start a local web server with `npx http-server .` and access the displayed
URL with a web browser (typically `http://localhost:8080`)
@ -261,24 +264,24 @@ The HTML table can be directly exported with `XLSX.utils.table_to_book`:
1) Save the following to `index.html`:
```html title="index.html"
<CodeBlock language="html" title="index.html">{`\
<!DOCTYPE html>
<html ng-app="s5s">
<head>
<title>SheetJS + AngularJS</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-sanitize.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
</head>
<body>
<h3><a href="https://sheetjs.com">SheetJS + AngularJS demo</a></h3>
\n\
<div ng-controller="sheetjs">
<button ng-click="exportSheetJS()">Export Table</button>
<div ng-bind-html="data" id="tbl"></div>
</div>
\n\
<script>
var app = angular.module('s5s', ['ngSanitize']);
app.controller('sheetjs', function($scope, $http) {
@ -298,8 +301,8 @@ app.controller('sheetjs', function($scope, $http) {
});
</script>
</body>
</html>
```
</html>`}
</CodeBlock>
2) Start a local web server with `npx http-server .` and access the displayed
URL with a web browser (typically `http://localhost:8080`)

@ -85,7 +85,7 @@ npx browserify xlsxworker.js > worker.js
npx http-server
```
5) Access the site http://localhost:8080/ and use the file input element to
5) Access the site `http://localhost:8080/` and use the file input element to
select a spreadsheet.
</details>
@ -262,7 +262,7 @@ writeFileXLSX(workbook, "Presidents.xlsx");
npx esbuild esbrowser.js --bundle --outfile=esb.browser.js
```
5) Start a local HTTP server, then go to http://localhost:8080/
5) Start a local HTTP server, then go to `http://localhost:8080/`
```bash
npx http-server .
@ -556,7 +556,7 @@ npx rollup index.js --plugin @rollup/plugin-node-resolve --file bundle.js --form
```
5) Start a local HTTP server, then go to http://localhost:8080/
5) Start a local HTTP server, then go to `http://localhost:8080/`
```bash
npx http-server .
@ -655,7 +655,7 @@ Unlike other bundlers, Snowpack requires a full page including `HEAD` element.
npx snowpack build
```
5) Start a local HTTP server, then go to http://localhost:8080/
5) Start a local HTTP server, then go to `http://localhost:8080/`
```bash
npx http-server build/
@ -772,7 +772,7 @@ This command will create the script `lib/web.js`
</html>
```
6) Start a local HTTP server, then go to http://localhost:8080/
6) Start a local HTTP server, then go to `http://localhost:8080/`
```bash
npx http-server build/
@ -1053,7 +1053,7 @@ web server in the `dist` folder:
npx http-server dist/
```
Access http://localhost:8080 in your web browser.
Access `http://localhost:8080` in your web browser.
</details>
@ -1251,7 +1251,7 @@ npx webpack --mode=production
</html>
```
6) Start a local HTTP server and go to http://localhost:8080/
6) Start a local HTTP server and go to `http://localhost:8080/`
```bash
npx http-server .
@ -1344,7 +1344,7 @@ writeFileXLSX(workbook, "Presidents.xlsx");
npx wmr build
```
5) Start a local HTTP server in `dist` folder and go to http://localhost:8080/
5) Start a local HTTP server in `dist` folder and go to `http://localhost:8080/`
```bash
npx http-server dist/

@ -8,6 +8,7 @@ sidebar_custom_props:
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
Over the years, many frameworks have been released. Some were popular years ago
but have waned in recent years. There are still many deployments using these
@ -192,11 +193,11 @@ includes details for use with `require`.
The demos use the async loading strategy with the SheetJS CDN:
```html
<CodeBlock language="html">{`\
<script>
dojoConfig = {
packages: [
{ name: "xlsx", location: "https://cdn.sheetjs.com/xlsx-latest/package/dist", main: "xlsx.full.min" }
{ name: "xlsx", location: "https://cdn.sheetjs.com/xlsx-${current}/package/dist", main: "xlsx.full.min" }
]
}
</script>
@ -205,8 +206,8 @@ dojoConfig = {
require(["dojo/request/xhr", "xlsx"], function(xhr, _XLSX) {
/* XLSX-related operations happen in the callback */
});
</script>
```
</script>`}
</CodeBlock>
</details>

@ -5,6 +5,7 @@ pagination_next: demos/net/index
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
Various JavaScript UI components provide a more interactive editing experience.
Most are able to interchange with arrays of arrays or arrays of data objects.
@ -202,9 +203,9 @@ function muidg_to_ws(rows: Row[]): WorkSheet {
1) Install dependencies:
```bash
npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz @mui/x-data-grid @emotion/react @emotion/styled
```
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/x-data-grid @emotion/react @emotion/styled`}
</CodeBlock>
2) Download [`App.tsx`](pathname:///muidg/App.tsx) and replace `src/App.tsx`.
@ -246,26 +247,36 @@ TABLE elements and when writing to XLSX and other spreadsheet formats.
When the page has a raw HTML table, the easiest solution is to attach an `id`:
```html
<CodeBlock language="html">{`\
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
\n\
<!-- table with id \`xport\` -->
<table id="xport"><tr><td>SheetJS</td></tr></table>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
\n\
<script>
/* as long as this script appears after the table, it will be visible */
var tbl = document.getElementById("xport");
const wb = XLSX.utils.table_to_book(tbl);
XLSX.writeFile(wb, "HTMLTable.xlsx");
</script>
```
</script>`}
</CodeBlock>
When programmatically constructing the table in the browser, retain a reference:
```js
/* assemble table */
var tbl = document.createElement("TABLE");
tbl.insertRow(0).insertCell(0).innerHTML = "SheetJS";
/* add to document body */
document.body.appendChild(tbl);
/* generate workbook and export */
const wb = XLSX.utils.table_to_book(tbl);
XLSX.writeFile(wb, "HTMLFlicker.xlsx");
/* remove from document body */
document.body.removeChild(tbl);
```
@ -337,9 +348,9 @@ cd sheetjs-mui
2) Install dependencies:
```bash
npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz @mui/material
```
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/material`}
</CodeBlock>
3) Replace `src/App.tsx` with the following code. This is based on the official
Material UI Table example. Differences are highlighted.

@ -3,6 +3,7 @@ title: HTTP Server Processing
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
Server-Side JS platforms like NodeJS and Deno have built-in APIs for listening
on network interfaces. They provide wrappers for requests and responses.
@ -64,172 +65,13 @@ require("http").createServer(function(req, res) {
}).listen(process.env.PORT || 3000);
```
## Deno
:::caution
Many hosted services like Deno Deploy do not offer filesystem access.
This breaks web frameworks that use the filesystem in body parsing.
:::
Deno provides the basic elements to implement a server. It does not provide a
body parser out of the box.
#### Drash
In testing, [Drash](https://drash.land/drash/) had an in-memory body parser
which could handle file uploads on hosted services like Deno Deploy.
_Reading Data_
`Request#bodyParam` reads body parameters. For uploaded files, the `content`
property is a `Uint8Array`:
```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 * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
class ParseResource extends Drash.Resource {
public paths = ["/"];
public POST(request: Drash.Request, response: Drash.Response) {
// assume a form upload like <input type="file" id="upload" name="upload">
// highlight-next-line
const file = request.bodyParam<Drash.Types.BodyFile>("upload");
if (!file) throw new Error("File is required!");
// highlight-next-line
var wb = read(file.content);
return response.html( utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]));
}
}
```
_Writing Data_
Headers are manually set with `Response#headers.set` while the raw body is set
with `Response#send`:
```ts
// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
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";
class WriteResource extends Drash.Resource {
public paths = ["/export"];
public GET(request: Drash.Request, response: Drash.Response): void {
// create some fixed workbook
const data = ["SheetJS".split(""), [5,4,3,3,7,9,5]];
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
// highlight-next-line
const file = write(wb, { bookType: "xlsx", type: "buffer"});
// set headers
response.headers.set("Content-Disposition", 'attachment; filename="SheetJSDrash.xlsx"');
// send data
// highlight-next-line
return response.send("application/vnd.ms-excel", file);
}
}
```
<details><summary><b>Complete Example</b> (click to show)</summary>
1) Save the following script to `SheetJSDrash.ts`:
```ts title="SheetJSDrash.ts"
/*! sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */
// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
import { read, utils, set_cptable } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
import * as cptable from 'https://cdn.sheetjs.com/xlsx-latest/package/dist/cpexcel.full.mjs';
set_cptable(cptable);
import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
class ParseResource extends Drash.Resource {
public paths = ["/"];
public POST(request: Drash.Request, response: Drash.Response) {
const file = request.bodyParam<Drash.Types.BodyFile>("file");
if (!file) throw new Error("File is required!");
var wb = read(file.content);
return response.html( utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]));
}
public GET(request: Drash.Request, response: Drash.Response): void {
return response.html(`\
<!DOCTYPE html>
<html>
<head>
<title>SheetJS Spreadsheet to HTML Conversion Service</title>
<meta charset="utf-8" />
</head>
<body>
<pre><h3><a href="//sheetjs.com/">SheetJS</a> Spreadsheet Conversion Service</h3>
<b>API</b>
Send a POST request to http://localhost:3000/ with the file in the "file" body parameter:
$ curl -X POST -F"file=@test.xlsx" http://localhost:3000/
The response will be an HTML TABLE generated from the first worksheet.
<b>Try it out!</b><form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
Use the file input element to select a file, then click "Submit"
<button type="submit">Submit</button>
</form>
</pre>
</body>
</html>`,
);
}
}
const server = new Drash.Server({
hostname: "",
port: 3000,
protocol: "http",
resources: [ ParseResource ],
});
server.run();
console.log(`Server running at ${server.address}.`);
```
2) Run the server:
```bash
deno run --allow-net SheetJSDrash.ts
```
3) Download the test file <https://sheetjs.com/pres.numbers>
4) Open http://localhost:3000/ in your browser.
Click "Choose File" and select `pres.numbers`. Then click "Submit"
The page should show the contents of the file as an HTML table.
</details>
## NodeJS
When processing small files, the work is best handled in the server response
handler function. This approach is used in the "Framework Demos" section.
When processing large files, the direct approach will freeze the server. NodeJS
provides "Worker Threads" for this exact use case.
provides ["Worker Threads"](#worker-threads) for this exact use case.
### Framework Demos
@ -289,9 +131,9 @@ app.listen(+process.env.PORT||3000);
1) Install dependencies:
```bash
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz express express-formidable
```
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz express express-formidable`}
</CodeBlock>
2) Start server (note: it will not print anything to console when running)
@ -308,7 +150,7 @@ curl -X POST -F upload=@pres.numbers http://localhost:3000/upload
The response should show the data in CSV rows.
4) Test GET requests by opening http://localhost:3000/download in your browser.
4) Test GET requests by opening `http://localhost:3000/download` in your browser.
It should prompt to download `SheetJSExpress.xlsx`
@ -362,13 +204,13 @@ export class SheetjsController {
1) Create a new project:
```bash
<CodeBlock language="bash">{`\
npx @nestjs/cli new -p npm sheetjs-nest
cd sheetjs-nest
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm i --save-dev @types/multer
mkdir -p upload
```
mkdir -p upload`}
</CodeBlock>
2) Create a new controller and a new module:
@ -417,7 +259,7 @@ curl -X POST -F upload=@pres.numbers http://localhost:3000/sheetjs/upload
The response should show the data in CSV rows.
4) Test GET requests by opening http://localhost:3000/sheetjs/download in your browser.
4) Test GET requests by opening `http://localhost:3000/sheetjs/download` in your browser.
It should prompt to download `SheetJSNest.xlsx`
@ -538,9 +380,9 @@ fastify.listen({port: process.env.PORT || 3000}, (err, addr) => { if(err) throw
1) Install dependencies:
```bash
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz fastify @fastify/multipart
```
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz fastify @fastify/multipart`}
</CodeBlock>
2) Start server
@ -557,7 +399,7 @@ curl -X POST -F upload=@pres.numbers http://localhost:3000/
The response should show the data in CSV rows.
4) Test GET requests by opening http://localhost:3000/ in your browser.
4) Test GET requests by opening `http://localhost:3000/` in your browser.
It should prompt to download `SheetJSFastify.xlsx`
@ -603,9 +445,9 @@ Versions: NodeJS 18.15.0 + ExpressJS 4.18.2 + Formidable 2.1.1
1) Install the dependencies:
```bash
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz express@4.18.2 formidable@2.1.1
```
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz express@4.18.2 formidable@2.1.1`}
</CodeBlock>
2) Create a worker script `worker.js` that listens for messages. When a message
is received, it will read the file from the filesystem, generate and pass back a
@ -697,3 +539,106 @@ curl -X POST -F upload=@pres.numbers http://localhost:7262/ -J -O
This will generate `SheetJSPool.xlsx`.
</details>
## Deno
:::caution
Many hosted services like Deno Deploy do not offer filesystem access.
This breaks web frameworks that use the filesystem in body parsing.
:::
Deno provides the basic elements to implement a server. It does not provide a
body parser out of the box.
#### Drash
In testing, [Drash](https://drash.land/drash/) had an in-memory body parser
which could handle file uploads on hosted services like Deno Deploy.
_Reading Data_
`Request#bodyParam` reads body parameters. For uploaded files, the `content`
property is a `Uint8Array`:
<CodeBlock language="ts">{`\
// @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts"
import { read, utils } from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
\n\
import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
\n\
class ParseResource extends Drash.Resource {
public paths = ["/"];
\n\
public POST(request: Drash.Request, response: Drash.Response) {
// assume a form upload like <input type="file" id="upload" name="upload">
// highlight-next-line
const file = request.bodyParam<Drash.Types.BodyFile>("upload");
if (!file) throw new Error("File is required!");
// highlight-next-line
var wb = read(file.content);
return response.html( utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]));
}
}`}
</CodeBlock>
_Writing Data_
Headers are manually set with `Response#headers.set` while the raw body is set
with `Response#send`:
<CodeBlock language="ts">{`\
// @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts"
import { utils, write } from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
\n\
import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
\n\
class WriteResource extends Drash.Resource {
public paths = ["/export"];
\n\
public GET(request: Drash.Request, response: Drash.Response): void {
// create some fixed workbook
const data = ["SheetJS".split(""), [5,4,3,3,7,9,5]];
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
// highlight-next-line
const file = write(wb, { bookType: "xlsx", type: "buffer"});
// set headers
response.headers.set("Content-Disposition", 'attachment; filename="SheetJSDrash.xlsx"');
// send data
// highlight-next-line
return response.send("application/vnd.ms-excel", file);
}
}`}
</CodeBlock>
<details><summary><b>Complete Example</b> (click to show)</summary>
1) Download [`SheetJSDrash.ts`](pathname:///server/SheetJSDrash.ts):
```bash
curl -LO https://docs.sheetjs.com/server/SheetJSDrash.ts
```
2) Run the server:
```bash
deno run --allow-net SheetJSDrash.ts
```
3) Download the test file <https://sheetjs.com/pres.numbers>
4) Open `http://localhost:7262/` in your browser.
Click "Choose File" and select `pres.numbers`. Then click "Submit"
The page should show the contents of the file as an HTML table.
5) Open `http://localhost:7262/export` in your browser.
The page should attempt to download `SheetJSDrash.xlsx` . Open the new file.
</details>

@ -156,7 +156,7 @@ curl -L -o _data/pres.numbers https://sheetjs.com/pres.numbers
deno task lume --serve
```
To verify it works, access http://localhost:3000 from your web browser. Open
To verify it works, access `http://localhost:3000` from your web browser. Open
`_data/pres.numbers` and add a new row to the bottom of the sheet. The page will
refresh and show the new contents.
@ -179,6 +179,6 @@ This will create a static site in the `_site` folder, which can be served with:
npx http-server _site
```
Accessing the page http://localhost:8080 will show the page contents.
Accessing the page `http://localhost:8080` will show the page contents.
This site is self-contained and ready for deployment!

@ -186,7 +186,7 @@ curl -L -o data/pres.xlsx https://sheetjs.com/pres.xlsx
npm run dev
```
Open a browser window to the displayed URL (typically http://localhost:5173 )
Open a browser window to the displayed URL (typically `http://localhost:5173` )
5) Replace the component `src/components/HelloWorld.vue` with:
@ -272,7 +272,7 @@ npm run build
npx http-server dist/
```
The terminal will display a url like http://127.0.0.1:8080. Access that page
The terminal will display a URL ( `http://127.0.0.1:8080` ). Access that page
with a web browser.
11) To confirm that only the raw data is present in the page, view the page

@ -244,16 +244,16 @@ npx next@13.1.1
Open a web browser and access:
- http://localhost:3000 landing page
- http://localhost:3000/getStaticProps shows data from the first sheet
- http://localhost:3000/getServerSideProps shows data from the first sheet
- http://localhost:3000/getStaticPaths shows a list (3 sheets)
- `http://localhost:3000` landing page
- `http://localhost:3000/getStaticProps` shows data from the first sheet
- `http://localhost:3000/getServerSideProps` shows data from the first sheet
- `http://localhost:3000/getStaticPaths` shows a list (3 sheets)
The individual worksheets are available at
- http://localhost:3000/sheets/0
- http://localhost:3000/sheets/1
- http://localhost:3000/sheets/2
- `http://localhost:3000/sheets/0`
- `http://localhost:3000/sheets/1`
- `http://localhost:3000/sheets/2`
6) Stop the server and run a production build:

@ -238,7 +238,7 @@ This will create a static site in the `dist` folder, which can be served with:
npx http-server dist
```
Accessing the page http://localhost:8080 will show the page contents. Verifying
Accessing the page `http://localhost:8080` will show the page contents. Verifying
the static nature is trivial: make another change in Excel and save. The page
will not change.
@ -398,7 +398,7 @@ npx nuxi typecheck
npx yarn run dev
```
Loading http://localhost:3000/pres should show some JSON data:
Loading `http://localhost:3000/pres` should show some JSON data:
```json
{
@ -462,6 +462,6 @@ This will create a static site in `.output/public`, which can be served with:
npx http-server .output/public
```
Accessing http://localhost:8080/pres will show the page contents. Verifying
Accessing `http://localhost:8080/pres` will show the page contents. Verifying
the static nature is trivial: make another change in Excel and save. The page
will not change.

@ -248,6 +248,6 @@ AstroJS will place the generated site in the `dist` subfolder.
npx http-server dist
```
Open a web browser and access the displayed URL (usually http://localhost:8080).
Open a web browser and access the displayed URL ( `http://localhost:8080` ).
View the page source and confirm that no JS was added to the page. It only
contains the content from the file in an HTML table.

@ -4,6 +4,9 @@ pagination_prev: demos/local/index
pagination_next: demos/extensions/index
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
Salesforce apps can use third-party libraries in "Lightning Web Components".
This demo assumes familiarity with Lightning Web Components. Salesforce has a
@ -118,7 +121,7 @@ The [standalone script](/docs/getting-started/installation/standalone) can be do
added as a static resource. Due to Salesforce naming restrictions, it will have
to be renamed to `sheetjs.js` when adding the static resource.
1) Download <https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js>
<p>1) Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/xlsx.full.min.js</a></p>
:::warning

@ -4,6 +4,12 @@ pagination_prev: demos/local/index
pagination_next: demos/extensions/index
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
The [AMD script](/docs/getting-started/installation/amd) can be uploaded to the
file cabinet and referenced in the `define` call in SuiteScripts.
This demo discusses the key SheetJS operations. Familiarity with SuiteScript 2
is assumed. The following sections of the SuiteScript documentation should be
perused before reading this demo:
@ -19,9 +25,8 @@ The library plays nice with each script type, including RESTlets and Suitelets.
## Installation
[This script](https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js)
plays nice with SuiteScript `define`. It should be downloaded and uploaded to
the File Cabinet.
<p><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>The
standalone script</a> should be downloaded and uploaded to the File Cabinet.</p>
After uploading, create a JSON configuration file (or add the alias to an
existing config file). The reference points to the file and omits the `.js`.

@ -157,7 +157,7 @@ curl -LO https://sheetjs.com/pres.numbers
curl -X POST -F "upload=@pres.numbers" http://localhost:7071/api/SheetJSAzure
```
To test downloads, access http://localhost:7071/api/SheetJSAzure and download
To test downloads, access `http://localhost:7071/api/SheetJSAzure` and download
the generated file. Confirm it is a valid file.
6) Deploy to Azure. Replace `NAME_OF_FUNCTION_APP` with the name:

@ -4,8 +4,10 @@ pagination_prev: demos/cloud/index
pagination_next: demos/bigdata/index
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
Photoshop, InDesign and other Adobe Creative Suite applications offer extension
support. Over the years there have been a few different JavaScript platforms:
@ -30,7 +32,7 @@ This demo was verified in the following deployments:
| Photoshop | ExtendScript | 2023-04-15 |
| InDesign | ExtendScript | 2023-04-15 |
| InDesign | CEP | 2023-04-30 |
| InDesign | UXP | 2023-04-15 |
| InDesign | UXP | 2023-05-02 |
:::
@ -74,8 +76,11 @@ author (`activeDocument.info.author`) will be changed accordingly.
0) Download the [test workbook](pathname:///files/SheetJS.xlsb).
1) Download the following scripts:
- [`xlsx.extendscript.js`](https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.extendscript.js)
- [`parse.jsx`](pathname:///extendscript/parse.jsx)
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.extendscript.js`}><code>xlsx.extendscript.js</code></a></li>
<li><a href={`https://docs.sheetjs.com/extendscript/parse.jsx`}><code>parse.jsx</code></a></li>
</ul>
and place in the scripts directory.
@ -104,8 +109,12 @@ the Layers window is "Title") will be set to the name of the first worksheet.
[InDesign template](pathname:///extendscript/Template.indd)
1) Download the following scripts:
- [`xlsx.extendscript.js`](https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.extendscript.js)
- [`esidparse.jsx`](pathname:///extendscript/esidparse.jsx)
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.extendscript.js`}><code>xlsx.extendscript.js</code></a></li>
<li><a href={`https://docs.sheetjs.com/extendscript/esidparse.jsx`}><code>esidparse.jsx</code></a></li>
</ul>
Move to the scripts directory. To find the directory, activate Scripts panel
(Windows > Utilities > Scripts), click `☰`, and select "Reveal in Explorer".
@ -154,8 +163,11 @@ will be "Author" and cell `B1` will be the active Photoshop document Author.
The PS author is available as `activeDocument.info.author`.
1) Download the following scripts:
- [`xlsx.extendscript.js`](https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.extendscript.js)
- [`write.jsx`](pathname:///extendscript/write.jsx)
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.extendscript.js`}><code>xlsx.extendscript.js</code></a></li>
<li><a href={`https://docs.sheetjs.com/extendscript/write.jsx`}><code>write.jsx</code></a></li>
</ul>
and place in the scripts directory.
@ -182,8 +194,11 @@ object will be scanned and a new worksheet will be created.
0) Download the [InDesign document](pathname:///extendscript/Filled.indd)
1) Download the following scripts:
- [`xlsx.extendscript.js`](https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.extendscript.js)
- [`esidwrite.jsx`](pathname:///extendscript/esidwrite.jsx)
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.extendscript.js`}><code>xlsx.extendscript.js</code></a></li>
<li><a href={`https://docs.sheetjs.com/extendscript/esidwrite.jsx`}><code>esidwrite.jsx</code></a></li>
</ul>
Move to the scripts directory. To find the directory, activate Scripts panel
(Windows > Utilities > Scripts), click `☰`, and select "Reveal in Explorer".
@ -310,17 +325,21 @@ If prompted, give administrator privileges.
## UXP
UXP uses bundled scripts with `.psjs` (PS) or `.idjs` (InDesign) extension. The
official samples use `webpack` to build bundles.
UXP uses scripts with `.psjs` (PS) or `.idjs` (InDesign) file extensions.
[The "Frameworks" instructions](/docs/getting-started/installation/frameworks)
describe installation steps for traditional `webpack` projects.
[The "Standalone" scripts](/docs/getting-started/installation/frameworks) can
be loaded directly in UXP scripts with `require`:
```js
// assuming xlsx.full.min.js is in the same folder as the idjs / psjs script
const XLSX = require("./xlsx.full.min.js");
```
Filesystem access is provided by the UXP storage module:
```js
const storage = require("uxp").storage;
const ufs = storage.localFileSystem;
const UXP = require("uxp");
const storage = UXP.storage, ufs = storage.localFileSystem;
```
### Reading Files
@ -337,6 +356,38 @@ const ab = await file.read({ format: storage.formats.binary });
const wb = XLSX.read(ab);
```
<details open><summary><b>Complete Example</b> (click to hide)</summary>
<Tabs groupId="ccapp">
<TabItem value="indesign" label="InDesign">
0) Open the "Scripts Panel" folder.
To find this folder: Open the Scripts panel in InDesign (Window > Utilities >
Scripts). In the Scripts panel, right-click "User" and select "Reveal". This
will open a Finder (macOS) or Explorer (Windows) window. Open "Scripts Panel"
1) Download the following scripts:
<ul>
<li><a href="pathname:///extendscript/parse.idjs"><code>parse.idjs</code></a></li>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}><code>xlsx.full.min.js</code></a></li>
</ul>
Move them to the Scripts Panel folder.
2) Download and open [`Template.idml`](pathname:///extendscript/Template.idml)
3) Download <https://sheetjs.com/pres.xlsx>
4) In the Scripts Panel, double-click "parse". Select the downloaded `pres.xlsx`
in the file picker.
</TabItem>
</Tabs>
</details>
### Writing Files
The `getFileForSaving` method resolves to a `File` object. The workbook should
@ -351,3 +402,34 @@ const file = await ufs.getFileForSaving("SheetJSUXP.xlsx");
/* write data */
await file.write(buf, { data: storage.formats.binary });
```
<details open><summary><b>Complete Example</b> (click to hide)</summary>
<Tabs groupId="ccapp">
<TabItem value="indesign" label="InDesign">
0) Open the "Scripts Panel" folder.
To find this folder: Open the Scripts panel in InDesign (Window > Utilities >
Scripts). In the Scripts panel, right-click "User" and select "Reveal". This
will open a Finder (macOS) or Explorer (Windows) window. Open "Scripts Panel"
1) Download the following scripts:
<ul>
<li><a href="pathname:///extendscript/write.idjs"><code>write.idjs</code></a></li>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}><code>xlsx.full.min.js</code></a></li>
</ul>
Move them to the Scripts Panel folder.
2) Download and open [`Filled.idml`](pathname:///extendscript/Filled.idml)
3) In the Scripts Panel, double-click "Write". Click "Save" in the dialog.
4) When the process finishes, open `SheetJSUXP.xlsx` and verify the contents.
</TabItem>
</Tabs>
</details>

@ -5,6 +5,8 @@ pagination_next: demos/bigdata/index
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
Open Scripting Architecture (OSA), a built-in feature in macOS introduced in
1993, enables users to communicate with applications with a standardized
@ -23,9 +25,6 @@ This demo was last tested on 2023 April 18 in macOS Monterey.
## Integration details
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs groupId="osa">
<TabItem value="js" label="JavaScript">

@ -6,6 +6,9 @@ sidebar_custom_props:
summary: Dense Mode + Incremental CSV / HTML / JSON Export
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
For maximal compatibility, the library reads entire files at once and generates
files at once. Browsers and other JS engines enforce tight memory limits. In
these cases, the library offers strategies to optimize for memory or space by
@ -154,21 +157,21 @@ is an XLS file over 50 MB
`https://raw.githubusercontent.com/SheetJS/libreoffice_test-files/master/calc/xlsx-import/perf/8-by-300000-cells.xlsx`
is an XLSX file with 300000 rows (approximately 20 MB)
```jsx live
<CodeBlock language="jsx" live>{`\
function SheetJSFetchCSVStreamWorker() {
const [__html, setHTML] = React.useState("");
const [state, setState] = React.useState("");
const [cnt, setCnt] = React.useState(0);
const [url, setUrl] = React.useState("https://oss.sheetjs.com/test_files/large_strings.xlsx");
\n\
return ( <>
<b>URL: </b><input type="text" value={url} onChange={(e) => setUrl(e.target.value)} size="80"/>
<button onClick={() => {
/* this mantra embeds the worker source in the function */
const worker = new Worker(URL.createObjectURL(new Blob([`\
const worker = new Worker(URL.createObjectURL(new Blob([\`\\
/* load standalone script from CDN */
importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js");
importScripts("https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js");
\n\
function sheet_to_csv_cb(ws, cb, opts, batch = 1000) {
XLSX.stream.set_readable(() => ({
__done: false,
@ -180,7 +183,7 @@ function sheet_to_csv_cb(ws, cb, opts, batch = 1000) {
}));
return XLSX.stream.to_csv(ws, opts);
}
\n\
/* this callback will run once the main context sends a message */
self.addEventListener('message', async(e) => {
try {
@ -188,7 +191,7 @@ self.addEventListener('message', async(e) => {
/* Fetch file */
const res = await fetch(e.data.url);
const ab = await res.arrayBuffer();
\n\
/* Parse file */
let len = ab.byteLength;
if(len < 1024) len += " bytes"; else { len /= 1024;
@ -197,7 +200,7 @@ self.addEventListener('message', async(e) => {
postMessage({state: "parsing " + len});
const wb = XLSX.read(ab, {dense: true});
const ws = wb.Sheets[wb.SheetNames[0]];
\n\
/* Generate CSV rows */
postMessage({state: "csv"});
const strm = sheet_to_csv_cb(ws, (csv) => {
@ -210,7 +213,7 @@ self.addEventListener('message', async(e) => {
postMessage({error: String(e.message || e) });
}
}, false);
`])));
\`])));
/* when the worker sends back data, add it to the DOM */
worker.onmessage = function(e) {
if(e.data.error) return setHTML(e.data.error);
@ -225,8 +228,8 @@ self.addEventListener('message', async(e) => {
<pre>State: <b>{state}</b><br/>Number of rows: <b>{cnt}</b></pre>
<pre dangerouslySetInnerHTML={{ __html }}/>
</> );
}
```
}`}
</CodeBlock>
</details>
@ -261,10 +264,10 @@ examples using the File System Access API.
Typically, the file and stream processing occurs in the Web Worker. CSV rows
can be sent back to the main thread in the callback:
```js title="worker.js"
<CodeBlock language="js" title="worker.js">{`\
/* load standalone script from CDN */
importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js");
importScripts("https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js");
\n\
function sheet_to_csv_cb(ws, cb, opts, batch = 1000) {
XLSX.stream.set_readable(() => ({
__done: false,
@ -276,7 +279,7 @@ function sheet_to_csv_cb(ws, cb, opts, batch = 1000) {
}));
return XLSX.stream.to_csv(ws, opts);
}
\n\
/* this callback will run once the main context sends a message */
self.addEventListener('message', async(e) => {
try {
@ -284,12 +287,12 @@ self.addEventListener('message', async(e) => {
/* Fetch file */
const res = await fetch(e.data.url);
const ab = await res.arrayBuffer();
\n\
/* Parse file */
postMessage({state: "parsing"});
const wb = XLSX.read(ab, {dense: true});
const ws = wb.Sheets[wb.SheetNames[0]];
\n\
/* Generate CSV rows */
postMessage({state: "csv"});
const strm = sheet_to_csv_cb(ws, (csv) => {
@ -301,8 +304,8 @@ self.addEventListener('message', async(e) => {
/* Pass the error message back */
postMessage({error: String(e.message || e) });
}
}, false);
```
}, false);`}
</CodeBlock>
The main thread will receive messages with CSV rows for further processing:
@ -322,10 +325,10 @@ worker.onmessage = function(e) {
Deno does not support NodeJS streams in normal execution, so a wrapper is used.
This example fetches <https://sheetjs.com/pres.numbers> and prints CSV rows:
```ts title="sheet2csv.ts"
// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
import { stream, Sheet2CSVOpts, WorkSheet } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
<CodeBlock language="ts" title="sheet2csv.ts">{`\
// @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts"
import { stream, Sheet2CSVOpts, WorkSheet } from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
\n\
interface Resumable { resume:()=>void; };
/* Generate row strings from a worksheet */
function sheet_to_csv_cb(ws: WorkSheet, cb:(d:string|null)=>void, opts: Sheet2CSVOpts = {}, batch = 1000): Resumable {
@ -339,20 +342,20 @@ function sheet_to_csv_cb(ws: WorkSheet, cb:(d:string|null)=>void, opts: Sheet2CS
}));
return stream.to_csv(ws, opts) as Resumable;
}
\n\
/* Callback invoked on each row (string) and at the end (null) */
const csv_cb = (d:string|null) => {
if(d == null) return;
/* The strings include line endings, so raw write ops should be used */
Deno.stdout.write(new TextEncoder().encode(d));
};
\n\
/* Fetch https://sheetjs.com/pres.numbers, parse, and get first worksheet */
import { read } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
import { read } from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
const ab = await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer();
const wb = read(ab, { dense: true });
const ws = wb.Sheets[wb.SheetNames[0]];
\n\
/* Create and start CSV stream */
sheet_to_csv_cb(ws, csv_cb).resume();
```
sheet_to_csv_cb(ws, csv_cb).resume();`}
</CodeBlock>

@ -317,22 +317,22 @@ In the following example, the script:
- sends the object URL to the main browser context
- performs a download action in the main browser context
```jsx live
<CodeBlock language="jsx" live>{`\
function SheetJSWriteFileWorker() {
const [__html, setHTML] = React.useState("");
\n\
const data = [
{ "SheetJS": "வணக்கம்", "in": "สวัสดี", "Web": "你好", "Workers": "가지마" },
{ "SheetJS": 1, "in": 2, "Web": 3, "Workers": 4 },
];
\n\
return ( <>
<button onClick={() => { setHTML("");
/* this mantra embeds the worker source in the function */
const worker = new Worker(URL.createObjectURL(new Blob([`\
const worker = new Worker(URL.createObjectURL(new Blob([\`\\
/* load standalone script from CDN */
importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js");
importScripts("https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js");
\n\
/* this callback will run once the main context sends a message */
self.addEventListener('message', async(e) => {
try {
@ -340,13 +340,13 @@ self.addEventListener('message', async(e) => {
const ws = XLSX.utils.json_to_sheet(e.data.data);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Data");
\n\
/* Write XLSB data (Uint8Array) */
const u8 = XLSX.write(wb, { bookType: "xlsb", type: "buffer" });
\n\
/* Generate URL */
const url = URL.createObjectURL(new Blob([u8]));
\n\
/* Reply with result */
postMessage({ url });
} catch(e) {
@ -354,11 +354,11 @@ self.addEventListener('message', async(e) => {
postMessage({error: String(e.message || e).bold() });
}
}, false);
`])));
\`])));
/* when the worker sends back the data, create a download */
worker.onmessage = function(e) {
if(e.data.error) return setHTML(e.data.error);
\n\
/* this mantra is the standard HTML5 download attribute technique */
const a = document.createElement("a");
a.download = "SheetJSWriteFileWorker.xlsb";
@ -372,8 +372,8 @@ self.addEventListener('message', async(e) => {
}}><b>Click to Start</b></button>
<div dangerouslySetInnerHTML={{ __html }}/>
</> );
}
```
}`}
</CodeBlock>
</details>
@ -422,30 +422,30 @@ element is used to select a file, the script:
- sends the string to the main browser context
- adds the HTML to the page in the main browser context
```jsx live
<CodeBlock language="jsx" live>{`\
function SheetJSDragDropWorker() {
const [__html, setHTML] = React.useState("");
/* suppress default behavior for drag and drop */
function suppress(e) { e.stopPropagation(); e.preventDefault(); }
\n\
/* this worker is shared between drag-drop and file input element */
const worker = new Worker(URL.createObjectURL(new Blob([`\
const worker = new Worker(URL.createObjectURL(new Blob([\`\\
/* load standalone script from CDN */
importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js");
importScripts("https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js");
\n\
/* this callback will run once the main context sends a message */
self.addEventListener('message', (e) => {
try {
/* Read file data */
const ab = new FileReaderSync().readAsArrayBuffer(e.data.file);
\n\
/* Parse file */
const wb = XLSX.read(ab, {dense: true});
const ws = wb.Sheets[wb.SheetNames[0]];
\n\
/* Generate HTML */
const html = XLSX.utils.sheet_to_html(ws);
\n\
/* Reply with result */
postMessage({ html });
} catch(e) {
@ -453,7 +453,7 @@ self.addEventListener('message', (e) => {
postMessage({html: String(e.message || e).bold() });
}
}, false);
`])));
\`])));
/* when the worker sends back the HTML, add it to the DOM */
worker.onmessage = function(e) { setHTML(e.data.html); };
return ( <>
@ -469,8 +469,8 @@ self.addEventListener('message', (e) => {
}}/>
<div dangerouslySetInnerHTML={{ __html }}/>
</> );
}
```
}`}
</CodeBlock>
</details>
@ -558,7 +558,7 @@ is an XLS file over 50 MB. The generated CSV file is about 55 MB.
`https://raw.githubusercontent.com/SheetJS/libreoffice_test-files/master/calc/xlsx-import/perf/8-by-300000-cells.xlsx`
is an XLSX file with 300000 rows (approximately 20 MB) yielding a CSV of 10 MB.
```jsx live
<CodeBlock language="jsx" live>{`\
function SheetJSFetchCSVStreamFile() {
const [state, setState] = React.useState("");
const [__html, setHTML] = React.useState("");
@ -566,16 +566,16 @@ function SheetJSFetchCSVStreamFile() {
const [hz, setHz] = React.useState(0);
const [url, setUrl] = React.useState("https://oss.sheetjs.com/test_files/large_strings.xlsx");
const ref = React.useRef(null);
\n\
return ( <>
<b>URL: </b><input type="text" value={url} onChange={(e) => setUrl(e.target.value)} size="80"/><br/>
<b>Commit each row: </b><input type="checkbox" ref={ref}/><br/>
<button onClick={async() => {
/* this mantra embeds the worker source in the function */
const worker = new Worker(URL.createObjectURL(new Blob([`\
const worker = new Worker(URL.createObjectURL(new Blob([\`\\
/* load standalone script from CDN */
importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js");
importScripts("https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js");
\n\
function sheet_to_csv_cb(ws, cb, opts, batch = 1000) {
XLSX.stream.set_readable(() => ({
__done: false,
@ -591,7 +591,7 @@ function sheet_to_csv_cb(ws, cb, opts, batch = 1000) {
}));
return XLSX.stream.to_csv(ws, opts);
}
\n\
/* this callback will run once the main context sends a message */
self.addEventListener('message', async(e) => {
try {
@ -601,14 +601,14 @@ self.addEventListener('message', async(e) => {
const res = await fetch(e.data.url);
const ab = await res.arrayBuffer();
postMessage({time: "fetch", ts: Date.now() - t});
\n\
/* Parse file */
postMessage({state: "parsing"});
t = Date.now();
const wb = XLSX.read(ab, {dense: true});
const ws = wb.Sheets[wb.SheetNames[0]];
postMessage({time: "parse", ts: Date.now() - t});
\n\
/* Generate CSV rows */
postMessage({state: "begin"});
t = Date.now();
@ -631,11 +631,11 @@ self.addEventListener('message', async(e) => {
postMessage({error: String(e.message || e) });
}
}, false);
`])));
\`])));
/* when the worker sends back data, add it to the DOM */
const log = (s, t) => setHTML(h => h + `${s}: ${(t/1000).toFixed(3).padStart(8)} sec\n`);
const log = (s, t) => setHTML(h => h + \`\${s}: \${(t/1000).toFixed(3).padStart(8)} sec\\n\`);
worker.onmessage = function(e) {
if(e.data.error) return setState(`Processing Error: ${e.data.error}`);
if(e.data.error) return setState(\`Processing Error: \${e.data.error}\`);
else if(e.data.state) {
setState(e.data.state);
if(e.data.c) setCnt(e.data.c);
@ -650,15 +650,15 @@ self.addEventListener('message', async(e) => {
suggestedName: "SheetJSStream.csv",
types: [ { description: 'csv', accept: { 'text/csv': ['.csv'] } } ]
});
\n\
/* post a message to the worker with the URL to fetch */
worker.postMessage({url, wFile, each: !!ref.current.checked});
} catch(e) { setState(`Selection Error: ${e && e.message || e}`); }
} catch(e) { setState(\`Selection Error: \${e && e.message || e}\`); }
}}><b>Click to Start</b></button>
<pre>State: <b>{state}</b><br/>Count: <b>{cnt}</b> <b>({hz|0} Hz)</b></pre>
<pre dangerouslySetInnerHTML={{__html}}/>
</> );
}
```
}`}
</CodeBlock>
</details>

@ -370,7 +370,7 @@ Deno must be run with the `--allow-net` flag to enable network requests:
deno run --allow-net test-server.ts
```
To test, submit a POST request to http://localhost:7262 including a file:
To test, submit a POST request to `http://localhost:7262` with an attachment:
```bash
curl -X POST -F "file=@test.xlsx" http://localhost:7262/

@ -7,6 +7,7 @@ sidebar_position: 5
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
## Writing Workbooks
@ -395,15 +396,15 @@ evt.respondWith(new Response(buf, {
Save the following script to `deno.ts` and run with `deno run -A deno.ts`. Open
a web browser and access `http://localhost:7262/` to download the workbook.
```ts title="deno.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';
<CodeBlock language="ts" title="deno.ts">{`\
// @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts"
import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
\n\
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet([
["a","b","c"], [1,2,3]
]), "Sheet1");
\n\
async function doNotAwaitThis(conn: Deno.Conn) {
for await (const e of Deno.serveHttp(conn)) e.respondWith(new Response(
XLSX.write(wb, {type:"buffer", bookType:"xlsx"}),
@ -416,12 +417,12 @@ async function doNotAwaitThis(conn: Deno.Conn) {
}
));
}
\n\
/* standard Deno web server */
const server = Deno.listen({ port: 7262 });
console.log(`HTTP webserver running. Access it at: http://localhost:7262/`);
for await (const conn of server) doNotAwaitThis(conn);
```
console.log(\`HTTP webserver running. Access it at: http://localhost:7262/\`);
for await (const conn of server) doNotAwaitThis(conn);`}
</CodeBlock>
</details>
@ -445,9 +446,9 @@ return new Response(buf, {
<details><summary><b>Complete Example</b> (click to show)</summary>
Download [`xlsx.mjs`](https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs).
Save the following script to `bun.js` and run with `bun bun.js`. Open a web
browser and access `http://localhost:7262/` to download the exported workbook.
<p>Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs`}><code>xlsx.mjs</code></a>.
Save the following script to <code>bun.js</code> and run with <code>bun bun.js</code>. Open a web
browser and access <code>http://localhost:7262/</code> to download the exported workbook.</p>
```js title="bun.js"
import * as XLSX from "./xlsx.mjs";
@ -695,33 +696,33 @@ Combining with `fetch`, constructing a site from a workbook is straightforward:
This example assigns the `innerHTML` of a DIV element:
```html
<CodeBlock language="html">{`\
<body>
<style>TABLE { border-collapse: collapse; } TD { border: 1px solid; }</style>
<div id="tavolo"></div>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
<script type="text/javascript">
(async() => {
/* fetch and parse workbook -- see the fetch example for details */
const workbook = XLSX.read(await (await fetch("sheetjs.xlsx")).arrayBuffer());
\n\
let output = [];
/* loop through the worksheet names in order */
workbook.SheetNames.forEach(name => {
\n\
/* generate HTML from the corresponding worksheets */
const worksheet = workbook.Sheets[name];
const html = XLSX.utils.sheet_to_html(worksheet);
\n\
/* add a header with the title name followed by the table */
output.push(`<H3>${name}</H3>${html}`);
output.push(\`<H3>\${name}</H3>\${html}\`);
});
/* write to the DOM at the end */
tavolo.innerHTML = output.join("\n");
tavolo.innerHTML = output.join("\\n");
})();
</script>
</body>
```
</body>`}
</CodeBlock>
</TabItem>
<TabItem value="react" label="React">

@ -193,6 +193,7 @@ const config = {
{ from: '/docs/demos/excelapi', to: '/docs/demos/extensions/excelapi/' },
{ from: '/docs/demos/chromium', to: '/docs/demos/extensions/chromium/' },
/* cloudata */
{ from: '/docs/demos/cloudata', to: '/docs/demos/cloud/' },
{ from: '/docs/demos/cloudata/gsheet', to: '/docs/demos/cloud/gsheet/' },
{ from: '/docs/demos/cloudata/airtable', to: '/docs/demos/cloud/airtable/' },
/* hosting */
@ -214,6 +215,14 @@ const config = {
{ from: '/docs/demos/ml', to: '/docs/demos/bigdata/ml/' },
{ from: '/docs/demos/worker', to: '/docs/demos/bigdata/worker/' },
{ from: '/docs/demos/stream', to: '/docs/demos/bigdata/stream/' },
/* installation */
{ from: '/docs/installation/standalone', to: '/docs/getting-started/installation/standalone/' },
{ from: '/docs/installation/frameworks', to: '/docs/getting-started/installation/frameworks/' },
{ from: '/docs/installation/nodejs', to: '/docs/getting-started/installation/nodejs/' },
{ from: '/docs/installation/amd', to: '/docs/getting-started/installation/amd/' },
{ from: '/docs/installation/extendscript', to: '/docs/getting-started/installation/extendscript/' },
{ from: '/docs/installation/deno', to: '/docs/getting-started/installation/deno/' },
{ from: '/docs/installation/bun', to: '/docs/getting-started/installation/bun/' },
]
}]
]

@ -11,7 +11,7 @@ function main_parse() {
var data = XLSX.utils.sheet_to_json(workbook.Sheets[wsname], { header: 1, raw: false });
/* Set title */
app.activeDocument.textFrames.itemByName("Title").texts[0].contents = wsname;
app.activeDocument.textFrames.itemByName("Title").texts.item(0).contents = wsname;
/* Set table */
var tabeller = app.activeDocument.textFrames.itemByName("Table Frame");

@ -0,0 +1,31 @@
const UXP = require("uxp");
const XLSX = require("./xlsx.full.min.js");
const storage = UXP.storage, ufs = storage.localFileSystem;
/* show file picker (single file, no folders) */
const file = await ufs.getFileForOpening({ types: ["xlsx", "xls", "xlsb"] });
/* read data into an ArrayBuffer */
const ab = await file.read({ format: storage.formats.binary });
/* parse with SheetJS */
const wb = XLSX.read(ab), wsname = wb.SheetNames[0];
const data = XLSX.utils.sheet_to_json(wb.Sheets[wsname], { header: 1, raw: false });
/* Set title */
app.activeDocument.textFrames.itemByName("Title").texts.item(0).contents = wsname;
/* Set table */
var tabeller = app.activeDocument.textFrames.itemByName("Table Frame");
var columns = data[0].length;
for(var R = 0; R < data.length; ++R) columns = Math.max(columns, data[R].length);
var table = tabeller.tables.add({
headerRowCount: 1,
bodyRowCount: data.length - 1,
columnCount: columns
});
for(R = 0; R < data.length; ++R) {
if(data[R] == null) continue;
for(var C = 0; C < data[R].length; ++C) {
if(data[R][C] == null) continue;
table.rows.item(R).cells.item(C).contents = data[R][C];
}
}

@ -0,0 +1,47 @@
const UXP = require("uxp");
const XLSX = require("./xlsx.full.min.js");
const storage = UXP.storage, ufs = storage.localFileSystem;
function workbook_add_table(wb, table) {
/* Collect Data */
var data = [];
var cnt = table.rows.count();
for(var R = 0; R < cnt; ++R) {
var row = table.rows.item(R);
data[R] = [];
var ccnt = row.cells.count();
for(var C = 0; C < ccnt; ++C) {
var value = row.cells.item(C).contents;
data[R][C] = value;
}
}
if(data.length == 0) return;
/* Create Worksheet */
var ws = XLSX.utils.aoa_to_sheet(data);
/* Create new Workbook and add worksheet */
XLSX.utils.book_append_sheet(wb, ws);
}
/* Create new Workbook */
var wb = XLSX.utils.book_new();
/* Find all tables and add them to workbook */
var tfcnt = app.activeDocument.textFrames.count();
for(var i = 0; i < tfcnt; ++i) {
var tf = app.activeDocument.textFrames.item(i);
var tcnt = tf.tables.count();
if(tcnt == 0) continue;
for(var j = 0; j < tcnt; ++j) workbook_add_table(wb, tf.tables.item(j));
}
/* generate XLSX with type: "buffer" */
const buf = XLSX.write(wb, { type: "buffer", bookType: "xlsx" });
/* show file picker */
const file = await ufs.getFileForSaving("SheetJSUXP.xlsx");
/* write data */
await file.write(buf, { data: storage.formats.binary });

@ -0,0 +1,78 @@
/*! sheetjs (C) SheetJS -- https://sheetjs.com */
// @deno-types="https://cdn.sheetjs.com/xlsx-latest/package/types/index.d.ts"
import { read, utils, write, set_cptable } from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
import * as cptable from 'https://cdn.sheetjs.com/xlsx-latest/package/dist/cpexcel.full.mjs';
set_cptable(cptable);
import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
class ParseResource extends Drash.Resource {
public paths = ["/"];
public POST(request: Drash.Request, response: Drash.Response) {
const file = request.bodyParam<Drash.Types.BodyFile>("file");
if (!file) throw new Error("File is required!");
var wb = read(file.content);
return response.html( utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]));
}
public GET(request: Drash.Request, response: Drash.Response): void {
return response.html(`\
<!DOCTYPE html>
<html>
<head>
<title>SheetJS Spreadsheet to HTML Conversion Service</title>
<meta charset="utf-8" />
</head>
<body>
<pre><h3><a href="//sheetjs.com/">SheetJS</a> Spreadsheet Conversion Service</h3>
<b>API</b>
Send a POST request to http://localhost:7262/ with the file in the "file" body parameter:
$ curl -X POST -F"file=@test.xlsx" http://localhost:7262/
The response will be an HTML TABLE generated from the first worksheet.
<b>Try it out!</b><form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
Use the file input element to select a file, then click "Submit"
<button type="submit">Submit</button>
</form>
</pre>
</body>
</html>`,
);
}
}
class WriteResource extends Drash.Resource {
public paths = ["/export"];
public GET(request: Drash.Request, response: Drash.Response): void {
// create some fixed workbook
const data = ["SheetJS".split(""), [5,4,3,3,7,9,5]];
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 = write(wb, { bookType: "xlsx", type: "buffer"});
// set headers
response.headers.set("Content-Disposition", 'attachment; filename="SheetJSDrash.xlsx"');
// send data
return response.send("application/vnd.ms-excel", file);
}
}
const server = new Drash.Server({
hostname: "",
port: 7262,
protocol: "http",
resources: [ ParseResource, WriteResource ],
});
server.run();
console.log(`Server running at ${server.address}.`);