experimental dense representation

- browser demo save file in global (fixes #573 h/t @WildDusk)
- flesh out XLSB record expectations (fixes #624 h/t @pgeeh)
- `dense` mode (fixes #34 h/t @schleumer)
This commit is contained in:
SheetJS 2017-04-08 02:55:35 -04:00
parent d086dbecbf
commit f43cacaf5e
30 changed files with 1419 additions and 1067 deletions

View File

@ -40,6 +40,7 @@ program
.option('--perf', 'do not generate output')
.option('--all', 'parse everything; write as much as possible')
.option('--dev', 'development mode')
.option('--sparse', 'sparse mode')
.option('--read', 'read but do not print out contents')
.option('-q, --quiet', 'quiet mode');
@ -111,6 +112,7 @@ if(program.all) {
opts.sheetStubs = true;
opts.cellDates = true;
}
if(program.sparse) opts.dense = false; else opts.dense = true;
if(program.dev) {
X.verbose = 2;

1
bits/03_dense.js Normal file
View File

@ -0,0 +1 @@
var DENSE = false;

View File

@ -6,7 +6,8 @@ function sheet_to_workbook(sheet/*:Worksheet*/, opts)/*:Workbook*/ {
function aoa_to_sheet(data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ {
var o = opts || {};
var ws/*:Worksheet*/ = ({}/*:any*/);
if(DENSE != null) o.dense = DENSE;
var ws/*:Worksheet*/ = o.dense ? ([]/*:any*/) : ({}/*:any*/);
var range/*:Range*/ = ({s: {c:10000000, r:10000000}, e: {c:0, r:0}}/*:any*/);
for(var R = 0; R != data.length; ++R) {
for(var C = 0; C != data[R].length; ++C) {
@ -16,7 +17,6 @@ function aoa_to_sheet(data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ {
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_ref = encode_cell(({c:C,r:R}/*:any*/));
if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; }
else if(typeof cell.v === 'number') cell.t = 'n';
else if(typeof cell.v === 'boolean') cell.t = 'b';
@ -26,7 +26,13 @@ function aoa_to_sheet(data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ {
else { cell.t = 'n'; cell.v = datenum(cell.v); cell.w = SSF.format(cell.z, cell.v); }
}
else cell.t = 's';
ws[cell_ref] = cell;
if(o.dense) {
if(!ws[R]) ws[R] = [];
ws[R][C] = cell;
} else {
var cell_ref = encode_cell(({c:C,r:R}/*:any*/));
ws[cell_ref] = cell;
}
}
}
if(range.s.c < 10000000) ws['!ref'] = encode_range(range);

View File

@ -122,7 +122,7 @@ var ct2type/*{[string]:string}*/ = ({
"application/vnd.ms-excel.Survey+xml": "TODO",
/* Drawing */
"application/vnd.openxmlformats-officedocument.drawing+xml": "TODO",
"application/vnd.openxmlformats-officedocument.drawing+xml": "drawings",
"application/vnd.openxmlformats-officedocument.drawingml.chart+xml": "TODO",
"application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml": "TODO",
"application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml": "TODO",
@ -193,7 +193,7 @@ function parse_ct(data/*:?string*/, opts) {
workbooks:[], sheets:[], charts:[], dialogs:[], macros:[],
rels:[], strs:[], comments:[],
coreprops:[], extprops:[], custprops:[], themes:[], styles:[],
calcchains:[], vba: [],
calcchains:[], vba: [], drawings: [],
TODO:[], xmlns: "" }/*:any*/);
if(!data || !data.match) return ct;
var ctext = {};
@ -256,7 +256,7 @@ function write_ct(ct, opts)/*:string*/ {
}
};
var f2 = function(w) {
ct[w].forEach(function(v) {
(ct[w]||[]).forEach(function(v) {
o[o.length] = (writextag('Override', null, {
'PartName': (v[0] == '/' ? "":"/") + v,
'ContentType': CT_LIST[w][opts.bookType || 'xlsx']
@ -273,11 +273,13 @@ function write_ct(ct, opts)/*:string*/ {
};
f1('workbooks');
f2('sheets');
f2('charts');
f3('themes');
['strs', 'styles'].forEach(f1);
['coreprops', 'extprops', 'custprops'].forEach(f3);
f3('vba');
f2('comments');
f3('comments');
f3('drawings');
if(o.length>2){ o[o.length] = ('</Types>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}

View File

@ -44,11 +44,9 @@ var RELS_ROOT = writextag('Relationships', null, {
/* TODO */
function write_rels(rels)/*:string*/ {
var o = [];
o[o.length] = (XML_HEADER);
o[o.length] = (RELS_ROOT);
keys(rels['!id']).forEach(function(rid) { var rel = rels['!id'][rid];
o[o.length] = (writextag('Relationship', null, rel));
var o = [XML_HEADER, RELS_ROOT];
keys(rels['!id']).forEach(function(rid) {
o[o.length] = (writextag('Relationship', null, rels['!id'][rid]));
});
if(o.length>2){ o[o.length] = ('</Relationships>'); o[1]=o[1].replace("/>",">"); }
return o.join("");

View File

@ -261,10 +261,12 @@ var SYLK = (function() {
var preamble/*:Array<string>*/ = ["ID;PWXL;N;E"], o/*:Array<string>*/ = [];
preamble.push("P;PGeneral");
var r = decode_range(ws['!ref']), cell/*:Cell*/;
var dense = Array.isArray(ws);
for(var R = r.s.r; R <= r.e.r; ++R) {
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) continue;
cell = dense ? (ws[R]||[])[C]: ws[coord];
if(!cell || cell.v == null) continue;
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
}
}
@ -337,6 +339,7 @@ var DIF = (function() {
return function sheet_to_dif(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
var o/*:Array<string>*/ = [];
var r = decode_range(ws['!ref']), cell/*:Cell*/;
var dense = Array.isArray(ws);
push_field(o, "TABLE", 0, 1, "sheetjs");
push_field(o, "VECTORS", 0, r.e.r - r.s.r + 1,"");
push_field(o, "TUPLES", 0, r.e.c - r.s.c + 1,"");
@ -345,7 +348,8 @@ var DIF = (function() {
push_value(o, -1, 0, "BOT");
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) { push_value(o, 1, 0, ""); continue;}
cell = dense ? (ws[R]||[])[C] : ws[coord];
if(!cell || cell.v == null) { push_value(o, 1, 0, ""); continue;}
switch(cell.t) {
case 'n': push_value(o, 0, (/*cell.w ||*/ cell.v), "V"); break;
case 'b': push_value(o, 0, cell.v ? 1 : 0, cell.v ? "TRUE" : "FALSE"); break;
@ -402,8 +406,10 @@ var PRN = (function() {
}
function dsv_to_sheet_str(str/*:string*/, opts)/*:Worksheet*/ {
var o = opts || {};
var sep = "";
var ws/*:Worksheet*/ = ({}/*:any*/);
if(DENSE != null) o.dense = DENSE;
var ws/*:Worksheet*/ = o.dense ? ([]/*:any*/) : ({}/*:any*/);
var range/*:Range*/ = ({s: {c:0, r:0}, e: {c:0, r:0}}/*:any*/);
/* known sep */
@ -423,7 +429,8 @@ var PRN = (function() {
else if(s == "FALSE") { cell.t = 'b'; cell.v = false; }
else if(!isNaN(v = parseFloat(s))) { cell.t = 'n'; cell.w = s; cell.v = v; }
else { cell.t = 's'; cell.v = s.replace(/^"/,"").replace(/"$/,"").replace(/""/g,'"'); }
ws[encode_cell({c:C,r:R})] = cell;
if(o.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = cell; }
else ws[encode_cell({c:C,r:R})] = cell;
start = end+1;
if(range.e.c < C) range.e.c = C;
if(range.e.r < R) range.e.r = R;
@ -456,11 +463,13 @@ var PRN = (function() {
function sheet_to_prn(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
var o/*:Array<string>*/ = [];
var r = decode_range(ws['!ref']), cell/*:Cell*/;
var dense = Array.isArray(ws);
for(var R = r.s.r; R <= r.e.r; ++R) {
var oo = [];
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) { oo.push(" "); continue; }
cell = dense ? (ws[R]||[])[C] : ws[coord];
if(!cell || cell.v == null) { oo.push(" "); continue; }
var w = (cell.w || (format_cell(cell), cell.w) || "").substr(0,10);
while(w.length < 10) w += " ";
oo.push(w + (C == 0 ? " " : ""));

View File

@ -27,8 +27,8 @@ var WK_ = (function() {
function lotus_to_workbook_buf(d,opts)/*:Workbook*/ {
if(!d) return d;
var o = opts || {};
var s = {}, n = "Sheet1", sidx = 0;
if(DENSE != null) o.dense = DENSE;
var s = (o.dense ? [] : {}), n = "Sheet1", sidx = 0;
var sheets = {}, snames = [n];
var refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
@ -45,13 +45,16 @@ var WK_ = (function() {
break;
case 0x06: refguess = val; break; /* RANGE */
case 0x0F: /* LABEL */
if(!opts.qpro) val[1].v = val[1].v.substr(1);
if(!o.qpro) val[1].v = val[1].v.substr(1);
/* falls through */
case 0x0D: /* INTEGER */
case 0x0E: /* NUMBER */
case 0x10: /* FORMULA */
case 0x33: /* STRING */
s[encode_cell(val[0])] = val[1];
if(o.dense) {
if(!s[val[0].r]) s[val[0].r] = [];
s[val[0].r][val[0].c] = val[1];
} else s[encode_cell(val[0])] = val[1];
/* TODO: FORMAT */
break;
} else switch(RT) {
@ -67,7 +70,7 @@ var WK_ = (function() {
if(val[3] > sidx) {
s["!ref"] = encode_range(refguess);
sheets[n] = s;
s = {};
s = (o.dense ? [] : {});
refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
sidx = val[3]; n = "Sheet" + (sidx + 1);
snames.push(n);

View File

@ -12,7 +12,6 @@ function parse_sst_bin(data, opts)/*:SST*/ {
case 'BrtBeginSst': s.Count = val[0]; s.Unique = val[1]; break;
case 'BrtSSTItem': s.push(val); break;
case 'BrtEndSst': return true;
/* TODO: produce a test case with a future record */
case 'BrtFRTBegin': pass = true; break;
case 'BrtFRTEnd': pass = false; break;
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + RT + " " + R.n);
@ -34,6 +33,7 @@ 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]));
/* FRTSST */
write_record(ba, "BrtEndSst");
return ba.end();
}

View File

@ -47,7 +47,7 @@ function parse_sty_bin(data, themes, opts) {
for(var y in SSF._table) styles.NumberFmt[y] = SSF._table[y];
styles.CellXf = [];
var state = ""; /* TODO: this should be a stack */
var state = [];
var pass = false;
recordhopper(data, function hopper_sty(val, R, RT) {
switch(R.n) {
@ -59,7 +59,7 @@ function parse_sty_bin(data, themes, opts) {
case 'BrtFill': break; /* TODO */
case 'BrtBorder': break; /* TODO */
case 'BrtXF':
if(state === "CELLXFS") {
if(state[state.length - 1] == "BrtBeginCellXFs") {
styles.CellXf.push(val);
}
break; /* TODO */
@ -67,48 +67,23 @@ function parse_sty_bin(data, themes, opts) {
case 'BrtDXF': break; /* TODO */
case 'BrtMRUColor': break; /* TODO */
case 'BrtIndexedColor': break; /* TODO */
case 'BrtBeginStyleSheet': break;
case 'BrtEndStyleSheet': break;
case 'BrtBeginTableStyle': break;
case 'BrtDXF14': break;
case 'BrtDXF15': break;
case 'BrtUid': break;
case 'BrtSlicerStyleElement': break;
case 'BrtTableStyleElement': break;
case 'BrtEndTableStyle': break;
case 'BrtBeginFmts': state = "FMTS"; break;
case 'BrtEndFmts': state = ""; break;
case 'BrtBeginFonts': state = "FONTS"; break;
case 'BrtEndFonts': state = ""; break;
case 'BrtACBegin': state = "ACFONTS"; break;
case 'BrtACEnd': state = ""; break;
case 'BrtBeginFills': state = "FILLS"; break;
case 'BrtEndFills': state = ""; break;
case 'BrtBeginBorders': state = "BORDERS"; break;
case 'BrtEndBorders': state = ""; break;
case 'BrtBeginCellStyleXFs': state = "CELLSTYLEXFS"; break;
case 'BrtEndCellStyleXFs': state = ""; break;
case 'BrtBeginCellXFs': state = "CELLXFS"; break;
case 'BrtEndCellXFs': state = ""; break;
case 'BrtBeginStyles': state = "STYLES"; break;
case 'BrtEndStyles': state = ""; break;
case 'BrtBeginDXFs': state = "DXFS"; break;
case 'BrtEndDXFs': state = ""; break;
case 'BrtBeginTableStyles': state = "TABLESTYLES"; break;
case 'BrtEndTableStyles': state = ""; break;
case 'BrtBeginColorPalette': state = "COLORPALETTE"; break;
case 'BrtEndColorPalette': state = ""; break;
case 'BrtBeginIndexedColors': state = "INDEXEDCOLORS"; break;
case 'BrtEndIndexedColors': state = ""; break;
case 'BrtBeginMRUColors': state = "MRUCOLORS"; break;
case 'BrtEndMRUColors': state = ""; break;
case 'BrtTimelineStyleElement': break;
case 'BrtFRTBegin': pass = true; break;
case 'BrtFRTEnd': pass = false; break;
case 'BrtBeginStyleSheetExt14': break;
case 'BrtBeginSlicerStyles': break;
case 'BrtEndSlicerStyles': break;
case 'BrtBeginTimelineStylesheetExt15': break;
case 'BrtEndTimelineStylesheetExt15': break;
case 'BrtBeginTimelineStyles': break;
case 'BrtEndTimelineStyles': break;
case 'BrtEndStyleSheetExt14': break;
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + RT + " " + R.n);
case 'BrtACBegin': state.push(R.n); break;
case 'BrtACEnd': state.pop(); break;
default:
if((R.n||"").indexOf("Begin") > 0) state.push(R.n);
else if((R.n||"").indexOf("End") > 0) state.pop();
else if(!pass || opts.WTF) throw new Error("Unexpected record " + RT + " " + R.n);
}
});
return styles;

View File

@ -1,3 +1,5 @@
RELS.IMG = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
RELS.DRAW = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
/* 20.5 DrawingML - SpreadsheetML Drawing */
function parse_drawing(data, rels/*:any*/) {
if(!data) return "??";
@ -16,3 +18,4 @@ function parse_drawing(data, rels/*:any*/) {
return rels['!id'][id].Target;
}

View File

@ -19,11 +19,18 @@ function parse_comments(zip, dirComments, sheets, sheetRels, opts) {
}
function insertCommentsIntoSheet(sheetName, sheet, comments) {
var dense = Array.isArray(sheet);
var cell, r;
comments.forEach(function(comment) {
var cell = sheet[comment.ref];
if(dense) {
r = decode_cell(comment.ref);
if(!sheet[r.r]) sheet[r.r] = [];
cell = sheet[r.r][r.c];
} else cell = sheet[comment.ref];
if (!cell) {
cell = {};
sheet[comment.ref] = cell;
if(dense) sheet[r.r][r.c] = cell;
else sheet[comment.ref] = cell;
var range = safe_decode_range(sheet["!ref"]||"BDWGO1000001:A1");
var thisCell = decode_cell(comment.ref);
if(range.s.r > thisCell.r) range.s.r = thisCell.r;

View File

@ -42,13 +42,17 @@ function parse_comments_bin(data, opts) {
if(opts.sheetRows && opts.sheetRows <= c.rfx.r) break;
if(!c.t) c.t = "";
delete c.rfx; out.push(c); break;
case 'BrtBeginComments': break;
case 'BrtEndComments': break;
case 'BrtBeginCommentAuthors': break;
case 'BrtEndCommentAuthors': break;
case 'BrtBeginCommentList': break;
case 'BrtEndCommentList': break;
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + RT + " " + R.n);
case 'BrtUid': break;
case 'BrtFRTBegin': pass = true; break;
case 'BrtFRTEnd': pass = false; break;
case 'BrtACBegin': break;
case 'BrtACEnd': break;
default:
if((R.n||"").indexOf("Begin") > 0){}
else if((R.n||"").indexOf("End") > 0){}
else if(!pass || opts.WTF) throw new Error("Unexpected record " + RT + " " + R.n);
}
});
return out;

View File

@ -10,8 +10,9 @@ var colregex = /<(?:\w*:)?col[^>]*[\/]?>/g;
/* 18.3 Worksheets */
function parse_ws_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
if(DENSE != null) opts.dense = DENSE;
/* 18.3.1.99 worksheet CT_Worksheet */
var s = ({}/*:any*/);
var s = opts.dense ? ([]/*:any*/) : ({}/*:any*/);
/* 18.3.1.35 dimension CT_SheetDimension ? */
// $FlowIgnore
@ -89,6 +90,7 @@ function write_ws_xml_protection(sp)/*:string*/ {
}
function parse_ws_xml_hlinks(s, data/*:Array<string>*/, rels) {
var dense = Array.isArray(s);
for(var i = 0; i != data.length; ++i) {
var val = parsexmltag(data[i], true);
if(!val.ref) return;
@ -106,8 +108,14 @@ function parse_ws_xml_hlinks(s, data/*:Array<string>*/, rels) {
var rng = safe_decode_range(val.ref);
for(var R=rng.s.r;R<=rng.e.r;++R) for(var C=rng.s.c;C<=rng.e.c;++C) {
var addr = encode_cell({c:C,r:R});
if(!s[addr]) s[addr] = {t:"z",v:undefined};
s[addr].l = val;
if(dense) {
if(!s[R]) s[R] = [];
if(!s[R][C]) s[R][C] = {t:"z",v:undefined};
s[R][C].l = val;
} else {
if(!s[addr]) s[addr] = {t:"z",v:undefined};
s[addr].l = val;
}
}
}
}
@ -191,6 +199,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) {
var fmtid = 0, fillid = 0, do_format = Array.isArray(styles.CellXf), cf;
var arrayf = [];
var sharedf = [];
var dense = Array.isArray(s);
for(var marr = sdata.split(rowregex), mt = 0, marrlen = marr.length; mt != marrlen; ++mt) {
x = marr[mt].trim();
var xlen = x.length;
@ -305,21 +314,27 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) {
if(opts.cellDates && do_format && p.t == 'n' && SSF.is_date(SSF._table[fmtid])) {
var _d = SSF.parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(Date.UTC(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u)); }
}
s[tag.r] = p;
if(dense) {
var _r = decode_cell(tag.r);
if(!s[_r.r]) s[_r.r] = [];
s[_r.r][_r.c] = p;
} else s[tag.r] = p;
}
}
}; })();
function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/, rels)/*:string*/ {
var o = [], r = [], range = safe_decode_range(ws['!ref']), cell, ref, rr = "", cols = [], R=0, C=0, rows = ws['!rows'];
var dense = Array.isArray(ws);
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(C = range.s.c; C <= range.e.c; ++C) {
ref = cols[C] + rr;
if(ws[ref] === undefined) continue;
if((cell = write_ws_xml_cell(ws[ref], ref, ws, opts, idx, wb)) != null) r.push(cell);
var _cell = dense ? (ws[R]||[])[C]: ws[ref];
if(_cell === undefined) continue;
if((cell = write_ws_xml_cell(_cell, ref, ws, opts, idx, wb)) != null) r.push(cell);
}
if(r.length > 0) {
var params = ({r:rr}/*:any*/);
@ -350,6 +365,7 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ {
var ref = ws['!ref']; if(ref === undefined) ref = 'A1';
if(!rels) rels = {};
ws['!comments'] = [];
ws['!drawing'] = [];
o[o.length] = (writextag('sheetPr', null, {'codeName': escapexml(wb.SheetNames[idx])}));
o[o.length] = (writextag('dimension', null, {'ref': ref}));
@ -385,12 +401,20 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ {
}
delete ws['!links'];
var hfidx = o.length;
o[o.length] = "";
if(ws['!drawing'].length > 0) {
rId = add_rels(rels, -1, "../drawings/drawing" + (idx+1) + ".xml", RELS.DRAW);
ws['!drawing'].rid = rId;
o[o.length] = writextag("drawing", null, {"r:id":"rId" + rId});
}
else delete ws['!drawing'];
if(ws['!comments'].length > 0) {
rId = add_rels(rels, -1, "../drawings/vmlDrawing" + (idx+1) + ".vml", RELS.VML);
o[o.length] = writextag("legacyDrawing", null, {"r:id":"rId" + rId});
ws['!legacy'] = rId;
}
// <legacyDrawing r:id="rId1"/>
if(o.length>2) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); }
return o.join("");

View File

@ -26,7 +26,8 @@ function write_BrtRowHdr(R/*:number*/, range, ws) {
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; }
var cell = Array.isArray(ws) ? (ws[caddr.r]||[])[caddr.c] : ws[encode_cell(caddr)];
if(cell) { if(first < 0) first = j; last = j; }
}
if(first < 0) continue;
++ncolspan;
@ -289,18 +290,19 @@ function write_BrtColInfo(C/*:number*/, col, o) {
}
/* [MS-XLSB] 2.1.7.61 Worksheet */
function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
var opts = _opts || {};
if(!rels) rels = {'!id':{}};
var s = {};
if(DENSE != null) opts.dense = DENSE;
var s = opts.dense ? [] : {};
var ref;
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 row, p, cf, R, C, addr, sstr, rr, cell;
var mergecells = [];
if(!opts) opts = {};
opts.biff = 12;
opts['!row'] = 0;
@ -319,14 +321,14 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
var defwidth = 0, defheight = 0; // twips / MDW respectively
var seencol = false;
recordhopper(data, function ws_parse(val, Record) {
recordhopper(data, function ws_parse(val, Record, RT) {
if(end) return;
switch(Record.n) {
case 'BrtWsDim': ref = val; break;
case 'BrtRowHdr':
row = val;
if(opts.sheetRows && opts.sheetRows <= row.r) end=true;
rr = encode_row(row.r);
rr = encode_row(R = row.r);
opts['!row'] = row.r;
break;
@ -349,7 +351,9 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
case 'str': p.t = 's'; p.v = utf8read(val[1]); break;
}
if((cf = styles.CellXf[val[0].iStyleRef])) safe_format(p,cf.ifmt,null,opts, themes, styles);
s[encode_col(C=val[0].c) + rr] = p;
C = val[0].c;
if(opts.dense) { if(!s[R]) s[R] = []; s[R][C] = p; }
else s[encode_col(C) + rr] = p;
if(opts.cellFormula) {
af = false;
for(ai = 0; ai < array_formulae.length; ++ai) {
@ -370,18 +374,18 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
}
break;
case 'BrtCellBlank': if(!opts.sheetStubs) break;
case 'BrtCellBlank':
if(!opts.sheetStubs) break;
p = ({t:'z',v:undefined}/*:any*/);
s[encode_col(C=val[0].c) + rr] = p;
C = val[0].c;
if(opts.dense) { if(!s[R]) s[R] = []; s[R][C] = p; }
else s[encode_col(C) + rr] = p;
if(refguess.s.r > row.r) refguess.s.r = row.r;
if(refguess.s.c > C) refguess.s.c = C;
if(refguess.e.r < row.r) refguess.e.r = row.r;
if(refguess.e.c < C) refguess.e.c = C;
break;
/* Merge Cells */
case 'BrtBeginMergeCells': break;
case 'BrtEndMergeCells': break;
case 'BrtMergeCell': mergecells.push(val); break;
case 'BrtHLink':
@ -392,140 +396,109 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
val.Rel = rel;
}
for(R=val.rfx.s.r;R<=val.rfx.e.r;++R) for(C=val.rfx.s.c;C<=val.rfx.e.c;++C) {
addr = encode_cell({c:C,r:R});
if(!s[addr]) s[addr] = {t:'s',v:undefined};
s[addr].l = val;
if(opts.dense) {
if(!s[R]) s[R] = [];
if(!s[R][C]) s[R][C] = {t:'z',v:undefined};
s[R][C].l = val;
} else {
addr = encode_cell({c:C,r:R});
if(!s[addr]) s[addr] = {t:'z',v:undefined};
s[addr].l = val;
}
}
break;
case 'BrtArrFmla': if(!opts.cellFormula) break;
case 'BrtArrFmla':
if(!opts.cellFormula) break;
array_formulae.push(val);
s[encode_col(C) + rr].f = stringify_formula(val[1], refguess, {r:row.r, c:C}, supbooks, opts);
s[encode_col(C) + rr].F = encode_range(val[0]);
cell = (opts.dense ? s[R][C] : s[encode_col(C) + rr]);
cell.f = stringify_formula(val[1], refguess, {r:row.r, c:C}, supbooks, opts);
cell.F = encode_range(val[0]);
break;
case 'BrtShrFmla': if(!opts.cellFormula) break;
// TODO
case 'BrtShrFmla':
if(!opts.cellFormula) break;
shared_formulae[encode_cell(val[0].s)] = val[1];
s[encode_col(C) + rr].f = stringify_formula(val[1], refguess, {r:row.r, c:C}, supbooks, opts);
cell = (opts.dense ? s[R][C] : s[encode_col(C) + rr]);
cell.f = stringify_formula(val[1], refguess, {r:row.r, c:C}, supbooks, opts);
break;
/* identical to 'ColInfo' in XLS */
case 'BrtColInfo': {
case 'BrtColInfo':
if(!opts.cellStyles) break;
while(val.e >= val.s) {
colinfo[val.e--] = { width: val.w/256 };
if(!seencol) { seencol = true; find_mdw_colw(val.w/256); }
process_col(colinfo[val.e+1]);
}
} break;
break;
case 'BrtAFilterDateGroupItem': break;
case 'BrtActiveX': break;
case 'BrtBigName': break;
case 'BrtBkHim': break;
case 'BrtBrk': break;
case 'BrtCFIcon': break;
case 'BrtCFRuleExt': break;
case 'BrtCFVO': break;
case 'BrtCFVO14': break;
case 'BrtCellIgnoreEC': break;
case 'BrtCellIgnoreEC14': break;
case 'BrtCellMeta': break;
case 'BrtCellSmartTagProperty': break;
case 'BrtCellWatch': break;
case 'BrtColor': break;
case 'BrtColor14': break;
case 'BrtColorFilter': break;
case 'BrtCustomFilter': break;
case 'BrtCustomFilter14': break;
case 'BrtDRef': break;
case 'BrtDVal': break;
case 'BrtDVal14': break;
case 'BrtDValList': break;
case 'BrtDrawing': break;
case 'BrtDynamicFilter': break;
case 'BrtFilter': break;
case 'BrtFilter14': break;
case 'BrtIconFilter': break;
case 'BrtIconFilter14': break;
case 'BrtLegacyDrawing': break;
case 'BrtLegacyDrawingHF': break;
case 'BrtListPart': break;
case 'BrtMargins': break;
case 'BrtOleObject': break;
case 'BrtPageSetup': break;
case 'BrtPane': break;
case 'BrtPhoneticInfo': break;
case 'BrtPrintOptions': break;
case 'BrtRangeProtection': break;
case 'BrtRangeProtection14': break;
case 'BrtRangeProtectionIso': break;
case 'BrtRangeProtectionIso14': break;
case 'BrtRwDescent': break;
case 'BrtSel': break;
case 'BrtSheetCalcProp': break;
case 'BrtSheetProtection': break;
case 'BrtSheetProtectionIso': break;
case 'BrtSlc': break;
case 'BrtSparkline': break;
case 'BrtTable': break;
case 'BrtTop10Filter': break;
case 'BrtUid': break;
case 'BrtValueMeta': break;
case 'BrtWebExtension': break;
case 'BrtWsFmtInfo': break;
case 'BrtWsFmtInfoEx14': break;
case 'BrtWsProp': break;
case 'BrtBeginSheet': break;
case 'BrtWsProp': break; // TODO
case 'BrtSheetCalcProp': break; // TODO
case 'BrtBeginWsViews': break; // TODO
case 'BrtBeginWsView': break; // TODO
case 'BrtPane': break; // TODO
case 'BrtSel': break; // TODO
case 'BrtEndWsView': break; // TODO
case 'BrtEndWsViews': break; // TODO
case 'BrtACBegin': break; // TODO
case 'BrtRwDescent': break; // TODO
case 'BrtACEnd': break; // TODO
case 'BrtWsFmtInfoEx14': break; // TODO
case 'BrtWsFmtInfo': break; // TODO
case 'BrtBeginColInfos': break; // TODO
case 'BrtEndColInfos': break; // TODO
case 'BrtBeginSheetData': break; // TODO
case 'BrtEndSheetData': break; // TODO
case 'BrtSheetProtection': break; // TODO
case 'BrtPrintOptions': break; // TODO
case 'BrtMargins': break; // TODO
case 'BrtPageSetup': break; // TODO
case 'BrtFRTBegin': pass = true; break;
case 'BrtFRTEnd': pass = false; break;
case 'BrtEndSheet': break; // TODO
case 'BrtDrawing': break; // TODO
case 'BrtLegacyDrawing': break; // TODO
case 'BrtLegacyDrawingHF': break; // TODO
case 'BrtPhoneticInfo': break; // TODO
case 'BrtBeginHeaderFooter': break; // TODO
case 'BrtEndHeaderFooter': break; // TODO
case 'BrtBrk': break; // TODO
case 'BrtBeginRwBrk': break; // TODO
case 'BrtEndRwBrk': break; // TODO
case 'BrtBeginColBrk': break; // TODO
case 'BrtEndColBrk': break; // TODO
case 'BrtBeginUserShViews': break; // TODO
case 'BrtBeginUserShView': break; // TODO
case 'BrtEndUserShView': break; // TODO
case 'BrtEndUserShViews': break; // TODO
case 'BrtBkHim': break; // TODO
case 'BrtBeginOleObjects': break; // TODO
case 'BrtOleObject': break; // TODO
case 'BrtEndOleObjects': break; // TODO
case 'BrtBeginListParts': break; // TODO
case 'BrtListPart': break; // TODO
case 'BrtEndListParts': break; // TODO
case 'BrtBeginSortState': break; // TODO
case 'BrtBeginSortCond': break; // TODO
case 'BrtEndSortCond': break; // TODO
case 'BrtEndSortState': break; // TODO
case 'BrtBeginConditionalFormatting': break; // TODO
case 'BrtEndConditionalFormatting': break; // TODO
case 'BrtBeginCFRule': break; // TODO
case 'BrtEndCFRule': break; // TODO
case 'BrtBeginDVals': break; // TODO
case 'BrtDVal': break; // TODO
case 'BrtEndDVals': break; // TODO
case 'BrtRangeProtection': break; // TODO
case 'BrtBeginDCon': break; // TODO
case 'BrtEndDCon': break; // TODO
case 'BrtBeginDRefs': break;
case 'BrtDRef': break;
case 'BrtEndDRefs': break;
case 'BrtACBegin': break;
case 'BrtACEnd': break;
/* ActiveX */
case 'BrtBeginActiveXControls': break;
case 'BrtActiveX': break;
case 'BrtEndActiveXControls': break;
/* AutoFilter */
case 'BrtBeginAFilter': break;
case 'BrtEndAFilter': break;
case 'BrtBeginFilterColumn': break;
case 'BrtBeginFilters': break;
case 'BrtFilter': break;
case 'BrtEndFilters': break;
case 'BrtEndFilterColumn': break;
case 'BrtDynamicFilter': break;
case 'BrtTop10Filter': break;
case 'BrtBeginCustomFilters': break;
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;
case 'BrtEndCellWatches': break;
/* Table */
case 'BrtTable': break;
/* Ignore Cell Errors */
case 'BrtBeginCellIgnoreECs': break;
case 'BrtCellIgnoreEC': break;
case 'BrtEndCellIgnoreECs': break;
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + Record.n);
default:
if((Record.n||"").indexOf("Begin") > 0){}
else if((Record.n||"").indexOf("End") > 0){}
else if(!pass || opts.WTF) throw new Error("Unexpected record " + RT + " " + Record.n);
}
}, opts);
@ -600,6 +573,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num
function write_CELLTABLE(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbook*/) {
var range = safe_decode_range(ws['!ref'] || "A1"), ref, rr = "", cols = [];
write_record(ba, 'BrtBeginSheetData');
var dense = Array.isArray(ws);
for(var R = range.s.r; R <= range.e.r; ++R) {
rr = encode_row(R);
/* [ACCELLTABLE] */
@ -609,9 +583,10 @@ function write_CELLTABLE(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbo
/* *16384CELL */
if(R === range.s.r) cols[C] = encode_col(C);
ref = cols[C] + rr;
if(!ws[ref]) continue;
var cell = dense ? (ws[R]||[])[C] : ws[ref];
if(!cell) continue;
/* write cell */
write_ws_bin_cell(ba, ws[ref], R, C, opts, ws);
write_ws_bin_cell(ba, cell, R, C, opts, ws);
}
}
write_record(ba, 'BrtEndSheetData');

View File

@ -1,5 +1,10 @@
RELS.CS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet";
var CS_XML_ROOT = writextag('chartsheet', null, {
'xmlns': XMLNS.main[0],
'xmlns:r': XMLNS.r
});
/* 18.3 Worksheets also covers Chartsheets */
function parse_cs_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
@ -14,34 +19,67 @@ function parse_cs_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksh
if(rels['!id'][s['!rel']]) s['!chart'] = rels['!id'][s['!rel']];
return s;
}
function write_cs_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ {
var o = [XML_HEADER, CS_XML_ROOT];
o[o.length] = writextag("drawing", null, {"r:id": "rId1"});
add_rels(rels, -1, "../drawings/drawing" + (idx+1) + ".xml", RELS.DRAW);
if(o.length>2) { o[o.length] = ('</chartsheet>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}
/* [MS-XLSB] 2.1.7.7 Chart Sheet */
function parse_cs_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
if(!rels) rels = {'!id':{}};
var s = {'!type':"chart", '!chart':null, '!rel':""};
var state = [];
var pass = false;
recordhopper(data, function cs_parse(val, Record) {
recordhopper(data, function cs_parse(val, Record, RT) {
switch(Record.n) {
case 'BrtDrawing': s['!rel'] = val; break;
case 'BrtBeginSheet': break;
case 'BrtCsProp': break; // TODO
case 'BrtBeginCsViews': break; // TODO
case 'BrtBeginCsView': break; // TODO
case 'BrtEndCsView': break; // TODO
case 'BrtEndCsViews': break; // TODO
case 'BrtCsProtection': break; // TODO
case 'BrtUid': break;
case 'BrtMargins': break; // TODO
case 'BrtLegacyDrawing': break; // TODO
case 'BrtLegacyDrawingHF': break; // TODO
case 'BrtBkHim': break; // TODO
case 'BrtCsProp': break; // TODO
case 'BrtCsProtection': break; // TODO
case 'BrtCsProtectionIso': break; // TODO
case 'BrtCsPageSetup': break; // TODO
case 'BrtEndSheet': break; // TODO
case 'BrtBeginHeaderFooter': break; // TODO
case 'BrtEndHeaderFooter': break; // TODO
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + Record.n);
case 'BrtFRTBegin': pass = true; break;
case 'BrtFRTEnd': pass = false; break;
case 'BrtACBegin': state.push(R.n); break;
case 'BrtACEnd': state.pop(); break;
default:
if((Record.n||"").indexOf("Begin") > 0) state.push(Record.n);
else if((Record.n||"").indexOf("End") > 0) state.pop();
else if(!pass || opts.WTF) throw new Error("Unexpected record " + RT + " " + Record.n);
}
}, opts);
if(rels['!id'][s['!rel']]) s['!chart'] = rels['!id'][s['!rel']];
return s;
}
function write_cs_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) {
var ba = buf_array();
write_record(ba, "BrtBeginSheet");
/* [BrtCsProp] */
/* CSVIEWS */
/* [[BrtCsProtectionIso] BrtCsProtection] */
/* [USERCSVIEWS] */
/* [BrtMargins] */
/* [BrtCsPageSetup] */
/* [HEADERFOOTER] */
/* BrtDrawing */
/* [BrtLegacyDrawing] */
/* [BrtLegacyDrawingHF] */
/* [BrtBkHim] */
/* [WEBPUBITEMS] */
/* FRTCHARTSHEET */
write_record(ba, "BrtEndSheet");
return ba.end();
}

View File

@ -68,60 +68,61 @@ function parse_wb_bin(data, opts)/*:WorkbookFile*/ {
var Names = {}, NameList = [];
recordhopper(data, function hopper_wb(val, R) {
recordhopper(data, function hopper_wb(val, R, RT) {
switch(R.n) {
case 'BrtBundleSh': wb.Sheets.push(val); break;
case 'BrtName':
Names[val.Name] = val; NameList.push(val.Name);
break;
case 'BrtNameExt': break;
case 'BrtBeginBook': break;
case 'BrtFileVersion': break;
case 'BrtWbProp14': case 'BrtWbProp': break;
case 'BrtACBegin': break;
case 'BrtAbsPath15': break;
case 'BrtACEnd': break;
case 'BrtWbFactoid': break;
/*case 'BrtBookProtectionIso': break;*/
case 'BrtBookProtection': break;
case 'BrtBeginBookViews': break;
case 'BrtBookProtectionIso': break;
case 'BrtBookView': break;
case 'BrtEndBookViews': break;
case 'BrtBeginBundleShs': break;
case 'BrtEndBundleShs': break;
case 'BrtBeginFnGroup': break;
case 'BrtEndFnGroup': break;
case 'BrtBeginExternals': break;
case 'BrtSupSelf': break;
case 'BrtSupBookSrc': break;
case 'BrtExternSheet': break;
case 'BrtEndExternals': break;
case 'BrtCalcProp': break;
case 'BrtUserBookView': break;
case 'BrtBeginPivotCacheIDs': break;
case 'BrtBeginPivotCacheID': break;
case 'BrtEndPivotCacheID': break;
case 'BrtEndPivotCacheIDs': break;
case 'BrtWebOpt': break;
case 'BrtCrashRecErr': break;
case 'BrtDecoupledPivotCacheID': break;
case 'BrtExternSheet': break;
case 'BrtFileRecover': break;
case 'BrtFileSharing': break;
/*case 'BrtBeginWebPubItems': break;
case 'BrtBeginWebPubItem': break;
case 'BrtEndWebPubItem': break;
case 'BrtEndWebPubItems': break;*/
/* Smart Tags */
case 'BrtBeginSmartTagTypes': break;
case 'BrtFileSharingIso': break;
case 'BrtFileVersion': break;
case 'BrtFnGroup': break;
case 'BrtModelRelationship': break;
case 'BrtModelTable': break;
case 'BrtModelTimeGroupingCalcCol': break;
case 'BrtOleSize': break;
case 'BrtPivotTableRef': break;
case 'BrtPlaceholderName': break;
case 'BrtRevisionPtr': break;
case 'BrtSmartTagType': break;
case 'BrtEndSmartTagTypes': break;
case 'BrtSupAddin': break;
case 'BrtSupBookSrc': break;
case 'BrtSupSame': break;
case 'BrtSupSelf': break;
case 'BrtTableSlicerCacheID': break;
case 'BrtTableSlicerCacheIDs': break;
case 'BrtTimelineCachePivotCacheID': break;
case 'BrtUid': break;
case 'BrtUserBookView': break;
case 'BrtWbFactoid': break;
case 'BrtWbProp': break;
case 'BrtWbProp14': break;
case 'BrtWebOpt': break;
case 'BrtWorkBookPr15': break;
case 'BrtFRTBegin': pass = true; break;
case 'BrtFRTArchID$': break;
case 'BrtWorkBookPr15': break;
case 'BrtFRTEnd': pass = false; break;
case 'BrtEndBook': break;
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + R.n);
case 'BrtACBegin': break;
case 'BrtACEnd': break;
case 'BrtFRTArchID$': break;
default:
if((R.n||"").indexOf("Begin") > 0){}
else if((R.n||"").indexOf("End") > 0){}
else if(!pass || opts.WTF) throw new Error("Unexpected record " + RT + " " + R.n);
}
}, opts);

View File

@ -51,10 +51,14 @@ function write_wb(wb, name/*:string*/, opts) {
return (name.slice(-4)===".bin" ? write_wb_bin : write_wb_xml)(wb, opts);
}
function write_ws(data/*:Worksheet*/, name/*:string*/, opts, wb/*:Workbook*/, rels) {
function write_ws(data/*:number*/, name/*:string*/, opts, wb/*:Workbook*/, rels) {
return (name.slice(-4)===".bin" ? write_ws_bin : write_ws_xml)(data, opts, wb, rels);
}
function write_cs(data/*:number*/, name/*:string*/, opts, wb/*:Workbook*/, rels) {
return (name.slice(-4)===".bin" ? write_cs_bin : write_cs_xml)(data, opts, wb, rels);
}
function write_sty(data, name/*:string*/, opts) {
return (name.slice(-4)===".bin" ? write_sty_bin : write_sty_xml)(data, opts);
}

View File

@ -174,7 +174,8 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
if(str.substr(0,1000).indexOf("<html") >= 0) return parse_html(str, opts);
var Rn;
var state = [], tmp;
var sheets = {}, sheetnames = [], cursheet = {}, sheetname = "";
if(DENSE != null) opts.dense = DENSE;
var sheets = {}, sheetnames = [], cursheet = (opts.dense ? [] : {}), sheetname = "";
var table = {}, cell = ({}/*:any*/), row = {};
var dtag = xlml_parsexmltag('<Data ss:Type="String">'), didx = 0;
var c = 0, r = 0;
@ -199,7 +200,12 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
case 'Cell':
if(Rn[1]==='/'){
if(comments.length > 0) cell.c = comments;
if((!opts.sheetRows || opts.sheetRows > r) && cell.v !== undefined) cursheet[encode_col(c) + encode_row(r)] = cell;
if((!opts.sheetRows || opts.sheetRows > r) && cell.v !== undefined) {
if(opts.dense) {
if(!cursheet[r]) cursheet[r] = [];
cursheet[r][c] = cell;
} else cursheet[encode_col(c) + encode_row(r)] = cell;
}
if(cell.HRef) {
cell.l = {Target:cell.HRef, Tooltip:cell.HRefScreenTip};
delete cell.HRef; delete cell.HRefScreenTip;
@ -214,7 +220,12 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
/*:: if(!cc) cc = 0; if(!rr) rr = 0; */
for(var cma = c; cma <= cc; ++cma) {
for(var cmd = r; cmd <= rr; ++cmd) {
if(cma > c || cmd > r) cursheet[encode_col(cma) + encode_row(cmd)] = {t:'z'};
if(cma > c || cmd > r) {
if(opts.dense) {
if(!cursheet[cmd]) cursheet[cmd] = [];
cursheet[cmd][cma] = {t:'z'};
} else cursheet[encode_col(cma) + encode_row(cmd)] = {t:'z'};
}
}
}
c = cc + 1;
@ -258,7 +269,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
state.push([Rn[3], false]);
tmp = xlml_parsexmltag(Rn[0]);
sheetname = unescapexml(tmp.Name);
cursheet = {};
cursheet = (opts.dense ? [] : {});
mergecells = [];
arrayf = [];
rowinfo = [];
@ -868,6 +879,7 @@ function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbo
var p = col_obj_w(i, n);
o.push(writextag("Column",null, {"ss:Index":i+1, "ss:Width":width2px(p.width)}));
});
var dense = Array.isArray(ws);
for(var R = range.s.r; R <= range.e.r; ++R) {
var row = ['<Row ss:Index="' + (R+1) + '">'];
for(var C = range.s.c; C <= range.e.c; ++C) {
@ -882,8 +894,8 @@ function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbo
}
if(skip) continue;
var addr = {r:R,c:C};
var ref = encode_cell(addr), cell = ws[ref];
row.push(write_ws_xlml_cell(ws[ref], ref, ws, opts, idx, wb, addr));
var ref = encode_cell(addr), cell = dense ? (ws[R]||[])[C] : ws[ref];
row.push(write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr));
}
row.push("</Row>");
if(row.length > 2) o.push(row.join(""));

View File

@ -82,7 +82,8 @@ function make_cell(val, ixfe, t)/*:any*/ {
function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
var wb = ({opts:{}}/*:any*/);
var Sheets = {};
var out = {};
if(DENSE != null) options.dense = DENSE;
var out = (options.dense ? [] : {});
var Directory = {};
var found_sheet = false;
var range/*:Range*/ = ({}/*:any*/);
@ -141,7 +142,12 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
}
}
if(options.sheetRows && lastcell.r >= options.sheetRows) cell_valid = false;
else out[last_cell] = line;
else {
if(options.dense) {
if(!out[cell.r]) out[cell.r] = [];
out[cell.r][cell.c] = line;
} else out[last_cell] = line;
}
};
var opts = ({
enc: false, // encrypted
@ -175,7 +181,6 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
/* explicit override for some broken writers */
opts.codepage = 1200;
set_cp(1200);
while(blob.l < blob.length - 1) {
var s = blob.l;
var RecordType = blob.read_shift(2);
@ -273,7 +278,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
Workbook.Sheets.push(wsprops);
}
if(cur_sheet === "") Preamble = out; else Sheets[cur_sheet] = out;
out = {};
out = options.dense ? [] : {};
} break;
case 'BOF': {
if(opts.biff !== 8){}
@ -286,7 +291,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
else if(val.BIFFVer === 0x0007) opts.biff = 2;
if(file_depth++) break;
cell_valid = true;
out = {};
out = (options.dense ? [] : {});
if(opts.biff < 5) {
if(cur_sheet === "") cur_sheet = "Sheet1";
@ -308,7 +313,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
} break;
case 'Number': case 'BIFF2NUM': case 'BIFF2INT': {
if(out["!type"] == "chart" && out[encode_cell({c:val.c, r:val.r})]) ++val.c;
if(out["!type"] == "chart") if(options.dense ? (out[val.r]||[])[val.c]: out[encode_cell({c:val.c, r:val.r})]) ++val.c;
temp_val = {ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.val, t:'n'};
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
@ -341,7 +346,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
var _fr = _f[0][0][1][0], _fc = _f[0][0][1][1];
var _fe = encode_cell({r:_fr, c:_fc});
if(shared_formulae[_fe]) temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts);
else temp_val.F = (out[_fe] || {}).F;
else temp_val.F = ((options.dense ? (out[_fr]||[])[_fc]: out[_fe]) || {}).F;
} else temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts);
}
safe_format_xf(temp_val, options, wb.opts.Date1904);
@ -364,11 +369,12 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
case 'Array': {
array_formulae.push(val);
var _arraystart = encode_cell(val[0].s);
if(options.cellFormula && out[_arraystart]) {
cc = options.dense ? (out[val[0].s.r]||[])[val[0].s.c] : out[_arraystart];
if(options.cellFormula && cc) {
if(!last_formula) break; /* technically unreachable */
if(!_arraystart || !out[_arraystart]) break;
out[_arraystart].f = ""+stringify_formula(val[1], range, val[0], supbooks, opts);
out[_arraystart].F = encode_range(val[0]);
if(!_arraystart || !cc) break;
cc.f = ""+stringify_formula(val[1], range, val[0], supbooks, opts);
cc.F = encode_range(val[0]);
}
} break;
case 'ShrFmla': {
@ -378,7 +384,8 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
/* TODO: capture range */
if(!last_formula) break; /* technically unreachable */
shared_formulae[encode_cell(last_formula.cell)]= val[0];
(out[encode_cell(last_formula.cell)]||{}).f = ""+stringify_formula(val[0], range, lastcell, supbooks, opts);
cc = options.dense ? (out[last_formula.cell.r]||[])[last_formula.cell.c] : out[encode_cell(last_formula.cell)];
(cc||{}).f = ""+stringify_formula(val[0], range, lastcell, supbooks, opts);
}
} break;
case 'LabelSst':
@ -428,21 +435,23 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
case 'HLink': {
for(rngR = val[0].s.r; rngR <= val[0].e.r; ++rngR)
for(rngC = val[0].s.c; rngC <= val[0].e.c; ++rngC)
if(out[encode_cell({c:rngC,r:rngR})])
out[encode_cell({c:rngC,r:rngR})].l = val[1];
for(rngC = val[0].s.c; rngC <= val[0].e.c; ++rngC) {
cc = options.dense ? (out[rngR]||[])[rngC] : out[encode_cell({c:rngC,r:rngR})];
if(cc) cc.l = val[1];
}
} break;
case 'HLinkTooltip': {
for(rngR = val[0].s.r; rngR <= val[0].e.r; ++rngR)
for(rngC = val[0].s.c; rngC <= val[0].e.c; ++rngC)
if(out[encode_cell({c:rngC,r:rngR})])
out[encode_cell({c:rngC,r:rngR})].l.Tooltip = val[1];
for(rngC = val[0].s.c; rngC <= val[0].e.c; ++rngC) {
cc = options.dense ? (out[rngR]||[])[rngC] : out[encode_cell({c:rngC,r:rngR})];
if(cc) cc.l.Tooltip = val[1];
}
} break;
/* Comments */
case 'Note': {
if(opts.biff <= 5 && opts.biff >= 2) break; /* TODO: BIFF5 */
cc = out[encode_cell(val[0])];
cc = options.dense ? (out[val[0].r]||[])[val[0].c] : out[encode_cell(val[0])];
var noteobj = objects[val[2]];
if(!cc) break;
if(!cc.c) cc.c = [];

View File

@ -75,21 +75,25 @@ function write_ws_biff_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:nu
}
function write_biff_ws(ba/*:BufArray*/, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbook*/) {
var dense = Array.isArray(ws);
var range = safe_decode_range(ws['!ref'] || "A1"), ref, rr = "", cols = [];
for(var R = range.s.r; R <= range.e.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);
ref = cols[C] + rr;
if(!ws[ref]) continue;
var cell = dense ? ws[R][C] : ws[ref];
if(!cell) continue;
/* write cell */
write_ws_biff_cell(ba, ws[ref], R, C, opts);
write_ws_biff_cell(ba, cell, R, C, opts);
}
}
}
/* Based on test files */
function write_biff_buf(wb/*:Workbook*/, o/*:WriteOpts*/) {
function write_biff_buf(wb/*:Workbook*/, opts/*:WriteOpts*/) {
var o = opts || {};
if(DENSE != null) o.dense = DENSE;
var ba = buf_array();
var idx = 0;
for(var i=0;i<wb.SheetNames.length;++i) if(wb.SheetNames[i] == o.sheet) idx=i;

View File

@ -1,6 +1,8 @@
/* TODO: in browser attach to DOM; in node use an html parser */
function parse_html(str/*:string*/, opts)/*:Workbook*/ {
var ws/*:Worksheet*/ = ({}/*:any*/);
function parse_html(str/*:string*/, _opts)/*:Workbook*/ {
var opts = _opts || {};
if(DENSE != null) opts.dense = DENSE;
var ws/*:Worksheet*/ = opts.dense ? ([]/*:any*/) : ({}/*:any*/);
var o/*:Workbook*/ = { SheetNames: ["Sheet1"], Sheets: {Sheet1:ws} };
var i = str.indexOf("<table"), j = str.indexOf("</table");
if(i == -1 || j == -1) throw new Error("Invalid HTML: missing <table> / </table> pair");
@ -23,10 +25,16 @@ function parse_html(str/*:string*/, opts)/*:Workbook*/ {
if(range.e.r < R) range.e.r = R;
if(range.s.c > C) range.s.c = C;
if(range.e.c < C) range.e.c = C;
var coord/*:string*/ = encode_cell({r:R, c:C});
/* TODO: value parsing */
if(Number(m) == Number(m)) ws[coord] = {t:'n', v:+m};
else ws[coord] = {t:'s', v:m};
if(opts.dense) {
if(!ws[R]) ws[R] = [];
if(Number(m) == Number(m)) ws[R][C] = {t:'n', v:+m};
else ws[R][C] = {t:'s', v:m};
} else {
var coord/*:string*/ = encode_cell({r:R, c:C});
/* TODO: value parsing */
if(Number(m) == Number(m)) ws[coord] = {t:'n', v:+m};
else ws[coord] = {t:'s', v:m};
}
}
++R; C = 0;
}
@ -34,8 +42,10 @@ function parse_html(str/*:string*/, opts)/*:Workbook*/ {
return o;
}
function parse_dom_table(table/*:HTMLElement*/, opts/*:?any*/)/*:Worksheet*/ {
var ws/*:Worksheet*/ = ({}/*:any*/);
function parse_dom_table(table/*:HTMLElement*/, _opts/*:?any*/)/*:Worksheet*/ {
var opts = _opts || {};
if(DENSE != null) opts.dense = DENSE;
var ws/*:Worksheet*/ = opts.dense ? ([]/*:any*/) : ({}/*:any*/);
var rows = table.getElementsByTagName('tr');
var range = {s:{r:0,c:0},e:{r:rows.length - 1,c:0}};
var merges = [], midx = 0;
@ -54,7 +64,8 @@ function parse_dom_table(table/*:HTMLElement*/, opts/*:?any*/)/*:Worksheet*/ {
if((RS = +elt.getAttribute("rowspan"))>0) merges.push({s:{r:R,c:C},e:{r:R + RS - 1, c:C + CS - 1}});
var o = {t:'s', v:v};
if(v != null && v.length && !isNaN(Number(v))) o = {t:'n', v:Number(v)};
ws[encode_cell({c:C, r:R})] = o;
if(opts.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = o; }
else ws[encode_cell({c:C, r:R})] = o;
if(range.e.c < C) range.e.c = C;
C += CS;
}

View File

@ -18,13 +18,15 @@ var parse_content_xml = (function() {
return function pcx(d/*:string*/, _opts)/*:Workbook*/ {
var opts = _opts || {};
if(DENSE != null) opts.dense = DENSE;
var str = xlml_normalize(d);
var state/*:Array<any>*/ = [], tmp;
var tag/*:: = {}*/;
var NFtag = {name:""}, NF = "", pidx = 0;
var sheetag/*:: = {name:"", '名称':""}*/;
var rowtag/*:: = {'行号':""}*/;
var Sheets = {}, SheetNames/*:Array<string>*/ = [], ws = {};
var Sheets = {}, SheetNames/*:Array<string>*/ = [];
var ws = opts.dense ? ([]/*:any*/) : ({}/*:any*/);
var Rn, q/*:: = ({t:"", v:null, z:null, w:"",c:[]}:any)*/;
var ctag = {value:""};
var textp = "", textpidx = 0, textptag/*:: = {}*/;
@ -52,7 +54,7 @@ var parse_content_xml = (function() {
sheetag = parsexmltag(Rn[0], false);
R = C = -1;
range.s.r = range.s.c = 10000000; range.e.r = range.e.c = 0;
ws = {}; merges = [];
ws = opts.dense ? ([]/*:any*/) : ({}/*:any*/); merges = [];
}
break;
@ -63,7 +65,10 @@ var parse_content_xml = (function() {
C = -1; break;
case 'covered-table-cell': // 9.1.5 <table:covered-table-cell>
++C;
if(opts.sheetStubs) ws[encode_cell({r:R,c:C})] = {t:'z'};
if(opts.sheetStubs) {
if(opts.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = {t:'z'}; }
else ws[encode_cell({r:R,c:C})] = {t:'z'};
}
break; /* stub */
case 'table-cell': case '数据':
if(Rn[0].charAt(Rn[0].length-2) === '/') {
@ -133,8 +138,14 @@ var parse_content_xml = (function() {
if(textp) q.w = textp;
if(!isstub || opts.sheetStubs) {
if(!(opts.sheetRows && opts.sheetRows < R)) {
ws[encode_cell({r:R,c:C})] = q;
while(--rept > 0) ws[encode_cell({r:R,c:++C})] = dup(q);
if(opts.dense) {
if(!ws[R]) ws[R] = [];
ws[R][C] = q;
while(--rept > 0) ws[R][++C] = dup(q);
} else {
ws[encode_cell({r:R,c:C})] = q;
while(--rept > 0) ws[encode_cell({r:R,c:++C})] = dup(q);
}
if(range.e.c <= C) range.e.c = C;
}
} else { C += rept; rept = 0; }

View File

@ -10,6 +10,7 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() {
o.push(' <table:table table:name="' + escapexml(wb.SheetNames[i]) + '">\n');
var R=0,C=0, range = decode_range(ws['!ref']);
var marr = ws['!merges'] || [], mi = 0;
var dense = Array.isArray(ws);
for(R = 0; R < range.s.r; ++R) o.push(' <table:table-row></table:table-row>\n');
for(; R <= range.e.r; ++R) {
o.push(' <table:table-row>\n');
@ -26,7 +27,7 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() {
break;
}
if(skip) { o.push(covered_cell_xml); continue; }
var ref = encode_cell({r:R, c:C}), cell = ws[ref];
var ref = encode_cell({r:R, c:C}), cell = dense ? (ws[R]||[])[C]: ws[ref];
var fmla = "";
if(cell && cell.f) {
fmla = ' table:formula="' + escapexml(csf_to_ods_formula(cell.f)) + '"';

View File

@ -115,14 +115,14 @@ function parse_zip(zip/*:ZIP*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
var sheetRels = ({}/*:any*/);
var path, relsPath;
//if(!props.Worksheets) {
{
var wbsheets = wb.Sheets;
props.Worksheets = wbsheets.length;
props.SheetNames = [];
for(var j = 0; j != wbsheets.length; ++j) {
props.SheetNames[j] = wbsheets[j].name;
}
//}
}
var wbext = xlsb ? "bin" : "xml";
var wbrelsfile = 'xl/_rels/workbook.' + wbext + '.rels';

View File

@ -14,9 +14,12 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
opts.Strings = /*::((*/[]/*:: :any):SST)*/; opts.Strings.Count = 0; opts.Strings.Unique = 0;
var wbext = opts.bookType == "xlsb" ? "bin" : "xml";
var vbafmt = opts.bookType == "xlsb" || opts.bookType == "xlsm";
var ct = { workbooks: [], sheets: [], calcchains: [], themes: [], styles: [],
coreprops: [], extprops: [], custprops: [], strs:[], comments: [], vba: [],
TODO:[], rels:[], xmlns: "" };
var ct = ({
workbooks:[], sheets:[], charts:[], dialogs:[], macros:[],
rels:[], strs:[], comments:[],
coreprops:[], extprops:[], custprops:[], themes:[], styles:[],
calcchains:[], vba: [], drawings: [],
TODO:[], xmlns: "" }/*:any*/);
fix_write_opts(opts = opts || {});
/*:: if(!jszip) throw new Error("JSZip is not available"); */
var zip = new jszip();
@ -56,14 +59,24 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
add_rels(opts.rels, 1, f, RELS.WB);
for(rId=1;rId <= wb.SheetNames.length; ++rId) {
f = "xl/worksheets/sheet" + rId + "." + wbext;
var wsrels = {'!id':{}};
zip.file(f, write_ws(rId-1, f, opts, wb, wsrels));
ct.sheets.push(f);
add_rels(opts.wbrels, -1, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]);
var ws = wb.Sheets[wb.SheetNames[rId-1]];
var _type = (ws || {})["!type"] || "sheet";
switch(_type) {
case "chart": /*
f = "xl/chartsheets/sheet" + rId + "." + wbext;
zip.file(f, write_cs(rId-1, f, opts, wb, wsrels));
ct.charts.push(f);
add_rels(wsrels, -1, "chartsheets/sheet" + rId + "." + wbext, RELS.CS);
break; */
/* falls through */
default:
f = "xl/worksheets/sheet" + rId + "." + wbext;
zip.file(f, write_ws(rId-1, f, opts, wb, wsrels));
ct.sheets.push(f);
add_rels(opts.wbrels, -1, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]);
}
if(ws) {
var comments = ws['!comments'];
if(comments && comments.length > 0) {
@ -79,7 +92,7 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
delete ws['!legacy'];
}
if(wsrels['!id'].rId1) zip.file(get_rels_path(f), write_rels(wsrels)); // get_rels_path('')
if(wsrels['!id'].rId1) zip.file(get_rels_path(f), write_rels(wsrels));
}
if(opts.Strings != null && opts.Strings.length > 0) {
@ -111,7 +124,7 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
}
zip.file("[Content_Types].xml", write_ct(ct, opts));
zip.file('_rels/.rels', write_rels(opts.rels)); // get_rels_path('')
zip.file('xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels)); // get_rels_path("xl/workbook." + wbext)
zip.file('_rels/.rels', write_rels(opts.rels));
zip.file('xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels));
return zip;
}

View File

@ -94,9 +94,10 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){
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;
var dense = Array.isArray(sheet);
for(C = r.s.c; C <= r.e.c; ++C) {
cols[C] = encode_col(C);
val = sheet[cols[C] + rr];
val = dense ? (sheet[r.s.r] || [])[C] : sheet[cols[C] + rr];
switch(header) {
case 1: hdr[C] = C; break;
case 2: hdr[C] = cols[C]; break;
@ -120,7 +121,7 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){
else row.__rowNum__ = R;
}
for (C = r.s.c; C <= r.e.c; ++C) {
val = sheet[cols[C] + rr];
val = dense ? (sheet[R] || [])[C] : sheet[cols[C] + rr];
if(val === undefined || val.t === undefined) {
if(defval === undefined) continue;
if(hdr[C] != null) { row[hdr[C]] = defval; isempty = false; }
@ -161,13 +162,14 @@ function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
var row = "", rr = "", cols = [];
var i = 0, cc = 0, val;
var R = 0, C = 0;
var dense = Array.isArray(sheet);
for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C);
for(R = r.s.r; R <= r.e.r; ++R) {
var isempty = true;
row = "";
rr = encode_row(R);
for(C = r.s.c; C <= r.e.c; ++C) {
val = sheet[cols[C] + rr];
val = dense ? (sheet[R]||[])[C]: sheet[cols[C] + rr];
if(val == null) txt = "";
else if(val.v != null) {
isempty = false;
@ -187,7 +189,7 @@ function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
}
return out;
}
var make_csv = sheet_to_csv;
function sheet_to_txt(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
if(!opts) opts = {}; opts.FS = "\t"; opts.RS = "\n";
var s = sheet_to_csv(sheet, opts);
@ -202,12 +204,13 @@ function sheet_to_formulae(sheet/*:Worksheet*/)/*:Array<string>*/ {
var r = safe_decode_range(sheet['!ref']), rr = "", cols = [], C;
var cmds = new Array((r.e.r-r.s.r+1)*(r.e.c-r.s.c+1));
var i = 0;
var dense = Array.isArray(sheet);
for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C);
for(var R = r.s.r; R <= r.e.r; ++R) {
rr = encode_row(R);
for(C = r.s.c; C <= r.e.c; ++C) {
y = cols[C] + rr;
x = sheet[y];
x = dense ? (sheet[R]||[])[C] : sheet[y];
val = "";
if(x === undefined) continue;
else if(x.F != null) {

View File

@ -23,7 +23,7 @@
<body>
<b>JS-XLSX Live Demo</b><br />
Output Format:
<select name="format">
<select name="format" onchange="setfmt()">
<option value="csv" selected> CSV</option>
<option value="json"> JSON</option>
<option value="form"> FORMULAE</option>
@ -186,7 +186,9 @@ function b64it() {
process_wb(wb);
}
var global_wb;
function process_wb(wb) {
global_wb = wb;
var output = "";
switch(get_radio_value("format")) {
case "json":
@ -202,6 +204,7 @@ function process_wb(wb) {
else out.innerText = output;
if(typeof console !== 'undefined') console.log("output", new Date());
}
function setfmt() {if(global_wb) process_wb(global_wb); }
var drop = document.getElementById('drop');
function handleDrop(e) {

207
test.js
View File

@ -222,19 +222,30 @@ describe('should parse test files', function() {
});
});
function get_cell(ws/*:Worksheet*/, addr/*:string*/) {
if(!Array.isArray(ws)) return ws[addr];
var a = X.utils.decode_cell(addr);
return (ws[a.r]||[])[a.c];
}
function each_cell(ws, f) {
if(Array.isArray(ws)) ws.forEach(function(row) { if(row) row.forEach(f); });
else Object.keys(ws).forEach(function(addr) { if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; f(ws[addr]); });
}
/* comments_stress_test family */
function check_comments(wb) {
var ws0 = wb.Sheets.Sheet2;
assert.equal(ws0.A1.c[0].a, 'Author');
assert.equal(ws0.A1.c[0].t, 'Author:\nGod thinks this is good');
assert.equal(ws0.C1.c[0].a, 'Author');
assert.equal(ws0.C1.c[0].t, 'I really hope that xlsx decides not to use magic like rPr');
assert.equal(get_cell(ws0,"A1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"A1").c[0].t, 'Author:\nGod thinks this is good');
assert.equal(get_cell(ws0,"C1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"C1").c[0].t, 'I really hope that xlsx decides not to use magic like rPr');
var ws3 = wb.Sheets.Sheet4;
assert.equal(ws3.B1.c[0].a, 'Author');
assert.equal(ws3.B1.c[0].t, 'The next comment is empty');
assert.equal(ws3.B2.c[0].a, 'Author');
assert.equal(ws3.B2.c[0].t, '');
assert.equal(get_cell(ws3,"B1").c[0].a, 'Author');
assert.equal(get_cell(ws3,"B1").c[0].t, 'The next comment is empty');
assert.equal(get_cell(ws3,"B2").c[0].a, 'Author');
assert.equal(get_cell(ws3,"B2").c[0].t, '');
}
describe('parse options', function() {
@ -248,17 +259,15 @@ describe('parse options', function() {
it('XLSX should generate HTML by default', function() {
var wb = X.readFile(paths.cstxlsx);
var ws = wb.Sheets.Sheet1;
Object.keys(ws).forEach(function(addr) {
if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return;
assert(html_cell_types.indexOf(ws[addr].t) === -1 || ws[addr].h);
each_cell(ws, function(cell) {
assert(html_cell_types.indexOf(cell.t) === -1 || cell.h);
});
});
it('XLSX should not generate HTML when requested', function() {
var wb = X.readFile(paths.cstxlsx, {cellHTML:false});
var ws = wb.Sheets.Sheet1;
Object.keys(ws).forEach(function(addr) {
if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return;
assert(typeof ws[addr].h === 'undefined');
each_cell(ws, function(cell) {
assert(typeof cell.h === 'undefined');
});
});
it('should generate formulae by default', function() {
@ -267,9 +276,8 @@ describe('parse options', function() {
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].f !== 'undefined') return (found = true);
each_cell(ws, function(cell) {
if(typeof cell.f !== 'undefined') return (found = true);
});
});
assert(found);
@ -280,98 +288,95 @@ describe('parse options', function() {
var wb =X.readFile(p,{cellFormula:false});
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].f === 'undefined');
each_cell(ws, function(cell) {
assert(typeof cell.f === 'undefined');
});
});
});
});
it('should not generate number formats by default', function() {
[paths.nfxls, paths.nfxlsx].forEach(function(p) {
[paths.nfxls, paths.nfxlsx, paths.nfxlsb].forEach(function(p) {
var wb = X.readFile(p);
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].z === 'undefined');
each_cell(ws, function(cell) {
assert(typeof cell.z === 'undefined');
});
});
});
});
it('should generate number formats when requested', function() {
[paths.nfxls, paths.nfxlsx].forEach(function(p) {
[paths.nfxls, paths.nfxlsx, paths.nfxlsb].forEach(function(p) {
var wb = X.readFile(p, {cellNF: true});
wb.SheetNames.forEach(function(s) {
var ws = wb.Sheets[s];
Object.keys(ws).forEach(function(addr) {
if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return;
assert(ws[addr].t!== 'n' || typeof ws[addr].z !== 'undefined');
each_cell(ws, function(cell) {
assert(cell.t!== 'n' || typeof cell.z !== 'undefined');
});
});
});
});
it('should not generate cell styles by default', function() {
[paths.cssxlsx, paths.cssxls, paths.cssxml].forEach(function(p) {
var wb = X.readFile(p);
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');
[paths.cssxlsx, paths.cssxlsb, paths.cssxls, paths.cssxml].forEach(function(p) {
var wb = X.readFile(p);
wb.SheetNames.forEach(function(s) {
var ws = wb.Sheets[s];
each_cell(ws, function(cell) {
assert(typeof cell.s === 'undefined');
});
});
});
});
});
it('should generate cell styles when requested', function() {
/* TODO: XLS / XLML */
[paths.cssxlsx /*,paths.cssxls, paths.cssxml*/].forEach(function(p) {
var wb = X.readFile(p, {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);
[paths.cssxlsx /*, paths.cssxlsb, paths.cssxls, paths.cssxml*/].forEach(function(p) {
var wb = X.readFile(p, {cellStyles:true});
var found = false;
wb.SheetNames.forEach(function(s) {
var ws = wb.Sheets[s];
each_cell(ws, function(cell) {
if(typeof cell.s !== 'undefined') return (found = true);
});
});
});
assert(found);
assert(found);
});
});
it('should not generate cell dates by default', function() {
var wb = X.readFile(paths.dtxlsx);
wb.SheetNames.forEach(function(s) {
var ws = wb.Sheets[s];
Object.keys(ws).forEach(function(addr) {
if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return;
assert(ws[addr].t !== 'd');
[paths.dtxlsx, paths.dtxlsb, paths.dtxls, paths.dtxml].forEach(function(p) {
var wb = X.readFile(p);
wb.SheetNames.forEach(function(s) {
var ws = wb.Sheets[s];
each_cell(ws, function(cell) {
assert(cell.t !== 'd');
});
});
});
});
it('XLSX should generate cell dates when requested', function() {
var wb = X.readFile(paths.dtxlsx, {cellDates: 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(ws[addr].t === 'd') return (found = true);
[paths.dtxlsx, paths.dtxlsb, paths.dtxls, paths.dtxml].forEach(function(p) {
var wb = X.readFile(paths.dtxlsx, {cellDates: true});
var found = false;
wb.SheetNames.forEach(function(s) {
var ws = wb.Sheets[s];
each_cell(ws, function(cell) {
if(cell.t === 'd') return (found = true);
});
});
assert(found);
});
assert(found);
});
});
describe('sheet', function() {
it('should not generate sheet stubs by default', function() {
[paths.mcxlsx, paths.mcxlsb, paths.mcods, paths.mcxls, paths.mcxml].forEach(function(p) {
var wb = X.readFile(p);
assert.throws(function() { return wb.Sheets.Merge.A2.v; });
assert.throws(function() { return get_cell(wb.Sheets.Merge, "A2").v; });
});
});
it('should generate sheet stubs when requested', function() {
[paths.mcxlsx, paths.mcxlsb, paths.mcods, paths.mcxls, paths.mcxml].forEach(function(p) {
var wb = X.readFile(p, {sheetStubs:true});
assert(wb.Sheets.Merge.A2.t == 'z');
assert(get_cell(wb.Sheets.Merge, "A2").t == 'z');
});
});
it('should handle stub cells', function() {
@ -384,10 +389,10 @@ describe('parse options', function() {
});
});
function checkcells(wb, A46, B26, C16, D2) {
assert((typeof wb.Sheets.Text.A46 !== 'undefined') == A46);
assert((typeof wb.Sheets.Text.B26 !== 'undefined') == B26);
assert((typeof wb.Sheets.Text.C16 !== 'undefined') == C16);
assert((typeof wb.Sheets.Text.D2 !== 'undefined') == D2);
assert((typeof get_cell(wb.Sheets.Text, "A46") !== 'undefined') == A46);
assert((typeof get_cell(wb.Sheets.Text, "B26") !== 'undefined') == B26);
assert((typeof get_cell(wb.Sheets.Text, "C16") !== 'undefined') == C16);
assert((typeof get_cell(wb.Sheets.Text, "D2") !== 'undefined') == D2);
}
it('should read all cells by default', function() {
[paths.fstxlsx, paths.fstxlsb, paths.fstods, paths.fstxls, paths.fstxml].forEach(function(p) {
@ -604,7 +609,7 @@ var stykeys = [
"bgColor.rgb"
];
function diffsty(ws, r1,r2) {
var c1 = ws[r1].s, c2 = ws[r2].s;
var c1 = get_cell(ws,r1).s, c2 = get_cell(ws,r2).s;
stykeys.forEach(function(m) {
var c = -1;
if(styexc.indexOf(r1+"|"+r2+"|"+m) > -1) c = 1;
@ -615,14 +620,14 @@ function diffsty(ws, r1,r2) {
function hlink(wb) {
var ws = wb.Sheets.Sheet1;
assert.equal(ws.A1.l.Target, "http://www.sheetjs.com");
assert.equal(ws.A2.l.Target, "http://oss.sheetjs.com");
assert.equal(ws.A3.l.Target, "http://oss.sheetjs.com#foo");
assert.equal(ws.A4.l.Target, "mailto:dev@sheetjs.com");
assert.equal(ws.A5.l.Target, "mailto:dev@sheetjs.com?subject=hyperlink");
assert.equal(ws.A6.l.Target, "../../sheetjs/Documents/Test.xlsx");
assert.equal(ws.A7.l.Target, "http://sheetjs.com");
assert.equal(ws.A7.l.Tooltip, "foo bar baz");
assert.equal(get_cell(ws, "A1").l.Target, "http://www.sheetjs.com");
assert.equal(get_cell(ws, "A2").l.Target, "http://oss.sheetjs.com");
assert.equal(get_cell(ws, "A3").l.Target, "http://oss.sheetjs.com#foo");
assert.equal(get_cell(ws, "A4").l.Target, "mailto:dev@sheetjs.com");
assert.equal(get_cell(ws, "A5").l.Target, "mailto:dev@sheetjs.com?subject=hyperlink");
assert.equal(get_cell(ws, "A6").l.Target, "../../sheetjs/Documents/Test.xlsx");
assert.equal(get_cell(ws, "A7").l.Target, "http://sheetjs.com");
assert.equal(get_cell(ws, "A7").l.Tooltip, "foo bar baz");
}
@ -668,12 +673,12 @@ describe('parse features', function() {
var wb4=X.readFile(paths.swcxml);
[wb1,wb2,wb3,wb4].map(function(wb) { return wb.Sheets[sheet]; }).forEach(function(ws, i) {
assert.equal(ws.B1.c.length, 1,"must have 1 comment");
assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author");
assert.equal(ws.B1.c[0].t, "Yegor Kozlov:\nfirst cell", "must have the concatenated texts");
assert.equal(get_cell(ws, "B1").c.length, 1,"must have 1 comment");
assert.equal(get_cell(ws, "B1").c[0].a, "Yegor Kozlov","must have the same author");
assert.equal(get_cell(ws, "B1").c[0].t, "Yegor Kozlov:\nfirst cell", "must have the concatenated texts");
if(i > 0) return;
assert.equal(ws.B1.c[0].r, '<r><rPr><b/><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t>Yegor Kozlov:</t></r><r><rPr><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t xml:space="preserve">\r\nfirst cell</t></r>', "must have the rich text representation");
assert.equal(ws.B1.c[0].h, '<span style="font-weight: bold;">Yegor Kozlov:</span><span style=""><br/>first cell</span>', "must have the html representation");
assert.equal(get_cell(ws, "B1").c[0].r, '<r><rPr><b/><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t>Yegor Kozlov:</t></r><r><rPr><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t xml:space="preserve">\r\nfirst cell</t></r>', "must have the rich text representation");
assert.equal(get_cell(ws, "B1").c[0].h, '<span style="font-weight: bold;">Yegor Kozlov:</span><span style=""><br/>first cell</span>', "must have the html representation");
});
});
[
@ -686,10 +691,10 @@ describe('parse features', function() {
var wb = X.readFile(m[1]);
check_comments(wb);
var ws0 = wb.Sheets.Sheet2;
assert.equal(ws0.A1.c[0].a, 'Author');
assert.equal(ws0.A1.c[0].t, 'Author:\nGod thinks this is good');
assert.equal(ws0.C1.c[0].a, 'Author');
assert.equal(ws0.C1.c[0].t, 'I really hope that xlsx decides not to use magic like rPr');
assert.equal(get_cell(ws0,"A1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"A1").c[0].t, 'Author:\nGod thinks this is good');
assert.equal(get_cell(ws0,"C1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"C1").c[0].t, 'I really hope that xlsx decides not to use magic like rPr');
}); });
});
@ -871,15 +876,15 @@ describe('parse features', function() {
var wb, ws;
wb = X.readFile(f[1]);
ws = wb.Sheets[f[2]];
assert.equal(ws[f[3]].w, f[4]);
assert.equal(ws[f[3]].t, 'n');
assert.equal(get_cell(ws, f[3]).w, f[4]);
assert.equal(get_cell(ws, f[3]).t, 'n');
}); });
it('should generate date cells if cellDates is true', function() { fmts.forEach(function(f) {
var wb, ws;
wb = X.readFile(f[1], {cellDates:true});
ws = wb.Sheets[f[2]];
assert.equal(ws[f[3]].w, f[4]);
assert.equal(ws[f[3]].t, 'd');
assert.equal(get_cell(ws, f[3]).w, f[4]);
assert.equal(get_cell(ws, f[3]).t, 'd');
}); });
});
@ -931,8 +936,8 @@ describe('parse features', function() {
bgColor: { theme: 7, raw_rgb: '8064A2' } }
];
ranges.forEach(function(rng) {
it('XLS | ' + rng,function(){cmparr(rn2(rng).map(function(x){ return wsxls[x].s; }));});
it('XLSX | ' + rng,function(){cmparr(rn2(rng).map(function(x){ return wsxlsx[x].s; }));});
it('XLS | ' + rng,function(){cmparr(rn2(rng).map(function(x){ return get_cell(wsxls,x).s; }));});
it('XLSX | ' + rng,function(){cmparr(rn2(rng).map(function(x){ return get_cell(wsxlsx,x).s; }));});
});
it('different styles', function() {
for(var i = 0; i != ranges.length-1; ++i) {
@ -944,8 +949,8 @@ describe('parse features', function() {
}
});
it('correct styles', function() {
var stylesxls = ranges.map(function(r) { return rn2(r)[0]; }).map(function(r) { return wsxls[r].s; });
var stylesxlsx = ranges.map(function(r) { return rn2(r)[0]; }).map(function(r) { return wsxlsx[r].s; });
var stylesxls = ranges.map(function(r) { return rn2(r)[0]; }).map(function(r) { return get_cell(wsxls,r).s; });
var stylesxlsx = ranges.map(function(r) { return rn2(r)[0]; }).map(function(r) { return get_cell(wsxlsx,r).s; });
for(var i = 0; i != exp.length; ++i) {
[
"fgColor.theme","fgColor.raw_rgb",
@ -1023,7 +1028,7 @@ describe('roundtrip features', function() {
var wb1 = X.readFile(f, {cellNF: true, cellDates: di, WTF: opts.WTF});
var _f = X.write(wb1, {type:'binary', cellDates:dj, WTF:opts.WTF});
var wb2 = X.read(_f, {type:'binary', cellDates: dk, WTF: opts.WTF});
var m = [wb1,wb2].map(function(x) { return x.Sheets[sheet][addr]; });
var m = [wb1,wb2].map(function(x) { return get_cell(x.Sheets[sheet], addr); });
assert.equal(m[0].w, m[1].w);
assert.equal(m[0].t, b);
@ -1320,8 +1325,8 @@ describe('csv output', function() {
it('should handle dateNF', function() {
var baseline = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,20140219,0.3\n,,,\nbaz,,qux,\n";
var _ws = X.utils.aoa_to_sheet(data, {cellDates:true});
delete _ws.C3.w;
delete _ws.C3.z;
delete get_cell(_ws,"C3").w;
delete get_cell(_ws,"C3").z;
assert.equal(baseline, X.utils.sheet_to_csv(_ws, {dateNF:"YYYYMMDD"}));
});
it('should handle strip', function() {
@ -1349,8 +1354,8 @@ describe('js -> file -> js', function() {
if(typeof before != 'undefined') before(bef);
else it('before', bef);
function eqcell(wb1, wb2, s, a) {
assert.equal(wb1.Sheets[s][a].v, wb2.Sheets[s][a].v);
assert.equal(wb1.Sheets[s][a].t, wb2.Sheets[s][a].t);
assert.equal(get_cell(wb1.Sheets[s], a).v, get_cell(wb2.Sheets[s], a).v);
assert.equal(get_cell(wb1.Sheets[s], a).t, get_cell(wb2.Sheets[s], a).t);
}
ofmt.forEach(function(f) {
it(f, function() {
@ -1386,9 +1391,9 @@ describe('corner cases', function() {
["baz", null, "q\"ux"]
];
var ws = X.utils.aoa_to_sheet(data);
ws.A1.f = ""; ws.A1.w = "";
delete ws.C3.w; delete ws.C3.z; ws.C3.XF = {ifmt:14};
ws.A4.t = "e";
get_cell(ws,"A1").f = ""; get_cell(ws,"A1").w = "";
delete get_cell(ws,"C3").w; delete get_cell(ws,"C3").z; get_cell(ws,"C3").XF = {ifmt:14};
get_cell(ws,"A4").t = "e";
X.utils.get_formulae(ws);
X.utils.make_csv(ws);
X.utils.make_json(ws);
@ -1400,7 +1405,7 @@ describe('corner cases', function() {
X.write(wb, {type: "base64", bookType: 'xlsb'});
X.write(wb, {type: "binary", bookType: 'ods'});
X.write(wb, {type: "binary", bookType: 'biff2'});
ws.A2.t = "f";
get_cell(ws,"A2").t = "f";
assert.throws(function() { X.utils.make_json(ws); });
});
it('SSF', function() {

File diff suppressed because it is too large Load Diff

756
xlsx.js

File diff suppressed because it is too large Load Diff