From 7e9f218f0f3ac0e7b404871de5eb42b62f24988e Mon Sep 17 00:00:00 2001 From: SheetJS Date: Wed, 12 Feb 2014 01:09:42 -0500 Subject: [PATCH] version bump 0.5.4: more options - cellHTML controls HTML generation - cellFormula controls formula output - sheetStubs now defaults to false - cleaned up unnecessary CSV quotes (fixed #45) - updated test_files to 20140211 - updated SSF to 0.5.7 - removed unused main function - removed some dead code --- README.md | 24 ++-- bits/10_ssf.js | 40 ++++--- bits/31_version.js | 2 +- bits/37_xlsbutils.js | 68 ------------ bits/38_recordhopper.js | 3 +- bits/39_parsestructs.js | 8 ++ bits/52_sstxml.js | 11 +- bits/54_sst.js | 4 - bits/65_comments.js | 26 ++--- bits/72_wsxml.js | 4 +- bits/73_wsbin.js | 34 ++++-- bits/79_xmlbin.js | 4 + bits/84_defaults.js | 4 +- bits/85_parsezip.js | 4 +- bits/90_utils.js | 17 +-- bits/99_footer.js | 6 - package.json | 4 +- test.js | 25 ++++- tests.lst | 8 ++ tests/files | 2 +- xlsx.js | 235 ++++++++++++++++------------------------ 21 files changed, 236 insertions(+), 297 deletions(-) delete mode 100644 bits/54_sst.js diff --git a/README.md b/README.md index 3c74a54..049129e 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,6 @@ In the browser: ## Usage -The node version installs a binary `xlsx2csv` which can read XLSX/XLSM/XLSB files and output the contents in various formats. The source is available at `xlsx2csv.njs` in the bin directory. - -See for a browser example. - -Note that older versions of IE does not support HTML5 File API, so the base64 mode is provided for testing. On OSX you can get the base64 encoding by running: - - $ for a browser example. + +Note that older versions of IE does not support HTML5 File API, so the base64 mode is provided for testing. On OSX you can get the base64 encoding by running: + + $ =d?t:(fill(c||0,d-t.length)+t);} function rpad(v,d,c){var t=String(v);return t.length>=d?t:(t+fill(c||0,d-t.length));} -SSF.version = '0.5.4'; +SSF.version = '0.5.7'; /* Options */ var opts_fmt = {}; function fixopts(o){for(var y in opts_fmt) if(o[y]===undefined) o[y]=opts_fmt[y];} @@ -147,23 +147,20 @@ var write_date = function(type, fmt, val) { switch(type) { case 'y': switch(fmt) { /* year */ case 'y': case 'yy': return pad(val.y % 100,2); - case 'yyy': case 'yyyy': return pad(val.y % 10000,4); - default: throw 'bad year format: ' + fmt; + default: return pad(val.y % 10000,4); } case 'm': switch(fmt) { /* month */ case 'm': return val.m; case 'mm': return pad(val.m,2); case 'mmm': return months[val.m-1][1]; - case 'mmmm': return months[val.m-1][2]; case 'mmmmm': return months[val.m-1][0]; - default: throw 'bad month format: ' + fmt; + default: return months[val.m-1][2]; } case 'd': switch(fmt) { /* day */ case 'd': return val.d; case 'dd': return pad(val.d,2); case 'ddd': return days[val.q][0]; - case 'dddd': return days[val.q][1]; - default: throw 'bad day format: ' + fmt; + default: return days[val.q][1]; } case 'h': switch(fmt) { /* 12-hour */ case 'h': return 1+(val.H+11)%12; @@ -196,7 +193,6 @@ var write_date = function(type, fmt, val) { } return fmt.length === 3 ? o : pad(o, 2); /* TODO: handle the ECMA spec format ee -> yy */ case 'e': { return val.y; } break; - default: throw 'bad format type ' + type + ' in ' + fmt; } }; /*jshint +W086 */ @@ -212,14 +208,20 @@ var write_num = function(type, fmt, val) { if(mul !== 0) return write_num(type, fmt, val * Math.pow(10,2*mul)) + fill("%",mul); if(fmt.indexOf("E") > -1) { var idx = fmt.indexOf("E") - fmt.indexOf(".") - 1; - if(fmt == '##0.0E+0') { - var period = fmt.length - 5; + if(fmt.match(/^#+0.0E\+0$/)) { + var period = fmt.indexOf("."); if(period === -1) period=fmt.indexOf('E'); var ee = (Number(val.toExponential(0).substr(2+(val<0))))%period; + if(ee < 0) ee += period; o = (val/Math.pow(10,ee)).toPrecision(idx+1+(period+ee)%period); if(!o.match(/[Ee]/)) { var fakee = (Number(val.toExponential(0).substr(2+(val<0)))); if(o.indexOf(".") === -1) o = o[0] + "." + o.substr(1) + "E+" + (fakee - o.length+ee); - else throw "missing E |" + o; + else o += "E+" + (fakee - ee); + while(o.substr(0,2) === "0.") { + o = o[0] + o.substr(2,period) + "." + o.substr(2+period); + o = o.replace(/^0+([1-9])/,"$1").replace(/^0+\./,"0."); + } + o = o.replace(/\+-/,"-"); } o = o.replace(/^([+-]?)([0-9]*)\.([0-9]*)[Ee]/,function($$,$1,$2,$3) { return $1 + $2 + $3.substr(0,(period+ee)%period) + "." + $3.substr(ee) + "E"; }); } else o = val.toExponential(idx); @@ -234,6 +236,7 @@ var write_num = function(type, fmt, val) { var myn = (rnd - base*den), myd = den; return sign + (base?base:"") + " " + (myn === 0 ? fill(" ", r[1].length + 1 + r[4].length) : pad(myn,r[1].length," ") + r[2] + "/" + r[3] + pad(myd,r[4].length)); } + if(fmt.match(/^#+0+$/)) fmt = fmt.replace(/#/g,""); if(fmt.match(/^00+$/)) return (val<0?"-":"")+pad(Math.round(aval),fmt.length); if(fmt.match(/^[#?]+$/)) return String(Math.round(val)).replace(/^0$/,""); if((r = fmt.match(/^#*0+\.(0+)/))) { @@ -323,7 +326,8 @@ function eval_fmt(fmt, v, opts, flen) { out.push(q); lst = c; break; case '[': /* TODO: Fix this -- ignore all conditionals and formatting */ o = c; - while(fmt[i++] !== ']') o += fmt[i]; + while(fmt[i++] !== ']' && i < fmt.length) o += fmt[i]; + if(o.substr(-1) !== ']') throw 'unterminated "[" block: |' + o + '|'; if(o.match(/\[[HhMmSs]*\]/)) { if(!dt) dt = parse_date_code(v, opts); if(!dt) return ""; @@ -344,7 +348,7 @@ function eval_fmt(fmt, v, opts, flen) { out.push({t:'D', v:o}); break; case ' ': out.push({t:c,v:c}); ++i; break; default: - if(",$-+/():!^&'~{}<>=".indexOf(c) === -1) + if(",$-+/():!^&'~{}<>=€".indexOf(c) === -1) throw 'unrecognized character ' + fmt[i] + ' in ' + fmt; out.push({t:'t', v:c}); ++i; break; } @@ -362,21 +366,20 @@ function eval_fmt(fmt, v, opts, flen) { /* replace fields */ for(i=0; i < out.length; ++i) { switch(out[i].t) { - case 't': case 'T': case ' ': break; - case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'A': case 'e': case 'Z': + case 't': case 'T': case ' ': case 'D': break; + case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'Z': out[i].v = write_date(out[i].t, out[i].v, dt); out[i].t = 't'; break; case 'n': case '(': case '?': var jj = i+1; - while(out[jj] && ("?D".indexOf(out[jj].t) > -1 || (" t".indexOf(out[jj].t) > -1 && "?t".indexOf((out[jj+1]||{}).t)>-1 && (out[jj+1].t == '?' || out[jj+1].v == '/')) || out[i].t == '(' && (out[jj].t == ')' || out[jj].t == 'n') || out[jj].t == 't' && (out[jj].v == '/' || out[jj].v == '$' || (out[jj].v == ' ' && (out[jj+1]||{}).t == '?')))) { + while(out[jj] && ("?D".indexOf(out[jj].t) > -1 || (" t".indexOf(out[jj].t) > -1 && "?t".indexOf((out[jj+1]||{}).t)>-1 && (out[jj+1].t == '?' || out[jj+1].v == '/')) || out[i].t == '(' && (out[jj].t == ')' || out[jj].t == 'n') || out[jj].t == 't' && (out[jj].v == '/' || '$€'.indexOf(out[jj].v) > -1 || (out[jj].v == ' ' && (out[jj+1]||{}).t == '?')))) { out[i].v += out[jj].v; delete out[jj]; ++jj; } - out[i].v = write_num(out[i].t, out[i].v, v); + out[i].v = write_num(out[i].t, out[i].v, (flen >1 && v < 0 && i>0 && out[i-1].v == "-" ? -v:v)); out[i].t = 't'; i = jj-1; break; case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break; - default: console.error(out); throw "unrecognized type " + out[i].t; } } return out.map(function(x){return x.v;}).join(""); @@ -386,6 +389,7 @@ function choose_fmt(fmt, v, o) { if(typeof fmt === 'number') fmt = ((o&&o.table) ? o.table : table_fmt)[fmt]; if(typeof fmt === "string") fmt = split_fmt(fmt); var l = fmt.length; + if(l<4 && fmt[l-1].indexOf("@")>-1) --l; switch(fmt.length) { case 1: fmt = fmt[0].indexOf("@")>-1 ? ["General", "General", "General", fmt[0]] : [fmt[0], fmt[0], fmt[0], "@"]; break; case 2: fmt = fmt[1].indexOf("@")>-1 ? [fmt[0], fmt[0], fmt[0], fmt[1]] : [fmt[0], fmt[1], fmt[0], "@"]; break; diff --git a/bits/31_version.js b/bits/31_version.js index e573b37..050d268 100644 --- a/bits/31_version.js +++ b/bits/31_version.js @@ -1 +1 @@ -XLSX.version = '0.5.3'; +XLSX.version = '0.5.4'; diff --git a/bits/37_xlsbutils.js b/bits/37_xlsbutils.js index 0c29061..0dd302c 100644 --- a/bits/37_xlsbutils.js +++ b/bits/37_xlsbutils.js @@ -16,40 +16,9 @@ function readIEEE754(buf, idx, isLE, nl, ml) { return (s ? -1 : 1) * m * Math.pow(2, e - ml); } -function s2a(s) { - if(typeof Buffer !== 'undefined') return new Buffer(s, "binary"); - var w = s.split("").map(function(x){ return x.charCodeAt(0) & 0xff; }); - return w; -} - var __toBuffer; if(typeof Buffer !== "undefined") { Buffer.prototype.hexlify= function() { return this.toString('hex'); }; - Buffer.prototype.utf16le= function(s,e){return this.toString('utf16le',s,e).replace(/\u0000/,'').replace(/[\u0001-\u0006]/,'!');}; - Buffer.prototype.utf8 = function(s,e) { return this.toString('utf8',s,e); }; - Buffer.prototype.lpstr = function(i) { var len = this.readUInt32LE(i); return len > 0 ? this.utf8(i+4,i+4+len-1) : "";}; - Buffer.prototype.lpwstr = function(i) { var len = 2*this.readUInt32LE(i); return this.utf8(i+4,i+4+len-1);}; - if(typeof cptable !== "undefined") Buffer.prototype.lpstr = function(i) { - var len = this.readUInt32LE(i); - if(len === 0) return ""; - if(typeof current_cptable === "undefined") return this.utf8(i+4,i+4+len-1); - var t = Array(this.slice(i+4,i+4+len-1)); - //1console.log("start", this.l, len, t); - var c, j = i+4, o = "", cc; - for(;j!=i+4+len;++j) { - c = this.readUInt8(j); - cc = current_cptable.dec[c]; - if(typeof cc === 'undefined') { - c = c*256 + this.readUInt8(++j); - cc = current_cptable.dec[c]; - } - if(typeof cc === 'undefined') throw "Unrecognized character " + c.toString(16); - if(c === 0) break; - o += cc; - //1console.log(cc, cc.charCodeAt(0), o, this.l); - } - return o; - }; __toBuffer = function(bufs) { return Buffer.concat(bufs[0]); }; } else { __toBuffer = function(bufs) { @@ -66,16 +35,6 @@ var __readUInt32LE = function(b, idx) { return b.readUInt32LE ? b.readUInt32LE(i var __readInt32LE = function(b, idx) { if(b.readInt32LE) return b.readInt32LE(idx); var u = __readUInt32LE(b,idx); if(!(u & 0x80000000)) return u; return (0xffffffff - u + 1) * -1; }; var __readDoubleLE = function(b, idx) { return b.readDoubleLE ? b.readDoubleLE(idx) : readIEEE754(b, idx||0);}; -var __hexlify = function(b) { return b.map(function(x){return (x<16?"0":"") + x.toString(16);}).join(""); }; - -var __utf16le = function(b,s,e) { if(b.utf16le) return b.utf16le(s,e); var str = ""; for(var i=s; i 0 ? __utf8(b, i+4,i+4+len-1) : "";}; -var __lpwstr = function(b,i) { if(b.lpwstr) return b.lpwstr(i); var len = 2*__readUInt32LE(b,i); return __utf8(b, i+4,i+4+len-1);}; - -function bconcat(bufs) { return (typeof Buffer !== 'undefined') ? Buffer.concat(bufs) : [].concat.apply([], bufs); } function ReadShift(size, t) { var o, w, vv, i, loc; t = t || 'u'; @@ -88,46 +47,19 @@ function ReadShift(size, t) { /* falls through */ case 16: o = this.toString('hex', this.l,this.l+size); break; - case 'utf8': size = t; o = __utf8(this, this.l, this.l + size); break; - case 'utf16le': size=2*t; o = __utf16le(this, this.l, this.l + size); break; - - /* [MS-OLEDS] 2.1.4 LengthPrefixedAnsiString */ - case 'lpstr': o = __lpstr(this, this.l); size = 5 + o.length; break; - - case 'lpwstr': o = __lpwstr(this, this.l); size = 5 + o.length; if(o[o.length-1] == '\u0000') size += 2; break; - /* sbcs and dbcs support continue records in the SST way TODO codepages */ /* TODO: DBCS http://msdn.microsoft.com/en-us/library/cc194788.aspx */ case 'dbcs': size = 2*t; o = ""; loc = this.l; for(i = 0; i != t; ++i) { - if(this.lens && this.lens.indexOf(loc) !== -1) { - w = __readUInt8(this, loc); - this.l = loc + 1; - vv = ReadShift.call(this, w ? 'dbcs' : 'sbcs', t-i); - return o + vv; - } o += _getchar(__readUInt16LE(this, loc)); loc+=2; } break; case 'sbcs': size = t; o = ""; loc = this.l; for(i = 0; i != t; ++i) { - if(this.lens && this.lens.indexOf(loc) !== -1) { - w = __readUInt8(this, loc); - this.l = loc + 1; - vv = ReadShift.call(this, w ? 'dbcs' : 'sbcs', t-i); - return o + vv; - } o += _getchar(__readUInt8(this, loc)); loc+=1; } break; - - case 'cstr': size = 0; o = ""; - while((w=__readUInt8(this, this.l + size++))!==0) o+= _getchar(w); - break; - case 'wstr': size = 0; o = ""; - while((w=__readUInt16LE(this,this.l +size))!==0){o+= _getchar(w);size+=2;} - size+=2; break; } this.l+=size; return o; } diff --git a/bits/38_recordhopper.js b/bits/38_recordhopper.js index 81be9e6..ae96e30 100644 --- a/bits/38_recordhopper.js +++ b/bits/38_recordhopper.js @@ -6,7 +6,8 @@ var recordhopper = function(data, cb) { var RT = data.read_shift(1); if(RT & 0x80) RT = (RT & 0x7F) + ((data.read_shift(1) & 0x7F)<<7); var R = RecordEnum[RT] || RecordEnum[0xFFFF]; - length = tmpbyte = data.read_shift(1); + tmpbyte = data.read_shift(1); + length = tmpbyte & 0x7F; for(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte); var d = R.f(data, length); if(cb(d, R, RT)) return; diff --git a/bits/39_parsestructs.js b/bits/39_parsestructs.js index 58b39eb..5cffb34 100644 --- a/bits/39_parsestructs.js +++ b/bits/39_parsestructs.js @@ -1,9 +1,16 @@ + +/* [MS-XLSB] 2.5.143 */ +var parse_StrRun = function(data, length) { + return { ich: data.read_shift(2), ifnt: data.read_shift(2) }; +} + /* [MS-XLSB] 2.1.7.121 */ var parse_RichStr = function(data, length) { var start = data.l; var flags = data.read_shift(1); var fRichStr = flags & 1, fExtStr = flags & 2; var str = parse_XLWideString(data); + var rgsStrRun = []; var z = { t: str, r:"" + escapexml(str) + "", @@ -12,6 +19,7 @@ var parse_RichStr = function(data, length) { if(fRichStr) { /* TODO: formatted string */ var dwSizeStrRun = data.read_shift(4); + for(var i = 0; i != dwSizeStrRun; ++i) rgsStrRun.push(parse_StrRun(data)); } if(fExtStr) { /* TODO: phonetic string */ diff --git a/bits/52_sstxml.js b/bits/52_sstxml.js index 11cb658..1a1952c 100644 --- a/bits/52_sstxml.js +++ b/bits/52_sstxml.js @@ -105,7 +105,8 @@ var parse_rs = (function() { })(); /* 18.4.8 si CT_Rst */ -var parse_si = function(x) { +var parse_si = function(x, opts) { + var html = opts ? opts.cellHTML : true; var z = {}; if(!x) return null; var y; @@ -113,14 +114,14 @@ var parse_si = function(x) { if(x[1] === 't') { z.t = utf8read(unescapexml(x.substr(x.indexOf(">")+1).split(/<\/t>/)[0])); z.r = x; - z.h = z.t; + if(html) z.h = z.t; } /* 18.4.4 r CT_RElt (Rich Text Run) */ else if((y = x.match(//))) { z.r = x; /* TODO: properly parse (note: no other valid child can have body text) */ z.t = utf8read(unescapexml(x.replace(/<[^>]*>/gm,""))); - z.h = parse_rs(x); + if(html) z.h = parse_rs(x); } /* 18.4.3 phoneticPr CT_PhoneticPr (TODO: needed for Asian support) */ /* 18.4.6 rPh CT_PhoneticRun (TODO: needed for Asian support) */ @@ -128,12 +129,12 @@ var parse_si = function(x) { }; /* 18.4 Shared String Table */ -var parse_sst_xml = function(data) { +var parse_sst_xml = function(data, opts) { var s = []; /* 18.4.9 sst CT_Sst */ var sst = data.match(new RegExp("]*)>([\\s\\S]*)<\/sst>","m")); if(isval(sst)) { - s = sst[2].replace(/<(?:si|sstItem)>/g,"").split(/<\/(?:si|sstItem)>/).map(parse_si).filter(function(x) { return x; }); + s = sst[2].replace(/<(?:si|sstItem)>/g,"").split(/<\/(?:si|sstItem)>/).map(function(x) { return parse_si(x, opts); }).filter(function(x) { return x; }); sst = parsexmltag(sst[1]); s.Count = sst.count; s.Unique = sst.uniqueCount; } return s; diff --git a/bits/54_sst.js b/bits/54_sst.js deleted file mode 100644 index e204fed..0000000 --- a/bits/54_sst.js +++ /dev/null @@ -1,4 +0,0 @@ - -var parse_sst = function(data, name) { - return name.substr(-4)===".bin" ? parse_sst_bin(data) : parse_sst_xml(data); -}; diff --git a/bits/65_comments.js b/bits/65_comments.js index 14ce374..ef4d01a 100644 --- a/bits/65_comments.js +++ b/bits/65_comments.js @@ -1,8 +1,6 @@ /* 18.7.3 CT_Comment */ -function parseComments(data) { - if(data.match(//)) { - throw new Error('Not a valid comments xml'); - } +function parse_comments_xml(data, opts) { + if(data.match(//)) return []; var authors = []; var commentList = []; data.match(/([^\u2603]*)<\/authors>/m)[1].split('').forEach(function(x) { @@ -18,26 +16,24 @@ function parseComments(data) { var rt = parse_si(textMatch[1]); comment.r = rt.r; comment.t = rt.t; - comment.h = rt.h; + if(opts.cellHTML) comment.h = rt.h; commentList.push(comment); }); return commentList; } -function parseCommentsAddToSheets(zip, dirComments, sheets, sheetRels) { +function parse_comments(zip, dirComments, sheets, sheetRels, opts) { for(var i = 0; i != dirComments.length; ++i) { var canonicalpath=dirComments[i]; - var comments=parseComments(getdata(getzipfile(zip, canonicalpath.replace(/^\//,'')))); + var comments=parse_comments_xml(getdata(getzipfile(zip, canonicalpath.replace(/^\//,''))), opts); // find the sheets targeted by these comments var sheetNames = Object.keys(sheets); for(var j = 0; j != sheetNames.length; ++j) { var sheetName = sheetNames[j]; var rels = sheetRels[sheetName]; - if (rels) { + if(rels) { var rel = rels[canonicalpath]; - if (rel) { - insertCommentsIntoSheet(sheetName, sheets[sheetName], comments); - } + if(rel) insertCommentsIntoSheet(sheetName, sheets[sheetName], comments); } } } @@ -59,10 +55,10 @@ function insertCommentsIntoSheet(sheetName, sheet, comments) { if (encoded !== sheet["!ref"]) sheet["!ref"] = encoded; } - if (!cell.c) { - cell.c = []; - } - cell.c.push({a: comment.author, t: comment.t, r: comment.r, h: comment.h}); + if (!cell.c) cell.c = []; + var o = {a: comment.author, t: comment.t, r: comment.r}; + if(comment.h) o.h = comment.h; + cell.c.push(o); }); } diff --git a/bits/72_wsxml.js b/bits/72_wsxml.js index 5295de5..eed57c0 100644 --- a/bits/72_wsxml.js +++ b/bits/72_wsxml.js @@ -37,7 +37,7 @@ function parse_ws_xml(data, opts) { /* SCHEMA IS ACTUALLY INCORRECT HERE. IF A CELL HAS NO T, EMIT "" */ if(cell.t === undefined && p.v === undefined) { - if(!opts.sheetEmptyCells) return; + if(!opts.sheetStubs) return; p.t = "str"; p.v = undefined; } else p.t = (cell.t ? cell.t : "n"); // default is "n" in schema @@ -49,7 +49,7 @@ function parse_ws_xml(data, opts) { sidx = parseInt(p.v, 10); p.v = strs[sidx].t; p.r = strs[sidx].r; - p.h = strs[sidx].h; + if(opts.cellHTML) p.h = strs[sidx].h; } break; case 'str': if(p.v) p.v = utf8read(p.v); break; case 'inlineStr': diff --git a/bits/73_wsbin.js b/bits/73_wsbin.js index 5a8411f..658878d 100644 --- a/bits/73_wsbin.js +++ b/bits/73_wsbin.js @@ -60,6 +60,14 @@ var parse_BrtCellSt = function(data, length) { return [cell, value, 'str']; }; +/* [MS-XLSB] 2.4.647 BrtFmlaBool */ +var parse_BrtFmlaBool = function(data, length) { + var cell = parse_Cell(data); + var value = data.read_shift(1); + data.l += length-9; + return [cell, value, 'b' /*, formula */]; +}; + /* [MS-XLSB] 2.4.648 BrtFmlaError */ var parse_BrtFmlaError = function(data, length) { var cell = parse_Cell(data); @@ -76,9 +84,16 @@ var parse_BrtFmlaNum = function(data, length) { return [cell, value, 'n' /*, formula */]; }; +/* [MS-XLSB] 2.4.650 BrtFmlaString */ +var parse_BrtFmlaString = function(data, length) { + var start = data.l; + var cell = parse_Cell(data); + var value = parse_XLWideString(data); + data.l = start + length; + return [cell, value, 'str' /*, formula */]; +}; + var parse_BrtCellBlank = parsenoop; -var parse_BrtFmlaBool = parsenoop; -var parse_BrtFmlaString = parsenoop; /* [MS-XLSB] 2.1.7.61 Worksheet */ var parse_ws_bin = function(data, opts) { @@ -92,15 +107,12 @@ var parse_ws_bin = function(data, opts) { recordhopper(data, function(val, R) { switch(R.n) { case 'BrtWsDim': ref = val; break; + case 'BrtRowHdr': row = val; break; - case 'BrtRowHdr': - row = val; - break; // TODO - - case 'BrtFmlaError': break; // TODO - case 'BrtFmlaString': break; // TODO - case 'BrtFmlaBool': break; // TODO + case 'BrtFmlaBool': + case 'BrtFmlaError': case 'BrtFmlaNum': + case 'BrtFmlaString': case 'BrtCellBool': case 'BrtCellError': case 'BrtCellIsst': @@ -112,9 +124,9 @@ var parse_ws_bin = function(data, opts) { case 's': p.v = strs[val[1]].t; p.r = strs[val[1]].r; break; case 'b': p.v = val[1] ? true : false; break; case 'e': p.raw = val[1]; p.v = BErr[p.raw]; break; - case 'str': if(p.v) p.v = utf8read(p.v); break; + case 'str': p.v = utf8read(val[1]); break; } - if(val[3]) p.f = val[3]; + if(val[3] && opts.cellFormula) p.f = val[3]; if((cf = styles.CellXf[val[0].iStyleRef])) try { p.w = SSF.format(cf.ifmt,p.v,_ssfopts); if(opts.cellNF) p.z = SSF._table[cf.ifmt]; diff --git a/bits/79_xmlbin.js b/bits/79_xmlbin.js index 3927460..03da3e9 100644 --- a/bits/79_xmlbin.js +++ b/bits/79_xmlbin.js @@ -9,3 +9,7 @@ function parse_ws(data, name, opts) { function parse_sty(data, name, opts) { return name.substr(-4)===".bin" ? parse_sty_bin(data, opts) : parse_sty_xml(data, opts); } + +function parse_sst(data, name, opts) { + return name.substr(-4)===".bin" ? parse_sst_bin(data, opts) : parse_sst_xml(data, opts); +} diff --git a/bits/84_defaults.js b/bits/84_defaults.js index 17ea03f..aa15f81 100644 --- a/bits/84_defaults.js +++ b/bits/84_defaults.js @@ -1,8 +1,10 @@ function fixopts(opts) { var defaults = [ ['cellNF', false], /* emit cell number format string as .z */ + ['cellHTML', true], /* emit html string as .h */ + ['cellFormula', true], /* emit formulae as .h */ - ['sheetStubs', true], /* emit empty cells */ + ['sheetStubs', false], /* emit empty cells */ ['WTF', false] /* WTF mode (do not use) */ ]; diff --git a/bits/85_parsezip.js b/bits/85_parsezip.js index f95cda8..7dee0fe 100644 --- a/bits/85_parsezip.js +++ b/bits/85_parsezip.js @@ -13,7 +13,7 @@ function parseZip(zip, opts) { xlsb = true; } strs = {}; - if(dir.sst) strs=parse_sst(getdata(getzipfile(zip, dir.sst.replace(/^\//,''))), dir.sst); + if(dir.sst) strs=parse_sst(getdata(getzipfile(zip, dir.sst.replace(/^\//,''))), dir.sst, opts); styles = {}; if(dir.style) styles = parse_sty(getdata(getzipfile(zip, dir.style.replace(/^\//,''))),dir.style); @@ -58,7 +58,7 @@ function parseZip(zip, opts) { } } - if(dir.comments) parseCommentsAddToSheets(zip, dir.comments, sheets, sheetRels); + if(dir.comments) parse_comments(zip, dir.comments, sheets, sheetRels, opts); return { Directory: dir, diff --git a/bits/90_utils.js b/bits/90_utils.js index 2ae2f34..a20a188 100644 --- a/bits/90_utils.js +++ b/bits/90_utils.js @@ -12,7 +12,7 @@ function decode_range(range) { var x =range.split(":").map(decode_cell); return function encode_range(range) { return encode_cell(range.s) + ":" + encode_cell(range.e); } function sheet_to_row_object_array(sheet, opts){ - var val, row, r, hdr = {}, isempty, R, C, v; + var val, row, r, hdr = {}, isempty, R, C; var out = []; opts = opts || {}; if(!sheet || !sheet["!ref"]) return out; @@ -54,12 +54,10 @@ function sheet_to_row_object_array(sheet, opts){ function sheet_to_csv(sheet, opts) { var stringify = function stringify(val) { if(!val.t) return ""; - if(typeof val.w !== 'undefined') return '"' + val.w.replace(/"/,'""') + '"'; + if(typeof val.w !== 'undefined') return val.w; switch(val.t){ case 'n': return String(val.v); - case 's': case 'str': - if(typeof val.v === 'undefined') return ""; - return '"' + val.v.replace(/"/,'""') + '"'; + case 's': case 'str': return typeof val.v !== 'undefined' ? val.v : ""; case 'b': return val.v ? "TRUE" : "FALSE"; case 'e': return val.v; /* throw out value in case of error */ default: throw 'unrecognized type ' + val.t; @@ -69,15 +67,18 @@ function sheet_to_csv(sheet, opts) { opts = opts || {}; if(!sheet || !sheet["!ref"]) return out; var r = XLSX.utils.decode_range(sheet["!ref"]); + var fs = opts.FS||",", rs = opts.RS||"\n"; for(var R = r.s.r; R <= r.e.r; ++R) { var row = []; for(var C = r.s.c; C <= r.e.c; ++C) { var val = sheet[XLSX.utils.encode_cell({c:C,r:R})]; if(!val) { row.push(""); continue; } - txt = stringify(val); - row.push(String(txt).replace(/\\r\\n/g,"\n").replace(/\\t/g,"\t").replace(/\\\\/g,"\\").replace("\\\"","\"\"")); + txt = String(stringify(val)); + if(txt.indexOf(fs)!==-1 || txt.indexOf(rs)!==-1 || txt.indexOf('"')!==-1) + txt = "\"" + txt.replace(/"/g, '""') + "\""; + row.push(txt); } - out += row.join(opts.FS||",") + (opts.RS||"\n"); + out += row.join(fs) + (rs); } return out; } diff --git a/bits/99_footer.js b/bits/99_footer.js index 2b56a9b..904f35c 100644 --- a/bits/99_footer.js +++ b/bits/99_footer.js @@ -4,10 +4,4 @@ if(typeof require !== 'undefined' && typeof exports !== 'undefined') { exports.readFile = XLSX.readFile; exports.utils = XLSX.utils; exports.version = XLSX.version; - exports.main = function(args) { - var zip = XLSX.read(args[0], {type:'file'}); - console.log(zip.Sheets); - }; -if(typeof module !== 'undefined' && require.main === module) - exports.main(process.argv.slice(2)); } diff --git a/package.json b/package.json index f6bb8a7..2dac6c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xlsx", - "version": "0.5.3", + "version": "0.5.4", "author": "sheetjs", "description": "XLSB / XLSX / XLSM parser", "keywords": [ "xlsx", "xlsb", "xlsm", "office", "excel", "spreadsheet" ], @@ -9,7 +9,7 @@ }, "main": "./xlsx", "dependencies": { - "ssf":">=0.5.5", + "ssf":"~0.5.6", "codepage":"", "jszip":"~2.1.0", "commander":"" diff --git a/test.js b/test.js index 2c97c41..07bed7b 100644 --- a/test.js +++ b/test.js @@ -4,7 +4,7 @@ var fs = require('fs'), assert = require('assert'); describe('source',function(){ it('should load', function(){ XLSX = require('./'); });}); var ex = [".xlsb", ".xlsm", ".xlsx"]; -var exp = ex.map(function(x){ return x + ".pending"; }); +var exp = ex.map(function(x){ return x + ".pending"; }); function test_file(x){return ex.indexOf(x.substr(-5))>=0||exp.indexOf(x.substr(-13))>=0;} var files = (fs.existsSync('tests.lst') ? fs.readFileSync('tests.lst', 'utf-8').split("\n") : fs.readdirSync('test_files')).filter(test_file); @@ -84,3 +84,26 @@ describe('should have comment as part of cell\'s properties', function(){ assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author"); }); }); + +describe('options', function() { + var html_cell_types = ['s']; + before(function() { + XLSX = require('./'); + }); + it('should generate HTML by default', function() { + var wb = XLSX.readFile('./test_files/comments_stress_test.xlsx'); + var ws = wb.Sheets.Sheet1; + Object.keys(ws).forEach(function(addr) { + if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; + assert(html_cell_types.indexOf(ws[addr].t) === -1 || ws[addr].h); + }); + }); + it('should not generate HTML when requested', function() { + var wb = XLSX.readFile('./test_files/comments_stress_test.xlsx', {cellHTML: false}); + var ws = wb.Sheets.Sheet1; + Object.keys(ws).forEach(function(addr) { + if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; + assert(typeof ws[addr].h === 'undefined'); + }); + }); +}); diff --git a/tests.lst b/tests.lst index 354b3ff..e65b2c3 100644 --- a/tests.lst +++ b/tests.lst @@ -1,10 +1,16 @@ +RkNumber.xlsb +comments_stress_test.xlsb formula_stress_test.xlsb +large_strings.xlsb.pending +merge_cells.xlsb +named_ranges_2011.xlsb number_format.xlsb rich_text_stress.xlsb time_stress_test_1.xlsb LONumbers-2010.xlsx LONumbers-2011.xlsx LONumbers.xlsx +RkNumber.xlsx apachepoi_45430.xlsx apachepoi_45540_classic_Footer.xlsx apachepoi_45540_classic_Header.xlsx @@ -153,6 +159,7 @@ jxls-core_simple.xlsx jxls-examples_stress1.xlsx jxls-examples_stress2.xlsx jxls-reader_departmentData.xlsx +large_strings.xlsx.pending merge_cells.xlsx mixed_sheets.xlsx named_ranges_2011.xlsx @@ -166,6 +173,7 @@ openpyxl_g_empty_with_no_properties.xlsx openpyxl_g_guess_types.xlsx openpyxl_g_libreoffice_nrt.xlsx openpyxl_g_merge_range.xlsx +openpyxl_r_bigfoot.xlsx.pending openpyxl_r_comments.xlsx openpyxl_r_complex-styles.xlsx openpyxl_r_conditional-formatting.xlsx diff --git a/tests/files b/tests/files index a2d49d3..709d865 160000 --- a/tests/files +++ b/tests/files @@ -1 +1 @@ -Subproject commit a2d49d344622e1bd657f03e829c24d9b24a942af +Subproject commit 709d865dc7f9d7173bc4b5f5c6b0c5aea945589c diff --git a/xlsx.js b/xlsx.js index 95da242..30ebfb2 100644 --- a/xlsx.js +++ b/xlsx.js @@ -9,7 +9,7 @@ var _strrev = function(x) { return String(x).split("").reverse().join("");}; function fill(c,l) { return new Array(l+1).join(c); } function pad(v,d,c){var t=String(v);return t.length>=d?t:(fill(c||0,d-t.length)+t);} function rpad(v,d,c){var t=String(v);return t.length>=d?t:(t+fill(c||0,d-t.length));} -SSF.version = '0.5.4'; +SSF.version = '0.5.7'; /* Options */ var opts_fmt = {}; function fixopts(o){for(var y in opts_fmt) if(o[y]===undefined) o[y]=opts_fmt[y];} @@ -150,23 +150,20 @@ var write_date = function(type, fmt, val) { switch(type) { case 'y': switch(fmt) { /* year */ case 'y': case 'yy': return pad(val.y % 100,2); - case 'yyy': case 'yyyy': return pad(val.y % 10000,4); - default: throw 'bad year format: ' + fmt; + default: return pad(val.y % 10000,4); } case 'm': switch(fmt) { /* month */ case 'm': return val.m; case 'mm': return pad(val.m,2); case 'mmm': return months[val.m-1][1]; - case 'mmmm': return months[val.m-1][2]; case 'mmmmm': return months[val.m-1][0]; - default: throw 'bad month format: ' + fmt; + default: return months[val.m-1][2]; } case 'd': switch(fmt) { /* day */ case 'd': return val.d; case 'dd': return pad(val.d,2); case 'ddd': return days[val.q][0]; - case 'dddd': return days[val.q][1]; - default: throw 'bad day format: ' + fmt; + default: return days[val.q][1]; } case 'h': switch(fmt) { /* 12-hour */ case 'h': return 1+(val.H+11)%12; @@ -199,7 +196,6 @@ var write_date = function(type, fmt, val) { } return fmt.length === 3 ? o : pad(o, 2); /* TODO: handle the ECMA spec format ee -> yy */ case 'e': { return val.y; } break; - default: throw 'bad format type ' + type + ' in ' + fmt; } }; /*jshint +W086 */ @@ -215,14 +211,20 @@ var write_num = function(type, fmt, val) { if(mul !== 0) return write_num(type, fmt, val * Math.pow(10,2*mul)) + fill("%",mul); if(fmt.indexOf("E") > -1) { var idx = fmt.indexOf("E") - fmt.indexOf(".") - 1; - if(fmt == '##0.0E+0') { - var period = fmt.length - 5; + if(fmt.match(/^#+0.0E\+0$/)) { + var period = fmt.indexOf("."); if(period === -1) period=fmt.indexOf('E'); var ee = (Number(val.toExponential(0).substr(2+(val<0))))%period; + if(ee < 0) ee += period; o = (val/Math.pow(10,ee)).toPrecision(idx+1+(period+ee)%period); if(!o.match(/[Ee]/)) { var fakee = (Number(val.toExponential(0).substr(2+(val<0)))); if(o.indexOf(".") === -1) o = o[0] + "." + o.substr(1) + "E+" + (fakee - o.length+ee); - else throw "missing E |" + o; + else o += "E+" + (fakee - ee); + while(o.substr(0,2) === "0.") { + o = o[0] + o.substr(2,period) + "." + o.substr(2+period); + o = o.replace(/^0+([1-9])/,"$1").replace(/^0+\./,"0."); + } + o = o.replace(/\+-/,"-"); } o = o.replace(/^([+-]?)([0-9]*)\.([0-9]*)[Ee]/,function($$,$1,$2,$3) { return $1 + $2 + $3.substr(0,(period+ee)%period) + "." + $3.substr(ee) + "E"; }); } else o = val.toExponential(idx); @@ -237,6 +239,7 @@ var write_num = function(type, fmt, val) { var myn = (rnd - base*den), myd = den; return sign + (base?base:"") + " " + (myn === 0 ? fill(" ", r[1].length + 1 + r[4].length) : pad(myn,r[1].length," ") + r[2] + "/" + r[3] + pad(myd,r[4].length)); } + if(fmt.match(/^#+0+$/)) fmt = fmt.replace(/#/g,""); if(fmt.match(/^00+$/)) return (val<0?"-":"")+pad(Math.round(aval),fmt.length); if(fmt.match(/^[#?]+$/)) return String(Math.round(val)).replace(/^0$/,""); if((r = fmt.match(/^#*0+\.(0+)/))) { @@ -326,7 +329,8 @@ function eval_fmt(fmt, v, opts, flen) { out.push(q); lst = c; break; case '[': /* TODO: Fix this -- ignore all conditionals and formatting */ o = c; - while(fmt[i++] !== ']') o += fmt[i]; + while(fmt[i++] !== ']' && i < fmt.length) o += fmt[i]; + if(o.substr(-1) !== ']') throw 'unterminated "[" block: |' + o + '|'; if(o.match(/\[[HhMmSs]*\]/)) { if(!dt) dt = parse_date_code(v, opts); if(!dt) return ""; @@ -347,7 +351,7 @@ function eval_fmt(fmt, v, opts, flen) { out.push({t:'D', v:o}); break; case ' ': out.push({t:c,v:c}); ++i; break; default: - if(",$-+/():!^&'~{}<>=".indexOf(c) === -1) + if(",$-+/():!^&'~{}<>=€".indexOf(c) === -1) throw 'unrecognized character ' + fmt[i] + ' in ' + fmt; out.push({t:'t', v:c}); ++i; break; } @@ -365,21 +369,20 @@ function eval_fmt(fmt, v, opts, flen) { /* replace fields */ for(i=0; i < out.length; ++i) { switch(out[i].t) { - case 't': case 'T': case ' ': break; - case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'A': case 'e': case 'Z': + case 't': case 'T': case ' ': case 'D': break; + case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'Z': out[i].v = write_date(out[i].t, out[i].v, dt); out[i].t = 't'; break; case 'n': case '(': case '?': var jj = i+1; - while(out[jj] && ("?D".indexOf(out[jj].t) > -1 || (" t".indexOf(out[jj].t) > -1 && "?t".indexOf((out[jj+1]||{}).t)>-1 && (out[jj+1].t == '?' || out[jj+1].v == '/')) || out[i].t == '(' && (out[jj].t == ')' || out[jj].t == 'n') || out[jj].t == 't' && (out[jj].v == '/' || out[jj].v == '$' || (out[jj].v == ' ' && (out[jj+1]||{}).t == '?')))) { + while(out[jj] && ("?D".indexOf(out[jj].t) > -1 || (" t".indexOf(out[jj].t) > -1 && "?t".indexOf((out[jj+1]||{}).t)>-1 && (out[jj+1].t == '?' || out[jj+1].v == '/')) || out[i].t == '(' && (out[jj].t == ')' || out[jj].t == 'n') || out[jj].t == 't' && (out[jj].v == '/' || '$€'.indexOf(out[jj].v) > -1 || (out[jj].v == ' ' && (out[jj+1]||{}).t == '?')))) { out[i].v += out[jj].v; delete out[jj]; ++jj; } - out[i].v = write_num(out[i].t, out[i].v, v); + out[i].v = write_num(out[i].t, out[i].v, (flen >1 && v < 0 && i>0 && out[i-1].v == "-" ? -v:v)); out[i].t = 't'; i = jj-1; break; case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break; - default: console.error(out); throw "unrecognized type " + out[i].t; } } return out.map(function(x){return x.v;}).join(""); @@ -389,6 +392,7 @@ function choose_fmt(fmt, v, o) { if(typeof fmt === 'number') fmt = ((o&&o.table) ? o.table : table_fmt)[fmt]; if(typeof fmt === "string") fmt = split_fmt(fmt); var l = fmt.length; + if(l<4 && fmt[l-1].indexOf("@")>-1) --l; switch(fmt.length) { case 1: fmt = fmt[0].indexOf("@")>-1 ? ["General", "General", "General", fmt[0]] : [fmt[0], fmt[0], fmt[0], "@"]; break; case 2: fmt = fmt[1].indexOf("@")>-1 ? [fmt[0], fmt[0], fmt[0], fmt[1]] : [fmt[0], fmt[1], fmt[0], "@"]; break; @@ -420,7 +424,7 @@ SSF.load_table = function(tbl) { for(var i=0; i!=0x0188; ++i) if(tbl[i]) SSF.loa make_ssf(SSF); var XLSX = {}; (function(XLSX){ -XLSX.version = '0.5.3'; +XLSX.version = '0.5.4'; var current_codepage, current_cptable, cptable; if(typeof module !== "undefined" && typeof require !== 'undefined') { if(typeof cptable === 'undefined') cptable = require('codepage'); @@ -560,40 +564,9 @@ function readIEEE754(buf, idx, isLE, nl, ml) { return (s ? -1 : 1) * m * Math.pow(2, e - ml); } -function s2a(s) { - if(typeof Buffer !== 'undefined') return new Buffer(s, "binary"); - var w = s.split("").map(function(x){ return x.charCodeAt(0) & 0xff; }); - return w; -} - var __toBuffer; if(typeof Buffer !== "undefined") { Buffer.prototype.hexlify= function() { return this.toString('hex'); }; - Buffer.prototype.utf16le= function(s,e){return this.toString('utf16le',s,e).replace(/\u0000/,'').replace(/[\u0001-\u0006]/,'!');}; - Buffer.prototype.utf8 = function(s,e) { return this.toString('utf8',s,e); }; - Buffer.prototype.lpstr = function(i) { var len = this.readUInt32LE(i); return len > 0 ? this.utf8(i+4,i+4+len-1) : "";}; - Buffer.prototype.lpwstr = function(i) { var len = 2*this.readUInt32LE(i); return this.utf8(i+4,i+4+len-1);}; - if(typeof cptable !== "undefined") Buffer.prototype.lpstr = function(i) { - var len = this.readUInt32LE(i); - if(len === 0) return ""; - if(typeof current_cptable === "undefined") return this.utf8(i+4,i+4+len-1); - var t = Array(this.slice(i+4,i+4+len-1)); - //1console.log("start", this.l, len, t); - var c, j = i+4, o = "", cc; - for(;j!=i+4+len;++j) { - c = this.readUInt8(j); - cc = current_cptable.dec[c]; - if(typeof cc === 'undefined') { - c = c*256 + this.readUInt8(++j); - cc = current_cptable.dec[c]; - } - if(typeof cc === 'undefined') throw "Unrecognized character " + c.toString(16); - if(c === 0) break; - o += cc; - //1console.log(cc, cc.charCodeAt(0), o, this.l); - } - return o; - }; __toBuffer = function(bufs) { return Buffer.concat(bufs[0]); }; } else { __toBuffer = function(bufs) { @@ -610,16 +583,6 @@ var __readUInt32LE = function(b, idx) { return b.readUInt32LE ? b.readUInt32LE(i var __readInt32LE = function(b, idx) { if(b.readInt32LE) return b.readInt32LE(idx); var u = __readUInt32LE(b,idx); if(!(u & 0x80000000)) return u; return (0xffffffff - u + 1) * -1; }; var __readDoubleLE = function(b, idx) { return b.readDoubleLE ? b.readDoubleLE(idx) : readIEEE754(b, idx||0);}; -var __hexlify = function(b) { return b.map(function(x){return (x<16?"0":"") + x.toString(16);}).join(""); }; - -var __utf16le = function(b,s,e) { if(b.utf16le) return b.utf16le(s,e); var str = ""; for(var i=s; i 0 ? __utf8(b, i+4,i+4+len-1) : "";}; -var __lpwstr = function(b,i) { if(b.lpwstr) return b.lpwstr(i); var len = 2*__readUInt32LE(b,i); return __utf8(b, i+4,i+4+len-1);}; - -function bconcat(bufs) { return (typeof Buffer !== 'undefined') ? Buffer.concat(bufs) : [].concat.apply([], bufs); } function ReadShift(size, t) { var o, w, vv, i, loc; t = t || 'u'; @@ -632,46 +595,19 @@ function ReadShift(size, t) { /* falls through */ case 16: o = this.toString('hex', this.l,this.l+size); break; - case 'utf8': size = t; o = __utf8(this, this.l, this.l + size); break; - case 'utf16le': size=2*t; o = __utf16le(this, this.l, this.l + size); break; - - /* [MS-OLEDS] 2.1.4 LengthPrefixedAnsiString */ - case 'lpstr': o = __lpstr(this, this.l); size = 5 + o.length; break; - - case 'lpwstr': o = __lpwstr(this, this.l); size = 5 + o.length; if(o[o.length-1] == '\u0000') size += 2; break; - /* sbcs and dbcs support continue records in the SST way TODO codepages */ /* TODO: DBCS http://msdn.microsoft.com/en-us/library/cc194788.aspx */ case 'dbcs': size = 2*t; o = ""; loc = this.l; for(i = 0; i != t; ++i) { - if(this.lens && this.lens.indexOf(loc) !== -1) { - w = __readUInt8(this, loc); - this.l = loc + 1; - vv = ReadShift.call(this, w ? 'dbcs' : 'sbcs', t-i); - return o + vv; - } o += _getchar(__readUInt16LE(this, loc)); loc+=2; } break; case 'sbcs': size = t; o = ""; loc = this.l; for(i = 0; i != t; ++i) { - if(this.lens && this.lens.indexOf(loc) !== -1) { - w = __readUInt8(this, loc); - this.l = loc + 1; - vv = ReadShift.call(this, w ? 'dbcs' : 'sbcs', t-i); - return o + vv; - } o += _getchar(__readUInt8(this, loc)); loc+=1; } break; - - case 'cstr': size = 0; o = ""; - while((w=__readUInt8(this, this.l + size++))!==0) o+= _getchar(w); - break; - case 'wstr': size = 0; o = ""; - while((w=__readUInt16LE(this,this.l +size))!==0){o+= _getchar(w);size+=2;} - size+=2; break; } this.l+=size; return o; } @@ -692,18 +628,26 @@ var recordhopper = function(data, cb) { var RT = data.read_shift(1); if(RT & 0x80) RT = (RT & 0x7F) + ((data.read_shift(1) & 0x7F)<<7); var R = RecordEnum[RT] || RecordEnum[0xFFFF]; - length = tmpbyte = data.read_shift(1); + tmpbyte = data.read_shift(1); + length = tmpbyte & 0x7F; for(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte); var d = R.f(data, length); if(cb(d, R, RT)) return; } }; + +/* [MS-XLSB] 2.5.143 */ +var parse_StrRun = function(data, length) { + return { ich: data.read_shift(2), ifnt: data.read_shift(2) }; +} + /* [MS-XLSB] 2.1.7.121 */ var parse_RichStr = function(data, length) { var start = data.l; var flags = data.read_shift(1); var fRichStr = flags & 1, fExtStr = flags & 2; var str = parse_XLWideString(data); + var rgsStrRun = []; var z = { t: str, r:"" + escapexml(str) + "", @@ -712,6 +656,7 @@ var parse_RichStr = function(data, length) { if(fRichStr) { /* TODO: formatted string */ var dwSizeStrRun = data.read_shift(4); + for(var i = 0; i != dwSizeStrRun; ++i) rgsStrRun.push(parse_StrRun(data)); } if(fExtStr) { /* TODO: phonetic string */ @@ -891,7 +836,8 @@ var parse_rs = (function() { })(); /* 18.4.8 si CT_Rst */ -var parse_si = function(x) { +var parse_si = function(x, opts) { + var html = opts ? opts.cellHTML : true; var z = {}; if(!x) return null; var y; @@ -899,14 +845,14 @@ var parse_si = function(x) { if(x[1] === 't') { z.t = utf8read(unescapexml(x.substr(x.indexOf(">")+1).split(/<\/t>/)[0])); z.r = x; - z.h = z.t; + if(html) z.h = z.t; } /* 18.4.4 r CT_RElt (Rich Text Run) */ else if((y = x.match(//))) { z.r = x; /* TODO: properly parse (note: no other valid child can have body text) */ z.t = utf8read(unescapexml(x.replace(/<[^>]*>/gm,""))); - z.h = parse_rs(x); + if(html) z.h = parse_rs(x); } /* 18.4.3 phoneticPr CT_PhoneticPr (TODO: needed for Asian support) */ /* 18.4.6 rPh CT_PhoneticRun (TODO: needed for Asian support) */ @@ -914,12 +860,12 @@ var parse_si = function(x) { }; /* 18.4 Shared String Table */ -var parse_sst_xml = function(data) { +var parse_sst_xml = function(data, opts) { var s = []; /* 18.4.9 sst CT_Sst */ var sst = data.match(new RegExp("]*)>([\\s\\S]*)<\/sst>","m")); if(isval(sst)) { - s = sst[2].replace(/<(?:si|sstItem)>/g,"").split(/<\/(?:si|sstItem)>/).map(parse_si).filter(function(x) { return x; }); + s = sst[2].replace(/<(?:si|sstItem)>/g,"").split(/<\/(?:si|sstItem)>/).map(function(x) { return parse_si(x, opts); }).filter(function(x) { return x; }); sst = parsexmltag(sst[1]); s.Count = sst.count; s.Unique = sst.uniqueCount; } return s; @@ -943,10 +889,6 @@ var parse_sst_bin = function(data) { }); return s; }; - -var parse_sst = function(data, name) { - return name.substr(-4)===".bin" ? parse_sst_bin(data) : parse_sst_xml(data); -}; var styles = {}; // shared styles /* 18.8.31 numFmts CT_NumFmts */ @@ -1230,10 +1172,8 @@ function parseRels(data, currentFilePath) { /* 18.7.3 CT_Comment */ -function parseComments(data) { - if(data.match(//)) { - throw new Error('Not a valid comments xml'); - } +function parse_comments_xml(data, opts) { + if(data.match(//)) return []; var authors = []; var commentList = []; data.match(/([^\u2603]*)<\/authors>/m)[1].split('').forEach(function(x) { @@ -1249,26 +1189,24 @@ function parseComments(data) { var rt = parse_si(textMatch[1]); comment.r = rt.r; comment.t = rt.t; - comment.h = rt.h; + if(opts.cellHTML) comment.h = rt.h; commentList.push(comment); }); return commentList; } -function parseCommentsAddToSheets(zip, dirComments, sheets, sheetRels) { +function parse_comments(zip, dirComments, sheets, sheetRels, opts) { for(var i = 0; i != dirComments.length; ++i) { var canonicalpath=dirComments[i]; - var comments=parseComments(getdata(getzipfile(zip, canonicalpath.replace(/^\//,'')))); + var comments=parse_comments_xml(getdata(getzipfile(zip, canonicalpath.replace(/^\//,''))), opts); // find the sheets targeted by these comments var sheetNames = Object.keys(sheets); for(var j = 0; j != sheetNames.length; ++j) { var sheetName = sheetNames[j]; var rels = sheetRels[sheetName]; - if (rels) { + if(rels) { var rel = rels[canonicalpath]; - if (rel) { - insertCommentsIntoSheet(sheetName, sheets[sheetName], comments); - } + if(rel) insertCommentsIntoSheet(sheetName, sheets[sheetName], comments); } } } @@ -1290,10 +1228,10 @@ function insertCommentsIntoSheet(sheetName, sheet, comments) { if (encoded !== sheet["!ref"]) sheet["!ref"] = encoded; } - if (!cell.c) { - cell.c = []; - } - cell.c.push({a: comment.author, t: comment.t, r: comment.r, h: comment.h}); + if (!cell.c) cell.c = []; + var o = {a: comment.author, t: comment.t, r: comment.r}; + if(comment.h) o.h = comment.h; + cell.c.push(o); }); } @@ -1339,7 +1277,7 @@ function parse_ws_xml(data, opts) { /* SCHEMA IS ACTUALLY INCORRECT HERE. IF A CELL HAS NO T, EMIT "" */ if(cell.t === undefined && p.v === undefined) { - if(!opts.sheetEmptyCells) return; + if(!opts.sheetStubs) return; p.t = "str"; p.v = undefined; } else p.t = (cell.t ? cell.t : "n"); // default is "n" in schema @@ -1351,7 +1289,7 @@ function parse_ws_xml(data, opts) { sidx = parseInt(p.v, 10); p.v = strs[sidx].t; p.r = strs[sidx].r; - p.h = strs[sidx].h; + if(opts.cellHTML) p.h = strs[sidx].h; } break; case 'str': if(p.v) p.v = utf8read(p.v); break; case 'inlineStr': @@ -1449,6 +1387,14 @@ var parse_BrtCellSt = function(data, length) { return [cell, value, 'str']; }; +/* [MS-XLSB] 2.4.647 BrtFmlaBool */ +var parse_BrtFmlaBool = function(data, length) { + var cell = parse_Cell(data); + var value = data.read_shift(1); + data.l += length-9; + return [cell, value, 'b' /*, formula */]; +}; + /* [MS-XLSB] 2.4.648 BrtFmlaError */ var parse_BrtFmlaError = function(data, length) { var cell = parse_Cell(data); @@ -1465,9 +1411,16 @@ var parse_BrtFmlaNum = function(data, length) { return [cell, value, 'n' /*, formula */]; }; +/* [MS-XLSB] 2.4.650 BrtFmlaString */ +var parse_BrtFmlaString = function(data, length) { + var start = data.l; + var cell = parse_Cell(data); + var value = parse_XLWideString(data); + data.l = start + length; + return [cell, value, 'str' /*, formula */]; +}; + var parse_BrtCellBlank = parsenoop; -var parse_BrtFmlaBool = parsenoop; -var parse_BrtFmlaString = parsenoop; /* [MS-XLSB] 2.1.7.61 Worksheet */ var parse_ws_bin = function(data, opts) { @@ -1481,15 +1434,12 @@ var parse_ws_bin = function(data, opts) { recordhopper(data, function(val, R) { switch(R.n) { case 'BrtWsDim': ref = val; break; + case 'BrtRowHdr': row = val; break; - case 'BrtRowHdr': - row = val; - break; // TODO - - case 'BrtFmlaError': break; // TODO - case 'BrtFmlaString': break; // TODO - case 'BrtFmlaBool': break; // TODO + case 'BrtFmlaBool': + case 'BrtFmlaError': case 'BrtFmlaNum': + case 'BrtFmlaString': case 'BrtCellBool': case 'BrtCellError': case 'BrtCellIsst': @@ -1501,9 +1451,9 @@ var parse_ws_bin = function(data, opts) { case 's': p.v = strs[val[1]].t; p.r = strs[val[1]].r; break; case 'b': p.v = val[1] ? true : false; break; case 'e': p.raw = val[1]; p.v = BErr[p.raw]; break; - case 'str': if(p.v) p.v = utf8read(p.v); break; + case 'str': p.v = utf8read(val[1]); break; } - if(val[3]) p.f = val[3]; + if(val[3] && opts.cellFormula) p.f = val[3]; if((cf = styles.CellXf[val[0].iStyleRef])) try { p.w = SSF.format(cf.ifmt,p.v,_ssfopts); if(opts.cellNF) p.z = SSF._table[cf.ifmt]; @@ -1811,6 +1761,10 @@ function parse_ws(data, name, opts) { function parse_sty(data, name, opts) { return name.substr(-4)===".bin" ? parse_sty_bin(data, opts) : parse_sty_xml(data, opts); } + +function parse_sst(data, name, opts) { + return name.substr(-4)===".bin" ? parse_sst_bin(data, opts) : parse_sst_xml(data, opts); +} /* [MS-XLSB] 2.3 Record Enumeration */ var RecordEnum = { 0x0000: { n:"BrtRowHdr", f:parse_BrtRowHdr }, @@ -2634,8 +2588,10 @@ var RecordEnum = { function fixopts(opts) { var defaults = [ ['cellNF', false], /* emit cell number format string as .z */ + ['cellHTML', true], /* emit html string as .h */ + ['cellFormula', true], /* emit formulae as .h */ - ['sheetStubs', true], /* emit empty cells */ + ['sheetStubs', false], /* emit empty cells */ ['WTF', false] /* WTF mode (do not use) */ ]; @@ -2656,7 +2612,7 @@ function parseZip(zip, opts) { xlsb = true; } strs = {}; - if(dir.sst) strs=parse_sst(getdata(getzipfile(zip, dir.sst.replace(/^\//,''))), dir.sst); + if(dir.sst) strs=parse_sst(getdata(getzipfile(zip, dir.sst.replace(/^\//,''))), dir.sst, opts); styles = {}; if(dir.style) styles = parse_sty(getdata(getzipfile(zip, dir.style.replace(/^\//,''))),dir.style); @@ -2701,7 +2657,7 @@ function parseZip(zip, opts) { } } - if(dir.comments) parseCommentsAddToSheets(zip, dir.comments, sheets, sheetRels); + if(dir.comments) parse_comments(zip, dir.comments, sheets, sheetRels, opts); return { Directory: dir, @@ -2756,7 +2712,7 @@ function decode_range(range) { var x =range.split(":").map(decode_cell); return function encode_range(range) { return encode_cell(range.s) + ":" + encode_cell(range.e); } function sheet_to_row_object_array(sheet, opts){ - var val, row, r, hdr = {}, isempty, R, C, v; + var val, row, r, hdr = {}, isempty, R, C; var out = []; opts = opts || {}; if(!sheet || !sheet["!ref"]) return out; @@ -2798,12 +2754,10 @@ function sheet_to_row_object_array(sheet, opts){ function sheet_to_csv(sheet, opts) { var stringify = function stringify(val) { if(!val.t) return ""; - if(typeof val.w !== 'undefined') return '"' + val.w.replace(/"/,'""') + '"'; + if(typeof val.w !== 'undefined') return val.w; switch(val.t){ case 'n': return String(val.v); - case 's': case 'str': - if(typeof val.v === 'undefined') return ""; - return '"' + val.v.replace(/"/,'""') + '"'; + case 's': case 'str': return typeof val.v !== 'undefined' ? val.v : ""; case 'b': return val.v ? "TRUE" : "FALSE"; case 'e': return val.v; /* throw out value in case of error */ default: throw 'unrecognized type ' + val.t; @@ -2813,15 +2767,18 @@ function sheet_to_csv(sheet, opts) { opts = opts || {}; if(!sheet || !sheet["!ref"]) return out; var r = XLSX.utils.decode_range(sheet["!ref"]); + var fs = opts.FS||",", rs = opts.RS||"\n"; for(var R = r.s.r; R <= r.e.r; ++R) { var row = []; for(var C = r.s.c; C <= r.e.c; ++C) { var val = sheet[XLSX.utils.encode_cell({c:C,r:R})]; if(!val) { row.push(""); continue; } - txt = stringify(val); - row.push(String(txt).replace(/\\r\\n/g,"\n").replace(/\\t/g,"\t").replace(/\\\\/g,"\\").replace("\\\"","\"\"")); + txt = String(stringify(val)); + if(txt.indexOf(fs)!==-1 || txt.indexOf(rs)!==-1 || txt.indexOf('"')!==-1) + txt = "\"" + txt.replace(/"/g, '""') + "\""; + row.push(txt); } - out += row.join(opts.FS||",") + (opts.RS||"\n"); + out += row.join(fs) + (rs); } return out; } @@ -2862,10 +2819,4 @@ if(typeof require !== 'undefined' && typeof exports !== 'undefined') { exports.readFile = XLSX.readFile; exports.utils = XLSX.utils; exports.version = XLSX.version; - exports.main = function(args) { - var zip = XLSX.read(args[0], {type:'file'}); - console.log(zip.Sheets); - }; -if(typeof module !== 'undefined' && require.main === module) - exports.main(process.argv.slice(2)); }