From f43cacaf5e776450f9f09abbd00d09d8fd71a8c5 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Sat, 8 Apr 2017 02:55:35 -0400 Subject: [PATCH] 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) --- bin/xlsx.njs | 2 + bits/03_dense.js | 1 + bits/27_csfutils.js | 12 +- bits/30_ctype.js | 10 +- bits/31_rels.js | 8 +- bits/40_harb.js | 19 +- bits/41_lotus.js | 13 +- bits/43_sstbin.js | 2 +- bits/48_stybin.js | 57 +--- bits/54_drawing.js | 3 + bits/56_cmntcommon.js | 11 +- bits/58_cmntbin.js | 18 +- bits/67_wsxml.js | 38 ++- bits/68_wsbin.js | 235 ++++++------- bits/70_csheet.js | 62 +++- bits/73_wbbin.js | 75 ++--- bits/74_xmlbin.js | 6 +- bits/75_xlml.js | 24 +- bits/76_xls.js | 47 +-- bits/78_writebiff.js | 10 +- bits/79_html.js | 29 +- bits/80_parseods.js | 21 +- bits/81_writeods.js | 3 +- bits/85_parsezip.js | 4 +- bits/86_writezip.js | 37 ++- bits/90_utils.js | 13 +- index.html | 5 +- test.js | 207 ++++++------ xlsx.flow.js | 758 ++++++++++++++++++++++++------------------ xlsx.js | 756 +++++++++++++++++++++++------------------ 30 files changed, 1419 insertions(+), 1067 deletions(-) create mode 100644 bits/03_dense.js diff --git a/bin/xlsx.njs b/bin/xlsx.njs index e9553fb..a370994 100755 --- a/bin/xlsx.njs +++ b/bin/xlsx.njs @@ -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; diff --git a/bits/03_dense.js b/bits/03_dense.js new file mode 100644 index 0000000..a8ad9e5 --- /dev/null +++ b/bits/03_dense.js @@ -0,0 +1 @@ +var DENSE = false; diff --git a/bits/27_csfutils.js b/bits/27_csfutils.js index f3ecfa4..90d3cce 100644 --- a/bits/27_csfutils.js +++ b/bits/27_csfutils.js @@ -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); diff --git a/bits/30_ctype.js b/bits/30_ctype.js index e6b72f7..23d6b16 100644 --- a/bits/30_ctype.js +++ b/bits/30_ctype.js @@ -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] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); } diff --git a/bits/31_rels.js b/bits/31_rels.js index aadfe9d..86c7088 100644 --- a/bits/31_rels.js +++ b/bits/31_rels.js @@ -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] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); diff --git a/bits/40_harb.js b/bits/40_harb.js index df5875e..6af2ac4 100644 --- a/bits/40_harb.js +++ b/bits/40_harb.js @@ -261,10 +261,12 @@ var SYLK = (function() { var preamble/*:Array*/ = ["ID;PWXL;N;E"], o/*:Array*/ = []; 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*/ = []; 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*/ = []; 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 ? " " : "")); diff --git a/bits/41_lotus.js b/bits/41_lotus.js index a9349ff..4354911 100644 --- a/bits/41_lotus.js +++ b/bits/41_lotus.js @@ -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); diff --git a/bits/43_sstbin.js b/bits/43_sstbin.js index 6cfa1db..a4787d2 100644 --- a/bits/43_sstbin.js +++ b/bits/43_sstbin.js @@ -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(); } diff --git a/bits/48_stybin.js b/bits/48_stybin.js index edbab07..4da5919 100644 --- a/bits/48_stybin.js +++ b/bits/48_stybin.js @@ -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; diff --git a/bits/54_drawing.js b/bits/54_drawing.js index 20c0d17..9499f90 100644 --- a/bits/54_drawing.js +++ b/bits/54_drawing.js @@ -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; } + diff --git a/bits/56_cmntcommon.js b/bits/56_cmntcommon.js index c0b00a0..4931e96 100644 --- a/bits/56_cmntcommon.js +++ b/bits/56_cmntcommon.js @@ -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; diff --git a/bits/58_cmntbin.js b/bits/58_cmntbin.js index bdda6a4..c170171 100644 --- a/bits/58_cmntbin.js +++ b/bits/58_cmntbin.js @@ -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; diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js index b592d7c..ce9c188 100644 --- a/bits/67_wsxml.js +++ b/bits/67_wsxml.js @@ -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*/, 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*/, 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; } -// if(o.length>2) { o[o.length] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); diff --git a/bits/68_wsbin.js b/bits/68_wsbin.js index 08381fe..f329534 100644 --- a/bits/68_wsbin.js +++ b/bits/68_wsbin.js @@ -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'); diff --git a/bits/70_csheet.js b/bits/70_csheet.js index 49b66eb..6f47c0c 100644 --- a/bits/70_csheet.js +++ b/bits/70_csheet.js @@ -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] = (''); 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(); +} diff --git a/bits/73_wbbin.js b/bits/73_wbbin.js index 7612e0f..ea90a8f 100644 --- a/bits/73_wbbin.js +++ b/bits/73_wbbin.js @@ -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); diff --git a/bits/74_xmlbin.js b/bits/74_xmlbin.js index c5d549b..13de83d 100644 --- a/bits/74_xmlbin.js +++ b/bits/74_xmlbin.js @@ -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); } diff --git a/bits/75_xlml.js b/bits/75_xlml.js index de76639..630535e 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -174,7 +174,8 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { if(str.substr(0,1000).indexOf("= 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(''), 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 = ['']; 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(""); if(row.length > 2) o.push(row.join("")); diff --git a/bits/76_xls.js b/bits/76_xls.js index b100462..4c1bec0 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -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 = []; diff --git a/bits/78_writebiff.js b/bits/78_writebiff.js index 259c41c..3122ff9 100644 --- a/bits/78_writebiff.js +++ b/bits/78_writebiff.js @@ -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 / 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; } diff --git a/bits/80_parseods.js b/bits/80_parseods.js index 5ed8c90..01c2860 100644 --- a/bits/80_parseods.js +++ b/bits/80_parseods.js @@ -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*/ = [], tmp; var tag/*:: = {}*/; var NFtag = {name:""}, NF = "", pidx = 0; var sheetag/*:: = {name:"", '名称':""}*/; var rowtag/*:: = {'行号':""}*/; - var Sheets = {}, SheetNames/*:Array*/ = [], ws = {}; + var Sheets = {}, SheetNames/*:Array*/ = []; + 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 ++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; } diff --git a/bits/81_writeods.js b/bits/81_writeods.js index 2ff89c5..f72ccf3 100644 --- a/bits/81_writeods.js +++ b/bits/81_writeods.js @@ -10,6 +10,7 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() { o.push(' \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(' \n'); for(; R <= range.e.r; ++R) { o.push(' \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)) + '"'; diff --git a/bits/85_parsezip.js b/bits/85_parsezip.js index 27daeb5..7e34d4f 100644 --- a/bits/85_parsezip.js +++ b/bits/85_parsezip.js @@ -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'; diff --git a/bits/86_writezip.js b/bits/86_writezip.js index de9be4d..4ee6547 100644 --- a/bits/86_writezip.js +++ b/bits/86_writezip.js @@ -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; } diff --git a/bits/90_utils.js b/bits/90_utils.js index 6d2f432..bea61fa 100644 --- a/bits/90_utils.js +++ b/bits/90_utils.js @@ -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*/ { 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) { diff --git a/index.html b/index.html index f3f5cdf..ef3cef6 100644 --- a/index.html +++ b/index.html @@ -23,7 +23,7 @@ JS-XLSX Live Demo
Output Format: - @@ -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) { diff --git a/test.js b/test.js index 1d60579..f9cffb4 100644 --- a/test.js +++ b/test.js @@ -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, 'Yegor Kozlov:\r\nfirst cell', "must have the rich text representation"); - assert.equal(ws.B1.c[0].h, 'Yegor Kozlov:
first cell
', "must have the html representation"); + assert.equal(get_cell(ws, "B1").c[0].r, 'Yegor Kozlov:\r\nfirst cell', "must have the rich text representation"); + assert.equal(get_cell(ws, "B1").c[0].h, 'Yegor Kozlov:
first cell
', "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() { diff --git a/xlsx.flow.js b/xlsx.flow.js index 5d444bf..b54280c 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -36,6 +36,7 @@ if(typeof cptable !== 'undefined') { return cptable.utils.decode(current_codepage, [x&255,x>>8])[0]; }; } +var DENSE = false; var Base64 = (function make_b64(){ var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; return { @@ -2092,7 +2093,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) { @@ -2102,7 +2104,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'; @@ -2112,7 +2113,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); @@ -2687,7 +2694,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", @@ -2758,7 +2765,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 = {}; @@ -2821,7 +2828,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'] @@ -2838,11 +2845,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] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); } @@ -2892,11 +2901,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] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); @@ -5021,10 +5028,12 @@ var SYLK = (function() { var preamble/*:Array*/ = ["ID;PWXL;N;E"], o/*:Array*/ = []; 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)); } } @@ -5097,6 +5106,7 @@ var DIF = (function() { return function sheet_to_dif(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ { var o/*:Array*/ = []; 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,""); @@ -5105,7 +5115,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; @@ -5162,8 +5173,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 */ @@ -5183,7 +5196,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; @@ -5216,11 +5230,13 @@ var PRN = (function() { function sheet_to_prn(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ { var o/*:Array*/ = []; 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 ? " " : "")); @@ -5266,8 +5282,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} }; @@ -5284,13 +5300,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) { @@ -5306,7 +5325,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); @@ -5784,7 +5803,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); @@ -5806,6 +5824,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(); } @@ -6503,7 +6522,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) { @@ -6515,7 +6534,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 */ @@ -6523,48 +6542,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; @@ -7001,6 +6995,8 @@ function parse_cc_bin(data, opts) { } function write_cc_bin(data, opts) { } +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 "??"; @@ -7019,6 +7015,7 @@ function parse_drawing(data, rels/*:any*/) { return rels['!id'][id].Target; } + /* L.5.5.2 SpreadsheetML Comments + VML Schema */ var _shapeid = 1024; function write_comments_vml(rId, comments) { @@ -7083,11 +7080,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; @@ -7208,13 +7212,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; @@ -9655,8 +9663,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 @@ -9734,6 +9743,7 @@ function write_ws_xml_protection(sp)/*:string*/ { } function parse_ws_xml_hlinks(s, data/*:Array*/, rels) { + var dense = Array.isArray(s); for(var i = 0; i != data.length; ++i) { var val = parsexmltag(data[i], true); if(!val.ref) return; @@ -9751,8 +9761,14 @@ function parse_ws_xml_hlinks(s, data/*:Array*/, 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; + } } } } @@ -9836,6 +9852,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; @@ -9950,21 +9967,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*/); @@ -9995,6 +10018,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})); @@ -10030,12 +10054,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; } -// if(o.length>2) { o[o.length] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); @@ -10068,7 +10100,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; @@ -10331,18 +10364,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; @@ -10361,14 +10395,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; @@ -10391,7 +10425,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) { @@ -10412,18 +10448,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': @@ -10434,140 +10470,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); @@ -10642,6 +10647,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] */ @@ -10651,9 +10657,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'); @@ -10783,6 +10790,11 @@ function parse_chart(data, name/*:string*/, opts, rels, wb, csheet) { } 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; @@ -10797,37 +10809,70 @@ 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] = (''); 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(); +} /* 18.2.28 (CT_WorkbookProtection) Defaults */ var WBPropsDef = [ ['allowRefreshQuery', '0'], @@ -11190,60 +11235,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); @@ -11413,10 +11459,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); } @@ -11609,7 +11659,8 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { if(str.substr(0,1000).indexOf("= 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(''), didx = 0; var c = 0, r = 0; @@ -11634,7 +11685,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; @@ -11649,7 +11705,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; @@ -11693,7 +11754,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 = []; @@ -12303,6 +12364,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 = ['']; for(var C = range.s.c; C <= range.e.c; ++C) { @@ -12317,8 +12379,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(""); if(row.length > 2) o.push(row.join("")); @@ -12439,7 +12501,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*/); @@ -12498,7 +12561,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 @@ -12532,7 +12600,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); @@ -12630,7 +12697,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){} @@ -12643,7 +12710,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"; @@ -12665,7 +12732,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); @@ -12698,7 +12765,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); @@ -12721,11 +12788,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': { @@ -12735,7 +12803,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': @@ -12785,21 +12854,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 = []; @@ -14448,21 +14519,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 / pair"); @@ -14500,10 +14577,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; } @@ -14511,8 +14594,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; @@ -14531,7 +14616,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; } @@ -14564,13 +14650,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*/ = [], tmp; var tag/*:: = {}*/; var NFtag = {name:""}, NF = "", pidx = 0; var sheetag/*:: = {name:"", '名称':""}*/; var rowtag/*:: = {'行号':""}*/; - var Sheets = {}, SheetNames/*:Array*/ = [], ws = {}; + var Sheets = {}, SheetNames/*:Array*/ = []; + 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/*:: = {}*/; @@ -14598,7 +14686,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; @@ -14609,7 +14697,10 @@ var parse_content_xml = (function() { C = -1; break; case 'covered-table-cell': // 9.1.5 ++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) === '/') { @@ -14679,8 +14770,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; } @@ -14975,6 +15072,7 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() { o.push(' \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(' \n'); for(; R <= range.e.r; ++R) { o.push(' \n'); @@ -14991,7 +15089,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)) + '"'; @@ -15251,14 +15349,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'; @@ -15320,9 +15418,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(); @@ -15362,14 +15463,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) { @@ -15385,7 +15496,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) { @@ -15417,8 +15528,8 @@ 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; } function firstbyte(f/*:RawData*/,o/*:?TypeOpts*/)/*:Array*/ { @@ -15692,9 +15803,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; @@ -15718,7 +15830,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; } @@ -15759,13 +15871,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; @@ -15785,7 +15898,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); @@ -15800,12 +15913,13 @@ function sheet_to_formulae(sheet/*:Worksheet*/)/*:Array*/ { 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) { diff --git a/xlsx.js b/xlsx.js index 1f606ce..4d0b403 100644 --- a/xlsx.js +++ b/xlsx.js @@ -35,6 +35,7 @@ if(typeof cptable !== 'undefined') { return cptable.utils.decode(current_codepage, [x&255,x>>8])[0]; }; } +var DENSE = false; var Base64 = (function make_b64(){ var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; return { @@ -2038,7 +2039,8 @@ function sheet_to_workbook(sheet, opts) { function aoa_to_sheet(data, opts) { var o = opts || {}; - var ws = ({}); + if(DENSE != null) o.dense = DENSE; + var ws = o.dense ? ([]) : ({}); 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) { @@ -2048,7 +2050,6 @@ function aoa_to_sheet(data, opts) { 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})); 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'; @@ -2058,7 +2059,13 @@ function aoa_to_sheet(data, opts) { 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})); + ws[cell_ref] = cell; + } } } if(range.s.c < 10000000) ws['!ref'] = encode_range(range); @@ -2633,7 +2640,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", @@ -2704,7 +2711,7 @@ function parse_ct(data, opts) { workbooks:[], sheets:[], charts:[], dialogs:[], macros:[], rels:[], strs:[], comments:[], coreprops:[], extprops:[], custprops:[], themes:[], styles:[], - calcchains:[], vba: [], + calcchains:[], vba: [], drawings: [], TODO:[], xmlns: "" }); if(!data || !data.match) return ct; var ctext = {}; @@ -2767,7 +2774,7 @@ function write_ct(ct, opts) { } }; 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'] @@ -2784,11 +2791,13 @@ function write_ct(ct, opts) { }; 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] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); } @@ -2838,11 +2847,9 @@ var RELS_ROOT = writextag('Relationships', null, { /* TODO */ function write_rels(rels) { - 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] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); @@ -4965,10 +4972,12 @@ var SYLK = (function() { var preamble = ["ID;PWXL;N;E"], o = []; preamble.push("P;PGeneral"); var r = decode_range(ws['!ref']), 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)); } } @@ -5041,6 +5050,7 @@ var DIF = (function() { return function sheet_to_dif(ws, opts) { var o = []; var r = decode_range(ws['!ref']), 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,""); @@ -5049,7 +5059,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; @@ -5106,8 +5117,10 @@ var PRN = (function() { } function dsv_to_sheet_str(str, opts) { + var o = opts || {}; var sep = ""; - var ws = ({}); + if(DENSE != null) o.dense = DENSE; + var ws = o.dense ? ([]) : ({}); var range = ({s: {c:0, r:0}, e: {c:0, r:0}}); /* known sep */ @@ -5127,7 +5140,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; @@ -5160,11 +5174,13 @@ var PRN = (function() { function sheet_to_prn(ws, opts) { var o = []; var r = decode_range(ws['!ref']), 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 ? " " : "")); @@ -5210,8 +5226,8 @@ var WK_ = (function() { function lotus_to_workbook_buf(d,opts) { 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} }; @@ -5228,13 +5244,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) { @@ -5250,7 +5269,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); @@ -5728,7 +5747,6 @@ function parse_sst_bin(data, opts) { 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); @@ -5750,6 +5768,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(); } @@ -6447,7 +6466,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) { @@ -6459,7 +6478,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 */ @@ -6467,48 +6486,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; @@ -6945,6 +6939,8 @@ function parse_cc_bin(data, opts) { } function write_cc_bin(data, opts) { } +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) { if(!data) return "??"; @@ -6963,6 +6959,7 @@ function parse_drawing(data, rels) { return rels['!id'][id].Target; } + /* L.5.5.2 SpreadsheetML Comments + VML Schema */ var _shapeid = 1024; function write_comments_vml(rId, comments) { @@ -7027,11 +7024,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; @@ -7152,13 +7156,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; @@ -9598,8 +9606,9 @@ var colregex = /<(?:\w*:)?col[^>]*[\/]?>/g; /* 18.3 Worksheets */ function parse_ws_xml(data, opts, rels, wb, themes, styles) { if(!data) return data; + if(DENSE != null) opts.dense = DENSE; /* 18.3.1.99 worksheet CT_Worksheet */ - var s = ({}); + var s = opts.dense ? ([]) : ({}); /* 18.3.1.35 dimension CT_SheetDimension ? */ // $FlowIgnore @@ -9677,6 +9686,7 @@ function write_ws_xml_protection(sp) { } function parse_ws_xml_hlinks(s, data, rels) { + var dense = Array.isArray(s); for(var i = 0; i != data.length; ++i) { var val = parsexmltag(data[i], true); if(!val.ref) return; @@ -9694,8 +9704,14 @@ function parse_ws_xml_hlinks(s, data, 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; + } } } } @@ -9779,6 +9795,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; @@ -9893,21 +9910,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, opts, idx, wb, rels) { 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}); @@ -9938,6 +9961,7 @@ function write_ws_xml(idx, opts, wb, rels) { 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})); @@ -9973,12 +9997,20 @@ function write_ws_xml(idx, opts, wb, rels) { } 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; } -// if(o.length>2) { o[o.length] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); @@ -10011,7 +10043,8 @@ function write_BrtRowHdr(R, 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; @@ -10274,18 +10307,19 @@ function write_BrtColInfo(C, col, o) { } /* [MS-XLSB] 2.1.7.61 Worksheet */ -function parse_ws_bin(data, opts, rels, wb, themes, styles) { +function parse_ws_bin(data, _opts, rels, wb, themes, styles) { 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; @@ -10304,14 +10338,14 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles) { 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; @@ -10334,7 +10368,9 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles) { 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) { @@ -10355,18 +10391,18 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles) { } break; - case 'BrtCellBlank': if(!opts.sheetStubs) break; + case 'BrtCellBlank': + if(!opts.sheetStubs) break; p = ({t:'z',v:undefined}); - 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': @@ -10377,140 +10413,109 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles) { 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); @@ -10585,6 +10590,7 @@ function write_ws_bin_cell(ba, cell, R, C, opts, ws) { function write_CELLTABLE(ba, ws, idx, opts, wb) { 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] */ @@ -10594,9 +10600,10 @@ function write_CELLTABLE(ba, ws, idx, opts, wb) { /* *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'); @@ -10726,6 +10733,11 @@ function parse_chart(data, name, opts, rels, wb, csheet) { } 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, opts, rels, wb, themes, styles) { if(!data) return data; @@ -10740,37 +10752,70 @@ function parse_cs_xml(data, opts, rels, wb, themes, styles) { if(rels['!id'][s['!rel']]) s['!chart'] = rels['!id'][s['!rel']]; return s; } +function write_cs_xml(idx, opts, wb, rels) { + 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] = (''); 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) { 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, opts, wb, 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(); +} /* 18.2.28 (CT_WorkbookProtection) Defaults */ var WBPropsDef = [ ['allowRefreshQuery', '0'], @@ -11133,60 +11178,61 @@ function parse_wb_bin(data, opts) { 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); @@ -11360,6 +11406,10 @@ function write_ws(data, name, opts, wb, rels) { return (name.slice(-4)===".bin" ? write_ws_bin : write_ws_xml)(data, opts, wb, rels); } +function write_cs(data, name, opts, wb, rels) { + return (name.slice(-4)===".bin" ? write_cs_bin : write_cs_xml)(data, opts, wb, rels); +} + function write_sty(data, name, opts) { return (name.slice(-4)===".bin" ? write_sty_bin : write_sty_xml)(data, opts); } @@ -11550,7 +11600,8 @@ function parse_xlml_xml(d, opts) { if(str.substr(0,1000).indexOf("= 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 = ({}), row = {}; var dtag = xlml_parsexmltag(''), didx = 0; var c = 0, r = 0; @@ -11575,7 +11626,12 @@ function parse_xlml_xml(d, opts) { 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; @@ -11589,7 +11645,12 @@ function parse_xlml_xml(d, opts) { else if(cell.MergeAcross || cell.MergeDown) { 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; @@ -11633,7 +11694,7 @@ for(var cma = c; cma <= cc; ++cma) { state.push([Rn[3], false]); tmp = xlml_parsexmltag(Rn[0]); sheetname = unescapexml(tmp.Name); - cursheet = {}; + cursheet = (opts.dense ? [] : {}); mergecells = []; arrayf = []; rowinfo = []; @@ -12242,6 +12303,7 @@ function write_ws_xlml_table(ws, opts, idx, wb) { 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 = ['']; for(var C = range.s.c; C <= range.e.c; ++C) { @@ -12256,8 +12318,8 @@ function write_ws_xlml_table(ws, opts, idx, wb) { } 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(""); if(row.length > 2) o.push(row.join("")); @@ -12378,7 +12440,8 @@ function make_cell(val, ixfe, t) { function parse_workbook(blob, options) { var wb = ({opts:{}}); var Sheets = {}; - var out = {}; + if(DENSE != null) options.dense = DENSE; + var out = (options.dense ? [] : {}); var Directory = {}; var found_sheet = false; var range = ({}); @@ -12437,7 +12500,12 @@ function parse_workbook(blob, options) { } } 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 @@ -12471,7 +12539,6 @@ function parse_workbook(blob, options) { /* 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); @@ -12569,7 +12636,7 @@ function parse_workbook(blob, options) { 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){} @@ -12582,7 +12649,7 @@ function parse_workbook(blob, options) { 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"; @@ -12604,7 +12671,7 @@ function parse_workbook(blob, options) { } 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); @@ -12637,7 +12704,7 @@ function parse_workbook(blob, options) { 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); @@ -12660,11 +12727,12 @@ function parse_workbook(blob, options) { 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': { @@ -12674,7 +12742,8 @@ function parse_workbook(blob, options) { /* 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': @@ -12724,21 +12793,23 @@ function parse_workbook(blob, options) { 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 = []; @@ -14387,21 +14458,25 @@ function write_ws_biff_cell(ba, cell, R, C, opts) { } function write_biff_ws(ba, ws, idx, opts, wb) { + 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, o) { +function write_biff_buf(wb, opts) { + var o = opts || {}; + if(DENSE != null) o.dense = DENSE; var ba = buf_array(); var idx = 0; for(var i=0;i / pair"); @@ -14439,10 +14516,16 @@ function parse_html(str, opts) { 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 = 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 = 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; } @@ -14450,8 +14533,10 @@ function parse_html(str, opts) { return o; } -function parse_dom_table(table, opts) { - var ws = ({}); +function parse_dom_table(table, _opts) { + var opts = _opts || {}; + if(DENSE != null) opts.dense = DENSE; + var ws = opts.dense ? ([]) : ({}); var rows = table.getElementsByTagName('tr'); var range = {s:{r:0,c:0},e:{r:rows.length - 1,c:0}}; var merges = [], midx = 0; @@ -14470,7 +14555,8 @@ function parse_dom_table(table, opts) { 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; } @@ -14503,13 +14589,15 @@ var parse_content_xml = (function() { return function pcx(d, _opts) { var opts = _opts || {}; + if(DENSE != null) opts.dense = DENSE; var str = xlml_normalize(d); var state = [], tmp; var tag; var NFtag = {name:""}, NF = "", pidx = 0; var sheetag; var rowtag; - var Sheets = {}, SheetNames = [], ws = {}; + var Sheets = {}, SheetNames = []; + var ws = opts.dense ? ([]) : ({}); var Rn, q; var ctag = {value:""}; var textp = "", textpidx = 0, textptag; @@ -14537,7 +14625,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 ? ([]) : ({}); merges = []; } break; @@ -14548,7 +14636,10 @@ var parse_content_xml = (function() { C = -1; break; case 'covered-table-cell': // 9.1.5 ++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) === '/') { @@ -14618,8 +14709,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; } @@ -14914,6 +15011,7 @@ var write_content_xml = (function() { o.push(' \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(' \n'); for(; R <= range.e.r; ++R) { o.push(' \n'); @@ -14930,7 +15028,7 @@ var write_content_xml = (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)) + '"'; @@ -15189,14 +15287,14 @@ function parse_zip(zip, opts) { var sheetRels = ({}); 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'; @@ -15258,9 +15356,12 @@ function write_zip(wb, opts) { opts.Strings = []; 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: "" }); fix_write_opts(opts = opts || {}); var zip = new jszip(); var f = "", rId = 0; @@ -15298,14 +15399,24 @@ f = "docProps/app.xml"; 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) { @@ -15321,7 +15432,7 @@ f = "docProps/app.xml"; 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) { @@ -15353,8 +15464,8 @@ f = "docProps/app.xml"; } 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; } function firstbyte(f,o) { @@ -15622,9 +15733,10 @@ function sheet_to_json(sheet, opts){ 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; @@ -15648,7 +15760,7 @@ function sheet_to_json(sheet, opts){ 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; } @@ -15689,13 +15801,14 @@ function sheet_to_csv(sheet, opts) { 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; @@ -15715,7 +15828,7 @@ function sheet_to_csv(sheet, opts) { } return out; } -var make_csv = sheet_to_csv; + function sheet_to_txt(sheet, opts) { if(!opts) opts = {}; opts.FS = "\t"; opts.RS = "\n"; var s = sheet_to_csv(sheet, opts); @@ -15730,12 +15843,13 @@ function sheet_to_formulae(sheet) { 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) {