version bump 0.12.7: chrome extension

- `writeFile` support chrome extension (fixes #1051 h/t @atkinsam)
- demo refresh
This commit is contained in:
SheetJS 2018-03-29 00:31:36 -04:00
parent dc2128caca
commit 08bb7e6e60
57 changed files with 1307 additions and 63 deletions

@ -90,24 +90,28 @@ prepend
prepended
repo
runtime
serverless
submodule
transpiled
- demos/altjs/README.md
ChakraCore
Duktape
Goja
Nashorn
- demos/angular/README.md
AngularJS
- demos/angular2/README.md
NativeScript
angular-cli
- demos/database/README.md
Knex
LowDB
MariaDB
MongoDB
MySQL
PostgreSQL
schemaless
@ -118,6 +122,9 @@ storages
Photoshop
minifier
- demos/function/README.md
microservice
- demos/headless/README.md
PhantomJS
SlimerJS

@ -212,10 +212,12 @@ The [`demos` directory](demos/) includes sample projects for:
**Platforms and Integrations**
- [`electron application`](demos/electron/)
- [`nw.js application`](demos/nwjs/)
- [`Chrome / Chromium extensions`](demos/chrome/)
- [`Adobe ExtendScript`](demos/extendscript/)
- [`Headless Browsers`](demos/headless/)
- [`canvas-datagrid`](demos/datagrid/)
- [`Swift JSC and other engines`](demos/altjs/)
- [`"serverless" functions`](demos/function/)
- [`internet explorer`](demos/oldie/)
### Optional Modules

@ -1 +1 @@
XLSX.version = '0.12.6';
XLSX.version = '0.12.7';

@ -9,7 +9,7 @@ function blobify(data) {
}
/* write or download file */
function write_dl(fname/*:string*/, payload/*:any*/, enc/*:?string*/) {
/*global IE_SaveFile, Blob, navigator, saveAs, URL, document, File */
/*global IE_SaveFile, Blob, navigator, saveAs, URL, document, File, chrome */
if(typeof _fs !== 'undefined' && _fs.writeFileSync) return enc ? _fs.writeFileSync(fname, payload, enc) : _fs.writeFileSync(fname, payload);
var data = (enc == "utf8") ? utf8write(payload) : payload;
/*:: declare var IE_SaveFile: any; */
@ -21,9 +21,14 @@ function write_dl(fname/*:string*/, payload/*:any*/, enc/*:?string*/) {
/*:: declare var saveAs: any; */
if(typeof saveAs !== 'undefined') return saveAs(blob, fname);
if(typeof URL !== 'undefined' && typeof document !== 'undefined' && document.createElement && URL.createObjectURL) {
var url = URL.createObjectURL(blob);
/*:: declare var chrome: any; */
if(typeof chrome === 'object' && typeof (chrome.downloads||{}).download == "function") {
if(URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000);
return chrome.downloads.download({ url: url, filename: fname, saveAs: true});
}
var a = document.createElement("a");
if(a.download != null) {
var url = URL.createObjectURL(blob);
/*:: if(document.body == null) throw new Error("unreachable"); */
a.download = fname; a.href = url; document.body.appendChild(a); a.click();
/*:: if(document.body == null) throw new Error("unreachable"); */ document.body.removeChild(a);

@ -40,10 +40,12 @@ can be installed with Bash on Windows or with `cygwin`.
**Platforms and Integrations**
- [`electron application`](electron/)
- [`nw.js application`](nwjs/)
- [`Chrome / Chromium extensions`](chrome/)
- [`Adobe ExtendScript`](extendscript/)
- [`Headless Browsers`](headless/)
- [`canvas-datagrid`](datagrid/)
- [`Swift JSC and other engines`](altjs/)
- [`"serverless" functions`](function/)
- [`internet explorer`](oldie/)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx)

@ -1,5 +1,5 @@
.PHONY: all
all: duktape nashorn rhinojs swift
all: duktape nashorn rhinojs swift goja
.PHONY: base
base:
@ -25,6 +25,12 @@ swift: base ## swift demo
swiftc SheetJSCore.swift main.swift -o SheetJSSwift
./SheetJSSwift
.PHONY: goja
goja: base ## goja demo
if [ ! -e xlsx.core.min.js ]; then cp ../../dist/xlsx.core.min.js .; fi
go build goja.go
for ext in xlsx xlsb biff8.xls xml.xls; do ./goja sheetjs.$$ext; done
.PHONY: chakra
chakra: base ## Chakra demo
node -pe "fs.writeFileSync('payload.js', 'var payload = \"' + fs.readFileSync('sheetjs.xlsx').toString('base64') + '\";')"

@ -124,4 +124,33 @@ char *buf = (char *)duk_get_buffer_data(ctx, -1, sz);
duk_pop(ctx);
```
## Goja
Goja is a pure Go implementation of ECMAScript 5. As of this writing, there are
some issues with processing Unicode data, but the `xlsx.core.min.js` script can
be processed. `[]byte` should be transformed to a binary string in the engine:
```go
/* read file */
data, _ := ioutil.ReadFile("sheetjs.xlsx")
/* load into engine */
vm.Set("buf", data)
/* convert to binary string */
_, _ = vm.RunString("var bstr = ''; for(var i = 0; i < buf.length; ++i) bstr += String.fromCharCode(buf[i]);")
/* parse */
wb, _ = vm.RunString("wb = XLSX.read(bstr, {type:'binary', cellNF:true});")
```
On the write side, `"base64"` strings can be decoded in Go:
```go
b64str, _ := vm.RunString("XLSX.write(wb, {type:'base64', bookType:'xlsx'})")
buf, _ := base64.StdEncoding.DecodeString(b64str.String())
_ = ioutil.WriteFile("sheetjs.xlsx", buf, 0644)
```
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx)

71
demos/altjs/goja.go Normal file

@ -0,0 +1,71 @@
package main
import (
b64 "encoding/base64"
"fmt"
"os"
"io/ioutil"
"github.com/dop251/goja"
)
func safe_run_file(vm *goja.Runtime, file string) {
data, err := ioutil.ReadFile(file)
if err != nil { panic(err) }
src := string(data)
_, err = vm.RunString(src)
if err != nil { panic(err) }
}
func eval_string(vm *goja.Runtime, cmd string) goja.Value {
v, err := vm.RunString(cmd)
if err != nil { panic(err) }
return v
}
func write_type(vm *goja.Runtime, t string) {
/* due to some wonkiness with array passing, use base64 */
b64str := eval_string(vm, "XLSX.write(wb, {type:'base64', bookType:'" + t + "'})")
buf, err := b64.StdEncoding.DecodeString(b64str.String());
if err != nil { panic(err) }
err = ioutil.WriteFile("sheetjsg." + t, buf, 0644)
if err != nil { panic(err) }
}
func main() {
vm := goja.New()
/* initialize */
eval_string(vm, "if(typeof global == 'undefined') global = (function(){ return this; }).call(null);")
/* load library */
safe_run_file(vm, "shim.min.js")
safe_run_file(vm, "xlsx.core.min.js")
/* get version string */
v := eval_string(vm, "XLSX.version")
fmt.Printf("SheetJS library version %s\n", v)
/* read file */
data, err := ioutil.ReadFile(os.Args[1])
if err != nil { panic(err) }
vm.Set("buf", data)
fmt.Printf("Loaded file %s\n", os.Args[1])
/* parse workbook */
eval_string(vm, "var bstr = ''; for(var i = 0; i < buf.length; ++i) bstr += String.fromCharCode(buf[i]);")
eval_string(vm, "wb = XLSX.read(bstr, {type:'binary', cellNF:true});")
eval_string(vm, "ws = wb.Sheets[wb.SheetNames[0]]")
/* print CSV */
csv := eval_string(vm, "XLSX.utils.sheet_to_csv(ws)")
fmt.Printf("%s\n", csv)
/* change cell A1 to 3 */
eval_string(vm, "ws['A1'].v = 3; delete ws['A1'].w;")
/* write file */
//write_type(vm, "xlsb")
//write_type(vm, "xlsx")
write_type(vm, "xls")
write_type(vm, "csv")
}

@ -1,2 +1,4 @@
dist
hooks
SheetJSIonic
SheetJSNS

@ -18,3 +18,13 @@ ionic:
ios android browser: ionic
cd SheetJSIonic; ionic cordova emulate $@ </dev/null; cd -
.PHONY: nativescript
nativescript:
bash ./nscript.sh
.PHONY: ns-ios ns-android
ns-ios: nativescript
cd SheetJSNS; tns run ios; cd -
ns-android: nativescript
cd SheetJSNS; tns run android; cd -

@ -12,6 +12,7 @@ the data, and a button to export the data.
Other scripts in this demo show:
- `ionic` deployment for iOS, android, and browser
- `nativescript` deployment for iOS and android
## Array of Arrays
@ -151,4 +152,28 @@ let blob = new Blob([wbout], {type: 'application/octet-stream'});
this.file.writeFile(url, filename, blob, {replace: true});
```
## NativeScript
Reproducing the full project is a little bit tricky. The included `nscript.sh`
script performs the necessary installation steps and adds the necessary shims
for `async` support. Due to incompatibilities with NativeScript and TypeScript
definitions, apps should require the `xlsx.full.min.js` file directly:
```typescript
const XLSX = require("./xlsx.full.min.js");
```
The `ISO_8859_1` encoding from the text module specifies `"binary"` strings.
`fs.File#readText` and `fs.File#writeText` reads and writes files:
```typescript
/* read a workbook */
const bstr: string = await file.readText(textModule.encoding.ISO_8859_1);
const wb = XLSX.read(bstr, { type: "binary" });
/* write a workbook */
const wbout: string = XLSX.write(wb, { bookType: 'xlsx', type: 'binary' });
await file.writeText(wbout, textModule.encoding.ISO_8859_1);
```
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx)

15
demos/angular2/nscript.sh Executable file

@ -0,0 +1,15 @@
#!/bin/bash
if [ ! -e SheetJSNS ]; then
tns create SheetJSNS --template nativescript-template-ng-tutorial
cd SheetJSNS
tns plugin add nativescript-nodeify
npm install xlsx
cd app
npm install xlsx
cd ../..
fi
cp ../../dist/xlsx.full.min.js SheetJSNS/
cp ../../dist/xlsx.full.min.js SheetJSNS/app/
cp nsmain.ts SheetJSNS/app/main.ts
cp nscript.ts SheetJSNS/app/app.component.ts

83
demos/angular2/nscript.ts Normal file

@ -0,0 +1,83 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* vim: set ts=2: */
import { Component } from "@angular/core";
import * as dockModule from "tns-core-modules/ui/layouts/dock-layout";
import * as buttonModule from "tns-core-modules/ui/button";
import * as textModule from "tns-core-modules/text";
import * as dialogs from "ui/dialogs";
import * as fs from "tns-core-modules/file-system";
/* NativeScript does not support import syntax for npm modules */
const XLSX = require("./xlsx.full.min.js");
@Component({
selector: "my-app",
template: `
<GridLayout rows="auto, *, auto">
<ActionBar row="0" title="SheetJS NativeScript Demo" class="action-bar"></ActionBar>
<!-- data converted to HTML and rendered in web view -->
<WebView row="1" src="{{html}}"></WebView>
<DockLayout row="2" dock="bottom" stretchLastChild="false">
<Button text="Import File" (tap)="import()" style="padding: 10px"></Button>
<Button text="Export File" (tap)="export()" style="padding: 10px"></Button>
</DockLayout>
</GridLayout>
`
})
export class AppComponent {
html: string = "";
constructor() {
const ws = XLSX.utils.aoa_to_sheet([[1,2],[3,4]]);
this.html = XLSX.utils.sheet_to_html(ws);
};
/* Import button */
async import() {
const filename: string = "SheetJSNS.xlsx";
/* find appropriate path */
const target: fs.Folder = fs.knownFolders.documents() || fs.knownFolders.ios.sharedPublic();
const url: string = fs.path.normalize(target.path + "///" + filename);
const file: fs.File = fs.File.fromPath(url);
try {
/* get binary string */
const bstr: string = await file.readText(textModule.encoding.ISO_8859_1);
/* read workbook */
const wb = XLSX.read(bstr, { type: "binary" });
/* grab first sheet */
const wsname: string = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
/* update table */
this.html = XLSX.utils.sheet_to_html(ws);
dialogs.alert(`Attempting to read to SheetJSNS.xlsx in ${url}`);
} catch(e) {
dialogs.alert(e.message);
}
};
/* Export button */
async export() {
const wb = XLSX.read(this.html, { type: "string" });
const filename: string = "SheetJSNS.xlsx";
/* generate binary string */
const wbout: string = XLSX.write(wb, { bookType: 'xlsx', type: 'binary' });
/* find appropriate path */
const target: fs.Folder = fs.knownFolders.documents() || fs.knownFolders.ios.sharedPublic();
const url: string = fs.path.normalize(target.path + "///" + filename);
const file: fs.File = fs.File.fromPath(url);
/* attempt to save binary string to file */
await file.writeText(wbout, textModule.encoding.ISO_8859_1);
dialogs.alert(`Wrote to SheetJSNS.xlsx in ${url}`);
};
}

65
demos/angular2/nsmain.ts Normal file

@ -0,0 +1,65 @@
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { AppModule } from "./app.module";
/*! ****************************************************************************
Code based on @Microsoft/tslib
Original license header:
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
// tslint:disable
if (!('__awaiter' in global)) {
global['__awaiter'] = function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
}
if (!('__generator' in global)) {
global['__generator'] = function (thisArg, body) {
var _: any = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
}
platformNativeScriptDynamic().bootstrapModule(AppModule);

2
demos/chrome/.gitignore vendored Normal file

@ -0,0 +1,2 @@
xlsx.*.js
logo.png

8
demos/chrome/Makefile Normal file

@ -0,0 +1,8 @@
.PHONY: init
init:
cp ../../dist/xlsx.full.min.js .
if [ ! -e logo.png ]; then curl -O http://sheetjs.com/logo.png; fi
.PHONY: lint
lint:
eslint content.js popup.js table.js

89
demos/chrome/README.md Normal file

@ -0,0 +1,89 @@
# Chrome and Chromium
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.
## Generating Downloads
The `writeFile` function works in a Chrome or Chromium extension:
```js
XLSX.writeFile(wb, "export.xlsx");
```
Under the hood, it uses the `chrome.downloads` API. `"downloads"` permission
should be set in `manifest.json`:
```js
"permissions": [
"downloads"
]
```
## Content Script Table Scraping
`table_to_book` and `table_to_sheet` can help build workbooks from DOM tables:
```js
var tables = document.getElementsByTagName("table");
var wb = XLSX.utils.book_new();
for(var i = 0; i < tables.length; ++i) {
var ws = XLSX.utils.table_to_sheet(tables[i]);
XLSX.utils.book_append_sheet(wb, ws, "Table" + i);
}
```
## Demo
The demo extension includes multiple features to demonstrate sample usage.
Production extensions should include proper error handling.
#### Table Exporter
The `content.js` content script converts a table in the DOM to workbook object
using the `table_to_book` utility function:
```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");
```
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.
#### Bookmark Exporter
`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:
```js
/* walk the bookmark tree */
function recurse_bookmarks(data, tree) {
if(tree.url) data.push({Name: tree.title, Location: tree.url});
(tree.children||[]).forEach(function(child) { recurse_bookmarks(data, child); });
}
/* get bookmark data */
chrome.bookmarks.getTree(function(res) {
/* load into an array */
var data = [];
res.forEach(function(t) { recurse_bookmarks(data, t); });
/* create worksheet */
var ws = XLSX.utils.json_to_sheet(data, { header: ['Name', 'Location'] });
/* create workbook and export */
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Bookmarks');
XLSX.writeFile(wb, "bookmarks.xlsx");
});
```
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx)

27
demos/chrome/content.js Normal file

@ -0,0 +1,27 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* eslint-env browser */
/* global XLSX, chrome */
var coords = [0,0];
document.addEventListener('mousedown', function(mouse) {
if(mouse && mouse.button == 2) coords = [mouse.clientX, mouse.clientY];
});
chrome.runtime.onMessage.addListener(function(msg, sender, cb) {
if(!msg && !msg['Sheet']) return;
if(msg.Sheet == "JS") {
var elt = document.elementFromPoint(coords[0], coords[1]);
while(elt != null) {
if(elt.tagName.toLowerCase() == "table") return cb(XLSX.utils.table_to_book(elt));
elt = elt.parentElement;
}
} else if(msg.Sheet == "J5") {
var tables = document.getElementsByTagName("table");
var wb = XLSX.utils.book_new();
for(var i = 0; i < tables.length; ++i) {
var ws = XLSX.utils.table_to_sheet(tables[i]);
XLSX.utils.book_append_sheet(wb, ws, "Table" + i);
}
return cb(wb);
}
cb(coords);
});

@ -0,0 +1,30 @@
{
"manifest_version": 2,
"name": "SheetJS Demo",
"description": "Sample Extension using SheetJS to interact with Chrome",
"version": "0.0.1",
"browser_action": {
"default_popup": "popup.html",
"default_icon": "logo.png"
},
"background": {
"scripts": ["xlsx.full.min.js", "table.js"],
"persistent": false
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js", "xlsx.full.min.js"],
"run_at": "document_end"
}],
"icons": {
"16": "logo.png"
},
"permissions": [
"activeTab",
"<all_urls>",
"bookmarks",
"contextMenus",
"downloads",
"tabs"
]
}

18
demos/chrome/popup.html Normal file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<!-- (C) 2013-present SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>
<title>SheetJS Chrome Extension Export Test</title>
<meta charset="utf-8" />
</head>
<body>
<!-- SheetJS js-xlsx library -->
<script type="text/javascript" src="xlsx.full.min.js"></script>
<button type="button" id="sjsdownload">Export Bookmarks</button>
<a><div id="sjsversion">Version</div></a>
<script type="text/javascript" src="popup.js"></script>
</body>
</html>

31
demos/chrome/popup.js Normal file

@ -0,0 +1,31 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* eslint-env browser */
/* global XLSX, chrome */
document.getElementById('sjsversion').innerText = "SheetJS " + XLSX.version;
document.getElementById('sjsversion').addEventListener('click', function() {
chrome.tabs.create({url: "https://sheetjs.com/"}); return false;
});
/* recursively walk the bookmark tree */
function recurse_bookmarks(data, tree, path) {
if(tree.url) data.push({Name: tree.title, Location: tree.url, Path:path});
var T = path ? (path + "::" + tree.title) : tree.title;
(tree.children||[]).forEach(function(C) { recurse_bookmarks(data, C, T); });
}
/* export bookmark data */
document.getElementById('sjsdownload').addEventListener('click', function() {
chrome.bookmarks.getTree(function(res) {
var data = [];
res.forEach(function(t) { recurse_bookmarks(data, t, ""); });
/* create worksheet */
var ws = XLSX.utils.json_to_sheet(data, { header: ['Name', 'Location', 'Path'] });
/* create workbook and export */
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Bookmarks');
XLSX.writeFile(wb, "bookmarks.xlsx");
});
});

43
demos/chrome/table.js Normal file

@ -0,0 +1,43 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* eslint-env browser */
/* global XLSX, chrome */
chrome.runtime.onInstalled.addListener(function() {
chrome.contextMenus.create({
type: "normal",
id: "sjsexport",
title: "Export Table to XLSX",
contexts: ["page", "selection"]
});
chrome.contextMenus.create({
type: "normal",
id: "sj5export",
title: "Export All Tables in Page",
contexts: ["page", "selection"]
});
chrome.contextMenus.onClicked.addListener(function(info/*, tab*/) {
var mode = "";
switch(info.menuItemId) {
case 'sjsexport': mode = "JS"; break;
case 'sj5export': mode = "J5"; break;
default: return;
}
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
chrome.tabs.sendMessage(tabs[0].id, {Sheet:mode}, sjsexport_cb);
});
});
chrome.contextMenus.create({
id: "sjsabout",
title: "About",
contexts: ["browser_action"]
});
chrome.contextMenus.onClicked.addListener(function(info/*, tab*/) {
if(info.menuItemId !== "sjsabout") return;
chrome.tabs.create({url: "https://sheetjs.com/"});
});
});
function sjsexport_cb(wb) {
if(!wb || !wb.SheetNames || !wb.Sheets) { console.log(wb); return alert("Error in exporting table"); }
XLSX.writeFile(wb, "export.xlsx");
}

@ -0,0 +1,89 @@
<!DOCTYPE html>
<!-- xlsx.js (C) 2013-present SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>SheetJS Firebase Dump Demo</title>
<style>
a { text-decoration: none }
</style>
</head>
<body>
<pre>
<h3><a href="//sheetjs.com/">SheetJS</a> Firebase Dump Demo</h3>
<b>Example Code</b>
/* ... connect to firebase */
const database = firebase.database();
/* import workbook */
await database.ref('foo').set(workbook);
/* change cells */
database.ref('foo').update({
"Sheets/Sheet1/A1": {"t": "s", "v": "J"},
"Sheets/Sheet1/A2": {"t": "n", "v": 5},
});
/* export snapshot */
const val = await database.ref('foo').once('value').val();
XLSX.writeFile(wb, "firebase.xlsx");
</pre>
<script src="xlsx.full.min.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.12.0/firebase.js"></script>
<script>
const Firebase = firebase;
const config = {
credential: {
getAccessToken: () => ({
expires_in: 0,
access_token: '',
}),
},
databaseURL: 'ws://localhost:5555'
};
/* make new workbook object from CSV */
const wb = XLSX.read('a,b,c\n1,2,3', {type:"binary"});
let P = Promise.resolve("sheetjs");
/* Connect to Firebase server and initialize collection */
P = P.then(async () => {
Firebase.initializeApp(config);
const database = Firebase.database();
await database.ref('foo').set(null);
return [database];
});
/* Insert entire workbook object into `foo` ref */
P = P.then(async ([database]) => {
await database.ref('foo').set(wb);
return [database];
});
/* Change cell A1 of Sheet1 to "J" and change A2 to 5 */
P = P.then(async ([database]) => {
database.ref('foo').update({
"Sheets/Sheet1/A1": {"t": "s", "v": "J"},
"Sheets/Sheet1/A2": {"t": "n", "v": 5},
});
return [database];
});
/* Write to file */
P = P.then(async ([database]) => {
const val = await database.ref('foo').once('value');
const wb = await val.val();
XLSX.writeFile(wb, "firebase.xlsx");
console.log(csv);
return [database];
});
/* Close connection */
P = P.then(async ([database]) => { database.app.delete(); });
</script>
</body>

@ -0,0 +1,58 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* eslint-env node */
const XLSX = require('xlsx');
const assert = require('assert');
const Firebase = require('firebase-admin');
const config = {
credential: {
getAccessToken: () => ({
expires_in: 0,
access_token: '',
}),
},
databaseURL: 'ws://localhost:5555'
};
/* make new workbook object from CSV */
const wb = XLSX.read('a,b,c\n1,2,3', {type:"binary", raw:true});
let P = Promise.resolve("sheetjs");
/* Connect to Firebase server and initialize collection */
P = P.then(async () => {
Firebase.initializeApp(config);
const database = Firebase.database();
await database.ref('foo').set(null);
return [database];
});
/* Insert entire workbook object into `foo` ref */
P = P.then(async ([database]) => {
await database.ref('foo').set(wb);
return [database];
});
/* Change cell A1 of Sheet1 to "J" and change A2 to 5 */
P = P.then(async ([database]) => {
database.ref('foo').update({
"Sheets/Sheet1/A1": {"t": "s", "v": "J"},
"Sheets/Sheet1/A2": {"t": "n", "v": 5},
});
return [database];
});
/* Write to file */
P = P.then(async ([database]) => {
const val = await database.ref('foo').once('value');
const wb = await val.val();
XLSX.writeFile(wb, "firebase.xlsx");
const ws = XLSX.readFile("firebase.xlsx").Sheets.Sheet1;
const csv = XLSX.utils.sheet_to_csv(ws);
assert.equal(csv, "J,b,c\n5,2,3\n");
console.log(csv);
return [database];
});
/* Close connection */
P = P.then(async ([database]) => { database.app.delete(); });

@ -13,4 +13,4 @@ lint: $(FILES)
.PHONY: clean
clean:
rm -f *.db *.xlsx
rm -f *.db *.xlsx *.csv

@ -0,0 +1,62 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* eslint-env node */
/* global Promise */
const XLSX = require('xlsx');
const SheetJSMongo = require("./SheetJSMongo");
const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017/sheetjs';
const db_name = 'sheetjs';
let P = Promise.resolve("sheetjs");
/* Connect to mongodb server */
P = P.then(async () => {
const client = await MongoClient.connect(url);
return [client];
});
/* Sample data table */
P = P.then(async ([client]) => {
const db = client.db(db_name);
try { await db.collection('pres').drop(); } catch(e) {}
const pres = db.collection('pres');
await pres.insertMany([
{ name: "Barack Obama", idx: 44 },
{ name: "Donald Trump", idx: 45 }
], {ordered: true});
try { await db.collection('fmts').drop(); } catch(e) {}
const fmts = db.collection('fmts');
await fmts.insertMany([
{ ext: 'XLSB', ctr: 'ZIP', multi: 1 },
{ ext: 'XLS', ctr: 'CFB', multi: 1 },
{ ext: 'XLML', multi: 1 },
{ ext: 'CSV', ctr: 'ZIP', multi: 0 },
], {ordered: true});
return [client, pres, fmts];
});
/* Export database to XLSX */
P = P.then(async ([client, pres, fmts]) => {
const wb = XLSX.utils.book_new();
await SheetJSMongo.book_append_mongo(wb, pres, "pres");
await SheetJSMongo.book_append_mongo(wb, fmts, "fmts");
XLSX.writeFile(wb, "mongocrud.xlsx");
return [client, pres, fmts];
});
/* Read the new file and dump all of the data */
P = P.then(() => {
const wb = XLSX.readFile('mongocrud.xlsx');
wb.SheetNames.forEach((n,i) => {
console.log(`Sheet #${i+1}: ${n}`);
const ws = wb.Sheets[n];
console.log(XLSX.utils.sheet_to_csv(ws));
});
});
/* Close connection */
P.then(async ([client]) => { client.close(); });

