v3manifest

This commit is contained in:
SheetJS 2023-06-06 06:01:12 -04:00
parent 7fbd794137
commit 709553e22e
8 changed files with 518 additions and 38 deletions

@ -130,6 +130,37 @@ _Writing Files_
XLSX.writeFile(wb, "SheetJS.xlsx");
```
<details><summary><b>Implementation Details</b> (click to show)</summary>
Under the hood, it creates a special URL and clicks a link. The library method
includes a few workarounds for legacy browsers
`XLSX.writeFile(wb, "SheetJS.xlsx");` is roughly equivalent to:
```js
/* write data -- note that writeFile infers bookType from filename */
const u8 = XLSX.write(wb, { bookType: "xlsx", type: "buffer" });
/* create Blob */
const blob = new Blob([u8]);
/* create object URL */
const url = URL.createObjectURL(new Blob([u8]));
/* create `A` DOM element */
const a = document.createElement("a");
/* set export file name */
a.download = "SheetJS.xlsx";
/* wire up the object URL to the DOM element */
a.href = url;
/* add to the page */
document.body.appendChild(a);
/* click the link */
a.click();
/* remove the element from the page */
document.body.removeChild(a);
```
</details>
:::caution Web Workers
`XLSX.writeFile` requires DOM access and will not work in a Web Worker!
@ -378,6 +409,51 @@ var wb = readFile("sheetjs.numbers");
writeFile(wb, "sheetjs.xlsx");
```
<details><summary><b>Implementation Details</b> (click to show)</summary>
**`XLSX.readFile(filepath)`** is equivalent to:
_CommonJS_
```js
var fs = require("fs");
var buf = fs.readFileSync(filepath);
var wb = XLSX.read(buf);
```
_ECMAScript Modules_
```js
import { read } from "xlsx";
import { readFileSync } from "fs";
var buf = readFileSync(filepath);
var wb = read(buf);
```
**`XLSX.writeFile(wb, filepath)`** is equivalent to:
_CommonJS_
```js
var fs = require("fs"), path = require("path");
var buf = XLSX.write(wb, { bookType: path.extname(filepath).slice(1), type: "buffer" });
fs.writeFileSync(filepath, buf);
```
_ECMAScript Modules_
```js
import { write } from "xlsx";
import { writeFileSync } from "fs";
import { extname } from "path";
var buf = write(wb, { bookType: extname(filepath).slice(1), type: "buffer" });
writeFileSync(filepath, buf);
```
</details>
### ExtendScript
In Photoshop and other Adobe apps, `readFile` and `writeFile` use the `File`
@ -390,6 +466,19 @@ var wb = XLSX.readFile("sheetjs.xlsx");
XLSX.writeFile(wb, "sheetjs.csv");
```
The [ExtendScript demo](/docs/demos/extensions/extendscript) covers the "Common
Extensibility Platform" (CEP) and "Unified Extensibility Platform" (UXP) details.
### Chrome Extensions
In Manifest v2 Chrome extensions, `writeFile` calls `chrome.downloads.download`.
This approach uses `URL.createObjectURL`, an API that is not supported in a
Manifest v3 Background Service Worker. For small exports, raw Base64 URLs can be
generated and downloaded.
The [Chromium demo](/docs/demos/extensions/chromium) covers the details.
### Deno
`readFile` uses `Deno.readFileSync` and `writeFile` uses `Deno.writeFileSync`:
@ -410,6 +499,34 @@ Any Deno script using `XLSX.writeFile` requires the `--allow-write` entitlement.
:::
<details><summary><b>Implementation Details</b> (click to show)</summary>
**`XLSX.readFile(filepath)`** is equivalent to:
_ECMAScript Modules_
<CodeBlock language="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 u8: Uint8Array = Deno.readFileSync(filepath);
const wb: XLSX.WorkBook = XLSX.read(u8);`}
</CodeBlock>
**`XLSX.writeFile(wb, filepath)`** is equivalent to:
_ECMAScript Modules_
<CodeBlock language="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 u8 = XLSX.write(wb, { bookType: filepath.slice(filepath.lastIndexOf(".")+1), type: "buffer" });
Deno.writeFileSync(filepath, u8);`}
</CodeBlock>
</details>
### Bun
Bun requires the `fs` module:
@ -423,10 +540,11 @@ var wb = readFile("sheetjs.numbers");
writeFile(wb, "sheetjs.xlsx");
```
The implementation is identical to [NodeJS ECMAScript Modules](#nodejs).
### Apps
Desktop and mobile apps have their own specific APIs covered in separate demos:
- [Electron and other desktop apps](/docs/demos/desktop)
- [React Native and other mobile apps](/docs/demos/mobile)

@ -0,0 +1,112 @@
---
title: Deno Deploy
pagination_prev: demos/local/index
pagination_next: demos/extensions/index
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
Deno Deploy offers "Serverless Functions" powered by Deno.
The [Deno installation](/docs/getting-started/installation/deno) instructions
apply to Deno Deploy scripts.
:::warning
Deno Deploy does not offer any sort of temporary file access in functions.
This breaks web frameworks that use the filesystem in body parsing.
:::
:::caution
When the demo was last tested, Deno Deploy required a GitHub account.
:::
## Supported Frameworks
When the demo was last tested, the `drash` server framework used an in-memory
approach for parsing POST request bodies.
### Parsing Data
When files are submitted via HTTP POST, the `bodyParam` method can fetch data.
The `content` property of the returned object can be parsed with `XLSX.read`.
The following example assumes the file is submitted at field name `file`:
<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';
import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
\n\
class SheetJSResource extends Drash.Resource {
public paths = ["/"];
\n\
public POST(request: Drash.Request, response: Drash.Response) {
// highlight-start
/* get data from body */
const file = request.bodyParam<Drash.Types.BodyFile>("file");
/* parse */
var wb = read(file.content, {type: "buffer", dense: true});
// highlight-end
/* generate HTML from first worksheet */
return response.html(utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]));
}
}`}
</CodeBlock>
## Demo
:::note
This demo was last tested on 2023 June 05. The service <https://s2c.sheetjs.com>
was implemented using this exact sequence.
:::
1) Register and Sign in.
2) Click "New Project" to create a new Project. In the next screen, look for the
"Hello World" sample and click the corresponding "Fork" button.
3) Download [`s2c.ts`](pathname:///deno/s2c.ts). Open with a text editor and
copy the contents into the playground editor (left pane).
4) Click "Save and Deploy".
### Testing
5) Download the test file <https://sheetjs.com/pres.xlsx>
6) In the browser window, click "Choose File" and select the downloaded file.
Click "Submit" and the page will show the contents in a HTML TABLE.
7) Click the "Fullscreen" icon in the top-right corner of the page window.
8) Open a terminal window and download <https://sheetjs.com/pres.numbers>:
```bash
curl -LO https://sheetjs.com/pres.numbers
```
9) Copy the first `curl` line from the page and run in the terminal. For
example, if the deployment is `clean-badger-69`, the command would be
```bash
curl -X POST -F"file=@pres.numbers" https://clean-badger-69.deno.dev/
```
The output will be an HTML table
10) Copy the second `curl` line from the page and run in the terminal. For
example, if the deployment is `clean-badger-69`, the command would be
```bash
curl -X POST -F"file=@pres.numbers" -F"type=csv" https://clean-badger-69.deno.dev/
```
The output will be CSV.

@ -15,9 +15,11 @@ support. Over the years there have been a few different JavaScript platforms:
- "ExtendScript": This uses an old JavaScript dialect but is supported in older
versions of Creative Suite and Creative Cloud.
- "CEP": This was recommended in CS6 but eventually deprecated.
- "Common Extensibility Platform" (CEP): This was introduced in Creative Suite.
App automation uses ExtendScript, but integration logic uses modern JS.
- "UXP": This is the current Adobe recommendation for new CC extensions.
- "Unified Extensibility Platform" (UXP): This is the current recommendation for
new Adobe CC extensions in supported apps (Photoshop 2021+ and InDesign 2022+)
This demo intends to cover parts relevant to SheetJS. General setup as well as
general Adobe considerations are not covered here. A basic familiarity with

@ -4,39 +4,88 @@ pagination_prev: demos/cloud/index
pagination_next: demos/bigdata/index
---
:::warning
:::note
This demo was written using the Manifest V2 extension platform. Chrome Web
Store will not accept new V2 extensions, but these can be sideloaded using the
"Load unpacked" extension option.
This demo showcases Manifest V2 and Manifest V3 extensions. Chrome Web Store
will not accept new V2 extensions, but these can be sideloaded using the
"Load unpacked" extension option in Developer mode.
:::
This library is compatible with Chrome and Chromium extensions and should just
work out of the box. Specific API support is listed in the Chrome extensions
API documentation.
The [Standalone scripts](/docs/getting-started/installation/standalone) can be
integrated in a Chromium extension.
[Right-Click and download the final CRX](pathname:///chromium/SheetJSDemo.crx)
This demo includes examples for exporting bookmarks from a popup and scraping
tables with a content script and a background script.
:::caution
[The demo](#demo) includes unpacked extensions for Manifest V2 and Manifest V3.
New releases of Chrome / Chromium will block with `CRX_REQUIRED_PROOF_MISSING`.
:::note
To try the extension:
1) Right-click and select "Save Link As ..." to save the CRX file
2) Open `chrome://extensions/` in the browser and enable Developer mode
3) Click and drag the downloaded CRX file into the Extensions page to install.
This demo was last tested on 2023 June 06 against Chrome 114
:::
## Loading SheetJS Scripts
SheetJS libraries should be bundled in the extension. For path purposes, it is
strongly recommended to place `xlsx.full.min.js` in the root folder.
#### Popup Pages
In Manifest V2 and Manifest V3 extensions, popup pages can load the standalone
script using a normal `<script>` tag:
```html
<script type="text/javascript" src="xlsx.full.min.js"></script>
```
#### Content Scripts
In Manifest V2 and Manifest V3 extensions, the standalone script can be loaded
through the `content_scripts` field:
```js
/* in manifest.json v2 or v3 */
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["xlsx.full.min.js", "content.js"],
"run_at": "document_end"
}],
```
The `XLSX` global will be visible to other content scripts.
#### Background Scripts
In Manifest V2 extensions, if the standalone script is added as a background
script, other background scripts will be able to access the `XLSX` global!
```js
/* in manifest.json v2 only! */
"background": {
"scripts": ["xlsx.full.min.js", "table.js"],
"persistent": false
},
```
In Manifest V3 extensions, background service workers can load the standalone
script through `importScripts`:
```js
/* assuming background script is in the same folder as xlsx.full.min.js */
importScripts("./xlsx.full.min.js");
// now XLSX will be available
```
## Relevant Operations
The official documentation covers details including required permissions.
### Generating Downloads
#### Manifest V2
The `writeFile` function works in a Chrome or Chromium extension:
```js
@ -46,6 +95,21 @@ XLSX.writeFile(wb, "export.xlsx");
Under the hood, it uses the `chrome.downloads` API. `"downloads"` permission
should be set in `manifest.json`.
#### Manifest V3
In a background service worker, `URL.createObjectURL` is unavailable. Instead,
`XLSX.write` can generate a Base64 string for a synthetic URL:
```js
/* generate Base64 string */
const b64 = XLSX.write(wb, {bookType: "xlsx", type: "base64"});
chrome.downloads.download({
/* make a base64 url manually */
url: `data:application/octet-stream;base64,${b64}`,
filename: `SheetJSTables.xlsx`
});
```
### Content Script Table Scraping
`table_to_book` and `table_to_sheet` can help build workbooks from DOM tables:
@ -64,24 +128,50 @@ for(var i = 0; i < tables.length; ++i) {
The demo extension includes multiple features to demonstrate sample usage.
Production extensions should include proper error handling.
<details><summary><b>Testing Unpacked Extension</b> (click to show)</summary>
<details open><summary><b>Testing Unpacked Extension</b> (click to hide)</summary>
1) [Right-Click and download the zip](pathname:///chromium/SheetJSChromiumUnpacked.zip)
1) Download the zip for the desired Manifest version:
2) Create a `SheetJSChromium` folder in your Downloads directory, move the zip
file into the folder, and extract the zip file.
- [Manifest V2](pathname:///chromium/SheetJSChromiumUnpackedV2.zip)
- [Manifest V3](pathname:///chromium/SheetJSChromiumUnpackedV3.zip)
3) Open `chrome://extensions/` in the browser and enable Developer mode
2) Open `chrome://extensions/` in the browser and enable Developer mode
4) Click "Load Unpacked" and select the `SheetJSChromium` folder.
3) Drag and drop the downloaded zip file into the window.
</details>
### Bookmark Exporter
<details open><summary><b>Testing</b> (click to hide)</summary>
0) Go to <https://sheetjs.com> and create a bookmark in the browser.
1) Click the Extensions icon (puzzle icon to the right of the address bar) and
select "SheetJS Demo".
2) If a small popup is not displayed, click on the SheetJS icon
3) Click "Export Bookmarks" and click "Save". Open the downloaded file!
</details>
```mermaid
sequenceDiagram
actor U as User
participant P as Popup
participant A as Chromium
U->>P: click icon
P->>A: `chrome.bookmarks.getTree`
A->>P: bookmark tree
Note over P: walk tree
Note over P: make workbook
P->>U: `XLSX.writeFile`
```
`chrome.bookmarks` API enables bookmark tree traversal. The "Export Bookmarks"
button in the extension pop-up recursively walks the bookmark tree, pushes the
bookmark URLs into a data array, and exports into a simple spreadsheet:
bookmark URLs into a data array, and exports into a simple spreadsheet.
```js
/* walk the bookmark tree */
@ -108,17 +198,57 @@ chrome.bookmarks.getTree(function(res) {
### Table Exporter
The `content.js` content script converts a table in the DOM to workbook object
using the `table_to_book` utility function:
<details open><summary><b>Testing</b> (click to hide)</summary>
```js
// event page script trigger
chrome.tabs.sendMessage(tab.id);
// content script convert
var wb = XLSX.utils.table_to_book(elt);
// event page script callback
XLSX.writeFile(wb, "export.xlsx");
1) Go to <https://sheetjs.com/demo/table>
2) Right-click anywhere in the page and select "SheetJS Demo" > "Export All Tables in Page"
3) Save and open the downloaded file!
</details>
The background script configures a context menu with the option to export data.
The flow diagrams show the data flow when the user chooses to export. They
differ in the denouement
```mermaid
sequenceDiagram
actor U as User
participant P as Background Script
participant A as Content Script
U->>P: Context Click > "Export"
Note over P: Query for active tab
P->>A: Ask active tab for data
Note over A: `table_to_sheet`
Note over A: generate workbook
A->>P: workbook object
Note over U,A: ... different denouement for Manifest V2 / V3 extensions ...
```
Since the workbook object is a plain JS object, the object is sent back to an
event page script which generates the file and attempts a download.
#### Manifest V2
For Manifest V2 extensions, `XLSX.writeFile` just works:
```mermaid
sequenceDiagram
actor U as User
participant P as Background Script
Note over P,U: ... background script received workbook ...
P->>U: `XLSX.writeFile` download
```
#### Manifest V3
For Manifest V3 extensions, since `URL.createObjectURL` is not available in
background service workers, a synthetic URL is created:
```mermaid
sequenceDiagram
actor U as User
participant P as Background Script
Note over P,U: ... background script received workbook ...
Note over P: `XLSX.write` Base64
Note over P: Create Data URL
P->>U: `chrome.downloads.download`
```

