version bump 0.12.7: chrome extension
- `writeFile` support chrome extension (fixes #1051 h/t @atkinsam) - demo refresh
This commit is contained in:
parent
dc2128caca
commit
08bb7e6e60
@ -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
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")
|
||||
}
|
2
demos/angular2/.gitignore
vendored
2
demos/angular2/.gitignore
vendored
@ -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
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
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
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
2
demos/chrome/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
xlsx.*.js
|
||||
logo.png
|
8
demos/chrome/Makefile
Normal file
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
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
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);
|
||||
});
|
30
demos/chrome/manifest.json
Normal file
30
demos/chrome/manifest.json
Normal file
@ -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
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
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
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");
|
||||
}
|
89
demos/database/FirebaseDemo.html
Normal file
89
demos/database/FirebaseDemo.html
Normal file
@ -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>
|
58
demos/database/FirebaseTest.js
Normal file
58
demos/database/FirebaseTest.js
Normal file
@ -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
|
||||
|
62
demos/database/MongoDBCRUD.js
Normal file
62
demos/database/MongoDBCRUD.js
Normal file
@ -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(); });
|
54
demos/database/MongoDBTest.js
Normal file
54
demos/database/MongoDBTest.js
Normal file
@ -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)
|
||||
|
15
demos/database/SheetJSMongo.js
Normal file
15
demos/database/SheetJSMongo.js
Normal file
@ -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
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
7
demos/function/.eslintrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"env": { "shared-node-browser":true },
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 8
|
||||
},
|
||||
"plugins": [ "html", "json" ]
|
||||
}
|
17
demos/function/AzureHTTPTrigger/function.json
Normal file
17
demos/function/AzureHTTPTrigger/function.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"disabled": false,
|
||||
"bindings": [
|
||||
{
|
||||
"authLevel": "function",
|
||||
"type": "httpTrigger",
|
||||
"direction": "in",
|
||||
"dataType": "binary",
|
||||
"name": "req"
|
||||
},
|
||||
{
|
||||
"type": "http",
|
||||
"direction": "out",
|
||||
"name": "res"
|
||||
}
|
||||
]
|
||||
}
|
45
demos/function/AzureHTTPTrigger/index.js
Normal file
45
demos/function/AzureHTTPTrigger/index.js
Normal file
@ -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();
|
||||
});
|
||||
};
|
40
demos/function/LambdaProxy/index.js
Normal file
40
demos/function/LambdaProxy/index.js
Normal file
@ -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);
|
||||
};
|
18
demos/function/LambdaProxy/template.yaml
Normal file
18
demos/function/LambdaProxy/template.yaml
Normal file
@ -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
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
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) => {
|
||||