Comparing version
{ | ||
"name": "ssf", | ||
"version": "0.5.1", | ||
"version": "0.5.2", | ||
"author": "SheetJS", | ||
@@ -23,2 +23,7 @@ "description": "pure-JS library to format data using ECMA-376 spreadsheet Format Codes", | ||
}, | ||
"config": { | ||
"blanket": { | ||
"pattern": "ssf.js" | ||
} | ||
}, | ||
"bugs": { "url": "https://github.com/SheetJS/ssf/issues" }, | ||
@@ -25,0 +30,0 @@ "license": "Apache-2.0", |
@@ -26,7 +26,7 @@ # SSF | ||
`.load(fmt, idx)` sets custom formats (generally indices above `164`) | ||
`.load(fmt, idx)` sets custom formats (generally indices above `164`). | ||
`.format(fmt, val)` formats `val` using the format `fmt`. If `fmt` is of type | ||
`number`, the internal table (and custom formats) will be used. If `fmt` is a | ||
literal format, then it will be parsed and evaluated. | ||
`.format(fmt, val, opts)` formats `val` using the format `fmt`. If `fmt` is of | ||
type `number`, the internal table (and custom formats) will be used. If `fmt` | ||
is a literal format, then it will be parsed and evaluated. | ||
@@ -40,2 +40,6 @@ `.parse_date_code(val, opts)` parses `val` as date code and returns object: | ||
`.get_table()` gets the internal format table (number to format mapping). | ||
`.load_table(table)` sets the internal format table. | ||
## Notes | ||
@@ -42,0 +46,0 @@ |
87
ssf.js
/* ssf.js (C) 2013-2014 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(); }; | ||
var _strrev = function(x) { return String(x).split("").reverse().join("");}; | ||
function fill(c,l) { return new Array(l+1).join(c); } | ||
@@ -17,2 +16,3 @@ function pad(v,d,c){var t=String(v);return t.length>=d?t:(fill(c||0,d-t.length)+t);} | ||
var table_fmt = { | ||
0: 'General', | ||
1: '0', | ||
@@ -87,3 +87,3 @@ 2: '0.00', | ||
if(!mixed) return [0, sgn * P, Q]; | ||
if(Q==0) throw "Unexpected state: "+P+" "+P_1+" "+P_2+" "+Q+" "+Q_1+" "+Q_2; | ||
if(Q===0) throw "Unexpected state: "+P+" "+P_1+" "+P_2+" "+Q+" "+Q_1+" "+Q_2; | ||
var q = Math.floor(sgn * P/Q); | ||
@@ -118,3 +118,3 @@ return [q, sgn*P - q*Q, Q]; | ||
var parse_date_code = function parse_date_code(v,opts) { | ||
var date = Math.floor(v), time = Math.floor(86400 * (v - date)), dow=0; | ||
var date = Math.floor(v), time = Math.floor(86400 * (v - date)+1e-6), dow=0; | ||
var dout=[], out={D:date, T:time, u:86400*(v-date)-time}; fixopts(opts = (opts||{})); | ||
@@ -132,3 +132,3 @@ if(opts.date1904) date += 1462; | ||
dow = d.getDay(); | ||
if(opts.mode === 'excel' && date < 60) dow = (dow + 6) % 7; | ||
if(/* opts.mode === 'excel' && */ date < 60) dow = (dow + 6) % 7; | ||
} | ||
@@ -143,9 +143,12 @@ out.y = dout[0]; out.m = dout[1]; out.d = dout[2]; | ||
SSF.parse_date_code = parse_date_code; | ||
/*jshint -W086 */ | ||
var write_date = function(type, fmt, val) { | ||
if(val < 0) return ""; | ||
var o; | ||
switch(type) { | ||
case 'y': switch(fmt) { /* year */ | ||
case 'y': case 'yy': return pad(val.y % 100,2); | ||
default: return val.y; | ||
} break; | ||
case 'yyy': case 'yyyy': return pad(val.y % 10000,4); | ||
default: throw 'bad year format: ' + fmt; | ||
} | ||
case 'm': switch(fmt) { /* month */ | ||
@@ -158,3 +161,3 @@ case 'm': return val.m; | ||
default: throw 'bad month format: ' + fmt; | ||
} break; | ||
} | ||
case 'd': switch(fmt) { /* day */ | ||
@@ -166,3 +169,3 @@ case 'd': return val.d; | ||
default: throw 'bad day format: ' + fmt; | ||
} break; | ||
} | ||
case 'h': switch(fmt) { /* 12-hour */ | ||
@@ -172,3 +175,3 @@ case 'h': return 1+(val.H+11)%12; | ||
default: throw 'bad hour format: ' + fmt; | ||
} break; | ||
} | ||
case 'H': switch(fmt) { /* 24-hour */ | ||
@@ -178,3 +181,3 @@ case 'h': return val.H; | ||
default: throw 'bad hour format: ' + fmt; | ||
} break; | ||
} | ||
case 'M': switch(fmt) { /* minutes */ | ||
@@ -184,13 +187,17 @@ case 'm': return val.M; | ||
default: throw 'bad minute format: ' + fmt; | ||
} break; | ||
} | ||
case 's': switch(fmt) { /* seconds */ | ||
case 's': return val.S; | ||
case 's': return Math.round(val.S+val.u); | ||
case 'ss': return pad(Math.round(val.S+val.u), 2); | ||
case 'ss.0': var o = pad(Math.round(10*(val.S+val.u)),3); return o.substr(0,2)+"." + o.substr(2); | ||
case 'ss.0': o = pad(Math.round(10*(val.S+val.u)),3); return o.substr(0,2)+"." + o.substr(2); | ||
case 'ss.00': o = pad(Math.round(100*(val.S+val.u)),4); return o.substr(0,2)+"." + o.substr(2); | ||
case 'ss.000': o = pad(Math.round(1000*(val.S+val.u)),5); return o.substr(0,2)+"." + o.substr(2); | ||
default: throw 'bad second format: ' + fmt; | ||
} break; | ||
} | ||
case 'Z': switch(fmt) { | ||
case '[h]': return val.D*24+val.H; | ||
case '[h]': case '[hh]': o = val.D*24+val.H; break; | ||
case '[m]': case '[mm]': o = (val.D*24+val.H)*60+val.M; break; | ||
case '[s]': case '[ss]': o = ((val.D*24+val.H)*60+val.M)*60+Math.round(val.S+val.u); break; | ||
default: throw 'bad abstime format: ' + fmt; | ||
} break; | ||
} return fmt.length === 3 ? o : pad(o, 2); | ||
/* TODO: handle the ECMA spec format ee -> yy */ | ||
@@ -202,4 +209,4 @@ case 'e': { return val.y; } break; | ||
}; | ||
String.prototype.reverse = function() { return this.split("").reverse().join(""); }; | ||
var commaify = function(s) { return s.reverse().replace(/.../g,"$&,").reverse().replace(/^,/,""); }; | ||
/*jshint +W086 */ | ||
var commaify = function(s) { return _strrev(_strrev(s).replace(/.../g,"$&,")).replace(/^,/,""); }; | ||
var write_num = function(type, fmt, val) { | ||
@@ -216,11 +223,13 @@ if(type === '(') { | ||
var idx = fmt.indexOf("E") - fmt.indexOf(".") - 1; | ||
//if(fmt.match(/^#+0\.0E\+0$/)) | ||
if(fmt == '##0.0E+0') { | ||
var ee = (Number(val.toExponential(0).substr(2+(val<0))))%3; | ||
o = (val/Math.pow(10,ee)).toPrecision(idx+1+(3+ee)%3); | ||
var period = fmt.length - 5; | ||
var ee = (Number(val.toExponential(0).substr(2+(val<0))))%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"; | ||
else throw "missing E |" + o; | ||
} | ||
o = o.replace(/^([+-]?)([0-9]*)\.([0-9]*)[Ee]/,function($$,$1,$2,$3) { return $1 + $2 + $3.substr(0,(3+ee)%3) + "." + $3.substr(ee) + "E"; }); | ||
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); | ||
@@ -238,2 +247,4 @@ 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(/^00*$/)) return (val<0?"-":"")+pad(Math.round(aval),fmt.length); | ||
if(fmt.match(/^####*$/)) return Math.round(val); | ||
switch(fmt) { | ||
@@ -247,2 +258,5 @@ case "0": return Math.round(val); | ||
return String(o/1000).replace(/^([^\.]+)$/,"$1.000").replace(/\.$/,".000").replace(/\.([0-9])$/,".$1"+"00").replace(/\.([0-9][0-9])$/,".$1"+"0"); | ||
case "#.##": o = Math.round(val*100); | ||
return String(o/100).replace(/^([^\.]+)$/,"$1.").replace(/^0\.$/,"."); | ||
case "#,###": var x = commaify(String(Math.round(aval))); return x !== "0" ? sign + x : ""; | ||
case "#,##0": return sign + commaify(String(Math.round(aval))); | ||
@@ -281,2 +295,6 @@ 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; | ||
switch((c = fmt[i])) { | ||
case 'G': /* General */ | ||
if(fmt.substr(i, i+6).toLowerCase() !== "general") | ||
throw 'unrecognized character ' + fmt[i] + ' in ' + fmt; | ||
out.push({t:'G',v:'General'}); i+=7; break; | ||
case '"': /* Literal text */ | ||
@@ -291,2 +309,5 @@ for(o="";fmt[++i] !== '"' && i < fmt.length;) o += fmt[i]; | ||
/* Dates */ | ||
case 'M': case 'D': case 'Y': case 'H': case 'S': case 'E': | ||
c = c.toLowerCase(); | ||
/* falls through */ | ||
case 'm': case 'd': case 'y': case 'h': case 's': case 'e': | ||
@@ -296,6 +317,7 @@ if(v < 0) return ""; | ||
if(!dt) return ""; | ||
o = fmt[i]; while(fmt[++i] === c) o+=c; | ||
o = fmt[i]; while((fmt[++i]||"").toLowerCase() === 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; | ||
o = o.toLowerCase(); | ||
q={t:c, v:o}; out.push(q); lst = c; break; | ||
@@ -308,3 +330,3 @@ case 'A': | ||
else if(fmt.substr(i,5) === "AM/PM") { q.v = dt.H >= 12 ? "PM" : "AM"; q.t = 'T'; i+=5; hr='h'; } | ||
else q.t = "t"; | ||
else { q.t = "t"; i++; } | ||
out.push(q); lst = c; break; | ||
@@ -314,3 +336,7 @@ case '[': /* TODO: Fix this -- ignore all conditionals and formatting */ | ||
while(fmt[i++] !== ']') o += fmt[i]; | ||
if(o == "[h]") out.push({t:'Z', v:o}); | ||
if(o.match(/\[[HhMmSs]*\]/)) { | ||
if(!dt) dt = parse_date_code(v, opts); | ||
if(!dt) return ""; | ||
out.push({t:'Z', v:o.toLowerCase()}); | ||
} else { o=""; } | ||
break; | ||
@@ -362,2 +388,3 @@ /* Numbers */ | ||
i = jj-1; break; | ||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break; | ||
default: throw "unrecognized type " + out[i].t; | ||
@@ -374,4 +401,5 @@ } | ||
switch(fmt.length) { | ||
case 1: fmt = [fmt[0], fmt[0], fmt[0], "@"]; break; | ||
case 2: fmt = [fmt[0], fmt[fmt[1] === "@"?0:1], fmt[0], "@"]; break; | ||
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; | ||
case 3: fmt = fmt[2].indexOf("@")>-1 ? [fmt[0], fmt[1], fmt[0], fmt[2]] : [fmt[0], fmt[1], fmt[2], "@"]; break; | ||
case 4: break; | ||
@@ -385,6 +413,7 @@ default: throw "cannot find right format for |" + fmt + "|"; | ||
fixopts(o = (o||{})); | ||
if(fmt === 0 || (typeof fmt === "string" && fmt.toLowerCase() === "general")) return general_fmt(v, o); | ||
if(typeof fmt === "string" && fmt.toLowerCase() === "general") return general_fmt(v, o); | ||
if(typeof fmt === 'number') fmt = (o.table || table_fmt)[fmt]; | ||
var f = choose_fmt(fmt, v, o); | ||
if(f[1].toLowerCase() === "general") return general_fmt(v,o); | ||
if(v === true) v = "TRUE"; if(v === false) v = "FALSE"; | ||
return eval_fmt(f[1], v, o, f[0]); | ||
@@ -391,0 +420,0 @@ }; |
258
ssf.md
@@ -183,2 +183,3 @@ # SSF | ||
var table_fmt = { | ||
0: 'General', | ||
1: '0', | ||
@@ -266,3 +267,3 @@ 2: '0.00', | ||
var parse_date_code = function parse_date_code(v,opts) { | ||
var date = Math.floor(v), time = Math.floor(86400 * (v - date)), dow=0; | ||
var date = Math.floor(v), time = Math.floor(86400 * (v - date)+1e-6), dow=0; | ||
var dout=[], out={D:date, T:time, u:86400*(v-date)-time}; fixopts(opts = (opts||{})); | ||
@@ -315,3 +316,3 @@ ``` | ||
``` | ||
if(opts.mode === 'excel' && date < 60) dow = (dow + 6) % 7; | ||
if(/* opts.mode === 'excel' && */ date < 60) dow = (dow + 6) % 7; | ||
} | ||
@@ -336,4 +337,3 @@ ``` | ||
```js>tmp/60_number.js | ||
String.prototype.reverse = function() { return this.split("").reverse().join(""); }; | ||
var commaify = function(s) { return s.reverse().replace(/.../g,"$&,").reverse().replace(/^,/,""); }; | ||
var commaify = function(s) { return _strrev(_strrev(s).replace(/.../g,"$&,")).replace(/^,/,""); }; | ||
var write_num = function(type, fmt, val) { | ||
@@ -371,5 +371,7 @@ ``` | ||
``` | ||
//if(fmt.match(/^#+0\.0E\+0$/)) | ||
if(fmt == '##0.0E+0') { | ||
var ee = (Number(val.toExponential(0).substr(2+(val<0))))%3; | ||
o = (val/Math.pow(10,ee)).toPrecision(idx+1+(3+ee)%3); | ||
var period = fmt.length - 5; | ||
var ee = (Number(val.toExponential(0).substr(2+(val<0))))%period; | ||
o = (val/Math.pow(10,ee)).toPrecision(idx+1+(period+ee)%period); | ||
if(!o.match(/[Ee]/)) { | ||
@@ -383,5 +385,5 @@ ``` | ||
if(o.indexOf(".") === -1) o = o[0] + "." + o.substr(1) + "E+" + (fakee - o.length+ee); | ||
else throw "missing E"; | ||
else throw "missing E |" + o; | ||
} | ||
o = o.replace(/^([+-]?)([0-9]*)\.([0-9]*)[Ee]/,function($$,$1,$2,$3) { return $1 + $2 + $3.substr(0,(3+ee)%3) + "." + $3.substr(ee) + "E"; }); | ||
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); | ||
@@ -411,2 +413,9 @@ if(fmt.match(/E\+00$/) && o.match(/e[+-][0-9]$/)) o = o.substr(0,o.length-1) + "0" + o[o.length-1]; | ||
A few special general cases can be handled in a very dumb manner: | ||
``` | ||
if(fmt.match(/^00*$/)) return (val<0?"-":"")+pad(Math.round(aval),fmt.length); | ||
if(fmt.match(/^####*$/)) return Math.round(val); | ||
``` | ||
The default cases are hard-coded. TODO: actually parse them | ||
@@ -423,2 +432,5 @@ | ||
return String(o/1000).replace(/^([^\.]+)$/,"$1.000").replace(/\.$/,".000").replace(/\.([0-9])$/,".$1"+"00").replace(/\.([0-9][0-9])$/,".$1"+"0"); | ||
case "#.##": o = Math.round(val*100); | ||
return String(o/100).replace(/^([^\.]+)$/,"$1.").replace(/^0\.$/,"."); | ||
case "#,###": var x = commaify(String(Math.round(aval))); return x !== "0" ? sign + x : ""; | ||
case "#,##0": return sign + commaify(String(Math.round(aval))); | ||
@@ -453,2 +465,11 @@ 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; | ||
LO Formats sometimes leak "GENERAL" or "General" to stand for general format: | ||
``` | ||
case 'G': /* General */ | ||
if(fmt.substr(i, i+6).toLowerCase() !== "general") | ||
throw 'unrecognized character ' + fmt[i] + ' in ' + fmt; | ||
out.push({t:'G',v:'General'}); i+=7; break; | ||
``` | ||
Text between double-quotes are treated literally, and individual characters are | ||
@@ -490,2 +511,5 @@ literal if they are preceded by a slash. | ||
/* Dates */ | ||
case 'M': case 'D': case 'Y': case 'H': case 'S': case 'E': | ||
c = c.toLowerCase(); | ||
/* falls through */ | ||
case 'm': case 'd': case 'y': case 'h': case 's': case 'e': | ||
@@ -505,3 +529,3 @@ ``` | ||
if(!dt) return ""; | ||
o = fmt[i]; while(fmt[++i] === c) o+=c; | ||
o = fmt[i]; while((fmt[++i]||"").toLowerCase() === c) o+=c; | ||
``` | ||
@@ -520,2 +544,3 @@ | ||
if(c === 'h') c = hr; | ||
o = o.toLowerCase(); | ||
q={t:c, v:o}; out.push(q); lst = c; break; | ||
@@ -539,8 +564,8 @@ ``` | ||
else if(fmt.substr(i,5) === "AM/PM") { q.v = dt.H >= 12 ? "PM" : "AM"; q.t = 'T'; i+=5; hr='h'; } | ||
else q.t = "t"; | ||
else { q.t = "t"; i++; } | ||
out.push(q); lst = c; break; | ||
``` | ||
Conditional and color blocks should be handled at one point (TODO). For now, | ||
only the absolute time `[h]` is captured (using the pseudo-type `Z`): | ||
Conditional and color blocks should be handled at one point (TODO). The | ||
pseudo-type `Z` is used to capture absolute time blocks: | ||
@@ -551,3 +576,7 @@ ``` | ||
while(fmt[i++] !== ']') o += fmt[i]; | ||
if(o == "[h]") out.push({t:'Z', v:o}); | ||
if(o.match(/\[[HhMmSs]*\]/)) { | ||
if(!dt) dt = parse_date_code(v, opts); | ||
if(!dt) return ""; | ||
out.push({t:'Z', v:o.toLowerCase()}); | ||
} else { o=""; } | ||
break; | ||
@@ -634,2 +663,3 @@ ``` | ||
i = jj-1; break; | ||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break; | ||
default: throw "unrecognized type " + out[i].t; | ||
@@ -650,11 +680,13 @@ } | ||
```js>tmp/50_date.js | ||
/*jshint -W086 */ | ||
var write_date = function(type, fmt, val) { | ||
if(val < 0) return ""; | ||
var o; | ||
switch(type) { | ||
case 'y': switch(fmt) { /* year */ | ||
case 'y': case 'yy': return pad(val.y % 100,2); | ||
default: return val.y; | ||
} break; | ||
case 'yyy': case 'yyyy': return pad(val.y % 10000,4); | ||
default: throw 'bad year format: ' + fmt; | ||
} | ||
case 'm': switch(fmt) { /* month */ | ||
@@ -667,3 +699,3 @@ case 'm': return val.m; | ||
default: throw 'bad month format: ' + fmt; | ||
} break; | ||
} | ||
case 'd': switch(fmt) { /* day */ | ||
@@ -675,3 +707,3 @@ case 'd': return val.d; | ||
default: throw 'bad day format: ' + fmt; | ||
} break; | ||
} | ||
case 'h': switch(fmt) { /* 12-hour */ | ||
@@ -681,3 +713,3 @@ case 'h': return 1+(val.H+11)%12; | ||
default: throw 'bad hour format: ' + fmt; | ||
} break; | ||
} | ||
case 'H': switch(fmt) { /* 24-hour */ | ||
@@ -687,3 +719,3 @@ case 'h': return val.H; | ||
default: throw 'bad hour format: ' + fmt; | ||
} break; | ||
} | ||
case 'M': switch(fmt) { /* minutes */ | ||
@@ -693,9 +725,11 @@ case 'm': return val.M; | ||
default: throw 'bad minute format: ' + fmt; | ||
} break; | ||
} | ||
case 's': switch(fmt) { /* seconds */ | ||
case 's': return val.S; | ||
case 's': return Math.round(val.S+val.u); | ||
case 'ss': return pad(Math.round(val.S+val.u), 2); | ||
case 'ss.0': var o = pad(Math.round(10*(val.S+val.u)),3); return o.substr(0,2)+"." + o.substr(2); | ||
case 'ss.0': o = pad(Math.round(10*(val.S+val.u)),3); return o.substr(0,2)+"." + o.substr(2); | ||
case 'ss.00': o = pad(Math.round(100*(val.S+val.u)),4); return o.substr(0,2)+"." + o.substr(2); | ||
case 'ss.000': o = pad(Math.round(1000*(val.S+val.u)),5); return o.substr(0,2)+"." + o.substr(2); | ||
default: throw 'bad second format: ' + fmt; | ||
} break; | ||
} | ||
``` | ||
@@ -707,5 +741,7 @@ | ||
case 'Z': switch(fmt) { | ||
case '[h]': return val.D*24+val.H; | ||
case '[h]': case '[hh]': o = val.D*24+val.H; break; | ||
case '[m]': case '[mm]': o = (val.D*24+val.H)*60+val.M; break; | ||
case '[s]': case '[ss]': o = ((val.D*24+val.H)*60+val.M)*60+Math.round(val.S+val.u); break; | ||
default: throw 'bad abstime format: ' + fmt; | ||
} break; | ||
} return fmt.length === 3 ? o : pad(o, 2); | ||
``` | ||
@@ -723,2 +759,3 @@ | ||
}; | ||
/*jshint +W086 */ | ||
``` | ||
@@ -735,4 +772,17 @@ | ||
switch(fmt.length) { | ||
case 1: fmt = [fmt[0], fmt[0], fmt[0], "@"]; break; | ||
case 2: fmt = [fmt[0], fmt[fmt[1] === "@"?0:1], fmt[0], "@"]; break; | ||
``` | ||
In the case of one format, if it contains an "@" then it is a text format. | ||
There is a big TODO here regarding how to best handle this case. | ||
``` | ||
case 1: fmt = fmt[0].indexOf("@")>-1 ? ["General", "General", "General", fmt[0]] : [fmt[0], fmt[0], fmt[0], "@"]; break; | ||
``` | ||
In the case of 2 or 3 formats, if an `@` appears in the last field of the format | ||
it is treated as the text format | ||
``` | ||
case 2: fmt = fmt[1].indexOf("@")>-1 ? [fmt[0], fmt[0], fmt[0], fmt[1]] : [fmt[0], fmt[1], fmt[0], "@"]; break; | ||
case 3: fmt = fmt[2].indexOf("@")>-1 ? [fmt[0], fmt[1], fmt[0], fmt[2]] : [fmt[0], fmt[1], fmt[2], "@"]; break; | ||
case 4: break; | ||
@@ -756,6 +806,15 @@ default: throw "cannot find right format for |" + fmt + "|"; | ||
``` | ||
if(fmt === 0 || (typeof fmt === "string" && fmt.toLowerCase() === "general")) return general_fmt(v, o); | ||
if(typeof fmt === "string" && fmt.toLowerCase() === "general") return general_fmt(v, o); | ||
if(typeof fmt === 'number') fmt = (o.table || table_fmt)[fmt]; | ||
var f = choose_fmt(fmt, v, o); | ||
if(f[1].toLowerCase() === "general") return general_fmt(v,o); | ||
``` | ||
The boolean TRUE and FALSE are formatted as if they are the uppercase text: | ||
``` | ||
if(v === true) v = "TRUE"; if(v === false) v = "FALSE"; | ||
``` | ||
``` | ||
return eval_fmt(f[1], v, o, f[0]); | ||
@@ -807,3 +866,3 @@ }; | ||
if(!mixed) return [0, sgn * P, Q]; | ||
if(Q==0) throw "Unexpected state: "+P+" "+P_1+" "+P_2+" "+Q+" "+Q_1+" "+Q_2; | ||
if(Q===0) throw "Unexpected state: "+P+" "+P_1+" "+P_2+" "+Q+" "+Q_1+" "+Q_2; | ||
var q = Math.floor(sgn * P/Q); | ||
@@ -820,4 +879,3 @@ return [q, sgn*P - q*Q, Q]; | ||
var make_ssf = function(SSF){ | ||
String.prototype.reverse=function(){return this.split("").reverse().join("");}; | ||
var _strrev = function(x) { return String(x).reverse(); }; | ||
var _strrev = function(x) { return String(x).split("").reverse().join("");}; | ||
function fill(c,l) { return new Array(l+1).join(c); } | ||
@@ -855,2 +913,10 @@ function pad(v,d,c){var t=String(v);return t.length>=d?t:(fill(c||0,d-t.length)+t);} | ||
```>.npmignore | ||
test/*.tsv | ||
node_modules/ | ||
tmp/ | ||
.gitignore | ||
.vocrc | ||
``` | ||
```make>Makefile | ||
@@ -863,8 +929,32 @@ .PHONY: test ssf | ||
npm test | ||
.PHONY: lint | ||
lint: | ||
jshint ssf.js test/ | ||
``` | ||
Coverage tests use [blanket](http://npm.im/blanket): | ||
``` | ||
.PHONY: cov | ||
cov: tmp/coverage.html | ||
tmp/coverage.html: ssf.md | ||
mocha --require blanket -R html-cov > tmp/coverage.html | ||
``` | ||
Coveralls.io support | ||
``` | ||
.PHONY: coveralls | ||
coveralls: | ||
mocha --require blanket --reporter mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js | ||
``` | ||
```json>package.json | ||
{ | ||
"name": "ssf", | ||
"version": "0.5.1", | ||
"version": "0.5.2", | ||
"author": "SheetJS", | ||
@@ -889,2 +979,7 @@ "description": "pure-JS library to format data using ECMA-376 spreadsheet Format Codes", | ||
}, | ||
"config": { | ||
"blanket": { | ||
"pattern": "ssf.js" | ||
} | ||
}, | ||
"bugs": { "url": "https://github.com/SheetJS/ssf/issues" }, | ||
@@ -907,2 +1002,6 @@ "license": "Apache-2.0", | ||
- "npm install -g mocha" | ||
- "npm install blanket" | ||
- "npm install coveralls mocha-lcov-reporter" | ||
after_success: | ||
- "make coveralls" | ||
``` | ||
@@ -944,2 +1043,8 @@ | ||
}); | ||
it('should fail for undefined and null', function() { | ||
assert.throws(function() { | ||
SSF.format("General", undefined); | ||
SSF.format("General", null); | ||
}); | ||
}); | ||
}); | ||
@@ -959,3 +1064,3 @@ ``` | ||
it(d[1]+" for "+d[0], skip.indexOf(d[1]) > -1 ? null : function(){ | ||
var expected = d[2], actual = SSF.format(d[1], d[0], {}) | ||
var expected = d[2], actual = SSF.format(d[1], d[0], {}); | ||
//var r = actual.match(/(-?)\d* *\d+\/\d+/); | ||
@@ -968,2 +1073,87 @@ assert.equal(actual, expected); | ||
The dates test driver tests the date and time formats: | ||
```js>test/date.js | ||
/* vim: set ts=2: */ | ||
/*jshint loopfunc:true */ | ||
var SSF = require('../'); | ||
var fs = require('fs'), assert = require('assert'); | ||
var dates = fs.readFileSync('./test/dates.tsv','utf8').split("\n"); | ||
var times = fs.readFileSync('./test/times.tsv','utf8').split("\n"); | ||
function doit(data) { | ||
var step = Math.ceil(data.length/100), i = 1; | ||
var headers = data[0].split("\t"); | ||
for(j=0;j<=100;++j) it(j, function() { | ||
for(var k = 0; k <= step; ++k,++i) { | ||
if(!data[i]) return; | ||
var d = data[i].replace(/#{255}/g,"").split("\t"); | ||
for(var w = 1; w < headers.length; ++w) { | ||
var expected = d[w], actual = SSF.format(headers[w], Number(d[0]), {}); | ||
if(actual != expected) throw [actual, expected, w, headers[w],d[0],d].join("|"); | ||
actual = SSF.format(headers[w].toUpperCase(), Number(d[0]), {}); | ||
if(actual != expected) throw [actual, expected, w, headers[w],d[0],d].join("|"); | ||
} | ||
} | ||
}); | ||
} | ||
describe('time formats', function() { doit(times.slice(0,1000)); }); | ||
describe('date formats', function() { | ||
doit(dates); | ||
it('should fail for bad formats', function() { | ||
var bad = ['yyyyy', 'mmmmmm', 'ddddd']; | ||
var chk = function(fmt){ return function(){ SSF.format(fmt,0); }; }; | ||
bad.forEach(function(fmt){assert.throws(chk(fmt));}); | ||
}); | ||
}); | ||
``` | ||
The exponential test driver tests exponential formats (pipe denotes fails) | ||
```js>test/exp.js | ||
/* vim: set ts=2: */ | ||
/*jshint loopfunc:true */ | ||
var SSF = require('../'); | ||
var fs = require('fs'), assert = require('assert'); | ||
var data = fs.readFileSync('./test/exp.tsv','utf8').split("\n"); | ||
function doit(d, headers) { | ||
it(d[0], function() { | ||
for(var w = 2; w < 3 /*TODO: 1:headers.length */; ++w) { | ||
var expected = d[w].replace("|", ""), actual; | ||
try { actual = SSF.format(headers[w], Number(d[0]), {}); } catch(e) { } | ||
if(actual != expected && d[w][0] !== "|") throw [actual, expected, w, headers[w],d[0],d].join("|"); | ||
} | ||
}); | ||
} | ||
describe('exponential formats', function() { | ||
var headers = data[0].split("\t"); | ||
for(var j=14/* TODO: start from 1 */;j<data.length;++j) { | ||
if(!data[j]) return; | ||
doit(data[j].replace(/#{255}/g,"").split("\t"), headers); | ||
} | ||
}); | ||
``` | ||
The oddities test driver tests random odd formats | ||
```js>test/oddities.js | ||
/* vim: set ts=2: */ | ||
/*jshint loopfunc:true */ | ||
var SSF = require('../'); | ||
var fs = require('fs'), assert = require('assert'); | ||
var data = JSON.parse(fs.readFileSync('./test/oddities.json','utf8')); | ||
describe('oddities', function() { | ||
data.forEach(function(d) { | ||
it(d[0], function(){ | ||
for(j=1;j<d.length;++j) { | ||
if(d[j].length == 2) { | ||
var expected = d[j][1], actual = SSF.format(d[0], d[j][0], {}); | ||
assert.equal(actual, expected); | ||
} else assert.throws(function() { SSF.format(d[0], d[j][0]); }); | ||
} | ||
}); | ||
}); | ||
}); | ||
``` | ||
# LICENSE | ||
@@ -970,0 +1160,0 @@ |
@@ -9,3 +9,3 @@ /* vim: set ts=2: */ | ||
it(d[1]+" for "+d[0], skip.indexOf(d[1]) > -1 ? null : function(){ | ||
var expected = d[2], actual = SSF.format(d[1], d[0], {}) | ||
var expected = d[2], actual = SSF.format(d[1], d[0], {}); | ||
//var r = actual.match(/(-?)\d* *\d+\/\d+/); | ||
@@ -12,0 +12,0 @@ assert.equal(actual, expected); |
@@ -12,2 +12,8 @@ /* vim: set ts=2: */ | ||
}); | ||
it('should fail for undefined and null', function() { | ||
assert.throws(function() { | ||
SSF.format("General", undefined); | ||
SSF.format("General", null); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
99191
14.91%20
33.33%1882
8.16%58
7.41%6
100%