@ -0,0 +1,54 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* eslint-env node */
/* global Promise */
const XLSX = require('xlsx');
const assert = require('assert');
const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017/sheetjs';
const db_name = 'sheetjs';
/* make new workbook object from CSV */
const wb = XLSX.read('a,b,c\n1,2,3', {type:"binary", raw:true});
let P = Promise.resolve("sheetjs");
/* Connect to mongodb server and initialize collection */
P = P.then(async () => {
const client = await MongoClient.connect(url);
const db = client.db(db_name);
try { await db.collection('wb').drop(); } catch(e) {}
const coll = db.collection('wb');
return [client, coll];
});
/* Insert entire workbook object as a document */
P = P.then(async ([client, coll]) => {
const res = await coll.insertOne(wb);
assert.equal(res.insertedCount, 1);
return [client, coll];
});
/* Change cell A1 of Sheet1 to "J" and change A2 to 5 */
P = P.then(async ([client, coll]) => {
const res = await coll.updateOne({}, { $set: {
"Sheets.Sheet1.A1": {"t": "s", "v": "J"},
"Sheets.Sheet1.A2": {"t": "n", "v": 5},
}});
assert.equal(res.matchedCount, 1);
assert.equal(res.modifiedCount, 1);
return [client, coll];
});
/* Write to file */
P = P.then(async ([client, coll]) => {
const res = await coll.find({}).toArray();
const wb = res[0];
XLSX.writeFile(wb, "mongo.xlsx");
const ws = XLSX.readFile("mongo.xlsx").Sheets.Sheet1;
console.log(XLSX.utils.sheet_to_csv(ws));
return [client, coll];
});
/* Close connection */
P.then(async ([client]) => { client.close(); });

