version bump 0.7.4: I/O improvements

- basic style + theme parsing, option .cellStyles (h/t @eladxxx)
- more XLSB writing stubs
- correct resolution of .xml/.bin files
- sheet_to_json improvements from js-xls
o opts.header = 1 for array of arrays
o opts.header = 'A' for spreadsheet column labels
o custom opts.header array for custom labels
o opts.range = n starts from row n
o opts.range = range restricts writer to work within the specified range
- Makefile adapted to work with cygwin on windows
This commit is contained in:
SheetJS 2014-05-29 18:30:03 -04:00
parent c91e94dbbb
commit a3d9c4d9ac
34 changed files with 1557 additions and 219 deletions

@ -1,12 +1,12 @@
LIB=xlsx
DEPS=$(wildcard bits/*.js)
DEPS=$(sort $(wildcard bits/*.js))
TARGET=$(LIB).js
FMT=xlsx xlsm xlsb misc full
REQS=jszip.js
ADDONS=dist/cpexcel.js
$(TARGET): $(DEPS)
cat $^ > $@
cat $^ | tr -d '\15\32' > $@
bits/01_version.js: package.json
echo "XLSX.version = '"`grep version package.json | awk '{gsub(/[^0-9a-z\.-]/,"",$$2); print $$2}'`"';" > $@

@ -11,7 +11,7 @@ program
.option('-s, --sheet <sheet>', 'print specified sheet (default first sheet)')
.option('-l, --list-sheets', 'list sheet names and exit')
.option('-o, --output <file>', 'output to specified file')
/*.option('-B, --xlsb', 'emit XLSB to <sheetname> or <file>.xlsb') */
.option('-B, --xlsb', 'emit XLSB to <sheetname> or <file>.xlsb')
.option('-M, --xlsm', 'emit XLSM to <sheetname> or <file>.xlsm')
.option('-X, --xlsx', 'emit XLSX to <sheetname> or <file>.xlsx')
.option('-S, --formulae', 'print formulae')

@ -1 +1 @@
XLSX.version = '0.7.3';
XLSX.version = '0.7.4';

@ -6,6 +6,7 @@ function evert(obj, arr) {
var o = {};
keys(obj).forEach(function(k) {
if(!obj.hasOwnProperty(k)) return;
if(arr && typeof arr === "string") o[obj[k][arr]] = k;
if(!arr) o[obj[k]] = k;
else (o[obj[k]]=o[obj[k]]||[]).push(k);
});

@ -34,7 +34,7 @@ function unescapexml(text){
function escapexml(text){
var s = text + '';
rencstr.forEach(function(y){s=s.replace(new RegExp(y,'g'), rencoding[y]);});
s = s.replace(/[\u0000-\u0007]/g,function(s) { return "_x" + ("0000"+_ord(s).toString(16)).substr(-4) + "_";}); /* TODO: verify range */
s = s.replace(/[\u0000-\u0008\u000b-\u001f]/g,function(s) { return "_x" + ("0000"+_ord(s).toString(16)).substr(-4) + "_";});
return s;
}
@ -83,8 +83,7 @@ function parseVector(data) {
function writetag(f,g) {return '<' + f + (g.match(/(^\s|\s$|\n)/)?' xml:space="preserve"' : "") + '>' + g + '</' + f + '>';}
/*jshint -W041 */
function writextag(f,g,h) { return '<' + f + (h != null ? keys(h).map(function(k) { return " " + k + '="' + h[k] + '"';}).join("") : "") + (g == null ? "/" : (g.match(/(^\s|\s$|\n)/)?' xml:space="preserve"' : "") + '>' + g + '</' + f) + '>';}
function writextag(f,g,h) { return '<' + f + (h !== null && h !== undefined ? keys(h).map(function(k) { return " " + k + '="' + h[k] + '"';}).join("") : "") + (g === null || g === undefined ? "/" : (g.match(/(^\s|\s$|\n)/)?' xml:space="preserve"' : "") + '>' + g + '</' + f) + '>';}
function write_w3cdtf(d, t) { try { return d.toISOString().replace(/\.\d*/,""); } catch(e) { if(t) throw e; } }

@ -16,16 +16,15 @@ function readIEEE754(buf, idx, isLE, nl, ml) {
return (s ? -1 : 1) * m * Math.pow(2, e - ml);
}
var __toBuffer;
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") {
Buffer.prototype.hexlify= function() { return this.toString('hex'); };
__toBuffer = function(bufs) { return Buffer.concat(bufs[0]); };
} else {
__toBuffer = function(bufs) {
var x = [];
for(var i = 0; i != bufs[0].length; ++i) { x = x.concat(bufs[0][i]); }
return x;
};
__toBuffer = function(bufs) { try { return Buffer.concat(bufs[0]); } catch(e) { return ___toBuffer(bufs);} };
}
var __readUInt8 = function(b, idx) { return b.readUInt8 ? b.readUInt8(idx) : b[idx]; };
@ -35,9 +34,10 @@ var __readUInt32LE = function(b, idx) { return b.readUInt32LE ? b.readUInt32LE(i
var __readInt32LE = function(b, idx) { if(b.readInt32LE) return b.readInt32LE(idx); var u = __readUInt32LE(b,idx); if(!(u & 0x80000000)) return u; return (0xffffffff - u + 1) * -1; };
var __readDoubleLE = function(b, idx) { return b.readDoubleLE ? b.readDoubleLE(idx) : readIEEE754(b, idx||0);};
var __hexlify = function(b,l) { if(b.hexlify) return b.hexlify((b.l||0), (b.l||0)+l); return b.slice(b.l||0,(b.l||0)+16).map(function(x){return (x<16?"0":"") + x.toString(16);}).join(""); };
function ReadShift(size, t) {
var o = "", oo = [], w, vv, i, loc; t = t || 'u';
var o="", oo=[], w, vv, i, loc; t = t || 'u';
if(size === 'ieee754') { size = 8; t = 'f'; }
switch(size) {
case 1: o = __readUInt8(this, this.l); break;
@ -45,28 +45,56 @@ function ReadShift(size, t) {
case 4: o = __readUInt32LE(this, this.l); break;
case 8: if(t === 'f') { o = __readDoubleLE(this, this.l); break; }
/* falls through */
case 16: o = this.toString('hex', this.l,this.l+size); break;
case 16: o = __hexlify(this, 16); break;
case 'dbcs': size = 2*t; loc = this.l;
for(i = 0; i != t; ++i) {
oo.push(_getchar(__readUInt16LE(this, loc)));
loc+=2;
} o = oo.join(""); break;
case 'sbcs': size = t; o = ""; loc = this.l;
for(i = 0; i != t; ++i) {
o += _getchar(__readUInt8(this, loc));
loc+=1;
} break;
}
this.l+=size; return o;
}
function prep_blob(blob, pos) {
blob.read_shift = ReadShift.bind(blob);
function WriteShift(t, val, f) {
var size, i;
if(t === 'ieee754') { f = 'f'; t = 8; }
switch(t) {
case 1: size = 1; this.writeUInt8(val, this.l); 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 */
case 16: break;
case -4: size = 4; this.writeInt32LE(val, this.l); break;
case 'dbcs':
for(i = 0; i != val.length; ++i) this.writeUInt16LE(val.charCodeAt(i), this.l + 2 * i);
size = 2 * val.length;
break;
}
this.l += size; return this;
}
function prep_blob(blob, pos, w) {
blob.l = pos || 0;
var read = ReadShift.bind(blob);
return [read];
if(w) {
var write = WriteShift.bind(blob);
blob.write_shift = write;
return [write];
} else {
var read = ReadShift.bind(blob);
blob.read_shift = read;
return [read];
}
}
function parsenoop(blob, length) { blob.l += length; }
function writenoop(blob, length) { blob.l += length; }
var new_buf = function(sz) {
var o = typeof Buffer !== 'undefined' ? new Buffer(sz) : new Array(sz);
prep_blob(o, 0, true);
return o;
};
var is_buf = function(a) { return (typeof Buffer !== 'undefined' && a instanceof Buffer) || Array.isArray(a); };

@ -15,6 +15,52 @@ var recordhopper = function(data, cb, opts) {
};
/* control buffer usage for fixed-length buffers */
var blobhopper = function() {
var bufs = [];
var buf_array = function() {
var bufs = [], blksz = 2048;
var newblk = function(sz) {
var o = new_buf(sz || blksz);
prep_blob(o, 0, true);
return o;
};
var curbuf = newblk();
var endbuf = function() {
curbuf.length = curbuf.l;
if(curbuf.length > 0) bufs.push(curbuf);
curbuf = null;
};
var next = function(sz) {
if(sz < curbuf.length - curbuf.l) return curbuf;
endbuf();
return (curbuf = newblk(Math.max(sz+1, blksz)));
};
var end = function() {
endbuf();
return __toBuffer([bufs]);
};
var push = function(buf) { endbuf(); curbuf = buf; next(); };
return { next:next, push:push, end:end, _bufs:bufs };
};
var write_record = function(ba, type, payload, length) {
var t = evert_RE[type], l;
if(!length) length = RecordEnum[t].p || (payload||[]).length || 0;
l = 1 + (t >= 0x80 ? 1 : 0) + 1 + length;
if(length >= 0x80) ++l; if(length >= 0x4000) ++l; if(length >= 0x200000) ++l;
var o = ba.next(l);
if(t <= 0x7F) o.write_shift(1, t);
else {
o.write_shift(1, (t & 0x7F) + 0x80);
o.write_shift(1, (t >> 7));
}
for(var i = 0; i != 4; ++i) {
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);
};

@ -41,8 +41,34 @@ function parse_Cell(data) {
/* [MS-XLSB] 2.5.21 */
var parse_CodeName = function(data, length) { return parse_XLWideString(data, length); };
/* [MS-XLSB] 2.5.166 */
var parse_XLNullableWideString = function(data) {
var cchCharacters = data.read_shift(4);
return cchCharacters === 0 || cchCharacters === 0xFFFFFFFF ? "" : data.read_shift('dbcs', cchCharacters);
};
var write_XLNullableWideString = function(data, o) {
if(!o) o = new_buf(127);
o.write_shift(4, data.length || 0xFFFFFFFF);
if(data.length > 0) o.write_shift('dbcs', data);
return o;
};
/* [MS-XLSB] 2.5.168 */
var parse_XLWideString = function(data) {
var cchCharacters = data.read_shift(4);
return cchCharacters === 0 ? "" : data.read_shift('dbcs', cchCharacters);
};
var write_XLWideString = function(data, o) {
if(!o) o = new_buf(127);
o.write_shift(4, data.length);
if(data.length > 0) o.write_shift('dbcs', data);
return o;
};
/* [MS-XLSB] 2.5.114 */
var parse_RelID = function(data, length) { return parse_XLNullableWideString(data, length); };
var parse_RelID = parse_XLNullableWideString;
var write_RelID = write_XLNullableWideString;
/* [MS-XLSB] 2.5.122 */
function parse_RkNumber(data) {
@ -64,20 +90,18 @@ var parse_UncheckedRfX = function(data) {
return cell;
};
/* [MS-XLSB] 2.5.166 */
var parse_XLNullableWideString = function(data) {
var cchCharacters = data.read_shift(4);
return cchCharacters === 0 || cchCharacters === 0xFFFFFFFF ? "" : data.read_shift('dbcs', cchCharacters);
};
/* [MS-XLSB] 2.5.168 */
var parse_XLWideString = function(data) {
var cchCharacters = data.read_shift(4);
return cchCharacters === 0 ? "" : data.read_shift('dbcs', cchCharacters);
var write_UncheckedRfX = function(r, o) {
if(!o) o = new_buf(16);
o.write_shift(4, r.s.r);
o.write_shift(4, r.e.r);
o.write_shift(4, r.s.c);
o.write_shift(4, r.e.c);
return o;
};
/* [MS-XLSB] 2.5.171 */
function parse_Xnum(data, length) { return data.read_shift('ieee754'); }
function write_Xnum(data, o) { return (o || new_buf(8)).write_shift('ieee754', data); }
/* [MS-XLSB] 2.5.198.2 */
var BErr = {
@ -108,7 +132,7 @@ function parse_BrtColor(data, length) {
out.bAlpha = read(1);
}
/* [MS-XLSB 2.5.52 */
/* [MS-XLSB] 2.5.52 */
function parse_FontFlags(data, length) {
var d = data.read_shift(1);
data.l++;

@ -208,6 +208,7 @@ var CTYPE_XML_ROOT = writextag('Types', null, {
var CTYPE_DEFAULTS = [
['xml', 'application/xml'],
['bin', 'application/vnd.ms-excel.sheet.binary.macroEnabled.main'],
['rels', type2ct.rels[0]]
].map(function(x) {
return writextag('Default', null, {'Extension':x[0], 'ContentType': x[1]});

@ -15,7 +15,7 @@ function parse_fills(t, opts) {
case '<patternFill':
if(y.patternType) fill.patternType = y.patternType;
break;
case '<patternFill/>': break;
case '<patternFill/>': case '</patternFill>': break;
/* 18.8.3 bgColor CT_Color */
case '<bgColor':
@ -23,7 +23,7 @@ function parse_fills(t, opts) {
if(y.indexed) fill.bgColor.indexed = parseInt(y.indexed);
if(y.theme) fill.bgColor.theme = parseInt(y.theme);
if(y.tint) fill.bgColor.tint = Number(y.tint);
/* Excel uses 8 character RGB strings? */
/* Excel uses ARGB strings */
if(y.rgb) fill.bgColor.rgb = y.rgb.substring(y.rgb.length - 6);
break;
case '</bgColor>': break;
@ -33,7 +33,7 @@ function parse_fills(t, opts) {
if(!fill.fgColor) fill.fgColor = {};
if(y.theme) fill.fgColor.theme = parseInt(y.theme);
if(y.tint) fill.fgColor.tint = Number(y.tint);
/* Excel uses 8 character RGB strings? */
/* Excel uses ARGB strings */
if(y.rgb) fill.fgColor.rgb = y.rgb.substring(y.rgb.length - 6);
break;
case '</fgColor>': break;
@ -149,8 +149,8 @@ function write_sty_xml(wb, opts) {
o.push(XML_HEADER);
o.push(STYLES_XML_ROOT);
if((w = write_numFmts(wb.SSF))) o.push(w);
o.push('<fonts count="1"><font><sz val="12"/><color theme="1"/><name val="Calibri"/><family val="2"/><scheme val="minor"/></font></fonts>');
o.push('<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
o.push('<fonts count="1"><font><sz val="12"/><color theme="1"/><name val="Calibri"/><family val="2"/><scheme val="minor"/></font></fonts>');
o.push('<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
o.push('<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>');
o.push('<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>');
if((w = write_cellXfs(opts.cellXfs))) o.push(w);

@ -64,8 +64,6 @@ function parse_sty_bin(data, opts) {
}
break; /* TODO */
case 'BrtStyle': break; /* TODO */
case 'BrtRowHdr': break; /* TODO */
case 'BrtCellMeta': break; /* ?? */
case 'BrtDXF': break; /* TODO */
case 'BrtMRUColor': break; /* TODO */
case 'BrtIndexedColor': break; /* TODO */
@ -115,3 +113,5 @@ function parse_sty_bin(data, opts) {
});
return styles;
}
function write_sty_bin(data, opts) { }

@ -113,7 +113,7 @@ function parse_clrScheme(t, opts) {
t[0].match(/<[^>]*>/g).forEach(function(x) {
var y = parsexmltag(x);
switch(y[0]) {
case '<a:clrScheme': case '<<a:clrScheme>': case '</<a:clrScheme>': break;
case '<a:clrScheme': case '</a:clrScheme>': break;
/* 20.1.2.3.32 srgbClr CT_SRgbColor */
case '<a:srgbClr': color.rgb = y.val; break;

@ -15,3 +15,4 @@ function parse_cc_xml(data, opts) {
return d;
}
function write_cc_xml(data, opts) { }

@ -26,3 +26,5 @@ function parse_cc_bin(data, opts) {
});
return out;
}
function write_cc_bin(data, opts) { }

@ -23,3 +23,5 @@ function parse_comments_xml(data, opts) {
});
return commentList;
}
function write_comments_xml(data, opts) { }

@ -42,3 +42,5 @@ function parse_comments_bin(data, opts) {
});
return out;
}
function write_comments_bin(data, opts) { }

@ -9,6 +9,7 @@ var parse_BrtRowHdr = function(data, length) {
/* [MS-XLSB] 2.4.812 BrtWsDim */
var parse_BrtWsDim = parse_UncheckedRfX;
var write_BrtWsDim = write_UncheckedRfX;
/* [MS-XLSB] 2.4.815 BrtWsProp */
var parse_BrtWsProp = function(data, length) {
@ -334,4 +335,61 @@ var parse_ws_bin = function(data, opts, rels) {
return s;
};
var write_ws_bin = function(wb, opts, rels) {};
function write_CELLTABLE(ba, ws, idx, opts, wb) {
var r = decode_range(ws['!ref'] || "A1");
write_record(ba, 'BrtBeginSheetData');
for(var i = r.s.r; i <= r.e.r; ++i) {
/* [ACCELLTABLE] */
/* BrtRowHdr */
/* *16384CELL */
}
write_record(ba, 'BrtEndSheetData');
}
var write_ws_bin = function(idx, opts, wb) {
var ba = buf_array();
var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {};
var r = decode_range(ws['!ref'] || "A1");
write_record(ba, "BrtBeginSheet");
/* [BrtWsProp] */
write_record(ba, "BrtWsDim", write_BrtWsDim(r));
/* [WSVIEWS2] */
/* [WSFMTINFO] */
/* *COLINFOS */
write_CELLTABLE(ba, ws, idx, opts, wb);
/* [BrtSheetCalcProp] */
/* [[BrtSheetProtectionIso] BrtSheetProtection] */
/* *([BrtRangeProtectionIso] BrtRangeProtection) */
/* [SCENMAN] */
/* [AUTOFILTER] */
/* [SORTSTATE] */
/* [DCON] */
/* [USERSHVIEWS] */
/* [MERGECELLS] */
/* [BrtPhoneticInfo] */
/* *CONDITIONALFORMATTING */
/* [DVALS] */
/* *BrtHLink */
/* [BrtPrintOptions] */
/* [BrtMargins] */
/* [BrtPageSetup] */
/* [HEADERFOOTER] */
/* [RWBRK] */
/* [COLBRK] */
/* *BrtBigName */
/* [CELLWATCHES] */
/* [IGNOREECS] */
/* [SMARTTAGS] */
/* [BrtDrawing] */
/* [BrtLegacyDrawing] */
/* [BrtLegacyDrawingHF] */
/* [BrtBkHim] */
/* [OLEOBJECTS] */
/* [ACTIVEXCONTROLS] */
/* [WEBPUBITEMS] */
/* [LISTPARTS] */
/* FRTWORKSHEET */
write_record(ba, "BrtEndSheet");
return ba.end();
};

@ -7,6 +7,36 @@ var parse_BrtBundleSh = function(data, length) {
z.name = parse_XLWideString(data);
return z;
};
var write_BrtBundleSh = function(data, o) {
if(!o) o = new_buf(127);
o.write_shift(4, data.hsState);
o.write_shift(4, data.iTabID);
write_RelID(data.strRelID, o);
write_XLWideString(data.name, o);
return o;
};
/* [MS-XLSB] 2.4.807 BrtWbProp */
var parse_BrtWbProp = function(data, length) {
data.read_shift(4);
var dwThemeVersion = data.read_shift(4);
var strName = (length > 8) ? parse_XLWideString(data) : "";
return [dwThemeVersion, strName];
};
var write_BrtWbProp = function(data, o) {
if(!o) o = new_buf(8);
o.write_shift(4, 0);
o.write_shift(4, 0);
return o;
};
var parse_BrtFRTArchID$ = function(data, length) {
var o = {};
data.read_shift(4);
o.ArchID = data.read_shift(4);
data.l += length - 8;
return o;
};
/* [MS-XLSB] 2.1.7.60 Workbook */
var parse_wb_bin = function(data, opts) {
@ -52,6 +82,7 @@ var parse_wb_bin = function(data, opts) {
case 'BrtEndWebPubItem': break;
case 'BrtEndWebPubItems': break;*/
case 'BrtFRTBegin': pass = true; break;
case 'BrtFRTArchID$': break;
case 'BrtFRTEnd': pass = false; break;
case 'BrtEndBook': break;
default: if(!pass) throw new Error("Unexpected record " + R.n);
@ -70,6 +101,80 @@ var parse_wb_bin = function(data, opts) {
return wb;
};
var write_wb_bin = function(wb, opts) {
/* [MS-XLSB] 2.1.7.60 Workbook */
function write_BUNDLESHS(ba, wb, opts) {
write_record(ba, "BrtBeginBundleShs");
wb.SheetNames.forEach(function(s, idx) {
var d = { hsState: 0, iTabID: idx+1, strRelID: 'rId' + (idx+1), name: s };
write_record(ba, "BrtBundleSh", write_BrtBundleSh(d));
});
write_record(ba, "BrtEndBundleShs");
}
/* [MS-XLSB] 2.4.643 BrtFileVersion */
function write_BrtFileVersion(data, o) {
if(!o) o = new_buf(127);
for(var i = 0; i != 4; ++i) o.write_shift(4, 0);
write_XLWideString("SheetJS", o);
write_XLWideString(XLSX.version, o);
write_XLWideString(XLSX.version, o);
write_XLWideString("7262", o);
o.length = o.l;
return o;
}
/* [MS-XLSB] 2.1.7.60 Workbook */
function write_BOOKVIEWS(ba, wb, opts) {
write_record(ba, "BrtBeginBookViews");
/* 1*(BrtBookView *FRT) */
write_record(ba, "BrtEndBookViews");
}
/* [MS-XLSB] 2.4.302 BrtCalcProp */
function write_BrtCalcProp(data, o) {
if(!o) o = new_buf(26);
o.write_shift(4,0); /* force recalc */
o.write_shift(4,1);
o.write_shift(4,0);
write_Xnum(0, o);
o.write_shift(-4, 1023);
o.write_shift(1, 0x33);
o.write_shift(1, 0x00);
return o;
}
function write_BrtFileRecover(data, o) {
if(!o) o = new_buf(1);
o.write_shift(1,0);
return o;
}
/* [MS-XLSB] 2.1.7.60 Workbook */
var write_wb_bin = function(wb, opts) {
var ba = buf_array();
write_record(ba, "BrtBeginBook");
write_record(ba, "BrtFileVersion", write_BrtFileVersion());
/* [[BrtFileSharingIso] BrtFileSharing] */
write_record(ba, "BrtWbProp", write_BrtWbProp());
/* [ACABSPATH] */
/* [[BrtBookProtectionIso] BrtBookProtection] */
write_BOOKVIEWS(ba, wb, opts);
write_BUNDLESHS(ba, wb, opts);
/* [FNGROUP] */
/* [EXTERNALS] */
/* *BrtName */
write_record(ba, "BrtCalcProp", write_BrtCalcProp());
/* [BrtOleSize] */
/* *(BrtUserBookView *FRT) */
/* [PIVOTCACHEIDS] */
/* [BrtWbFactoid] */
/* [SMARTTAGTYPES] */
/* [BrtWebOpt] */
write_record(ba, "BrtFileRecover", write_BrtFileRecover());
/* [WEBPUBITEMS] */
/* [CRERRS] */
/* FRTWORKBOOK */
write_record(ba, "BrtEndBook");
return ba.end();
};

@ -12,6 +12,7 @@ var RecordEnum = {
0x0009: { n:"BrtFmlaNum", f:parse_BrtFmlaNum },
0x000A: { n:"BrtFmlaBool", f:parse_BrtFmlaBool },
0x000B: { n:"BrtFmlaError", f:parse_BrtFmlaError },
0x0010: { n:"BrtFRTArchID$", f:parse_BrtFRTArchID$ },
0x0013: { n:"BrtSSTItem", f:parse_RichStr },
0x0014: { n:"BrtPCDIMissing", f:parsenoop },
0x0015: { n:"BrtPCDINumber", f:parsenoop },
@ -65,7 +66,7 @@ var RecordEnum = {
0x0080: { n:"BrtFileVersion", f:parsenoop },
0x0081: { n:"BrtBeginSheet", f:parsenoop },
0x0082: { n:"BrtEndSheet", f:parsenoop },
0x0083: { n:"BrtBeginBook", f:parsenoop },
0x0083: { n:"BrtBeginBook", f:parsenoop, p:0 },
0x0084: { n:"BrtEndBook", f:parsenoop },
0x0085: { n:"BrtBeginWsViews", f:parsenoop },
0x0086: { n:"BrtEndWsViews", f:parsenoop },
@ -82,10 +83,10 @@ var RecordEnum = {
0x0091: { n:"BrtBeginSheetData", f:parsenoop },
0x0092: { n:"BrtEndSheetData", f:parsenoop },
0x0093: { n:"BrtWsProp", f:parse_BrtWsProp },
0x0094: { n:"BrtWsDim", f:parse_BrtWsDim },
0x0094: { n:"BrtWsDim", f:parse_BrtWsDim, p:16 },
0x0097: { n:"BrtPane", f:parsenoop },
0x0098: { n:"BrtSel", f:parsenoop },
0x0099: { n:"BrtWbProp", f:parsenoop },
0x0099: { n:"BrtWbProp", f:parse_BrtWbProp },
0x009A: { n:"BrtWbFactoid", f:parsenoop },
0x009B: { n:"BrtFileRecover", f:parsenoop },
0x009C: { n:"BrtBundleSh", f:parse_BrtBundleSh },
@ -821,3 +822,4 @@ var RecordEnum = {
0xFFFF: { n:"", f:parsenoop }
};
var evert_RE = evert(RecordEnum, 'n');

@ -17,6 +17,7 @@ function parse_zip(zip, opts) {
dir.workbooks.push(binname);
xlsb = true;
}
if(dir.workbooks[0].substr(-3) == "bin") xlsb = true;
if(xlsb) set_cp(1200);
if(!opts.bookSheets && !opts.bookProps) {
@ -27,7 +28,7 @@ function parse_zip(zip, opts) {
if(dir.style) styles = parse_sty(getzipdata(zip, dir.style.replace(/^\//,'')),dir.style, opts);
themes = {};
if(opts.cellStyles && dir.themes) themes = parse_theme(getzipdata(zip, dir.themes[0].replace(/^\//,'')),dir.themes[0], opts);
if(opts.cellStyles && dir.themes.length) themes = parse_theme(getzipdata(zip, dir.themes[0].replace(/^\//,'')),dir.themes[0], opts);
}
var wb = parse_wb(getzipdata(zip, dir.workbooks[0].replace(/^\//,'')), dir.workbooks[0], opts);
@ -76,18 +77,20 @@ function parse_zip(zip, opts) {
}
}
var wbrelsfile = 'xl/_rels/workbook.xml.rels';
var wbext = xlsb ? "bin" : "xml";
var wbrelsfile = 'xl/_rels/workbook.' + wbext + '.rels';
var wbrels = parse_rels(getzipdata(zip, wbrelsfile, true), wbrelsfile);
if(wbrels) try {
wbrels = wb.Sheets.map(function(w) { return [w.name, wbrels['!id'][w.id].Target]; });
} catch(e) { wbrels = null; }
if(wbrels && wbrels.length === 0) wbrels = null;
/* Numbers iOS hack */
var nmode = (getzipdata(zip,"xl/worksheets/sheet.xml",true))?1:0;
for(i = 0; i != props.Worksheets; ++i) {
try {
if(wbrels) path = 'xl/' + (wbrels[i][1]).replace(/[\/]?xl\//, "");
else {
path = 'xl/worksheets/sheet'+(i+1-nmode)+(xlsb?'.bin':'.xml');
path = 'xl/worksheets/sheet'+(i+1-nmode)+"." + wbext;
path = path.replace(/sheet0\./,"sheet.");
}
relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels");

@ -43,7 +43,7 @@ function write_zip(wb, opts) {
ct.extprops.push(f);
add_rels(opts.rels, 3, f, RELS.EXT_PROPS);
if(wb.Custprops !== wb.Props) { /* TODO: fix xlsjs */
if(wb.Custprops !== wb.Props && keys(wb.Custprops||{}).length > 0) {
f = "docProps/custom.xml";
zip.file(f, write_cust_props(wb.Custprops, opts));
ct.custprops.push(f);
@ -78,13 +78,13 @@ function write_zip(wb, opts) {
/* TODO: something more intelligent with styles */
f = "xl/styles.xml";
f = "xl/styles." + wbext;
zip.file(f, write_sty(wb, f, opts));
ct.styles.push(f);
add_rels(opts.wbrels, ++rId, "styles." + wbext, RELS.STY);
zip.file("[Content_Types].xml", write_ct(ct, opts));
zip.file('_rels/.rels', write_rels(opts.rels));
zip.file('xl/_rels/workbook.xml.rels', write_rels(opts.wbrels));
zip.file('xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels));
return zip;
}

@ -29,22 +29,31 @@ function format_cell(cell, v) {
try { return (cell.w = SSF.format(cell.XF.ifmt||0, v)); } catch(e) { return v; }
}
function sheet_to_row_object_array(sheet, opts){
var val, row, r, hdr = {}, isempty, R, C, v;
function sheet_to_json(sheet, opts){
var val, row, range, header, offset = 1, r, hdr = {}, isempty, R, C, v;
var out = [];
opts = opts || {};
if(!sheet || !sheet["!ref"]) return out;
r = decode_range(sheet["!ref"]);
range = opts.range || sheet["!ref"];
header = opts.header || "";
switch(typeof range) {
case 'string': r = decode_range(range); break;
case 'number': r = decode_range(sheet["!ref"]); r.s.r = range; break;
default: r = range;
}
if(header) offset = 0;
for(R=r.s.r, C = r.s.c; C <= r.e.c; ++C) {
val = sheet[encode_cell({c:C,r:R})];
if(!val) continue;
hdr[C] = format_cell(val);
if(header === "A") hdr[C] = encode_col(C);
else if(header === 1) hdr[C] = C;
else if(Array.isArray(header)) hdr[C] = header[C - r.s.c];
else if(!val) continue;
else hdr[C] = format_cell(val);
}
for (R = r.s.r + 1; R <= r.e.r; ++R) {
for (R = r.s.r + offset; R <= r.e.r; ++R) {
isempty = true;
/* row index available as __rowNum__ */
row = Object.create({ __rowNum__ : R });
row = header === 1 ? [] : Object.create({ __rowNum__ : R });
for (C = r.s.c; C <= r.e.c; ++C) {
val = sheet[encode_cell({c: C,r: R})];
if(!val || !val.t) continue;
@ -65,6 +74,8 @@ function sheet_to_row_object_array(sheet, opts){
return out;
}
function sheet_to_row_object_array(sheet, opts) { if(!opts) opts = {}; delete opts.range; return sheet_to_json(sheet, opts); }
function sheet_to_csv(sheet, opts) {
var out = [], txt = "";
opts = opts || {};
@ -114,8 +125,9 @@ var utils = {
decode_range: decode_range,
sheet_to_csv: sheet_to_csv,
make_csv: sheet_to_csv,
make_json: sheet_to_row_object_array,
make_json: sheet_to_json,
get_formulae: get_formulae,
format_cell: format_cell,
sheet_to_json: sheet_to_json,
sheet_to_row_object_array: sheet_to_row_object_array
};

10
dist/xlsx.core.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

10
dist/xlsx.full.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

664
dist/xlsx.js vendored

File diff suppressed because one or more lines are too long

8
dist/xlsx.min.js vendored

File diff suppressed because one or more lines are too long

2
dist/xlsx.min.map vendored

File diff suppressed because one or more lines are too long

@ -1,6 +1,6 @@
{
"name": "xlsx",
"version": "0.7.3",
"version": "0.7.4",
"author": "sheetjs",
"description": "XLSB/XLSX/XLSM (Excel 2007+ Spreadsheet) parser and writer",
"keywords": [ "xlsx", "xlsb", "xlsm", "office", "excel", "spreadsheet" ],

228
test.js

@ -26,6 +26,8 @@ var dir = "./test_files/";
var paths = {
cp1: dir + 'custom_properties.xlsx',
cp2: dir + 'custom_properties.xlsb',
css1: dir + 'cell_style_simple.xlsx',
css2: dir + 'cell_style_simple.xlsb',
cst1: dir + 'comments_stress_test.xlsx',
cst2: dir + 'comments_stress_test.xlsb',
fst1: dir + 'formula_stress_test.xlsx',
@ -47,6 +49,7 @@ var N2 = 'XLSB';
function parsetest(x, wb, full, ext) {
ext = (ext ? " [" + ext + "]": "");
if(!full && ext) return;
describe(x + ext + ' should have all bits', function() {
var sname = dir + '2011/' + x + '.sheetnames';
it('should have all sheets', function() {
@ -125,14 +128,21 @@ function parsetest(x, wb, full, ext) {
});
}
var wbtable = {};
describe('should parse test files', function() {
files.forEach(function(x) {
if(!fs.existsSync(dir + x)) return;
it(x, x.substr(-8) == ".pending" ? null : function() {
var wb = X.readFile(dir + x, opts);
wbtable[dir + x] = wb;
parsetest(x, wb, true);
['.xlsx', '.xlsm'].forEach(function(ext, idx) {
parsetest(x, X.read(X.write(wb, {type:"buffer", bookType:ext.replace(/\./,""), bookSST: idx != 1})), true, ext);
});
fullex.forEach(function(ext, idx) {
it(x + ' [' + ext + ']', function(){
var wb = wbtable[dir + x];
if(!wb) wb = X.readFile(dir + x, opts);
parsetest(x, X.read(X.write(wb, {type:"buffer", bookType:ext.replace(/\./,""), bookSST: idx != 1}), {WTF:opts.WTF}), ext.replace(/\./,"") !== "xlsb", ext);
});
});
});
@ -209,6 +219,28 @@ describe('parse options', function() {
});
});
});
it('should not generate cell styles by default', function() {
var wb = X.readFile(paths.css1);
wb.SheetNames.forEach(function(s) {
var ws = wb.Sheets[s];
Object.keys(ws).forEach(function(addr) {
if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return;
assert(typeof ws[addr].s === 'undefined');
});
});
});
it('XLSX should generate cell styles when requested', function() {
var wb = X.readFile(paths.css1, {cellStyles:true});
var found = false;
wb.SheetNames.forEach(function(s) {
var ws = wb.Sheets[s];
Object.keys(ws).forEach(function(addr) {
if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return;
if(typeof ws[addr].s !== 'undefined') return found = true;
});
});
assert(found);
});
});
describe('sheet', function() {
it('should not generate sheet stubs by default', function() {
@ -385,6 +417,34 @@ function custprop(wb) {
assert.equal(wb.Custprops.Counter, -3.14);
}
function cmparr(x){ for(var i=1;i!=x.length;++i) assert.deepEqual(x[0], x[i]); }
function deepcmp(x,y,k,m,c) {
var s = k.indexOf(".");
m = (m||"") + "|" + (s > -1 ? k.substr(0,s) : k);
if(s < 0) return assert[c<0?'notEqual':'equal'](x[k], y[k], m);
return deepcmp(x[k.substr(0,s)],y[k.substr(0,s)],k.substr(s+1),m,c)
}
var styexc = [
'A2|H10|bgColor.rgb',
'F6|H1|patternType'
]
var stykeys = [
"patternType",
"fgColor.rgb",
"bgColor.rgb"
];
function diffsty(ws, r1,r2) {
var c1 = ws[r1].s, c2 = ws[r2].s;
stykeys.forEach(function(m) {
var c = -1;
if(styexc.indexOf(r1+"|"+r2+"|"+m) > -1) c = 1;
else if(styexc.indexOf(r2+"|"+r1+"|"+m) > -1) c = 1;
deepcmp(c1,c2,m,r1+","+r2,c);
});
}
describe('parse features', function() {
it('should have comment as part of cell properties', function(){
var X = require('./');
@ -496,6 +556,36 @@ describe('parse features', function() {
assert.equal(sheet[3]['てすと'], '2/14/14');
});
});
describe('should correctly handle styles', function() {
var ws, rn, rn2;
before(function() {
ws=X.readFile(paths.css1, {cellStyles:true}).Sheets.Sheet1;
rn = function(range) {
var r = X.utils.decode_range(range);
var out = [];
for(var R = r.s.r; R <= r.e.r; ++R) for(var C = r.s.c; C <= r.e.c; ++C)
out.push(X.utils.encode_cell({c:C,r:R}));
return out;
};
rn2 = function(r) { return [].concat.apply([], r.split(",").map(rn)); };
});
var ranges = [
'A1:D1,F1:G1', 'A2:D2,F2:G2', /* rows */
'A3:A10', 'B3:B10', 'E1:E10', 'F6:F8', /* cols */
'H1:J4', 'H10' /* blocks */
]
ranges.forEach(function(rng) {
it(rng,function(){cmparr(rn2(rng).map(function(x){ return ws[x].s; }));});
});
it('different styles', function() {
for(var i = 0; i != ranges.length-1; ++i) {
for(var j = i+1; j != ranges.length; ++j) {
diffsty(ws, rn2(ranges[i])[0], rn2(ranges[j])[0]);
}
}
});
});
});
describe('roundtrip features', function() {
@ -577,3 +667,137 @@ describe('invalid files', function() {
});
});
});
function datenum(v, date1904) {
if(date1904) v+=1462;
var epoch = Date.parse(v);
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}
function sheet_from_array_of_arrays(data, opts) {
var ws = {};
var range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }};
for(var R = 0; R != data.length; ++R) {
for(var C = 0; C != data[R].length; ++C) {
if(range.s.r > R) range.s.r = R;
if(range.s.c > C) range.s.c = C;
if(range.e.r < R) range.e.r = R;
if(range.e.c < C) range.e.c = C;
var cell = {v: data[R][C] };
if(cell.v == null) continue;
var cell_ref = X.utils.encode_cell({c:C,r:R});
if(typeof cell.v === 'number') cell.t = 'n';
else if(typeof cell.v === 'boolean') cell.t = 'b';
else if(cell.v instanceof Date) {
cell.t = 'n'; cell.z = X.SSF._table[14];
cell.v = datenum(cell.v);
}
else cell.t = 's';
ws[cell_ref] = cell;
}
}
if(range.s.c < 10000000) ws['!ref'] = X.utils.encode_range(range);
return ws;
}
describe('json output', function() {
function seeker(json, keys, val) {
for(var i = 0; i != json.length; ++i) {
for(var j = 0; j != keys.length; ++j) {
if(json[i][keys[j]] === val) throw new Error("found " + val + " in row " + i + " key " + keys[j]);
}
}
}
var data, ws;
before(function() {
data = [
[1,2,3],
[true, false, null, "sheetjs"],
["foo","bar",new Date("2014-02-19T14:30Z"), "0.3"],
["baz", null, "qux"]
];
ws = sheet_from_array_of_arrays(data);
});
it('should use first-row headers and full sheet by default', function() {
var json = X.utils.sheet_to_json(ws);
assert.equal(json.length, data.length - 1);
assert.equal(json[0][1], true);
assert.equal(json[1][2], "bar");
assert.equal(json[2][3], "qux");
assert.doesNotThrow(function() { seeker(json, [1,2,3], "sheetjs"); });
assert.throws(function() { seeker(json, [1,2,3], "baz"); });
});
it('should create array of arrays if header == 1', function() {
var json = X.utils.sheet_to_json(ws, {header:1});
assert.equal(json.length, data.length);
assert.equal(json[1][0], true);
assert.equal(json[2][1], "bar");
assert.equal(json[3][2], "qux");
assert.doesNotThrow(function() { seeker(json, [0,1,2], "sheetjs"); });
assert.throws(function() { seeker(json, [0,1,2,3], "sheetjs"); });
assert.throws(function() { seeker(json, [0,1,2], "baz"); });
});
it('should use column names if header == "A"', function() {
var json = X.utils.sheet_to_json(ws, {header:'A'});
assert.equal(json.length, data.length);
assert.equal(json[1]['A'], true);
assert.equal(json[2]['B'], "bar");
assert.equal(json[3]['C'], "qux");
assert.doesNotThrow(function() { seeker(json, "ABC", "sheetjs"); });
assert.throws(function() { seeker(json, "ABCD", "sheetjs"); });
assert.throws(function() { seeker(json, "ABC", "baz"); });
});
it('should use column labels if specified', function() {
var json = X.utils.sheet_to_json(ws, {header:["O","D","I","N"]});
assert.equal(json.length, data.length);
assert.equal(json[1]['O'], true);
assert.equal(json[2]['D'], "bar");
assert.equal(json[3]['I'], "qux");
assert.doesNotThrow(function() { seeker(json, "ODI", "sheetjs"); });
assert.throws(function() { seeker(json, "ODIN", "sheetjs"); });
assert.throws(function() { seeker(json, "ODIN", "baz"); });
});
[["string", "A2:D4"], ["numeric", 1], ["object", {s:{r:1,c:0},e:{r:3,c:3}}]].forEach(function(w) {
it('should accept custom ' + w[0] + ' range', function() {
var json = X.utils.sheet_to_json(ws, {header:1, range:w[1]});
assert.equal(json.length, 3);
assert.equal(json[0][0], true);
assert.equal(json[1][1], "bar");
assert.equal(json[2][2], "qux");
assert.doesNotThrow(function() { seeker(json, [0,1,2], "sheetjs"); });
assert.throws(function() { seeker(json, [0,1,2,3], "sheetjs"); });
assert.throws(function() { seeker(json, [0,1,2], "baz"); });
});
});
});
describe('corner cases', function() {
it('output functions', function() {
var data = [
[1,2,3],
[true, false, null, "sheetjs"],
["foo","bar",new Date("2014-02-19T14:30Z"), "0.3"],
["baz", null, "q\"ux"]
];
ws = sheet_from_array_of_arrays(data);
ws.A1.f = ""; ws.A1.w = "";
delete ws.C3.w; delete ws.C3.z; ws.C3.XF = {ifmt:14};
ws.A4.t = "e";
X.utils.get_formulae(ws);
X.utils.make_csv(ws);
X.utils.make_json(ws);
ws.A2.t = "f";
assert.throws(function() { X.utils.make_json(ws); });
});
it('SSF', function() {
X.SSF.format("General", "dafuq");
assert.throws(function(x) { return X.SSF.format("General", {sheet:"js"});});
X.SSF.format("b e ddd hh AM/PM", 41722.4097222222);
X.SSF.format("b ddd hh m", 41722.4097222222);
["hhh","hhh A/P","hhmmm","sss","[hhh]","G eneral"].forEach(function(f) {
assert.throws(function(x) { return X.SSF.format(f, 12345.6789);});
});
["[m]","[s]"].forEach(function(f) {
assert.doesNotThrow(function(x) { return X.SSF.format(f, 12345.6789);});
});
});
});

@ -1 +1 @@
Subproject commit 43c63e345c81a1fa3145777fe79a79e9b1cc04ee
Subproject commit 5561c42b441a7593092652d7acde8f4ed9caff33

@ -3,9 +3,11 @@ BlankSheetTypes.xlsb.pending
NumberFormatCondition.xlsb
RkNumber.xlsb
calendar_stress_test.xlsb.pending
cell_style_simple.xlsb
comments_stress_test.xlsb
custom_properties.xlsb
formula_stress_test.xlsb
formulae_test_simple.xlsb
hyperlink_stress_test_2011.xlsb
merge_cells.xlsb
named_ranges_2011.xlsb
@ -167,6 +169,7 @@ apachepoi_workbookProtection_workbook_windows_protected.xlsx
apachepoi_workbookProtection_worksheet_protected.xlsx
apachepoi_xlsx-jdbc.xlsx
calendar_stress_test.xlsx.pending
cell_style_simple.xlsx
comments_stress_test.xlsx
custom_properties.xlsx
excel-reader-xlsx_data01.xlsx
@ -181,6 +184,7 @@ excel-reader-xlsx_error08.xlsx.pending
excel-reader-xlsx_inline01.xlsx
excel-reader-xlsx_libre01.xlsx
formula_stress_test.xlsx
formulae_test_simple.xlsx
hyperlink_stress_test_2011.xlsx
interview.xlsx
issue.xlsx

418
xlsx.js

@ -2,7 +2,7 @@
/* vim: set ts=2: */
var XLSX = {};
(function(XLSX){
XLSX.version = '0.7.3';
XLSX.version = '0.7.4';
var current_codepage = 1252, current_cptable;
if(typeof module !== "undefined" && typeof require !== 'undefined') {
if(typeof cptable === 'undefined') cptable = require('./dist/cpexcel');
@ -599,6 +599,7 @@ function evert(obj, arr) {
var o = {};
keys(obj).forEach(function(k) {