forked from sheetjs/sheetjs
version bump 0.5.2: cleanup
more formats: o fractional seconds o absolute time formats o generalized engineering notation o better resolution in splitting format strings o support for LO uppercase date+time formats other changes - no more prototype pollution - many many more tests - passes jshint - code coverage: blanket + coveralls + travis - npmignore test files
This commit is contained in:
parent
c299585bfb
commit
b0b3ffea84
5
.npmignore
Normal file
5
.npmignore
Normal file
@ -0,0 +1,5 @@
|
||||
test/*.tsv
|
||||
node_modules/
|
||||
tmp/
|
||||
.gitignore
|
||||
.vocrc
|
@ -4,3 +4,7 @@ node_js:
|
||||
- "0.8"
|
||||
before_install:
|
||||
- "npm install -g mocha"
|
||||
- "npm install blanket"
|
||||
- "npm install coveralls mocha-lcov-reporter"
|
||||
after_success:
|
||||
- "make coveralls"
|
||||
|
14
Makefile
14
Makefile
@ -4,3 +4,17 @@ ssf: ssf.md
|
||||
|
||||
test:
|
||||
npm test
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
jshint ssf.js test/
|
||||
|
||||
.PHONY: cov
|
||||
cov: tmp/coverage.html
|
||||
|
||||
tmp/coverage.html: ssf.md
|
||||
mocha --require blanket -R html-cov > tmp/coverage.html
|
||||
|
||||
.PHONY: coveralls
|
||||
coveralls:
|
||||
mocha --require blanket --reporter mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js
|
||||
|
12
README.md
12
README.md
@ -23,11 +23,11 @@ define `DO_NOT_EXPORT_SSF`:
|
||||
|
||||
## Usage
|
||||
|
||||
`.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.
|
||||
|
||||
`.parse_date_code(val, opts)` parses `val` as date code and returns object:
|
||||
|
||||
@ -36,6 +36,10 @@ literal format, then it will be parsed and evaluated.
|
||||
- `H,M,S,u`: (0-23)Hour, Minute, Second, Sub-second
|
||||
- `q`: Day of Week (0=Sunday, 1=Monday, ..., 5=Friday, 6=Saturday)
|
||||
|
||||
`.get_table()` gets the internal format table (number to format mapping).
|
||||
|
||||
`.load_table(table)` sets the internal format table.
|
||||
|
||||
## Notes
|
||||
|
||||
Format code 14 in the spec is broken; the correct format is 'mm/dd/yy' (dashes,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ssf",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.2",
|
||||
"author": "SheetJS",
|
||||
"description": "pure-JS library to format data using ECMA-376 spreadsheet Format Codes",
|
||||
"keywords": [ "format", "sprintf", "spreadsheet" ],
|
||||
@ -20,6 +20,11 @@
|
||||
"bin": {
|
||||
"ssf": "./bin/ssf.njs"
|
||||
},
|
||||
"config": {
|
||||
"blanket": {
|
||||
"pattern": "ssf.js"
|
||||
}
|
||||
},
|
||||
"bugs": { "url": "https://github.com/SheetJS/ssf/issues" },
|
||||
"license": "Apache-2.0",
|
||||
"engines": { "node": ">=0.8" }
|
||||
|
87
ssf.js
87
ssf.js
@ -1,8 +1,7 @@
|
||||
/* 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); }
|
||||
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));}
|
||||
@ -14,6 +13,7 @@ opts_fmt.date1904 = 0;
|
||||
opts_fmt.output = "";
|
||||
opts_fmt.mode = "";
|
||||
var table_fmt = {
|
||||
0: 'General',
|
||||
1: '0',
|
||||
2: '0.00',
|
||||
3: '#,##0',
|
||||
@ -83,7 +83,7 @@ var frac = function frac(x, D, mixed) {
|
||||
if(Q > D) { Q = Q_1; P = P_1; }
|
||||
if(Q > D) { Q = Q_2; P = P_2; }
|
||||
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);
|
||||
return [q, sgn*P - q*Q, Q];
|
||||
};
|
||||
@ -113,7 +113,7 @@ var general_fmt = function(v) {
|
||||
};
|
||||
SSF._general = general_fmt;
|
||||
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||{}));
|
||||
if(opts.date1904) date += 1462;
|
||||
if(date > 2958465) return null;
|
||||
@ -126,7 +126,7 @@ var parse_date_code = function parse_date_code(v,opts) {
|
||||
d.setDate(d.getDate() + date - 1);
|
||||
dout = [d.getFullYear(), d.getMonth()+1,d.getDate()];
|
||||
dow = d.getDay();
|
||||
if(opts.mode === 'excel' && date < 60) dow = (dow + 6) % 7;
|
||||
if(/* opts.mode === 'excel' && */ date < 60) dow = (dow + 6) % 7;
|
||||
}
|
||||
out.y = dout[0]; out.m = dout[1]; out.d = dout[2];
|
||||
out.S = time % 60; time = Math.floor(time / 60);
|
||||
@ -136,13 +136,16 @@ var parse_date_code = function parse_date_code(v,opts) {
|
||||
return out;
|
||||
};
|
||||
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 */
|
||||
case 'm': return val.m;
|
||||
case 'mm': return pad(val.m,2);
|
||||
@ -150,47 +153,51 @@ var write_date = function(type, fmt, val) {
|
||||
case 'mmmm': return months[val.m-1][2];
|
||||
case 'mmmmm': return months[val.m-1][0];
|
||||
default: throw 'bad month format: ' + fmt;
|
||||
} break;
|
||||
}
|
||||
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;
|
||||
} break;
|
||||
}
|
||||
case 'h': switch(fmt) { /* 12-hour */
|
||||
case 'h': return 1+(val.H+11)%12;
|
||||
case 'hh': return pad(1+(val.H+11)%12, 2);
|
||||
default: throw 'bad hour format: ' + fmt;
|
||||
} break;
|
||||
}
|
||||
case 'H': switch(fmt) { /* 24-hour */
|
||||
case 'h': return val.H;
|
||||
case 'hh': return pad(val.H, 2);
|
||||
default: throw 'bad hour format: ' + fmt;
|
||||
} break;
|
||||
}
|
||||
case 'M': switch(fmt) { /* minutes */
|
||||
case 'm': return val.M;
|
||||
case 'mm': return pad(val.M, 2);
|
||||
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 */
|
||||
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(/^,/,""); };
|
||||
/*jshint +W086 */
|
||||
var commaify = function(s) { return _strrev(_strrev(s).replace(/.../g,"$&,")).replace(/^,/,""); };
|
||||
var write_num = function(type, fmt, val) {
|
||||
if(type === '(') {
|
||||
var ffmt = fmt.replace(/\( */,"").replace(/ \)/,"").replace(/\)/,"");
|
||||
@ -202,15 +209,17 @@ 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.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);
|
||||
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");
|
||||
@ -223,6 +232,8 @@ 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[2].length) : pad(myn,r[1].length," ") + "/" + pad(myd,r[2].length));
|
||||
}
|
||||
if(fmt.match(/^00*$/)) return (val<0?"-":"")+pad(Math.round(aval),fmt.length);
|
||||
if(fmt.match(/^####*$/)) return Math.round(val);
|
||||
switch(fmt) {
|
||||
case "0": return Math.round(val);
|
||||
case "0.0": o = Math.round(val*10);
|
||||
@ -231,6 +242,9 @@ var write_num = function(type, fmt, val) {
|
||||
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 "#.##": 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)));
|
||||
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);
|
||||
@ -264,6 +278,10 @@ function eval_fmt(fmt, v, opts, flen) {
|
||||
/* Tokenize */
|
||||
while(i < fmt.length) {
|
||||
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 */
|
||||
for(o="";fmt[++i] !== '"' && i < fmt.length;) o += fmt[i];
|
||||
out.push({t:'t', v:o}); ++i; break;
|
||||
@ -273,14 +291,18 @@ function eval_fmt(fmt, v, opts, flen) {
|
||||
case '@': /* Text Placeholder */
|
||||
out.push({t:'T', v:v}); ++i; break;
|
||||
/* 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':
|
||||
if(v < 0) return "";
|
||||
if(!dt) dt = parse_date_code(v, opts);
|
||||
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;
|
||||
case 'A':
|
||||
if(!dt) dt = parse_date_code(v, opts);
|
||||
@ -288,12 +310,16 @@ function eval_fmt(fmt, v, opts, flen) {
|
||||
q={t:c,v:"A"};
|
||||
if(fmt.substr(i, 3) === "A/P") {q.v = dt.H >= 12 ? "P" : "A"; q.t = 'T'; hr='h';i+=3;}
|
||||
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;
|
||||
case '[': /* TODO: Fix this -- ignore all conditionals and formatting */
|
||||
o = c;
|
||||
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;
|
||||
/* Numbers */
|
||||
case '0': case '#':
|
||||
@ -340,6 +366,7 @@ function eval_fmt(fmt, v, opts, flen) {
|
||||
out[i].v = write_num(out[i].t, out[i].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: throw "unrecognized type " + out[i].t;
|
||||
}
|
||||
}
|
||||
@ -351,8 +378,9 @@ function choose_fmt(fmt, v, o) {
|
||||
if(typeof fmt === "string") fmt = split_fmt(fmt);
|
||||
var l = fmt.length;
|
||||
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;
|
||||
default: throw "cannot find right format for |" + fmt + "|";
|
||||
}
|
||||
@ -361,10 +389,11 @@ function choose_fmt(fmt, v, o) {
|
||||
}
|
||||
var format = function format(fmt,v,o) {
|
||||
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]);
|
||||
};
|
||||
|
||||
|
258
ssf.md
258
ssf.md
@ -180,6 +180,7 @@ None of the international formats are included here.
|
||||
|
||||
```js>tmp/20_consts.js
|
||||
var table_fmt = {
|
||||
0: 'General',
|
||||
1: '0',
|
||||
2: '0.00',
|
||||
3: '#,##0',
|
||||
@ -262,7 +263,7 @@ portion of a 24 hour day).
|
||||
|
||||
```js>tmp/50_date.js
|
||||
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||{}));
|
||||
```
|
||||
|
||||
@ -310,7 +311,7 @@ Saturday. The "right" thing to do is to keep the DOW consistent and just break
|
||||
the fact that there are two Wednesdays in that "week".
|
||||
|
||||
```
|
||||
if(opts.mode === 'excel' && date < 60) dow = (dow + 6) % 7;
|
||||
if(/* opts.mode === 'excel' && */ date < 60) dow = (dow + 6) % 7;
|
||||
}
|
||||
```
|
||||
|
||||
@ -330,8 +331,7 @@ SSF.parse_date_code = parse_date_code;
|
||||
## Evaluating Number Formats
|
||||
|
||||
```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) {
|
||||
```
|
||||
|
||||
@ -364,9 +364,11 @@ For exponents, get the exponent and mantissa and format them separately:
|
||||
For the special case of engineering notation, "shift" the decimal:
|
||||
|
||||
```
|
||||
//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]/)) {
|
||||
```
|
||||
|
||||
@ -375,9 +377,9 @@ TODO: something reasonable
|
||||
```
|
||||
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);
|
||||
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");
|
||||
@ -402,6 +404,13 @@ Fractions with known denominator are resolved by rounding:
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```js>tmp/60_number.js
|
||||
@ -413,6 +422,9 @@ The default cases are hard-coded. TODO: actually parse them
|
||||
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 "#.##": 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)));
|
||||
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);
|
||||
@ -442,6 +454,15 @@ function eval_fmt(fmt, v, opts, flen) {
|
||||
switch((c = fmt[i])) {
|
||||
```
|
||||
|
||||
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
|
||||
literal if they are preceded by a slash.
|
||||
|
||||
@ -478,6 +499,9 @@ The date codes `m,d,y,h,s` are standard. There are some special formats like
|
||||
|
||||
```
|
||||
/* 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':
|
||||
```
|
||||
|
||||
@ -492,7 +516,7 @@ Merge strings like "mmmmm" or "hh" into one block:
|
||||
```
|
||||
if(!dt) dt = parse_date_code(v, opts);
|
||||
if(!dt) return "";
|
||||
o = fmt[i]; while(fmt[++i] === c) o+=c;
|
||||
o = fmt[i]; while((fmt[++i]||"").toLowerCase() === c) o+=c;
|
||||
```
|
||||
|
||||
For the special case of s.00, the suffix should be swallowed with the s:
|
||||
@ -506,6 +530,7 @@ Only the forward corrections are made here. The reverse corrections are made la
|
||||
```
|
||||
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;
|
||||
```
|
||||
|
||||
@ -524,18 +549,22 @@ the HH/hh jazz. TODO: investigate this further.
|
||||
q={t:c,v:"A"};
|
||||
if(fmt.substr(i, 3) === "A/P") {q.v = dt.H >= 12 ? "P" : "A"; q.t = 'T'; hr='h';i+=3;}
|
||||
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:
|
||||
|
||||
```
|
||||
case '[': /* TODO: Fix this -- ignore all conditionals and formatting */
|
||||
o = c;
|
||||
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;
|
||||
```
|
||||
|
||||
@ -617,6 +646,7 @@ The default magic characters are listed in subsubsections 18.8.30-31 of ECMA376:
|
||||
out[i].v = write_num(out[i].t, out[i].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: throw "unrecognized type " + out[i].t;
|
||||
}
|
||||
}
|
||||
@ -632,15 +662,17 @@ There is some overloading of the `m` character. According to the spec:
|
||||
hours) or immediately before the "ss" code (for seconds), the application shall
|
||||
display minutes instead of the month.
|
||||
|
||||
|
||||
```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 */
|
||||
case 'm': return val.m;
|
||||
case 'mm': return pad(val.m,2);
|
||||
@ -648,44 +680,48 @@ var write_date = function(type, fmt, val) {
|
||||
case 'mmmm': return months[val.m-1][2];
|
||||
case 'mmmmm': return months[val.m-1][0];
|
||||
default: throw 'bad month format: ' + fmt;
|
||||
} break;
|
||||
}
|
||||
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;
|
||||
} break;
|
||||
}
|
||||
case 'h': switch(fmt) { /* 12-hour */
|
||||
case 'h': return 1+(val.H+11)%12;
|
||||
case 'hh': return pad(1+(val.H+11)%12, 2);
|
||||
default: throw 'bad hour format: ' + fmt;
|
||||
} break;
|
||||
}
|
||||
case 'H': switch(fmt) { /* 24-hour */
|
||||
case 'h': return val.H;
|
||||
case 'hh': return pad(val.H, 2);
|
||||
default: throw 'bad hour format: ' + fmt;
|
||||
} break;
|
||||
}
|
||||
case 'M': switch(fmt) { /* minutes */
|
||||
case 'm': return val.M;
|
||||
case 'mm': return pad(val.M, 2);
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
The `Z` type refers to absolute time measures:
|
||||
|
||||
```
|
||||
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);
|
||||
```
|
||||
|
||||
The `e` format behavior in excel diverges from the spec. It claims that `ee`
|
||||
@ -698,6 +734,7 @@ should be a two-digit year, but `ee` in excel is actually the four-digit year:
|
||||
default: throw 'bad format type ' + type + ' in ' + fmt;
|
||||
}
|
||||
};
|
||||
/*jshint +W086 */
|
||||
```
|
||||
|
||||
Based on the value, `choose_fmt` picks the right format string. If formats have
|
||||
@ -709,8 +746,21 @@ function choose_fmt(fmt, v, o) {
|
||||
if(typeof fmt === "string") fmt = split_fmt(fmt);
|
||||
var l = fmt.length;
|
||||
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;
|
||||
default: throw "cannot find right format for |" + fmt + "|";
|
||||
}
|
||||
@ -729,10 +779,19 @@ var format = function format(fmt,v,o) {
|
||||
LibreOffice appears to emit the format "GENERAL" for general:
|
||||
|
||||
```
|
||||
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]);
|
||||
};
|
||||
|
||||
@ -779,7 +838,7 @@ var frac = function frac(x, D, mixed) {
|
||||
if(Q > D) { Q = Q_1; P = P_1; }
|
||||
if(Q > D) { Q = Q_2; P = P_2; }
|
||||
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);
|
||||
return [q, sgn*P - q*Q, Q];
|
||||
};
|
||||
@ -791,8 +850,7 @@ var frac = function frac(x, D, mixed) {
|
||||
/* 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); }
|
||||
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));}
|
||||
@ -825,6 +883,14 @@ node_modules/
|
||||
.vocrc
|
||||
```
|
||||
|
||||
```>.npmignore
|
||||
test/*.tsv
|
||||
node_modules/
|
||||
tmp/
|
||||
.gitignore
|
||||
.vocrc
|
||||
```
|
||||
|
||||
```make>Makefile
|
||||
.PHONY: test ssf
|
||||
ssf: ssf.md
|
||||
@ -832,12 +898,36 @@ ssf: ssf.md
|
||||
|
||||
test:
|
||||
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",
|
||||
"description": "pure-JS library to format data using ECMA-376 spreadsheet Format Codes",
|
||||
"keywords": [ "format", "sprintf", "spreadsheet" ],
|
||||
@ -857,6 +947,11 @@ test:
|
||||
"bin": {
|
||||
"ssf": "./bin/ssf.njs"
|
||||
},
|
||||
"config": {
|
||||
"blanket": {
|
||||
"pattern": "ssf.js"
|
||||
}
|
||||
},
|
||||
"bugs": { "url": "https://github.com/SheetJS/ssf/issues" },
|
||||
"license": "Apache-2.0",
|
||||
"engines": { "node": ">=0.8" }
|
||||
@ -874,6 +969,10 @@ node_js:
|
||||
- "0.8"
|
||||
before_install:
|
||||
- "npm install -g mocha"
|
||||
- "npm install blanket"
|
||||
- "npm install coveralls mocha-lcov-reporter"
|
||||
after_success:
|
||||
- "make coveralls"
|
||||
```
|
||||
|
||||
The mocha test driver tests the implied formats:
|
||||
@ -910,6 +1009,12 @@ describe('General format', function() {
|
||||
assert.equal(SSF.format(d[1], d[0], {}), d[2]);
|
||||
});
|
||||
});
|
||||
it('should fail for undefined and null', function() {
|
||||
assert.throws(function() {
|
||||
SSF.format("General", undefined);
|
||||
SSF.format("General", null);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@ -924,7 +1029,7 @@ var skip = [];
|
||||
describe('fractional formats', function() {
|
||||
data.forEach(function(d) {
|
||||
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+/);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
@ -932,6 +1037,91 @@ describe('fractional formats', function() {
|
||||
});
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```>LICENSE
|
||||
|
32
test/date.js
Normal file
32
test/date.js
Normal file
@ -0,0 +1,32 @@
|
||||
/* 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));});
|
||||
});
|
||||
});
|
295849
test/dates.tsv
Normal file
295849
test/dates.tsv
Normal file
File diff suppressed because it is too large
Load Diff
21
test/exp.js
Normal file
21
test/exp.js
Normal file
@ -0,0 +1,21 @@
|
||||
/* 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);
|
||||
}
|
||||
});
|
46
test/exp.tsv
Normal file
46
test/exp.tsv
Normal file
@ -0,0 +1,46 @@
|
||||
value #0.0E+0 ##0.0E+0 ###0.0E+0 ####0.0E+0
|
||||
1.23457E-13 12.3E-14 123.5E-15 1234.6E-16 123.5E-15
|
||||
1.23457E-12 1.2E-12 1.2E-12 1.2E-12 1234.6E-15
|
||||
1.23457E-11 12.3E-12 12.3E-12 12.3E-12 12345.7E-15
|
||||
1.23457E-10 1.2E-10 123.5E-12 123.5E-12 1.2E-10
|
||||
1.23457E-09 12.3E-10 1.2E-9 1234.6E-12 12.3E-10
|
||||
1.23457E-08 1.2E-8 12.3E-9 1.2E-8 123.5E-10
|
||||
0.000000123457 12.3E-8 123.5E-9 12.3E-8 1234.6E-10
|
||||
0.00000123457 1.2E-6 1.2E-6 123.5E-8 12345.7E-10
|
||||
0.0000123457 12.3E-6 12.3E-6 1234.6E-8 1.2E-5
|
||||
0.000123457 1.2E-4 123.5E-6 1.2E-4 12.3E-5
|
||||
0.001234568 12.3E-4 1.2E-3 12.3E-4 123.5E-5
|
||||
0.012345679 1.2E-2 12.3E-3 123.5E-4 1234.6E-5
|
||||
0.123456789 12.3E-2 123.5E-3 1234.6E-4 12345.7E-5
|
||||
1.23456789 1.2E+0 |1.2E+0 1.2E+0 1.2E+0
|
||||
12.3456789 12.3E+0 |12.3E+0 12.3E+0 12.3E+0
|
||||
123.456789 1.2E+2 |123.5E+0 123.5E+0 123.5E+0
|
||||
1234.56789 12.3E+2 1.2E+3 1234.6E+0 1234.6E+0
|
||||
12345.6789 1.2E+4 12.3E+3 1.2E+4 12345.7E+0
|
||||
123456.789 12.3E+4 123.5E+3 12.3E+4 1.2E+5
|
||||
1234567.89 1.2E+6 1.2E+6 123.5E+4 12.3E+5
|
||||
12345678.9 12.3E+6 12.3E+6 1234.6E+4 123.5E+5
|
||||
123456789 1.2E+8 123.5E+6 1.2E+8 1234.6E+5
|
||||
1234567890 12.3E+8 1.2E+9 12.3E+8 12345.7E+5
|
||||
12345678900 1.2E+10 12.3E+9 123.5E+8 1.2E+10
|
||||
123456789000 12.3E+10 123.5E+9 1234.6E+8 12.3E+10
|
||||
1234567890000 1.2E+12 1.2E+12 1.2E+12 123.5E+10
|
||||
12345678900000 12.3E+12 12.3E+12 12.3E+12 1234.6E+10
|
||||
123456789000000 1.2E+14 123.5E+12 123.5E+12 12345.7E+10
|
||||
1234567890000000 12.3E+14 1.2E+15 1234.6E+12 1.2E+15
|
||||
12345678900000000 1.2E+16 12.3E+15 1.2E+16 12.3E+15
|
||||
123456789000000000 12.3E+16 123.5E+15 12.3E+16 123.5E+15
|
||||
1234567890000000000 1.2E+18 1.2E+18 123.5E+16 1234.6E+15
|
||||
12345678900000000000 12.3E+18 12.3E+18 1234.6E+16 12345.7E+15
|
||||
123456789000000000000 1.2E+20 123.5E+18 1.2E+20 1.2E+20
|
||||
1234567890000000000000 12.3E+20 1.2E+21 12.3E+20 12.3E+20
|
||||
12345678900000000000000 1.2E+22 12.3E+21 123.5E+20 123.5E+20
|
||||
123456789000000000000000 12.3E+22 123.5E+21 1234.6E+20 1234.6E+20
|
||||
1234567890000000000000000 1.2E+24 1.2E+24 1.2E+24 12345.7E+20
|
||||
12345678900000000000000000 12.3E+24 12.3E+24 12.3E+24 1.2E+25
|
||||
123456789000000000000000000 1.2E+26 123.5E+24 123.5E+24 12.3E+25
|
||||
1234567890000000000000000000 12.3E+26 1.2E+27 1234.6E+24 123.5E+25
|
||||
12345678900000000000000000000 1.2E+28 12.3E+27 1.2E+28 |1234.6E+25
|
||||
123456789000000000000000000000 |12.3E+28 123.5E+27 |12.3E+28 |12345.7E+25
|
||||
1234567890000000000000000000000 1.2E+30 1.2E+30 |123.5E+28 1.2E+30
|
||||
12345678900000000000000000000000 |12.3E+30 12.3E+30 |1234.6E+28 |12.3E+30
|
|
@ -6,7 +6,7 @@ var skip = [];
|
||||
describe('fractional formats', function() {
|
||||
data.forEach(function(d) {
|
||||
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+/);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
@ -9,4 +9,10 @@ describe('General format', function() {
|
||||
assert.equal(SSF.format(d[1], d[0], {}), d[2]);
|
||||
});
|
||||
});
|
||||
it('should fail for undefined and null', function() {
|
||||
assert.throws(function() {
|
||||
SSF.format("General", undefined);
|
||||
SSF.format("General", null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
17
test/oddities.js
Normal file
17
test/oddities.js
Normal file
@ -0,0 +1,17 @@
|
||||
/* 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]); });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
38
test/oddities.json
Normal file
38
test/oddities.json
Normal file
@ -0,0 +1,38 @@
|
||||
[
|
||||
["\"foo\";\"bar\";\"baz\";\"qux\"",
|
||||
[1, "foo"], [-1, "bar"], [0, "baz"], ["sheetjs", "qux"]
|
||||
],
|
||||
["\"foo\";\"bar\";\"baz\"",
|
||||
[1, "foo"], [-1, "bar"], [0, "baz"], ["sheetjs", "sheetjs"]
|
||||
],
|
||||
["\"foo\";\"bar\";@",
|
||||
[1, "foo"], [-1, "bar"], [0, "foo"], ["sheetjs", "sheetjs"]
|
||||
],
|
||||
["\"foo\";\"bar\"",
|
||||
[1, "foo"], [-1, "bar"], [0, "foo"], ["sheetjs", "sheetjs"]
|
||||
],
|
||||
["@@", [1, "1"], [-1, "-1"], [0, "0"], ["sheetjs", "sheetjssheetjs"]],
|
||||
["[Blue]General", [1, "1"], [-1, "-1"], [0, "0"], ["sheetjs", "sheetjs"]],
|
||||
["[Blue]G3neral", [1], [-1], [0], ["TODO","TODO"]],
|
||||
["A\"TODO\"", [1, "ATODO"], [-1, "ATODO"], [0, "ATODO"], ["TODO","TODO"]],
|
||||
["r", [1], [-1], [0], ["TODO","TODO"]],
|
||||
["((;@", [1], [-1], [0], ["TODO","TODO"]],
|
||||
["\\r", [1, "r"], [-1, "r"], [0, "r"], ["TODO","TODO"]],
|
||||
["_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)", [1, " $1 "], [-1, " $(1)"], [0," $- "], ["TODO", " TODO "]],
|
||||
["#,##0.0", [1, "1.0"], [-1, "-1.0"], [0,"0.0"], ["TODO", "TODO"]],
|
||||
["#,###", [1, "1"], [-1, "-1"], [0,""], ["TODO", "TODO"]],
|
||||
["#.##", [1, "1."], [-1, "-1."], [0,"."], ["sheetjs", "sheetjs"]],
|
||||
["0;0", [1.1, "1"], [-1.1, "-1"], [0,"0"], ["sheetjs", "sheetjs"]],
|
||||
["0.0", [1, "1.0"], [-1, "-1.0"], [0,"0.0"], ["sheetjs", "sheetjs"]],
|
||||
["0.00", [1, "1.00"], [-1, "-1.00"], [0,"0.00"], ["sheetjs", "sheetjs"]],
|
||||
["0.000", [1, "1.000"], [-1, "-1.000"], [0,"0.000"], ["sheetjs", "sheetjs"]],
|
||||
["hh:mm AM/PM", [0.7, "04:48 PM"]],
|
||||
["hhh:mm AM/PM", [0.7]],
|
||||
["hhh:mmm:sss", [0.7]],
|
||||
["hh:mmm:sss", [0.7]],
|
||||
["hh:mm:sss", [0.7]],
|
||||
["[hhh]", [0.7]],
|
||||
["A/P", [0.7, "P"]],
|
||||
["e", [0.7, "1900"]],
|
||||
["\"foo\";\"bar\";\"baz\";\"qux\";\"foobar\"", [1], [0], [-1], ["sheetjs"]]
|
||||
]
|
1048576
test/times.tsv
Normal file
1048576
test/times.tsv
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user