@ -296,5 +296,45 @@ LowDB is a small schemaless database powered by `lodash`. `_.get` and `_.set`
helper functions make storing metadata a breeze. The included `SheetJSLowDB.js`
script demonstrates a simple adapter that can load and dump data.
### Document Databases
Since document databases are capable of holding more complex objects, they can
actually hold the underlying worksheet objects! In some cases, where arrays are
supported, they can even hold the workbook object.
#### MongoDB
MongoDB is a popular document-oriented database engine. `MongoDBTest.js` uses
MongoDB to hold a simple workbook and export to XLSX.
`MongoDBCRUD.js` follows the SQL examples using an idiomatic collection
structure. Exporting and importing collections are straightforward:
<details>
<summary><b>Example code</b> (click to show)</summary>
```js
/* generate a worksheet from a collection */
const aoa = await db.collection('coll').find({}).toArray();
aoa.forEach((x) => delete x._id);
const ws = XLSX.utils.json_to_sheet(aoa);
/* import data from a worksheet to a collection */
const aoa = XLSX.utils.sheet_to_json(ws);
await db.collection('coll').insertMany(aoa, {ordered: true});
```
#### Firebase
[`firebase-server`](https://www.npmjs.com/package/firebase-server) is a simple
mock Firebase server used in the tests, but the same code works in an external
Firebase deployment when plugging in the database connection info.
`FirebaseDemo.html` and `FirebaseTest.js` demonstrate a whole-workbook process.
The entire workbook object is persisted, a few cells are changed, and the stored
data is dumped and exported to XLSX.
</details>
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx)

