forked from sheetjs/sheetjs
version bump 0.5.7: addressing extraneous '['
- extraneous '[' does not cause infinite loop - dates follow excel form (`yyyyyy` treated as `yyyy`) - more general exponential form (more tests) - unreachable default cases removed - 100% test coverage - added test_min and cov_min targets
This commit is contained in:
parent
71a974653d
commit
a866c9eabf
9
Makefile
9
Makefile
@ -5,6 +5,9 @@ ssf: ssf.md
|
||||
test:
|
||||
npm test
|
||||
|
||||
test_min:
|
||||
MINTEST=1 npm test
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
jshint ssf.js test/
|
||||
@ -12,9 +15,13 @@ lint:
|
||||
.PHONY: cov
|
||||
cov: tmp/coverage.html
|
||||
|
||||
tmp/coverage.html: ssf.md
|
||||
tmp/coverage.html: ssf
|
||||
mocha --require blanket -R html-cov > tmp/coverage.html
|
||||
|
||||
.PHONY: cov_min
|
||||
cov_min:
|
||||
MINTEST=1 make cov
|
||||
|
||||
.PHONY: coveralls
|
||||
coveralls:
|
||||
mocha --require blanket --reporter mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ssf",
|
||||
"version": "0.5.6",
|
||||
"version": "0.5.7",
|
||||
"author": "SheetJS",
|
||||
"description": "pure-JS library to format data using ECMA-376 spreadsheet Format Codes",
|
||||
"keywords": [ "format", "sprintf", "spreadsheet" ],
|
||||
|
22
ssf.js
22
ssf.js
@ -5,7 +5,7 @@ var _strrev = function(x) { return String(x).split("").reverse().join("");};
|
||||
function fill(c,l) { return new Array(l+1).join(c); }
|
||||
function pad(v,d,c){var t=String(v);return t.length>=d?t:(fill(c||0,d-t.length)+t);}
|
||||
function rpad(v,d,c){var t=String(v);return t.length>=d?t:(t+fill(c||0,d-t.length));}
|
||||
SSF.version = '0.5.6';
|
||||
SSF.version = '0.5.7';
|
||||
/* Options */
|
||||
var opts_fmt = {};
|
||||
function fixopts(o){for(var y in opts_fmt) if(o[y]===undefined) o[y]=opts_fmt[y];}
|
||||
@ -146,23 +146,20 @@ var write_date = function(type, fmt, val) {
|
||||
switch(type) {
|
||||
case 'y': switch(fmt) { /* year */
|
||||
case 'y': case 'yy': return pad(val.y % 100,2);
|
||||
case 'yyy': case 'yyyy': return pad(val.y % 10000,4);
|
||||
default: throw 'bad year format: ' + fmt;
|
||||
default: return pad(val.y % 10000,4);
|
||||
}
|
||||
case 'm': switch(fmt) { /* month */
|
||||
case 'm': return val.m;
|
||||
case 'mm': return pad(val.m,2);
|
||||
case 'mmm': return months[val.m-1][1];
|
||||
case 'mmmm': return months[val.m-1][2];
|
||||
case 'mmmmm': return months[val.m-1][0];
|
||||
default: throw 'bad month format: ' + fmt;
|
||||
default: return months[val.m-1][2];
|
||||
}
|
||||
case 'd': switch(fmt) { /* day */
|
||||
case 'd': return val.d;
|
||||
case 'dd': return pad(val.d,2);
|
||||
case 'ddd': return days[val.q][0];
|
||||
case 'dddd': return days[val.q][1];
|
||||
default: throw 'bad day format: ' + fmt;
|
||||
default: return days[val.q][1];
|
||||
}
|
||||
case 'h': switch(fmt) { /* 12-hour */
|
||||
case 'h': return 1+(val.H+11)%12;
|
||||
@ -195,7 +192,6 @@ var write_date = function(type, fmt, val) {
|
||||
} return fmt.length === 3 ? o : pad(o, 2);
|
||||
/* TODO: handle the ECMA spec format ee -> yy */
|
||||
case 'e': { return val.y; } break;
|
||||
default: throw 'bad format type ' + type + ' in ' + fmt;
|
||||
}
|
||||
};
|
||||
/*jshint +W086 */
|
||||
@ -211,7 +207,7 @@ var write_num = function(type, fmt, val) {
|
||||
if(mul !== 0) return write_num(type, fmt, val * Math.pow(10,2*mul)) + fill("%",mul);
|
||||
if(fmt.indexOf("E") > -1) {
|
||||
var idx = fmt.indexOf("E") - fmt.indexOf(".") - 1;
|
||||
if(fmt == '##0.0E+0') {
|
||||
if(fmt.match(/^#+0.0E\+0$/)) {
|
||||
var period = fmt.indexOf("."); if(period === -1) period=fmt.indexOf('E');
|
||||
var ee = (Number(val.toExponential(0).substr(2+(val<0))))%period;
|
||||
if(ee < 0) ee += period;
|
||||
@ -329,7 +325,8 @@ function eval_fmt(fmt, v, opts, flen) {
|
||||
out.push(q); lst = c; break;
|
||||
case '[': /* TODO: Fix this -- ignore all conditionals and formatting */
|
||||
o = c;
|
||||
while(fmt[i++] !== ']') o += fmt[i];
|
||||
while(fmt[i++] !== ']' && i < fmt.length) o += fmt[i];
|
||||
if(o.substr(-1) !== ']') throw 'unterminated "[" block: |' + o + '|';
|
||||
if(o.match(/\[[HhMmSs]*\]/)) {
|
||||
if(!dt) dt = parse_date_code(v, opts);
|
||||
if(!dt) return "";
|
||||
@ -368,8 +365,8 @@ function eval_fmt(fmt, v, opts, flen) {
|
||||
/* replace fields */
|
||||
for(i=0; i < out.length; ++i) {
|
||||
switch(out[i].t) {
|
||||
case 't': case 'T': case ' ': break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'A': case 'e': case 'Z':
|
||||
case 't': case 'T': case ' ': case 'D': break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'Z':
|
||||
out[i].v = write_date(out[i].t, out[i].v, dt);
|
||||
out[i].t = 't'; break;
|
||||
case 'n': case '(': case '?':
|
||||
@ -382,7 +379,6 @@ function eval_fmt(fmt, v, opts, flen) {
|
||||
out[i].t = 't';
|
||||
i = jj-1; break;
|
||||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break;
|
||||
default: console.error(out); throw "unrecognized type " + out[i].t;
|
||||
}
|
||||
}
|
||||
return out.map(function(x){return x.v;}).join("");
|
||||
|
71
ssf.md
71
ssf.md
@ -376,7 +376,7 @@ For exponents, get the exponent and mantissa and format them separately:
|
||||
For the special case of engineering notation, "shift" the decimal:
|
||||
|
||||
```
|
||||
if(fmt == '##0.0E+0') {
|
||||
if(fmt.match(/^#+0.0E\+0$/)) {
|
||||
var period = fmt.indexOf("."); if(period === -1) period=fmt.indexOf('E');
|
||||
var ee = (Number(val.toExponential(0).substr(2+(val<0))))%period;
|
||||
if(ee < 0) ee += period;
|
||||
@ -589,7 +589,8 @@ 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];
|
||||
while(fmt[i++] !== ']' && i < fmt.length) o += fmt[i];
|
||||
if(o.substr(-1) !== ']') throw 'unterminated "[" block: |' + o + '|';
|
||||
if(o.match(/\[[HhMmSs]*\]/)) {
|
||||
if(!dt) dt = parse_date_code(v, opts);
|
||||
if(!dt) return "";
|
||||
@ -663,8 +664,8 @@ The default magic characters are listed in subsubsections 18.8.30-31 of ECMA376:
|
||||
/* replace fields */
|
||||
for(i=0; i < out.length; ++i) {
|
||||
switch(out[i].t) {
|
||||
case 't': case 'T': case ' ': break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'A': case 'e': case 'Z':
|
||||
case 't': case 'T': case ' ': case 'D': break;
|
||||
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'Z':
|
||||
out[i].v = write_date(out[i].t, out[i].v, dt);
|
||||
out[i].t = 't'; break;
|
||||
case 'n': case '(': case '?':
|
||||
@ -683,7 +684,12 @@ positive when there is an explicit hyphen before it (e.g. `#,##0.0;-#,##0.0`):
|
||||
out[i].t = 't';
|
||||
i = jj-1; break;
|
||||
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break;
|
||||
default: console.error(out); throw "unrecognized type " + out[i].t;
|
||||
```
|
||||
|
||||
The default case should not be reachable. In testing, add the line
|
||||
`default: console.error(out); throw "unrecognized type " + out[i].t;`
|
||||
|
||||
```
|
||||
}
|
||||
}
|
||||
return out.map(function(x){return x.v;}).join("");
|
||||
@ -706,23 +712,35 @@ var write_date = function(type, fmt, val) {
|
||||
switch(type) {
|
||||
case 'y': switch(fmt) { /* year */
|
||||
case 'y': case 'yy': return pad(val.y % 100,2);
|
||||
case 'yyy': case 'yyyy': return pad(val.y % 10000,4);
|
||||
default: throw 'bad year format: ' + fmt;
|
||||
```
|
||||
|
||||
Apparently, even `yyyyyyyyyyyyyyyyyyyy` is a 4 digit year
|
||||
|
||||
```
|
||||
default: return pad(val.y % 10000,4);
|
||||
}
|
||||
case 'm': switch(fmt) { /* month */
|
||||
case 'm': return val.m;
|
||||
case 'mm': return pad(val.m,2);
|
||||
case 'mmm': return months[val.m-1][1];
|
||||
case 'mmmm': return months[val.m-1][2];
|
||||
case 'mmmmm': return months[val.m-1][0];
|
||||
default: throw 'bad month format: ' + fmt;
|
||||
```
|
||||
|
||||
Strangely enough, `mmmmmmmmmmmmmmmmmmmm` is treated as the full month name:
|
||||
|
||||
```
|
||||
default: return months[val.m-1][2];
|
||||
}
|
||||
case 'd': switch(fmt) { /* day */
|
||||
case 'd': return val.d;
|
||||
case 'dd': return pad(val.d,2);
|
||||
case 'ddd': return days[val.q][0];
|
||||
case 'dddd': return days[val.q][1];
|
||||
default: throw 'bad day format: ' + fmt;
|
||||
```
|
||||
|
||||
Strangely enough, `dddddddddddddddddddd` is treated as the full day name:
|
||||
|
||||
```
|
||||
default: return days[val.q][1];
|
||||
}
|
||||
case 'h': switch(fmt) { /* 12-hour */
|
||||
case 'h': return 1+(val.H+11)%12;
|
||||
@ -766,7 +784,12 @@ should be a two-digit year, but `ee` in excel is actually the four-digit year:
|
||||
```
|
||||
/* TODO: handle the ECMA spec format ee -> yy */
|
||||
case 'e': { return val.y; } break;
|
||||
default: throw 'bad format type ' + type + ' in ' + fmt;
|
||||
```
|
||||
|
||||
There is no input to the function that ends up triggering the default behavior:
|
||||
it is not exported and is only called when the type is in `ymdhHMsZe`
|
||||
|
||||
```
|
||||
}
|
||||
};
|
||||
/*jshint +W086 */
|
||||
@ -939,6 +962,9 @@ ssf: ssf.md
|
||||
test:
|
||||
npm test
|
||||
|
||||
test_min:
|
||||
MINTEST=1 npm test
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
jshint ssf.js test/
|
||||
@ -951,8 +977,12 @@ Coverage tests use [blanket](http://npm.im/blanket):
|
||||
.PHONY: cov
|
||||
cov: tmp/coverage.html
|
||||
|
||||
tmp/coverage.html: ssf.md
|
||||
tmp/coverage.html: ssf
|
||||
mocha --require blanket -R html-cov > tmp/coverage.html
|
||||
|
||||
.PHONY: cov_min
|
||||
cov_min:
|
||||
MINTEST=1 make cov
|
||||
```
|
||||
|
||||
Coveralls.io support
|
||||
@ -967,7 +997,7 @@ coveralls:
|
||||
```json>package.json
|
||||
{
|
||||
"name": "ssf",
|
||||
"version": "0.5.6",
|
||||
"version": "0.5.7",
|
||||
"author": "SheetJS",
|
||||
"description": "pure-JS library to format data using ECMA-376 spreadsheet Format Codes",
|
||||
"keywords": [ "format", "sprintf", "spreadsheet" ],
|
||||
@ -1105,9 +1135,9 @@ function doit(data) {
|
||||
}
|
||||
describe('time formats', function() { doit(times.slice(0,1000)); });
|
||||
describe('date formats', function() {
|
||||
doit(dates);
|
||||
doit(process.env.MINTEST ? dates.slice(0,1000) : dates);
|
||||
it('should fail for bad formats', function() {
|
||||
var bad = ['yyyyy', 'mmmmmm', 'ddddd'];
|
||||
var bad = [];
|
||||
var chk = function(fmt){ return function(){ SSF.format(fmt,0); }; };
|
||||
bad.forEach(function(fmt){assert.throws(chk(fmt));});
|
||||
});
|
||||
@ -1124,7 +1154,7 @@ 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) {
|
||||
for(var w = 1; w < 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("|");
|
||||
@ -1133,7 +1163,7 @@ function doit(d, headers) {
|
||||
}
|
||||
describe('exponential formats', function() {
|
||||
var headers = data[0].split("\t");
|
||||
for(var j=14/* TODO: start from 1 */;j<data.length;++j) {
|
||||
for(var j=1;j<data.length;++j) {
|
||||
if(!data[j]) return;
|
||||
doit(data[j].replace(/#{255}/g,"").split("\t"), headers);
|
||||
}
|
||||
@ -1159,6 +1189,11 @@ describe('oddities', function() {
|
||||
}
|
||||
});
|
||||
});
|
||||
it('should fail for bad formats', function() {
|
||||
var bad = ['##,##'];
|
||||
var chk = function(fmt){ return function(){ SSF.format(fmt,0); }; };
|
||||
bad.forEach(function(fmt){assert.throws(chk(fmt));});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -23,9 +23,9 @@ function doit(data) {
|
||||
}
|
||||
describe('time formats', function() { doit(times.slice(0,1000)); });
|
||||
describe('date formats', function() {
|
||||
doit(dates);
|
||||
doit(process.env.MINTEST ? dates.slice(0,1000) : dates);
|
||||
it('should fail for bad formats', function() {
|
||||
var bad = ['yyyyy', 'mmmmmm', 'ddddd'];
|
||||
var bad = [];
|
||||
var chk = function(fmt){ return function(){ SSF.format(fmt,0); }; };
|
||||
bad.forEach(function(fmt){assert.throws(chk(fmt));});
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ 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) {
|
||||
for(var w = 1; w < 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("|");
|
||||
@ -14,7 +14,7 @@ function doit(d, headers) {
|
||||
}
|
||||
describe('exponential formats', function() {
|
||||
var headers = data[0].split("\t");
|
||||
for(var j=14/* TODO: start from 1 */;j<data.length;++j) {
|
||||
for(var j=1;j<data.length;++j) {
|
||||
if(!data[j]) return;
|
||||
doit(data[j].replace(/#{255}/g,"").split("\t"), headers);
|
||||
}
|
||||
|
@ -15,13 +15,13 @@ value #0.0E+0 ##0.0E+0 ###0.0E+0 ####0.0E+0
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
@ -14,4 +14,9 @@ describe('oddities', function() {
|
||||
}
|
||||
});
|
||||
});
|
||||
it('should fail for bad formats', function() {
|
||||
var bad = ['##,##'];
|
||||
var chk = function(fmt){ return function(){ SSF.format(fmt,0); }; };
|
||||
bad.forEach(function(fmt){assert.throws(chk(fmt));});
|
||||
});
|
||||
});
|
||||
|
@ -41,7 +41,9 @@
|
||||
["hh:mmm:sss", [0.7]],
|
||||
["hh:mm:sss", [0.7]],
|
||||
["[hhh]", [0.7]],
|
||||
["[", [0.7]],
|
||||
["A/P", [0.7, "P"]],
|
||||
["e", [0.7, "1900"]],
|
||||
["123", [0.7, "123"], [0, "123"], ["sheetjs", "sheetjs"]],
|
||||
["\"foo\";\"bar\";\"baz\";\"qux\";\"foobar\"", [1], [0], [-1], ["sheetjs"]]
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user