Binary file not shown.

Binary file not shown.

118
docz/static/deno/s2c.ts Normal file

@ -0,0 +1,118 @@
// @deno-types="https://cdn.sheetjs.com/xlsx-0.19.3/package/types/index.d.ts"
import { read, utils, set_cptable, version } from 'https://cdn.sheetjs.com/xlsx-0.19.3/package/xlsx.mjs';
import * as cptable from 'https://cdn.sheetjs.com/xlsx-0.19.3/package/dist/cpexcel.full.mjs';
set_cptable(cptable);
import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
class SheetJSResource extends Drash.Resource {
public paths = ["/"];
public OPTIONS(request: Drash.Request, response: Drash.Response) {
const allHttpMethods: string[] = [ "GET", "POST", "PUT", "DELETE" ];
response.headers.set("Allow", allHttpMethods.join());
response.headers.set("Access-Control-Allow-Methods", allHttpMethods.join());
response.headers.set("access-control-allow-origin", "*");
response.status_code = 204;
return response;
}
public POST(request: Drash.Request, response: Drash.Response) {
const file = request.bodyParam<Drash.Types.BodyFile>("file");
const type = request.bodyParam<string>("type");
try { response.headers.set("access-control-allow-origin", "*"); } catch(e) {}
if (!file) throw new Error("File is required!");
var wb = read(file.content, {type: "buffer", dense: true});
return response.html( (type == "csv" ? utils.sheet_to_csv : utils.sheet_to_html)(wb.Sheets[wb.SheetNames[0]]));
}
public GET(request: Drash.Request, response: Drash.Response): void {
try { response.headers.set("access-control-allow-origin", "*"); } catch(e) {}
return response.html(`\
<!DOCTYPE html>
<html>
<head>
<title>SheetJS Spreadsheet to HTML Conversion Service</title>
<meta charset="utf-8" />
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html, body {
width: 100vw;
max-width: 100%;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
-webkit-font-smoothing: antialiased;
background: white;
color: black;
}
body {
padding-top: 5px;
max-width: 760px;
margin-left: auto;
margin-right: auto;
}
table {
border-collapse: collapse;
}
table, tr, th, td { border: 1px solid; }
div {
padding: 5px;
}
li { margin: 5px 0;}
</style>
</head>
<body>
<h2><a href="//sheetjs.com/">SheetJS</a> Spreadsheet Conversion Service</h2>
<br/>
<h3>API</h3>
<br/>
Send a <code>POST</code> request to <a id="u">https://s2c.sheetjs.com</a> with the file in the <code>file</code> body parameter:<br/>
<br/>
<pre>
curl -X POST -F"file=@pres.numbers" <span id="v">https://s2c.sheetjs.com/</span>
</pre>
<br/>
The response will be an HTML TABLE generated from the first worksheet.
<br/><br/>
For CSV data, pass the parameter <code>type=csv</code>:<br/>
<br/>
<pre>
curl -X POST -F"file=@pres.numbers" -F"type=csv" <span id="w">https://s2c.sheetjs.com/</span>
</pre>
<br/><br/>
<h3>Try it out!</h3>
<br/>
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="file" /><br/><br/>
Use the file input element to select a file, then click "Submit"<br/><br/>
<button type="submit">Submit</button><br/><br/>
</form>
SheetJS Library Version: <code>${version}</code>
</body>
<script>w.innerHTML = v.innerHTML = u.innerHTML = u.href = window.location;</script>
</html>`,
);
}
}
const server = new Drash.Server({
hostname: "",
port: 3000,
protocol: "http",
resources: [
SheetJSResource,
],
});
server.run();
console.log(`Server running at ${server.address}.`);