diff --git a/bits/10_ssf.js b/bits/10_ssf.js index b0822cf..c48f4d4 100644 --- a/bits/10_ssf.js +++ b/bits/10_ssf.js @@ -1,7 +1,7 @@ /* Spreadsheet Format -- jump to XLSX for the XLSX code */ -if(typeof require !== "undefined") SSF = require('ssf'); -var SSF = (function() { - var SSF = {}; +/* ssf.js (C) 2013 SheetJS -- http://sheetjs.com */ +var SSF = {}; +var make_ssf = function(SSF){ String.prototype.reverse=function(){return this.split("").reverse().join("");}; var _strrev = function(x) { return String(x).reverse(); }; function fill(c,l) { return new Array(l+1).join(c); } @@ -23,7 +23,7 @@ var table_fmt = { 11: '0.00E+00', 12: '# ?/?', 13: '# ??/??', - 14: 'mm/dd/yy', + 14: 'm/d/yy', 15: 'd-mmm-yy', 16: 'd-mmm', 17: 'mmm-yy', @@ -65,15 +65,48 @@ var months = [ ['N', 'Nov', 'November'], ['D', 'Dec', 'December'] ]; +var frac = function(x, D, mixed) { + var n1 = Math.floor(x), d1 = 1; + var n2 = n1+1, d2 = 1; + if(x !== n1) while(d1 <= D && d2 <= D) { + var m = (n1 + n2) / (d1 + d2); + if(x === m) { + if(d1 + d2 <= D) { d1+=d2; n1+=n2; d2=D+1; } + else if(d1 > d2) d2=D+1; + else d1=D+1; + break; + } + else if(x < m) { n2 = n1+n2; d2 = d1+d2; } + else { n1 = n1+n2; d1 = d1+d2; } + } + if(d1 > D) { d1 = d2; n1 = n2; } + if(!mixed) return [0, n1, d1]; + var q = Math.floor(n1/d1); + return [q, n1 - q*d1, d1]; +}; var general_fmt = function(v) { if(typeof v === 'boolean') return v ? "TRUE" : "FALSE"; + if(typeof v === 'number') { + var o, V = v < 0 ? -v : v; + if(V >= 0.1 && V < 1) o = v.toPrecision(9); + else if(V >= 0.01 && V < 0.1) o = v.toPrecision(8); + else if(V >= 0.001 && V < 0.01) o = v.toPrecision(7); + else if(V > Math.pow(10,-9) && V < Math.pow(10,10)) { + o = v.toPrecision(10); if(o.length > 11+(v<0?1:0)) o = v.toExponential(5); + } + else o = v.toPrecision(6); + return o.replace("e","E").replace(/\.0*$/,"").replace(/\.(.*[^0])0*$/,".$1").replace(/(E[+-])([0-9])$/,"$1"+"0"+"$2"); + } + if(typeof v === 'string') return v; + throw "unsupported value in General format: " + v; }; SSF._general = general_fmt; var parse_date_code = function parse_date_code(v,opts) { var date = Math.floor(v), time = Math.round(86400 * (v - date)), dow=0; - var dout=[], out={D:date, T:time}; fixopts(opts = (opts||{})); + var dout=[], out={D:date, T:time, u:86400*(v-date)-time}; fixopts(opts = (opts||{})); if(opts.date1904) date += 1462; if(date === 60) {dout = [1900,2,29]; dow=3;} + else if(date === 0) {dout = [1900,1,0]; dow=6;} else { if(date > 60) --date; /* 1 = Jan 1 1900 */ @@ -92,6 +125,7 @@ var parse_date_code = function parse_date_code(v,opts) { }; SSF.parse_date_code = parse_date_code; var write_date = function(type, fmt, val) { + if(val < 0) return ""; switch(type) { case 'y': switch(fmt) { /* year */ case 'y': case 'yy': return pad(val.y % 100,2); @@ -130,19 +164,76 @@ var write_date = function(type, fmt, val) { case 's': switch(fmt) { /* seconds */ case 's': return val.S; case 'ss': return pad(val.S, 2); + case 'ss.0': return pad(val.S,2) + "." + Math.round(10*val.u); default: throw 'bad second format: ' + fmt; } break; + case 'Z': switch(fmt) { + case '[h]': return val.D*24+val.H; + default: throw 'bad abstime format: ' + fmt; + } break; /* TODO: handle the ECMA spec format ee -> yy */ case 'e': { return val.y; } break; case 'A': return (val.h>=12 ? 'P' : 'A') + fmt.substr(1); default: throw 'bad format type ' + type + ' in ' + fmt; } }; +String.prototype.reverse = function() { return this.split("").reverse().join(""); }; +var commaify = function(s) { return s.reverse().replace(/.../g,"$&,").reverse().replace(/^,/,""); }; +var write_num = function(type, fmt, val) { + if(type === '(') { + var ffmt = fmt.replace(/\( */,"").replace(/ \)/,"").replace(/\)/,""); + if(val >= 0) return write_num('n', ffmt, val); + return '(' + write_num('n', ffmt, -val) + ')'; + } + var mul = 0, o; + fmt = fmt.replace(/%/g,function(x) { mul++; return ""; }); + 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 ee = Number(val.toExponential(0).substr(3))%3; + o = (val/Math.pow(10,ee%3)).toPrecision(idx+1+(ee%3)).replace(/^([+-]?)([0-9]*)\.([0-9]*)[Ee]/,function($$,$1,$2,$3) { return $1 + $2 + $3.substr(0,ee) + "." + $3.substr(ee) + "E"; }); + } else o = val.toExponential(idx); + if(fmt.match(/E\+00$/) && o.match(/e[+-][0-9]$/)) o = o.substr(0,o.length-1) + "0" + o[o.length-1]; + if(fmt.match(/E\-/) && o.match(/e\+/)) o = o.replace(/e\+/,"e"); + return o.replace("e","E"); + } + if(fmt[0] === "$") return "$"+write_num(type,fmt.substr(fmt[1]==' '?2:1),val); + var r, ff, aval = val < 0 ? -val : val, sign = val < 0 ? "-" : ""; + switch(fmt) { + case "0": return Math.round(val); + case "0.0": o = Math.round(val*10); + return String(o/10).replace(/^([^\.]+)$/,"$1.0").replace(/\.$/,".0"); + case "0.00": o = Math.round(val*100); + return String(o/100).replace(/^([^\.]+)$/,"$1.00").replace(/\.$/,".00").replace(/\.([0-9])$/,".$1"+"0"); + case "0.000": o = Math.round(val*1000); + return String(o/1000).replace(/^([^\.]+)$/,"$1.000").replace(/\.$/,".000").replace(/\.([0-9])$/,".$1"+"00").replace(/\.([0-9][0-9])$/,".$1"+"0"); + case "#,##0": return sign + commaify(String(Math.round(aval))); + case "#,##0.0": r = Math.round((val-Math.floor(val))*10); return val < 0 ? "-" + write_num(type, fmt, -val) : commaify(String(Math.floor(val))) + "." + r; + case "#,##0.00": r = Math.round((val-Math.floor(val))*100); return val < 0 ? "-" + write_num(type, fmt, -val) : commaify(String(Math.floor(val))) + "." + (r < 10 ? "0"+r:r); + case "# ? / ?": ff = frac(val<0?-val:val, 10, true); return (val<0?"-":"") + ff[0] + " " + ff[1] + "/" + ff[2]; + case "# ?? / ??": ff = frac(val<0?-val:val, 100, true); return (val<0?"-":"") + ff[0] + " " + ff[1] + "/" + ff[2]; + default: + } + throw new Error("unsupported format |" + fmt + "|"); +}; function split_fmt(fmt) { - return fmt.reverse().split(/;(?!\\)/).reverse().map(_strrev); + var out = []; + var in_str = -1; + for(var i = 0, j = 0; i < fmt.length; ++i) { + if(in_str != -1) { if(fmt[i] == '"') in_str = -1; continue; } + if(fmt[i] == "_" || fmt[i] == "*" || fmt[i] == "\\") { ++i; continue; } + if(fmt[i] == '"') { in_str = i; continue; } + if(fmt[i] != ";") continue; + out.push(fmt.slice(j,i)); + j = i+1; + } + out.push(fmt.slice(j)); + if(in_str !=-1) throw "Format |" + fmt + "| unterminated string at " + in_str; + return out; } SSF._split = split_fmt; -function eval_fmt(fmt, v, opts) { +function eval_fmt(fmt, v, opts, flen) { var out = [], o = "", i = 0, c = "", lst='t', q = {}, dt; fixopts(opts = (opts || {})); var hr='H'; @@ -150,15 +241,19 @@ function eval_fmt(fmt, v, opts) { while(i < fmt.length) { switch((c = fmt[i])) { case '"': /* Literal text */ - for(o="";fmt[++i] !== '"';) o += fmt[(fmt[i] === '\\' ? ++i : i)]; - out.push({t:'t', v:o}); break; - case '\\': out.push({t:'t', v:fmt[++i]}); ++i; break; + for(o="";fmt[++i] !== '"';) o += fmt[i]; + out.push({t:'t', v:o}); ++i; break; + case '\\': var w = fmt[++i], t = "()".indexOf(w) === -1 ? 't' : w; + out.push({t:t, v:w}); ++i; break; + case '_': out.push({t:'t', v:" "}); i+=2; break; case '@': /* Text Placeholder */ out.push({t:'T', v:v}); ++i; break; /* Dates */ case 'm': case 'd': case 'y': case 'h': case 's': case 'e': + if(v < 0) return ""; if(!dt) dt = parse_date_code(v, opts); o = fmt[i]; while(fmt[++i] === c) o+=c; + if(c === 's' && fmt[i] === '.' && fmt[i+1] === '0') { o+='.'; while(fmt[++i] === '0') o+= '0'; } if(c === 'm' && lst.toLowerCase() === 'h') c = 'M'; /* m = minute */ if(c === 'h') c = hr; q={t:c, v:o}; out.push(q); lst = c; break; @@ -170,13 +265,27 @@ function eval_fmt(fmt, v, opts) { else q.t = "t"; out.push(q); lst = c; break; case '[': /* TODO: Fix this -- ignore all conditionals and formatting */ - while(fmt[i++] !== ']'); break; + o = c; + while(fmt[i++] !== ']') o += fmt[i]; + if(o == "[h]") out.push({t:'Z', v:o}); + break; + /* Numbers */ + case '0': case '#': + var nn = c; while("0#?.,E+-%".indexOf(c=fmt[++i]) > -1) nn += c; + out.push({t:'n', v:nn}); break; + case '?': + o = fmt[i]; while(fmt[++i] === c) o+=c; + q={t:c, v:o}; out.push(q); lst = c; break; + case '*': ++i; if(fmt[i] == ' ') ++i; break; // ** + case '(': case ')': out.push({t:(flen===1?'t':c),v:c}); ++i; 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; } } + /* walk backwards */ for(i=out.length-1, lst='t'; i >= 0; --i) { switch(out[i].t) { @@ -189,10 +298,19 @@ function eval_fmt(fmt, v, opts) { /* replace fields */ for(i=0; i < out.length; ++i) { switch(out[i].t) { - case 't': case 'T': break; - case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'A': case 'e': + 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': out[i].v = write_date(out[i].t, out[i].v, dt); out[i].t = 't'; break; + case 'n': case '(': + var jj = i+1; + while(out[jj] && (out[jj].t == '?' || out[jj].t == ' ' || out[i].t == '(' && (out[jj].t == ')' || out[jj].t == 'n') || out[jj].t == 't' && (out[jj].v == '/' || out[jj].v == '$'))) { + if(out[jj].v!==' ') out[i].v += ' ' + out[jj].v; + delete out[jj]; ++jj; + } + out[i].v = write_num(out[i].t, out[i].v, v); + out[i].t = 't'; + i = jj; break; default: throw "unrecognized type " + out[i].t; } } @@ -200,23 +318,31 @@ function eval_fmt(fmt, v, opts) { return out.map(function(x){return x.v;}).join(""); } SSF._eval = eval_fmt; -function choose_fmt(fmt, v) { +function choose_fmt(fmt, v, o) { + if(typeof fmt === 'number') fmt = table_fmt[fmt]; if(typeof fmt === "string") fmt = split_fmt(fmt); - if(typeof v !== "number") return fmt[3]; - return v > 0 ? fmt[0] : v < 0 ? fmt[1] : fmt[2]; + var l = fmt.length; + switch(fmt.length) { + case 1: fmt = [fmt[0], fmt[0], fmt[0], "@"]; break; + case 2: fmt = [fmt[0], fmt[1], fmt[0], "@"]; break; + case 4: break; + default: throw "cannot find right format for |" + fmt + "|"; + } + if(typeof v !== "number") return [fmt.length, fmt[3]]; + return [l, v > 0 ? fmt[0] : v < 0 ? fmt[1] : fmt[2]]; } var format = function format(fmt,v,o) { fixopts(o = (o||{})); if(fmt === 0) return general_fmt(v, o); - if(typeof fmt === 'number') fmt = table_fmt[fmt]; + fmt = table_fmt[fmt]; var f = choose_fmt(fmt, v, o); - return eval_fmt(f, v, o); + return eval_fmt(f[1], v, o, f[0]); }; SSF._choose = choose_fmt; SSF._table = table_fmt; SSF.load = function(fmt, idx) { table_fmt[idx] = fmt; }; SSF.format = format; - return SSF; -})(); +}; +make_ssf(SSF); diff --git a/package.json b/package.json index 51a0dd0..9e164e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xlsx", - "version": "0.3.3", + "version": "0.3.4", "author": "sheetjs", "description": "(one day) a full-featured XLSX parser and writer. For now, primitive parser", "keywords": [ "xlsx", "office", "excel", "spreadsheet" ], diff --git a/tests.lst b/tests.lst index 5ce330d..7fa2335 100644 --- a/tests.lst +++ b/tests.lst @@ -1,9 +1,9 @@ 45430.xlsx -45540_classic_Footer.xlsx.pending -45540_classic_Header.xlsx.pending +45540_classic_Footer.xlsx +45540_classic_Header.xlsx 45540_form_Footer.xlsx 45540_form_Header.xlsx -45544.xlsx.pending +45544.xlsx 46535.xlsx 46536.xlsx 47090.xlsx @@ -20,12 +20,12 @@ 48779.xlsx 48923.xlsx 49156.xlsx -49273.xlsx.pending +49273.xlsx 49325.xlsx 49609.xlsx 49783.xlsx 49872.xlsx -49928.xlsx.pending +49928.xlsx 49966.xlsx 50096.xlsx 50299.xlsx @@ -55,8 +55,9 @@ 54436.xlsx 54524.xlsx 54607.xlsx -55640.xlsx.pending -55745.xlsx.pending +55640.xlsx +55745.xlsx +55850.xlsx AverageTaxRates.xlsx Booleans.xlsx BrNotClosed.xlsx @@ -70,10 +71,10 @@ ElapsedFormatTests.xlsx ForShifting.xlsx FormatChoiceTests.xlsx FormatConditionTests.xlsx -Formatting.xlsx.pending -FormulaEvalTestData_Copy.xlsx.pending +Formatting.xlsx +FormulaEvalTestData_Copy.xlsx GeneralFormatTests.xlsx -GroupTest.xlsx.pending +GroupTest.xlsx InlineStrings.xlsx NewlineInFormulas.xlsx NumberFormatApproxTests.xlsx @@ -113,7 +114,7 @@ excel-reader-xlsx_error07.xlsx.pending excel-reader-xlsx_error08.xlsx.pending excel-reader-xlsx_inline01.xlsx excel-reader-xlsx_libre01.xlsx -formula_stress_test.xlsx.pending +formula_stress_test.xlsx interview.xlsx issue.xlsx jxls-core_formulaOneRow.xlsx diff --git a/tests/files b/tests/files index 6c9129b..448fa13 160000 --- a/tests/files +++ b/tests/files @@ -1 +1 @@ -Subproject commit 6c9129b1519cfd593e065ca77aa09f8bc4f676cc +Subproject commit 448fa13fe614753d1e2f476119201cfca136d2d9 diff --git a/xlsx.js b/xlsx.js index bd77f1b..fde090a 100644 --- a/xlsx.js +++ b/xlsx.js @@ -2,9 +2,9 @@ /* vim: set ts=2: */ /*jshint eqnull:true */ /* Spreadsheet Format -- jump to XLSX for the XLSX code */ -if(typeof module !== "undefined" && typeof require !== 'undefined') SSF = require('ssf'); -var SSF = (function() { - var SSF = {}; +/* ssf.js (C) 2013 SheetJS -- http://sheetjs.com */ +var SSF = {}; +var make_ssf = function(SSF){ String.prototype.reverse=function(){return this.split("").reverse().join("");}; var _strrev = function(x) { return String(x).reverse(); }; function fill(c,l) { return new Array(l+1).join(c); } @@ -26,7 +26,7 @@ var table_fmt = { 11: '0.00E+00', 12: '# ?/?', 13: '# ??/??', - 14: 'mm/dd/yy', + 14: 'm/d/yy', 15: 'd-mmm-yy', 16: 'd-mmm', 17: 'mmm-yy', @@ -68,15 +68,48 @@ var months = [ ['N', 'Nov', 'November'], ['D', 'Dec', 'December'] ]; +var frac = function(x, D, mixed) { + var n1 = Math.floor(x), d1 = 1; + var n2 = n1+1, d2 = 1; + if(x !== n1) while(d1 <= D && d2 <= D) { + var m = (n1 + n2) / (d1 + d2); + if(x === m) { + if(d1 + d2 <= D) { d1+=d2; n1+=n2; d2=D+1; } + else if(d1 > d2) d2=D+1; + else d1=D+1; + break; + } + else if(x < m) { n2 = n1+n2; d2 = d1+d2; } + else { n1 = n1+n2; d1 = d1+d2; } + } + if(d1 > D) { d1 = d2; n1 = n2; } + if(!mixed) return [0, n1, d1]; + var q = Math.floor(n1/d1); + return [q, n1 - q*d1, d1]; +}; var general_fmt = function(v) { if(typeof v === 'boolean') return v ? "TRUE" : "FALSE"; + if(typeof v === 'number') { + var o, V = v < 0 ? -v : v; + if(V >= 0.1 && V < 1) o = v.toPrecision(9); + else if(V >= 0.01 && V < 0.1) o = v.toPrecision(8); + else if(V >= 0.001 && V < 0.01) o = v.toPrecision(7); + else if(V > Math.pow(10,-9) && V < Math.pow(10,10)) { + o = v.toPrecision(10); if(o.length > 11+(v<0?1:0)) o = v.toExponential(5); + } + else o = v.toPrecision(6); + return o.replace("e","E").replace(/\.0*$/,"").replace(/\.(.*[^0])0*$/,".$1").replace(/(E[+-])([0-9])$/,"$1"+"0"+"$2"); + } + if(typeof v === 'string') return v; + throw "unsupported value in General format: " + v; }; SSF._general = general_fmt; var parse_date_code = function parse_date_code(v,opts) { var date = Math.floor(v), time = Math.round(86400 * (v - date)), dow=0; - var dout=[], out={D:date, T:time}; fixopts(opts = (opts||{})); + var dout=[], out={D:date, T:time, u:86400*(v-date)-time}; fixopts(opts = (opts||{})); if(opts.date1904) date += 1462; if(date === 60) {dout = [1900,2,29]; dow=3;} + else if(date === 0) {dout = [1900,1,0]; dow=6;} else { if(date > 60) --date; /* 1 = Jan 1 1900 */ @@ -95,6 +128,7 @@ var parse_date_code = function parse_date_code(v,opts) { }; SSF.parse_date_code = parse_date_code; var write_date = function(type, fmt, val) { + if(val < 0) return ""; switch(type) { case 'y': switch(fmt) { /* year */ case 'y': case 'yy': return pad(val.y % 100,2); @@ -133,19 +167,76 @@ var write_date = function(type, fmt, val) { case 's': switch(fmt) { /* seconds */ case 's': return val.S; case 'ss': return pad(val.S, 2); + case 'ss.0': return pad(val.S,2) + "." + Math.round(10*val.u); default: throw 'bad second format: ' + fmt; } break; + case 'Z': switch(fmt) { + case '[h]': return val.D*24+val.H; + default: throw 'bad abstime format: ' + fmt; + } break; /* TODO: handle the ECMA spec format ee -> yy */ case 'e': { return val.y; } break; case 'A': return (val.h>=12 ? 'P' : 'A') + fmt.substr(1); default: throw 'bad format type ' + type + ' in ' + fmt; } }; +String.prototype.reverse = function() { return this.split("").reverse().join(""); }; +var commaify = function(s) { return s.reverse().replace(/.../g,"$&,").reverse().replace(/^,/,""); }; +var write_num = function(type, fmt, val) { + if(type === '(') { + var ffmt = fmt.replace(/\( */,"").replace(/ \)/,"").replace(/\)/,""); + if(val >= 0) return write_num('n', ffmt, val); + return '(' + write_num('n', ffmt, -val) + ')'; + } + var mul = 0, o; + fmt = fmt.replace(/%/g,function(x) { mul++; return ""; }); + 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 ee = Number(val.toExponential(0).substr(3))%3; + o = (val/Math.pow(10,ee%3)).toPrecision(idx+1+(ee%3)).replace(/^([+-]?)([0-9]*)\.([0-9]*)[Ee]/,function($$,$1,$2,$3) { return $1 + $2 + $3.substr(0,ee) + "." + $3.substr(ee) + "E"; }); + } else o = val.toExponential(idx); + if(fmt.match(/E\+00$/) && o.match(/e[+-][0-9]$/)) o = o.substr(0,o.length-1) + "0" + o[o.length-1]; + if(fmt.match(/E\-/) && o.match(/e\+/)) o = o.replace(/e\+/,"e"); + return o.replace("e","E"); + } + if(fmt[0] === "$") return "$"+write_num(type,fmt.substr(fmt[1]==' '?2:1),val); + var r, ff, aval = val < 0 ? -val : val, sign = val < 0 ? "-" : ""; + switch(fmt) { + case "0": return Math.round(val); + case "0.0": o = Math.round(val*10); + return String(o/10).replace(/^([^\.]+)$/,"$1.0").replace(/\.$/,".0"); + case "0.00": o = Math.round(val*100); + return String(o/100).replace(/^([^\.]+)$/,"$1.00").replace(/\.$/,".00").replace(/\.([0-9])$/,".$1"+"0"); + case "0.000": o = Math.round(val*1000); + return String(o/1000).replace(/^([^\.]+)$/,"$1.000").replace(/\.$/,".000").replace(/\.([0-9])$/,".$1"+"00").replace(/\.([0-9][0-9])$/,".$1"+"0"); + case "#,##0": return sign + commaify(String(Math.round(aval))); + case "#,##0.0": r = Math.round((val-Math.floor(val))*10); return val < 0 ? "-" + write_num(type, fmt, -val) : commaify(String(Math.floor(val))) + "." + r; + case "#,##0.00": r = Math.round((val-Math.floor(val))*100); return val < 0 ? "-" + write_num(type, fmt, -val) : commaify(String(Math.floor(val))) + "." + (r < 10 ? "0"+r:r); + case "# ? / ?": ff = frac(val<0?-val:val, 10, true); return (val<0?"-":"") + ff[0] + " " + ff[1] + "/" + ff[2]; + case "# ?? / ??": ff = frac(val<0?-val:val, 100, true); return (val<0?"-":"") + ff[0] + " " + ff[1] + "/" + ff[2]; + default: + } + throw new Error("unsupported format |" + fmt + "|"); +}; function split_fmt(fmt) { - return fmt.reverse().split(/;(?!\\)/).reverse().map(_strrev); + var out = []; + var in_str = -1; + for(var i = 0, j = 0; i < fmt.length; ++i) { + if(in_str != -1) { if(fmt[i] == '"') in_str = -1; continue; } + if(fmt[i] == "_" || fmt[i] == "*" || fmt[i] == "\\") { ++i; continue; } + if(fmt[i] == '"') { in_str = i; continue; } + if(fmt[i] != ";") continue; + out.push(fmt.slice(j,i)); + j = i+1; + } + out.push(fmt.slice(j)); + if(in_str !=-1) throw "Format |" + fmt + "| unterminated string at " + in_str; + return out; } SSF._split = split_fmt; -function eval_fmt(fmt, v, opts) { +function eval_fmt(fmt, v, opts, flen) { var out = [], o = "", i = 0, c = "", lst='t', q = {}, dt; fixopts(opts = (opts || {})); var hr='H'; @@ -153,15 +244,19 @@ function eval_fmt(fmt, v, opts) { while(i < fmt.length) { switch((c = fmt[i])) { case '"': /* Literal text */ - for(o="";fmt[++i] !== '"';) o += fmt[(fmt[i] === '\\' ? ++i : i)]; - out.push({t:'t', v:o}); break; - case '\\': out.push({t:'t', v:fmt[++i]}); ++i; break; + for(o="";fmt[++i] !== '"';) o += fmt[i]; + out.push({t:'t', v:o}); ++i; break; + case '\\': var w = fmt[++i], t = "()".indexOf(w) === -1 ? 't' : w; + out.push({t:t, v:w}); ++i; break; + case '_': out.push({t:'t', v:" "}); i+=2; break; case '@': /* Text Placeholder */ out.push({t:'T', v:v}); ++i; break; /* Dates */ case 'm': case 'd': case 'y': case 'h': case 's': case 'e': + if(v < 0) return ""; if(!dt) dt = parse_date_code(v, opts); o = fmt[i]; while(fmt[++i] === c) o+=c; + if(c === 's' && fmt[i] === '.' && fmt[i+1] === '0') { o+='.'; while(fmt[++i] === '0') o+= '0'; } if(c === 'm' && lst.toLowerCase() === 'h') c = 'M'; /* m = minute */ if(c === 'h') c = hr; q={t:c, v:o}; out.push(q); lst = c; break; @@ -173,13 +268,27 @@ function eval_fmt(fmt, v, opts) { else q.t = "t"; out.push(q); lst = c; break; case '[': /* TODO: Fix this -- ignore all conditionals and formatting */ - while(fmt[i++] !== ']'); break; + o = c; + while(fmt[i++] !== ']') o += fmt[i]; + if(o == "[h]") out.push({t:'Z', v:o}); + break; + /* Numbers */ + case '0': case '#': + var nn = c; while("0#?.,E+-%".indexOf(c=fmt[++i]) > -1) nn += c; + out.push({t:'n', v:nn}); break; + case '?': + o = fmt[i]; while(fmt[++i] === c) o+=c; + q={t:c, v:o}; out.push(q); lst = c; break; + case '*': ++i; if(fmt[i] == ' ') ++i; break; // ** + case '(': case ')': out.push({t:(flen===1?'t':c),v:c}); ++i; 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; } } + /* walk backwards */ for(i=out.length-1, lst='t'; i >= 0; --i) { switch(out[i].t) { @@ -192,10 +301,19 @@ function eval_fmt(fmt, v, opts) { /* replace fields */ for(i=0; i < out.length; ++i) { switch(out[i].t) { - case 't': case 'T': break; - case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'A': case 'e': + 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': out[i].v = write_date(out[i].t, out[i].v, dt); out[i].t = 't'; break; + case 'n': case '(': + var jj = i+1; + while(out[jj] && (out[jj].t == '?' || out[jj].t == ' ' || out[i].t == '(' && (out[jj].t == ')' || out[jj].t == 'n') || out[jj].t == 't' && (out[jj].v == '/' || out[jj].v == '$'))) { + if(out[jj].v!==' ') out[i].v += ' ' + out[jj].v; + delete out[jj]; ++jj; + } + out[i].v = write_num(out[i].t, out[i].v, v); + out[i].t = 't'; + i = jj; break; default: throw "unrecognized type " + out[i].t; } } @@ -203,26 +321,34 @@ function eval_fmt(fmt, v, opts) { return out.map(function(x){return x.v;}).join(""); } SSF._eval = eval_fmt; -function choose_fmt(fmt, v) { +function choose_fmt(fmt, v, o) { + if(typeof fmt === 'number') fmt = table_fmt[fmt]; if(typeof fmt === "string") fmt = split_fmt(fmt); - if(typeof v !== "number") return fmt[3]; - return v > 0 ? fmt[0] : v < 0 ? fmt[1] : fmt[2]; + var l = fmt.length; + switch(fmt.length) { + case 1: fmt = [fmt[0], fmt[0], fmt[0], "@"]; break; + case 2: fmt = [fmt[0], fmt[1], fmt[0], "@"]; break; + case 4: break; + default: throw "cannot find right format for |" + fmt + "|"; + } + if(typeof v !== "number") return [fmt.length, fmt[3]]; + return [l, v > 0 ? fmt[0] : v < 0 ? fmt[1] : fmt[2]]; } var format = function format(fmt,v,o) { fixopts(o = (o||{})); if(fmt === 0) return general_fmt(v, o); - if(typeof fmt === 'number') fmt = table_fmt[fmt]; + fmt = table_fmt[fmt]; var f = choose_fmt(fmt, v, o); - return eval_fmt(f, v, o); + return eval_fmt(f[1], v, o, f[0]); }; SSF._choose = choose_fmt; SSF._table = table_fmt; SSF.load = function(fmt, idx) { table_fmt[idx] = fmt; }; SSF.format = format; - return SSF; -})(); +}; +make_ssf(SSF); var XLSX = {}; (function(XLSX){ function parsexmltag(tag) {