@ -0,0 +1,15 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* eslint-env node */
var XLSX = require("xlsx");
async function book_append_mongo(wb, coll, name) {
const aoo = await coll.find({}).toArray();
aoo.forEach((x) => delete x._id);
const ws = XLSX.utils.json_to_sheet(aoo);
XLSX.utils.book_append_sheet(wb, ws, name);
return ws;
}
module.exports = {
book_append_mongo
};

10
demos/electron/.eslintrc Normal file

@ -0,0 +1,10 @@
{
"env": {
"browser": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 8
},
"plugins": [ "html", "json" ]
}

@ -3,6 +3,9 @@ init:
mkdir -p node_modules
cd node_modules; if [ ! -e xlsx ]; then ln -s ../../../ xlsx ; fi; cd -
.PHONY: lint
lint:
eslint *.js
.PHONY: run
run:
electron .

@ -1,3 +1,7 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/*global Uint8Array, console */
/* exported export_xlsx */
/* eslint no-use-before-define:0 */
var XLSX = require('xlsx');
var electron = require('electron').remote;
@ -60,7 +64,7 @@ var do_file = (function() {
(function() {
var readf = document.getElementById('readf');
function handleF(e) {
function handleF(/*e*/) {
var o = electron.dialog.showOpenDialog({
title: 'Select a file',
filters: [{
@ -97,3 +101,4 @@ var export_xlsx = (function() {
electron.dialog.showMessageBox({ message: "Exported data to " + o, buttons: ["OK"] });
};
})();
void export_xlsx;

@ -1,3 +1,4 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* from the electron quick-start */
var electron = require('electron');
var XLSX = require('xlsx');

7
demos/function/.eslintrc Normal file

@ -0,0 +1,7 @@
{
"env": { "shared-node-browser":true },
"parserOptions": {
"ecmaVersion": 8
},
"plugins": [ "html", "json" ]
}

@ -0,0 +1,17 @@
{
"disabled": false,
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"dataType": "binary",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}

@ -0,0 +1,45 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* eslint-env node */
// base64 sheetjs.xlsb | curl -F "data=@-;filename=test.xlsb" http://localhost:7262/api/AzureHTTPTrigger
const XLSX = require('xlsx');
const formidable = require('formidable');
const Readable = require('stream').Readable;
var fs = require('fs');
/* formidable expects the request object to be a stream */
const streamify = (req) => {
if(typeof req.on !== 'undefined') return req;
const s = new Readable();
s._read = ()=>{};
s.push(new Buffer(req.body));
s.push(null);
Object.assign(s, req);
return s;
};
module.exports = (context, req) => {
const form = new formidable.IncomingForm();
form.parse(streamify(req), (err, fields, files) => {
/* grab the first file */
var f = Object.values(files)[0];
if(!f) {
context.res = { status: 400, body: "Must submit a file for processing!" };
} else {
/* since the file is Base64-encoded, read the file and parse as "base64" */
const b64 = fs.readFileSync(f.path).toString();
const wb = XLSX.read(b64, {type:"base64"});
/* convert to specified output type -- default CSV */
const ext = (fields.bookType || "csv").toLowerCase();
const out = XLSX.write(wb, {type:"string", bookType:ext});
context.res = {
status: 200,
headers: { "Content-Disposition": `attachment; filename="download.${ext}";` },
body: out
};
}
context.done();
});
};

@ -0,0 +1,40 @@
/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* eslint-env node */
// base64 sheetjs.xlsb | curl -F "data=@-;filename=test.xlsb" http://localhost:3000/LambdaProxy
'use strict';
var XLSX = require('xlsx');
var Busboy = require('busboy');
exports.handler = function(event, context, callback) {
/* set up busboy */
var ctype = event.headers['Content-Type']||event.headers['content-type'];
var bb = new Busboy({headers:{'content-type':ctype}});
/* busboy is evented; accumulate the fields and files manually */
var fields = {}, files = {};
bb.on('error', function(err) { console.log('err', err); callback(err); });
bb.on('field', function(fieldname, val) {fields[fieldname] = val });
bb.on('file', function(fieldname, file, filename) {
/* concatenate the individual data buffers */
var buffers = [];
file.on('data', function(data) { buffers.push(data); });
file.on('end', function() { files[fieldname] = [Buffer.concat(buffers), filename]; });
});
/* on the finish event, all of the fields and files are ready */
bb.on('finish', function() {
/* grab the first file */
var f = files[Object.keys(files)[0]];
if(!f) callback(new Error("Must submit a file for processing!"));
/* f[0] is a buffer, convert to string and interpret as Base64 */
var wb = XLSX.read(f[0].toString(), {type:"base64"});
/* grab first worksheet and convert to CSV */
var ws = wb.Sheets[wb.SheetNames[0]];
callback(null, { body: XLSX.utils.sheet_to_csv(ws) });
});
bb.end(event.body);
};

@ -0,0 +1,18 @@
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Sample Lambda API Gateway Normalizer
Resources:
LambdaProxy:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs6.10
Handler: index.handler
BinaryMediaTypes: '*/*'
Events:
Api:
Type: Api
Properties:
Path: /LambdaProxy
Method: any
BinaryMediaTypes: '*/*'

28
demos/function/Makefile Normal file

@ -0,0 +1,28 @@
.PHONY: microcule
microcule: mcstream.js
microcule $<
.PHONY: aws
aws: lambda-proxy
.PHONY: lambda-proxy
lambda-proxy:
cd LambdaProxy; mkdir -p node_modules; npm install xlsx busboy; sam local start-api; cd -
.PHONY: azure
azure:
func start
.PHONY: azure-server
azure-server:
mkdir -p /tmp/azurite
azurite -l /tmp/azurite
FILES=$(filter-out xlsx.full.min.js,$(wildcard *.js)) $(wildcard *.html)
.PHONY: lint
lint: $(FILES)
eslint $(FILES)
.PHONY: clean
clean:
rm -f *.db *.xlsx *.csv

123
demos/function/README.md Normal file

@ -0,0 +1,123 @@
# "Serverless" Functions
Because the library is pure JS, the hard work of reading and writing files can
be performed in the client browser or on the server side. On the server side,
the mechanical process is essentially independent from the data parsing or
generation. As a result, it is sometimes sensible to organize applications so
that the "last mile" conversion between JSON data and spreadsheet files is
independent from the main application.
The most obvious architecture would split off the JSON data conversion as a
separate microservice or application. Since it is only needed when an import or
export is requested, and since the process itself is relatively independent from
the rest of a typical service, a "Serverless" architecture makes a great fit.
Since the "function" is separate from the rest of the application, it is easy to
integrate into a platform built in Java or Go or Python or another language!
This demo discusses general architectures and provides examples for popular
commercial systems and self-hosted alternatives. The examples are merely
intended to demonstrate very basic functionality.
## Simple Strategies
#### Data Normalization
Most programming languages and platforms can process CSV or JSON but can't use
XLS or XLSX or XLSB directly. Form data from an HTTP POST request can be parsed
and contained files can be converted to CSV or JSON. The `XLSX.stream.to_csv`
utility can stream rows to a standard HTTP response. `XLSX.utils.sheet_to_json`
can generate an array of objects that can be fed to another service.
At the simplest level, a file on the filesystem can be converted using the bin
script that ships with the `npm` module:
```bash
$ xlsx /path/to/uploads/file > /tmp/new_csv_file
```
From a utility script, workbooks can be converted in two lines:
```js
var workbook = XLSX.readFile("path/to/file.xlsb");
XLSX.writeFile(workbook, "output/path/file.csv");
```
The `mcstream.js` demo uses the `microcule` framework to show a simple body
converter. It accepts raw data from a POST connection, parses as a workbook,
and streams back the first worksheet as CSV:
<details>
<summary><b>Code Sketch</b> (click to show)</summary>
```js
const XLSX = require('xlsx');
module.exports = (hook) => {
/* process_RS from the main README under "Streaming Read" section */
process_RS(hook.req, (wb) => {