version bump 0.8.2: ODS and cleanup

- README and example cleanup
- basic XLSB and ODS write support
- flow typecheck for ODS file
  Note: xlsx.js flow fails: https://github.com/facebook/flow/issues/380
- exposed jszip compression (fixes #220, closes #284)

README issues:

|  id  | author         | comment                                      |
|-----:|:---------------|:---------------------------------------------|
| #202 | @sao93859      | closes #202                                  |
| #211 | @alexanderchan | closes #211 corrected examples               |
| #327 | @cskaandorp    | changed saveAs example to match write tests  |
| #424 | @dskrvk        | added note about s2roa h/t @LeonardoPatignio |
| #496 | @jimmywarting  | closes #496 adapted rABS examples with rAAS  |

ODS file format issues:

|  id  | author         | comment                                      |
|-----:|:---------------|:---------------------------------------------|
| #148 | @user4815162342| closes #148 h/t @ziacik                      |
| #166 | @paulproteus   | closes #166 rudimentary ODS write support    |
| #177 | @ziacik        | closes #177                                  |
| #179 | @ziacik        | closes #179 use JSON when available          |
| #317 | @ziacik        | closes #317                                  |
| #328 | @think01       | closes #328                                  |
| #383 | @mdamt         | closes #383 duplicate cells should be copied |
| #430 | @RB-Lab        | closes #430                                  |
| #546 | @lgodard       | closes #546 thanks to other changes          |
This commit is contained in:
SheetJS 2017-02-03 15:50:45 -05:00
parent 2a756fffcc
commit 86d6a093f0
66 changed files with 13914 additions and 298 deletions

@ -11,8 +11,21 @@
.*/demo/browser.js
.*/shim.js
.*/odsbits/.*
.*/ods.js
.*/xlsx.js
.*/xlsxworker.js
.*/xlsxworker1.js
.*/xlsxworker2.js
.*/jszip.js
.*/tests/.*
.*/xlsx.flow.js
[include]
xlsx.js
xlsxworker.flow.js
xlsxworker1.flow.js
xlsxworker2.flow.js
ods.flow.js
.*/bin/.*.njs
.*/demo/browser.flow.js
@ -23,4 +36,4 @@ misc/flowdeps.js
[options]
module.file_ext=.js
module.file_ext=.njs
module.ignore_non_literal_requires=true

3
.gitignore vendored

@ -13,8 +13,7 @@ tmp
*.xlsb
*.xlsm
*.xlsx
*.xlsm
*.xlsb
*.ods
*.xml
*.htm
*.html

@ -14,6 +14,7 @@ tmp
*.xlsb
*.xlsm
*.xlsx
*.ods
*.xml
*.htm
*.html

@ -5,7 +5,9 @@ node_js:
- "5"
- "4.2"
- "0.12"
- "0.11"
- "0.10"
- "0.9"
- "0.8"
before_install:
- "npm install -g npm@next"

@ -11,13 +11,19 @@ ULIB=$(shell echo $(LIB) | tr a-z A-Z)
DEPS=$(sort $(wildcard bits/*.js))
TARGET=$(LIB).js
FLOWTARGET=$(LIB).flow.js
FLOWAUX=$(patsubst %.js,%.flow.js,$(AUXTARGETS))
AUXSCPTS=xlsxworker1.js xlsxworker2.js xlsxworker.js
FLOWTGTS=$(TARGET) $(AUXTARGETS) $(AUXSCPTS)
## Main Targets
.PHONY: all
all: $(TARGET) $(AUXTARGETS) ## Build library and auxiliary scripts
all: $(TARGET) $(AUXTARGETS) $(AUXSCPTS) ## Build library and auxiliary scripts
$(TARGET): $(DEPS)
$(FLOWTGTS): %.js : %.flow.js
node -e 'process.stdout.write(require("fs").readFileSync("$<","utf8").replace(/^[ \t]*\/\*[:#][^*]*\*\/\s*(\n)?/gm,"").replace(/\/\*[:#][^*]*\*\//gm,""))' > $@
$(FLOWTARGET): $(DEPS)
cat $^ | tr -d '\15\32' > $@
bits/01_version.js: package.json
@ -58,6 +64,8 @@ dist-deps: ods.js ## Copy dependencies for distribution
cp node_modules/codepage/dist/cpexcel.full.js dist/cpexcel.js
cp jszip.js dist/jszip.js
cp ods.js dist/ods.js
uglifyjs ods.js -o dist/ods.min.js --source-map dist/ods.min.map --preamble "$$(head -n 1 bits/00_header.js)"
misc/strip_sourcemap.sh dist/ods.min.js
bower.json: misc/_bower.json package.json
cat $< | sed 's/_VERSION_/'`grep version package.json | awk '{gsub(/[^0-9a-z\.-]/,"",$$2); print $$2}'`'/' > $@
@ -69,9 +77,8 @@ aux: $(AUXTARGETS)
ods: ods.js
ODSDEPS=$(sort $(wildcard odsbits/*.js))
ods.js: $(ODSDEPS) ## Build ODS support library
ods.flow.js: $(ODSDEPS) ## Build ODS support library
cat $(ODSDEPS) | tr -d '\15\32' > $@
cp ods.js dist/ods.js
## Testing
@ -90,7 +97,7 @@ $(TESTFMT): test_%:
## Code Checking
.PHONY: lint
lint: $(TARGET) ## Run jshint and jscs checks
lint: $(TARGET) $(AUXTARGETS) ## Run jshint and jscs checks
@jshint --show-non-errors $(TARGET) $(AUXTARGETS)
@jshint --show-non-errors $(CMDS)
@jshint --show-non-errors package.json bower.json

@ -14,14 +14,18 @@ Supported read formats:
Supported write formats:
- XLSX
- Excel 2007+ XML Formats (XLSX/XLSM)
- Excel 2007+ Binary Format (XLSB) nodejs only
- CSV (and general DSV)
- JSON and JS objects (various styles)
- OpenDocument Spreadsheet (ODS)
Demo: <http://oss.sheetjs.com/js-xlsx>
Source: <http://git.io/xlsx>
Paid support available through the [reinforcements program](http://sheetjs.com/reinforcements)
## Installation
With [npm](https://www.npmjs.org/package/xlsx):
@ -53,7 +57,7 @@ circumstances, so they do not ship with the core. For browser use, they must
be included directly:
```html
<!-- international support from https://github.com/sheetjs/js-codepage -->
<!-- international support from js-codepage -->
<script src="dist/cpexcel.js"></script>
<!-- ODS support -->
<script src="dist/ods.js"></script>
@ -116,45 +120,75 @@ oReq.onload = function(e) {
oReq.send();
```
- HTML5 drag-and-drop using readAsBinaryString:
- HTML5 drag-and-drop using readAsBinaryString or readAsArrayBuffer:
note: readAsBinaryString and readAsArrayBuffer may not be available in every
browser. Use dynamic feature tests to determine which method to use.
```js
/* processing array buffers, only required for readAsArrayBuffer */
function fixdata(data) {
var o = "", l = 0, w = 10240;
for(; l<data.byteLength/w; ++l) o+=String.fromCharCode.apply(null,new Uint8Array(data.slice(l*w,l*w+w)));
o+=String.fromCharCode.apply(null, new Uint8Array(data.slice(l*w)));
return o;
}
var rABS = true; // true: readAsBinaryString ; false: readAsArrayBuffer
/* set up drag-and-drop event */
function handleDrop(e) {
e.stopPropagation();
e.preventDefault();
var files = e.dataTransfer.files;
var i,f;
for (i = 0, f = files[i]; i != files.length; ++i) {
for (i = 0; i != files.length; ++i) {
f = files[i];
var reader = new FileReader();
var name = f.name;
reader.onload = function(e) {
var data = e.target.result;
/* if binary string, read with type 'binary' */
var workbook = XLSX.read(data, {type: 'binary'});
var workbook;
if(rABS) {
/* if binary string, read with type 'binary' */
workbook = XLSX.read(data, {type: 'binary'});
} else {
/* if array buffer, convert to base64 */
var arr = fixdata(data);
workbook = XLSX.read(btoa(arr), {type: 'base64'});
}
/* DO SOMETHING WITH workbook HERE */
};
reader.readAsBinaryString(f);
if(rABS) reader.readAsBinaryString(f);
else reader.readAsArrayBuffer(f);
}
}
drop_dom_element.addEventListener('drop', handleDrop, false);
```
- HTML5 input file element using readAsBinaryString:
- HTML5 input file element using readAsBinaryString or readAsArrayBuffer:
```js
/* fixdata and rABS are defined in the drag and drop example */
function handleFile(e) {
var files = e.target.files;
var i,f;
for (i = 0, f = files[i]; i != files.length; ++i) {
for (i = 0; i != files.length; ++i) {
f = files[i];
var reader = new FileReader();
var name = f.name;
reader.onload = function(e) {
var data = e.target.result;
var workbook = XLSX.read(data, {type: 'binary'});
var workbook;
if(rABS) {
/* if binary string, read with type 'binary' */
workbook = XLSX.read(data, {type: 'binary'});
} else {
/* if array buffer, convert to base64 */
var arr = fixdata(data);
workbook = XLSX.read(btoa(arr), {type: 'base64'});
}
/* DO SOMETHING WITH workbook HERE */
};
@ -164,6 +198,11 @@ function handleFile(e) {
input_dom_element.addEventListener('change', handleFile, false);
```
The readAsArrayBuffer form requires some preprocessing:
```js
```
## Working with the Workbook
The full object format is described later in this README.
@ -254,7 +293,7 @@ function s2ab(s) {
}
/* the saveAs call downloads a file on the local machine */
saveAs(new Blob([s2ab(wbout)],{type:""}), "test.xlsx")
saveAs(new Blob([s2ab(wbout)],{type:"application/octet-stream"}), "test.xlsx");
```
Complete examples:
@ -290,8 +329,11 @@ Utilities are available in the `XLSX.utils` object:
Exporting:
- `sheet_to_json` converts a workbook object to an array of JSON objects.
- `sheet_to_csv` generates delimiter-separated-values output
- `sheet_to_formulae` generates a list of the formulae (with value fallbacks)
`sheet_to_row_object_array` is an alias that will be removed in the future.
- `sheet_to_csv` generates delimiter-separated-values output.
- `sheet_to_formulae` generates a list of the formulae (with value fallbacks).
The `sheet_to_*` functions accept a worksheet and an optional options object.
Cell and cell address manipulation:
@ -371,7 +413,7 @@ Type `d` is the Date type, generated only when the option `cellDates` is passed.
Since JSON does not have a natural Date type, parsers are generally expected to
store ISO 8601 Date strings like you would get from `date.toISOString()`. On
the other hand, writers and exporters should be able to handle date strings and
JS Date objects. Note that Excel disregards the timezone modifier and treats all
JS Date objects. Note that Excel disregards timezone modifiers and treats all
dates in the local timezone. js-xlsx does not correct for this error.
Type `s` is the String type. `v` should be explicitly stored as a string to
@ -395,7 +437,7 @@ Special worksheet keys (accessible as `worksheet[key]`, each starting with `!`):
Functions that handle worksheets should test for the presence of `!ref` field.
If the `!ref` is omitted or is not a valid range, functions are free to treat
the sheet as empty or attempt to guess the range. The standard utilities that
ship with this library treat sheets as empty (for example, the CSV output is an
ship with this library treat sheets as empty (for example, the CSV output is
empty string).
When reading a worksheet with the `sheetRows` property set, the ref parameter
@ -468,9 +510,10 @@ The exported `write` and `writeFile` functions accept an options argument:
| Option Name | Default | Description |
| :---------- | ------: | :--------------------------------------------------- |
| cellDates | false | Store dates as type `d` (default is `n`) |
| bookSST | false | Generate Shared String Table ** |
| cellDates | `false` | Store dates as type `d` (default is `n`) |
| bookSST | `false` | Generate Shared String Table ** |
| bookType | 'xlsx' | Type of Workbook ("xlsx" or "xlsm" or "xlsb") |
| compression | `false` | Use file compression for formats with ZIP containers |
- `bookSST` is slower and more memory intensive, but has better compatibility
with older versions of iOS Numbers
@ -483,7 +526,7 @@ The exported `write` and `writeFile` functions accept an options argument:
## Tested Environments
- NodeJS 0.8, 0.10 (latest release), 0.11.14 (unstable), io.js
- NodeJS 0.8, 0.9, 0.10, 0.11, 0.12, 4.x, 5.x, 6.x, 7.x
- IE 6/7/8/9/10/11 using Base64 mode (IE10/11 using HTML5 mode)
- FF 18 using Base64 or HTML5 mode
- Chrome 24 using Base64 or HTML5 mode
@ -491,8 +534,8 @@ The exported `write` and `writeFile` functions accept an options argument:
Tests utilize the mocha testing framework. Travis-CI and Sauce Labs links:
- <https://travis-ci.org/SheetJS/js-xlsx> for XLSX module in nodejs
- <https://travis-ci.org/SheetJS/SheetJS.github.io> for XLS* modules
- <https://saucelabs.com/u/sheetjs> for XLS* modules using Sauce Labs
- <https://travis-ci.org/SheetJS/SheetJS.github.io> for XLS\* modules
- <https://saucelabs.com/u/sheetjs> for XLS\* modules using Sauce Labs
## Test Files
@ -537,7 +580,7 @@ version release and should not be committed between versions.
## License
Please consult the attached LICENSE file for details. All rights not explicitly
granted by the Apache 2.0 license are reserved by the Original Author.
granted by the Apache 2.0 License are reserved by the Original Author.
It is the opinion of the Original Author that this code conforms to the terms of
the Microsoft Open Specifications Promise, falling under the same terms as
@ -573,10 +616,16 @@ Open Document Format for Office Applications Version 1.2 (29 September 2011)
## Badges
[![Build Status](https://saucelabs.com/browser-matrix/xlsx.svg)](https://saucelabs.com/u/xlsx)
[![Build Status](https://travis-ci.org/SheetJS/js-xlsx.svg?branch=master)](https://travis-ci.org/SheetJS/js-xlsx)
[![Coverage Status](http://img.shields.io/coveralls/SheetJS/js-xlsx/master.svg)](https://coveralls.io/r/SheetJS/js-xlsx?branch=master)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx)
[![NPM Downloads](https://img.shields.io/npm/dt/xlsx.svg)](https://npmjs.org/package/xlsx)
[![Dependencies Status](https://david-dm.org/sheetjs/js-xlsx/status.svg)](https://david-dm.org/sheetjs/js-xlsx)
[![ghit.me](https://ghit.me/badge.svg?repo=sheetjs/js-xlsx)](https://ghit.me/repo/sheetjs/js-xlsx)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx)

@ -48,7 +48,7 @@ if(process.version === 'v0.10.31') {
process.exit(1);
}
var filename, sheetname = '';
var filename/*:?string*/, sheetname = '';
if(program.args[0]) {
filename = program.args[0];
if(program.args[1]) sheetname = program.args[1];
@ -60,13 +60,13 @@ if(!filename) {
console.error(n + ": must specify a filename");
process.exit(1);
}
/*:: if(filename) { */
if(!fs.existsSync(filename)) {
console.error(n + ": " + filename + ": No such file or directory");
process.exit(2);
}
var opts = {}, wb;
var opts = {}, wb/*:?Workbook*/;
if(program.listSheets) opts.bookSheets = true;
if(program.sheetRows) opts.sheetRows = program.sheetRows;
if(program.password) opts.password = program.password;
@ -98,6 +98,7 @@ else try {
}
if(program.read) process.exit(0);
/*:: if(wb) { */
if(program.listSheets) {
console.log(wb.SheetNames.join("\n"));
process.exit(0);
@ -105,9 +106,9 @@ if(program.listSheets) {
var wopts = {WTF:opts.WTF, bookSST:program.sst};
if(program.xlsx) return X.writeFile(wb, sheetname || (filename + ".xlsx"), wopts);
if(program.xlsm) return X.writeFile(wb, sheetname || (filename + ".xlsm"), wopts);
if(program.xlsb) return X.writeFile(wb, sheetname || (filename + ".xlsb"), wopts);
if(program.xlsx) { X.writeFile(wb, sheetname || (filename + ".xlsx"), wopts); process.exit(0); }
if(program.xlsm) { X.writeFile(wb, sheetname || (filename + ".xlsm"), wopts); process.exit(0); }
if(program.xlsb) { X.writeFile(wb, sheetname || (filename + ".xlsb"), wopts); process.exit(0); }
var target_sheet = sheetname || '';
if(target_sheet === '') target_sheet = wb.SheetNames[0];
@ -121,7 +122,7 @@ try {
process.exit(4);
}
if(program.perf) return;
if(program.perf) process.exit(0);
var oo = "";
if(!program.quiet) console.error(target_sheet);
@ -132,3 +133,5 @@ else oo = X.utils.make_csv(ws, {FS:program.fieldSep, RS:program.rowSep});
if(program.output) fs.writeFileSync(program.output, oo);
else console.log(oo);
/*:: } */
/*:: } */

@ -1 +1 @@
XLSX.version = '0.8.1';
XLSX.version = '0.8.2';

8
bits/09_types.js Normal file

@ -0,0 +1,8 @@
/*::
declare type Block = any;
declare type BufArray = {
next(sz:number):Block;
end():any;
push(buf:Block):void;
};
*/

@ -1,4 +1,4 @@
function isval(x) { return x !== undefined && x !== null; }
function isval(x/*:?any*/)/*:boolean*/ { return x !== undefined && x !== null; }
function keys(o) { return Object.keys(o); }
@ -36,7 +36,7 @@ function datenum(v, date1904) {
return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
}
function cc2str(arr) {
function cc2str(arr/*:Array<number>*/)/*:string*/ {
var o = "";
for(var i = 0; i != arr.length; ++i) o += String.fromCharCode(arr[i]);
return o;

@ -26,12 +26,13 @@ function getzipfile(zip, file) {
return o;
}
function getzipdata(zip, file, safe) {
function getzipdata(zip, file, safe/*:?boolean*/) {
if(!safe) return getdata(getzipfile(zip, file));
if(!file) return null;
try { return getzipdata(zip, file); } catch(e) { return null; }
}
/*:: declare var JSZip:any; */
var _fs, jszip;
if(typeof JSZip !== 'undefined') jszip = JSZip;
if (typeof exports !== 'undefined') {

@ -132,7 +132,8 @@ function WriteShift(t, val, f) {
size = 2 * val.length;
} else switch(t) {
case 1: size = 1; this[this.l] = val&255; break;
case 3: size = 3; this[this.l+2] = val & 255; val >>>= 8; this[this.l+1] = val&255; val >>>= 8; this[this.l] = val&255; break;
case 2: size = 2; this[this.l] = val&255; val >>>= 8; this[this.l+1] = val&255; break;
case 3: size = 3; this[this.l] = val&255; val >>>= 8; this[this.l+1] = val&255; val >>>= 8; this[this.l+2] = val&255; break;
case 4: size = 4; this.writeUInt32LE(val, this.l); break;
case 8: size = 8; if(f === 'f') { this.writeDoubleLE(val, this.l); break; }
/* falls through */
@ -148,16 +149,16 @@ function CheckField(hexstr, fld) {
this.l += hexstr.length>>1;
}
function prep_blob(blob, pos) {
function prep_blob(blob, pos/*:number*/) {
blob.l = pos;
blob.read_shift = ReadShift;
blob.chk = CheckField;
blob.write_shift = WriteShift;
}
function parsenoop(blob, length) { blob.l += length; }
function parsenoop(blob, length/*:number*/) { blob.l += length; }
function writenoop(blob, length) { blob.l += length; }
function writenoop(blob, length/*:number*/) { blob.l += length; }
function new_buf(sz) {
var o = new_raw_buf(sz);

@ -26,7 +26,7 @@ function buf_array() {
var curbuf = newblk(blksz);
var endbuf = function ba_endbuf() {
curbuf.length = curbuf.l;
if(curbuf.length > curbuf.l) curbuf = curbuf.slice(0, curbuf.l);
if(curbuf.length > 0) bufs.push(curbuf);
curbuf = null;
};
@ -47,7 +47,7 @@ function buf_array() {
return { next:next, push:push, end:end, _bufs:bufs };
}
function write_record(ba, type, payload, length) {
function write_record(ba/*:BufArray*/, type/*:string*/, payload, length/*:?number*/) {
var t = evert_RE[type], l;
if(!length) length = XLSBRecordEnum[t].p || (payload||[]).length || 0;
l = 1 + (t >= 0x80 ? 1 : 0) + 1 + length;
@ -62,5 +62,5 @@ function write_record(ba, type, payload, length) {
if(length >= 0x80) { o.write_shift(1, (length & 0x7F)+0x80); length >>= 7; }
else { o.write_shift(1, length); break; }
}
if(length > 0 && is_buf(payload)) ba.push(payload);
if(/*:: length != null &&*/length > 0 && is_buf(payload)) ba.push(payload);
}

@ -91,6 +91,15 @@ function parse_RkNumber(data) {
var RK = fInt === 0 ? __double([0,0,0,0,b[0],b[1],b[2],b[3]],0) : __readInt32LE(b,0)>>2;
return fX100 ? RK/100 : RK;
}
function write_RkNumber(data/*:number*/, o) {
if(o == null) o = new_buf(4);
var fX100 = 0, fInt = 0, d100 = data * 100;
if(data == (data | 0) && data >= -(1<<29) && data < (1 << 29)) { fInt = 1; }
else if(d100 == (d100 | 0) && d100 >= -(1<<29) && d100 < (1 << 29)) { fInt = 1; fX100 = 1; }
if(fInt) o.write_shift(-4, ((fX100 ? d100 : data) << 2) + (fX100 + 2));
else throw new Error("unsupported RkNumber " + data); // TODO
}
/* [MS-XLSB] 2.5.153 */
function parse_UncheckedRfX(data) {
@ -113,8 +122,9 @@ function write_UncheckedRfX(r, o) {
/* [MS-XLSB] 2.5.171 */
/* [MS-XLS] 2.5.342 */
/* TODO: error checking, NaN and Infinity values are not valid Xnum */
function parse_Xnum(data, length) { return data.read_shift(8, 'f'); }
function write_Xnum(data, o) { return (o || new_buf(8)).write_shift(8, 'f', data); }
function write_Xnum(data, o) { return (o || new_buf(8)).write_shift(8, data, 'f'); }
/* [MS-XLSB] 2.5.198.2 */
var BErr = {

@ -40,7 +40,7 @@ function parse_cust_props(data, opts) {
p[name] = unescapexml(text);
break;
default:
if(typeof console !== 'undefined') console.warn('Unexpected', x, type, toks);
if(opts.WTF && typeof console !== 'undefined') console.warn('Unexpected', x, type, toks);
}
} else if(x.substr(0,2) === "</") {
} else if(opts.WTF) throw new Error(x);

@ -24,7 +24,7 @@ function parse_ws_xml(data, opts, rels) {
var mergecells = [];
if(data.indexOf("</mergeCells>")!==-1) {
var merges = data.match(mergecregex);
for(ridx = 0; ridx != merges.length; ++ridx)
if(merges) for(ridx = 0; ridx != merges.length; ++ridx)
mergecells[ridx] = safe_decode_range(merges[ridx].substr(merges[ridx].indexOf("\"")+1));
}
@ -36,7 +36,7 @@ function parse_ws_xml(data, opts, rels) {
parse_ws_xml_cols(columns, cols);
}
var refguess = {s: {r:1000000, c:1000000}, e: {r:0, c:0} };
var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
/* 18.3.1.80 sheetData CT_SheetData ? */
var mtch=data.match(sheetdataregex);
@ -282,7 +282,7 @@ var WS_XML_ROOT = writextag('worksheet', null, {
'xmlns:r': XMLNS.r
});
function write_ws_xml(idx, opts, wb) {
function write_ws_xml(idx/*:number*/, opts, wb)/*:string*/ {
var o = [XML_HEADER, WS_XML_ROOT];
var s = wb.SheetNames[idx], sidx = 0, rdata = "";
var ws = wb.Sheets[s];

@ -6,6 +6,45 @@ function parse_BrtRowHdr(data, length) {
data.l += length-4;
return z;
}
function write_BrtRowHdr(R/*:number*/, range, ws) {
var o = new_buf(17+8*16);
o.write_shift(4, R);
/* TODO: flags styles */
o.write_shift(4, 0);
o.write_shift(2, 0x0140);
o.write_shift(2, 0);
o.write_shift(1, 0);
/* [MS-XLSB] 2.5.8 BrtColSpan explains the mechanism */
var ncolspan = 0, lcs = o.l;
o.l += 4;
var caddr = {r:R, c:0};
for(var i = 0; i < 16; ++i) {
if(range.s.c > ((i+1) << 10) || range.e.c < (i << 10)) continue;
var first = -1, last = -1;
for(var j = (i<<10); j < ((i+1)<<10); ++j) {
caddr.c = j;
if(ws[encode_cell(caddr)]) { if(first < 0) first = j; last = j; }
}
if(first < 0) continue;
++ncolspan;
o.write_shift(4, first);
o.write_shift(4, last);
}
var l = o.l;
o.l = lcs;
o.write_shift(4, ncolspan);
o.l = l;
return o.length > o.l ? o.slice(0, o.l) : o;
}
function write_row_header(ba, ws, range, R) {
var o = write_BrtRowHdr(R, range, ws);
if(o.length > 17) write_record(ba, 'BrtRowHdr', o);
}
/* [MS-XLSB] 2.4.812 BrtWsDim */
var parse_BrtWsDim = parse_UncheckedRfX;
@ -25,9 +64,9 @@ function parse_BrtCellBlank(data, length) {
var cell = parse_XLSBCell(data);
return [cell];
}
function write_BrtCellBlank(cell, val, o) {
function write_BrtCellBlank(cell, ncell, o) {
if(o == null) o = new_buf(8);
return write_XLSBCell(val, o);
return write_XLSBCell(ncell, o);
}
@ -37,12 +76,18 @@ function parse_BrtCellBool(data, length) {
var fBool = data.read_shift(1);
return [cell, fBool, 'b'];
}
function write_BrtCellBool(cell, ncell, o) {
if(o == null) o = new_buf(9);
write_XLSBCell(ncell, o);
o.write_shift(1, cell.v ? 1 : 0);
return o;
}
/* [MS-XLSB] 2.4.305 BrtCellError */
function parse_BrtCellError(data, length) {
var cell = parse_XLSBCell(data);
var fBool = data.read_shift(1);
return [cell, fBool, 'e'];
var bError = data.read_shift(1);
return [cell, bError, 'e'];
}
/* [MS-XLSB] 2.4.308 BrtCellIsst */
@ -51,6 +96,12 @@ function parse_BrtCellIsst(data, length) {
var isst = data.read_shift(4);
return [cell, isst, 's'];
}
function write_BrtCellIsst(cell, ncell, o) {
if(o == null) o = new_buf(12);
write_XLSBCell(ncell, o);
o.write_shift(4, ncell.v);
return o;
}
/* [MS-XLSB] 2.4.310 BrtCellReal */
function parse_BrtCellReal(data, length) {
@ -58,6 +109,12 @@ function parse_BrtCellReal(data, length) {
var value = parse_Xnum(data);
return [cell, value, 'n'];
}
function write_BrtCellReal(cell, ncell, o) {
if(o == null) o = new_buf(16);
write_XLSBCell(ncell, o);
write_Xnum(cell.v, o);
return o;
}
/* [MS-XLSB] 2.4.311 BrtCellRk */
function parse_BrtCellRk(data, length) {
@ -65,6 +122,13 @@ function parse_BrtCellRk(data, length) {
var value = parse_RkNumber(data);
return [cell, value, 'n'];
}
function write_BrtCellRk(cell, ncell, o) {
if(o == null) o = new_buf(12);
write_XLSBCell(ncell, o);
write_RkNumber(cell.v, o);
return o;
}
/* [MS-XLSB] 2.4.314 BrtCellSt */
function parse_BrtCellSt(data, length) {
@ -72,6 +136,12 @@ function parse_BrtCellSt(data, length) {
var value = parse_XLWideString(data);
return [cell, value, 'str'];
}
function write_BrtCellSt(cell, ncell, o) {
if(o == null) o = new_buf(12 + 4 * cell.v.length);
write_XLSBCell(ncell, o);
write_XLWideString(cell.v, o);
return o.length > o.l ? o.slice(0, o.l) : o;
}
/* [MS-XLSB] 2.4.647 BrtFmlaBool */
function parse_BrtFmlaBool(data, length, opts) {
@ -147,12 +217,13 @@ function parse_ws_bin(data, opts, rels) {
var s = {};
var ref;
var refguess = {s: {r:1000000, c:1000000}, e: {r:0, c:0} };
var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
var pass = false, end = false;
var row, p, cf, R, C, addr, sstr, rr;
var mergecells = [];
recordhopper(data, function ws_parse(val, R) {
//console.log(R);
if(end) return;
switch(R.n) {
case 'BrtWsDim': ref = val; break;
@ -329,7 +400,7 @@ function parse_ws_bin(data, opts, rels) {
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + R.n);
}
}, opts);
if(!s["!ref"] && (refguess.s.r < 1000000 || ref.e.r > 0 || ref.e.c > 0 || ref.s.r > 0 || ref.s.c > 0)) s["!ref"] = encode_range(ref);
if(!s["!ref"] && (refguess.s.r < 2000000 || ref.e.r > 0 || ref.e.c > 0 || ref.s.r > 0 || ref.s.c > 0)) s["!ref"] = encode_range(ref);
if(opts.sheetRows && s["!ref"]) {
var tmpref = safe_decode_range(s["!ref"]);
if(opts.sheetRows < +tmpref.e.r) {
@ -362,12 +433,23 @@ function write_ws_bin_cell(ba, cell, R, C, opts) {
case 's': case 'str':
if(opts.bookSST) {
vv = get_sst_id(opts.Strings, cell.v);
o.t = "s"; break;
o.t = "s"; o.v = vv;
write_record(ba, "BrtCellIsst", write_BrtCellIsst(cell, o));
} else {
o.t = "str";
write_record(ba, "BrtCellSt", write_BrtCellSt(cell, o));
}
o.t = "str"; break;
case 'n': break;
case 'b': o.t = "b"; break;
case 'e': o.t = "e"; break;
return;
case 'n':
/* TODO: determine threshold for Real vs RK */
if(cell.v == (cell.v | 0) && cell.v > -1000 && cell.v < 1000) write_record(ba, "BrtCellRk", write_BrtCellRk(cell, o));
else write_record(ba, "BrtCellReal", write_BrtCellReal(cell, o));
return;
case 'b':
o.t = "b";
write_record(ba, "BrtCellBool", write_BrtCellBool(cell, o));
return;
case 'e': /* TODO: error */ o.t = "e"; break;
}
write_record(ba, "BrtCellBlank", write_BrtCellBlank(cell, o));
}
@ -379,6 +461,7 @@ function write_CELLTABLE(ba, ws, idx, opts, wb) {
rr = encode_row(R);
/* [ACCELLTABLE] */
/* BrtRowHdr */
write_row_header(ba, ws, range, R);
for(var C = range.s.c; C <= range.e.c; ++C) {
/* *16384CELL */
if(R === range.s.r) cols[C] = encode_col(C);

@ -13,7 +13,7 @@ function write_BrtBundleSh(data, o) {
o.write_shift(4, data.iTabID);
write_RelID(data.strRelID, o);
write_XLWideString(data.name.substr(0,31), o);
return o;
return o.length > o.l ? o.slice(0, o.l) : o;
}
/* [MS-XLSB] 2.4.807 BrtWbProp */
@ -121,7 +121,7 @@ function write_BrtFileVersion(data, o) {
write_XLWideString(XLSX.version, o);
write_XLWideString("7262", o);
o.length = o.l;
return o;
return o.length > o.l ? o.slice(0, o.l) : o;
}
/* [MS-XLSB] 2.1.7.60 Workbook */
@ -144,6 +144,7 @@ function write_BrtCalcProp(data, o) {
return o;
}
/* [MS-XLSB] 2.4.640 BrtFileRecover */
function write_BrtFileRecover(data, o) {
if(!o) o = new_buf(1);
o.write_shift(1,0);
@ -156,22 +157,22 @@ function write_wb_bin(wb, opts) {
write_record(ba, "BrtBeginBook");
write_record(ba, "BrtFileVersion", write_BrtFileVersion());
/* [[BrtFileSharingIso] BrtFileSharing] */
write_record(ba, "BrtWbProp", write_BrtWbProp());
if(0) write_record(ba, "BrtWbProp", write_BrtWbProp());
/* [ACABSPATH] */
/* [[BrtBookProtectionIso] BrtBookProtection] */
write_BOOKVIEWS(ba, wb, opts);
if(0) write_BOOKVIEWS(ba, wb, opts);
write_BUNDLESHS(ba, wb, opts);
/* [FNGROUP] */
/* [EXTERNALS] */
/* *BrtName */
write_record(ba, "BrtCalcProp", write_BrtCalcProp());
if(0) write_record(ba, "BrtCalcProp", write_BrtCalcProp());
/* [BrtOleSize] */
/* *(BrtUserBookView *FRT) */
/* [PIVOTCACHEIDS] */
/* [BrtWbFactoid] */
/* [SMARTTAGTYPES] */
/* [BrtWebOpt] */
write_record(ba, "BrtFileRecover", write_BrtFileRecover());
if(0) write_record(ba, "BrtFileRecover", write_BrtFileRecover());
/* [WEBPUBITEMS] */
/* [CRERRS] */
/* FRTWORKBOOK */

@ -147,7 +147,7 @@ function parse_xlml_xml(d, opts) {
var sheets = {}, sheetnames = [], cursheet = {}, sheetname = "";
var table = {}, cell = {}, row = {}, dtag, didx;
var c = 0, r = 0;
var refguess = {s: {r:1000000, c:1000000}, e: {r:0, c:0} };
var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
var styles = {}, stag = {};
var ss = "", fidx = 0;
var mergecells = [];
@ -207,7 +207,7 @@ function parse_xlml_xml(d, opts) {
if(mergecells.length) cursheet["!merges"] = mergecells;
sheets[sheetname] = cursheet;
} else {
refguess = {s: {r:1000000, c:1000000}, e: {r:0, c:0} };
refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
r = c = 0;
state.push([Rn[3], false]);
tmp = xlml_parsexmltag(Rn[0]);

11
bits/83_ods.js Normal file

@ -0,0 +1,11 @@
/* Helper functions to call out to ODS */
function parse_ods(zip, opts) {
if(typeof module !== "undefined" && typeof require !== 'undefined' && typeof ODS === 'undefined') ODS = require('./od' + 's');
if(typeof ODS === 'undefined' || !ODS.parse_ods) throw new Error("Unsupported ODS");
return ODS.parse_ods(zip, opts);
}
function write_ods(wb, opts) {
if(typeof module !== "undefined" && typeof require !== 'undefined' && typeof ODS === 'undefined') ODS = require('./od' + 's');
if(typeof ODS === 'undefined' || !ODS.write_ods) throw new Error("Unsupported ODS");
return ODS.write_ods(wb, opts);
}

@ -1,6 +0,0 @@
/* Helper function to call out to ODS parser */
function parse_ods(zip, opts) {
if(typeof module !== "undefined" && typeof require !== 'undefined' && typeof ODS === 'undefined') ODS = require('./od' + 's');
if(typeof ODS === 'undefined' || !ODS.parse_ods) throw new Error("Unsupported ODS");
return ODS.parse_ods(zip, opts);
}

@ -36,5 +36,7 @@ var fix_write_opts = fix_opts_func([
['bookType', 'xlsx'], /* Type of workbook (xlsx/m/b) */
['compression', false], /* Use file compression */
['WTF', false] /* WTF mode (throws errors) */
]);

@ -10,6 +10,7 @@ function add_rels(rels, rId, f, type, relobj) {
}
function write_zip(wb, opts) {
if(opts.bookType == "ods") return write_ods(wb, opts);
if(wb && !wb.SSF) {
wb.SSF = SSF.get_table();
}

@ -1,13 +1,17 @@
function write_zip_type(wb, opts) {
var o = opts||{};
var z = write_zip(wb, o);
var oopts = {};
if(opts.compression) oopts.compression = 'DEFLATE';
switch(o.type) {
case "base64": return z.generate({type:"base64"});
case "binary": return z.generate({type:"string"});
case "buffer": return z.generate({type:"nodebuffer"});
case "file": return _fs.writeFileSync(o.file, z.generate({type:"nodebuffer"}));
case "base64": oopts.type = "base64"; break;
case "binary": oopts.type = "string"; break;
case "buffer":
case "file": oopts.type = "nodebuffer"; break;
default: throw new Error("Unrecognized type " + o.type);
}
if(o.type === "file") return _fs.writeFileSync(o.file, z.generate(oopts));
return z.generate(oopts);
}
function writeSync(wb, opts) {
@ -21,13 +25,14 @@ function writeSync(wb, opts) {
function writeFileSync(wb, filename, opts) {
var o = opts||{}; o.type = 'file';
o.file = filename;
switch(o.file.substr(-5).toLowerCase()) {
if(!o.bookType) switch(o.file.substr(-5).toLowerCase()) {
case '.xlsx': o.bookType = 'xlsx'; break;
case '.xlsm': o.bookType = 'xlsm'; break;
case '.xlsb': o.bookType = 'xlsb'; break;
default: switch(o.file.substr(-4).toLowerCase()) {
case '.xls': o.bookType = 'xls'; break;
case '.xml': o.bookType = 'xml'; break;
case '.ods': o.bookType = 'ods'; break;
}}
return writeSync(wb, o);
}

@ -2,7 +2,7 @@
"name": "js-xlsx",
"homepage": "https://github.com/SheetJS/js-xlsx",
"main": "dist/xlsx.js",
"version": "0.8.1",
"version": "0.8.2",
"ignore": [
"bin",
"bits",

287
dist/ods.js vendored

@ -21,6 +21,14 @@ function cc2str(arr) {
for(var i = 0; i != arr.length; ++i) o += String.fromCharCode(arr[i]);
return o;
}
function dup(o) {
if(typeof JSON != 'undefined') return JSON.parse(JSON.stringify(o));
if(typeof o != 'object' || !o) return o;
var out = {};
for(var k in o) if(o.hasOwnProperty(k)) out[k] = dup(o[k]);
return out;
}
function getdata(data) {
if(!data) return null;
if(data.data) return data.data;
@ -67,7 +75,7 @@ function parsexmltag(tag, skip_root) {
for(; eq !== tag.length; ++eq) if((c = tag.charCodeAt(eq)) === 32 || c === 10 || c === 13) break;
if(!skip_root) z[0] = tag.substr(0, eq);
if(eq === tag.length) return z;
var m = tag.match(attregexg), j=0, w="", v="", i=0, q="", cc="";
var m = tag.match(attregexg), j=0, v="", i=0, q="", cc="";
if(m) for(i = 0; i != m.length; ++i) {
cc = m[i];
for(c=0; c != cc.length; ++c) if(cc.charCodeAt(c) === 61) break;
@ -108,7 +116,7 @@ function escapexml(text){
return s.replace(decregex, function(y) { return rencoding[y]; }).replace(charegex,function(s) { return "_x" + ("000"+s.charCodeAt(0).toString(16)).substr(-4) + "_";});
}
function parsexmlbool(value, tag) {
function parsexmlbool(value) {
switch(value) {
case '1': case 'true': case 'TRUE': return true;
/* case '0': case 'false': case 'FALSE':*/
@ -147,6 +155,8 @@ function parse_isodur(s) {
}
return sec;
}
var XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n';
/* copied from js-xls (C) SheetJS Apache2 license */
function xlml_normalize(d) {
if(has_buf && Buffer.isBuffer(d)) return d.toString('utf8');
@ -157,14 +167,14 @@ function xlml_normalize(d) {
var xlmlregex = /<(\/?)([a-z0-9]*:|)([\w-]+)[^>]*>/mg;
/* Part 3 Section 4 Manifest File */
var CT_ODS = "application/vnd.oasis.opendocument.spreadsheet";
var parse_manifest = function(d, opts) {
function parse_manifest(d, opts) {
var str = xlml_normalize(d);
var Rn;
var FEtag;
while((Rn = xlmlregex.exec(str))) switch(Rn[3]) {
case 'manifest': break; // 4.2 <manifest:manifest>
case 'file-entry': // 4.3 <manifest:file-entry>
FEtag = parsexmltag(Rn[0]);
FEtag = parsexmltag(Rn[0], false);
if(FEtag.path == '/' && FEtag.type !== CT_ODS) throw new Error("This OpenDocument is not a spreadsheet");
break;
case 'encryption-data': // 4.4 <manifest:encryption-data>
@ -172,11 +182,46 @@ var parse_manifest = function(d, opts) {
case 'start-key-generation': // 4.6 <manifest:start-key-generation>
case 'key-derivation': // 4.7 <manifest:key-derivation>
throw new Error("Unsupported ODS Encryption");
default: throw Rn;
default: if(opts && opts.WTF) throw Rn;
}
};
}
function write_manifest(manifest, opts) {
var o = [XML_HEADER];
o.push('<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2">\n');
o.push(' <manifest:file-entry manifest:full-path="/" manifest:version="1.2" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>\n');
for(var i = 0; i < manifest.length; ++i) o.push(' <manifest:file-entry manifest:full-path="' + manifest[i][0] + '" manifest:media-type="' + manifest[i][1] + '"/>\n');
o.push('</manifest:manifest>');
return o.join("");
}
/* Part 3 Section 6 Metadata Manifest File */
function write_rdf_type(file, res, tag) {
return [
' <rdf:Description rdf:about="' + file + '">\n',
' <rdf:type rdf:resource="http://docs.oasis-open.org/ns/office/1.2/meta/' + (tag || "odf") + '#' + res + '"/>\n',
' </rdf:Description>\n'
].join("");
}
function write_rdf_has(base, file) {
return [
' <rdf:Description rdf:about="' + base + '">\n',
' <ns0:hasPart xmlns:ns0="http://docs.oasis-open.org/ns/office/1.2/meta/pkg#" rdf:resource="' + file + '"/>\n',
' </rdf:Description>\n'
].join("");
}
function write_rdf(rdf, opts) {
var o = [XML_HEADER];
o.push('<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n');
for(var i = 0; i != rdf.length; ++i) {
o.push(write_rdf_type(rdf[i][0], rdf[i][1]));
o.push(write_rdf_has("",rdf[i][0]));
}
o.push(write_rdf_type("","Document", "pkg"));
o.push('</rdf:RDF>');
return o.join("");
}
var parse_text_p = function(text, tag) {
return utf8read(text.replace(/<text:s\/>/g," ").replace(/<[^>]*>/g,""));
return unescapexml(utf8read(text.replace(/<text:s\/>/g," ").replace(/<[^>]*>/g,"")));
};
var utf8read = function utf8reada(orig) {
@ -213,27 +258,29 @@ var parse_content_xml = (function() {
var str = xlml_normalize(d);
var state = [], tmp;
var tag;
var NFtag, NF, pidx;
var NFtag = {name:""}, NF = "", pidx = 0;
var sheetag;
var Sheets = {}, SheetNames = [], ws = {};
var Rn, q;
var ctag;
var textp, textpidx, textptag;
var R, C, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}};
var ctag = {value:""};
var textp = "", textpidx = 0, textptag;
var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}};
var number_format_map = {};
var merges = [], mrange = {}, mR = 0, mC = 0;
var rept = 1;
xlmlregex.lastIndex = 0;
while((Rn = xlmlregex.exec(str))) switch(Rn[3]) {
case 'table': // 9.1.2 <table:table>
if(Rn[1]==='/') {
if(range.e.c >= range.s.c && range.e.r >= range.s.r) ws['!ref'] = get_utils().encode_range(range);
if(merges.length) ws['!merges'] = merges;
sheetag.name = utf8read(sheetag.name);
SheetNames.push(sheetag.name);
Sheets[sheetag.name] = ws;
}
else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
sheetag = parsexmltag(Rn[0]);
sheetag = parsexmltag(Rn[0], false);
R = C = -1;
range.s.r = range.s.c = 10000000; range.e.r = range.e.c = 0;
ws = {}; merges = [];
@ -247,17 +294,18 @@ var parse_content_xml = (function() {
++C; break; /* stub */
case 'table-cell':
if(Rn[0].charAt(Rn[0].length-2) === '/') {
ctag = parsexmltag(Rn[0]);
ctag = parsexmltag(Rn[0], false);
if(ctag['number-columns-repeated']) C+= parseInt(ctag['number-columns-repeated'], 10);
else ++C;
}
else if(Rn[1]!=='/') {
++C;
rept = 1;
if(C > range.e.c) range.e.c = C;
if(R > range.e.r) range.e.r = R;
if(C < range.s.c) range.s.c = C;
if(R < range.s.r) range.s.r = R;
ctag = parsexmltag(Rn[0]);
ctag = parsexmltag(Rn[0], false);
q = {t:ctag['value-type'], v:null};
if(ctag['number-columns-spanned'] || ctag['number-rows-spanned']) {
mR = parseInt(ctag['number-rows-spanned'],10) || 0;
@ -265,6 +313,10 @@ var parse_content_xml = (function() {
mrange = {s: {r:R,c:C}, e:{r:R + mR-1,c:C + mC-1}};
merges.push(mrange);
}
/* 19.675.2 table:number-columns-repeated */
if(ctag['number-columns-repeated']) rept = parseInt(ctag['number-columns-repeated'], 10);
/* 19.385 office:value-type */
switch(q.t) {
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']); break;
@ -273,14 +325,22 @@ var parse_content_xml = (function() {
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
case 'date': q.t = 'n'; q.v = datenum(ctag['date-value']); q.z = 'm/d/yy'; break;
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400; break;
case 'string': q.t = 's'; break;
default: throw new Error('Unsupported value type ' + q.t);
default:
if(q.t === 'string' || !q.t) {
q.t = 's';
if(ctag['string-value'] != null) textp = ctag['string-value'];
} else throw new Error('Unsupported value type ' + q.t);
}
} else {
if(q.t === 's') q.v = textp;
if(q.t === 's') q.v = textp || '';
if(textp) q.w = textp;
if(!(opts.sheetRows && opts.sheetRows < R)) ws[get_utils().encode_cell({r:R,c:C})] = q;
q = null;
if(!(opts.sheetRows && opts.sheetRows < R)) {
ws[get_utils().encode_cell({r:R,c:C})] = q;
while(--rept > 0) ws[get_utils().encode_cell({r:R,c:++C})] = dup(q);
if(range.e.c <= C) range.e.c = C;
}
q = {};
textp = "";
}
break; // 9.1.4 <table:table-cell>
@ -296,6 +356,13 @@ var parse_content_xml = (function() {
/* ignore state */
case 'shapes': // 9.2.8 <table:shapes>
case 'frame': // 10.4.2 <draw:frame>
case 'text-box': // 10.4.3 <draw:text-box>
case 'image': // 10.4.4 <draw:image>
case 'data-pilot-tables': // 9.6.2 <table:data-pilot-tables>
case 'list-style': // 16.30 <text:list-style>
case 'form': // 13.13 <form:form>
case 'dde-links': // 9.8 <table:dde-links>
case 'annotation': // 14.1 <office:annotation>
if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;}
else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], false]);
break;
@ -309,7 +376,7 @@ var parse_content_xml = (function() {