From 4880e9219f308215c4f671111d9920d9d4f8481f Mon Sep 17 00:00:00 2001 From: SheetJS Date: Fri, 10 Mar 2017 03:39:51 -0500 Subject: [PATCH] more formula parsing logic - added more function argc counts and cleaned up error rendering - disabled XLS error on MTRSettings (fixes #466 h/t @dskrvk) - handle more unexpected XML empty tags (fixes #510 h/t @rahulsindc) - throw error if SheetNames is not unique (fixes #231 h/t @zippy1981) --- Makefile | 2 +- bits/23_binutils.js | 2 +- bits/25_cellutils.js | 21 +++-- bits/47_styxml.js | 4 +- bits/62_fxls.js | 79 +++++++++++-------- bits/64_ftab.js | 48 +++++++++++- bits/71_wbcommon.js | 7 ++ bits/72_wbxml.js | 8 +- bits/75_xlml.js | 3 +- bits/76_xls.js | 4 +- bits/88_write.js | 1 + test.js | 9 ++- tests.lst | 8 +- xlsx.flow.js | 177 +++++++++++++++++++++++++++++++------------ xlsx.js | 177 +++++++++++++++++++++++++++++++------------ 15 files changed, 403 insertions(+), 147 deletions(-) diff --git a/Makefile b/Makefile index 4e50086..36094a3 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ aux: $(AUXTARGETS) nexe: xlsx.exe xlsx.exe: bin/xlsx.js xlsx.js - nexe -i bin/xlsx.njs -o xlsx.exe + nexe -i $< -o $@ --flags ## Testing diff --git a/bits/23_binutils.js b/bits/23_binutils.js index 3f84bcc..00ad8a2 100644 --- a/bits/23_binutils.js +++ b/bits/23_binutils.js @@ -85,7 +85,7 @@ function ReadShift(size, t) { case 'wstr': if(typeof cptable !== 'undefined') o = cptable.utils.decode(current_codepage, this.slice(this.l, this.l+2*size)); else return ReadShift.call(this, size, 'dbcs'); - o = size = 2 * size; break; + size = 2 * size; break; /* [MS-OLEDS] 2.1.4 LengthPrefixedAnsiString */ case 'lpstr': o = __lpstr(this, this.l); size = 5 + o.length; break; diff --git a/bits/25_cellutils.js b/bits/25_cellutils.js index b308244..c439b76 100644 --- a/bits/25_cellutils.js +++ b/bits/25_cellutils.js @@ -15,10 +15,11 @@ function shift_cell_xls(cell, tgt, opts) { return out; } -function shift_range_xls(cell, range) { - cell.s = shift_cell_xls(cell.s, range.s); - cell.e = shift_cell_xls(cell.e, range.s); - return cell; +function shift_range_xls(cell, range, opts) { + var out = dup(cell); + out.s = shift_cell_xls(out.s, range.s, opts); + out.e = shift_cell_xls(out.e, range.s, opts); + return out; } function encode_cell_xls(c)/*:string*/ { @@ -28,6 +29,16 @@ function encode_cell_xls(c)/*:string*/ { return s; } -function encode_range_xls(r)/*:string*/ { +function encode_range_xls(r, opts)/*:string*/ { + if(r.s.r == 0 && !r.s.rRel) { + if(r.e.r == opts.biff >= 12 ? 0xFFFFF : 0xFFFF && !r.e.rRel) { + return (r.s.cRel ? "" : "$") + encode_col(r.s.c) + ":" + (r.e.cRel ? "" : "$") + encode_col(r.e.c); + } + } + if(r.s.c == 0 && !r.s.cRel) { + if(r.e.c == opts.biff >= 12 ? 0xFFFF : 0xFF && !r.e.cRel) { + return (r.s.rRel ? "" : "$") + encode_row(r.s.r) + ":" + (r.e.rRel ? "" : "$") + encode_row(r.e.r); + } + } return encode_cell_xls(r.s) + ":" + encode_cell_xls(r.e); } diff --git a/bits/47_styxml.js b/bits/47_styxml.js index 31eea7e..6967935 100644 --- a/bits/47_styxml.js +++ b/bits/47_styxml.js @@ -13,6 +13,7 @@ function parse_fills(t, opts) { /* 18.8.32 patternFill CT_PatternFill */ case '': if(y.patternType) fill.patternType = y.patternType; break; case '': case '': break; @@ -58,6 +59,7 @@ function parse_numFmts(t, opts) { var f=unescapexml(utf8read(y.formatCode)), j=parseInt(y.numFmtId,10); styles.NumberFmt[j] = f; if(j>0) SSF.load(f,j); } break; + case '': break; default: if(opts.WTF) throw new Error('unrecognized ' + y[0] + ' in numFmts'); } } @@ -90,7 +92,7 @@ function parse_cellXfs(t, opts) { case '': break; /* 18.8.1 alignment CT_CellAlignment */ - case '': break; + case '': case '': break; /* 18.8.33 protection CT_CellProtection */ case '': case '': break; diff --git a/bits/62_fxls.js b/bits/62_fxls.js index 32ec63c..4402adb 100644 --- a/bits/62_fxls.js +++ b/bits/62_fxls.js @@ -33,8 +33,8 @@ function parse_RgceArea_BIFF2(blob, length, opts) { } /* 2.5.198.105 TODO */ -function parse_RgceAreaRel(blob, length) { - var r=blob.read_shift(2), R=blob.read_shift(2); +function parse_RgceAreaRel(blob, length, opts) { + var r=blob.read_shift(length == 12 ? 4 : 2), R=blob.read_shift(length == 12 ? 4 : 2); var c=parse_ColRelU(blob, 2); var C=parse_ColRelU(blob, 2); return { s:{r:r, c:c[0], cRel:c[1], rRel:c[2]}, e:{r:R, c:C[0], cRel:C[1], rRel:C[2]} }; @@ -100,9 +100,9 @@ function parse_PtgArea3d(blob, length, opts) { } /* 2.5.198.29 */ -function parse_PtgAreaErr(blob, length) { +function parse_PtgAreaErr(blob, length, opts) { var type = (blob[blob.l++] & 0x60) >> 5; - blob.l += 8; + blob.l += opts && opts.biff > 8 ? 12 : 8; return [type]; } /* 2.5.198.30 */ @@ -119,9 +119,9 @@ function parse_PtgAreaErr3d(blob, length, opts) { } /* 2.5.198.31 */ -function parse_PtgAreaN(blob, length) { +function parse_PtgAreaN(blob, length, opts) { var type = (blob[blob.l++] & 0x60) >> 5; - var area = parse_RgceAreaRel(blob, 8); + var area = parse_RgceAreaRel(blob, opts && opts.biff > 8 ? 12 : 8, opts); return [type, area]; } @@ -386,6 +386,19 @@ function parse_PtgRefErr(blob, length, opts) { return [type]; } +/* 2.5.198.87 */ +function parse_PtgRefErr3d(blob, length, opts) { + var type = (blob[blob.l++] & 0x60) >> 5; + var ixti = blob.read_shift(2); + var w = 4; + if(opts) switch(opts.biff) { + case 5: throw new Error("PtgRefErr3d -- 5"); // TODO: find test case + case 12: w = 6; break; + } + blob.l += w; + return [type, ixti]; +} + /* 2.5.198.26 */ var parse_PtgAdd = parseread1; /* 2.5.198.45 */ @@ -429,8 +442,6 @@ var parse_PtgUplus = parseread1; var parse_PtgMemErr = parsenoop; /* 2.5.198.73 */ var parse_PtgMemNoMem = parsenoop; -/* 2.5.198.87 */ -var parse_PtgRefErr3d = parsenoop; /* 2.5.198.92 */ var parse_PtgTbl = parsenoop; @@ -676,6 +687,7 @@ var PtgBinOp = { PtgSub: "-" }; function stringify_formula(formula, range, cell, supbooks, opts) { + //console.log(formula); var _range = /*range != null ? range :*/ {s:{c:0, r:0},e:{c:0, r:0}}; var stack = [], e1, e2, type, c, ixti, nameidx, r, sname=""; if(!formula[0] || !formula[0][0]) return ""; @@ -685,7 +697,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { var f = formula[0][ff]; //console.log("++",f, stack) switch(f[0]) { - /* 2.2.2.1 Unary Operator Tokens */ /* 2.5.198.93 */ case 'PtgUminus': stack.push("-" + stack.pop()); break; /* 2.5.198.95 */ @@ -693,7 +704,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { /* 2.5.198.81 */ case 'PtgPercent': stack.push(stack.pop() + "%"); break; - /* 2.2.2.1 Binary Value Operator Token */ case 'PtgAdd': /* 2.5.198.26 */ case 'PtgConcat': /* 2.5.198.43 */ case 'PtgDiv': /* 2.5.198.45 */ @@ -713,7 +723,7 @@ function stringify_formula(formula, range, cell, supbooks, opts) { case 1: sp = fill("\r", formula[0][last_sp][1][1]); break; default: sp = ""; - if(opts.WTF) throw new Error("Unexpected PtgSpace type " + formula[0][last_sp][1][0]); + if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + formula[0][last_sp][1][0]); } e2 = e2 + sp; last_sp = -1; @@ -721,7 +731,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(e2+PtgBinOp[f[0]]+e1); break; - /* 2.2.2.1 Binary Reference Operator Token */ /* 2.5.198.67 */ case 'PtgIsect': e1 = stack.pop(); e2 = stack.pop(); @@ -736,7 +745,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(e2+":"+e1); break; - /* 2.2.2.3 Control Tokens "can be ignored" */ /* 2.5.198.34 */ case 'PtgAttrChoose': break; /* 2.5.198.35 */ @@ -763,11 +771,11 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(sname + "!" + encode_cell(c)); break; - /* Function Call */ /* 2.5.198.62 */ case 'PtgFunc': /* 2.5.198.63 */ case 'PtgFuncVar': + //console.log(f[1]); /* f[1] = [argc, func, type] */ var argc = f[1][0], func = f[1][1]; if(!argc) argc = 0; @@ -787,10 +795,15 @@ function stringify_formula(formula, range, cell, supbooks, opts) { case 'PtgStr': stack.push('"' + f[1] + '"'); break; /* 2.5.198.57 */ case 'PtgErr': stack.push(f[1]); break; + /* 2.5.198.31 TODO */ + case 'PtgAreaN': + type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); + stack.push(encode_range_xls(r, opts)); + break; /* 2.5.198.27 TODO: fixed points */ case 'PtgArea': - type = f[1][0]; r = shift_range_xls(f[1][1], _range); - stack.push(encode_range_xls(r)); + type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); + stack.push(encode_range_xls(r, opts)); break; /* 2.5.198.28 */ case 'PtgArea3d': // TODO: lots of stuff @@ -803,7 +816,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push("SUM(" + stack.pop() + ")"); break; - /* Expression Prefixes */ /* 2.5.198.37 */ case 'PtgAttrSemi': break; @@ -834,7 +846,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(externbook.body); break; - /* 2.2.2.4 Display Tokens */ /* 2.5.198.80 */ case 'PtgParen': var lp = '(', rp = ')'; @@ -843,10 +854,10 @@ function stringify_formula(formula, range, cell, supbooks, opts) { switch(formula[0][last_sp][1][0]) { case 2: lp = fill(" ", formula[0][last_sp][1][1]) + lp; break; case 3: lp = fill("\r", formula[0][last_sp][1][1]) + lp; break; - case 4: rp = fill(" ", formula[0][last_sp][1][1]) + lp; break; - case 5: rp = fill("\r", formula[0][last_sp][1][1]) + lp; break; + case 4: rp = fill(" ", formula[0][last_sp][1][1]) + rp; break; + case 5: rp = fill("\r", formula[0][last_sp][1][1]) + rp; break; default: - if(opts.WTF) throw new Error("Unexpected PtgSpace type " + formula[0][last_sp][1][0]); + if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + formula[0][last_sp][1][0]); } last_sp = -1; } @@ -855,6 +866,9 @@ function stringify_formula(formula, range, cell, supbooks, opts) { /* 2.5.198.86 */ case 'PtgRefErr': stack.push('#REF!'); break; + /* 2.5.198.87 */ + case 'PtgRefErr3d': stack.push('#REF!'); break; + /* */ /* 2.5.198.58 TODO */ case 'PtgExp': @@ -884,7 +898,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push("{" + stringify_array(f[1]) + "}"); break; - /* 2.2.2.5 Mem Tokens */ /* 2.5.198.70 TODO: confirm this is a non-display */ case 'PtgMemArea': //stack.push("(" + f[2].map(encode_range).join(",") + ")"); @@ -908,31 +921,31 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(""); break; - /* 2.5.198.29 TODO */ - case 'PtgAreaErr': break; - - /* 2.5.198.31 TODO */ - case 'PtgAreaN': stack.push(""); break; - - /* 2.5.198.87 TODO */ - case 'PtgRefErr3d': break; + /* 2.5.198.29 */ + case 'PtgAreaErr': stack.push("#REF!"); /* 2.5.198.72 TODO */ case 'PtgMemFunc': break; - default: throw 'Unrecognized Formula Token: ' + f; + default: throw new Error('Unrecognized Formula Token: ' + f); } var PtgNonDisp = ['PtgAttrSpace', 'PtgAttrSpaceSemi', 'PtgAttrGoto']; if(last_sp >= 0 && PtgNonDisp.indexOf(formula[0][ff][0]) == -1) { f = formula[0][last_sp]; + var _left = true; switch(f[1][0]) { + /* note: some bad XLSB files omit the PtgParen */ + case 4: _left = false; + /* falls through */ case 0: sp = fill(" ", f[1][1]); break; + case 5: _left = false; + /* falls through */ case 1: sp = fill("\r", f[1][1]); break; default: sp = ""; - if(opts.WTF) throw new Error("Unexpected PtgSpace type " + f[1][0]); + if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + f[1][0]); } - stack.push(sp + stack.pop()); + stack.push((_left ? sp : "") + stack.pop() + (_left ? "" : sp)); last_sp = -1; } //console.log("::",f, stack) diff --git a/bits/64_ftab.js b/bits/64_ftab.js index 36beab5..502cf7d 100644 --- a/bits/64_ftab.js +++ b/bits/64_ftab.js @@ -870,7 +870,8 @@ var Ftab = { /*::[*/0x01D1/*::]*/: 'WEEKNUM', /*::[*/0x01D2/*::]*/: 'AMORDEGRC', /*::[*/0x01D3/*::]*/: 'AMORLINC', - /*::[*/0x01D4/*::]*/: 'SHEETJS', + /*::[*/0x01D4/*::]*/: 'CONVERT', + /*::[*/0x02D4/*::]*/: 'SHEETJS', /*::[*/0x01D5/*::]*/: 'ACCRINT', /*::[*/0x01D6/*::]*/: 'ACCRINTM', /*::[*/0x01D7/*::]*/: 'WORKDAY', @@ -1079,9 +1080,54 @@ var FtabArgc = { /*::[*/0x0178/*::]*/: 1, /* ROUNDBAHTDOWN */ /*::[*/0x0179/*::]*/: 1, /* ROUNDBAHTUP */ /*::[*/0x017A/*::]*/: 1, /* THAIYEAR */ + /*::[*/0x017E/*::]*/: 3, /* CUBEMEMBERPROPERTY */ + /*::[*/0x0181/*::]*/: 1, /* HEX2DEC */ + /*::[*/0x0188/*::]*/: 1, /* OCT2DEC */ + /*::[*/0x0189/*::]*/: 1, /* BIN2DEC */ + /*::[*/0x018C/*::]*/: 2, /* IMSUB */ + /*::[*/0x018D/*::]*/: 2, /* IMDIV */ + /*::[*/0x018E/*::]*/: 2, /* IMPOWER */ + /*::[*/0x018F/*::]*/: 1, /* IMABS */ + /*::[*/0x0190/*::]*/: 1, /* IMSQRT */ + /*::[*/0x0191/*::]*/: 1, /* IMLN */ + /*::[*/0x0192/*::]*/: 1, /* IMLOG2 */ + /*::[*/0x0193/*::]*/: 1, /* IMLOG10 */ + /*::[*/0x0194/*::]*/: 1, /* IMSIN */ + /*::[*/0x0195/*::]*/: 1, /* IMCOS */ + /*::[*/0x0196/*::]*/: 1, /* IMEXP */ + /*::[*/0x0197/*::]*/: 1, /* IMARGUMENT */ + /*::[*/0x0198/*::]*/: 1, /* IMCONJUGATE */ + /*::[*/0x0199/*::]*/: 1, /* IMAGINARY */ + /*::[*/0x019A/*::]*/: 1, /* IMREAL */ + /*::[*/0x019E/*::]*/: 4, /* SERIESSUM */ + /*::[*/0x019F/*::]*/: 1, /* FACTDOUBLE */ /*::[*/0x01A0/*::]*/: 1, /* SQRTPI */ + /*::[*/0x01A1/*::]*/: 2, /* QUOTIENT */ + /*::[*/0x01A4/*::]*/: 1, /* ISEVEN */ + /*::[*/0x01A5/*::]*/: 1, /* ISODD */ + /*::[*/0x01A6/*::]*/: 2, /* MROUND */ + /*::[*/0x01A8/*::]*/: 1, /* ERFC */ + /*::[*/0x01A9/*::]*/: 2, /* BESSELJ */ + /*::[*/0x01AA/*::]*/: 2, /* BESSELK */ + /*::[*/0x01AB/*::]*/: 2, /* BESSELY */ + /*::[*/0x01AC/*::]*/: 2, /* BESSELI */ + /*::[*/0x01AE/*::]*/: 3, /* XNPV */ + /*::[*/0x01B6/*::]*/: 3, /* TBILLEQ */ + /*::[*/0x01B7/*::]*/: 3, /* TBILLPRICE */ + /*::[*/0x01B8/*::]*/: 3, /* TBILLYIELD */ + /*::[*/0x01BB/*::]*/: 2, /* DOLLARDE */ + /*::[*/0x01BC/*::]*/: 2, /* DOLLARFR */ + /*::[*/0x01BD/*::]*/: 2, /* NOMINAL */ + /*::[*/0x01BE/*::]*/: 2, /* EFFECT */ + /*::[*/0x01BF/*::]*/: 6, /* CUMPRINC */ + /*::[*/0x01C0/*::]*/: 6, /* CUMIPMT */ /*::[*/0x01C1/*::]*/: 2, /* EDATE */ /*::[*/0x01C2/*::]*/: 2, /* EOMONTH */ + /*::[*/0x01D0/*::]*/: 2, /* RANDBETWEEN */ + /*::[*/0x01D4/*::]*/: 3, /* CONVERT */ + /*::[*/0x01DC/*::]*/: 2, /* FVSCHEDULE */ + /*::[*/0x01DF/*::]*/: 1, /* CUBESETCOUNT */ + /*::[*/0x01E0/*::]*/: 2, /* IFERROR */ /*::[*/0xFFFF/*::]*/: 0 }; /* [MS-XLSX] 2.2.3 Functions */ diff --git a/bits/71_wbcommon.js b/bits/71_wbcommon.js index a70ddc0..2ffee7d 100644 --- a/bits/71_wbcommon.js +++ b/bits/71_wbcommon.js @@ -98,3 +98,10 @@ function parse_wb_defaults(wb) { _ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904, 'date1904'); } + +/* TODO: validate workbook */ +function check_wb(wb) { + if(!wb || !wb.SheetNames || !wb.Sheets) throw new Error("Invalid Workbook"); + for(var i = 0; i < wb.SheetNames.length; ++i) for(var j = 0; j < i; ++j) + if(wb.SheetNames[i] == wb.SheetNames[j]) throw new Error("Duplicate Sheet Name: " + wb.SheetNames[i]); +} diff --git a/bits/72_wbxml.js b/bits/72_wbxml.js index 32c12d7..e2cc436 100644 --- a/bits/72_wbxml.js +++ b/bits/72_wbxml.js @@ -18,7 +18,7 @@ function parse_wb_xml(data, opts) { /* 18.2.13 fileVersion CT_FileVersion ? */ case '': break; + case '': case '': break; /* 18.2.12 fileSharing CT_FileSharing ? */ case '': break; @@ -26,6 +26,7 @@ function parse_wb_xml(data, opts) { /* 18.2.28 workbookPr CT_WorkbookPr ? */ case '': delete y[0]; wb.WBProps = y; break; + case '': break; /* 18.2.29 workbookProtection CT_WorkbookProtection ? */ case '': case '': break; /* 18.2.30 workbookView CT_BookView + */ case '': break; /* 18.2.20 sheets CT_Sheets 1 */ case '': case '': break; // aggregate sheet /* 18.2.19 sheet CT_Sheet + */ case '': break; /* 18.2.15 functionGroups CT_FunctionGroups ? */ case '': break; @@ -61,6 +64,7 @@ function parse_wb_xml(data, opts) { /* 18.2.2 calcPr CT_CalcPr ? */ case '': delete y[0]; wb.CalcPr = y; break; + case '': break; /* 18.2.16 oleSize CT_OleSize ? (ref required) */ case '': pass=false; break; - default: if(!pass && opts.WTF) throw 'unrecognized ' + y[0] + ' in workbook'; + default: if(!pass && opts.WTF) throw new Error('unrecognized ' + y[0] + ' in workbook'); } }); if(XMLNS.main.indexOf(wb.xmlns) === -1) throw new Error("Unknown Namespace: " + wb.xmlns); diff --git a/bits/75_xlml.js b/bits/75_xlml.js index 99e4747..af43de7 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -158,7 +158,7 @@ function xlml_normalize(d)/*:string*/ { /* TODO: Everything */ /* UOS uses CJK in tags */ -var xlmlregex = /<(\/?)([^\s?>\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; +var xlmlregex = /<(\/?)([^\s?>!\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; //var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg; function parse_xlml_xml(d, opts)/*:Workbook*/ { var str = debom(xlml_normalize(d)); @@ -178,6 +178,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { var cstys = [], csty; var arrayf = []; xlmlregex.lastIndex = 0; + str = str.replace(//mg,""); while((Rn = xlmlregex.exec(str))) switch(Rn[3]) { case 'Data': if(state[state.length-1][1]) break; diff --git a/bits/76_xls.js b/bits/76_xls.js index c245f4c..55b4e28 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -204,9 +204,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'RefreshAll': wb.opts.RefreshAll = val; break; case 'BookBool': break; // TODO case 'UsesELFs': /* if(val) console.error("Unsupported ELFs"); */ break; - case 'MTRSettings': { - if(val[0] && val[1]) throw "Unsupported threads: " + val; - } break; // TODO: actually support threads + case 'MTRSettings': break; case 'CalcCount': wb.opts.CalcCount = val; break; case 'CalcDelta': wb.opts.CalcDelta = val; break; case 'CalcIter': wb.opts.CalcIter = val; break; diff --git a/bits/88_write.js b/bits/88_write.js index dbf526d..8abcbf2 100644 --- a/bits/88_write.js +++ b/bits/88_write.js @@ -43,6 +43,7 @@ function write_binary_type(out, opts/*:WriteOpts*/) { } function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) { + check_wb(wb); var o = opts||{}; switch(o.bookType || 'xlsx') { case 'xml': return write_string_type(write_xlml(wb, o), o); diff --git a/test.js b/test.js index e36edd1..27dfd16 100644 --- a/test.js +++ b/test.js @@ -13,7 +13,7 @@ if(process.env.WTF) { } var fullex = [".xlsb", ".xlsm", ".xlsx"]; var ofmt = ["xlsb", "xlsm", "xlsx", "ods", "biff2"]; -var ex = fullex.slice(); ex.push(".ods"); ex.push(".xls"); ex.push("xml"); +var ex = fullex.slice(); ex = ex.concat([".ods", ".xls", ".xml", ".fods"]); if(process.env.FMTS === "full") process.env.FMTS = ex.join(":"); if(process.env.FMTS) ex=process.env.FMTS.split(":").map(function(x){return x[0]==="."?x:"."+x;}); var exp = ex.map(function(x){ return x + ".pending"; }); @@ -952,6 +952,13 @@ describe('invalid files', function() { }); }); }); + it('should fail if SheetNames has duplicate entries', function() { + var wb = X.readFile(paths.fstxlsx); + wb.SheetNames.push(wb.SheetNames[0]); + assert.throws(function() { + X.write(wb, {type:'binary'}); + }); + }); }); }); diff --git a/tests.lst b/tests.lst index a096830..f12ad1b 100644 --- a/tests.lst +++ b/tests.lst @@ -517,7 +517,7 @@ roo_comments.ods roo_datetime.ods roo_dreimalvier.ods roo_emptysheets.ods -roo_encrypted-letmein.ods +# roo_encrypted-letmein.ods roo_formula.ods roo_hidden_sheets.ods roo_html-escape.ods @@ -1161,9 +1161,9 @@ formulae_test_simple.xml hyperlink_stress_test_2011.xml interview.xlsx.xml issue.xlsx.xml -large_strings.xls.xml -large_strings.xlsb.xml -large_strings.xlsx.xml +large_strings.xls.xml.pending +large_strings.xlsb.xml.pending +large_strings.xlsx.xml.pending merge_cells.xls.xml merge_cells.xlsb.xml merge_cells.xlsx.xml diff --git a/xlsx.flow.js b/xlsx.flow.js index a6eab77..ca03b88 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -1717,7 +1717,7 @@ function ReadShift(size, t) { case 'wstr': if(typeof cptable !== 'undefined') o = cptable.utils.decode(current_codepage, this.slice(this.l, this.l+2*size)); else return ReadShift.call(this, size, 'dbcs'); - o = size = 2 * size; break; + size = 2 * size; break; /* [MS-OLEDS] 2.1.4 LengthPrefixedAnsiString */ case 'lpstr': o = __lpstr(this, this.l); size = 5 + o.length; break; @@ -1904,10 +1904,11 @@ function shift_cell_xls(cell, tgt, opts) { return out; } -function shift_range_xls(cell, range) { - cell.s = shift_cell_xls(cell.s, range.s); - cell.e = shift_cell_xls(cell.e, range.s); - return cell; +function shift_range_xls(cell, range, opts) { + var out = dup(cell); + out.s = shift_cell_xls(out.s, range.s, opts); + out.e = shift_cell_xls(out.e, range.s, opts); + return out; } function encode_cell_xls(c)/*:string*/ { @@ -1917,7 +1918,17 @@ function encode_cell_xls(c)/*:string*/ { return s; } -function encode_range_xls(r)/*:string*/ { +function encode_range_xls(r, opts)/*:string*/ { + if(r.s.r == 0 && !r.s.rRel) { + if(r.e.r == opts.biff >= 12 ? 0xFFFFF : 0xFFFF && !r.e.rRel) { + return (r.s.cRel ? "" : "$") + encode_col(r.s.c) + ":" + (r.e.cRel ? "" : "$") + encode_col(r.e.c); + } + } + if(r.s.c == 0 && !r.s.cRel) { + if(r.e.c == opts.biff >= 12 ? 0xFFFF : 0xFF && !r.e.cRel) { + return (r.s.rRel ? "" : "$") + encode_row(r.s.r) + ":" + (r.e.rRel ? "" : "$") + encode_row(r.e.r); + } + } return encode_cell_xls(r.s) + ":" + encode_cell_xls(r.e); } var OFFCRYPTO = {}; @@ -4950,6 +4961,7 @@ function parse_fills(t, opts) { /* 18.8.32 patternFill CT_PatternFill */ case '': if(y.patternType) fill.patternType = y.patternType; break; case '': case '': break; @@ -4995,6 +5007,7 @@ function parse_numFmts(t, opts) { var f=unescapexml(utf8read(y.formatCode)), j=parseInt(y.numFmtId,10); styles.NumberFmt[j] = f; if(j>0) SSF.load(f,j); } break; + case '': break; default: if(opts.WTF) throw new Error('unrecognized ' + y[0] + ' in numFmts'); } } @@ -5027,7 +5040,7 @@ function parse_cellXfs(t, opts) { case '': break; /* 18.8.1 alignment CT_CellAlignment */ - case '': break; + case '': case '': break; /* 18.8.33 protection CT_CellProtection */ case '': case '': break; @@ -5832,8 +5845,8 @@ function parse_RgceArea_BIFF2(blob, length, opts) { } /* 2.5.198.105 TODO */ -function parse_RgceAreaRel(blob, length) { - var r=blob.read_shift(2), R=blob.read_shift(2); +function parse_RgceAreaRel(blob, length, opts) { + var r=blob.read_shift(length == 12 ? 4 : 2), R=blob.read_shift(length == 12 ? 4 : 2); var c=parse_ColRelU(blob, 2); var C=parse_ColRelU(blob, 2); return { s:{r:r, c:c[0], cRel:c[1], rRel:c[2]}, e:{r:R, c:C[0], cRel:C[1], rRel:C[2]} }; @@ -5899,9 +5912,9 @@ function parse_PtgArea3d(blob, length, opts) { } /* 2.5.198.29 */ -function parse_PtgAreaErr(blob, length) { +function parse_PtgAreaErr(blob, length, opts) { var type = (blob[blob.l++] & 0x60) >> 5; - blob.l += 8; + blob.l += opts && opts.biff > 8 ? 12 : 8; return [type]; } /* 2.5.198.30 */ @@ -5918,9 +5931,9 @@ function parse_PtgAreaErr3d(blob, length, opts) { } /* 2.5.198.31 */ -function parse_PtgAreaN(blob, length) { +function parse_PtgAreaN(blob, length, opts) { var type = (blob[blob.l++] & 0x60) >> 5; - var area = parse_RgceAreaRel(blob, 8); + var area = parse_RgceAreaRel(blob, opts && opts.biff > 8 ? 12 : 8, opts); return [type, area]; } @@ -6185,6 +6198,19 @@ function parse_PtgRefErr(blob, length, opts) { return [type]; } +/* 2.5.198.87 */ +function parse_PtgRefErr3d(blob, length, opts) { + var type = (blob[blob.l++] & 0x60) >> 5; + var ixti = blob.read_shift(2); + var w = 4; + if(opts) switch(opts.biff) { + case 5: throw new Error("PtgRefErr3d -- 5"); // TODO: find test case + case 12: w = 6; break; + } + blob.l += w; + return [type, ixti]; +} + /* 2.5.198.26 */ var parse_PtgAdd = parseread1; /* 2.5.198.45 */ @@ -6228,8 +6254,6 @@ var parse_PtgUplus = parseread1; var parse_PtgMemErr = parsenoop; /* 2.5.198.73 */ var parse_PtgMemNoMem = parsenoop; -/* 2.5.198.87 */ -var parse_PtgRefErr3d = parsenoop; /* 2.5.198.92 */ var parse_PtgTbl = parsenoop; @@ -6475,6 +6499,7 @@ var PtgBinOp = { PtgSub: "-" }; function stringify_formula(formula, range, cell, supbooks, opts) { + //console.log(formula); var _range = /*range != null ? range :*/ {s:{c:0, r:0},e:{c:0, r:0}}; var stack = [], e1, e2, type, c, ixti, nameidx, r, sname=""; if(!formula[0] || !formula[0][0]) return ""; @@ -6484,7 +6509,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { var f = formula[0][ff]; //console.log("++",f, stack) switch(f[0]) { - /* 2.2.2.1 Unary Operator Tokens */ /* 2.5.198.93 */ case 'PtgUminus': stack.push("-" + stack.pop()); break; /* 2.5.198.95 */ @@ -6492,7 +6516,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { /* 2.5.198.81 */ case 'PtgPercent': stack.push(stack.pop() + "%"); break; - /* 2.2.2.1 Binary Value Operator Token */ case 'PtgAdd': /* 2.5.198.26 */ case 'PtgConcat': /* 2.5.198.43 */ case 'PtgDiv': /* 2.5.198.45 */ @@ -6512,7 +6535,7 @@ function stringify_formula(formula, range, cell, supbooks, opts) { case 1: sp = fill("\r", formula[0][last_sp][1][1]); break; default: sp = ""; - if(opts.WTF) throw new Error("Unexpected PtgSpace type " + formula[0][last_sp][1][0]); + if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + formula[0][last_sp][1][0]); } e2 = e2 + sp; last_sp = -1; @@ -6520,7 +6543,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(e2+PtgBinOp[f[0]]+e1); break; - /* 2.2.2.1 Binary Reference Operator Token */ /* 2.5.198.67 */ case 'PtgIsect': e1 = stack.pop(); e2 = stack.pop(); @@ -6535,7 +6557,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(e2+":"+e1); break; - /* 2.2.2.3 Control Tokens "can be ignored" */ /* 2.5.198.34 */ case 'PtgAttrChoose': break; /* 2.5.198.35 */ @@ -6562,11 +6583,11 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(sname + "!" + encode_cell(c)); break; - /* Function Call */ /* 2.5.198.62 */ case 'PtgFunc': /* 2.5.198.63 */ case 'PtgFuncVar': + //console.log(f[1]); /* f[1] = [argc, func, type] */ var argc = f[1][0], func = f[1][1]; if(!argc) argc = 0; @@ -6586,10 +6607,15 @@ function stringify_formula(formula, range, cell, supbooks, opts) { case 'PtgStr': stack.push('"' + f[1] + '"'); break; /* 2.5.198.57 */ case 'PtgErr': stack.push(f[1]); break; + /* 2.5.198.31 TODO */ + case 'PtgAreaN': + type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); + stack.push(encode_range_xls(r, opts)); + break; /* 2.5.198.27 TODO: fixed points */ case 'PtgArea': - type = f[1][0]; r = shift_range_xls(f[1][1], _range); - stack.push(encode_range_xls(r)); + type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); + stack.push(encode_range_xls(r, opts)); break; /* 2.5.198.28 */ case 'PtgArea3d': // TODO: lots of stuff @@ -6602,7 +6628,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push("SUM(" + stack.pop() + ")"); break; - /* Expression Prefixes */ /* 2.5.198.37 */ case 'PtgAttrSemi': break; @@ -6633,7 +6658,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(externbook.body); break; - /* 2.2.2.4 Display Tokens */ /* 2.5.198.80 */ case 'PtgParen': var lp = '(', rp = ')'; @@ -6642,10 +6666,10 @@ function stringify_formula(formula, range, cell, supbooks, opts) { switch(formula[0][last_sp][1][0]) { case 2: lp = fill(" ", formula[0][last_sp][1][1]) + lp; break; case 3: lp = fill("\r", formula[0][last_sp][1][1]) + lp; break; - case 4: rp = fill(" ", formula[0][last_sp][1][1]) + lp; break; - case 5: rp = fill("\r", formula[0][last_sp][1][1]) + lp; break; + case 4: rp = fill(" ", formula[0][last_sp][1][1]) + rp; break; + case 5: rp = fill("\r", formula[0][last_sp][1][1]) + rp; break; default: - if(opts.WTF) throw new Error("Unexpected PtgSpace type " + formula[0][last_sp][1][0]); + if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + formula[0][last_sp][1][0]); } last_sp = -1; } @@ -6654,6 +6678,9 @@ function stringify_formula(formula, range, cell, supbooks, opts) { /* 2.5.198.86 */ case 'PtgRefErr': stack.push('#REF!'); break; + /* 2.5.198.87 */ + case 'PtgRefErr3d': stack.push('#REF!'); break; + /* */ /* 2.5.198.58 TODO */ case 'PtgExp': @@ -6683,7 +6710,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push("{" + stringify_array(f[1]) + "}"); break; - /* 2.2.2.5 Mem Tokens */ /* 2.5.198.70 TODO: confirm this is a non-display */ case 'PtgMemArea': //stack.push("(" + f[2].map(encode_range).join(",") + ")"); @@ -6707,31 +6733,31 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(""); break; - /* 2.5.198.29 TODO */ - case 'PtgAreaErr': break; - - /* 2.5.198.31 TODO */ - case 'PtgAreaN': stack.push(""); break; - - /* 2.5.198.87 TODO */ - case 'PtgRefErr3d': break; + /* 2.5.198.29 */ + case 'PtgAreaErr': stack.push("#REF!"); /* 2.5.198.72 TODO */ case 'PtgMemFunc': break; - default: throw 'Unrecognized Formula Token: ' + f; + default: throw new Error('Unrecognized Formula Token: ' + f); } var PtgNonDisp = ['PtgAttrSpace', 'PtgAttrSpaceSemi', 'PtgAttrGoto']; if(last_sp >= 0 && PtgNonDisp.indexOf(formula[0][ff][0]) == -1) { f = formula[0][last_sp]; + var _left = true; switch(f[1][0]) { + /* note: some bad XLSB files omit the PtgParen */ + case 4: _left = false; + /* falls through */ case 0: sp = fill(" ", f[1][1]); break; + case 5: _left = false; + /* falls through */ case 1: sp = fill("\r", f[1][1]); break; default: sp = ""; - if(opts.WTF) throw new Error("Unexpected PtgSpace type " + f[1][0]); + if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + f[1][0]); } - stack.push(sp + stack.pop()); + stack.push((_left ? sp : "") + stack.pop() + (_left ? "" : sp)); last_sp = -1; } //console.log("::",f, stack) @@ -7631,7 +7657,8 @@ var Ftab = { /*::[*/0x01D1/*::]*/: 'WEEKNUM', /*::[*/0x01D2/*::]*/: 'AMORDEGRC', /*::[*/0x01D3/*::]*/: 'AMORLINC', - /*::[*/0x01D4/*::]*/: 'SHEETJS', + /*::[*/0x01D4/*::]*/: 'CONVERT', + /*::[*/0x02D4/*::]*/: 'SHEETJS', /*::[*/0x01D5/*::]*/: 'ACCRINT', /*::[*/0x01D6/*::]*/: 'ACCRINTM', /*::[*/0x01D7/*::]*/: 'WORKDAY', @@ -7840,9 +7867,54 @@ var FtabArgc = { /*::[*/0x0178/*::]*/: 1, /* ROUNDBAHTDOWN */ /*::[*/0x0179/*::]*/: 1, /* ROUNDBAHTUP */ /*::[*/0x017A/*::]*/: 1, /* THAIYEAR */ + /*::[*/0x017E/*::]*/: 3, /* CUBEMEMBERPROPERTY */ + /*::[*/0x0181/*::]*/: 1, /* HEX2DEC */ + /*::[*/0x0188/*::]*/: 1, /* OCT2DEC */ + /*::[*/0x0189/*::]*/: 1, /* BIN2DEC */ + /*::[*/0x018C/*::]*/: 2, /* IMSUB */ + /*::[*/0x018D/*::]*/: 2, /* IMDIV */ + /*::[*/0x018E/*::]*/: 2, /* IMPOWER */ + /*::[*/0x018F/*::]*/: 1, /* IMABS */ + /*::[*/0x0190/*::]*/: 1, /* IMSQRT */ + /*::[*/0x0191/*::]*/: 1, /* IMLN */ + /*::[*/0x0192/*::]*/: 1, /* IMLOG2 */ + /*::[*/0x0193/*::]*/: 1, /* IMLOG10 */ + /*::[*/0x0194/*::]*/: 1, /* IMSIN */ + /*::[*/0x0195/*::]*/: 1, /* IMCOS */ + /*::[*/0x0196/*::]*/: 1, /* IMEXP */ + /*::[*/0x0197/*::]*/: 1, /* IMARGUMENT */ + /*::[*/0x0198/*::]*/: 1, /* IMCONJUGATE */ + /*::[*/0x0199/*::]*/: 1, /* IMAGINARY */ + /*::[*/0x019A/*::]*/: 1, /* IMREAL */ + /*::[*/0x019E/*::]*/: 4, /* SERIESSUM */ + /*::[*/0x019F/*::]*/: 1, /* FACTDOUBLE */ /*::[*/0x01A0/*::]*/: 1, /* SQRTPI */ + /*::[*/0x01A1/*::]*/: 2, /* QUOTIENT */ + /*::[*/0x01A4/*::]*/: 1, /* ISEVEN */ + /*::[*/0x01A5/*::]*/: 1, /* ISODD */ + /*::[*/0x01A6/*::]*/: 2, /* MROUND */ + /*::[*/0x01A8/*::]*/: 1, /* ERFC */ + /*::[*/0x01A9/*::]*/: 2, /* BESSELJ */ + /*::[*/0x01AA/*::]*/: 2, /* BESSELK */ + /*::[*/0x01AB/*::]*/: 2, /* BESSELY */ + /*::[*/0x01AC/*::]*/: 2, /* BESSELI */ + /*::[*/0x01AE/*::]*/: 3, /* XNPV */ + /*::[*/0x01B6/*::]*/: 3, /* TBILLEQ */ + /*::[*/0x01B7/*::]*/: 3, /* TBILLPRICE */ + /*::[*/0x01B8/*::]*/: 3, /* TBILLYIELD */ + /*::[*/0x01BB/*::]*/: 2, /* DOLLARDE */ + /*::[*/0x01BC/*::]*/: 2, /* DOLLARFR */ + /*::[*/0x01BD/*::]*/: 2, /* NOMINAL */ + /*::[*/0x01BE/*::]*/: 2, /* EFFECT */ + /*::[*/0x01BF/*::]*/: 6, /* CUMPRINC */ + /*::[*/0x01C0/*::]*/: 6, /* CUMIPMT */ /*::[*/0x01C1/*::]*/: 2, /* EDATE */ /*::[*/0x01C2/*::]*/: 2, /* EOMONTH */ + /*::[*/0x01D0/*::]*/: 2, /* RANDBETWEEN */ + /*::[*/0x01D4/*::]*/: 3, /* CONVERT */ + /*::[*/0x01DC/*::]*/: 2, /* FVSCHEDULE */ + /*::[*/0x01DF/*::]*/: 1, /* CUBESETCOUNT */ + /*::[*/0x01E0/*::]*/: 2, /* IFERROR */ /*::[*/0xFFFF/*::]*/: 0 }; /* [MS-XLSX] 2.2.3 Functions */ @@ -9067,6 +9139,13 @@ function parse_wb_defaults(wb) { _ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904, 'date1904'); } + +/* TODO: validate workbook */ +function check_wb(wb) { + if(!wb || !wb.SheetNames || !wb.Sheets) throw new Error("Invalid Workbook"); + for(var i = 0; i < wb.SheetNames.length; ++i) for(var j = 0; j < i; ++j) + if(wb.SheetNames[i] == wb.SheetNames[j]) throw new Error("Duplicate Sheet Name: " + wb.SheetNames[i]); +} /* 18.2 Workbook */ var wbnsregex = /<\w+:workbook/; function parse_wb_xml(data, opts) { @@ -9087,7 +9166,7 @@ function parse_wb_xml(data, opts) { /* 18.2.13 fileVersion CT_FileVersion ? */ case '': break; + case '': case '': break; /* 18.2.12 fileSharing CT_FileSharing ? */ case '': break; @@ -9095,6 +9174,7 @@ function parse_wb_xml(data, opts) { /* 18.2.28 workbookPr CT_WorkbookPr ? */ case '': delete y[0]; wb.WBProps = y; break; + case '': break; /* 18.2.29 workbookProtection CT_WorkbookProtection ? */ case '': case '': break; /* 18.2.30 workbookView CT_BookView + */ case '': break; /* 18.2.20 sheets CT_Sheets 1 */ case '': case '': break; // aggregate sheet /* 18.2.19 sheet CT_Sheet + */ case '': break; /* 18.2.15 functionGroups CT_FunctionGroups ? */ case '': break; @@ -9130,6 +9212,7 @@ function parse_wb_xml(data, opts) { /* 18.2.2 calcPr CT_CalcPr ? */ case '': delete y[0]; wb.CalcPr = y; break; + case '': break; /* 18.2.16 oleSize CT_OleSize ? (ref required) */ case '': pass=false; break; - default: if(!pass && opts.WTF) throw 'unrecognized ' + y[0] + ' in workbook'; + default: if(!pass && opts.WTF) throw new Error('unrecognized ' + y[0] + ' in workbook'); } }); if(XMLNS.main.indexOf(wb.xmlns) === -1) throw new Error("Unknown Namespace: " + wb.xmlns); @@ -9637,7 +9720,7 @@ function xlml_normalize(d)/*:string*/ { /* TODO: Everything */ /* UOS uses CJK in tags */ -var xlmlregex = /<(\/?)([^\s?>\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; +var xlmlregex = /<(\/?)([^\s?>!\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; //var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg; function parse_xlml_xml(d, opts)/*:Workbook*/ { var str = debom(xlml_normalize(d)); @@ -9657,6 +9740,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { var cstys = [], csty; var arrayf = []; xlmlregex.lastIndex = 0; + str = str.replace(//mg,""); while((Rn = xlmlregex.exec(str))) switch(Rn[3]) { case 'Data': if(state[state.length-1][1]) break; @@ -10413,9 +10497,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'RefreshAll': wb.opts.RefreshAll = val; break; case 'BookBool': break; // TODO case 'UsesELFs': /* if(val) console.error("Unsupported ELFs"); */ break; - case 'MTRSettings': { - if(val[0] && val[1]) throw "Unsupported threads: " + val; - } break; // TODO: actually support threads + case 'MTRSettings': break; case 'CalcCount': wb.opts.CalcCount = val; break; case 'CalcDelta': wb.opts.CalcDelta = val; break; case 'CalcIter': wb.opts.CalcIter = val; break; @@ -13153,6 +13235,7 @@ function write_binary_type(out, opts/*:WriteOpts*/) { } function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) { + check_wb(wb); var o = opts||{}; switch(o.bookType || 'xlsx') { case 'xml': return write_string_type(write_xlml(wb, o), o); diff --git a/xlsx.js b/xlsx.js index ef24385..3900205 100644 --- a/xlsx.js +++ b/xlsx.js @@ -1675,7 +1675,7 @@ function ReadShift(size, t) { case 'wstr': if(typeof cptable !== 'undefined') o = cptable.utils.decode(current_codepage, this.slice(this.l, this.l+2*size)); else return ReadShift.call(this, size, 'dbcs'); - o = size = 2 * size; break; + size = 2 * size; break; /* [MS-OLEDS] 2.1.4 LengthPrefixedAnsiString */ case 'lpstr': o = __lpstr(this, this.l); size = 5 + o.length; break; @@ -1862,10 +1862,11 @@ function shift_cell_xls(cell, tgt, opts) { return out; } -function shift_range_xls(cell, range) { - cell.s = shift_cell_xls(cell.s, range.s); - cell.e = shift_cell_xls(cell.e, range.s); - return cell; +function shift_range_xls(cell, range, opts) { + var out = dup(cell); + out.s = shift_cell_xls(out.s, range.s, opts); + out.e = shift_cell_xls(out.e, range.s, opts); + return out; } function encode_cell_xls(c) { @@ -1875,7 +1876,17 @@ function encode_cell_xls(c) { return s; } -function encode_range_xls(r) { +function encode_range_xls(r, opts) { + if(r.s.r == 0 && !r.s.rRel) { + if(r.e.r == opts.biff >= 12 ? 0xFFFFF : 0xFFFF && !r.e.rRel) { + return (r.s.cRel ? "" : "$") + encode_col(r.s.c) + ":" + (r.e.cRel ? "" : "$") + encode_col(r.e.c); + } + } + if(r.s.c == 0 && !r.s.cRel) { + if(r.e.c == opts.biff >= 12 ? 0xFFFF : 0xFF && !r.e.cRel) { + return (r.s.rRel ? "" : "$") + encode_row(r.s.r) + ":" + (r.e.rRel ? "" : "$") + encode_row(r.e.r); + } + } return encode_cell_xls(r.s) + ":" + encode_cell_xls(r.e); } var OFFCRYPTO = {}; @@ -4908,6 +4919,7 @@ function parse_fills(t, opts) { /* 18.8.32 patternFill CT_PatternFill */ case '': if(y.patternType) fill.patternType = y.patternType; break; case '': case '': break; @@ -4953,6 +4965,7 @@ function parse_numFmts(t, opts) { var f=unescapexml(utf8read(y.formatCode)), j=parseInt(y.numFmtId,10); styles.NumberFmt[j] = f; if(j>0) SSF.load(f,j); } break; + case '': break; default: if(opts.WTF) throw new Error('unrecognized ' + y[0] + ' in numFmts'); } } @@ -4985,7 +4998,7 @@ function parse_cellXfs(t, opts) { case '': break; /* 18.8.1 alignment CT_CellAlignment */ - case '': break; + case '': case '': break; /* 18.8.33 protection CT_CellProtection */ case '': case '': break; @@ -5790,8 +5803,8 @@ function parse_RgceArea_BIFF2(blob, length, opts) { } /* 2.5.198.105 TODO */ -function parse_RgceAreaRel(blob, length) { - var r=blob.read_shift(2), R=blob.read_shift(2); +function parse_RgceAreaRel(blob, length, opts) { + var r=blob.read_shift(length == 12 ? 4 : 2), R=blob.read_shift(length == 12 ? 4 : 2); var c=parse_ColRelU(blob, 2); var C=parse_ColRelU(blob, 2); return { s:{r:r, c:c[0], cRel:c[1], rRel:c[2]}, e:{r:R, c:C[0], cRel:C[1], rRel:C[2]} }; @@ -5857,9 +5870,9 @@ function parse_PtgArea3d(blob, length, opts) { } /* 2.5.198.29 */ -function parse_PtgAreaErr(blob, length) { +function parse_PtgAreaErr(blob, length, opts) { var type = (blob[blob.l++] & 0x60) >> 5; - blob.l += 8; + blob.l += opts && opts.biff > 8 ? 12 : 8; return [type]; } /* 2.5.198.30 */ @@ -5876,9 +5889,9 @@ function parse_PtgAreaErr3d(blob, length, opts) { } /* 2.5.198.31 */ -function parse_PtgAreaN(blob, length) { +function parse_PtgAreaN(blob, length, opts) { var type = (blob[blob.l++] & 0x60) >> 5; - var area = parse_RgceAreaRel(blob, 8); + var area = parse_RgceAreaRel(blob, opts && opts.biff > 8 ? 12 : 8, opts); return [type, area]; } @@ -6143,6 +6156,19 @@ function parse_PtgRefErr(blob, length, opts) { return [type]; } +/* 2.5.198.87 */ +function parse_PtgRefErr3d(blob, length, opts) { + var type = (blob[blob.l++] & 0x60) >> 5; + var ixti = blob.read_shift(2); + var w = 4; + if(opts) switch(opts.biff) { + case 5: throw new Error("PtgRefErr3d -- 5"); // TODO: find test case + case 12: w = 6; break; + } + blob.l += w; + return [type, ixti]; +} + /* 2.5.198.26 */ var parse_PtgAdd = parseread1; /* 2.5.198.45 */ @@ -6186,8 +6212,6 @@ var parse_PtgUplus = parseread1; var parse_PtgMemErr = parsenoop; /* 2.5.198.73 */ var parse_PtgMemNoMem = parsenoop; -/* 2.5.198.87 */ -var parse_PtgRefErr3d = parsenoop; /* 2.5.198.92 */ var parse_PtgTbl = parsenoop; @@ -6433,6 +6457,7 @@ var PtgBinOp = { PtgSub: "-" }; function stringify_formula(formula, range, cell, supbooks, opts) { + //console.log(formula); var _range = /*range != null ? range :*/ {s:{c:0, r:0},e:{c:0, r:0}}; var stack = [], e1, e2, type, c, ixti, nameidx, r, sname=""; if(!formula[0] || !formula[0][0]) return ""; @@ -6442,7 +6467,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { var f = formula[0][ff]; //console.log("++",f, stack) switch(f[0]) { - /* 2.2.2.1 Unary Operator Tokens */ /* 2.5.198.93 */ case 'PtgUminus': stack.push("-" + stack.pop()); break; /* 2.5.198.95 */ @@ -6450,7 +6474,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { /* 2.5.198.81 */ case 'PtgPercent': stack.push(stack.pop() + "%"); break; - /* 2.2.2.1 Binary Value Operator Token */ case 'PtgAdd': /* 2.5.198.26 */ case 'PtgConcat': /* 2.5.198.43 */ case 'PtgDiv': /* 2.5.198.45 */ @@ -6470,7 +6493,7 @@ function stringify_formula(formula, range, cell, supbooks, opts) { case 1: sp = fill("\r", formula[0][last_sp][1][1]); break; default: sp = ""; - if(opts.WTF) throw new Error("Unexpected PtgSpace type " + formula[0][last_sp][1][0]); + if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + formula[0][last_sp][1][0]); } e2 = e2 + sp; last_sp = -1; @@ -6478,7 +6501,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(e2+PtgBinOp[f[0]]+e1); break; - /* 2.2.2.1 Binary Reference Operator Token */ /* 2.5.198.67 */ case 'PtgIsect': e1 = stack.pop(); e2 = stack.pop(); @@ -6493,7 +6515,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(e2+":"+e1); break; - /* 2.2.2.3 Control Tokens "can be ignored" */ /* 2.5.198.34 */ case 'PtgAttrChoose': break; /* 2.5.198.35 */ @@ -6520,11 +6541,11 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(sname + "!" + encode_cell(c)); break; - /* Function Call */ /* 2.5.198.62 */ case 'PtgFunc': /* 2.5.198.63 */ case 'PtgFuncVar': + //console.log(f[1]); /* f[1] = [argc, func, type] */ var argc = f[1][0], func = f[1][1]; if(!argc) argc = 0; @@ -6544,10 +6565,15 @@ function stringify_formula(formula, range, cell, supbooks, opts) { case 'PtgStr': stack.push('"' + f[1] + '"'); break; /* 2.5.198.57 */ case 'PtgErr': stack.push(f[1]); break; + /* 2.5.198.31 TODO */ + case 'PtgAreaN': + type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); + stack.push(encode_range_xls(r, opts)); + break; /* 2.5.198.27 TODO: fixed points */ case 'PtgArea': - type = f[1][0]; r = shift_range_xls(f[1][1], _range); - stack.push(encode_range_xls(r)); + type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); + stack.push(encode_range_xls(r, opts)); break; /* 2.5.198.28 */ case 'PtgArea3d': // TODO: lots of stuff @@ -6560,7 +6586,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push("SUM(" + stack.pop() + ")"); break; - /* Expression Prefixes */ /* 2.5.198.37 */ case 'PtgAttrSemi': break; @@ -6591,7 +6616,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(externbook.body); break; - /* 2.2.2.4 Display Tokens */ /* 2.5.198.80 */ case 'PtgParen': var lp = '(', rp = ')'; @@ -6600,10 +6624,10 @@ function stringify_formula(formula, range, cell, supbooks, opts) { switch(formula[0][last_sp][1][0]) { case 2: lp = fill(" ", formula[0][last_sp][1][1]) + lp; break; case 3: lp = fill("\r", formula[0][last_sp][1][1]) + lp; break; - case 4: rp = fill(" ", formula[0][last_sp][1][1]) + lp; break; - case 5: rp = fill("\r", formula[0][last_sp][1][1]) + lp; break; + case 4: rp = fill(" ", formula[0][last_sp][1][1]) + rp; break; + case 5: rp = fill("\r", formula[0][last_sp][1][1]) + rp; break; default: - if(opts.WTF) throw new Error("Unexpected PtgSpace type " + formula[0][last_sp][1][0]); + if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + formula[0][last_sp][1][0]); } last_sp = -1; } @@ -6612,6 +6636,9 @@ function stringify_formula(formula, range, cell, supbooks, opts) { /* 2.5.198.86 */ case 'PtgRefErr': stack.push('#REF!'); break; + /* 2.5.198.87 */ + case 'PtgRefErr3d': stack.push('#REF!'); break; + /* */ /* 2.5.198.58 TODO */ case 'PtgExp': @@ -6641,7 +6668,6 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push("{" + stringify_array(f[1]) + "}"); break; - /* 2.2.2.5 Mem Tokens */ /* 2.5.198.70 TODO: confirm this is a non-display */ case 'PtgMemArea': //stack.push("(" + f[2].map(encode_range).join(",") + ")"); @@ -6665,31 +6691,31 @@ function stringify_formula(formula, range, cell, supbooks, opts) { stack.push(""); break; - /* 2.5.198.29 TODO */ - case 'PtgAreaErr': break; - - /* 2.5.198.31 TODO */ - case 'PtgAreaN': stack.push(""); break; - - /* 2.5.198.87 TODO */ - case 'PtgRefErr3d': break; + /* 2.5.198.29 */ + case 'PtgAreaErr': stack.push("#REF!"); /* 2.5.198.72 TODO */ case 'PtgMemFunc': break; - default: throw 'Unrecognized Formula Token: ' + f; + default: throw new Error('Unrecognized Formula Token: ' + f); } var PtgNonDisp = ['PtgAttrSpace', 'PtgAttrSpaceSemi', 'PtgAttrGoto']; if(last_sp >= 0 && PtgNonDisp.indexOf(formula[0][ff][0]) == -1) { f = formula[0][last_sp]; + var _left = true; switch(f[1][0]) { + /* note: some bad XLSB files omit the PtgParen */ + case 4: _left = false; + /* falls through */ case 0: sp = fill(" ", f[1][1]); break; + case 5: _left = false; + /* falls through */ case 1: sp = fill("\r", f[1][1]); break; default: sp = ""; - if(opts.WTF) throw new Error("Unexpected PtgSpace type " + f[1][0]); + if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + f[1][0]); } - stack.push(sp + stack.pop()); + stack.push((_left ? sp : "") + stack.pop() + (_left ? "" : sp)); last_sp = -1; } //console.log("::",f, stack) @@ -7589,7 +7615,8 @@ var Ftab = { 0x01D1: 'WEEKNUM', 0x01D2: 'AMORDEGRC', 0x01D3: 'AMORLINC', -0x01D4: 'SHEETJS', +0x01D4: 'CONVERT', +0x02D4: 'SHEETJS', 0x01D5: 'ACCRINT', 0x01D6: 'ACCRINTM', 0x01D7: 'WORKDAY', @@ -7798,9 +7825,54 @@ var FtabArgc = { 0x0178: 1, /* ROUNDBAHTDOWN */ 0x0179: 1, /* ROUNDBAHTUP */ 0x017A: 1, /* THAIYEAR */ +0x017E: 3, /* CUBEMEMBERPROPERTY */ +0x0181: 1, /* HEX2DEC */ +0x0188: 1, /* OCT2DEC */ +0x0189: 1, /* BIN2DEC */ +0x018C: 2, /* IMSUB */ +0x018D: 2, /* IMDIV */ +0x018E: 2, /* IMPOWER */ +0x018F: 1, /* IMABS */ +0x0190: 1, /* IMSQRT */ +0x0191: 1, /* IMLN */ +0x0192: 1, /* IMLOG2 */ +0x0193: 1, /* IMLOG10 */ +0x0194: 1, /* IMSIN */ +0x0195: 1, /* IMCOS */ +0x0196: 1, /* IMEXP */ +0x0197: 1, /* IMARGUMENT */ +0x0198: 1, /* IMCONJUGATE */ +0x0199: 1, /* IMAGINARY */ +0x019A: 1, /* IMREAL */ +0x019E: 4, /* SERIESSUM */ +0x019F: 1, /* FACTDOUBLE */ 0x01A0: 1, /* SQRTPI */ +0x01A1: 2, /* QUOTIENT */ +0x01A4: 1, /* ISEVEN */ +0x01A5: 1, /* ISODD */ +0x01A6: 2, /* MROUND */ +0x01A8: 1, /* ERFC */ +0x01A9: 2, /* BESSELJ */ +0x01AA: 2, /* BESSELK */ +0x01AB: 2, /* BESSELY */ +0x01AC: 2, /* BESSELI */ +0x01AE: 3, /* XNPV */ +0x01B6: 3, /* TBILLEQ */ +0x01B7: 3, /* TBILLPRICE */ +0x01B8: 3, /* TBILLYIELD */ +0x01BB: 2, /* DOLLARDE */ +0x01BC: 2, /* DOLLARFR */ +0x01BD: 2, /* NOMINAL */ +0x01BE: 2, /* EFFECT */ +0x01BF: 6, /* CUMPRINC */ +0x01C0: 6, /* CUMIPMT */ 0x01C1: 2, /* EDATE */ 0x01C2: 2, /* EOMONTH */ +0x01D0: 2, /* RANDBETWEEN */ +0x01D4: 3, /* CONVERT */ +0x01DC: 2, /* FVSCHEDULE */ +0x01DF: 1, /* CUBESETCOUNT */ +0x01E0: 2, /* IFERROR */ 0xFFFF: 0 }; /* [MS-XLSX] 2.2.3 Functions */ @@ -9025,6 +9097,13 @@ function parse_wb_defaults(wb) { _ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904, 'date1904'); } + +/* TODO: validate workbook */ +function check_wb(wb) { + if(!wb || !wb.SheetNames || !wb.Sheets) throw new Error("Invalid Workbook"); + for(var i = 0; i < wb.SheetNames.length; ++i) for(var j = 0; j < i; ++j) + if(wb.SheetNames[i] == wb.SheetNames[j]) throw new Error("Duplicate Sheet Name: " + wb.SheetNames[i]); +} /* 18.2 Workbook */ var wbnsregex = /<\w+:workbook/; function parse_wb_xml(data, opts) { @@ -9045,7 +9124,7 @@ function parse_wb_xml(data, opts) { /* 18.2.13 fileVersion CT_FileVersion ? */ case '': break; + case '': case '': break; /* 18.2.12 fileSharing CT_FileSharing ? */ case '': break; @@ -9053,6 +9132,7 @@ function parse_wb_xml(data, opts) { /* 18.2.28 workbookPr CT_WorkbookPr ? */ case '': delete y[0]; wb.WBProps = y; break; + case '': break; /* 18.2.29 workbookProtection CT_WorkbookProtection ? */ case '': case '': break; /* 18.2.30 workbookView CT_BookView + */ case '': break; /* 18.2.20 sheets CT_Sheets 1 */ case '': case '': break; // aggregate sheet /* 18.2.19 sheet CT_Sheet + */ case '': break; /* 18.2.15 functionGroups CT_FunctionGroups ? */ case '': break; @@ -9088,6 +9170,7 @@ function parse_wb_xml(data, opts) { /* 18.2.2 calcPr CT_CalcPr ? */ case '': delete y[0]; wb.CalcPr = y; break; + case '': break; /* 18.2.16 oleSize CT_OleSize ? (ref required) */ case '': pass=false; break; - default: if(!pass && opts.WTF) throw 'unrecognized ' + y[0] + ' in workbook'; + default: if(!pass && opts.WTF) throw new Error('unrecognized ' + y[0] + ' in workbook'); } }); if(XMLNS.main.indexOf(wb.xmlns) === -1) throw new Error("Unknown Namespace: " + wb.xmlns); @@ -9593,7 +9676,7 @@ function xlml_normalize(d) { /* TODO: Everything */ /* UOS uses CJK in tags */ -var xlmlregex = /<(\/?)([^\s?>\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; +var xlmlregex = /<(\/?)([^\s?>!\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; //var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg; function parse_xlml_xml(d, opts) { var str = debom(xlml_normalize(d)); @@ -9613,6 +9696,7 @@ function parse_xlml_xml(d, opts) { var cstys = [], csty; var arrayf = []; xlmlregex.lastIndex = 0; + str = str.replace(//mg,""); while((Rn = xlmlregex.exec(str))) switch(Rn[3]) { case 'Data': if(state[state.length-1][1]) break; @@ -10369,9 +10453,7 @@ function parse_workbook(blob, options) { case 'RefreshAll': wb.opts.RefreshAll = val; break; case 'BookBool': break; // TODO case 'UsesELFs': /* if(val) console.error("Unsupported ELFs"); */ break; - case 'MTRSettings': { - if(val[0] && val[1]) throw "Unsupported threads: " + val; - } break; // TODO: actually support threads + case 'MTRSettings': break; case 'CalcCount': wb.opts.CalcCount = val; break; case 'CalcDelta': wb.opts.CalcDelta = val; break; case 'CalcIter': wb.opts.CalcIter = val; break; @@ -13106,6 +13188,7 @@ function write_binary_type(out, opts) { } function writeSync(wb, opts) { + check_wb(wb); var o = opts||{}; switch(o.bookType || 'xlsx') { case 'xml': return write_string_type(write_xlml(wb, o), o);