From d3eaa62d4577d16e78ff32f23df0374d653b199c Mon Sep 17 00:00:00 2001 From: SheetJS Date: Wed, 15 Mar 2017 04:19:02 -0400 Subject: [PATCH] unify stub cells with type `z` - fixes #382 h/t @jugaltheshah @pimpelsang - fixes #333 h/t @xushuheng0623 @abhishek1234321 @mateuszkrzeszowiec - fixes #79 h/t @volodymyrl @elad --- CHANGELOG.md | 1 + README.md | 6 ++++- bits/39_xlsbiff.js | 12 ++++++++- bits/66_wscommon.js | 1 + bits/67_wsxml.js | 8 +++--- bits/68_wsbin.js | 2 +- bits/75_xlml.js | 15 +++++++++-- bits/76_xls.js | 16 +++++++++-- bits/80_parseods.js | 8 +++--- bits/90_utils.js | 4 ++- test.js | 14 +++++++--- xlsx.flow.js | 66 +++++++++++++++++++++++++++++++++++---------- xlsx.js | 65 ++++++++++++++++++++++++++++++++++---------- 13 files changed, 172 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eb66a1..614af48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ changes may not be included if they are not expected to break existing code. ## Unreleased * XLML property names are more closely mapped to the XLSX equivalent +* Stub cells are now cell type `z` ## 0.9.2 (2017-03-13) diff --git a/README.md b/README.md index bbd9a68..b1f30d2 100644 --- a/README.md +++ b/README.md @@ -477,6 +477,10 @@ dates in the local timezone. js-xlsx does not correct for this error. Type `s` is the String type. `v` should be explicitly stored as a string to avoid possible confusion. +Type `z` represents blank stub cells. These do not have any data or type, and +are not processed by any of the core library functions. By default these cells +will not be generated; the parser `cellStubs` option must be set to `true`. + ### Formulae The A1-style formula string is stored in the `f` field. Even though different @@ -549,7 +553,7 @@ The exported `read` and `readFile` functions accept an options argument: | cellNF | false | Save number format string to the .z field | | cellStyles | false | Save style/theme info to the .s field | | cellDates | false | Store dates as type `d` (default is `n`) ** | -| sheetStubs | false | Create cell objects for stub cells | +| sheetStubs | false | Create cell objects of type `z` for stub cells | | sheetRows | 0 | If >0, read the first `sheetRows` rows ** | | bookDeps | false | If true, parse calculation chains | | bookFiles | false | If true, add raw files to book object ** | diff --git a/bits/39_xlsbiff.js b/bits/39_xlsbiff.js index 8338a74..2ded65b 100644 --- a/bits/39_xlsbiff.js +++ b/bits/39_xlsbiff.js @@ -315,6 +315,17 @@ function parse_MulRk(blob, length) { if(rkrecs.length != lastcol - col + 1) throw "MulRK length mismatch"; return {r:rw, c:col, C:lastcol, rkrec:rkrecs}; } +/* 2.4.174 */ +function parse_MulBlank(blob, length) { + var target = blob.l + length - 2; + var rw = blob.read_shift(2), col = blob.read_shift(2); + var ixfes = []; + while(blob.l < target) ixfes.push(blob.read_shift(2)); + if(blob.l !== target) throw "MulBlank read error"; + var lastcol = blob.read_shift(2); + if(ixfes.length != lastcol - col + 1) throw "MulBlank length mismatch"; + return {r:rw, c:col, C:lastcol, ixfe:ixfes}; +} /* 2.5.20 2.5.249 TODO: interpret values here */ function parse_CellStyleXF(blob, length, style) { @@ -711,7 +722,6 @@ var parse_SXLI = parsenoop; var parse_SXPI = parsenoop; var parse_DocRoute = parsenoop; var parse_RecipName = parsenoop; -var parse_MulBlank = parsenoop; var parse_SXDI = parsenoop; var parse_SXDB = parsenoop; var parse_SXFDB = parsenoop; diff --git a/bits/66_wscommon.js b/bits/66_wscommon.js index 6b01b2b..fb642c6 100644 --- a/bits/66_wscommon.js +++ b/bits/66_wscommon.js @@ -23,6 +23,7 @@ function get_cell_style(styles, cell, opts) { } function safe_format(p, fmtid, fillid, opts) { + if(p.t === 'z') return; if(p.t === 'd' && typeof p.v === 'string') p.v = new Date(p.v); try { if(p.t === 'e') p.w = p.w || BErr[p.v]; diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js index feb3d30..6ef817e 100644 --- a/bits/67_wsxml.js +++ b/bits/67_wsxml.js @@ -87,7 +87,7 @@ 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:"stub",v:undefined}; + if(!s[addr]) s[addr] = {t:"z",v:undefined}; s[addr].l = val; } } @@ -126,7 +126,7 @@ function write_ws_xml_cols(ws, cols)/*:string*/ { } function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { - if(cell.v === undefined) return ""; + if(cell.v === undefined || cell.t === 'z') return ""; var vv = ""; var oldt = cell.t, oldv = cell.v; switch(cell.t) { @@ -239,7 +239,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess) { /* SCHEMA IS ACTUALLY INCORRECT HERE. IF A CELL HAS NO T, EMIT "" */ if(tag.t === undefined && p.v === undefined) { if(!opts.sheetStubs) continue; - p.t = "stub"; + p.t = "z"; } else p.t = tag.t || "n"; if(guess.s.c > idx) guess.s.c = idx; @@ -251,7 +251,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess) { sstr = strs[parseInt(p.v, 10)]; if(typeof p.v == 'undefined') { if(!opts.sheetStubs) continue; - p.t = "stub"; + p.t = 'z'; } p.v = sstr.t; p.r = sstr.r; diff --git a/bits/68_wsbin.js b/bits/68_wsbin.js index d99d765..8c7dbda 100644 --- a/bits/68_wsbin.js +++ b/bits/68_wsbin.js @@ -324,7 +324,7 @@ function parse_ws_bin(data, opts, rels, wb)/*:Worksheet*/ { break; case 'BrtCellBlank': if(!opts.sheetStubs) break; - p = ({t:'s',v:undefined}/*:any*/); + p = ({t:'z',v:undefined}/*:any*/); s[encode_col(C=val[0].c) + rr] = p; if(refguess.s.r > row.r) refguess.s.r = row.r; if(refguess.s.c > C) refguess.s.c = C; diff --git a/bits/75_xlml.js b/bits/75_xlml.js index e3139d3..0f04859 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -58,6 +58,7 @@ function xlml_set_custprop(Custprops, Rn, cp, val/*:string*/) { } function safe_format_xlml(cell/*:Cell*/, nf, o) { + if(cell.t === 'z') return; try { if(cell.t === 'e') { cell.w = cell.w || BErr[cell.v]; } else if(nf === "General") { @@ -200,8 +201,17 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { var rr = r + (parseInt(cell.MergeDown,10)|0); mergecells.push({s:{c:c,r:r},e:{c:cc,r:rr}}); } - ++c; - if(cell.MergeAcross) c += +cell.MergeAcross; + if(!opts.sheetStubs) { if(cell.MergeAcross) c = cc + 1; else ++c; } + else if(cell.MergeAcross || cell.MergeDown) { + /*:: 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'}; + } + } + c = cc + 1; + } + else ++c; } else { cell = xlml_parsexmltagobj(Rn[0]); if(cell.Index) c = +cell.Index - 1; @@ -756,6 +766,7 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{ var t = "", p = ""; switch(cell.t) { + case 'z': return ""; case 'n': t = 'Number'; p = String(cell.v); break; case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break; case 'e': t = 'Error'; p = BErr[cell.v]; break; diff --git a/bits/76_xls.js b/bits/76_xls.js index adf8518..370c8ca 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -53,6 +53,7 @@ function slurp(R, blob, length/*:number*/, opts) { } function safe_format_xf(p/*:any*/, opts/*:ParseOpts*/, date1904/*:?boolean*/) { + if(p.t === 'z') return; if(p.t === 'e') { p.w = p.w || BErr[p.v]; } if(!p.XF) return; try { @@ -354,6 +355,19 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:val.c, r:val.r}, temp_val, options); break; + case 'Blank': if(options.sheetStubs) { + temp_val = {ixfe: val.ixfe, XF: XFs[val.ixfe], t:'z'}; + safe_format_xf(temp_val, options, wb.opts.Date1904); + addcell({c:val.c, r:val.r}, temp_val, options); + } break; + case 'MulBlank': if(options.sheetStubs) { + for(var _j = val.c; _j <= val.C; ++_j) { + var _ixfe = val.ixfe[_j-val.c]; + temp_val= {ixfe:_ixfe, XF:XFs[_ixfe], t:'z'}; + safe_format_xf(temp_val, options, wb.opts.Date1904); + addcell({c:_j, r:val.r}, temp_val, options); + } + } break; case 'RString': case 'Label': case 'BIFF2STR': temp_val=make_cell(val.val, val.ixfe, 's'); @@ -420,7 +434,6 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'ColInfo': break; // TODO case 'Row': break; // TODO case 'DBCell': break; // TODO - case 'MulBlank': break; // TODO case 'EntExU2': break; // TODO case 'SxView': break; // TODO case 'Sxvd': break; // TODO @@ -435,7 +448,6 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'Feat': break; case 'FeatHdr': case 'FeatHdr11': break; case 'Feature11': case 'Feature12': case 'List12': break; - case 'Blank': break; case 'Country': country = val; break; case 'RecalcId': break; case 'DefaultRowHeight': case 'DxGCol': break; // TODO: htmlify diff --git a/bits/80_parseods.js b/bits/80_parseods.js index 42b01b3..b3b3c7f 100644 --- a/bits/80_parseods.js +++ b/bits/80_parseods.js @@ -58,8 +58,10 @@ var parse_content_xml = (function() { rowtag = parsexmltag(Rn[0], false); if(rowtag['行号']) R = rowtag['行号'] - 1; else ++R; C = -1; break; - case 'covered-table-cell': // 9.1.5 table:covered-table-cell - ++C; break; /* stub */ + case 'covered-table-cell': // 9.1.5 + ++C; + if(opts.sheetStubs) ws[encode_cell({r:R,c:C})] = {t:'z'}; + break; /* stub */ case 'table-cell': case '数据': if(Rn[0].charAt(Rn[0].length-2) === '/') { ctag = parsexmltag(Rn[0], false); @@ -121,7 +123,7 @@ var parse_content_xml = (function() { isstub = textpidx == 0; } if(textp) q.w = textp; - if(!isstub || opts.cellStubs) { + 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); diff --git a/bits/90_utils.js b/bits/90_utils.js index 56f7c43..b1f3b89 100644 --- a/bits/90_utils.js +++ b/bits/90_utils.js @@ -66,7 +66,7 @@ function safe_format_cell(cell/*:Cell*/, v/*:any*/) { } function format_cell(cell/*:Cell*/, v/*:any*/) { - if(cell == null || cell.t == null) return ""; + if(cell == null || cell.t == null || cell.t == 'z') return ""; if(cell.w !== undefined) return cell.w; if(v === undefined) return safe_format_cell(cell, cell.v); return safe_format_cell(cell, v); @@ -121,6 +121,7 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ if(val === undefined || val.t === undefined) continue; v = val.v; switch(val.t){ + case 'z': continue; case 'e': continue; case 's': break; case 'b': case 'n': break; @@ -187,6 +188,7 @@ function sheet_to_formulae(sheet/*:Worksheet*/)/*:Array*/ { if(y.indexOf(":") == -1) y = y + ":" + y; } if(x.f != null) val = x.f; + else if(x.t == 'z') continue; else if(x.t == 'n' && x.v != null) val = "" + x.v; else if(x.t == 'b') val = x.v ? "TRUE" : "FALSE"; else if(x.w !== undefined) val = "'" + x.w; diff --git a/test.js b/test.js index 677b17b..9dfb1d2 100644 --- a/test.js +++ b/test.js @@ -346,10 +346,18 @@ describe('parse options', function() { }); }); it('should generate sheet stubs when requested', function() { - /* TODO: ODS/XLS/XML */ - [paths.mcxlsx, paths.mcxlsb /*, paths.mcods, paths.mcxls, paths.mcxml*/].forEach(function(p) { + [paths.mcxlsx, paths.mcxlsb, paths.mcods, paths.mcxls, paths.mcxml].forEach(function(p) { var wb = X.readFile(p, {sheetStubs:true}); - assert(typeof wb.Sheets.Merge.A2.t !== 'undefined'); + assert(wb.Sheets.Merge.A2.t == 'z'); + }); + }); + it('should handle stub cells', function() { + [paths.mcxlsx, paths.mcxlsb, paths.mcods, paths.mcxls, paths.mcxml].forEach(function(p) { + var wb = X.readFile(p, {sheetStubs:true}); + X.utils.sheet_to_csv(wb.Sheets.Merge); + X.utils.sheet_to_json(wb.Sheets.Merge); + X.utils.sheet_to_formulae(wb.Sheets.Merge); + ofmt.forEach(function(f) { X.write(wb, {type:"binary", bookType:f}); }); }); }); function checkcells(wb, A46, B26, C16, D2) { diff --git a/xlsx.flow.js b/xlsx.flow.js index 5a1bd36..ec207bb 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -3852,6 +3852,17 @@ function parse_MulRk(blob, length) { if(rkrecs.length != lastcol - col + 1) throw "MulRK length mismatch"; return {r:rw, c:col, C:lastcol, rkrec:rkrecs}; } +/* 2.4.174 */ +function parse_MulBlank(blob, length) { + var target = blob.l + length - 2; + var rw = blob.read_shift(2), col = blob.read_shift(2); + var ixfes = []; + while(blob.l < target) ixfes.push(blob.read_shift(2)); + if(blob.l !== target) throw "MulBlank read error"; + var lastcol = blob.read_shift(2); + if(ixfes.length != lastcol - col + 1) throw "MulBlank length mismatch"; + return {r:rw, c:col, C:lastcol, ixfe:ixfes}; +} /* 2.5.20 2.5.249 TODO: interpret values here */ function parse_CellStyleXF(blob, length, style) { @@ -4248,7 +4259,6 @@ var parse_SXLI = parsenoop; var parse_SXPI = parsenoop; var parse_DocRoute = parsenoop; var parse_RecipName = parsenoop; -var parse_MulBlank = parsenoop; var parse_SXDI = parsenoop; var parse_SXDB = parsenoop; var parse_SXFDB = parsenoop; @@ -8219,6 +8229,7 @@ function get_cell_style(styles, cell, opts) { } function safe_format(p, fmtid, fillid, opts) { + if(p.t === 'z') return; if(p.t === 'd' && typeof p.v === 'string') p.v = new Date(p.v); try { if(p.t === 'e') p.w = p.w || BErr[p.v]; @@ -8340,7 +8351,7 @@ 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:"stub",v:undefined}; + if(!s[addr]) s[addr] = {t:"z",v:undefined}; s[addr].l = val; } } @@ -8379,7 +8390,7 @@ function write_ws_xml_cols(ws, cols)/*:string*/ { } function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { - if(cell.v === undefined) return ""; + if(cell.v === undefined || cell.t === 'z') return ""; var vv = ""; var oldt = cell.t, oldv = cell.v; switch(cell.t) { @@ -8492,7 +8503,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess) { /* SCHEMA IS ACTUALLY INCORRECT HERE. IF A CELL HAS NO T, EMIT "" */ if(tag.t === undefined && p.v === undefined) { if(!opts.sheetStubs) continue; - p.t = "stub"; + p.t = "z"; } else p.t = tag.t || "n"; if(guess.s.c > idx) guess.s.c = idx; @@ -8504,7 +8515,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess) { sstr = strs[parseInt(p.v, 10)]; if(typeof p.v == 'undefined') { if(!opts.sheetStubs) continue; - p.t = "stub"; + p.t = 'z'; } p.v = sstr.t; p.r = sstr.r; @@ -8910,7 +8921,7 @@ function parse_ws_bin(data, opts, rels, wb)/*:Worksheet*/ { break; case 'BrtCellBlank': if(!opts.sheetStubs) break; - p = ({t:'s',v:undefined}/*:any*/); + p = ({t:'z',v:undefined}/*:any*/); s[encode_col(C=val[0].c) + rr] = p; if(refguess.s.r > row.r) refguess.s.r = row.r; if(refguess.s.c > C) refguess.s.c = C; @@ -9767,6 +9778,7 @@ function xlml_set_custprop(Custprops, Rn, cp, val/*:string*/) { } function safe_format_xlml(cell/*:Cell*/, nf, o) { + if(cell.t === 'z') return; try { if(cell.t === 'e') { cell.w = cell.w || BErr[cell.v]; } else if(nf === "General") { @@ -9909,8 +9921,17 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { var rr = r + (parseInt(cell.MergeDown,10)|0); mergecells.push({s:{c:c,r:r},e:{c:cc,r:rr}}); } - ++c; - if(cell.MergeAcross) c += +cell.MergeAcross; + if(!opts.sheetStubs) { if(cell.MergeAcross) c = cc + 1; else ++c; } + else if(cell.MergeAcross || cell.MergeDown) { + /*:: 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'}; + } + } + c = cc + 1; + } + else ++c; } else { cell = xlml_parsexmltagobj(Rn[0]); if(cell.Index) c = +cell.Index - 1; @@ -10465,6 +10486,7 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{ var t = "", p = ""; switch(cell.t) { + case 'z': return ""; case 'n': t = 'Number'; p = String(cell.v); break; case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break; case 'e': t = 'Error'; p = BErr[cell.v]; break; @@ -10574,6 +10596,7 @@ function slurp(R, blob, length/*:number*/, opts) { } function safe_format_xf(p/*:any*/, opts/*:ParseOpts*/, date1904/*:?boolean*/) { + if(p.t === 'z') return; if(p.t === 'e') { p.w = p.w || BErr[p.v]; } if(!p.XF) return; try { @@ -10875,6 +10898,19 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:val.c, r:val.r}, temp_val, options); break; + case 'Blank': if(options.sheetStubs) { + temp_val = {ixfe: val.ixfe, XF: XFs[val.ixfe], t:'z'}; + safe_format_xf(temp_val, options, wb.opts.Date1904); + addcell({c:val.c, r:val.r}, temp_val, options); + } break; + case 'MulBlank': if(options.sheetStubs) { + for(var _j = val.c; _j <= val.C; ++_j) { + var _ixfe = val.ixfe[_j-val.c]; + temp_val= {ixfe:_ixfe, XF:XFs[_ixfe], t:'z'}; + safe_format_xf(temp_val, options, wb.opts.Date1904); + addcell({c:_j, r:val.r}, temp_val, options); + } + } break; case 'RString': case 'Label': case 'BIFF2STR': temp_val=make_cell(val.val, val.ixfe, 's'); @@ -10941,7 +10977,6 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'ColInfo': break; // TODO case 'Row': break; // TODO case 'DBCell': break; // TODO - case 'MulBlank': break; // TODO case 'EntExU2': break; // TODO case 'SxView': break; // TODO case 'Sxvd': break; // TODO @@ -10956,7 +10991,6 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'Feat': break; case 'FeatHdr': case 'FeatHdr11': break; case 'Feature11': case 'Feature12': case 'List12': break; - case 'Blank': break; case 'Country': country = val; break; case 'RecalcId': break; case 'DefaultRowHeight': case 'DxGCol': break; // TODO: htmlify @@ -12673,8 +12707,10 @@ var parse_content_xml = (function() { rowtag = parsexmltag(Rn[0], false); if(rowtag['行号']) R = rowtag['行号'] - 1; else ++R; C = -1; break; - case 'covered-table-cell': // 9.1.5 table:covered-table-cell - ++C; break; /* stub */ + case 'covered-table-cell': // 9.1.5 + ++C; + if(opts.sheetStubs) ws[encode_cell({r:R,c:C})] = {t:'z'}; + break; /* stub */ case 'table-cell': case '数据': if(Rn[0].charAt(Rn[0].length-2) === '/') { ctag = parsexmltag(Rn[0], false); @@ -12736,7 +12772,7 @@ var parse_content_xml = (function() { isstub = textpidx == 0; } if(textp) q.w = textp; - if(!isstub || opts.cellStubs) { + 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); @@ -13571,7 +13607,7 @@ function safe_format_cell(cell/*:Cell*/, v/*:any*/) { } function format_cell(cell/*:Cell*/, v/*:any*/) { - if(cell == null || cell.t == null) return ""; + if(cell == null || cell.t == null || cell.t == 'z') return ""; if(cell.w !== undefined) return cell.w; if(v === undefined) return safe_format_cell(cell, cell.v); return safe_format_cell(cell, v); @@ -13626,6 +13662,7 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ if(val === undefined || val.t === undefined) continue; v = val.v; switch(val.t){ + case 'z': continue; case 'e': continue; case 's': break; case 'b': case 'n': break; @@ -13692,6 +13729,7 @@ function sheet_to_formulae(sheet/*:Worksheet*/)/*:Array*/ { if(y.indexOf(":") == -1) y = y + ":" + y; } if(x.f != null) val = x.f; + else if(x.t == 'z') continue; else if(x.t == 'n' && x.v != null) val = "" + x.v; else if(x.t == 'b') val = x.v ? "TRUE" : "FALSE"; else if(x.w !== undefined) val = "'" + x.w; diff --git a/xlsx.js b/xlsx.js index b82edba..659dbfd 100644 --- a/xlsx.js +++ b/xlsx.js @@ -3800,6 +3800,17 @@ function parse_MulRk(blob, length) { if(rkrecs.length != lastcol - col + 1) throw "MulRK length mismatch"; return {r:rw, c:col, C:lastcol, rkrec:rkrecs}; } +/* 2.4.174 */ +function parse_MulBlank(blob, length) { + var target = blob.l + length - 2; + var rw = blob.read_shift(2), col = blob.read_shift(2); + var ixfes = []; + while(blob.l < target) ixfes.push(blob.read_shift(2)); + if(blob.l !== target) throw "MulBlank read error"; + var lastcol = blob.read_shift(2); + if(ixfes.length != lastcol - col + 1) throw "MulBlank length mismatch"; + return {r:rw, c:col, C:lastcol, ixfe:ixfes}; +} /* 2.5.20 2.5.249 TODO: interpret values here */ function parse_CellStyleXF(blob, length, style) { @@ -4196,7 +4207,6 @@ var parse_SXLI = parsenoop; var parse_SXPI = parsenoop; var parse_DocRoute = parsenoop; var parse_RecipName = parsenoop; -var parse_MulBlank = parsenoop; var parse_SXDI = parsenoop; var parse_SXDB = parsenoop; var parse_SXFDB = parsenoop; @@ -8166,6 +8176,7 @@ function get_cell_style(styles, cell, opts) { } function safe_format(p, fmtid, fillid, opts) { + if(p.t === 'z') return; if(p.t === 'd' && typeof p.v === 'string') p.v = new Date(p.v); try { if(p.t === 'e') p.w = p.w || BErr[p.v]; @@ -8287,7 +8298,7 @@ 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:"stub",v:undefined}; + if(!s[addr]) s[addr] = {t:"z",v:undefined}; s[addr].l = val; } } @@ -8326,7 +8337,7 @@ function write_ws_xml_cols(ws, cols) { } function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { - if(cell.v === undefined) return ""; + if(cell.v === undefined || cell.t === 'z') return ""; var vv = ""; var oldt = cell.t, oldv = cell.v; switch(cell.t) { @@ -8439,7 +8450,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess) { /* SCHEMA IS ACTUALLY INCORRECT HERE. IF A CELL HAS NO T, EMIT "" */ if(tag.t === undefined && p.v === undefined) { if(!opts.sheetStubs) continue; - p.t = "stub"; + p.t = "z"; } else p.t = tag.t || "n"; if(guess.s.c > idx) guess.s.c = idx; @@ -8451,7 +8462,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess) { sstr = strs[parseInt(p.v, 10)]; if(typeof p.v == 'undefined') { if(!opts.sheetStubs) continue; - p.t = "stub"; + p.t = 'z'; } p.v = sstr.t; p.r = sstr.r; @@ -8857,7 +8868,7 @@ function parse_ws_bin(data, opts, rels, wb) { break; case 'BrtCellBlank': if(!opts.sheetStubs) break; - p = ({t:'s',v:undefined}); + p = ({t:'z',v:undefined}); s[encode_col(C=val[0].c) + rr] = p; if(refguess.s.r > row.r) refguess.s.r = row.r; if(refguess.s.c > C) refguess.s.c = C; @@ -9712,6 +9723,7 @@ function xlml_set_custprop(Custprops, Rn, cp, val) { } function safe_format_xlml(cell, nf, o) { + if(cell.t === 'z') return; try { if(cell.t === 'e') { cell.w = cell.w || BErr[cell.v]; } else if(nf === "General") { @@ -9854,8 +9866,16 @@ function parse_xlml_xml(d, opts) { var rr = r + (parseInt(cell.MergeDown,10)|0); mergecells.push({s:{c:c,r:r},e:{c:cc,r:rr}}); } - ++c; - if(cell.MergeAcross) c += +cell.MergeAcross; + if(!opts.sheetStubs) { if(cell.MergeAcross) c = cc + 1; else ++c; } + 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'}; + } + } + c = cc + 1; + } + else ++c; } else { cell = xlml_parsexmltagobj(Rn[0]); if(cell.Index) c = +cell.Index - 1; @@ -10409,6 +10429,7 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr){ var t = "", p = ""; switch(cell.t) { + case 'z': return ""; case 'n': t = 'Number'; p = String(cell.v); break; case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break; case 'e': t = 'Error'; p = BErr[cell.v]; break; @@ -10518,6 +10539,7 @@ function slurp(R, blob, length, opts) { } function safe_format_xf(p, opts, date1904) { + if(p.t === 'z') return; if(p.t === 'e') { p.w = p.w || BErr[p.v]; } if(!p.XF) return; try { @@ -10819,6 +10841,19 @@ function parse_workbook(blob, options) { safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:val.c, r:val.r}, temp_val, options); break; + case 'Blank': if(options.sheetStubs) { + temp_val = {ixfe: val.ixfe, XF: XFs[val.ixfe], t:'z'}; + safe_format_xf(temp_val, options, wb.opts.Date1904); + addcell({c:val.c, r:val.r}, temp_val, options); + } break; + case 'MulBlank': if(options.sheetStubs) { + for(var _j = val.c; _j <= val.C; ++_j) { + var _ixfe = val.ixfe[_j-val.c]; + temp_val= {ixfe:_ixfe, XF:XFs[_ixfe], t:'z'}; + safe_format_xf(temp_val, options, wb.opts.Date1904); + addcell({c:_j, r:val.r}, temp_val, options); + } + } break; case 'RString': case 'Label': case 'BIFF2STR': temp_val=make_cell(val.val, val.ixfe, 's'); @@ -10885,7 +10920,6 @@ function parse_workbook(blob, options) { case 'ColInfo': break; // TODO case 'Row': break; // TODO case 'DBCell': break; // TODO - case 'MulBlank': break; // TODO case 'EntExU2': break; // TODO case 'SxView': break; // TODO case 'Sxvd': break; // TODO @@ -10900,7 +10934,6 @@ function parse_workbook(blob, options) { case 'Feat': break; case 'FeatHdr': case 'FeatHdr11': break; case 'Feature11': case 'Feature12': case 'List12': break; - case 'Blank': break; case 'Country': country = val; break; case 'RecalcId': break; case 'DefaultRowHeight': case 'DxGCol': break; // TODO: htmlify @@ -12617,8 +12650,10 @@ var parse_content_xml = (function() { rowtag = parsexmltag(Rn[0], false); if(rowtag['行号']) R = rowtag['行号'] - 1; else ++R; C = -1; break; - case 'covered-table-cell': // 9.1.5 table:covered-table-cell - ++C; break; /* stub */ + case 'covered-table-cell': // 9.1.5 + ++C; + if(opts.sheetStubs) ws[encode_cell({r:R,c:C})] = {t:'z'}; + break; /* stub */ case 'table-cell': case '数据': if(Rn[0].charAt(Rn[0].length-2) === '/') { ctag = parsexmltag(Rn[0], false); @@ -12680,7 +12715,7 @@ var parse_content_xml = (function() { isstub = textpidx == 0; } if(textp) q.w = textp; - if(!isstub || opts.cellStubs) { + 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); @@ -13507,7 +13542,7 @@ function safe_format_cell(cell, v) { } function format_cell(cell, v) { - if(cell == null || cell.t == null) return ""; + if(cell == null || cell.t == null || cell.t == 'z') return ""; if(cell.w !== undefined) return cell.w; if(v === undefined) return safe_format_cell(cell, cell.v); return safe_format_cell(cell, v); @@ -13562,6 +13597,7 @@ function sheet_to_json(sheet, opts){ if(val === undefined || val.t === undefined) continue; v = val.v; switch(val.t){ + case 'z': continue; case 'e': continue; case 's': break; case 'b': case 'n': break; @@ -13628,6 +13664,7 @@ function sheet_to_formulae(sheet) { if(y.indexOf(":") == -1) y = y + ":" + y; } if(x.f != null) val = x.f; + else if(x.t == 'z') continue; else if(x.t == 'n' && x.v != null) val = "" + x.v; else if(x.t == 'b') val = x.v ? "TRUE" : "FALSE"; else if(x.w !== undefined) val = "'" + x.w;