version bump 0.7.8: docs, defaults, details

- browser shim updated (h/t @wintersm for discovering this)
- smart tag ignores (h/t @lostinplace)
- sheet_to_row_object_array bugfix (fixes #80, h/t @ChrisBurkeBSD)
- README improved
- baltic and vietnamese codepages: updated codepage to 1.3.4
- iOS Numbers can handle inline strings -> disabling SST by default
- avoid Buffer accessors (see https://github.com/joyent/node/issues/7809)
- caching certain hot regexes
This commit is contained in:
SheetJS 2014-07-28 09:22:32 -04:00
parent 19c22fa34a
commit d4999ac421
39 changed files with 990 additions and 418 deletions

4
.gitignore vendored

@ -1,4 +1,8 @@
node_modules
misc/coverage.html
misc/prof.js
v8.log
tmp
*.xlsx
*.xlsm
*.xlsb

@ -4,4 +4,20 @@ index.html
misc/
node_modules
tmp
*.xls
*.xlsb
*.xlsm
*.xlsx
*.xml
.gitignore
.jshintrc
CONTRIBUTING.md
Makefile
tests.lst
.npmignore
xlsworker.js
shim.js
test.js
.jscs.json
.gitmodules
.travis.yml

160
README.md

@ -90,7 +90,7 @@ oReq.onload = function(e) {
oReq.send();
```
- html5 drag-and-drop using readAsBinaryString:
- HTML5 drag-and-drop using readAsBinaryString:
```
/* set up drag-and-drop event */
@ -106,7 +106,7 @@ function handleDrop(e) {
var data = e.target.result;
/* if binary string, read with type 'binary' */
var wb = XLSX.read(data, {type: 'binary'});
var workbook = XLSX.read(data, {type: 'binary'});
/* DO SOMETHING WITH workbook HERE */
};
@ -116,6 +116,28 @@ function handleDrop(e) {
drop_dom_element.addEventListener('drop', handleDrop, false);
```
- HTML5 input file element using readAsBinaryString:
```
function handleFile(e) {
var files = e.target.files;
var i,f;
for (i = 0, f = files[i]; i != files.length; ++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'});
/* DO SOMETHING WITH workbook HERE */
};
reader.readAsBinaryString(f);
}
}
input_dom_element.addEventListener('change', handleFile, false);
```
This example walks through every cell of every sheet and dumps the values:
```
@ -150,7 +172,7 @@ Some helper functions in `XLSX.utils` generate different views of the sheets:
- `XLSX.utils.sheet_to_csv` generates CSV
- `XLSX.utils.sheet_to_json` generates an array of objects
- `XLSX.utils.get_formulae` generates a list of formulae
- `XLSX.utils.sheet_to_formulae` generates a list of formulae
## Writing Workbooks
@ -167,7 +189,7 @@ XLSX.writeFile(workbook, 'out.xlsx');
```
/* bookType can be 'xlsx' or 'xlsm' or 'xlsb' */
var wopts = { bookType:'xlsx', bookSST:true, type:'binary' };
var wopts = { bookType:'xlsx', bookSST:false, type:'binary' };
var wbout = XLSX.write(workbook,wopts);
@ -185,44 +207,127 @@ Complete examples:
- <http://sheetjs.com/demos/writexlsx.html> generates a simple file
- <http://git.io/WEK88Q> writing an array of arrays in nodejs
- <http://sheetjs.com/demos/table.html> exporting an HTML table
## Interface
`XLSX` is the exposed variable in the browser and the exported nodejs variable
`XLSX.version` is the version of the library (added by the build script).
`XLSX.SSF` is an embedded version of the [format library](http://git.io/ssf).
### Parsing functions
`XLSX.read(data, read_opts)` attempts to parse `data`.
`XLSX.readFile(filename, read_opts)` attempts to read `filename` and parse.
### Writing functions
`XLSX.write(wb, write_opts)` attempts to write the workbook `wb`
`XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename`
## Cell Object Description
### Utilities
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)
Cell and cell address manipulation:
- `format_cell` generates the text value for a cell (using number formats)
- `{en,de}code_{row,col}` convert between 0-indexed rows/cols and A1 forms.
- `{en,de}code_cell` converts cell addresses
- `{en,de}code_range` converts cell ranges
## Workbook / Worksheet / Cell Object Description
js-xlsx conforms to the Common Spreadsheet Format (CSF):
`.SheetNames` is an ordered list of the sheets in the workbook
### General Structures
`.Sheets[sheetname]` returns a data structure representing the sheet. Each key
that does not start with `!` corresponds to a cell (using `A-1` notation).
Cell address objects are stored as `{c:C, r:R}` where `C` and `R` are 0-indexed
column and row numbers, respectively. For example, the cell address `B5` is
represented by the object `{c:1, r:4}`.
`.Sheets[sheetname][address]` returns the specified cell:
Cell range objects are stored as `{s:S, e:E}` where `S` is the first cell and
`E` is the last cell in the range. The ranges are inclusive. For example, the
range `A3:B7` is represented by the object `{s:{c:0, r:2}, e:{c:1, r:6}}`. Utils
use the following pattern to walk each of the cells in a range:
- `.v` : the raw value of the cell
- `.w` : the formatted text of the cell (if applicable)
- `.t` : the type of the cell (constrained to the enumeration `ST_CellType` as
documented in page 4215 of ISO/IEC 29500-1:2012(E) )
- `.f` : the formula of the cell (if applicable)
- `.r` : the rich text encoding of a cell text (if applicable)
- `.h` : an HTML rendering of the rich text (if applicable)
- `.c` : comments associated with the cell
- `.z` : the number format string associated with the cell (if requested)
- `.l` : the hyperlink of the cell (.Target holds link, .tooltip is tooltip)
- `.s` : the style/theme of the cell (if applicable)
```
for(var R = range.s.r; R <= range.e.r; ++R) {
for(var C = range.s.c; C <= range.e.c; ++C) {
var cell_address = {c:C, r:R};
}
}
```
### Cell Object
| Key | Description |
| --- | ----------- |
| `v` | raw value ** |
| `w` | formatted text (if applicable) |
| `t` | cell type: `b` Boolean, `n` Number, `e` error, `s/str` String |
| `f` | cell formula (if applicable) |
| `r` | rich text encoding (if applicable) |
| `h` | HTML rendering of the rich text (if applicable) |
| `c` | comments associated with the cell ** |
| `z` | number format string associated with the cell (if requested) |
| `l` | cell hyperlink object (.Target holds link, .tooltip is tooltip) |
| `s` | the style/theme of the cell (if applicable) |
- For dates, `.v` holds the raw date code from the sheet and `.w` holds the text
Built-in export utilities (such as the CSV exporter) will use the `w` text if it
is available. To change a value, be sure to delete `cell.w` (or set it to
`undefined`) before attempting to export. The utilities will regenerate the `w`
text from the number format (`cell.z`) and the raw value if possible.
### Worksheet Object
Each key that does not start with `!` maps to a cell (using `A-1` notation)
`worksheet[address]` returns the cell object for the specified address.
Special worksheet keys (accessible as `worksheet[key]`, each starting with `!`):
- `ws['!ref']`: A-1 based range representing the worksheet range. Functions that
work with sheets should use this parameter to determine the range. Cells that
are assigned outside of the range are not processed. In particular, when
writing a worksheet by hand, be sure to update the range. For a longer
discussion, see <http://git.io/KIaNKQ>
When reading a worksheet with the `sheetRows` property set, the ref parameter
will use the restricted range. The original range is set at `ws['!fullref']`
- `ws['!cols']`: array of column properties objects. Column widths are actually
stored in files in a normalized manner, measured in terms of the "Maximum
Digit Width" (the largest width of the rendered digits 0-9, in pixels). When
parsed, the column objects store the pixel width in the `wpx` field, character
width in the `wch` field, and the maximum digit width in the `MDW` field.
- `ws['!merges']`: array of range objects corresponding to the merged cells in
the worksheet. Plaintext utilities are unaware of merge cells. CSV export
will write all cells in the merge range if they exist, so be sure that only
the first cell (upper-left) in the range is set.
### Workbook Object
`workbook.SheetNames` is an ordered list of the sheets in the workbook
`wb.Sheets[sheetname]` returns an object representing the worksheet.
`wb.Props` is an object storing the standard properties. `wb.Custprops` stores
custom properties.
For dates, `.v` holds the raw date code from the sheet and `.w` holds the text
## Parsing Options
@ -264,7 +369,7 @@ The exported `write` and `writeFile` functions accept an options argument:
| bookType | 'xlsx' | Type of Workbook ("xlsx" or "xlsm" or "xlsb") |
- `bookSST` is slower and more memory intensive, but has better compatibility
with iOS Numbers
with older versions of iOS Numbers
- `bookType = 'xlsb'` is stubbed and far from complete
- The raw data is the only thing guaranteed to be saved. Formulae, formatting,
and other niceties may not be serialized (pending CSF standardization)
@ -308,6 +413,17 @@ For a much smaller test, run `make test_misc`.
Due to the precarious nature of the Open Specifications Promise, it is very
important to ensure code is cleanroom. Consult CONTRIBUTING.md
The xlsx.js file is constructed from the files in the `bits` subdirectory. The
build script (run `make`) will concatenate the individual bits to produce the
script. Before submitting a contribution, ensure that running make will produce
the xlsx.js file exactly. The simplest way to test is to move the script:
```
$ mv xlsx.js xlsx.new.js
$ make
$ diff xlsx.js xlsx.new.js
```
## XLS Support
XLS is available in [js-xls](http://git.io/xls).

@ -20,7 +20,7 @@ program
.option('-F, --field-sep <sep>', 'CSV field separator', ",")
.option('-R, --row-sep <sep>', 'CSV row separator', "\n")
.option('-n, --sheet-rows <num>', 'Number of rows to process (0=all rows)')
.option('--no-sst', 'do not generate sst')
.option('--sst', 'generate sst')
.option('--perf', 'do not generate output')
.option('--all', 'parse everything; XLS[XMB] write as much as possible')
.option('--dev', 'development mode')
@ -109,7 +109,7 @@ try {
if(program.perf) return;
var oo = "";
var oo = "";
if(!program.quiet) console.error(target_sheet);
if(program.formulae) oo = X.utils.get_formulae(ws).join("\n");
else if(program.json) oo = JSON.stringify(X.utils.sheet_to_row_object_array(ws));

@ -1 +1 @@
XLSX.version = '0.7.7';
XLSX.version = '0.7.8';

@ -4,12 +4,15 @@ if(typeof module !== "undefined" && typeof require !== 'undefined') {
current_cptable = cptable[current_codepage];
}
function reset_cp() { set_cp(1252); }
function set_cp(cp) { current_codepage = cp; if(typeof cptable !== 'undefined') current_cptable = cptable[cp]; }
var set_cp = function(cp) { current_codepage = cp; };
function char_codes(data) { var o = []; for(var i = 0; i != data.length; ++i) o[i] = data.charCodeAt(i); return o; }
function debom_xml(data) {
if(typeof cptable !== 'undefined') {
function char_codes(data) { var o = []; for(var i = 0, len = data.length; i < len; ++i) o[i] = data.charCodeAt(i); return o; }
var debom_xml = function(data) { return data; };
if(typeof cptable !== 'undefined') {
set_cp = function(cp) { current_codepage = cp; current_cptable = cptable[cp]; };
debom_xml = function(data) {
if(data.charCodeAt(0) === 0xFF && data.charCodeAt(1) === 0xFE) { return cptable.utils.decode(1200, char_codes(data.substr(2))); }
}
return data;
return data;
};
}

@ -41,3 +41,5 @@ function cc2str(arr) {
for(var i = 0; i != arr.length; ++i) o += String.fromCharCode(arr[i]);
return o;
}
var has_buf = (typeof Buffer !== 'undefined');

@ -2,11 +2,11 @@ function getdata(data) {
if(!data) return null;
if(data.name.substr(-4) === ".bin") {
if(data.data) return char_codes(data.data);
if(data.asNodeBuffer && typeof Buffer !== 'undefined') return data.asNodeBuffer();
if(data.asNodeBuffer && has_buf) return data.asNodeBuffer();
if(data._data && data._data.getContent) return Array.prototype.slice.call(data._data.getContent());
} else {
if(data.data) return data.name.substr(-4) !== ".bin" ? debom_xml(data.data) : char_codes(data.data);
if(data.asNodeBuffer && typeof Buffer !== 'undefined') return debom_xml(data.asNodeBuffer().toString('binary'));
if(data.asNodeBuffer && has_buf) return debom_xml(data.asNodeBuffer().toString('binary'));
if(data.asBinary) return debom_xml(data.asBinary());
if(data._data && data._data.getContent) return debom_xml(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
}
@ -30,8 +30,8 @@ var _fs, jszip;
if(typeof JSZip !== 'undefined') jszip = JSZip;
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
if(typeof Buffer !== 'undefined' && typeof jszip === 'undefined') jszip = require('jszip');
if(typeof jszip === 'undefined') jszip = require('./jszip').JSZip;
_fs = require('fs');
if(has_buf && typeof jszip === 'undefined') jszip = require('js'+'zip');
if(typeof jszip === 'undefined') jszip = require('./js'+'zip').JSZip;
_fs = require('f'+'s');
}
}

@ -1,4 +1,3 @@
var _chr = function(c) { return String.fromCharCode(c); };
var attregexg=/\b[\w:]+=["'][^"]*['"]/g;
var tagregex=/<[^>]*>/g;
var nsregex=/<\w*:/, nsregex2 = /<(\/?)\w+:/;
@ -32,10 +31,10 @@ var rencoding = evert(encodings);
var rencstr = "&<>'\"".split("");
// TODO: CP remap (need to read file version to determine OS)
var encregex = /&[a-z]*;/g, coderegex = /_x([0-9a-fA-F]+)_/g;
var encregex = /&[a-z]*;/g, coderegex = /_x([\da-fA-F]+)_/g;
function unescapexml(text){
var s = text + '';
return s.replace(encregex, function($$) { return encodings[$$]; }).replace(coderegex,function(m,c) {return _chr(parseInt(c,16));});
return s.replace(encregex, function($$) { return encodings[$$]; }).replace(coderegex,function(m,c) {return String.fromCharCode(parseInt(c,16));});
}
var decregex=/[&<>'"]/g, charegex = /[\u0000-\u0008\u000b-\u001f]/g;
function escapexml(text){
@ -69,7 +68,7 @@ var utf8read = function utf8reada(orig) {
};
if(typeof Buffer !== "undefined") {
if(has_buf) {
var utf8readb = function utf8readb(data) {
var out = new Buffer(2*data.length), w, i, j = 1, k = 0, ww=0, c;
for(i = 0; i < data.length; i+=j) {

@ -17,38 +17,37 @@ function readIEEE754(buf, idx, isLE, nl, ml) {
}
var __toBuffer, ___toBuffer;
__toBuffer = ___toBuffer = function(bufs) {
var x = [];
for(var i = 0; i != bufs[0].length; ++i) { x = x.concat(bufs[0][i]); }
return x;
};
if(typeof Buffer !== "undefined") {
__toBuffer = ___toBuffer = function toBuffer_(bufs) { var x = []; for(var i = 0; i < bufs[0].length; ++i) { x.push.apply(x, bufs[0][i]); } return x; };
var __double, ___double;
__double = ___double = function(b, idx) { return readIEEE754(b, idx);};
var is_buf = function is_buf_a(a) { return Array.isArray(a); };
if(has_buf) {
__toBuffer = function(bufs) { return (bufs[0].length > 0 && Buffer.isBuffer(bufs[0][0])) ? Buffer.concat(bufs[0]) : ___toBuffer(bufs);};
__double = function double_(b,i) { if(Buffer.isBuffer(b)) return b.readDoubleLE(i); return ___double(b,i); };
is_buf = function is_buf_b(a) { return Buffer.isBuffer(a) || Array.isArray(a); };
}
var ___readUInt32LE = function(b, idx) { return b.readUInt32LE ? b.readUInt32LE(idx) : b[idx+3]*(1<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx]; };
var ___readInt32LE = function(b, idx) { return (b[idx+3]<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx]; };
var __readUInt8 = function(b, idx) { return b.readUInt8 ? b.readUInt8(idx) : b[idx]; };
var __readUInt16LE = function(b, idx) { return b.readUInt16LE ? b.readUInt16LE(idx) : b[idx+1]*(1<<8)+b[idx]; };
var __readInt16LE = function(b, idx) { var u = __readUInt16LE(b,idx); if(!(u & 0x8000)) return u; return (0xffff - u + 1) * -1; };
var __readUInt32LE = typeof Buffer !== "undefined" ? function(b, i) { return Buffer.isBuffer(b) ? b.readUInt32LE(i) : ___readUInt32LE(b,i); } : ___readUInt32LE;
var __readInt32LE = typeof Buffer !== "undefined" ? function(b, i) { return Buffer.isBuffer(b) ? b.readInt32LE(i) : ___readInt32LE(b,i); } : ___readInt32LE;
var __readDoubleLE = function(b, idx) { return b.readDoubleLE ? b.readDoubleLE(idx) : readIEEE754(b, idx||0);};
var __readUInt8 = function(b, idx) { return b[idx]; };
var __readUInt16LE = function(b, idx) { return b[idx+1]*(1<<8)+b[idx]; };
var __readInt16LE = function(b, idx) { var u = b[idx+1]*(1<<8)+b[idx]; return (u < 0x8000) ? u : (0xffff - u + 1) * -1; };
var __readUInt32LE = function(b, idx) { return b[idx+3]*(1<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx]; };
var __readInt32LE = function(b, idx) { return (b[idx+3]<<24)|(b[idx+2]<<16)|(b[idx+1]<<8)|b[idx]; };
function ReadShift(size, t) {
var o="", oo=[], w, vv, i, loc;
if(t === 'dbcs') {
loc = this.l;
if(typeof Buffer !== 'undefined' && this instanceof Buffer) o = this.slice(this.l, this.l+2*size).toString("utf16le");
if(has_buf && Buffer.isBuffer(this)) o = this.slice(this.l, this.l+2*size).toString("utf16le");
else for(i = 0; i != size; ++i) { o+=String.fromCharCode(__readUInt16LE(this, loc)); loc+=2; }
size *= 2;
} else switch(size) {
case 1: o = __readUInt8(this, this.l); break;
case 2: o = (t === 'i' ? __readInt16LE : __readUInt16LE)(this, this.l); break;
case 4: o = __readUInt32LE(this, this.l); break;
case 8: if(t === 'f') { o = __readDoubleLE(this, this.l); break; }
case 8: if(t === 'f') { o = __double(this, this.l); break; }
}
this.l+=size; return o;
}
@ -59,7 +58,8 @@ function WriteShift(t, val, f) {
for(i = 0; i != val.length; ++i) this.writeUInt16LE(val.charCodeAt(i), this.l + 2 * i);
size = 2 * val.length;
} else switch(t) {
case 1: size = 1; this.writeUInt8(val, this.l); break;
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 4: size = 4; this.writeUInt32LE(val, this.l); break;
case 8: size = 8; if(f === 'f') { this.writeDoubleLE(val, this.l); break; }
/* falls through */
@ -70,7 +70,7 @@ function WriteShift(t, val, f) {
}
function prep_blob(blob, pos) {
blob.l = pos || 0;
blob.l = pos;
blob.read_shift = ReadShift;
blob.write_shift = WriteShift;
}
@ -80,9 +80,8 @@ function parsenoop(blob, length) { blob.l += length; }
function writenoop(blob, length) { blob.l += length; }
function new_buf(sz) {
var o = typeof Buffer !== 'undefined' ? new Buffer(sz) : new Array(sz);
var o = has_buf ? new Buffer(sz) : new Array(sz);
prep_blob(o, 0);
return o;
}
function is_buf(a) { return (typeof Buffer !== 'undefined' && a instanceof Buffer) || Array.isArray(a); }

@ -24,6 +24,13 @@ function parse_RichStr(data, length) {
data.l = start + length;
return z;
}
function write_RichStr(str, o) {
/* TODO: formatted string */
if(o == null) o = new_buf(5+2*str.t.length);
o.write_shift(1,0);
write_XLWideString(str.t, o);
return o;
}
/* [MS-XLSB] 2.5.9 */
function parse_Cell(data) {
@ -33,6 +40,14 @@ function parse_Cell(data) {
var fPhShow = data.read_shift(1);
return { c:col, iStyleRef: iStyleRef };
}
function write_Cell(cell, o) {
if(o == null) o = new_buf(8);
o.write_shift(-4, cell.c);
o.write_shift(3, cell.iStyleRef === undefined ? cell.iStyleRef : cell.s);
o.write_shift(1, 0); /* fPhShow */
return o;
}
/* [MS-XLSB] 2.5.21 */
function parse_CodeName (data, length) { return parse_XLWideString(data, length); }
@ -55,7 +70,7 @@ function parse_XLWideString(data) {
return cchCharacters === 0 ? "" : data.read_shift(cchCharacters, 'dbcs');
}
function write_XLWideString(data, o) {
if(o == null) o = new_buf(127);
if(o == null) o = new_buf(4+2*data.length);
o.write_shift(4, data.length);
if(data.length > 0) o.write_shift(0, data, 'dbcs');
return o;
@ -72,7 +87,7 @@ function parse_RkNumber(data) {
var fX100 = b[0] & 1, fInt = b[0] & 2;
data.l+=4;
b[0] &= 0xFC;
var RK = fInt === 0 ? __readDoubleLE([0,0,0,0,b[0],b[1],b[2],b[3]],0) : __readInt32LE(b,0)>>2;
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;
}

@ -21,14 +21,21 @@ var CORE_PROPS = [
XMLNS.CORE_PROPS = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
RELS.CORE_PROPS = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties';
var CORE_PROPS_REGEX = (function() {
var r = new Array(CORE_PROPS.length);
for(var i = 0; i < CORE_PROPS.length; ++i) {
var f = CORE_PROPS[i];
var g = "(?:"+ f[0].substr(0,f[0].indexOf(":")) +":)"+ f[0].substr(f[0].indexOf(":")+1);
r[i] = new RegExp("<" + g + "[^>]*>(.*)<\/" + g + ">");
}
return r;
})();
function parse_core_props(data) {
var p = {};
for(var i = 0; i != CORE_PROPS.length; ++i) {
var f = CORE_PROPS[i];
var g = "(?:"+ f[0].substr(0,f[0].indexOf(":")) +":)"+ f[0].substr(f[0].indexOf(":")+1);
var cur = data.match(new RegExp("<" + g + "[^>]*>(.*)<\/" + g + ">"));
for(var i = 0; i < CORE_PROPS.length; ++i) {
var f = CORE_PROPS[i], cur = data.match(CORE_PROPS_REGEX[i]);
if(cur != null && cur.length > 0) p[f[1]] = cur[1];
if(f[2] === 'date' && p[f[1]]) p[f[1]] = new Date(p[f[1]]);
}
@ -47,7 +54,6 @@ var CORE_PROPS_XML_ROOT = writextag('cp:coreProperties', null, {
function cp_doit(f, g, h, o, p) {
if(p[f] != null || g == null || g === "") return;
if(typeof g !== 'string') g = String(g); /* TODO: remove */
p[f] = g;
o[o.length] = (h ? writextag(f,g,h) : writetag(f,g));
}

@ -59,13 +59,13 @@ function write_ext_props(cp, opts) {
o[o.length] = (EXT_PROPS_XML_ROOT);
EXT_PROPS.forEach(function(f) {
if(typeof cp[f[1]] === 'undefined') return;
if(cp[f[1]] === undefined) return;
var v;
switch(f[2]) {
case 'string': v = cp[f[1]]; break;
case 'bool': v = cp[f[1]] ? 'true' : 'false'; break;
}
if(typeof v !== 'undefined') o[o.length] = (W(f[0], v));
if(v !== undefined) o[o.length] = (W(f[0], v));
});
/* TODO: HeadingPairs, TitlesOfParts */

@ -178,7 +178,7 @@ function parse_sst_xml(data, opts) {
}
RELS.SST = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
var straywsregex = /^\s|\s$|[\t\n\r]/;
function write_sst_xml(sst, opts) {
if(!opts.bookSST) return "";
var o = [XML_HEADER];
@ -193,7 +193,7 @@ function write_sst_xml(sst, opts) {
if(s.r) sitag += s.r;
else {
sitag += "<t";
if(s.t.match(/^\s|\s$|[\t\n\r]/)) sitag += ' xml:space="preserve"';
if(s.t.match(straywsregex)) sitag += ' xml:space="preserve"';
sitag += ">" + escapexml(s.t) + "</t>";
}
sitag += "</si>";

@ -21,4 +21,19 @@ function parse_sst_bin(data, opts) {
return s;
}
function write_sst_bin(sst, opts) { }
function write_BrtBeginSst(sst, o) {
if(!o) o = new_buf(8);
o.write_shift(4, sst.Count);
o.write_shift(4, sst.Unique);
return o;
}
var write_BrtSSTItem = write_RichStr;
function write_sst_bin(sst, opts) {
var ba = buf_array();
write_record(ba, "BrtBeginSst", write_BrtBeginSst(sst));
for(var i = 0; i < sst.length; ++i) write_record(ba, "BrtSSTItem", write_BrtSSTItem(sst[i]));
write_record(ba, "BrtEndSst");
return ba.end();
}

@ -47,14 +47,14 @@ function parse_fills(t, opts) {
function parse_numFmts(t, opts) {
styles.NumberFmt = [];
var k = keys(SSF._table);
for(var i=0; i != k.length; ++i) styles.NumberFmt[k[i]] = SSF._table[k[i]];
for(var i=0; i < k.length; ++i) styles.NumberFmt[k[i]] = SSF._table[k[i]];
var m = t[0].match(tagregex);
for(i=0; i != m.length; ++i) {
for(i=0; i < m.length; ++i) {
var y = parsexmltag(m[i]);
switch(y[0]) {
case '<numFmts': case '</numFmts>': case '<numFmts/>': case '<numFmts>': break;
case '<numFmt': {
var f=unescapexml(y.formatCode), j=parseInt(y.numFmtId,10);
var f=unescapexml(utf8read(y.formatCode)), j=parseInt(y.numFmtId,10);
styles.NumberFmt[j] = f; if(j>0) SSF.load(f,j);
} break;
default: if(opts.WTF) throw 'unrecognized ' + y[0] + ' in numFmts';
@ -67,8 +67,8 @@ function write_numFmts(NF, opts) {
[[5,8],[23,26],[41,44],[63,66],[164,392]].forEach(function(r) {
for(var i = r[0]; i <= r[1]; ++i) if(NF[i] !== undefined) o[o.length] = (writextag('numFmt',null,{numFmtId:i,formatCode:escapexml(NF[i])}));
});
if(o.length === 1) return "";
o[o.length] = ("</numFmts>");
if(o.length === 2) return "";
o[0] = writextag('numFmts', null, { count:o.length-2 }).replace("/>", ">");
return o.join("");
}
@ -112,23 +112,29 @@ function write_cellXfs(cellXfs) {
}
/* 18.8 Styles CT_Stylesheet*/
function parse_sty_xml(data, opts) {
var parse_sty_xml= (function make_pstyx() {
var numFmtRegex = /<numFmts([^>]*)>.*<\/numFmts>/;
var cellXfRegex = /<cellXfs([^>]*)>.*<\/cellXfs>/;
var fillsRegex = /<fills([^>]*)>.*<\/fills>/;
return function parse_sty_xml(data, opts) {
/* 18.8.39 styleSheet CT_Stylesheet */
var t;
/* numFmts CT_NumFmts ? */
if((t=data.match(/<numFmts([^>]*)>.*<\/numFmts>/))) parse_numFmts(t, opts);
if((t=data.match(numFmtRegex))) parse_numFmts(t, opts);
/* fonts CT_Fonts ? */
// if((t=data.match(/<fonts([^>]*)>.*<\/fonts>/))) parse_fonts(t, opts);
/* fills CT_Fills */
if((t=data.match(/<fills([^>]*)>.*<\/fills>/))) parse_fills(t, opts);
if((t=data.match(fillsRegex))) parse_fills(t, opts);
/* borders CT_Borders ? */
/* cellStyleXfs CT_CellStyleXfs ? */
/* cellXfs CT_CellXfs ? */
if((t=data.match(/<cellXfs([^>]*)>.*<\/cellXfs>/))) parse_cellXfs(t, opts);
if((t=data.match(cellXfRegex))) parse_cellXfs(t, opts);
/* dxfs CT_Dxfs ? */
/* tableStyles CT_TableStyles ? */
@ -136,7 +142,8 @@ function parse_sty_xml(data, opts) {
/* extLst CT_ExtensionList ? */
return styles;
}
};
})();
var STYLES_XML_ROOT = writextag('styleSheet', null, {
'xmlns': XMLNS.main[0],
@ -146,10 +153,8 @@ var STYLES_XML_ROOT = writextag('styleSheet', null, {
RELS.STY = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
function write_sty_xml(wb, opts) {
var o = [], p = {}, w;
o[o.length] = (XML_HEADER);
o[o.length] = (STYLES_XML_ROOT);
if((w = write_numFmts(wb.SSF))) o[o.length] = (w);
var o = [XML_HEADER, STYLES_XML_ROOT], w;
if((w = write_numFmts(wb.SSF)) != null) o[o.length] = w;
o[o.length] = ('<fonts count="1"><font><sz val="12"/><color theme="1"/><name val="Calibri"/><family val="2"/><scheme val="minor"/></font></fonts>');
o[o.length] = ('<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
o[o.length] = ('<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>');

@ -113,4 +113,21 @@ function parse_sty_bin(data, opts) {
return styles;
}
function write_sty_bin(data, opts) { }
/* [MS-XLSB] 2.1.7.50 Styles */
function write_sty_bin(data, opts) {
var ba = buf_array();
write_record(ba, "BrtBeginStyleSheet");
/* [FMTS] */
/* [FONTS] */
/* [FILLS] */
/* [BORDERS] */
/* CELLSTYLEXFS */
/* CELLXFS*/
/* STYLES */
/* DXFS */
/* TABLESTYLES */
/* [COLORPALETTE] */
/* FRTSTYLESHEET*/
write_record(ba, "BrtEndStyleSheet");
return ba.end();
}

@ -4,14 +4,14 @@ var _ssfopts = {}; // spreadsheet formatting options
RELS.WS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
function get_sst_id(sst, str) {
for(var i = 0; i != sst.length; ++i) if(sst[i].t === str) { sst.Count ++; return i; }
sst[sst.length] = {t:str}; sst.Count ++; sst.Unique ++; return sst.length-1;
for(var i = 0, len = sst.length; i < len; ++i) if(sst[i].t === str) { sst.Count ++; return i; }
sst[len] = {t:str}; sst.Count ++; sst.Unique ++; return len;
}
function get_cell_style(styles, cell, opts) {
var z = opts.revssf[cell.z != null ? cell.z : "General"];
for(var i = 0; i != styles.length; ++i) if(styles[i].numFmtId === z) return i;
styles[styles.length] = {
for(var i = 0, len = styles.length; i != len; ++i) if(styles[i].numFmtId === z) return i;
styles[len] = {
numFmtId:z,
fontId:0,
fillId:0,
@ -19,7 +19,7 @@ function get_cell_style(styles, cell, opts) {
xfId:0,
applyNumberFormat:1
};
return styles.length-1;
return len;
}
function safe_format(p, fmtid, fillid, opts) {

@ -1,10 +1,12 @@
function parse_ws_xml_dim(ws, s) {
var d = safe_decode_range(s);
if(d.s.r<=d.e.r && d.s.c<=d.e.c && d.s.r>=0 && d.e.r>=0) ws["!ref"] = encode_range(d);
if(d.s.r<=d.e.r && d.s.c<=d.e.c && d.s.r>=0 && d.s.c>=0) ws["!ref"] = encode_range(d);
}
var mergecregex = /<mergeCell ref="[A-Z0-9:]+"\s*\/>/g;
var sheetdataregex = /<(?:\w+:)?sheetData>([^\u2603]*)<\/(?:\w+:)?sheetData>/;
var hlinkregex = /<hyperlink[^>]*\/>/g;
var dimregex = /"(\w*:\w*)"/;
var colregex = /<col[^>]*\/>/g;
/* 18.3 Worksheets */
function parse_ws_xml(data, opts, rels) {
if(!data) return data;
@ -14,7 +16,7 @@ function parse_ws_xml(data, opts, rels) {
/* 18.3.1.35 dimension CT_SheetDimension ? */
var ridx = data.indexOf("<dimension");
if(ridx > 0) {
var ref = data.substr(ridx,50).match(/"(\w*:\w*)"/);
var ref = data.substr(ridx,50).match(dimregex);
if(ref != null) parse_ws_xml_dim(s, ref[1]);
}
@ -30,7 +32,7 @@ function parse_ws_xml(data, opts, rels) {
var columns = [];
if(opts.cellStyles && data.indexOf("</cols>")!==-1) {
/* 18.3.1.13 col CT_Col */
var cols = data.match(/<col[^>]*\/>/g);
var cols = data.match(colregex);
parse_ws_xml_cols(columns, cols);
}
@ -126,15 +128,15 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
var os = get_cell_style(opts.cellXfs, cell, opts);
if(os !== 0) o.s = os;
switch(cell.t) {
case 's': case 'str':
case 'n': break;
case 'b': o.t = "b"; break;
case 'e': o.t = "e"; break;
default:
if(opts.bookSST) {
v = writetag('v', ''+get_sst_id(opts.Strings, cell.v));
o.t = "s"; break;
}
o.t = "str"; break;
case 'n': break;
case 'b': o.t = "b"; break;
case 'e': o.t = "e"; break;
}
return writextag('c', v, o);
}
@ -149,20 +151,22 @@ return function parse_ws_xml_data(sdata, s, opts, guess) {
var tag;
var sstr;
var fmtid = 0, fillid = 0, do_format = Array.isArray(styles.CellXf), cf;
for(var marr = sdata.split(rowregex), mt = 0; mt != marr.length; ++mt) {
for(var marr = sdata.split(rowregex), mt = 0, marrlen = marr.length; mt != marrlen; ++mt) {
x = marr[mt].trim();
if(x.length === 0) continue;
var xlen = x.length;
if(xlen === 0) continue;
/* 18.3.1.73 row CT_Row */
for(ri = 0; ri != x.length; ++ri) if(x.charCodeAt(ri) === 62) break; ++ri;
for(ri = 0; ri < xlen; ++ri) if(x.charCodeAt(ri) === 62) break; ++ri;
tag = parsexmltag(x.substr(0,ri), true);
if(opts.sheetRows && opts.sheetRows < +tag.r) continue;
if(guess.s.r > tag.r - 1) guess.s.r = tag.r - 1;
if(guess.e.r < tag.r - 1) guess.e.r = tag.r - 1;
var tagr = parseInt(tag.r, 10);
if(opts.sheetRows && opts.sheetRows < tagr) continue;
if(guess.s.r > tagr - 1) guess.s.r = tagr - 1;
if(guess.e.r < tagr - 1) guess.e.r = tagr - 1;
/* 18.3.1.4 c CT_Cell */
cells = x.substr(ri).split(cellregex);
for(ri = 0; ri != cells.length; ++ri) {
for(ri = 1, cellen = cells.length; ri != cellen; ++ri) {
x = cells[ri].trim();
if(x.length === 0) continue;
cref = x.match(rregex); idx = ri; i=0; cc=0;
@ -221,7 +225,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess) {
cf = styles.CellXf[tag.s];
if(cf != null) {
if(cf.numFmtId != null) fmtid = cf.numFmtId;
if(opts.cellStyles && cf.fillId != undefined) fillid = cf.fillId;
if(opts.cellStyles && cf.fillId != null) fillid = cf.fillId;
}
}
safe_format(p, fmtid, fillid, opts);
@ -231,17 +235,17 @@ return function parse_ws_xml_data(sdata, s, opts, guess) {
}; })();
function write_ws_xml_data(ws, opts, idx, wb) {
var o = [], r = [], range = safe_decode_range(ws['!ref']), cell, ref, rr = "", cols = [];
for(var R = range.s.r; R <= range.e.r; ++R) {
var o = [], r = [], range = safe_decode_range(ws['!ref']), cell, ref, rr = "", cols = [], R, C;
for(C = range.s.c; C <= range.e.c; ++C) cols[C] = encode_col(C);
for(R = range.s.r; R <= range.e.r; ++R) {
r = [];
rr = encode_row(R);
for(var C = range.s.c; C <= range.e.c; ++C) {
if(R === range.s.r) cols[C] = encode_col(C);
for(C = range.s.c; C <= range.e.c; ++C) {
ref = cols[C] + rr;
if(!ws[ref]) continue;
if((cell = write_ws_xml_cell(ws[ref], ref, ws, opts, idx, wb))) r.push(cell);
if(ws[ref] === undefined) continue;
if((cell = write_ws_xml_cell(ws[ref], ref, ws, opts, idx, wb)) != null) r.push(cell);
}
if(r.length) o[o.length] = (writextag('row', r.join(""), {r:rr}));
if(r.length > 0) o[o.length] = (writextag('row', r.join(""), {r:rr}));
}
return o.join("");
}
@ -253,13 +257,18 @@ var WS_XML_ROOT = writextag('worksheet', null, {
function write_ws_xml(idx, opts, wb) {
var o = [XML_HEADER, WS_XML_ROOT];
var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {}, sidx = 0, rdata = "";
o[o.length] = (writextag('dimension', null, {'ref': ws['!ref'] || 'A1'}));
if((ws['!cols']||[]).length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols']));
sidx = o.length;
o[o.length] = (writextag('sheetData', null));
if(ws['!ref']) rdata = write_ws_xml_data(ws, opts, idx, wb);
if(rdata.length) o[o.length] = (rdata);
var s = wb.SheetNames[idx], sidx = 0, rdata = "";
var ws = wb.Sheets[s];
if(ws === undefined) ws = {};
var ref = ws['!ref']; if(ref === undefined) ref = 'A1';
o[o.length] = (writextag('dimension', null, {'ref': ref}));
if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols']));
o[sidx = o.length] = '<sheetData/>';
if(ws['!ref'] !== undefined) {
rdata = write_ws_xml_data(ws, opts, idx, wb);
if(rdata.length > 0) o[o.length] = (rdata);
}
if(o.length>sidx+1) { o[o.length] = ('</sheetData>'); o[sidx]=o[sidx].replace("/>",">"); }
if(o.length>2) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); }

@ -25,6 +25,11 @@ function parse_BrtCellBlank(data, length) {
var cell = parse_Cell(data);
return [cell];
}
function write_BrtCellBlank(cell, val, o) {
if(o == null) o = new_buf(8);
return write_Cell(val, o);
}
/* [MS-XLSB] 2.4.304 BrtCellBool */
function parse_BrtCellBool(data, length) {
@ -299,6 +304,15 @@ function parse_ws_bin(data, opts, rels) {
case 'BrtCustomFilter': break;
case 'BrtEndCustomFilters': break;
/* Smart Tags */
case 'BrtBeginSmartTags': break;
case 'BrtBeginCellSmartTags': break;
case 'BrtBeginCellSmartTag': break;
case 'BrtCellSmartTagProperty': break;
case 'BrtEndCellSmartTag': break;
case 'BrtEndCellSmartTags': break;
case 'BrtEndSmartTags': break;
/* Cell Watch */
case 'BrtBeginCellWatches': break;
case 'BrtCellWatch': break;
@ -332,14 +346,47 @@ function parse_ws_bin(data, opts, rels) {
return s;
}
/* TODO: something useful -- this is a stub */
function write_ws_bin_cell(ba, cell, R, C, opts) {
if(cell.v === undefined) return "";
var vv = "";
switch(cell.t) {
case 'b': vv = cell.v ? "1" : "0"; break;
case 'n': case 'e': vv = ''+cell.v; break;
default: vv = cell.v; break;
}
var o = {r:R, c:C};
/* TODO: cell style */
o.s = get_cell_style(opts.cellXfs, cell, opts);
switch(cell.t) {
case 's': case 'str':
if(opts.bookSST) {
vv = get_sst_id(opts.Strings, cell.v);
o.t = "s"; break;
}
o.t = "str"; break;
case 'n': break;
case 'b': o.t = "b"; break;
case 'e': o.t = "e"; break;
}
write_record(ba, "BrtCellBlank", write_BrtCellBlank(cell, o));
}
function write_CELLTABLE(ba, ws, idx, opts, wb) {
var r = safe_decode_range(ws['!ref'] || "A1");
var range = safe_decode_range(ws['!ref'] || "A1"), ref, rr = "", cols = [];
write_record(ba, 'BrtBeginSheetData');
for(var i = r.s.r; i <= r.e.r; ++i) {
for(var R = range.s.r; R <= range.e.r; ++R) {
rr = encode_row(R);
/* [ACCELLTABLE] */
/* BrtRowHdr */
/* *16384CELL */
for(var C = range.s.c; C <= range.e.c; ++C) {
/* *16384CELL */
if(R === range.s.r) cols[C] = encode_col(C);
ref = cols[C] + rr;
if(!ws[ref]) continue;
/* write cell */
write_ws_bin_cell(ba, ws[ref], R, C, opts);
}
}
write_record(ba, 'BrtEndSheetData');
}

@ -1,4 +1,5 @@
/* 18.2 Workbook */
var wbnsregex = /<\w+:workbook/;
function parse_wb_xml(data, opts) {
var wb = { AppVersion:{}, WBProps:{}, WBView:[], Sheets:[], CalcPr:{}, xmlns: "" };
var pass = false, xmlns = "xmlns";
@ -9,7 +10,7 @@ function parse_wb_xml(data, opts) {
/* 18.2.27 workbook CT_Workbook 1 */
case '<workbook':
if(x.match(/<\w+:workbook/)) xmlns = "xmlns" + x.match(/<(\w+):/)[1];
if(x.match(wbnsregex)) xmlns = "xmlns" + x.match(/<(\w+):/)[1];
wb.xmlns = y[xmlns];
break;
case '</workbook>': break;

@ -82,11 +82,18 @@ function parse_wb_bin(data, opts) {
case 'BrtBeginWebPubItem': break;
case 'BrtEndWebPubItem': break;
case 'BrtEndWebPubItems': break;*/
/* Smart Tags */
case 'BrtBeginSmartTagTypes': break;
case 'BrtSmartTagType': break;
case 'BrtEndSmartTagTypes': break;
case 'BrtFRTBegin': pass = true; break;
case 'BrtFRTArchID$': break;
case 'BrtWorkBookPr15': break;
case 'BrtFRTEnd': pass = false; break;
case 'BrtEndBook': break;
default: if(!pass) throw new Error("Unexpected record " + R.n);
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + R.n);
}
});

@ -2,7 +2,7 @@ function fix_opts_func(defaults) {
return function fix_opts(opts) {
for(var i = 0; i != defaults.length; ++i) {
var d = defaults[i];
if(typeof opts[d[0]] === 'undefined') opts[d[0]] = d[1];
if(opts[d[0]] === undefined) opts[d[0]] = d[1];
if(d[2] === 'n') opts[d[0]] = Number(opts[d[0]]);
}
};

@ -1,7 +1,7 @@
function readSync(data, opts) {
var zip, d = data;
var o = opts||{};
if(!o.type) o.type = (typeof Buffer !== 'undefined' && data instanceof Buffer) ? "buffer" : "base64";
if(!o.type) o.type = (has_buf && Buffer.isBuffer(data)) ? "buffer" : "base64";
switch(o.type) {
case "base64": zip = new jszip(d, { base64:true }); break;
case "binary": zip = new jszip(d, { base64:false }); break;

@ -23,27 +23,28 @@ function encode_range(cs,ce) {
function safe_decode_range(range) {
var o = {s:{c:0,r:0},e:{c:0,r:0}};
var idx = 0, i = 0, cc = 0;
for(idx = 0; i != range.length; ++i) {
var len = range.length;
for(idx = 0; i < len; ++i) {
if((cc=range.charCodeAt(i)-64) < 1 || cc > 26) break;
idx = 26*idx + cc;
}
o.s.c = --idx;
for(idx = 0; i != range.length; ++i) {
for(idx = 0; i < len; ++i) {
if((cc=range.charCodeAt(i)-48) < 0 || cc > 9) break;
idx = 10*idx + cc;
}
o.s.r = --idx;
if(i === range.length || range.charCodeAt(++i) === 58) { o.e.c=o.s.c; o.e.r=o.s.r; return o; }
if(i === len || range.charCodeAt(++i) === 58) { o.e.c=o.s.c; o.e.r=o.s.r; return o; }
for(idx = 0; i != range.length; ++i) {
for(idx = 0; i != len; ++i) {
if((cc=range.charCodeAt(i)-64) < 1 || cc > 26) break;
idx = 26*idx + cc;
}
o.e.c = --idx;
for(idx = 0; i != range.length; ++i) {
for(idx = 0; i != len; ++i) {
if((cc=range.charCodeAt(i)-48) < 0 || cc > 9) break;
idx = 10*idx + cc;
}
@ -66,9 +67,9 @@ function format_cell(cell, v) {
function sheet_to_json(sheet, opts){
var val, row, range, header = 0, offset = 1, r, hdr = [], isempty, R, C, v;
var out = [];
var o = opts != null ? opts : {};
if(!sheet || !sheet["!ref"]) return out;
var raw = o.raw;
if(sheet == null || sheet["!ref"] == null) return [];
range = o.range !== undefined ? o.range : sheet["!ref"];
if(o.header === 1) header = 1;
else if(o.header === "A") header = 2;
@ -80,7 +81,9 @@ function sheet_to_json(sheet, opts){
}
if(header > 0) offset = 0;
var rr = encode_row(r.s.r);
var cols = [];
var cols = new Array(r.e.c-r.s.c+1);
var out = new Array(r.e.r-r.s.r-offset+1);
var outi = 0;
for(C = r.s.c; C <= r.e.c; ++C) {
cols[C] = encode_col(C);
val = sheet[cols[C] + rr];
@ -89,7 +92,7 @@ function sheet_to_json(sheet, opts){
case 2: hdr[C] = cols[C]; break;
case 3: hdr[C] = o.header[C - r.s.c]; break;
default:
if(!val) continue;
if(val === undefined) continue;
hdr[C] = format_cell(val);
}
}
@ -100,7 +103,7 @@ function sheet_to_json(sheet, opts){
row = header === 1 ? [] : Object.create({ __rowNum__ : R });
for (C = r.s.c; C <= r.e.c; ++C) {
val = sheet[cols[C] + rr];
if(!val || !val.t) continue;
if(val === undefined || val.t === undefined) continue;
v = val.v;
switch(val.t){
case 'e': continue;
@ -109,16 +112,17 @@ function sheet_to_json(sheet, opts){
default: throw 'unrecognized type ' + val.t;
}
if(v !== undefined) {
row[hdr[C]] = o.raw ? v : format_cell(val,v);
row[hdr[C]] = raw ? v : format_cell(val,v);
isempty = false;
}
}
if(!isempty) out.push(row);
if(isempty === false) out[outi++] = row;
}
out.length = outi;
return out;
}
function sheet_to_row_object_array(sheet, opts) { return sheet_to_json(sheet, opts == null ? opts : {}); }
function sheet_to_row_object_array(sheet, opts) { return sheet_to_json(sheet, opts != null ? opts : {}); }
function sheet_to_csv(sheet, opts) {
var out = "", txt = "", qreg = /"/g;