diff --git a/.spelling b/.spelling index 19efca3..9f1fd22 100644 --- a/.spelling +++ b/.spelling @@ -36,7 +36,6 @@ CDNjs CommonJS Ethercalc ExtendScript -FileSaver IndexedDB JavaScriptCore LocalStorage @@ -58,6 +57,7 @@ webpack weex # Other terms +ActiveX APIs ArrayBuffer Base64 @@ -66,6 +66,7 @@ JS NoSQL README UTF-16 +VBScript XHR XMLHttpRequest bundler diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e3aac..a434819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ This log is intended to keep track of backwards-incompatible changes, including but not limited to API changes and file location changes. Minor behavioral changes may not be included if they are not expected to break existing code. +## 0.11.19 + +* Error on empty workbook + ## 0.11.16 (2017-12-30) * XLS ANSI/CP separation diff --git a/README.md b/README.md index e24b4f8..c423bc6 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,7 @@ The [`demos` directory](demos/) includes sample projects for: - [`Headless Browsers`](demos/headless/) - [`canvas-datagrid`](demos/datagrid/) - [`Swift JSC and other engines`](demos/altjs/) +- [`internet explorer`](demos/oldie/) ### Optional Modules @@ -339,7 +340,7 @@ var worksheet = XLSX.read(htmlstr, {type:'string'}); Browser download file (ajax) (click to show) Note: for a more complete example that works in older browsers, check the demo -at ). The directory also +at ). The [`xhr` demo](demos/xhr/) includes more examples with `XMLHttpRequest` and `fetch`. ```js @@ -414,6 +415,8 @@ input_dom_element.addEventListener('change', handleFile, false); +More specialized cases, including mobile app file processing, are covered in the +[included demos](demos/) ### Parsing Examples @@ -590,8 +593,7 @@ Assuming `workbook` is a workbook object:
nodejs write a file (click to show) -`writeFile` is only available in server environments. Browsers have no API for -writing arbitrary files given a path, so another strategy must be used. +`XLSX.writeFile` uses `fs.writeFileSync` in server environments: ```js if(typeof require !== 'undefined') XLSX = require('xlsx'); @@ -603,7 +605,7 @@ XLSX.writeFile(workbook, 'out.xlsb');
- Browser add to web page (click to show) + Browser add TABLE element to page (click to show) The `sheet_to_html` utility function generates HTML code that can be added to any DOM element. @@ -614,29 +616,10 @@ var container = document.getElementById('tableau'); container.innerHTML = XLSX.utils.sheet_to_html(worksheet); ``` -
- Browser save file (click to show) - -Note: browser generates binary blob and forces a "download" to client. This -example uses [FileSaver](https://github.com/eligrey/FileSaver.js/): - -```js -/* bookType can be any supported output type */ -var wopts = { bookType:'xlsx', bookSST:false, type:'array' }; - -var wbout = XLSX.write(workbook,wopts); - -/* the saveAs call downloads a file on the local machine */ -saveAs(new Blob([wbout],{type:"application/octet-stream"}), "test.xlsx"); -``` - -
- -
- Browser upload to server (click to show) + Browser upload file (ajax) (click to show) A complete example using XHR is [included in the XHR demo](demos/xhr/), along with examples for fetch and wrapper libraries. This example assumes the server @@ -658,6 +641,65 @@ req.send(formdata);
+
+ Browser save file (click to show) + +`XLSX.writeFile` wraps a few techniques for triggering a file save: + +- `URL` browser API creates an object URL for the file, which the library uses + by creating a link and forcing a click. It is supported in modern browsers. +- `msSaveBlob` is an IE10+ API for triggering a file save. +- `IE_FileSave` uses VBScript and ActiveX to write a file in IE6+ for Windows + XP and Windows 7. The shim must be included in the containing HTML page. + +There is no standard way to determine if the actual file has been downloaded. + +```js +/* output format determined by filename */ +XLSX.writeFile(workbook, 'out.xlsb'); +/* at this point, out.xlsb will have been downloaded */ +``` + +
+ +
+ Browser save file (compatibility) (click to show) + +`XLSX.writeFile` techniques work for most modern browsers as well as older IE. +For much older browsers, there are workarounds implemented by wrapper libraries. + +[`FileSaver.js`](https://github.com/eligrey/FileSaver.js/) implements `saveAs`. +Note: `XLSX.writeFile` will automatically call `saveAs` if available. + +```js +/* bookType can be any supported output type */ +var wopts = { bookType:'xlsx', bookSST:false, type:'array' }; + +var wbout = XLSX.write(workbook,wopts); + +/* the saveAs call downloads a file on the local machine */ +saveAs(new Blob([wbout],{type:"application/octet-stream"}), "test.xlsx"); +``` + +[`Downloadify`](https://github.com/dcneiner/downloadify) uses a Flash SWF button +to generate local files, suitable for environments where ActiveX is unavailable: + +```js +Downloadify.create(id,{ + /* other options are required! read the downloadify docs for more info */ + filename: "test.xlsx", + data: function() { return XLSX.write(wb, {bookType:"xlsx", type:'base64'}); }, + append: false, + dataType: 'base64' +}); +``` + +The [`oldie` demo](demos/oldie/) shows an IE-compatible fallback scenario. + +
+ +The [included demos](demos/) cover mobile apps and other special deployments. + ### Writing Examples - exporting an HTML table @@ -705,7 +747,8 @@ Parse options are described in the [Parsing Options](#parsing-options) section. `XLSX.write(wb, write_opts)` attempts to write the workbook `wb` -`XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename` +`XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename`. +In browser-based environments, it will attempt to force a client-side download. `XLSX.writeFileAsync(filename, wb, o, cb)` attempts to write `wb` to `filename`. If `o` is omitted, the writer will use the third argument as the callback. @@ -2015,6 +2058,7 @@ produces HTML output. The function takes an options argument: | Option Name | Default | Description | | :---------- | :------: | :-------------------------------------------------- | +|`id` | | Specify the `id` attribute for the `TABLE` element | |`editable` | false | If true, set `contenteditable="true"` for every TD | |`header` | | Override header (default `html body`) | |`footer` | | Override footer (default `/body /html`) | diff --git a/bits/01_version.js b/bits/01_version.js index 72d3786..571d0f6 100644 --- a/bits/01_version.js +++ b/bits/01_version.js @@ -1 +1 @@ -XLSX.version = '0.11.18'; +XLSX.version = '0.11.19'; diff --git a/bits/05_buf.js b/bits/05_buf.js index 5efd3fc..ac12910 100644 --- a/bits/05_buf.js +++ b/bits/05_buf.js @@ -19,11 +19,16 @@ function s2ab(s/*:string*/) { return buf; } -function arr2str(data/*:any*/)/*:string*/ { +function a2s(data/*:any*/)/*:string*/ { if(Array.isArray(data)) return data.map(_chr).join(""); var o/*:Array*/ = []; for(var i = 0; i < data.length; ++i) o[i] = _chr(data[i]); return o.join(""); } +function a2u(data/*:Array*/)/*:Uint8Array*/ { + if(typeof Uint8Array === 'undefined') throw new Error("Unsupported"); + return new Uint8Array(data); +} + function ab2a(data/*:ArrayBuffer|Uint8Array*/)/*:Array*/ { if(typeof ArrayBuffer == 'undefined') throw new Error("Unsupported"); if(data instanceof ArrayBuffer) return ab2a(new Uint8Array(data)); diff --git a/bits/19_fsutils.js b/bits/19_fsutils.js new file mode 100644 index 0000000..e7cc315 --- /dev/null +++ b/bits/19_fsutils.js @@ -0,0 +1,37 @@ +var _fs; +if(typeof require !== 'undefined') try { _fs = require('fs'); } catch(e) {} + +/* normalize data for blob ctor */ +function blobify(data) { + if(typeof data === "string") return s2ab(data); + if(Array.isArray(data)) return a2u(data); + return data; +} +/* write or download file */ +function write_dl(fname/*:string*/, payload/*:any*/, enc/*:?string*/) { + /*global IE_SaveFile, Blob, navigator, saveAs, URL, document */ + 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; */ + if(typeof IE_SaveFile !== 'undefined') return IE_SaveFile(data, fname); + if(typeof Blob !== 'undefined') { + var blob = new Blob([blobify(data)], {type:"application/octet-stream"}); + /*:: declare var navigator: any; */ + if(typeof navigator !== 'undefined' && navigator.msSaveBlob) return navigator.msSaveBlob(blob, fname); + /*:: declare var saveAs: any; */ + if(typeof saveAs !== 'undefined') return saveAs(blob, fname); + if(typeof URL !== 'undefined' && typeof document !== 'undefined' && document.createElement && URL.createObjectURL) { + 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); + if(URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000); + return url; + } + } + } + throw new Error("cannot initiate download"); +} + diff --git a/bits/21_ziputils.js b/bits/21_ziputils.js index ba25d0c..6a2b9e4 100644 --- a/bits/21_ziputils.js +++ b/bits/21_ziputils.js @@ -51,14 +51,13 @@ function getzipstr(zip, file/*:string*/, safe/*:?boolean*/)/*:?string*/ { try { return getzipstr(zip, file); } catch(e) { return null; } } -var _fs, jszip; +var jszip; /*:: declare var JSZip:any; */ /*global JSZip:true */ if(typeof JSZip !== 'undefined') jszip = JSZip; -if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { +if(typeof exports !== 'undefined') { + if(typeof module !== 'undefined' && module.exports) { if(typeof jszip === 'undefined') jszip = require('./jszip.js'); - try { _fs = require('fs'); } catch(e) { } } } diff --git a/bits/71_wbcommon.js b/bits/71_wbcommon.js index d6b2cfa..6af56dc 100644 --- a/bits/71_wbcommon.js +++ b/bits/71_wbcommon.js @@ -132,6 +132,7 @@ function check_wb_names(N) { } function check_wb(wb) { if(!wb || !wb.SheetNames || !wb.Sheets) throw new Error("Invalid Workbook"); + if(!wb.SheetNames.length) throw new Error("Workbook is empty"); check_wb_names(wb.SheetNames); /* TODO: validate workbook */ } diff --git a/bits/75_xlml.js b/bits/75_xlml.js index fd9e3d4..11c4218 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -833,7 +833,7 @@ function parse_xlml(data/*:RawBytes|string*/, opts)/*:Workbook*/ { switch(opts.type||"base64") { case "base64": return parse_xlml_xml(Base64.decode(data), opts); case "binary": case "buffer": case "file": return parse_xlml_xml(data, opts); - case "array": return parse_xlml_xml(arr2str(data), opts); + case "array": return parse_xlml_xml(a2s(data), opts); } /*:: throw new Error("unsupported type " + opts.type); */ } diff --git a/bits/79_html.js b/bits/79_html.js index 223264e..f9e83c0 100644 --- a/bits/79_html.js +++ b/bits/79_html.js @@ -84,9 +84,9 @@ var HTML_ = (function() { var preamble = ""; return preamble + oo.join("") + ""; } - function make_html_preamble(/*::ws:Worksheet, R:Range, o:Sheet2HTMLOpts*/)/*:string*/ { + function make_html_preamble(ws/*:Worksheet*/, R/*:Range*/, o/*:Sheet2HTMLOpts*/)/*:string*/ { var out/*:Array*/ = []; - return out.join("") + ''; + return out.join("") + ''; } var _BEGIN = 'SheetJS Table Export'; var _END = ''; diff --git a/bits/87_read.js b/bits/87_read.js index 061a3d2..e345174 100644 --- a/bits/87_read.js +++ b/bits/87_read.js @@ -100,7 +100,6 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ { case 0x0A: case 0x0D: case 0x20: return read_plaintext_raw(d, o); } if(n[2] <= 12 && n[3] <= 31) return DBF.to_workbook(d, o); - if(0x20>n[0]||n[0]>0x7F) throw new Error("Unsupported file " + n.join("|")); return read_prn(data, d, o, str); } diff --git a/bits/88_write.js b/bits/88_write.js index 250ae2c..6308a36 100644 --- a/bits/88_write.js +++ b/bits/88_write.js @@ -6,12 +6,12 @@ function write_zip_type(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:any*/ { switch(o.type) { case "base64": oopts.type = "base64"; break; case "binary": oopts.type = "string"; break; - case "string": throw new Error("'string' output type invalid for '" + o.bookType + ' files'); + case "string": throw new Error("'string' output type invalid for '" + o.bookType + "' files"); case "buffer": - case "file": oopts.type = "nodebuffer"; break; + case "file": oopts.type = has_buf ? "nodebuffer" : "string"; break; default: throw new Error("Unrecognized type " + o.type); } - if(o.type === "file") return _fs.writeFileSync(o.file, z.generate(oopts)); + if(o.type === "file") return write_dl(o.file, z.generate(oopts)); var out = z.generate(oopts); // $FlowIgnore return o.type == "string" ? utf8read(out) : out; @@ -23,8 +23,8 @@ function write_cfb_type(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:any*/ { switch(o.type) { case "base64": case "binary": break; case "buffer": case "array": o.type = ""; break; - case "file": return _fs.writeFileSync(o.file, CFB.write(cfb, {type:'buffer'})); - case "string": throw new Error("'string' output type invalid for '" + o.bookType + ' files'); + case "file": return write_dl(o.file, CFB.write(cfb, {type:has_buf ? 'buffer' : ""})); + case "string": throw new Error("'string' output type invalid for '" + o.bookType + "' files"); default: throw new Error("Unrecognized type " + o.type); } return CFB.write(cfb, o); @@ -37,7 +37,7 @@ function write_string_type(out/*:string*/, opts/*:WriteOpts*/, bom/*:?string*/)/ case "base64": return Base64.encode(utf8write(o)); case "binary": return utf8write(o); case "string": return out; - case "file": return _fs.writeFileSync(opts.file, o, 'utf8'); + case "file": return write_dl(opts.file, o, 'utf8'); case "buffer": { if(has_buf) return new Buffer(o, 'utf8'); else return write_string_type(o, {type:'binary'}).split("").map(function(c) { return c.charCodeAt(0); }); @@ -51,7 +51,7 @@ function write_stxt_type(out/*:string*/, opts/*:WriteOpts*/)/*:any*/ { case "base64": return Base64.encode(out); case "binary": return out; case "string": return out; /* override in sheet_to_txt */ - case "file": return _fs.writeFileSync(opts.file, out, 'binary'); + case "file": return write_dl(opts.file, out, 'binary'); case "buffer": { if(has_buf) return new Buffer(out, 'binary'); else return out.split("").map(function(c) { return c.charCodeAt(0); }); @@ -70,7 +70,7 @@ function write_binary_type(out, opts/*:WriteOpts*/)/*:any*/ { // $FlowIgnore for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]); return opts.type == 'base64' ? Base64.encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr; - case "file": return _fs.writeFileSync(opts.file, out); + case "file": return write_dl(opts.file, out); case "buffer": return out; default: throw new Error("Unrecognized type " + opts.type); } diff --git a/bits/90_utils.js b/bits/90_utils.js index 35d2dfb..77db4a5 100644 --- a/bits/90_utils.js +++ b/bits/90_utils.js @@ -131,7 +131,7 @@ function sheet_to_txt(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) { var s = sheet_to_csv(sheet, opts); if(typeof cptable == 'undefined' || opts.type == 'string') return s; var o = cptable.utils.encode(1200, s, 'str'); - return "\xff\xfe" + o; + return String.fromCharCode(255) + String.fromCharCode(254) + o; } function sheet_to_formulae(sheet/*:Worksheet*/)/*:Array*/ { diff --git a/demos/README.md b/demos/README.md index 56437d1..941852e 100644 --- a/demos/README.md +++ b/demos/README.md @@ -44,5 +44,6 @@ can be installed with Bash on Windows or with `cygwin`. - [`Headless Browsers`](headless/) - [`canvas-datagrid`](datagrid/) - [`Swift JSC and other engines`](altjs/) +- [`internet explorer`](oldie/) [![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx) diff --git a/demos/angular/README.md b/demos/angular/README.md index 1df7d35..90c651c 100644 --- a/demos/angular/README.md +++ b/demos/angular/README.md @@ -81,18 +81,15 @@ var ws = XLSX.utils.json_to_sheet(data); var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Presidents"); -/* write workbook (use type 'array' for ArrayBuffer) */ -var wbout = XLSX.write(wb, {bookType:'xlsx', type:'array'}); - -/* generate a download */ -saveAs(new Blob([wbout],{type:"application/octet-stream"}), "sheetjs.xlsx"); +/* write workbook and force a download */ +XLSX.writeFile(wb, "sheetjs.xlsx"); ``` `SheetJSExportService` exposes export functions for `XLSB` and `XLSX`. Other formats are easily supported by changing the `bookType` variable. It grabs -values from the grid, builds an array of arrays, generates a workbook and uses -FileSaver to generate a download. By setting the `filename` and `sheetname` -options in the ui-grid options, the output can be controlled. +values from the grid, builds an array of arrays, generates a workbook and forces +a download. By setting the `filename` and `sheetname` options in the ui-grid +options, the output can be controlled. [![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx) diff --git a/demos/angular/SheetJS-angular.js b/demos/angular/SheetJS-angular.js index 9330799..70cedc1 100644 --- a/demos/angular/SheetJS-angular.js +++ b/demos/angular/SheetJS-angular.js @@ -12,8 +12,7 @@ function SheetJSExportService(uiGridExporterService) { var wb = XLSX.utils.book_new(), ws = uigrid_to_sheet(data, columns); XLSX.utils.book_append_sheet(wb, ws, sheetName); - var wbout = XLSX.write(wb, wopts); - saveAs(new Blob([wbout], { type: 'application/octet-stream' }), fileName); + XLSX.writeFile(wb, fileName); } var service = {}; diff --git a/demos/angular/index.html b/demos/angular/index.html index 111524e..8fd13d9 100644 --- a/demos/angular/index.html +++ b/demos/angular/index.html @@ -13,10 +13,8 @@ - - - + diff --git a/demos/angular/shim.js b/demos/angular/shim.js new file mode 120000 index 0000000..7ec5819 --- /dev/null +++ b/demos/angular/shim.js @@ -0,0 +1 @@ +../../shim.js \ No newline at end of file diff --git a/demos/angular2/Makefile b/demos/angular2/Makefile index 529f916..f11f28f 100644 --- a/demos/angular2/Makefile +++ b/demos/angular2/Makefile @@ -2,24 +2,27 @@ angular: # Test Angular2 build cp package.json-angular2 package.json + rm -rf node_modules npm install if [ ! -e node_modules ]; then mkdir node_modules; fi if [ ! -e node_modules/xlsx ]; then cd node_modules; ln -s ../../../ xlsx; cd -; fi - ng build + npm run build # Test Angular4 build cp package.json-angular4 package.json + rm -rf node_modules npm install if [ ! -e node_modules ]; then mkdir node_modules; fi if [ ! -e node_modules/xlsx ]; then cd node_modules; ln -s ../../../ xlsx; cd -; fi - ng build + npm run build # Test Angular5 build cp package.json-angular5 package.json + rm -rf node_modules npm install if [ ! -e node_modules ]; then mkdir node_modules; fi if [ ! -e node_modules/xlsx ]; then cd node_modules; ln -s ../../../ xlsx; cd -; fi - ng build + npm run build .PHONY: ionic ionic: diff --git a/demos/angular2/README.md b/demos/angular2/README.md index effc575..ee992c9 100644 --- a/demos/angular2/README.md +++ b/demos/angular2/README.md @@ -38,8 +38,7 @@ const wb: XLSX.WorkBook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Sheet1'); /* save to file */ -const wbout: string = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); -saveAs(new Blob([wbout]), 'SheetJS.xlsx'); +XLSX.writeFile(wb, 'SheetJS.xlsx'); ``` `sheet_to_json` with the option `header:1` makes importing simple: diff --git a/demos/angular2/ionic.sh b/demos/angular2/ionic.sh index a991e4e..0afb5bb 100755 --- a/demos/angular2/ionic.sh +++ b/demos/angular2/ionic.sh @@ -5,7 +5,7 @@ if [ ! -e SheetJSIonic ]; then ionic cordova platform add browser (XLSX.utils.sheet_to_json(ws, {header: 1})); }; - write(): ArrayBuffer { + write(): XLSX.WorkBook { /* generate worksheet */ const ws: XLSX.WorkSheet = XLSX.utils.aoa_to_sheet(this.data); @@ -56,9 +55,7 @@ export class HomePage { const wb: XLSX.WorkBook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'SheetJS'); - /* save to ArrayBuffer */ - const wbout: ArrayBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); - return wbout; + return wb; }; /* File Input element for browser */ @@ -91,17 +88,26 @@ export class HomePage { /* Export button */ async export() { - const wbout: ArrayBuffer = this.write(); + const wb: XLSX.WorkBook = this.write(); const filename: string = "SheetJSIonic.xlsx"; - const blob: Blob = new Blob([wbout], {type: 'application/octet-stream'}); try { + /* generate Blob */ + const wbout: ArrayBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); + const blob: Blob = new Blob([wbout], {type: 'application/octet-stream'}); + + /* find appropriate path for mobile */ const target: string = this.file.documentsDirectory || this.file.externalDataDirectory || this.file.dataDirectory || ''; const dentry = await this.file.resolveDirectoryUrl(target); const url: string = dentry.nativeURL || ''; + + /* attempt to save blob to file */ await this.file.writeFile(url, filename, blob, {replace: true}); alert(`Wrote to SheetJSIonic.xlsx in ${url}`); } catch(e) { - if(e.message.match(/It was determined/)) saveAs(blob, filename); + if(e.message.match(/It was determined/)) { + /* in the browser, use writeFile */ + XLSX.writeFile(wb, filename); + } else alert(`Error: ${e.message}`); } }; diff --git a/demos/angular2/package.json b/demos/angular2/package.json index 4ac455f..1f62707 100644 --- a/demos/angular2/package.json +++ b/demos/angular2/package.json @@ -24,11 +24,10 @@ "rxjs": "^5.5.2", - "zone.js": "^0.8.14", - "file-saver": "^1.3.3" + "zone.js": "^0.8.14" }, "devDependencies": { - "@angular/cli": "1.5.0", + "@angular/cli": "^1.5.3", "@angular/compiler-cli": "^5.0.0", "@angular/language-service": "^5.0.0", "@types/node": "~6.0.60", diff --git a/demos/angular2/package.json-angular2 b/demos/angular2/package.json-angular2 index eabba54..84f0026 100644 --- a/demos/angular2/package.json-angular2 +++ b/demos/angular2/package.json-angular2 @@ -24,8 +24,7 @@ "reflect-metadata": "^0.1.8", "rxjs": "^5.0.2", "systemjs": "0.19.40", - "zone.js": "^0.7.4", - "file-saver": "^1.3.3" + "zone.js": "^0.7.4" }, "devDependencies": { "@angular/cli": "1.1.2", diff --git a/demos/angular2/package.json-angular4 b/demos/angular2/package.json-angular4 index 52a3c5a..2891383 100644 --- a/demos/angular2/package.json-angular4 +++ b/demos/angular2/package.json-angular4 @@ -24,8 +24,7 @@ "rxjs": "^5.1.0", - "zone.js": "^0.8.4", - "file-saver": "^1.3.3" + "zone.js": "^0.8.4" }, "devDependencies": { "@angular/cli": "1.1.2", diff --git a/demos/angular2/package.json-angular5 b/demos/angular2/package.json-angular5 index 4ac455f..1f62707 100644 --- a/demos/angular2/package.json-angular5 +++ b/demos/angular2/package.json-angular5 @@ -24,11 +24,10 @@ "rxjs": "^5.5.2", - "zone.js": "^0.8.14", - "file-saver": "^1.3.3" + "zone.js": "^0.8.14" }, "devDependencies": { - "@angular/cli": "1.5.0", + "@angular/cli": "^1.5.3", "@angular/compiler-cli": "^5.0.0", "@angular/language-service": "^5.0.0", "@types/node": "~6.0.60", diff --git a/demos/angular2/src/app/sheetjs.component.ts b/demos/angular2/src/app/sheetjs.component.ts index 9e89b7b..57f1a4a 100644 --- a/demos/angular2/src/app/sheetjs.component.ts +++ b/demos/angular2/src/app/sheetjs.component.ts @@ -4,8 +4,6 @@ import { Component } from '@angular/core'; import * as XLSX from 'xlsx'; -import { saveAs } from 'file-saver'; - type AOA = any[][]; @Component({ @@ -57,7 +55,6 @@ export class SheetJSComponent { XLSX.utils.book_append_sheet(wb, ws, 'Sheet1'); /* save to file */ - const wbout: ArrayBuffer = XLSX.write(wb, this.wopts); - saveAs(new Blob([wbout], { type: 'application/octet-stream' }), this.fileName); + XLSX.writeFile(wb, this.fileName); } } diff --git a/demos/database/RedisTest.js b/demos/database/RedisTest.js index e0307f6..e5c7c18 100644 --- a/demos/database/RedisTest.js +++ b/demos/database/RedisTest.js @@ -9,42 +9,42 @@ var client = redis.createClient(); /* Sample data */ var init = [ - ["FLUSHALL", []], - ["SADD", ["birdpowers", "flight", "pecking"]], - ["SET", ["foo", "bar"]], - ["SET", ["baz", 0]], - ["RPUSH", ["friends", "sam", "alice", "bob"]], - ["ZADD", ["hackers", 1906, 'Grace Hopper', 1912, 'Alan Turing', 1916, 'Claude Shannon', 1940, 'Alan Kay', 1953, 'Richard Stallman', 1957, 'Sophie Wilson', 1965, 'Yukihiro Matsumoto', 1969, 'Linus Torvalds']], - ["SADD", ["superpowers", "flight", 'x-ray vision']], - ["HMSET", ["user:1000", "name", 'John Smith', "email", 'john.smith@example.com', "password", "s3cret", "visits", 1]], - ["HMSET", ["user:1001", "name", 'Mary Jones', "email", 'mjones@example.com', "password", "hidden"]] + ["FLUSHALL", []], + ["SADD", ["birdpowers", "flight", "pecking"]], + ["SET", ["foo", "bar"]], + ["SET", ["baz", 0]], + ["RPUSH", ["friends", "sam", "alice", "bob"]], + ["ZADD", ["hackers", 1906, 'Grace Hopper', 1912, 'Alan Turing', 1916, 'Claude Shannon', 1940, 'Alan Kay', 1953, 'Richard Stallman', 1957, 'Sophie Wilson', 1965, 'Yukihiro Matsumoto', 1969, 'Linus Torvalds']], + ["SADD", ["superpowers", "flight", 'x-ray vision']], + ["HMSET", ["user:1000", "name", 'John Smith', "email", 'john.smith@example.com', "password", "s3cret", "visits", 1]], + ["HMSET", ["user:1001", "name", 'Mary Jones', "email", 'mjones@example.com', "password", "hidden"]] ]; const R = (()=>{ - const Rcache = {}; - const R_ = (n) => Rcache[n] || (Rcache[n] = util.promisify(client[n]).bind(client)); - return (n) => R_(n.toLowerCase()); + const Rcache = {}; + const R_ = (n) => Rcache[n] || (Rcache[n] = util.promisify(client[n]).bind(client)); + return (n) => R_(n.toLowerCase()); })(); (async () => { - for(var i = 0; i < init.length; ++i) await R(init[i][0])(init[i][1]); + for(var i = 0; i < init.length; ++i) await R(init[i][0])(init[i][1]); /* Export database to XLSX */ - var wb = await SheetJSRedis.redis_to_wb(R); + var wb = await SheetJSRedis.redis_to_wb(R); XLSX.writeFile(wb, "redis.xlsx"); /* Import XLSX to database */ - await R("flushall")(); + await R("flushall")(); var wb2 = XLSX.readFile("redis.xlsx"); - await SheetJSRedis.wb_to_redis(wb2, R); + await SheetJSRedis.wb_to_redis(wb2, R); - /* Verify */ - assert.equal(await R("get")("foo"), "bar"); - assert.equal(await R("lindex")("friends", 1), "alice"); - assert.equal(await R("zscore")("hackers", "Claude Shannon"), 1916); - assert.equal(await R("hget")("user:1000", "name"), "John Smith"); - assert.equal(await R("sismember")("superpowers", "flight"), "1"); - assert.equal(await R("sismember")("birdpowers", "pecking"), "1"); + /* Verify */ + assert.equal(await R("get")("foo"), "bar"); + assert.equal(await R("lindex")("friends", 1), "alice"); + assert.equal(await R("zscore")("hackers", "Claude Shannon"), 1916); + assert.equal(await R("hget")("user:1000", "name"), "John Smith"); + assert.equal(await R("sismember")("superpowers", "flight"), "1"); + assert.equal(await R("sismember")("birdpowers", "pecking"), "1"); - client.quit(); + client.quit(); })(); diff --git a/demos/database/SheetJSRedis.js b/demos/database/SheetJSRedis.js index 8f5b3f0..e3f4b64 100644 --- a/demos/database/SheetJSRedis.js +++ b/demos/database/SheetJSRedis.js @@ -6,68 +6,68 @@ const pair = (arr) => arr.map((x,i)=>!(i%2)&&[x,+arr[i+1]]).filter(x=>x); const keyify = (obj) => Object.keys(obj).map(x => [x, obj[x]]); async function redis_to_wb(R) { - var wb = XLSX.utils.book_new(); - var manifest = [], strs = []; + var wb = XLSX.utils.book_new(); + var manifest = [], strs = []; - /* store strings in strs and keep note of other objects in manifest */ - var keys = await R("keys")("*"), type = ""; - for(var i = 0; i < keys.length; ++i) { - type = await R("type")(keys[i]); - switch(type) { - case "string": strs.push({key:keys[i], value: await R("get")(keys[i])}); break; - case "list": case "zset": case "set": case "hash": manifest.push({key:keys[i], type:type}); break; - default: throw new Error("bad type " + type); - } - } + /* store strings in strs and keep note of other objects in manifest */ + var keys = await R("keys")("*"), type = ""; + for(var i = 0; i < keys.length; ++i) { + type = await R("type")(keys[i]); + switch(type) { + case "string": strs.push({key:keys[i], value: await R("get")(keys[i])}); break; + case "list": case "zset": case "set": case "hash": manifest.push({key:keys[i], type:type}); break; + default: throw new Error("bad type " + type); + } + } - /* add worksheets if relevant */ - if(strs.length > 0) { - var wss = XLSX.utils.json_to_sheet(strs, {header: ["key", "value"], skipHeader:1}); - XLSX.utils.book_append_sheet(wb, wss, "_strs"); - } - if(manifest.length > 0) { - var wsm = XLSX.utils.json_to_sheet(manifest, {header: ["key", "type"]}); - XLSX.utils.book_append_sheet(wb, wsm, "_manifest"); - } - for(i = 0; i < manifest.length; ++i) { - var sn = "obj" + i; - var aoa, key = manifest[i].key; - switch((type=manifest[i].type)) { - case "list": - aoa = (await R("lrange")(key, 0, -1)).map(x => [x]); break; - case "set": - aoa = (await R("smembers")(key)).map(x => [x]); break; - case "zset": - aoa = pair(await R("zrange")(key, 0, -1, "withscores")); break; - case "hash": - aoa = keyify(await R("hgetall")(key)); break; - default: throw new Error("bad type " + type); - } - XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sn); - } - return wb; + /* add worksheets if relevant */ + if(strs.length > 0) { + var wss = XLSX.utils.json_to_sheet(strs, {header: ["key", "value"], skipHeader:1}); + XLSX.utils.book_append_sheet(wb, wss, "_strs"); + } + if(manifest.length > 0) { + var wsm = XLSX.utils.json_to_sheet(manifest, {header: ["key", "type"]}); + XLSX.utils.book_append_sheet(wb, wsm, "_manifest"); + } + for(i = 0; i < manifest.length; ++i) { + var sn = "obj" + i; + var aoa, key = manifest[i].key; + switch((type=manifest[i].type)) { + case "list": + aoa = (await R("lrange")(key, 0, -1)).map(x => [x]); break; + case "set": + aoa = (await R("smembers")(key)).map(x => [x]); break; + case "zset": + aoa = pair(await R("zrange")(key, 0, -1, "withscores")); break; + case "hash": + aoa = keyify(await R("hgetall")(key)); break; + default: throw new Error("bad type " + type); + } + XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sn); + } + return wb; } /* convert worksheet aoa to specific redis type */ const aoa_to_redis = { - list: async (aoa, R, key) => await R("RPUSH")([key].concat(aoa.map(x=>x[0]))), - zset: async (aoa, R, key) => await R("ZADD" )([key].concat(aoa.reduce((acc,x)=>acc.concat([+x[1], x[0]]), []))), - hash: async (aoa, R, key) => await R("HMSET")([key].concat(aoa.reduce((acc,x)=>acc.concat(x), []))), - set: async (aoa, R, key) => await R("SADD" )([key].concat(aoa.map(x=>x[0]))) + list: async (aoa, R, key) => await R("RPUSH")([key].concat(aoa.map(x=>x[0]))), + zset: async (aoa, R, key) => await R("ZADD" )([key].concat(aoa.reduce((acc,x)=>acc.concat([+x[1], x[0]]), []))), + hash: async (aoa, R, key) => await R("HMSET")([key].concat(aoa.reduce((acc,x)=>acc.concat(x), []))), + set: async (aoa, R, key) => await R("SADD" )([key].concat(aoa.map(x=>x[0]))) }; async function wb_to_redis(wb, R) { - if(wb.Sheets._strs) { - var strs = XLSX.utils.sheet_to_json(wb.Sheets._strs, {header:1}); - for(var i = 0; i < strs.length; ++i) await R("SET")(strs[i]); - } - if(!wb.Sheets._manifest) return; - var M = XLSX.utils.sheet_to_json(wb.Sheets._manifest); - for(i = 0; i < M.length; ++i) { - var aoa = XLSX.utils.sheet_to_json(wb.Sheets["obj" + i], {header:1}); - await aoa_to_redis[M[i].type](aoa, R, M[i].key); - } + if(wb.Sheets._strs) { + var strs = XLSX.utils.sheet_to_json(wb.Sheets._strs, {header:1}); + for(var i = 0; i < strs.length; ++i) await R("SET")(strs[i]); + } + if(!wb.Sheets._manifest) return; + var M = XLSX.utils.sheet_to_json(wb.Sheets._manifest); + for(i = 0; i < M.length; ++i) { + var aoa = XLSX.utils.sheet_to_json(wb.Sheets["obj" + i], {header:1}); + await aoa_to_redis[M[i].type](aoa, R, M[i].key); + } } module.exports = { - redis_to_wb, - wb_to_redis + redis_to_wb, + wb_to_redis }; diff --git a/demos/database/SheetJSSQL.js b/demos/database/SheetJSSQL.js index 02eb9d4..a5bab14 100644 --- a/demos/database/SheetJSSQL.js +++ b/demos/database/SheetJSSQL.js @@ -49,9 +49,9 @@ function sheet_to_sql(ws, sname, mode) { var out = []; - var BT = mode == "PGSQL" ? "" : "`"; - var Q = mode == "PGSQL" ? "'" : '"'; - var R = mode == "PGSQL" ? /'/g : /"/g; + var BT = mode == "PGSQL" ? "" : "`"; + var Q = mode == "PGSQL" ? "'" : '"'; + var R = mode == "PGSQL" ? /'/g : /"/g; out.push("DROP TABLE IF EXISTS " + BT + sname + BT ); out.push("CREATE TABLE " + BT + sname + BT + " (" + names.map(function(n, i) { return BT + n + BT + " " + (types[i]||"TEXT"); }).join(", ") + ");" ); diff --git a/demos/datagrid/README.md b/demos/datagrid/README.md index 7ffc6b0..e11f1a9 100644 --- a/demos/datagrid/README.md +++ b/demos/datagrid/README.md @@ -82,7 +82,8 @@ var ws = XLSX.utils.aoa_to_sheet(prep(grid.data)); var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'SheetJS'); -/* .. generate download (see documentation for examples) .. */ +/* generate download */ +XLSX.writeFile(wb, "SheetJS.xlsx"); ``` ## Additional Features diff --git a/demos/datagrid/index.html b/demos/datagrid/index.html index 25d01c1..ffd9268 100644 --- a/demos/datagrid/index.html +++ b/demos/datagrid/index.html @@ -40,8 +40,7 @@ Use readAsBinaryString: (when available)
- - + diff --git a/demos/datagrid/shim.js b/demos/datagrid/shim.js new file mode 120000 index 0000000..7ec5819 --- /dev/null +++ b/demos/datagrid/shim.js @@ -0,0 +1 @@ +../../shim.js \ No newline at end of file diff --git a/demos/meteor/Makefile b/demos/meteor/Makefile index 42534d6..77af10d 100644 --- a/demos/meteor/Makefile +++ b/demos/meteor/Makefile @@ -6,9 +6,9 @@ start: init: if [ ! -e .meteor ]; then meteor create .; fi; @npm install babel-runtime meteor-node-stubs - @meteor add pfafman:filesaver check + @meteor add check @mkdir -p node_modules; cd node_modules; ln -s ../../../ xlsx; cd - .PHONY: lint lint: - @meteor npm run lint \ No newline at end of file + @meteor npm run lint diff --git a/demos/meteor/README.md b/demos/meteor/README.md index 6ef87f6..e925752 100644 --- a/demos/meteor/README.md +++ b/demos/meteor/README.md @@ -61,13 +61,9 @@ const html = document.getElementById('out').innerHTML; // SERVER SIDE const wb = XLSX.read(html, { type: 'binary' }); // CLIENT SIDE -const o = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); -saveAs(new Blob([o], {type:'application/octet-stream'}), 'sheetjs.xlsx'); +XLSX.writeFile(wb, 'sheetjs.xlsx'); ``` -This demo uses the FileSaver library for writing files, installed through the -[`pfafman:filesaver` wrapper](https://atmospherejs.com/pfafman/filesaver). - ## Setup @@ -76,7 +72,6 @@ This tree does not include the `.meteor` structure. Rebuild the project with: ```bash meteor create . npm install babel-runtime meteor-node-stubs xlsx -meteor add pfafman:filesaver meteor ``` diff --git a/demos/meteor/client/main.js b/demos/meteor/client/main.js index aafddd0..75b7e40 100644 --- a/demos/meteor/client/main.js +++ b/demos/meteor/client/main.js @@ -1,7 +1,5 @@ /* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ import XLSX from 'xlsx'; -/* note: saveAs is made available via the smart package */ -/* global saveAs */ import { Meteor } from 'meteor/meteor'; import { Template } from 'meteor/templating'; @@ -13,11 +11,12 @@ Template.sheetjs.events({ /* "Browser file upload form element" from SheetJS README */ const file = event.currentTarget.files[0]; const reader = new FileReader(); + const rABS = !!reader.readAsBinaryString; reader.onload = function(e) { const data = e.target.result; const name = file.name; /* Meteor magic */ - Meteor.call('upload', data, name, function(err, wb) { + Meteor.call(rABS ? 'uploadS' : 'uploadU', rABS ? data : new Uint8Array(data), name, function(err, wb) { if (err) throw err; /* load the first worksheet */ const ws = wb.Sheets[wb.SheetNames[0]]; @@ -27,15 +26,14 @@ Template.sheetjs.events({ document.getElementById('dnload').disabled = false; }); }; - reader.readAsBinaryString(file); + if(rABS) reader.readAsBinaryString(file); else reader.readAsArrayBuffer(file); }, 'click button' () { const html = document.getElementById('out').innerHTML; Meteor.call('download', html, function(err, wb) { if (err) throw err; /* "Browser download file" from SheetJS README */ - const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); - saveAs(new Blob([wbout], { type: 'application/octet-stream' }), 'sheetjs.xlsx'); + XLSX.writeFile(wb, 'sheetjs.xlsx'); }); }, }); diff --git a/demos/meteor/server/main.js b/demos/meteor/server/main.js index 72e3d13..ea30a72 100644 --- a/demos/meteor/server/main.js +++ b/demos/meteor/server/main.js @@ -4,12 +4,17 @@ import { check } from 'meteor/check'; import XLSX from 'xlsx'; Meteor.methods({ - upload: (bstr, name) => { - /* read the data and return the workbook object to the frontend */ + /* read the data and return the workbook object to the frontend */ + uploadS: (bstr, name) => { check(bstr, String); check(name, String); return XLSX.read(bstr, { type: 'binary' }); }, + uploadU: (ab, name) => { + check(ab, Uint8Array); + check(name, String); + return XLSX.read(ab, { type: 'array' }); + }, download: (html) => { check(html, String); let wb; diff --git a/demos/oldie/README.md b/demos/oldie/README.md new file mode 100644 index 0000000..8ac5b38 --- /dev/null +++ b/demos/oldie/README.md @@ -0,0 +1,70 @@ +# Internet Explorer + +Despite the efforts to deprecate the pertinent operating systems, IE is still +very popular, required for various government and corporate websites throughout +the world. The modern download strategies are not available in older versions +of IE, but there are alternative approaches. + +## Strategies + +#### IE10 and IE11 File API + +As part of the File API implementation, IE10 and IE11 provide the `msSaveBlob` +and `msSaveOrOpenBlob` functions to save blobs to the client computer. This +approach is embedded in `XLSX.writeFile` and no additional shims are necessary. + +#### Flash-based Download + +It is possible to write to the file system using a SWF. `Downloadify` library +implements one solution. Since a genuine click is required, there is no way to +force a download. The demo generates a button for each desired output format. + +#### ActiveX-based Download + +Through the `Scripting.FileSystemObject` object model, a script in the VBScript +scripting language can write to an arbitrary path on the filesystem. The shim +includes a special `IE_SaveFile` function to write binary strings to file. It +attempts to write to the Downloads folder or Documents folder or Desktop. + +This approach can be triggered, but it requires the user to enable ActiveX. It +is embedded as a strategy in `writeFile` and used only if the shim script is +included in the page and the relevant features are enabled on the target system. + +## Demo + +The included demo starts from an array of arrays, generating an editable HTML +table with `aoa_to_sheet` and adding it to the page: + +```js +var ws = XLSX.utils.aoa_to_sheet(aoa); +var html_string = XLSX.utils.sheet_to_html(ws, { id: "table", editable: true }); +document.getElementById("container").innerHTML = html_string; +``` + +The included download buttons use `table_to_book` to construct a new workbook +based on the table and `writeFile` to force a download: + + +```js +var elt = document.getElementById('table'); +var wb = XLSX.utils.table_to_book(elt, { sheet: "Sheet JS" }); +XLSX.writeFile(wb, filename); +``` + +The shim is included in the HTML page, unlocking the ActiveX pathway if enabled +in browser settings. + +The corresponding SWF buttons are displayed in environments where Flash is +available and `Downloadify` is supported. The easiest solution involves writing +to a Base64 string and passing to the library: + +```js +Downloadify.create(element_id, { + /* the demo includes the other options required by Downloadify */ + filename: "test.xlsx", + data: function() { return XLSX.write(wb, {bookType:"xlsx", type:'base64'}); }, + dataType: 'base64' +}); +``` + +[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx) diff --git a/demos/oldie/base64.min.js b/demos/oldie/base64.min.js new file mode 100644 index 0000000..2ad238a --- /dev/null +++ b/demos/oldie/base64.min.js @@ -0,0 +1 @@ +!function(){function t(t){this.message=t}var r="undefined"!=typeof exports?exports:self,e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";t.prototype=new Error,t.prototype.name="InvalidCharacterError",r.btoa||(r.btoa=function(r){for(var o,n,a=String(r),i=0,c=e,d="";a.charAt(0|i)||(c="=",i%1);d+=c.charAt(63&o>>8-i%1*8)){if(n=a.charCodeAt(i+=.75),n>255)throw new t("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");o=o<<8|n}return d}),r.atob||(r.atob=function(r){var o=String(r).replace(/=+$/,"");if(o.length%4==1)throw new t("'atob' failed: The string to be decoded is not correctly encoded.");for(var n,a,i=0,c=0,d="";a=o.charAt(c++);~a&&(n=i%4?64*n+a:a,i++%4)?d+=String.fromCharCode(255&n>>(-2*i&6)):0)a=e.indexOf(a);return d})}(); diff --git a/demos/oldie/download.png b/demos/oldie/download.png new file mode 100755 index 0000000..d2fabbf Binary files /dev/null and b/demos/oldie/download.png differ diff --git a/demos/oldie/downloadify.min.js b/demos/oldie/downloadify.min.js new file mode 100755 index 0000000..faf490c --- /dev/null +++ b/demos/oldie/downloadify.min.js @@ -0,0 +1,3 @@ +/* Downloadify 0.2 (c) 2009 by Douglas Neiner. Licensed under the MIT license */ +/* See http://github.com/dcneiner/Downloadify for license and more info */ +(function(){Downloadify=window.Downloadify={queue:{},uid:new Date().getTime(),getTextForSave:function(a){var b=Downloadify.queue[a];if(b)return b.getData();return""},getFileNameForSave:function(a){var b=Downloadify.queue[a];if(b)return b.getFilename();return""},getDataTypeForSave:function(a){var b=Downloadify.queue[a];if(b)return b.getDataType();return""},saveComplete:function(a){var b=Downloadify.queue[a];if(b)b.complete();return true},saveCancel:function(a){var b=Downloadify.queue[a];if(b)b.cancel();return true},saveError:function(a){var b=Downloadify.queue[a];if(b)b.error();return true},addToQueue:function(a){Downloadify.queue[a.queue_name]=a},getUID:function(a){if(a.id=="")a.id='downloadify_'+Downloadify.uid++;return a.id}};Downloadify.create=function(a,b){var c=(typeof(a)=="string"?document.getElementById(a):a);return new Downloadify.Container(c,b)};Downloadify.Container=function(d,e){var f=this;f.el=d;f.enabled=true;f.dataCallback=null;f.filenameCallback=null;f.data=null;f.filename=null;var g=function(){f.options=e;if(!f.options.append)f.el.innerHTML="";f.flashContainer=document.createElement('span');f.el.appendChild(f.flashContainer);f.queue_name=Downloadify.getUID(f.flashContainer);if(typeof(f.options.filename)==="function")f.filenameCallback=f.options.filename;else if(f.options.filename)f.filename=f.options.filename;if(typeof(f.options.data)==="function")f.dataCallback=f.options.data;else if(f.options.data)f.data=f.options.data;var a={queue_name:f.queue_name,width:f.options.width,height:f.options.height};var b={allowScriptAccess:'always'};var c={id:f.flashContainer.id,name:f.flashContainer.id};if(f.options.enabled===false)f.enabled=false;if(f.options.transparent===true)b.wmode="transparent";if(f.options.downloadImage)a.downloadImage=f.options.downloadImage;swfobject.embedSWF(f.options.swf,f.flashContainer.id,f.options.width,f.options.height,"10",null,a,b,c);Downloadify.addToQueue(f)};f.enable=function(){var a=document.getElementById(f.flashContainer.id);a.setEnabled(true);f.enabled=true};f.disable=function(){var a=document.getElementById(f.flashContainer.id);a.setEnabled(false);f.enabled=false};f.getData=function(){if(!f.enabled)return"";if(f.dataCallback)return f.dataCallback();else if(f.data)return f.data;else return""};f.getFilename=function(){if(f.filenameCallback)return f.filenameCallback();else if(f.filename)return f.filename;else return""};f.getDataType=function(){if(f.options.dataType)return f.options.dataType;return"string"};f.complete=function(){if(typeof(f.options.onComplete)==="function")f.options.onComplete()};f.cancel=function(){if(typeof(f.options.onCancel)==="function")f.options.onCancel()};f.error=function(){if(typeof(f.options.onError)==="function")f.options.onError()};g()};Downloadify.defaultOptions={swf:'media/downloadify.swf',downloadImage:'images/download.png',width:100,height:30,transparent:true,append:false,dataType:"string"}})();if(typeof(jQuery)!="undefined"){(function($){$.fn.downloadify=function(b){return this.each(function(){b=$.extend({},Downloadify.defaultOptions,b);var a=Downloadify.create(this,b);$(this).data('Downloadify',a)})}})(jQuery)};if(typeof(MooTools)!='undefined'){Element.implement({downloadify:function(a){a=$merge(Downloadify.defaultOptions,a);return this.store('Downloadify',Downloadify.create(this,a))}})}; \ No newline at end of file diff --git a/demos/oldie/downloadify.swf b/demos/oldie/downloadify.swf new file mode 100755 index 0000000..2a2f01c Binary files /dev/null and b/demos/oldie/downloadify.swf differ diff --git a/demos/oldie/index.html b/demos/oldie/index.html new file mode 100644 index 0000000..ab5bf10 --- /dev/null +++ b/demos/oldie/index.html @@ -0,0 +1,122 @@ + + + + + +SheetJS JS-XLSX In-Browser HTML Table Export Demo + + + + + + + + + + + + +
+

SheetJS JS-XLSX In-Browser HTML Table Export Demo

+Compatibility notes: +- Editable table leverages the HTML5 contenteditable feature, supported in most browsers. +- IE6-9 requires ActiveX or Flash to download files. +- iOS Safari file download may not work. This is a known issue. + +Editable Data Table: (click a cell to edit it) +
+
+ +
+
Export it!
+
+ + + + + +
XLSX Excel 2007+ XML
+

+

Flash required for actually downloading the generated file.

+
XLSB Excel 2007+ Binary
+

+

Flash required for actually downloading the generated file.

+
XLS Excel 97-2004 Binary
+

+

Flash required for actually downloading the generated file.

+
ODS
+

+

Flash required for actually downloading the generated file.

+
Flat ODS
+

+

Flash required for actually downloading the generated file.

+
+
Powered by the community version of js-xlsx
+ + + + diff --git a/demos/oldie/shim.min.js b/demos/oldie/shim.min.js new file mode 120000 index 0000000..cfd40cc --- /dev/null +++ b/demos/oldie/shim.min.js @@ -0,0 +1 @@ +../../dist/shim.min.js \ No newline at end of file diff --git a/demos/oldie/swfobject.js b/demos/oldie/swfobject.js new file mode 100755 index 0000000..f9e4489 --- /dev/null +++ b/demos/oldie/swfobject.js @@ -0,0 +1,4 @@ +/* SWFObject v2.2 + is released under the MIT License +*/ +var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab pages/sheetjs.js + cp ../../shim.js static/shim.js next .PHONY: native @@ -24,4 +25,3 @@ android: native ## react-native android sim init: ## set up node_modules and symlink mkdir -p node_modules cd node_modules; if [ ! -e xlsx ]; then ln -s ../../../ xlsx; fi; cd - - if [ ! -e node_modules/file-saver ]; then npm install file-saver; fi diff --git a/demos/react/index.html b/demos/react/index.html index 4d051dc..a6cde0d 100644 --- a/demos/react/index.html +++ b/demos/react/index.html @@ -9,8 +9,8 @@ + - diff --git a/demos/react/nexthdr.js b/demos/react/nexthdr.js index 18594af..4224974 100644 --- a/demos/react/nexthdr.js +++ b/demos/react/nexthdr.js @@ -1,3 +1,2 @@ /* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ import XLSX from 'xlsx'; -import { saveAs } from 'file-saver'; diff --git a/demos/react/pages/index.js b/demos/react/pages/index.js index 6935cd9..0ae8f28 100644 --- a/demos/react/pages/index.js +++ b/demos/react/pages/index.js @@ -6,6 +6,7 @@ export default () => ( SheetJS React Demo + diff --git a/demos/react/preact.html b/demos/react/preact.html index 3e9bb6a..b08d90d 100644 --- a/demos/react/preact.html +++ b/demos/react/preact.html @@ -4,20 +4,20 @@ -SheetJS React Demo +SheetJS Preact Demo - - + +
-

SheetJS React Demo

+

SheetJS Preact Demo


Source Code Repo
Issues? Something look weird? Click here and report an issue

diff --git a/demos/react/sheetjs.jsx b/demos/react/sheetjs.jsx index c8216b9..4da567f 100644 --- a/demos/react/sheetjs.jsx +++ b/demos/react/sheetjs.jsx @@ -18,10 +18,11 @@ class SheetJSApp extends React.Component { handleFile(file/*:File*/) { /* Boilerplate to set up FileReader */ const reader = new FileReader(); + const rABS = !!reader.readAsBinaryString; reader.onload = (e) => { /* Parse data */ const bstr = e.target.result; - const wb = XLSX.read(bstr, {type:'binary'}); + const wb = XLSX.read(bstr, {type:rABS ? 'binary' : 'array'}); /* Get first worksheet */ const wsname = wb.SheetNames[0]; const ws = wb.Sheets[wsname]; @@ -30,17 +31,15 @@ class SheetJSApp extends React.Component { /* Update state */ this.setState({ data: data, cols: make_cols(ws['!ref']) }); }; - reader.readAsBinaryString(file); + if(rABS) reader.readAsBinaryString(file); else reader.readAsArrayBuffer(file); }; exportFile() { /* convert state to workbook */ const ws = XLSX.utils.aoa_to_sheet(this.state.data); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "SheetJS"); - /* generate XLSX file */ - const wbout = XLSX.write(wb, {type:"array", bookType:"xlsx"}); - /* send to client */ - saveAs(new Blob([wbout],{type:"application/octet-stream"}), "sheetjs.xlsx"); + /* generate XLSX file and send to client */ + XLSX.writeFile(wb, "sheetjs.xlsx") }; render() { return ( @@ -137,4 +136,8 @@ const SheetJSFT = [ ].map(function(x) { return "." + x; }).join(","); /* generate an array of column objects */ -const make_cols = refstr => Array(XLSX.utils.decode_range(refstr).e.c + 1).fill(0).map((x,i) => ({name:XLSX.utils.encode_col(i), key:i})); +const make_cols = refstr => { + let o = [], C = XLSX.utils.decode_range(refstr).e.c + 1; + for(var i = 0; i < C; ++i) o[i] = {name:XLSX.utils.encode_col(i), key:i} + return o; +}; diff --git a/demos/server/README.md b/demos/server/README.md index 2930e57..99308bf 100644 --- a/demos/server/README.md +++ b/demos/server/README.md @@ -33,6 +33,23 @@ var buf = fs.readFileSync("sheetjs.xlsx"); var wb = XLSX.read(buf, {type:'buffer'}); ``` +### Responding to Form Uploads + +Using `formidable`, files uploaded to forms are stored to temporary files that +can be read with `readFile`: + +```js +/* within the server callback function(request, response) { */ +var form = new formidable.IncomingForm(); +form.parse(req, function(err, fields, files) { + var f = files[Object.keys(files)[0]]; + var workbook = XLSX.readFile(f.path); + /* DO SOMETHING WITH workbook HERE */ +}); +``` + +The `node.js` demo shows a plain HTTP server that accepts file uploads and +converts data to requested output format. ### Example servers diff --git a/demos/server/node.js b/demos/server/node.js new file mode 100644 index 0000000..7b54d37 --- /dev/null +++ b/demos/server/node.js @@ -0,0 +1,84 @@ +/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ + +var http = require('http'); +var XLSX = require('xlsx'); +var formidable = require('formidable'); +var html = ""; +var PORT = 3000; + +var extmap = {}; + +var server = http.createServer(function(req, res) { + if(req.method !== 'POST') return res.end(html); + var form = new formidable.IncomingForm(); + form.parse(req, function(err, fields, files) { + var f = files[Object.keys(files)[0]]; + var wb = XLSX.readFile(f.path); + var ext = (fields.bookType || "xlsx").toLowerCase(); + res.setHeader('Content-Disposition', 'attachment; filename="download.' + (extmap[ext] || ext) + '";'); + res.end(XLSX.write(wb, {type:"buffer", bookType:ext})); + }); +}).listen(PORT); + +html = [ +'
',
+'

SheetJS File Converter

', +'Upload a file to convert the contents to another format.', +'', +'Form Fields:', +'- bookType: output format type (defaults to "XLSX")', +'- basename: basename for output file (defaults to "download")', +'', +'
', +'', +'', +'', +'
', +'', +'Form code:', +'<form method="POST" enctype="multipart/form-data" action="/">', +'<input type="file" id="file" name="file"/>', +'<select name="bookType">', +'<!-- options here -->', +'</select>', +'<input type="submit" value="Submit Form">', +'</form>', +'', +'fetch Code:', +'var blob = new Blob("1,2,3\\n4,5,6".split("")); // original file', +'var fd = new FormData();', +'fd.set("data", blob, "foo.bar");', +'fd.set("bookType", "xlsb");', +'var res = await fetch("/", {method:"POST", body:fd});', +'var data = await res.arrayBuffer();', +'
' +].join("\n"); + +extmap = { + "biff2" : "xls", + "biff5" : "xls", + "biff8" : "xls", + "xlml" : "xls" +}; +console.log('listening on port ' + PORT); diff --git a/demos/typescript/Makefile b/demos/typescript/Makefile index 3ba92c6..13c67d4 100644 --- a/demos/typescript/Makefile +++ b/demos/typescript/Makefile @@ -1,3 +1,9 @@ .PHONY: all all: npm run build + +.PHONY: init +init: + mkdir -p node_modules + npm install typescript + cd node_modules; ln -s ../../../ xlsx; cd - diff --git a/demos/vue/README.md b/demos/vue/README.md index 4380d50..78c90fe 100644 --- a/demos/vue/README.md +++ b/demos/vue/README.md @@ -101,8 +101,8 @@ The scripts should be treated as external resources in `nuxt.config.js`: module.exports = { head: { script: [ - { src: "https://unpkg.com/xlsx/dist/xlsx.full.min.js" }, // library - { src: "https://unpkg.com/file-saver/FileSaver.js" } // saveAs shim + { src: "https://unpkg.com/xlsx/dist/shim.min.js" }, + { src: "https://unpkg.com/xlsx/dist/xlsx.full.min.js" } ] } }; diff --git a/demos/vue/SheetJS-vue.js b/demos/vue/SheetJS-vue.js index 0e6c546..e508434 100644 --- a/demos/vue/SheetJS-vue.js +++ b/demos/vue/SheetJS-vue.js @@ -55,11 +55,8 @@ Vue.component('html-preview', { onexport: function(evt) { /* generate workbook object from table */ var wb = XLSX.utils.table_to_book(document.getElementById('out-table')); - /* get binary string as output */ - var wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); - - /* force a download */ - saveAs(new Blob([wbout], { type: 'application/octet-stream' }), "sheetjs.xlsx"); + /* generate file and force a download*/ + XLSX.writeFile(wb, "sheetjs.xlsx"); } } }); diff --git a/demos/vue/index.html b/demos/vue/index.html index 3957bfe..67cdaa6 100644 --- a/demos/vue/index.html +++ b/demos/vue/index.html @@ -7,8 +7,8 @@ - - + + diff --git a/demos/vue/nuxt.config.js b/demos/vue/nuxt.config.js index b02cf96..9fddd9c 100644 --- a/demos/vue/nuxt.config.js +++ b/demos/vue/nuxt.config.js @@ -1,9 +1,10 @@ module.exports = { head: { script: [ - // { src: "https://unpkg.com/xlsx/dist/xlsx.full.min.js" }, // CDN - { src: "xlsx.full.min.js" }, // development - { src: "https://unpkg.com/file-saver/FileSaver.js" } + // { src: "https://unpkg.com/xlsx/dist/shim.min.js" }, // CDN + // { src: "https://unpkg.com/xlsx/dist/xlsx.full.min.js" } // CDN + { src: "shim.js" }, // development + { src: "xlsx.full.min.js" } // development ] } }; diff --git a/demos/vue/pages/index.vue b/demos/vue/pages/index.vue index 14537d4..0dedc48 100644 --- a/demos/vue/pages/index.vue +++ b/demos/vue/pages/index.vue @@ -66,10 +66,8 @@ export default { const ws = XLSX.utils.aoa_to_sheet(this.data); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "SheetJS"); - /* generate X file */ - const wbout = XLSX.write(wb, {type:"array", bookType:"xlsx"}); - /* send to client */ - saveAs(new Blob([wbout],{type:"application/octet-stream"}), "sheetjs.xlsx"); + /* generate file and send to client */ + XLSX.writeFile(wb, "sheetjs.xlsx"); }, _file(file) { /* Boilerplate to set up FileReader */ diff --git a/demos/vue/shim.js b/demos/vue/shim.js new file mode 120000 index 0000000..7ec5819 --- /dev/null +++ b/demos/vue/shim.js @@ -0,0 +1 @@ +../../shim.js \ No newline at end of file diff --git a/demos/vue/static/shim.js b/demos/vue/static/shim.js new file mode 120000 index 0000000..d672390 --- /dev/null +++ b/demos/vue/static/shim.js @@ -0,0 +1 @@ +../shim.js \ No newline at end of file diff --git a/demos/xhr/README.md b/demos/xhr/README.md index aaa80dd..cea1924 100644 --- a/demos/xhr/README.md +++ b/demos/xhr/README.md @@ -87,7 +87,7 @@ The upload portion only differs in the actual request command: superagent.post("/upload").send(fd); ``` -### superagent Wrapper Library +### axios Wrapper Library The `axios` library presents a Promise interface. The axios demo uses a single promise, but for production deployments it may make sense to separate parsing: diff --git a/dist/shim.min.js b/dist/shim.min.js index ef31ade..e240a7f 100644 --- a/dist/shim.min.js +++ b/dist/shim.min.js @@ -1,2 +1,2 @@ /* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ -if(!Object.keys){Object.keys=function(){var r=Object.prototype.hasOwnProperty,t=!{toString:null}.propertyIsEnumerable("toString"),e=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],n=e.length;return function(i){if(typeof i!=="object"&&typeof i!=="function"||i===null)throw new TypeError("Object.keys called on non-object");var o=[];for(var f in i){if(r.call(i,f))o.push(f)}if(t){for(var a=0;a>>0;if(typeof r!="function")throw new TypeError;var n=[];var i=arguments[1];for(var o=0;o>>0;if(typeof r!=="function")throw new TypeError;var n=arguments.length>=2?arguments[1]:void 0;for(var i=0;i>>0;if(typeof r!=="function"){throw new TypeError(r+" is not a function")}if(t){e=t}n=new Array(f);i=0;while(i>>0;t=+t||0;if(Math.abs(t)===Infinity){t=0}if(t<0){t+=e;if(t<0){t=0}}for(;t=e||r>=t){return new ArrayBuffer(0)}var n=Math.min(e-r,t-r);var i=new ArrayBuffer(n);var o=new Uint8Array(i);o.set(new Uint8Array(this,r,n));return i}}(function(){var r=typeof exports!="undefined"?exports:typeof self!="undefined"?self:$.global;var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";function e(r){this.message=r}e.prototype=new Error;e.prototype.name="InvalidCharacterError";r.btoa||(r.btoa=function(r){var n=String(r);for(var i,o,f=0,a=t,s="";n.charAt(f|0)||(a="=",f%1);s+=a.charAt(63&i>>8-f%1*8)){o=n.charCodeAt(f+=3/4);if(o>255){throw new e("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.")}i=i<<8|o}return s});r.atob||(r.atob=function(r){var n=String(r).replace(/[=]+$/,"");if(n.length%4==1){throw new e("'atob' failed: The string to be decoded is not correctly encoded.")}for(var i=0,o,f,a=0,s="";f=n.charAt(a++);~f&&(o=i%4?o*64+f:f,i++%4)?s+=String.fromCharCode(255&o>>(-2*i&6)):0){f=t.indexOf(f)}return s})})();if(!Date.prototype.toISOString){(function(){function r(r){if(r<10){return"0"+r}return r}Date.prototype.toISOString=function(){return this.getUTCFullYear()+"-"+r(this.getUTCMonth()+1)+"-"+r(this.getUTCDate())+"T"+r(this.getUTCHours())+":"+r(this.getUTCMinutes())+":"+r(this.getUTCSeconds())+"."+(this.getUTCMilliseconds()/1e3).toFixed(3).slice(2,5)+"Z"}})()}if(typeof Uint8Array!=="undefined"&&!Uint8Array.prototype.slice)Uint8Array.prototype.slice=function(r,t){if(r<0){r+=this.length;if(r<0)r=0}if(r>=this.length)return new Uint8Array(0);if(t==null)t=this.length;if(t<0){t+=this.length;if(t<0)t=0}if(t>this.length)t=this.length;var e=new Uint8Array(t-r);while(r<=--t)e[t-r]=this[t];return e}; \ No newline at end of file +if(!Object.keys){Object.keys=function(){var t=Object.prototype.hasOwnProperty,r=!{toString:null}.propertyIsEnumerable("toString"),e=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],n=e.length;return function(i){if(typeof i!=="object"&&typeof i!=="function"||i===null)throw new TypeError("Object.keys called on non-object");var o=[];for(var a in i){if(t.call(i,a))o.push(a)}if(r){for(var f=0;f>>0;if(typeof t!="function")throw new TypeError;var n=[];var i=arguments[1];for(var o=0;o>>0;if(typeof t!=="function")throw new TypeError;var n=arguments.length>=2?arguments[1]:void 0;for(var i=0;i>>0;if(typeof t!=="function"){throw new TypeError(t+" is not a function")}if(r){e=r}n=new Array(a);i=0;while(i>>0;r=+r||0;if(Math.abs(r)===Infinity){r=0}if(r<0){r+=e;if(r<0){r=0}}for(;r=e||t>=r){return new ArrayBuffer(0)}var n=Math.min(e-t,r-t);var i=new ArrayBuffer(n);var o=new Uint8Array(i);o.set(new Uint8Array(this,t,n));return i}}(function(){var t=typeof exports!="undefined"?exports:typeof self!="undefined"?self:$.global;var r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";function e(t){this.message=t}e.prototype=new Error;e.prototype.name="InvalidCharacterError";t.btoa||(t.btoa=function(t){var n=String(t);for(var i,o,a=0,f=r,l="";n.charAt(a|0)||(f="=",a%1);l+=f.charAt(63&i>>8-a%1*8)){o=n.charCodeAt(a+=3/4);if(o>255){throw new e("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.")}i=i<<8|o}return l});t.atob||(t.atob=function(t){var n=String(t).replace(/[=]+$/,"");if(n.length%4==1){throw new e("'atob' failed: The string to be decoded is not correctly encoded.")}for(var i=0,o,a,f=0,l="";a=n.charAt(f++);~a&&(o=i%4?o*64+a:a,i++%4)?l+=String.fromCharCode(255&o>>(-2*i&6)):0){a=r.indexOf(a)}return l})})();if(!Date.prototype.toISOString){(function(){function t(t){if(t<10){return"0"+t}return t}Date.prototype.toISOString=function(){return this.getUTCFullYear()+"-"+t(this.getUTCMonth()+1)+"-"+t(this.getUTCDate())+"T"+t(this.getUTCHours())+":"+t(this.getUTCMinutes())+":"+t(this.getUTCSeconds())+"."+(this.getUTCMilliseconds()/1e3).toFixed(3).slice(2,5)+"Z"}})()}if(typeof Uint8Array!=="undefined"&&!Uint8Array.prototype.slice)Uint8Array.prototype.slice=function(t,r){if(t<0){t+=this.length;if(t<0)t=0}if(t>=this.length)return new Uint8Array(0);if(r==null)r=this.length;if(r<0){r+=this.length;if(r<0)r=0}if(r>this.length)r=this.length;var e=new Uint8Array(r-t);while(t<=--r)e[r-t]=this[r];return e};var IE_SaveFile=function(){try{if(typeof IE_SaveFile_Impl=="undefined")document.write([' + + + diff --git a/tests/write.js b/tests/write.js index dd1c28e..abb48b8 100644 --- a/tests/write.js +++ b/tests/write.js @@ -1,7 +1,9 @@ /* writing feature test -- look for TEST: in comments */ /* vim: set ts=2 ft=javascript: */ -var ext = !!process.argv[2]; +if(typeof console === 'undefined') console = {log: function(){}}; + +var ext = typeof process !== 'undefined' && !!process.argv[2]; /* original data */ var data = [ @@ -191,9 +193,21 @@ var filenames = [ ['sheetjs.prn'] ]; +var OUT = ["base64", "binary", "string", "array"]; +if(typeof Buffer !== 'undefined') OUT.push("buffer"); filenames.forEach(function(r) { - /* write file */ - XLSX.writeFile(wb, r[0], r[1]); - /* test by reading back files */ - XLSX.readFile(r[0]); + /* write file */ + XLSX.writeFile(wb, r[0], r[1]); + /* test by reading back files */ + if(typeof process !== 'undefined') XLSX.readFile(r[0]); + + var ext = r[1] && r[1].bookType || r[0].split(".")[1]; + ext = {"htm":"html"}[ext] || ext; + OUT.forEach(function(type) { + if(type == "string" && ["xlsx", "xlsm", "xlsb", "xlam", "biff8", "biff5", "xla", "ods", "dbf"].indexOf(ext) > -1) return; + if(type == "array" && ["xlsx", "xlsm", "xlsb", "xlam", "ods"].indexOf(ext) > -1 && typeof Uint8Array === 'undefined') return; + var datout = XLSX.write(wb, {type: type, bookType: ext, sheet:r[1] && r[1].sheet || null}); + XLSX.read(datout, {type:type}); + if(type == "array") console.log(ext, datout); + }); }); diff --git a/types/index.d.ts b/types/index.d.ts index 4d65b20..5417128 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -10,11 +10,11 @@ export const SSF: any; /** CFB Library */ export const CFB: any; -/** Attempts to read filename and parse */ +/** NODE ONLY! Attempts to read filename and parse */ export function readFile(filename: string, opts?: ParsingOptions): WorkBook; /** Attempts to parse data */ export function read(data: any, opts?: ParsingOptions): WorkBook; -/** NODE ONLY! Attempts to write workbook data to filename */ +/** Attempts to write or download workbook data to file */ export function writeFile(data: WorkBook, filename: string, opts?: WritingOptions): any; /** Attempts to write the workbook data */ export function write(data: WorkBook, opts?: WritingOptions): any; @@ -602,6 +602,9 @@ export interface OriginOption { } export interface Sheet2HTMLOpts { + /** TABLE element id attribute */ + id?: string; + /** Add contenteditable to every cell */ editable?: boolean; diff --git a/xlsx.flow.js b/xlsx.flow.js index 6f8a053..01894f0 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -4,7 +4,7 @@ /*global global, exports, module, require:false, process:false, Buffer:false, ArrayBuffer:false */ var XLSX = {}; (function make_xlsx(XLSX){ -XLSX.version = '0.11.18'; +XLSX.version = '0.11.19'; var current_codepage = 1200, current_ansi = 1252; /*:: declare var cptable:any; */ /*global cptable:true */ @@ -141,11 +141,16 @@ function s2ab(s/*:string*/) { return buf; } -function arr2str(data/*:any*/)/*:string*/ { +function a2s(data/*:any*/)/*:string*/ { if(Array.isArray(data)) return data.map(_chr).join(""); var o/*:Array*/ = []; for(var i = 0; i < data.length; ++i) o[i] = _chr(data[i]); return o.join(""); } +function a2u(data/*:Array*/)/*:Uint8Array*/ { + if(typeof Uint8Array === 'undefined') throw new Error("Unsupported"); + return new Uint8Array(data); +} + function ab2a(data/*:ArrayBuffer|Uint8Array*/)/*:Array*/ { if(typeof ArrayBuffer == 'undefined') throw new Error("Unsupported"); if(data instanceof ArrayBuffer) return ab2a(new Uint8Array(data)); @@ -1853,6 +1858,43 @@ return exports; })(); if(typeof require !== 'undefined' && typeof module !== 'undefined' && typeof DO_NOT_EXPORT_CFB === 'undefined') { module.exports = CFB; } +var _fs; +if(typeof require !== 'undefined') try { _fs = require('fs'); } catch(e) {} + +/* normalize data for blob ctor */ +function blobify(data) { + if(typeof data === "string") return s2ab(data); + if(Array.isArray(data)) return a2u(data); + return data; +} +/* write or download file */ +function write_dl(fname/*:string*/, payload/*:any*/, enc/*:?string*/) { + /*global IE_SaveFile, Blob, navigator, saveAs, URL, document */ + 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; */ + if(typeof IE_SaveFile !== 'undefined') return IE_SaveFile(data, fname); + if(typeof Blob !== 'undefined') { + var blob = new Blob([blobify(data)], {type:"application/octet-stream"}); + /*:: declare var navigator: any; */ + if(typeof navigator !== 'undefined' && navigator.msSaveBlob) return navigator.msSaveBlob(blob, fname); + /*:: declare var saveAs: any; */ + if(typeof saveAs !== 'undefined') return saveAs(blob, fname); + if(typeof URL !== 'undefined' && typeof document !== 'undefined' && document.createElement && URL.createObjectURL) { + 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); + if(URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000); + return url; + } + } + } + throw new Error("cannot initiate download"); +} + function keys(o/*:any*/)/*:Array*/ { return Object.keys(o); } function evert_key(obj/*:any*/, key/*:string*/)/*:EvertType*/ { @@ -2045,14 +2087,13 @@ function getzipstr(zip, file/*:string*/, safe/*:?boolean*/)/*:?string*/ { try { return getzipstr(zip, file); } catch(e) { return null; } } -var _fs, jszip; +var jszip; /*:: declare var JSZip:any; */ /*global JSZip:true */ if(typeof JSZip !== 'undefined') jszip = JSZip; -if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { +if(typeof exports !== 'undefined') { + if(typeof module !== 'undefined' && module.exports) { if(typeof jszip === 'undefined') jszip = require('./jszip.js'); - try { _fs = require('fs'); } catch(e) { } } } @@ -13378,6 +13419,7 @@ function check_wb_names(N) { } function check_wb(wb) { if(!wb || !wb.SheetNames || !wb.Sheets) throw new Error("Invalid Workbook"); + if(!wb.SheetNames.length) throw new Error("Workbook is empty"); check_wb_names(wb.SheetNames); /* TODO: validate workbook */ } @@ -14821,7 +14863,7 @@ function parse_xlml(data/*:RawBytes|string*/, opts)/*:Workbook*/ { switch(opts.type||"base64") { case "base64": return parse_xlml_xml(Base64.decode(data), opts); case "binary": case "buffer": case "file": return parse_xlml_xml(data, opts); - case "array": return parse_xlml_xml(arr2str(data), opts); + case "array": return parse_xlml_xml(a2s(data), opts); } /*:: throw new Error("unsupported type " + opts.type); */ } @@ -17641,9 +17683,9 @@ var HTML_ = (function() { var preamble = ""; return preamble + oo.join("") + ""; } - function make_html_preamble(/*::ws:Worksheet, R:Range, o:Sheet2HTMLOpts*/)/*:string*/ { + function make_html_preamble(ws/*:Worksheet*/, R/*:Range*/, o/*:Sheet2HTMLOpts*/)/*:string*/ { var out/*:Array*/ = []; - return out.join("") + ''; + return out.join("") + ''; } var _BEGIN = 'SheetJS Table Export'; var _END = ''; @@ -19010,7 +19052,6 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ { case 0x0A: case 0x0D: case 0x20: return read_plaintext_raw(d, o); } if(n[2] <= 12 && n[3] <= 31) return DBF.to_workbook(d, o); - if(0x20>n[0]||n[0]>0x7F) throw new Error("Unsupported file " + n.join("|")); return read_prn(data, d, o, str); } @@ -19026,12 +19067,12 @@ function write_zip_type(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:any*/ { switch(o.type) { case "base64": oopts.type = "base64"; break; case "binary": oopts.type = "string"; break; - case "string": throw new Error("'string' output type invalid for '" + o.bookType + ' files'); + case "string": throw new Error("'string' output type invalid for '" + o.bookType + "' files"); case "buffer": - case "file": oopts.type = "nodebuffer"; break; + case "file": oopts.type = has_buf ? "nodebuffer" : "string"; break; default: throw new Error("Unrecognized type " + o.type); } - if(o.type === "file") return _fs.writeFileSync(o.file, z.generate(oopts)); + if(o.type === "file") return write_dl(o.file, z.generate(oopts)); var out = z.generate(oopts); // $FlowIgnore return o.type == "string" ? utf8read(out) : out; @@ -19043,8 +19084,8 @@ function write_cfb_type(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:any*/ { switch(o.type) { case "base64": case "binary": break; case "buffer": case "array": o.type = ""; break; - case "file": return _fs.writeFileSync(o.file, CFB.write(cfb, {type:'buffer'})); - case "string": throw new Error("'string' output type invalid for '" + o.bookType + ' files'); + case "file": return write_dl(o.file, CFB.write(cfb, {type:has_buf ? 'buffer' : ""})); + case "string": throw new Error("'string' output type invalid for '" + o.bookType + "' files"); default: throw new Error("Unrecognized type " + o.type); } return CFB.write(cfb, o); @@ -19057,7 +19098,7 @@ function write_string_type(out/*:string*/, opts/*:WriteOpts*/, bom/*:?string*/)/ case "base64": return Base64.encode(utf8write(o)); case "binary": return utf8write(o); case "string": return out; - case "file": return _fs.writeFileSync(opts.file, o, 'utf8'); + case "file": return write_dl(opts.file, o, 'utf8'); case "buffer": { if(has_buf) return new Buffer(o, 'utf8'); else return write_string_type(o, {type:'binary'}).split("").map(function(c) { return c.charCodeAt(0); }); @@ -19071,7 +19112,7 @@ function write_stxt_type(out/*:string*/, opts/*:WriteOpts*/)/*:any*/ { case "base64": return Base64.encode(out); case "binary": return out; case "string": return out; /* override in sheet_to_txt */ - case "file": return _fs.writeFileSync(opts.file, out, 'binary'); + case "file": return write_dl(opts.file, out, 'binary'); case "buffer": { if(has_buf) return new Buffer(out, 'binary'); else return out.split("").map(function(c) { return c.charCodeAt(0); }); @@ -19090,7 +19131,7 @@ function write_binary_type(out, opts/*:WriteOpts*/)/*:any*/ { // $FlowIgnore for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]); return opts.type == 'base64' ? Base64.encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr; - case "file": return _fs.writeFileSync(opts.file, out); + case "file": return write_dl(opts.file, out); case "buffer": return out; default: throw new Error("Unrecognized type " + opts.type); } @@ -19293,7 +19334,7 @@ function sheet_to_txt(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) { var s = sheet_to_csv(sheet, opts); if(typeof cptable == 'undefined' || opts.type == 'string') return s; var o = cptable.utils.encode(1200, s, 'str'); - return "\xff\xfe" + o; + return String.fromCharCode(255) + String.fromCharCode(254) + o; } function sheet_to_formulae(sheet/*:Worksheet*/)/*:Array*/ { diff --git a/xlsx.js b/xlsx.js index 6e53d16..893da3f 100644 --- a/xlsx.js +++ b/xlsx.js @@ -4,7 +4,7 @@ /*global global, exports, module, require:false, process:false, Buffer:false, ArrayBuffer:false */ var XLSX = {}; (function make_xlsx(XLSX){ -XLSX.version = '0.11.18'; +XLSX.version = '0.11.19'; var current_codepage = 1200, current_ansi = 1252; /*global cptable:true */ if(typeof module !== "undefined" && typeof require !== 'undefined') { @@ -140,11 +140,16 @@ function s2ab(s) { return buf; } -function arr2str(data) { +function a2s(data) { if(Array.isArray(data)) return data.map(_chr).join(""); var o = []; for(var i = 0; i < data.length; ++i) o[i] = _chr(data[i]); return o.join(""); } +function a2u(data) { + if(typeof Uint8Array === 'undefined') throw new Error("Unsupported"); + return new Uint8Array(data); +} + function ab2a(data) { if(typeof ArrayBuffer == 'undefined') throw new Error("Unsupported"); if(data instanceof ArrayBuffer) return ab2a(new Uint8Array(data)); @@ -1782,6 +1787,39 @@ return exports; })(); if(typeof require !== 'undefined' && typeof module !== 'undefined' && typeof DO_NOT_EXPORT_CFB === 'undefined') { module.exports = CFB; } +var _fs; +if(typeof require !== 'undefined') try { _fs = require('fs'); } catch(e) {} + +/* normalize data for blob ctor */ +function blobify(data) { + if(typeof data === "string") return s2ab(data); + if(Array.isArray(data)) return a2u(data); + return data; +} +/* write or download file */ +function write_dl(fname, payload, enc) { + /*global IE_SaveFile, Blob, navigator, saveAs, URL, document */ + if(typeof _fs !== 'undefined' && _fs.writeFileSync) return enc ? _fs.writeFileSync(fname, payload, enc) : _fs.writeFileSync(fname, payload); + var data = (enc == "utf8") ? utf8write(payload) : payload; +if(typeof IE_SaveFile !== 'undefined') return IE_SaveFile(data, fname); + if(typeof Blob !== 'undefined') { + var blob = new Blob([blobify(data)], {type:"application/octet-stream"}); +if(typeof navigator !== 'undefined' && navigator.msSaveBlob) return navigator.msSaveBlob(blob, fname); +if(typeof saveAs !== 'undefined') return saveAs(blob, fname); + if(typeof URL !== 'undefined' && typeof document !== 'undefined' && document.createElement && URL.createObjectURL) { + var a = document.createElement("a"); + if(a.download != null) { + var url = URL.createObjectURL(blob); +a.download = fname; a.href = url; document.body.appendChild(a); a.click(); +document.body.removeChild(a); + if(URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000); + return url; + } + } + } + throw new Error("cannot initiate download"); +} + function keys(o) { return Object.keys(o); } function evert_key(obj, key) { @@ -1973,13 +2011,12 @@ function getzipstr(zip, file, safe) { try { return getzipstr(zip, file); } catch(e) { return null; } } -var _fs, jszip; +var jszip; /*global JSZip:true */ if(typeof JSZip !== 'undefined') jszip = JSZip; -if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { +if(typeof exports !== 'undefined') { + if(typeof module !== 'undefined' && module.exports) { if(typeof jszip === 'undefined') jszip = require('./jszip.js'); - try { _fs = require('fs'); } catch(e) { } } } @@ -13286,6 +13323,7 @@ function check_wb_names(N) { } function check_wb(wb) { if(!wb || !wb.SheetNames || !wb.Sheets) throw new Error("Invalid Workbook"); + if(!wb.SheetNames.length) throw new Error("Workbook is empty"); check_wb_names(wb.SheetNames); /* TODO: validate workbook */ } @@ -14723,7 +14761,7 @@ function parse_xlml(data, opts) { switch(opts.type||"base64") { case "base64": return parse_xlml_xml(Base64.decode(data), opts); case "binary": case "buffer": case "file": return parse_xlml_xml(data, opts); - case "array": return parse_xlml_xml(arr2str(data), opts); + case "array": return parse_xlml_xml(a2s(data), opts); } } @@ -17536,9 +17574,9 @@ var HTML_ = (function() { var preamble = ""; return preamble + oo.join("") + ""; } - function make_html_preamble() { + function make_html_preamble(ws, R, o) { var out = []; - return out.join("") + '
'; + return out.join("") + ''; } var _BEGIN = 'SheetJS Table Export'; var _END = ''; @@ -18899,7 +18937,6 @@ function readSync(data, opts) { case 0x0A: case 0x0D: case 0x20: return read_plaintext_raw(d, o); } if(n[2] <= 12 && n[3] <= 31) return DBF.to_workbook(d, o); - if(0x20>n[0]||n[0]>0x7F) throw new Error("Unsupported file " + n.join("|")); return read_prn(data, d, o, str); } @@ -18915,12 +18952,12 @@ function write_zip_type(wb, opts) { switch(o.type) { case "base64": oopts.type = "base64"; break; case "binary": oopts.type = "string"; break; - case "string": throw new Error("'string' output type invalid for '" + o.bookType + ' files'); + case "string": throw new Error("'string' output type invalid for '" + o.bookType + "' files"); case "buffer": - case "file": oopts.type = "nodebuffer"; break; + case "file": oopts.type = has_buf ? "nodebuffer" : "string"; break; default: throw new Error("Unrecognized type " + o.type); } - if(o.type === "file") return _fs.writeFileSync(o.file, z.generate(oopts)); + if(o.type === "file") return write_dl(o.file, z.generate(oopts)); var out = z.generate(oopts); // $FlowIgnore return o.type == "string" ? utf8read(out) : out; @@ -18932,8 +18969,8 @@ function write_cfb_type(wb, opts) { switch(o.type) { case "base64": case "binary": break; case "buffer": case "array": o.type = ""; break; - case "file": return _fs.writeFileSync(o.file, CFB.write(cfb, {type:'buffer'})); - case "string": throw new Error("'string' output type invalid for '" + o.bookType + ' files'); + case "file": return write_dl(o.file, CFB.write(cfb, {type:has_buf ? 'buffer' : ""})); + case "string": throw new Error("'string' output type invalid for '" + o.bookType + "' files"); default: throw new Error("Unrecognized type " + o.type); } return CFB.write(cfb, o); @@ -18946,7 +18983,7 @@ function write_string_type(out, opts, bom) { case "base64": return Base64.encode(utf8write(o)); case "binary": return utf8write(o); case "string": return out; - case "file": return _fs.writeFileSync(opts.file, o, 'utf8'); + case "file": return write_dl(opts.file, o, 'utf8'); case "buffer": { if(has_buf) return new Buffer(o, 'utf8'); else return write_string_type(o, {type:'binary'}).split("").map(function(c) { return c.charCodeAt(0); }); @@ -18960,7 +18997,7 @@ function write_stxt_type(out, opts) { case "base64": return Base64.encode(out); case "binary": return out; case "string": return out; /* override in sheet_to_txt */ - case "file": return _fs.writeFileSync(opts.file, out, 'binary'); + case "file": return write_dl(opts.file, out, 'binary'); case "buffer": { if(has_buf) return new Buffer(out, 'binary'); else return out.split("").map(function(c) { return c.charCodeAt(0); }); @@ -18979,7 +19016,7 @@ function write_binary_type(out, opts) { // $FlowIgnore for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]); return opts.type == 'base64' ? Base64.encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr; - case "file": return _fs.writeFileSync(opts.file, out); + case "file": return write_dl(opts.file, out); case "buffer": return out; default: throw new Error("Unrecognized type " + opts.type); } @@ -19182,7 +19219,7 @@ function sheet_to_txt(sheet, opts) { var s = sheet_to_csv(sheet, opts); if(typeof cptable == 'undefined' || opts.type == 'string') return s; var o = cptable.utils.encode(1200, s, 'str'); - return "\xff\xfe" + o; + return String.fromCharCode(255) + String.fromCharCode(254) + o; } function sheet_to_formulae(sheet) {