From 7b4bafba49aa5f2af4c483bab779d805c03ff128 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Thu, 11 May 2017 03:29:59 -0400 Subject: [PATCH] Miscellaneous format fixes - SSF update to address extendscript issue (fixes #608 h/t @mjdb3d) - XLSX cellDates set date format (fixes #647 h/t @psalmody) - ODS add stub stylesheet for Excel (fixes #649 h/t @WaqasJaved1) - CSV with UTF8 BOM (fixes #650 h/t @charlesread) - DIF/CSV fuzzy date speculation - SYLK parse RC formulae - CSF utils for manipulating workbooks --- bits/10_ssf.js | 39 +++-- bits/20_jsutils.js | 13 ++ bits/27_csfutils.js | 3 +- bits/32_odmanrdf.js | 8 + bits/40_harb.js | 46 +++--- bits/67_wsxml.js | 8 +- bits/71_wbcommon.js | 15 +- bits/76_xls.js | 4 +- bits/80_parseods.js | 14 ++ bits/81_writeods.js | 56 ++++++- bits/83_ods.js | 44 ------ bits/87_read.js | 2 +- bits/90_utils.js | 3 +- bits/95_api.js | 103 +++++++++++++ bits/97_node.js | 1 + multiformat.lst | 2 +- test.js | 37 ++++- tests/write.js | 38 ++++- xlsx.flow.js | 359 ++++++++++++++++++++++++++++++++------------ xlsx.js | 357 +++++++++++++++++++++++++++++++------------ 20 files changed, 855 insertions(+), 297 deletions(-) delete mode 100644 bits/83_ods.js create mode 100644 bits/95_api.js diff --git a/bits/10_ssf.js b/bits/10_ssf.js index c179063..11f2c6e 100644 --- a/bits/10_ssf.js +++ b/bits/10_ssf.js @@ -101,7 +101,7 @@ function frac(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array*/ var q = Math.floor(sgn * P/Q); return [q, sgn*P - q*Q, Q]; } -function general_fmt_int(v/*:number*/, opts/*:?any*/)/*:string*/ { return ""+v; } +function general_fmt_int(v/*:number*/)/*:string*/ { return ""+v; } SSF._general_int = general_fmt_int; var general_fmt_num = (function make_general_fmt_num() { var gnr1 = /\.(\d*[1-9])0+$/, gnr2 = /\.0*$/, gnr4 = /\.(\d*[1-9])0+/, gnr5 = /\.0*[Ee]/, gnr6 = /(E[+-])(\d)$/; @@ -121,11 +121,9 @@ function gfn4(o) { return o; } function gfn5(o) { - //for(var i = 0; i != o.length; ++i) if(o.charCodeAt(i) === 46) return o.replace(gnr2,"").replace(gnr1,".$1"); - //return o; return o.indexOf(".") > -1 ? o.replace(gnr2,"").replace(gnr1,".$1") : o; } -return function general_fmt_num(v/*:number*/, opts/*:?any*/)/*:string*/ { +return function general_fmt_num(v/*:number*/)/*:string*/ { var V = Math.floor(Math.log(Math.abs(v))*Math.LOG10E), o; if(V >= -4 && V <= -1) o = v.toPrecision(10+V); else if(Math.abs(V) <= 9) o = gfn2(v); @@ -134,18 +132,18 @@ return function general_fmt_num(v/*:number*/, opts/*:?any*/)/*:string*/ { return gfn5(gfn4(o)); };})(); SSF._general_num = general_fmt_num; -function general_fmt(v/*:any*/, opts/*:?any*/) { +function general_fmt(v/*:any*/) { switch(typeof v) { case 'string': return v; case 'boolean': return v ? "TRUE" : "FALSE"; - case 'number': return (v|0) === v ? general_fmt_int(v, opts) : general_fmt_num(v, opts); + case 'number': return (v|0) === v ? general_fmt_int(v/*, opts*/) : general_fmt_num(v/*, opts*/); case 'undefined': return ""; case 'object': if(v == null) return ""; } throw new Error("unsupported value in General format: " + v); } SSF._general = general_fmt; -function fix_hijri(date, o) { return 0; } +function fix_hijri(/*date, o*/) { return 0; } function parse_date_code(v/*:number*/,opts/*:?any*/,b2/*:?boolean*/) { if(v > 2958465 || v < 0) return null; var date = (v|0), time = Math.floor(86400 * (v - date)), dow=0; @@ -526,8 +524,7 @@ function write_num_int(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string case "##,###": case "#,###": var x = commaify(""+aval); return x !== "0" ? sign + x : ""; default: - if(fmt.slice(-3) == ".00") return write_num_int(type, fmt.slice(0,-3), val) + ".00"; - if(fmt.slice(-2) == ".0") return write_num_int(type, fmt.slice(0,-2), val) + ".0"; + if(fmt.match(/\.[0#?]*$/)) return write_num_int(type, fmt.slice(0,fmt.lastIndexOf(".")), val) + hashq(fmt.slice(fmt.lastIndexOf("."))); } throw new Error("unsupported format |" + fmt + "|"); } @@ -536,8 +533,8 @@ return function write_num(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:str };})(); function split_fmt(fmt/*:string*/)/*:Array*/ { var out/*:Array*/ = []; - var in_str = false, cc; - for(var i = 0, j = 0; i < fmt.length; ++i) switch((cc=fmt.charCodeAt(i))) { + var in_str = false/*, cc*/; + for(var i = 0, j = 0; i < fmt.length; ++i) switch((/*cc=*/fmt.charCodeAt(i))) { case 34: /* '"' */ in_str = !in_str; break; case 95: case 42: case 92: /* '_' '*' '\\' */ @@ -553,11 +550,11 @@ function split_fmt(fmt/*:string*/)/*:Array*/ { SSF._split = split_fmt; var abstime = /\[[HhMmSs]*\]/; function fmt_is_date(fmt/*:string*/)/*:boolean*/ { - var i = 0, cc = 0, c = "", o = ""; + var i = 0, /*cc = 0,*/ c = "", o = ""; while(i < fmt.length) { switch((c = fmt.charAt(i))) { case 'G': if(isgeneral(fmt, i)) i+= 6; i++; break; - case '"': for(;(cc=fmt.charCodeAt(++i)) !== 34 && i < fmt.length;) ++i; ++i; break; + case '"': for(;(/*cc=*/fmt.charCodeAt(++i)) !== 34 && i < fmt.length;) ++i; ++i; break; case '\\': i+=2; break; case '_': i+=2; break; case '@': ++i; break; @@ -579,13 +576,13 @@ function fmt_is_date(fmt/*:string*/)/*:boolean*/ { case '.': /* falls through */ case '0': case '#': - while(i < fmt.length && ("0#?.,E+-%".indexOf(c=fmt.charAt(++i)) > -1 || c=='\\' && fmt.charAt(i+1) == "-" && "0#".indexOf(fmt.charAt(i+2))>-1)){} + while(i < fmt.length && ("0#?.,E+-%".indexOf(c=fmt.charAt(++i)) > -1 || (c=='\\' && fmt.charAt(i+1) == "-" && "0#".indexOf(fmt.charAt(i+2))>-1))){/* empty */} break; - case '?': while(fmt.charAt(++i) === c){} break; + case '?': while(fmt.charAt(++i) === c){/* empty */} break; case '*': ++i; if(fmt.charAt(i) == ' ' || fmt.charAt(i) == '*') ++i; break; case '(': case ')': ++i; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1){} break; + while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1){/* empty */} break; case ' ': ++i; break; default: ++i; break; } @@ -655,7 +652,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { } /* falls through */ case '0': case '#': - o = c; while(++i < fmt.length && "0#?.,E+-%".indexOf(c=fmt.charAt(i)) > -1 || c=='\\' && fmt.charAt(i+1) == "-" && i < fmt.length - 2 && "0#".indexOf(fmt.charAt(i+2))>-1) o += c; + o = c; while((++i < fmt.length && "0#?.,E+-%".indexOf(c=fmt.charAt(i)) > -1) || (c=='\\' && fmt.charAt(i+1) == "-" && i < fmt.length - 2 && "0#".indexOf(fmt.charAt(i+2))>-1)) o += c; out[out.length] = {t:'n', v:o}; break; case '?': o = c; while(fmt.charAt(++i) === c) o+=c; @@ -717,9 +714,9 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { jj = i+1; while(out[jj] != null && ( (c=out[jj].t) === "?" || c === "D" || - (c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/') || - out[i].t === '(' && (c === ' ' || c === 'n' || c === ')') || - c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?') + ((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) || + (out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) || + (c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?')) )) { out[i].v += out[jj].v; out[jj] = {v:"", t:";"}; ++jj; @@ -773,7 +770,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { if(jj>=0 && lasti-1&&i===decpt?out[i].v.indexOf(".")+1:0; vv = out[i].v.substr(0,j); for(; j 8099) return n; + if((m > 0 || d > 1) && y != 101) return o; + if(s.toLowerCase().match(/jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec/)) return o; + if(!s.match(/[a-zA-Z]/)) return o; + return n; +} + diff --git a/bits/27_csfutils.js b/bits/27_csfutils.js index b199bd7..fca8683 100644 --- a/bits/27_csfutils.js +++ b/bits/27_csfutils.js @@ -89,11 +89,12 @@ function aoa_to_sheet(data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ { for(var C = 0; C != data[R].length; ++C) { if(typeof data[R][C] === 'undefined') continue; var cell/*:Cell*/ = ({v: data[R][C] }/*:any*/); + if(Array.isArray(cell.v)) { cell.f = data[R][C][1]; cell.v = cell.v[0]; } if(range.s.r > R) range.s.r = R; if(range.s.c > C) range.s.c = C; if(range.e.r < R) range.e.r = R; if(range.e.c < C) range.e.c = C; - if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; } + if(cell.v === null) { if(cell.f) cell.t = 'n'; else if(!o.cellStubs) continue; else cell.t = 'z'; } else if(typeof cell.v === 'number') cell.t = 'n'; else if(typeof cell.v === 'boolean') cell.t = 'b'; else if(cell.v instanceof Date) { diff --git a/bits/32_odmanrdf.js b/bits/32_odmanrdf.js index 84a7647..793a038 100644 --- a/bits/32_odmanrdf.js +++ b/bits/32_odmanrdf.js @@ -55,3 +55,11 @@ function write_rdf(rdf, opts) { o.push(''); return o.join(""); } +/* TODO: pull properties */ +var write_meta_ods/*:{(wb:any, opts:any):string}*/ = (function() { + var payload = 'Sheet' + 'JS ' + XLSX.version + ''; + return function wmo(wb, opts) { + return payload; + }; +})(); + diff --git a/bits/40_harb.js b/bits/40_harb.js index 5066631..922f6e3 100644 --- a/bits/40_harb.js +++ b/bits/40_harb.js @@ -160,7 +160,7 @@ function dbf_to_aoa(buf, opts)/*:AOA*/ { case 'T': var day = dd.read_shift(4), ms = dd.read_shift(4); throw new Error(day + " | " + ms); - //out[R][C] = new Date(); // FIXME!!! + //out[R][C] = new Date(); // TODO //break; case 'Y': out[R][C] = dd.read(4,'i')/1e4; break; case '0': @@ -210,7 +210,9 @@ var SYLK = (function() { var Mval = 0, j; for (; ri !== records.length; ++ri) { Mval = 0; - var rstr=records[ri].trim(), record=rstr.split(";"), RT=record[0], val; + var rstr=records[ri].trim(); + var record=rstr.replace(/;;/g, "\u0001").split(";").map(function(x) { return x.replace(/\u0001/g, ";"); }); + var RT=record[0], val; if(rstr.length > 0) switch(RT) { case 'ID': break; /* header */ case 'E': break; /* EOF */ @@ -240,17 +242,19 @@ var SYLK = (function() { next_cell_format = null; break; case 'E': - /* formula = record[rj].substr(1); */ - break; /* TODO: formula */ + formula = rc_to_a1(record[rj].substr(1), {r:R,c:C}); + arr[R][C] = [arr[R][C], formula]; + break; default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr); } break; case 'F': + var F_seen = 0; for(rj=1; rj 0) { rowinfo[R].hpt = Mval; rowinfo[R].hpx = pt2px(Mval); } else if(Mval == 0) rowinfo[R].hidden = true; break; default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr); - } break; + } + if(F_seen < 2) next_cell_format = null; break; default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr); } } @@ -387,7 +396,7 @@ var DIF = (function() { if(data === 'TRUE') arr[R][C] = true; else if(data === 'FALSE') arr[R][C] = false; else if(+value == +value) arr[R][C] = +value; - else if(!isNaN(new Date(value).getDate())) arr[R][C] = parseDate(value); + else if(!isNaN(fuzzydate(value).getDate())) arr[R][C] = parseDate(value); else arr[R][C] = value; ++C; break; case 1: @@ -519,7 +528,7 @@ var PRN = (function() { else if(s == "TRUE") { cell.t = 'b'; cell.v = true; } else if(s == "FALSE") { cell.t = 'b'; cell.v = false; } else if(!isNaN(v = +s)) { cell.t = 'n'; cell.w = s; cell.v = v; } - else if(!isNaN(new Date(s).getDate())) { cell.t = 'd'; cell.v = parseDate(s); } + else if(!isNaN(fuzzydate(s).getDate())) { cell.t = 'd'; cell.v = parseDate(s); } else { cell.t = 's'; if(s.charAt(0) == '"' && s.charAt(s.length - 1) == '"') s = s.slice(1,-1).replace(/""/g,'"'); @@ -550,13 +559,16 @@ var PRN = (function() { } function prn_to_sheet(d/*:RawData*/, opts)/*:Worksheet*/ { + var str = "", bytes = firstbyte(d, opts); switch(opts.type) { - case 'base64': return prn_to_sheet_str(Base64.decode(d), opts); - case 'binary': return prn_to_sheet_str(d, opts); - case 'buffer': return prn_to_sheet_str(d.toString('binary'), opts); - case 'array': return prn_to_sheet_str(cc2str(d), opts); + case 'base64': str = Base64.decode(d); break; + case 'binary': str = d; break; + case 'buffer': str = d.toString('binary'); break; + case 'array': str = cc2str(d); break; + default: throw new Error("Unrecognized type " + opts.type); } - throw new Error("Unrecognized type " + opts.type); + if(bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) str = utf8read(str); + return prn_to_sheet_str(str, opts); } function prn_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(prn_to_sheet(str, opts), opts); } diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js index d7f0bd0..a326f83 100644 --- a/bits/67_wsxml.js +++ b/bits/67_wsxml.js @@ -198,8 +198,8 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { else { cell.t = 'n'; vv = ''+(cell.v = datenum(parseDate(cell.v))); - if(typeof cell.z === 'undefined') cell.z = SSF._table[14]; } + if(typeof cell.z === 'undefined') cell.z = SSF._table[14]; break; default: vv = cell.v; break; } @@ -312,8 +312,10 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { } if(tag.t == null && p.v === undefined) { - if(!opts.sheetStubs) continue; - p.t = "z"; + if(p.f || p.F) { + p.v = 0; p.t = "n"; + } else if(!opts.sheetStubs) continue; + else p.t = "z"; } else p.t = tag.t || "n"; if(guess.s.c > idx) guess.s.c = idx; diff --git a/bits/71_wbcommon.js b/bits/71_wbcommon.js index 6ff4d1b..1abb5bb 100644 --- a/bits/71_wbcommon.js +++ b/bits/71_wbcommon.js @@ -99,11 +99,20 @@ function parse_wb_defaults(wb) { _ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904, 'date1904'); } +var badchars = "][*?\/\\".split(""); +function check_ws_name(n/*:string*/, safe/*:boolean*/)/*:boolean*/ { + if(n.length > 31) { if(safe) return false; throw new Error("Sheet names cannot exceed 31 chars"); } + var _good = true; + badchars.forEach(function(c) { + if(n.indexOf(c) == -1) return; + if(!safe) throw new Error("Sheet name cannot contain : \\ / ? * [ ]"); + _good = false; + }); + return _good; +} function check_wb_names(N) { - var badchars = "][*?\/\\".split(""); N.forEach(function(n,i) { - badchars.forEach(function(c) { if(n.indexOf(c) > -1) throw new Error("Sheet name cannot contain : \\ / ? * [ ]"); }); - if(n.length > 31) throw new Error("Sheet names cannot exceed 31 chars"); + check_ws_name(n); for(var j = 0; j < i; ++j) if(n == N[j]) throw new Error("Duplicate Sheet Name: " + n); }); } diff --git a/bits/76_xls.js b/bits/76_xls.js index 8ca3a90..f0f6410 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -219,7 +219,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { opts.enc = val; if(opts.WTF) console.error(val); if(!options.password) throw new Error("File is password-protected"); - if(val.Type !== 0) throw new Error("Encryption scheme unsupported"); + if(val.valid == null) throw new Error("Encryption scheme unsupported"); if(!val.valid) throw new Error("Password is incorrect"); break; case 'WriteAccess': opts.lastuser = val; break; @@ -236,7 +236,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'Template': break; // TODO case 'RefreshAll': wb.opts.RefreshAll = val; break; case 'BookBool': break; // TODO - case 'UsesELFs': /* if(val) console.error("Unsupported ELFs"); */ break; + case 'UsesELFs': break; case 'MTRSettings': break; case 'CalcCount': wb.opts.CalcCount = val; break; case 'CalcDelta': wb.opts.CalcDelta = val; break; diff --git a/bits/80_parseods.js b/bits/80_parseods.js index 0a94934..d0008a5 100644 --- a/bits/80_parseods.js +++ b/bits/80_parseods.js @@ -1,3 +1,4 @@ +/* OpenDocument */ var parse_content_xml = (function() { var parse_text_p = function(text, tag) { @@ -437,3 +438,16 @@ var parse_content_xml = (function() { return out; }; })(); + +function parse_ods(zip/*:ZIPFile*/, opts/*:?ParseOpts*/) { + opts = opts || ({}/*:any*/); + var ods = !!safegetzipfile(zip, 'objectdata'); + if(ods) var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts); + var content = getzipstr(zip, 'content.xml'); + if(!content) throw new Error("Missing content.xml in " + (ods ? "ODS" : "UOF")+ " file"); + return parse_content_xml(ods ? content : utf8read(content), opts); +} +function parse_fods(data/*:string*/, opts/*:?ParseOpts*/) { + return parse_content_xml(data, opts); +} + diff --git a/bits/81_writeods.js b/bits/81_writeods.js index 9b339f7..7088d0c 100644 --- a/bits/81_writeods.js +++ b/bits/81_writeods.js @@ -1,4 +1,11 @@ -var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() { +/* OpenDocument */ +var write_styles_ods/*:{(wb:any, opts:any):string}*/ = (function() { + var payload = ''; + return function wso(wb, opts) { + return payload; + }; +})(); +var write_content_ods/*:{(wb:any, opts:any):string}*/ = (function() { var null_cell_xml = ' \n'; var covered_cell_xml = ' \n'; var write_ws = function(ws, wb, i/*:number*/, opts)/*:string*/ { @@ -61,7 +68,7 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() { //case 'e': default: o.push(null_cell_xml); continue; } - o.push(writextag('table:table-cell', writextag('text:p', textp, {}), ct)); + o.push(' ' + writextag('table:table-cell', writextag('text:p', textp, {}), ct) + '\n'); } o.push(' \n'); } @@ -143,3 +150,48 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() { return o.join(""); }; })(); + +function write_ods(wb/*:any*/, opts/*:any*/) { + if(opts.bookType == "fods") return write_content_ods(wb, opts); + + /*:: if(!jszip) throw new Error("JSZip is not available"); */ + var zip = new jszip(); + var f = ""; + + var manifest/*:Array >*/ = []; + var rdf = []; + + /* 3:3.3 and 2:2.2.4 */ + f = "mimetype"; + zip.file(f, "application/vnd.oasis.opendocument.spreadsheet"); + + /* Part 1 Section 2.2 Documents */ + f = "content.xml"; + zip.file(f, write_content_ods(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "ContentFile"]); + + /* TODO: these are hard-coded styles to satiate excel */ + f = "styles.xml"; + zip.file(f, write_styles_ods(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "StylesFile"]); + + /* Part 3 Section 6 Metadata Manifest File */ + f = "manifest.rdf"; + zip.file(f, write_rdf(rdf, opts)); + manifest.push([f, "application/rdf+xml"]); + + /* TODO: this is hard-coded to satiate excel */ + f = "meta.xml"; + zip.file(f, write_meta_ods(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "MetadataFile"]); + + /* Part 3 Section 4 Manifest File */ + f = "META-INF/manifest.xml"; + zip.file(f, write_manifest(manifest, opts)); + + return zip; +} + diff --git a/bits/83_ods.js b/bits/83_ods.js deleted file mode 100644 index a0a9f3c..0000000 --- a/bits/83_ods.js +++ /dev/null @@ -1,44 +0,0 @@ -/* Part 3: Packages */ -function parse_ods(zip/*:ZIPFile*/, opts/*:?ParseOpts*/) { - opts = opts || ({}/*:any*/); - var ods = !!safegetzipfile(zip, 'objectdata'); - if(ods) var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts); - var content = getzipstr(zip, 'content.xml'); - if(!content) throw new Error("Missing content.xml in " + (ods ? "ODS" : "UOF")+ " file"); - return parse_content_xml(ods ? content : utf8read(content), opts); -} -function parse_fods(data/*:string*/, opts/*:?ParseOpts*/) { - return parse_content_xml(data, opts); -} - -function write_ods(wb/*:any*/, opts/*:any*/) { - if(opts.bookType == "fods") return write_content_xml(wb, opts); - - /*:: if(!jszip) throw new Error("JSZip is not available"); */ - var zip = new jszip(); - var f = ""; - - var manifest/*:Array >*/ = []; - var rdf = []; - - /* 3:3.3 and 2:2.2.4 */ - f = "mimetype"; - zip.file(f, "application/vnd.oasis.opendocument.spreadsheet"); - - /* Part 1 Section 2.2 Documents */ - f = "content.xml"; - zip.file(f, write_content_xml(wb, opts)); - manifest.push([f, "text/xml"]); - rdf.push([f, "ContentFile"]); - - /* Part 3 Section 6 Metadata Manifest File */ - f = "manifest.rdf"; - zip.file(f, write_rdf(rdf, opts)); - manifest.push([f, "application/rdf+xml"]); - - /* Part 3 Section 4 Manifest File */ - f = "META-INF/manifest.xml"; - zip.file(f, write_manifest(manifest, opts)); - - return zip; -} diff --git a/bits/87_read.js b/bits/87_read.js index d9c57a8..47c3d4b 100644 --- a/bits/87_read.js +++ b/bits/87_read.js @@ -52,7 +52,7 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ { case 0x49: if(n[1] == 0x44) return SYLK.to_workbook(d, o); break; case 0x54: if(n[1] == 0x41 && n[2] == 0x42 && n[3] == 0x4C) return DIF.to_workbook(d, o); break; case 0x50: if(n[1] == 0x4B && n[2] < 0x20 && n[3] < 0x20) return read_zip(d, o); break; - case 0xEF: return parse_xlml(d, o); + case 0xEF: return n[3] == 0x3C ? parse_xlml(d, o) : PRN.to_workbook(d,o); case 0xFF: if(n[1] == 0xFE){ return read_utf16(d, o); } break; case 0x00: if(n[1] == 0x00 && n[2] >= 0x02 && n[3] == 0x00) return WK_.to_workbook(d, o); break; case 0x03: case 0x83: case 0x8B: return DBF.to_workbook(d, o); diff --git a/bits/90_utils.js b/bits/90_utils.js index 4dcdbea..e24c5af 100644 --- a/bits/90_utils.js +++ b/bits/90_utils.js @@ -21,7 +21,7 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ var outi = 0, counter = 0; var dense = Array.isArray(sheet); var R = r.s.r, C = 0, CC = 0; - if(!sheet[R]) sheet[R] = []; + if(dense && !sheet[R]) sheet[R] = []; for(C = r.s.c; C <= r.e.c; ++C) { cols[C] = encode_col(C); val = dense ? sheet[R][C] : sheet[cols[C] + rr]; @@ -189,3 +189,4 @@ var utils = { sheet_to_formulae: sheet_to_formulae, sheet_to_row_object_array: sheet_to_json }; + diff --git a/bits/95_api.js b/bits/95_api.js new file mode 100644 index 0000000..8636de2 --- /dev/null +++ b/bits/95_api.js @@ -0,0 +1,103 @@ +(function(utils) { +utils.consts = utils.consts || {}; +function add_consts(R) { R.forEach(function(a){ utils.consts[a[0]] = a[1]; }); } + +function get_default(x, y, z) { return x[y] != null ? x[y] : (x[y] = z); } + +/* get cell, creating a stub if necessary */ +function ws_get_cell_stub(ws/*:Worksheet*/, R, C/*:?number*/)/*:Cell*/ { + /* A1 cell address */ + if(typeof R == "string") return ws[R] || (ws[R] = {t:'z'}); + /* cell address object */ + if(typeof R != "number") return ws_get_cell_stub(ws, encode_cell(R)); + /* R and C are 0-based indices */ + return ws_get_cell_stub(ws, encode_cell({r:R,c:C})); +} + +/* find sheet index for given name / validate index */ +function wb_sheet_idx(wb/*:Workbook*/, sh/*:number|string*/) { + if(typeof sh == "number") { + if(sh >= 0 && wb.SheetNames.length > sh) return sh; + throw new Error("Cannot find sheet # " + sh); + } else if(typeof sh == "string") { + var idx = wb.SheetNames.indexOf(sh); + if(idx > -1) return idx; + throw new Error("Cannot find sheet name |" + sh + "|"); + } else throw new Error("Cannot find sheet |" + sh + "|"); +} + +/* simple blank workbook object */ +utils.book_new = function()/*:Workbook*/ { + return { SheetNames: [], Sheets: {} }; +}; + +/* add a worksheet to the end of a given workbook */ +utils.book_append_sheet = function(wb/*:Workbook*/, ws/*:Worksheet*/, name/*:?string*/) { + if(!name) for(var i = 1; i <= 0xFFFF; ++i) if(wb.SheetNames.indexOf("Sheet" + i) == -1) break; + check_ws_name(name); + if(wb.SheetNames.indexOf(name) >= 0) throw new Error("Worksheet with name |" + name + "| already exists!"); + + wb.SheetNames.push(name); + wb.Sheets[name] = ws; +}; + +/* set sheet visibility (visible/hidden/very hidden) */ +utils.book_set_sheet_visibility = function(wb/*:Workbook*/, sh/*:number|string*/, vis/*:number*/) { + get_default(wb,"Workbook",{}); + get_default(wb.Workbook,"Sheets",[]); + + var idx = wb_sheet_idx(wb, sh); + get_default(wb.Workbook.Sheets,idx, {}); + + switch(vis) { + case 0: case 1: case 2: break; + default: throw new Error("Bad sheet visibility setting " + vis); + } + wb.Workbook.Sheets[idx].Hidden = vis; +}; +add_consts([ + ["SHEET_VISIBLE", 0], + ["SHEET_HIDDEN", 1], + ["SHEET_VERY_HIDDEN", 2] +]); + +/* set number format */ +utils.cell_set_number_format = function(cell/*:Cell*/, fmt/*:string|number*/) { + cell.z = fmt; + return cell; +}; + +/* set cell hyperlink */ +utils.cell_set_hyperlink = function(cell/*:Cell*/, target/*:string*/, tooltip/*:?string*/) { + if(!target) { + delete cell.l; + } else { + cell.l = { Target: target }; + if(tooltip) cell.l.Tooltip = tooltip; + } + return cell; +}; + +/* add to cell comments */ +utils.cell_add_comment = function(cell/*:Cell*/, text/*:string*/, author/*:?string*/) { + if(!cell.c) cell.c = []; + cell.c.push({t:text, a:author||"SheetJS"}); +}; + +/* set array formula and flush related cells */ +utils.sheet_set_array_formula = function(ws/*:Worksheet*/, range, formula/*:string*/) { + var rng = typeof range != "string" ? range : safe_decode_range(range); + var rngstr = typeof range == "string" ? range : encode_range(range); + for(var R = rng.s.r; R <= rng.e.r; ++R) for(var C = rng.s.c; C <= rng.e.c; ++C) { + var cell = ws_get_cell_stub(ws, R, C); + cell.t = 'n'; + cell.F = rngstr; + delete cell.v; + if(R == rng.s.r && C == rng.s.c) cell.f = formula; + } + return ws; +} + +return utils; +})(utils); + diff --git a/bits/97_node.js b/bits/97_node.js index 785dc9a..6f782cf 100644 --- a/bits/97_node.js +++ b/bits/97_node.js @@ -60,3 +60,4 @@ if(has_buf && typeof require != 'undefined') (function() { to_csv: write_csv_stream }; })(); + diff --git a/multiformat.lst b/multiformat.lst index 1f2790c..438403c 100644 --- a/multiformat.lst +++ b/multiformat.lst @@ -26,8 +26,8 @@ named_ranges_2011 .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml # yes-formula # no-csv (macro serialization in xml) number_format .xls .xlsb .xlsm .xls.xml .xlsb.xml .xlsm.xml -number_format_entities .xls .xlsb .xlsx .xml # yes-csv +number_format_entities .xls .xlsb .xlsx .xml pivot_table_named_range .xls .xlsb .xlsx .xml pivot_table_test .xls .xlsb .xlsm rich_text_stress .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml diff --git a/test.js b/test.js index d0b5e00..4676940 100644 --- a/test.js +++ b/test.js @@ -548,16 +548,19 @@ describe('input formats', function() { X.read(fs.readFileSync(paths.cstxlsb, 'binary'), {type: 'binary'}); X.read(fs.readFileSync(paths.cstxls, 'binary'), {type: 'binary'}); X.read(fs.readFileSync(paths.cstxml, 'binary'), {type: 'binary'}); + X.read(fs.readFileSync(paths.cstods, 'binary'), {type: 'binary'}); }); it('should read base64 strings', function() { X.read(fs.readFileSync(paths.cstxls, 'base64'), {type: 'base64'}); X.read(fs.readFileSync(paths.cstxml, 'base64'), {type: 'base64'}); + X.read(fs.readFileSync(paths.cstods, 'base64'), {type: 'base64'}); X.read(fs.readFileSync(paths.cstxlsx, 'base64'), {type: 'base64'}); X.read(fs.readFileSync(paths.cstxlsb, 'base64'), {type: 'base64'}); }); it('should read buffers', function() { X.read(fs.readFileSync(paths.cstxls), {type: 'buffer'}); X.read(fs.readFileSync(paths.cstxml), {type: 'buffer'}); + X.read(fs.readFileSync(paths.cstods), {type: 'buffer'}); X.read(fs.readFileSync(paths.cstxlsx), {type: 'buffer'}); X.read(fs.readFileSync(paths.cstxlsb), {type: 'buffer'}); }); @@ -571,12 +574,14 @@ describe('input formats', function() { it('should throw if format is unknown', function() { assert.throws(function() { X.read(fs.readFileSync(paths.cstxls), {type: 'dafuq'}); }); assert.throws(function() { X.read(fs.readFileSync(paths.cstxml), {type: 'dafuq'}); }); + assert.throws(function() { X.read(fs.readFileSync(paths.cstods), {type: 'dafuq'}); }); assert.throws(function() { X.read(fs.readFileSync(paths.cstxlsx), {type: 'dafuq'}); }); assert.throws(function() { X.read(fs.readFileSync(paths.cstxlsb), {type: 'dafuq'}); }); }); it('should infer buffer type', function() { X.read(fs.readFileSync(paths.cstxls)); X.read(fs.readFileSync(paths.cstxml)); + X.read(fs.readFileSync(paths.cstods)); X.read(fs.readFileSync(paths.cstxlsx)); X.read(fs.readFileSync(paths.cstxlsb)); }); @@ -809,8 +814,9 @@ describe('parse features', function() { var wb2 = X.readFile(paths.fstxlsb, opts); var wb3 = X.readFile(paths.fstxls, opts); var wb4 = X.readFile(paths.fstxml, opts); + var wb5 = X.readFile(paths.fstods, opts); /* TODO */ - [wb1, wb2 /*, wb3, wb4 */].forEach(function(wb) { + [wb1, wb2 /*, wb3, wb4, wb5 */].forEach(function(wb) { assert.equal(wb.Sheets.Text["!fullref"],"A1:F49"); assert.equal(wb.Sheets.Text["!ref"],"A1:F10"); }); @@ -821,8 +827,9 @@ describe('parse features', function() { var wb2 = X.readFile(paths.cstxlsb, opts); var wb3 = X.readFile(paths.cstxls, opts); var wb4 = X.readFile(paths.cstxml, opts); + var wb5 = X.readFile(paths.cstods, opts); /* TODO */ - [wb1, wb2 /*, wb3, wb4 */].forEach(function(wb) { + [wb1, wb2 /*, wb3, wb4, wb5 */].forEach(function(wb) { assert.equal(wb.Sheets.Sheet7["!fullref"],"A1:N34"); assert.equal(wb.Sheets.Sheet7["!ref"],"A1"); }); @@ -1408,8 +1415,12 @@ describe('roundtrip features', function() { }); }); -function password_file(x){return x.match(/^password.*\.xls$/); } -var password_files = fs.readdirSync('test_files').filter(password_file); +//function password_file(x){return x.match(/^password.*\.xls$/); } +//var password_files = fs.readdirSync('test_files').filter(password_file); +var password_files = [ + //"password_2002_40_972000.xls", + "password_2002_40_xor.xls" +]; describe('invalid files', function() { describe('parse', function() { [ ['password', 'apachepoi_password.xls'], @@ -1765,10 +1776,20 @@ describe('encryption', function() { password_files.forEach(function(x) { describe(x, function() { it('should throw with no password', function() {assert.throws(function() { X.readFile(dir + x); }); }); - it('should throw with wrong password', function() {assert.throws(function() { X.readFile(dir + x, {password:'passwor',WTF:opts.WTF}); }); }); - it.skip('should recognize correct password', function() { - try { X.readFile(dir + x, {password:'password',WTF:opts.WTF}); } - catch(e) { if(e.message == "Password is incorrect") throw e; } + it('should throw with wrong password', function() { + try { + X.readFile(dir + x, {password:'passwor',WTF:opts.WTF}); + throw new Error("incorrect password was accepted"); + } catch(e) { + if(e.message != "Password is incorrect") throw e; + } + }); + it('should recognize correct password', function() { + try { + X.readFile(dir + x, {password:'password',WTF:opts.WTF}); + } catch(e) { + if(e.message == "Password is incorrect") throw e; + } }); it.skip('should decrypt file', function() { var wb = X.readFile(dir + x, {password:'password',WTF:opts.WTF}); diff --git a/tests/write.js b/tests/write.js index 277738f..775ef61 100644 --- a/tests/write.js +++ b/tests/write.js @@ -28,7 +28,7 @@ var wsrows = [ {hpx: 24}, {hidden:true}, // hide row {hidden:false} -] +]; console.log("Sheet Name: " + ws_name); console.log("Data: "); for(var i=0; i!=data.length; ++i) console.log(data[i]); @@ -40,27 +40,39 @@ console.log("Columns :"); for(i=0; i!=wscols.length;++i) console.log(wscols[i]); if(typeof XLSX === "undefined") { try { XLSX = require('./'); } catch(e) { XLSX = require('../'); } } /* blank workbook constructor */ +/* var wb = { SheetNames: [], Sheets: {} }; +*/ +var wb = XLSX.utils.book_new(); /* convert an array of arrays in JS to a CSF spreadsheet */ var ws = XLSX.utils.aoa_to_sheet(data, {cellDates:true}); /* TEST: add worksheet to workbook */ +/* wb.SheetNames.push(ws_name); wb.Sheets[ws_name] = ws; +*/ +XLSX.utils.book_append_sheet(wb, ws, ws_name); /* TEST: simple formula */ ws['C1'].f = "A1+B1"; ws['C2'] = {t:'n', f:"A1+B1"}; /* TEST: single-cell array formula */ +/* ws['D1'] = {t:'n', f:"SUM(A1:C1*A1:C1)", F:"D1:D1"}; +*/ +XLSX.utils.sheet_set_array_formula(ws, 'D1:D1', "SUM(A1:C1*A1:C1)"); /* TEST: multi-cell array formula */ +/* ws['E1'] = {t:'n', f:"TRANSPOSE(A1:D1)", F:"E1:E4"}; ws['E2'] = {t:'n', F:"E1:E4"}; ws['E3'] = {t:'n', F:"E1:E4"}; ws['E4'] = {t:'n', F:"E1:E4"}; +*/ +XLSX.utils.sheet_set_array_formula(ws, 'E1:E4', "TRANSPOSE(A1:D1)"); ws["!ref"] = "A1:E6"; /* TEST: column props */ @@ -70,25 +82,40 @@ ws['!cols'] = wscols; ws['!rows'] = wsrows; /* TEST: hyperlink note: Excel does not automatically style hyperlinks */ +/* ws['A3'].l = { Target: "http://sheetjs.com", Tooltip: "Visit us " }; +*/ +XLSX.utils.cell_set_hyperlink(ws['A3'], "http://sheetjs.com", "Visit us " ); /* TEST: built-in format */ +/* ws['B1'].z = "0%"; // Format Code 9 +*/ +XLSX.utils.cell_set_number_format(ws['B1'], "0%"); /* TEST: custom format */ var custfmt = "\"This is \"\\ 0.0"; +/* ws['C2'].z = custfmt; +*/ +XLSX.utils.cell_set_number_format(ws['C2'], custfmt); /* TEST: page margins */ ws['!margins'] = { left:1.0, right:1.0, top:1.0, bottom:1.0, header:0.5, footer:0.5 }; -console.log("JSON Data:");console.log(XLSX.utils.sheet_to_json(ws, {header:1})); +console.log("JSON Data:"); +console.log(XLSX.utils.sheet_to_json(ws, {header:1})); /* TEST: hidden sheets */ +/* wb.SheetNames.push("Hidden"); wb.Sheets["Hidden"] = XLSX.utils.aoa_to_sheet(["Hidden".split(""), [1,2,3]]); wb.Workbook = {Sheets:[]}; wb.Workbook.Sheets[1] = {Hidden:1}; +*/ +var data_2 = ["Hidden".split(""), [1,2,3]]; +XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(data_2), "Hidden"); +XLSX.utils.book_set_sheet_visibility(wb, "Hidden", XLSX.utils.consts.SHEET_HIDDEN); /* TEST: properties */ wb.Props = { @@ -102,11 +129,14 @@ wb.Props = { Comments: "Nothing to say here", LastAuthor: "Not SheetJS", CreatedDate: new Date(2017,1,19) -} +}; /* TEST: comments */ +/* ws['A4'].c = []; ws['A4'].c.push({a:"SheetJS",t:"I'm a little comment, short and stout!\n\nWell, Stout may be the wrong word"}); +*/ +XLSX.utils.cell_add_comment(ws['A4'], "I'm a little comment, short and stout!\n\nWell, Stout may be the wrong word", "SheetJS"); /* TEST: sheet protection */ ws['!protect'] = { @@ -119,7 +149,7 @@ ws['!protect'] = { scenarios:1 }; -console.log("Worksheet Model:") +console.log("Worksheet Model:"); console.log(ws); [ diff --git a/xlsx.flow.js b/xlsx.flow.js index 0d26636..776a0c7 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -218,7 +218,7 @@ function frac(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array*/ var q = Math.floor(sgn * P/Q); return [q, sgn*P - q*Q, Q]; } -function general_fmt_int(v/*:number*/, opts/*:?any*/)/*:string*/ { return ""+v; } +function general_fmt_int(v/*:number*/)/*:string*/ { return ""+v; } SSF._general_int = general_fmt_int; var general_fmt_num = (function make_general_fmt_num() { var gnr1 = /\.(\d*[1-9])0+$/, gnr2 = /\.0*$/, gnr4 = /\.(\d*[1-9])0+/, gnr5 = /\.0*[Ee]/, gnr6 = /(E[+-])(\d)$/; @@ -238,11 +238,9 @@ function gfn4(o) { return o; } function gfn5(o) { - //for(var i = 0; i != o.length; ++i) if(o.charCodeAt(i) === 46) return o.replace(gnr2,"").replace(gnr1,".$1"); - //return o; return o.indexOf(".") > -1 ? o.replace(gnr2,"").replace(gnr1,".$1") : o; } -return function general_fmt_num(v/*:number*/, opts/*:?any*/)/*:string*/ { +return function general_fmt_num(v/*:number*/)/*:string*/ { var V = Math.floor(Math.log(Math.abs(v))*Math.LOG10E), o; if(V >= -4 && V <= -1) o = v.toPrecision(10+V); else if(Math.abs(V) <= 9) o = gfn2(v); @@ -251,18 +249,18 @@ return function general_fmt_num(v/*:number*/, opts/*:?any*/)/*:string*/ { return gfn5(gfn4(o)); };})(); SSF._general_num = general_fmt_num; -function general_fmt(v/*:any*/, opts/*:?any*/) { +function general_fmt(v/*:any*/) { switch(typeof v) { case 'string': return v; case 'boolean': return v ? "TRUE" : "FALSE"; - case 'number': return (v|0) === v ? general_fmt_int(v, opts) : general_fmt_num(v, opts); + case 'number': return (v|0) === v ? general_fmt_int(v/*, opts*/) : general_fmt_num(v/*, opts*/); case 'undefined': return ""; case 'object': if(v == null) return ""; } throw new Error("unsupported value in General format: " + v); } SSF._general = general_fmt; -function fix_hijri(date, o) { return 0; } +function fix_hijri(/*date, o*/) { return 0; } function parse_date_code(v/*:number*/,opts/*:?any*/,b2/*:?boolean*/) { if(v > 2958465 || v < 0) return null; var date = (v|0), time = Math.floor(86400 * (v - date)), dow=0; @@ -643,8 +641,7 @@ function write_num_int(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string case "##,###": case "#,###": var x = commaify(""+aval); return x !== "0" ? sign + x : ""; default: - if(fmt.slice(-3) == ".00") return write_num_int(type, fmt.slice(0,-3), val) + ".00"; - if(fmt.slice(-2) == ".0") return write_num_int(type, fmt.slice(0,-2), val) + ".0"; + if(fmt.match(/\.[0#?]*$/)) return write_num_int(type, fmt.slice(0,fmt.lastIndexOf(".")), val) + hashq(fmt.slice(fmt.lastIndexOf("."))); } throw new Error("unsupported format |" + fmt + "|"); } @@ -653,8 +650,8 @@ return function write_num(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:str };})(); function split_fmt(fmt/*:string*/)/*:Array*/ { var out/*:Array*/ = []; - var in_str = false, cc; - for(var i = 0, j = 0; i < fmt.length; ++i) switch((cc=fmt.charCodeAt(i))) { + var in_str = false/*, cc*/; + for(var i = 0, j = 0; i < fmt.length; ++i) switch((/*cc=*/fmt.charCodeAt(i))) { case 34: /* '"' */ in_str = !in_str; break; case 95: case 42: case 92: /* '_' '*' '\\' */ @@ -670,11 +667,11 @@ function split_fmt(fmt/*:string*/)/*:Array*/ { SSF._split = split_fmt; var abstime = /\[[HhMmSs]*\]/; function fmt_is_date(fmt/*:string*/)/*:boolean*/ { - var i = 0, cc = 0, c = "", o = ""; + var i = 0, /*cc = 0,*/ c = "", o = ""; while(i < fmt.length) { switch((c = fmt.charAt(i))) { case 'G': if(isgeneral(fmt, i)) i+= 6; i++; break; - case '"': for(;(cc=fmt.charCodeAt(++i)) !== 34 && i < fmt.length;) ++i; ++i; break; + case '"': for(;(/*cc=*/fmt.charCodeAt(++i)) !== 34 && i < fmt.length;) ++i; ++i; break; case '\\': i+=2; break; case '_': i+=2; break; case '@': ++i; break; @@ -696,13 +693,13 @@ function fmt_is_date(fmt/*:string*/)/*:boolean*/ { case '.': /* falls through */ case '0': case '#': - while(i < fmt.length && ("0#?.,E+-%".indexOf(c=fmt.charAt(++i)) > -1 || c=='\\' && fmt.charAt(i+1) == "-" && "0#".indexOf(fmt.charAt(i+2))>-1)){} + while(i < fmt.length && ("0#?.,E+-%".indexOf(c=fmt.charAt(++i)) > -1 || (c=='\\' && fmt.charAt(i+1) == "-" && "0#".indexOf(fmt.charAt(i+2))>-1))){/* empty */} break; - case '?': while(fmt.charAt(++i) === c){} break; + case '?': while(fmt.charAt(++i) === c){/* empty */} break; case '*': ++i; if(fmt.charAt(i) == ' ' || fmt.charAt(i) == '*') ++i; break; case '(': case ')': ++i; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1){} break; + while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1){/* empty */} break; case ' ': ++i; break; default: ++i; break; } @@ -772,7 +769,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { } /* falls through */ case '0': case '#': - o = c; while(++i < fmt.length && "0#?.,E+-%".indexOf(c=fmt.charAt(i)) > -1 || c=='\\' && fmt.charAt(i+1) == "-" && i < fmt.length - 2 && "0#".indexOf(fmt.charAt(i+2))>-1) o += c; + o = c; while((++i < fmt.length && "0#?.,E+-%".indexOf(c=fmt.charAt(i)) > -1) || (c=='\\' && fmt.charAt(i+1) == "-" && i < fmt.length - 2 && "0#".indexOf(fmt.charAt(i+2))>-1)) o += c; out[out.length] = {t:'n', v:o}; break; case '?': o = c; while(fmt.charAt(++i) === c) o+=c; @@ -834,9 +831,9 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { jj = i+1; while(out[jj] != null && ( (c=out[jj].t) === "?" || c === "D" || - (c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/') || - out[i].t === '(' && (c === ' ' || c === 'n' || c === ')') || - c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?') + ((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) || + (out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) || + (c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?')) )) { out[i].v += out[jj].v; out[jj] = {v:"", t:";"}; ++jj; @@ -890,7 +887,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { if(jj>=0 && lasti-1&&i===decpt?out[i].v.indexOf(".")+1:0; vv = out[i].v.substr(0,j); for(; j 8099) return n; + if((m > 0 || d > 1) && y != 101) return o; + if(s.toLowerCase().match(/jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec/)) return o; + if(!s.match(/[a-zA-Z]/)) return o; + return n; +} + function getdatastr(data)/*:?string*/ { if(!data) return null; if(data.data) return debom(data.data); @@ -2237,11 +2247,12 @@ function aoa_to_sheet(data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ { for(var C = 0; C != data[R].length; ++C) { if(typeof data[R][C] === 'undefined') continue; var cell/*:Cell*/ = ({v: data[R][C] }/*:any*/); + if(Array.isArray(cell.v)) { cell.f = data[R][C][1]; cell.v = cell.v[0]; } if(range.s.r > R) range.s.r = R; if(range.s.c > C) range.s.c = C; if(range.e.r < R) range.e.r = R; if(range.e.c < C) range.e.c = C; - if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; } + if(cell.v === null) { if(cell.f) cell.t = 'n'; else if(!o.cellStubs) continue; else cell.t = 'z'; } else if(typeof cell.v === 'number') cell.t = 'n'; else if(typeof cell.v === 'boolean') cell.t = 'b'; else if(cell.v instanceof Date) { @@ -3221,6 +3232,14 @@ function write_rdf(rdf, opts) { o.push(''); return o.join(""); } +/* TODO: pull properties */ +var write_meta_ods/*:{(wb:any, opts:any):string}*/ = (function() { + var payload = 'Sheet' + 'JS ' + XLSX.version + ''; + return function wmo(wb, opts) { + return payload; + }; +})(); + /* ECMA-376 Part II 11.1 Core Properties Part */ /* [MS-OSHARED] 2.3.3.2.[1-2].1 (PIDSI/PIDDSI) */ var CORE_PROPS/*:Array >*/ = [ @@ -5278,7 +5297,7 @@ function dbf_to_aoa(buf, opts)/*:AOA*/ { case 'T': var day = dd.read_shift(4), ms = dd.read_shift(4); throw new Error(day + " | " + ms); - //out[R][C] = new Date(); // FIXME!!! + //out[R][C] = new Date(); // TODO //break; case 'Y': out[R][C] = dd.read(4,'i')/1e4; break; case '0': @@ -5328,7 +5347,9 @@ var SYLK = (function() { var Mval = 0, j; for (; ri !== records.length; ++ri) { Mval = 0; - var rstr=records[ri].trim(), record=rstr.split(";"), RT=record[0], val; + var rstr=records[ri].trim(); + var record=rstr.replace(/;;/g, "\u0001").split(";").map(function(x) { return x.replace(/\u0001/g, ";"); }); + var RT=record[0], val; if(rstr.length > 0) switch(RT) { case 'ID': break; /* header */ case 'E': break; /* EOF */ @@ -5358,17 +5379,19 @@ var SYLK = (function() { next_cell_format = null; break; case 'E': - /* formula = record[rj].substr(1); */ - break; /* TODO: formula */ + formula = rc_to_a1(record[rj].substr(1), {r:R,c:C}); + arr[R][C] = [arr[R][C], formula]; + break; default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr); } break; case 'F': + var F_seen = 0; for(rj=1; rj 0) { rowinfo[R].hpt = Mval; rowinfo[R].hpx = pt2px(Mval); } else if(Mval == 0) rowinfo[R].hidden = true; break; default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr); - } break; + } + if(F_seen < 2) next_cell_format = null; break; default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr); } } @@ -5505,7 +5533,7 @@ var DIF = (function() { if(data === 'TRUE') arr[R][C] = true; else if(data === 'FALSE') arr[R][C] = false; else if(+value == +value) arr[R][C] = +value; - else if(!isNaN(new Date(value).getDate())) arr[R][C] = parseDate(value); + else if(!isNaN(fuzzydate(value).getDate())) arr[R][C] = parseDate(value); else arr[R][C] = value; ++C; break; case 1: @@ -5637,7 +5665,7 @@ var PRN = (function() { else if(s == "TRUE") { cell.t = 'b'; cell.v = true; } else if(s == "FALSE") { cell.t = 'b'; cell.v = false; } else if(!isNaN(v = +s)) { cell.t = 'n'; cell.w = s; cell.v = v; } - else if(!isNaN(new Date(s).getDate())) { cell.t = 'd'; cell.v = parseDate(s); } + else if(!isNaN(fuzzydate(s).getDate())) { cell.t = 'd'; cell.v = parseDate(s); } else { cell.t = 's'; if(s.charAt(0) == '"' && s.charAt(s.length - 1) == '"') s = s.slice(1,-1).replace(/""/g,'"'); @@ -5668,13 +5696,16 @@ var PRN = (function() { } function prn_to_sheet(d/*:RawData*/, opts)/*:Worksheet*/ { + var str = "", bytes = firstbyte(d, opts); switch(opts.type) { - case 'base64': return prn_to_sheet_str(Base64.decode(d), opts); - case 'binary': return prn_to_sheet_str(d, opts); - case 'buffer': return prn_to_sheet_str(d.toString('binary'), opts); - case 'array': return prn_to_sheet_str(cc2str(d), opts); + case 'base64': str = Base64.decode(d); break; + case 'binary': str = d; break; + case 'buffer': str = d.toString('binary'); break; + case 'array': str = cc2str(d); break; + default: throw new Error("Unrecognized type " + opts.type); } - throw new Error("Unrecognized type " + opts.type); + if(bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) str = utf8read(str); + return prn_to_sheet_str(str, opts); } function prn_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(prn_to_sheet(str, opts), opts); } @@ -10788,8 +10819,8 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { else { cell.t = 'n'; vv = ''+(cell.v = datenum(parseDate(cell.v))); - if(typeof cell.z === 'undefined') cell.z = SSF._table[14]; } + if(typeof cell.z === 'undefined') cell.z = SSF._table[14]; break; default: vv = cell.v; break; } @@ -10902,8 +10933,10 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { } if(tag.t == null && p.v === undefined) { - if(!opts.sheetStubs) continue; - p.t = "z"; + if(p.f || p.F) { + p.v = 0; p.t = "n"; + } else if(!opts.sheetStubs) continue; + else p.t = "z"; } else p.t = tag.t || "n"; if(guess.s.c > idx) guess.s.c = idx; @@ -12152,11 +12185,20 @@ function parse_wb_defaults(wb) { _ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904, 'date1904'); } +var badchars = "][*?\/\\".split(""); +function check_ws_name(n/*:string*/, safe/*:boolean*/)/*:boolean*/ { + if(n.length > 31) { if(safe) return false; throw new Error("Sheet names cannot exceed 31 chars"); } + var _good = true; + badchars.forEach(function(c) { + if(n.indexOf(c) == -1) return; + if(!safe) throw new Error("Sheet name cannot contain : \\ / ? * [ ]"); + _good = false; + }); + return _good; +} function check_wb_names(N) { - var badchars = "][*?\/\\".split(""); N.forEach(function(n,i) { - badchars.forEach(function(c) { if(n.indexOf(c) > -1) throw new Error("Sheet name cannot contain : \\ / ? * [ ]"); }); - if(n.length > 31) throw new Error("Sheet names cannot exceed 31 chars"); + check_ws_name(n); for(var j = 0; j < i; ++j) if(n == N[j]) throw new Error("Duplicate Sheet Name: " + n); }); } @@ -14009,7 +14051,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { opts.enc = val; if(opts.WTF) console.error(val); if(!options.password) throw new Error("File is password-protected"); - if(val.Type !== 0) throw new Error("Encryption scheme unsupported"); + if(val.valid == null) throw new Error("Encryption scheme unsupported"); if(!val.valid) throw new Error("Password is incorrect"); break; case 'WriteAccess': opts.lastuser = val; break; @@ -14026,7 +14068,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'Template': break; // TODO case 'RefreshAll': wb.opts.RefreshAll = val; break; case 'BookBool': break; // TODO - case 'UsesELFs': /* if(val) console.error("Unsupported ELFs"); */ break; + case 'UsesELFs': break; case 'MTRSettings': break; case 'CalcCount': wb.opts.CalcCount = val; break; case 'CalcDelta': wb.opts.CalcDelta = val; break; @@ -16117,6 +16159,7 @@ function parse_dom_table(table/*:HTMLElement*/, _opts/*:?any*/)/*:Worksheet*/ { function table_to_book(table/*:HTMLElement*/, opts/*:?any*/)/*:Workbook*/ { return sheet_to_workbook(parse_dom_table(table, opts), opts); } +/* OpenDocument */ var parse_content_xml = (function() { var parse_text_p = function(text, tag) { @@ -16556,7 +16599,27 @@ var parse_content_xml = (function() { return out; }; })(); -var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() { + +function parse_ods(zip/*:ZIPFile*/, opts/*:?ParseOpts*/) { + opts = opts || ({}/*:any*/); + var ods = !!safegetzipfile(zip, 'objectdata'); + if(ods) var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts); + var content = getzipstr(zip, 'content.xml'); + if(!content) throw new Error("Missing content.xml in " + (ods ? "ODS" : "UOF")+ " file"); + return parse_content_xml(ods ? content : utf8read(content), opts); +} +function parse_fods(data/*:string*/, opts/*:?ParseOpts*/) { + return parse_content_xml(data, opts); +} + +/* OpenDocument */ +var write_styles_ods/*:{(wb:any, opts:any):string}*/ = (function() { + var payload = ''; + return function wso(wb, opts) { + return payload; + }; +})(); +var write_content_ods/*:{(wb:any, opts:any):string}*/ = (function() { var null_cell_xml = ' \n'; var covered_cell_xml = ' \n'; var write_ws = function(ws, wb, i/*:number*/, opts)/*:string*/ { @@ -16619,7 +16682,7 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() { //case 'e': default: o.push(null_cell_xml); continue; } - o.push(writextag('table:table-cell', writextag('text:p', textp, {}), ct)); + o.push(' ' + writextag('table:table-cell', writextag('text:p', textp, {}), ct) + '\n'); } o.push(' \n'); } @@ -16701,6 +16764,51 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() { return o.join(""); }; })(); + +function write_ods(wb/*:any*/, opts/*:any*/) { + if(opts.bookType == "fods") return write_content_ods(wb, opts); + + /*:: if(!jszip) throw new Error("JSZip is not available"); */ + var zip = new jszip(); + var f = ""; + + var manifest/*:Array >*/ = []; + var rdf = []; + + /* 3:3.3 and 2:2.2.4 */ + f = "mimetype"; + zip.file(f, "application/vnd.oasis.opendocument.spreadsheet"); + + /* Part 1 Section 2.2 Documents */ + f = "content.xml"; + zip.file(f, write_content_ods(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "ContentFile"]); + + /* TODO: these are hard-coded styles to satiate excel */ + f = "styles.xml"; + zip.file(f, write_styles_ods(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "StylesFile"]); + + /* Part 3 Section 6 Metadata Manifest File */ + f = "manifest.rdf"; + zip.file(f, write_rdf(rdf, opts)); + manifest.push([f, "application/rdf+xml"]); + + /* TODO: this is hard-coded to satiate excel */ + f = "meta.xml"; + zip.file(f, write_meta_ods(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "MetadataFile"]); + + /* Part 3 Section 4 Manifest File */ + f = "META-INF/manifest.xml"; + zip.file(f, write_manifest(manifest, opts)); + + return zip; +} + /* actual implementation elsewhere, wrappers are for read/write */ function write_obj_str(factory/*:WriteObjStrFactory*/) { return function write_str(wb/*:Workbook*/, o/*:WriteOpts*/)/*:string*/ { @@ -16717,50 +16825,6 @@ var write_slk_str = write_obj_str(SYLK); var write_dif_str = write_obj_str(DIF); var write_prn_str = write_obj_str(PRN); var write_txt_str = write_obj_str({from_sheet:sheet_to_txt}); -/* Part 3: Packages */ -function parse_ods(zip/*:ZIPFile*/, opts/*:?ParseOpts*/) { - opts = opts || ({}/*:any*/); - var ods = !!safegetzipfile(zip, 'objectdata'); - if(ods) var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts); - var content = getzipstr(zip, 'content.xml'); - if(!content) throw new Error("Missing content.xml in " + (ods ? "ODS" : "UOF")+ " file"); - return parse_content_xml(ods ? content : utf8read(content), opts); -} -function parse_fods(data/*:string*/, opts/*:?ParseOpts*/) { - return parse_content_xml(data, opts); -} - -function write_ods(wb/*:any*/, opts/*:any*/) { - if(opts.bookType == "fods") return write_content_xml(wb, opts); - - /*:: if(!jszip) throw new Error("JSZip is not available"); */ - var zip = new jszip(); - var f = ""; - - var manifest/*:Array >*/ = []; - var rdf = []; - - /* 3:3.3 and 2:2.2.4 */ - f = "mimetype"; - zip.file(f, "application/vnd.oasis.opendocument.spreadsheet"); - - /* Part 1 Section 2.2 Documents */ - f = "content.xml"; - zip.file(f, write_content_xml(wb, opts)); - manifest.push([f, "text/xml"]); - rdf.push([f, "ContentFile"]); - - /* Part 3 Section 6 Metadata Manifest File */ - f = "manifest.rdf"; - zip.file(f, write_rdf(rdf, opts)); - manifest.push([f, "application/rdf+xml"]); - - /* Part 3 Section 4 Manifest File */ - f = "META-INF/manifest.xml"; - zip.file(f, write_manifest(manifest, opts)); - - return zip; -} function fix_opts_func(defaults/*:Array >*/)/*:{(o:any):void}*/ { return function fix_opts(opts) { for(var i = 0; i != defaults.length; ++i) { @@ -17199,7 +17263,7 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ { case 0x49: if(n[1] == 0x44) return SYLK.to_workbook(d, o); break; case 0x54: if(n[1] == 0x41 && n[2] == 0x42 && n[3] == 0x4C) return DIF.to_workbook(d, o); break; case 0x50: if(n[1] == 0x4B && n[2] < 0x20 && n[3] < 0x20) return read_zip(d, o); break; - case 0xEF: return parse_xlml(d, o); + case 0xEF: return n[3] == 0x3C ? parse_xlml(d, o) : PRN.to_workbook(d,o); case 0xFF: if(n[1] == 0xFE){ return read_utf16(d, o); } break; case 0x00: if(n[1] == 0x00 && n[2] >= 0x02 && n[3] == 0x00) return WK_.to_workbook(d, o); break; case 0x03: case 0x83: case 0x8B: return DBF.to_workbook(d, o); @@ -17353,7 +17417,7 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ var outi = 0, counter = 0; var dense = Array.isArray(sheet); var R = r.s.r, C = 0, CC = 0; - if(!sheet[R]) sheet[R] = []; + if(dense && !sheet[R]) sheet[R] = []; for(C = r.s.c; C <= r.e.c; ++C) { cols[C] = encode_col(C); val = dense ? sheet[R][C] : sheet[cols[C] + rr]; @@ -17521,6 +17585,110 @@ var utils = { sheet_to_formulae: sheet_to_formulae, sheet_to_row_object_array: sheet_to_json }; + +(function(utils) { +utils.consts = utils.consts || {}; +function add_consts(R) { R.forEach(function(a){ utils.consts[a[0]] = a[1]; }); } + +function get_default(x, y, z) { return x[y] != null ? x[y] : (x[y] = z); } + +/* get cell, creating a stub if necessary */ +function ws_get_cell_stub(ws/*:Worksheet*/, R, C/*:?number*/)/*:Cell*/ { + /* A1 cell address */ + if(typeof R == "string") return ws[R] || (ws[R] = {t:'z'}); + /* cell address object */ + if(typeof R != "number") return ws_get_cell_stub(ws, encode_cell(R)); + /* R and C are 0-based indices */ + return ws_get_cell_stub(ws, encode_cell({r:R,c:C})); +} + +/* find sheet index for given name / validate index */ +function wb_sheet_idx(wb/*:Workbook*/, sh/*:number|string*/) { + if(typeof sh == "number") { + if(sh >= 0 && wb.SheetNames.length > sh) return sh; + throw new Error("Cannot find sheet # " + sh); + } else if(typeof sh == "string") { + var idx = wb.SheetNames.indexOf(sh); + if(idx > -1) return idx; + throw new Error("Cannot find sheet name |" + sh + "|"); + } else throw new Error("Cannot find sheet |" + sh + "|"); +} + +/* simple blank workbook object */ +utils.book_new = function()/*:Workbook*/ { + return { SheetNames: [], Sheets: {} }; +}; + +/* add a worksheet to the end of a given workbook */ +utils.book_append_sheet = function(wb/*:Workbook*/, ws/*:Worksheet*/, name/*:?string*/) { + if(!name) for(var i = 1; i <= 0xFFFF; ++i) if(wb.SheetNames.indexOf("Sheet" + i) == -1) break; + check_ws_name(name); + if(wb.SheetNames.indexOf(name) >= 0) throw new Error("Worksheet with name |" + name + "| already exists!"); + + wb.SheetNames.push(name); + wb.Sheets[name] = ws; +}; + +/* set sheet visibility (visible/hidden/very hidden) */ +utils.book_set_sheet_visibility = function(wb/*:Workbook*/, sh/*:number|string*/, vis/*:number*/) { + get_default(wb,"Workbook",{}); + get_default(wb.Workbook,"Sheets",[]); + + var idx = wb_sheet_idx(wb, sh); + get_default(wb.Workbook.Sheets,idx, {}); + + switch(vis) { + case 0: case 1: case 2: break; + default: throw new Error("Bad sheet visibility setting " + vis); + } + wb.Workbook.Sheets[idx].Hidden = vis; +}; +add_consts([ + ["SHEET_VISIBLE", 0], + ["SHEET_HIDDEN", 1], + ["SHEET_VERY_HIDDEN", 2] +]); + +/* set number format */ +utils.cell_set_number_format = function(cell/*:Cell*/, fmt/*:string|number*/) { + cell.z = fmt; + return cell; +}; + +/* set cell hyperlink */ +utils.cell_set_hyperlink = function(cell/*:Cell*/, target/*:string*/, tooltip/*:?string*/) { + if(!target) { + delete cell.l; + } else { + cell.l = { Target: target }; + if(tooltip) cell.l.Tooltip = tooltip; + } + return cell; +}; + +/* add to cell comments */ +utils.cell_add_comment = function(cell/*:Cell*/, text/*:string*/, author/*:?string*/) { + if(!cell.c) cell.c = []; + cell.c.push({t:text, a:author||"SheetJS"}); +}; + +/* set array formula and flush related cells */ +utils.sheet_set_array_formula = function(ws/*:Worksheet*/, range, formula/*:string*/) { + var rng = typeof range != "string" ? range : safe_decode_range(range); + var rngstr = typeof range == "string" ? range : encode_range(range); + for(var R = rng.s.r; R <= rng.e.r; ++R) for(var C = rng.s.c; C <= rng.e.c; ++C) { + var cell = ws_get_cell_stub(ws, R, C); + cell.t = 'n'; + cell.F = rngstr; + delete cell.v; + if(R == rng.s.r && C == rng.s.c) cell.f = formula; + } + return ws; +} + +return utils; +})(utils); + if(has_buf && typeof require != 'undefined') (function() { var Readable = require('stream').Readable; @@ -17583,6 +17751,7 @@ if(has_buf && typeof require != 'undefined') (function() { to_csv: write_csv_stream }; })(); + XLSX.parse_xlscfb = parse_xlscfb; XLSX.parse_ods = parse_ods; XLSX.parse_fods = parse_fods; diff --git a/xlsx.js b/xlsx.js index 6a78a58..cd599c6 100644 --- a/xlsx.js +++ b/xlsx.js @@ -198,7 +198,7 @@ function frac(x, D, mixed) { var q = Math.floor(sgn * P/Q); return [q, sgn*P - q*Q, Q]; } -function general_fmt_int(v, opts) { return ""+v; } +function general_fmt_int(v) { return ""+v; } SSF._general_int = general_fmt_int; var general_fmt_num = (function make_general_fmt_num() { var gnr1 = /\.(\d*[1-9])0+$/, gnr2 = /\.0*$/, gnr4 = /\.(\d*[1-9])0+/, gnr5 = /\.0*[Ee]/, gnr6 = /(E[+-])(\d)$/; @@ -218,11 +218,9 @@ function gfn4(o) { return o; } function gfn5(o) { - //for(var i = 0; i != o.length; ++i) if(o.charCodeAt(i) === 46) return o.replace(gnr2,"").replace(gnr1,".$1"); - //return o; return o.indexOf(".") > -1 ? o.replace(gnr2,"").replace(gnr1,".$1") : o; } -return function general_fmt_num(v, opts) { +return function general_fmt_num(v) { var V = Math.floor(Math.log(Math.abs(v))*Math.LOG10E), o; if(V >= -4 && V <= -1) o = v.toPrecision(10+V); else if(Math.abs(V) <= 9) o = gfn2(v); @@ -231,18 +229,18 @@ return function general_fmt_num(v, opts) { return gfn5(gfn4(o)); };})(); SSF._general_num = general_fmt_num; -function general_fmt(v, opts) { +function general_fmt(v) { switch(typeof v) { case 'string': return v; case 'boolean': return v ? "TRUE" : "FALSE"; - case 'number': return (v|0) === v ? general_fmt_int(v, opts) : general_fmt_num(v, opts); + case 'number': return (v|0) === v ? general_fmt_int(v/*, opts*/) : general_fmt_num(v/*, opts*/); case 'undefined': return ""; case 'object': if(v == null) return ""; } throw new Error("unsupported value in General format: " + v); } SSF._general = general_fmt; -function fix_hijri(date, o) { return 0; } +function fix_hijri(/*date, o*/) { return 0; } function parse_date_code(v,opts,b2) { if(v > 2958465 || v < 0) return null; var date = (v|0), time = Math.floor(86400 * (v - date)), dow=0; @@ -620,8 +618,7 @@ return "." + $1 + fill("0", r[1].length-$1.length); }); case "##,###": case "#,###": var x = commaify(""+aval); return x !== "0" ? sign + x : ""; default: - if(fmt.slice(-3) == ".00") return write_num_int(type, fmt.slice(0,-3), val) + ".00"; - if(fmt.slice(-2) == ".0") return write_num_int(type, fmt.slice(0,-2), val) + ".0"; + if(fmt.match(/\.[0#?]*$/)) return write_num_int(type, fmt.slice(0,fmt.lastIndexOf(".")), val) + hashq(fmt.slice(fmt.lastIndexOf("."))); } throw new Error("unsupported format |" + fmt + "|"); } @@ -630,8 +627,8 @@ return function write_num(type, fmt, val) { };})(); function split_fmt(fmt) { var out = []; - var in_str = false, cc; - for(var i = 0, j = 0; i < fmt.length; ++i) switch((cc=fmt.charCodeAt(i))) { + var in_str = false/*, cc*/; + for(var i = 0, j = 0; i < fmt.length; ++i) switch((/*cc=*/fmt.charCodeAt(i))) { case 34: /* '"' */ in_str = !in_str; break; case 95: case 42: case 92: /* '_' '*' '\\' */ @@ -647,11 +644,11 @@ function split_fmt(fmt) { SSF._split = split_fmt; var abstime = /\[[HhMmSs]*\]/; function fmt_is_date(fmt) { - var i = 0, cc = 0, c = "", o = ""; + var i = 0, /*cc = 0,*/ c = "", o = ""; while(i < fmt.length) { switch((c = fmt.charAt(i))) { case 'G': if(isgeneral(fmt, i)) i+= 6; i++; break; - case '"': for(;(cc=fmt.charCodeAt(++i)) !== 34 && i < fmt.length;) ++i; ++i; break; + case '"': for(;(/*cc=*/fmt.charCodeAt(++i)) !== 34 && i < fmt.length;) ++i; ++i; break; case '\\': i+=2; break; case '_': i+=2; break; case '@': ++i; break; @@ -673,13 +670,13 @@ function fmt_is_date(fmt) { case '.': /* falls through */ case '0': case '#': - while(i < fmt.length && ("0#?.,E+-%".indexOf(c=fmt.charAt(++i)) > -1 || c=='\\' && fmt.charAt(i+1) == "-" && "0#".indexOf(fmt.charAt(i+2))>-1)){} + while(i < fmt.length && ("0#?.,E+-%".indexOf(c=fmt.charAt(++i)) > -1 || (c=='\\' && fmt.charAt(i+1) == "-" && "0#".indexOf(fmt.charAt(i+2))>-1))){/* empty */} break; - case '?': while(fmt.charAt(++i) === c){} break; + case '?': while(fmt.charAt(++i) === c){/* empty */} break; case '*': ++i; if(fmt.charAt(i) == ' ' || fmt.charAt(i) == '*') ++i; break; case '(': case ')': ++i; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1){} break; + while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1){/* empty */} break; case ' ': ++i; break; default: ++i; break; } @@ -749,7 +746,7 @@ function eval_fmt(fmt, v, opts, flen) { } /* falls through */ case '0': case '#': - o = c; while(++i < fmt.length && "0#?.,E+-%".indexOf(c=fmt.charAt(i)) > -1 || c=='\\' && fmt.charAt(i+1) == "-" && i < fmt.length - 2 && "0#".indexOf(fmt.charAt(i+2))>-1) o += c; + o = c; while((++i < fmt.length && "0#?.,E+-%".indexOf(c=fmt.charAt(i)) > -1) || (c=='\\' && fmt.charAt(i+1) == "-" && i < fmt.length - 2 && "0#".indexOf(fmt.charAt(i+2))>-1)) o += c; out[out.length] = {t:'n', v:o}; break; case '?': o = c; while(fmt.charAt(++i) === c) o+=c; @@ -808,9 +805,9 @@ out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0); jj = i+1; while(out[jj] != null && ( (c=out[jj].t) === "?" || c === "D" || - (c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/') || - out[i].t === '(' && (c === ' ' || c === 'n' || c === ')') || - c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?') + ((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) || + (out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) || + (c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?')) )) { out[i].v += out[jj].v; out[jj] = {v:"", t:";"}; ++jj; @@ -864,7 +861,7 @@ out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0); if(jj>=0 && lasti-1&&i===decpt?out[i].v.indexOf(".")+1:0; vv = out[i].v.substr(0,j); for(; j 8099) return n; + if((m > 0 || d > 1) && y != 101) return o; + if(s.toLowerCase().match(/jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec/)) return o; + if(!s.match(/[a-zA-Z]/)) return o; + return n; +} + function getdatastr(data) { if(!data) return null; if(data.data) return debom(data.data); @@ -2178,11 +2188,12 @@ function aoa_to_sheet(data, opts) { for(var C = 0; C != data[R].length; ++C) { if(typeof data[R][C] === 'undefined') continue; var cell = ({v: data[R][C] }); + if(Array.isArray(cell.v)) { cell.f = data[R][C][1]; cell.v = cell.v[0]; } if(range.s.r > R) range.s.r = R; if(range.s.c > C) range.s.c = C; if(range.e.r < R) range.e.r = R; if(range.e.c < C) range.e.c = C; - if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; } + if(cell.v === null) { if(cell.f) cell.t = 'n'; else if(!o.cellStubs) continue; else cell.t = 'z'; } else if(typeof cell.v === 'number') cell.t = 'n'; else if(typeof cell.v === 'boolean') cell.t = 'b'; else if(cell.v instanceof Date) { @@ -3162,6 +3173,14 @@ function write_rdf(rdf, opts) { o.push(''); return o.join(""); } +/* TODO: pull properties */ +var write_meta_ods = (function() { + var payload = 'Sheet' + 'JS ' + XLSX.version + ''; + return function wmo(wb, opts) { + return payload; + }; +})(); + /* ECMA-376 Part II 11.1 Core Properties Part */ /* [MS-OSHARED] 2.3.3.2.[1-2].1 (PIDSI/PIDDSI) */ var CORE_PROPS = [ @@ -5217,7 +5236,7 @@ function dbf_to_aoa(buf, opts) { case 'T': var day = dd.read_shift(4), ms = dd.read_shift(4); throw new Error(day + " | " + ms); - //out[R][C] = new Date(); // FIXME!!! + //out[R][C] = new Date(); // TODO //break; case 'Y': out[R][C] = dd.read(4,'i')/1e4; break; case '0': @@ -5267,7 +5286,9 @@ var SYLK = (function() { var Mval = 0, j; for (; ri !== records.length; ++ri) { Mval = 0; - var rstr=records[ri].trim(), record=rstr.split(";"), RT=record[0], val; + var rstr=records[ri].trim(); + var record=rstr.replace(/;;/g, "\u0001").split(";").map(function(x) { return x.replace(/\u0001/g, ";"); }); + var RT=record[0], val; if(rstr.length > 0) switch(RT) { case 'ID': break; /* header */ case 'E': break; /* EOF */ @@ -5297,17 +5318,19 @@ var SYLK = (function() { next_cell_format = null; break; case 'E': - /* formula = record[rj].substr(1); */ - break; /* TODO: formula */ + formula = rc_to_a1(record[rj].substr(1), {r:R,c:C}); + arr[R][C] = [arr[R][C], formula]; + break; default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr); } break; case 'F': + var F_seen = 0; for(rj=1; rj 0) { rowinfo[R].hpt = Mval; rowinfo[R].hpx = pt2px(Mval); } else if(Mval == 0) rowinfo[R].hidden = true; break; default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr); - } break; + } + if(F_seen < 2) next_cell_format = null; break; default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr); } } @@ -5444,7 +5472,7 @@ var DIF = (function() { if(data === 'TRUE') arr[R][C] = true; else if(data === 'FALSE') arr[R][C] = false; else if(+value == +value) arr[R][C] = +value; - else if(!isNaN(new Date(value).getDate())) arr[R][C] = parseDate(value); + else if(!isNaN(fuzzydate(value).getDate())) arr[R][C] = parseDate(value); else arr[R][C] = value; ++C; break; case 1: @@ -5576,7 +5604,7 @@ var PRN = (function() { else if(s == "TRUE") { cell.t = 'b'; cell.v = true; } else if(s == "FALSE") { cell.t = 'b'; cell.v = false; } else if(!isNaN(v = +s)) { cell.t = 'n'; cell.w = s; cell.v = v; } - else if(!isNaN(new Date(s).getDate())) { cell.t = 'd'; cell.v = parseDate(s); } + else if(!isNaN(fuzzydate(s).getDate())) { cell.t = 'd'; cell.v = parseDate(s); } else { cell.t = 's'; if(s.charAt(0) == '"' && s.charAt(s.length - 1) == '"') s = s.slice(1,-1).replace(/""/g,'"'); @@ -5607,13 +5635,16 @@ var PRN = (function() { } function prn_to_sheet(d, opts) { + var str = "", bytes = firstbyte(d, opts); switch(opts.type) { - case 'base64': return prn_to_sheet_str(Base64.decode(d), opts); - case 'binary': return prn_to_sheet_str(d, opts); - case 'buffer': return prn_to_sheet_str(d.toString('binary'), opts); - case 'array': return prn_to_sheet_str(cc2str(d), opts); + case 'base64': str = Base64.decode(d); break; + case 'binary': str = d; break; + case 'buffer': str = d.toString('binary'); break; + case 'array': str = cc2str(d); break; + default: throw new Error("Unrecognized type " + opts.type); } - throw new Error("Unrecognized type " + opts.type); + if(bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) str = utf8read(str); + return prn_to_sheet_str(str, opts); } function prn_to_workbook(str, opts) { return sheet_to_workbook(prn_to_sheet(str, opts), opts); } @@ -10726,8 +10757,8 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { else { cell.t = 'n'; vv = ''+(cell.v = datenum(parseDate(cell.v))); - if(typeof cell.z === 'undefined') cell.z = SSF._table[14]; } + if(typeof cell.z === 'undefined') cell.z = SSF._table[14]; break; default: vv = cell.v; break; } @@ -10840,8 +10871,10 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { } if(tag.t == null && p.v === undefined) { - if(!opts.sheetStubs) continue; - p.t = "z"; + if(p.f || p.F) { + p.v = 0; p.t = "n"; + } else if(!opts.sheetStubs) continue; + else p.t = "z"; } else p.t = tag.t || "n"; if(guess.s.c > idx) guess.s.c = idx; @@ -12090,11 +12123,20 @@ function parse_wb_defaults(wb) { _ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904, 'date1904'); } +var badchars = "][*?\/\\".split(""); +function check_ws_name(n, safe) { + if(n.length > 31) { if(safe) return false; throw new Error("Sheet names cannot exceed 31 chars"); } + var _good = true; + badchars.forEach(function(c) { + if(n.indexOf(c) == -1) return; + if(!safe) throw new Error("Sheet name cannot contain : \\ / ? * [ ]"); + _good = false; + }); + return _good; +} function check_wb_names(N) { - var badchars = "][*?\/\\".split(""); N.forEach(function(n,i) { - badchars.forEach(function(c) { if(n.indexOf(c) > -1) throw new Error("Sheet name cannot contain : \\ / ? * [ ]"); }); - if(n.length > 31) throw new Error("Sheet names cannot exceed 31 chars"); + check_ws_name(n); for(var j = 0; j < i; ++j) if(n == N[j]) throw new Error("Duplicate Sheet Name: " + n); }); } @@ -13943,7 +13985,7 @@ function parse_workbook(blob, options) { opts.enc = val; if(opts.WTF) console.error(val); if(!options.password) throw new Error("File is password-protected"); - if(val.Type !== 0) throw new Error("Encryption scheme unsupported"); + if(val.valid == null) throw new Error("Encryption scheme unsupported"); if(!val.valid) throw new Error("Password is incorrect"); break; case 'WriteAccess': opts.lastuser = val; break; @@ -13960,7 +14002,7 @@ function parse_workbook(blob, options) { case 'Template': break; // TODO case 'RefreshAll': wb.opts.RefreshAll = val; break; case 'BookBool': break; // TODO - case 'UsesELFs': /* if(val) console.error("Unsupported ELFs"); */ break; + case 'UsesELFs': break; case 'MTRSettings': break; case 'CalcCount': wb.opts.CalcCount = val; break; case 'CalcDelta': wb.opts.CalcDelta = val; break; @@ -16051,6 +16093,7 @@ function parse_dom_table(table, _opts) { function table_to_book(table, opts) { return sheet_to_workbook(parse_dom_table(table, opts), opts); } +/* OpenDocument */ var parse_content_xml = (function() { var parse_text_p = function(text, tag) { @@ -16490,7 +16533,27 @@ var parse_content_xml = (function() { return out; }; })(); -var write_content_xml = (function() { + +function parse_ods(zip, opts) { + opts = opts || ({}); + var ods = !!safegetzipfile(zip, 'objectdata'); + if(ods) var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts); + var content = getzipstr(zip, 'content.xml'); + if(!content) throw new Error("Missing content.xml in " + (ods ? "ODS" : "UOF")+ " file"); + return parse_content_xml(ods ? content : utf8read(content), opts); +} +function parse_fods(data, opts) { + return parse_content_xml(data, opts); +} + +/* OpenDocument */ +var write_styles_ods = (function() { + var payload = ''; + return function wso(wb, opts) { + return payload; + }; +})(); +var write_content_ods = (function() { var null_cell_xml = ' \n'; var covered_cell_xml = ' \n'; var write_ws = function(ws, wb, i, opts) { @@ -16553,7 +16616,7 @@ var write_content_xml = (function() { //case 'e': default: o.push(null_cell_xml); continue; } - o.push(writextag('table:table-cell', writextag('text:p', textp, {}), ct)); + o.push(' ' + writextag('table:table-cell', writextag('text:p', textp, {}), ct) + '\n'); } o.push(' \n'); } @@ -16635,6 +16698,50 @@ var write_content_xml = (function() { return o.join(""); }; })(); + +function write_ods(wb, opts) { + if(opts.bookType == "fods") return write_content_ods(wb, opts); + +var zip = new jszip(); + var f = ""; + + var manifest = []; + var rdf = []; + + /* 3:3.3 and 2:2.2.4 */ + f = "mimetype"; + zip.file(f, "application/vnd.oasis.opendocument.spreadsheet"); + + /* Part 1 Section 2.2 Documents */ + f = "content.xml"; + zip.file(f, write_content_ods(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "ContentFile"]); + + /* TODO: these are hard-coded styles to satiate excel */ + f = "styles.xml"; + zip.file(f, write_styles_ods(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "StylesFile"]); + + /* Part 3 Section 6 Metadata Manifest File */ + f = "manifest.rdf"; + zip.file(f, write_rdf(rdf, opts)); + manifest.push([f, "application/rdf+xml"]); + + /* TODO: this is hard-coded to satiate excel */ + f = "meta.xml"; + zip.file(f, write_meta_ods(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "MetadataFile"]); + + /* Part 3 Section 4 Manifest File */ + f = "META-INF/manifest.xml"; + zip.file(f, write_manifest(manifest, opts)); + + return zip; +} + /* actual implementation elsewhere, wrappers are for read/write */ function write_obj_str(factory) { return function write_str(wb, o) { @@ -16651,49 +16758,6 @@ var write_slk_str = write_obj_str(SYLK); var write_dif_str = write_obj_str(DIF); var write_prn_str = write_obj_str(PRN); var write_txt_str = write_obj_str({from_sheet:sheet_to_txt}); -/* Part 3: Packages */ -function parse_ods(zip, opts) { - opts = opts || ({}); - var ods = !!safegetzipfile(zip, 'objectdata'); - if(ods) var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts); - var content = getzipstr(zip, 'content.xml'); - if(!content) throw new Error("Missing content.xml in " + (ods ? "ODS" : "UOF")+ " file"); - return parse_content_xml(ods ? content : utf8read(content), opts); -} -function parse_fods(data, opts) { - return parse_content_xml(data, opts); -} - -function write_ods(wb, opts) { - if(opts.bookType == "fods") return write_content_xml(wb, opts); - -var zip = new jszip(); - var f = ""; - - var manifest = []; - var rdf = []; - - /* 3:3.3 and 2:2.2.4 */ - f = "mimetype"; - zip.file(f, "application/vnd.oasis.opendocument.spreadsheet"); - - /* Part 1 Section 2.2 Documents */ - f = "content.xml"; - zip.file(f, write_content_xml(wb, opts)); - manifest.push([f, "text/xml"]); - rdf.push([f, "ContentFile"]); - - /* Part 3 Section 6 Metadata Manifest File */ - f = "manifest.rdf"; - zip.file(f, write_rdf(rdf, opts)); - manifest.push([f, "application/rdf+xml"]); - - /* Part 3 Section 4 Manifest File */ - f = "META-INF/manifest.xml"; - zip.file(f, write_manifest(manifest, opts)); - - return zip; -} function fix_opts_func(defaults) { return function fix_opts(opts) { for(var i = 0; i != defaults.length; ++i) { @@ -17129,7 +17193,7 @@ function readSync(data, opts) { case 0x49: if(n[1] == 0x44) return SYLK.to_workbook(d, o); break; case 0x54: if(n[1] == 0x41 && n[2] == 0x42 && n[3] == 0x4C) return DIF.to_workbook(d, o); break; case 0x50: if(n[1] == 0x4B && n[2] < 0x20 && n[3] < 0x20) return read_zip(d, o); break; - case 0xEF: return parse_xlml(d, o); + case 0xEF: return n[3] == 0x3C ? parse_xlml(d, o) : PRN.to_workbook(d,o); case 0xFF: if(n[1] == 0xFE){ return read_utf16(d, o); } break; case 0x00: if(n[1] == 0x00 && n[2] >= 0x02 && n[3] == 0x00) return WK_.to_workbook(d, o); break; case 0x03: case 0x83: case 0x8B: return DBF.to_workbook(d, o); @@ -17283,7 +17347,7 @@ function sheet_to_json(sheet, opts){ var outi = 0, counter = 0; var dense = Array.isArray(sheet); var R = r.s.r, C = 0, CC = 0; - if(!sheet[R]) sheet[R] = []; + if(dense && !sheet[R]) sheet[R] = []; for(C = r.s.c; C <= r.e.c; ++C) { cols[C] = encode_col(C); val = dense ? sheet[R][C] : sheet[cols[C] + rr]; @@ -17451,6 +17515,110 @@ var utils = { sheet_to_formulae: sheet_to_formulae, sheet_to_row_object_array: sheet_to_json }; + +(function(utils) { +utils.consts = utils.consts || {}; +function add_consts(R) { R.forEach(function(a){ utils.consts[a[0]] = a[1]; }); } + +function get_default(x, y, z) { return x[y] != null ? x[y] : (x[y] = z); } + +/* get cell, creating a stub if necessary */ +function ws_get_cell_stub(ws, R, C) { + /* A1 cell address */ + if(typeof R == "string") return ws[R] || (ws[R] = {t:'z'}); + /* cell address object */ + if(typeof R != "number") return ws_get_cell_stub(ws, encode_cell(R)); + /* R and C are 0-based indices */ + return ws_get_cell_stub(ws, encode_cell({r:R,c:C})); +} + +/* find sheet index for given name / validate index */ +function wb_sheet_idx(wb, sh) { + if(typeof sh == "number") { + if(sh >= 0 && wb.SheetNames.length > sh) return sh; + throw new Error("Cannot find sheet # " + sh); + } else if(typeof sh == "string") { + var idx = wb.SheetNames.indexOf(sh); + if(idx > -1) return idx; + throw new Error("Cannot find sheet name |" + sh + "|"); + } else throw new Error("Cannot find sheet |" + sh + "|"); +} + +/* simple blank workbook object */ +utils.book_new = function() { + return { SheetNames: [], Sheets: {} }; +}; + +/* add a worksheet to the end of a given workbook */ +utils.book_append_sheet = function(wb, ws, name) { + if(!name) for(var i = 1; i <= 0xFFFF; ++i) if(wb.SheetNames.indexOf("Sheet" + i) == -1) break; + check_ws_name(name); + if(wb.SheetNames.indexOf(name) >= 0) throw new Error("Worksheet with name |" + name + "| already exists!"); + + wb.SheetNames.push(name); + wb.Sheets[name] = ws; +}; + +/* set sheet visibility (visible/hidden/very hidden) */ +utils.book_set_sheet_visibility = function(wb, sh, vis) { + get_default(wb,"Workbook",{}); + get_default(wb.Workbook,"Sheets",[]); + + var idx = wb_sheet_idx(wb, sh); + get_default(wb.Workbook.Sheets,idx, {}); + + switch(vis) { + case 0: case 1: case 2: break; + default: throw new Error("Bad sheet visibility setting " + vis); + } + wb.Workbook.Sheets[idx].Hidden = vis; +}; +add_consts([ + ["SHEET_VISIBLE", 0], + ["SHEET_HIDDEN", 1], + ["SHEET_VERY_HIDDEN", 2] +]); + +/* set number format */ +utils.cell_set_number_format = function(cell, fmt) { + cell.z = fmt; + return cell; +}; + +/* set cell hyperlink */ +utils.cell_set_hyperlink = function(cell, target, tooltip) { + if(!target) { + delete cell.l; + } else { + cell.l = { Target: target }; + if(tooltip) cell.l.Tooltip = tooltip; + } + return cell; +}; + +/* add to cell comments */ +utils.cell_add_comment = function(cell, text, author) { + if(!cell.c) cell.c = []; + cell.c.push({t:text, a:author||"SheetJS"}); +}; + +/* set array formula and flush related cells */ +utils.sheet_set_array_formula = function(ws, range, formula) { + var rng = typeof range != "string" ? range : safe_decode_range(range); + var rngstr = typeof range == "string" ? range : encode_range(range); + for(var R = rng.s.r; R <= rng.e.r; ++R) for(var C = rng.s.c; C <= rng.e.c; ++C) { + var cell = ws_get_cell_stub(ws, R, C); + cell.t = 'n'; + cell.F = rngstr; + delete cell.v; + if(R == rng.s.r && C == rng.s.c) cell.f = formula; + } + return ws; +} + +return utils; +})(utils); + if(has_buf && typeof require != 'undefined') (function() { var Readable = require('stream').Readable; @@ -17513,6 +17681,7 @@ if(has_buf && typeof require != 'undefined') (function() { to_csv: write_csv_stream }; })(); + XLSX.parse_xlscfb = parse_xlscfb; XLSX.parse_ods = parse_ods; XLSX.parse_fods = parse_fods;