Skip to content

Commit

Permalink
version bump 0.5.1: more love from tests
Browse files Browse the repository at this point in the history
- SSF updated to 0.5.2
- SSF integration with CSV / JSON output
- Worker + SSF fixes from gh-pages
- introduced CSV baselines
- updated test_files
- xls2csv options to control output
- code coverage
  • Loading branch information
SheetJSDev committed Jan 21, 2014
1 parent 74ffbd2 commit 8876109
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 238 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Expand Up @@ -4,6 +4,10 @@ node_js:
- "0.8"
before_install:
- "npm install -g mocha"
- "npm install blanket"
- "npm install coveralls mocha-lcov-reporter"
before_script:
- "make init"
- "cd test_files; make; cd .."
after_success:
- "make coveralls"
10 changes: 10 additions & 0 deletions Makefile
Expand Up @@ -26,3 +26,13 @@ test mocha:
.PHONY: lint
lint: $(TARGET)
jshint --show-non-errors $(TARGET)

.PHONY: cov
cov: misc/coverage.html

misc/coverage.html: xls.js
mocha --require blanket -R html-cov > misc/coverage.html

.PHONY: coveralls
coveralls:
mocha --require blanket --reporter mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js
14 changes: 10 additions & 4 deletions bin/xls2csv.njs
@@ -1,15 +1,19 @@
#!/usr/bin/env node
/* xls.js (C) 2013 SheetJS -- http://sheetjs.com */
/* xls.js (C) 2013-2014 SheetJS -- http://sheetjs.com */
/* vim: set ts=2: */
var XLS = require('../');
var fs = require('fs'), program = require('commander');
program
.version('0.4.11')
.version('0.5.1')
.usage('[options] <file> [sheetname]')
.option('-f, --file <file>', 'use specified workbook')
.option('-s, --sheet <sheet>', 'print specified sheet (default first sheet)')
.option('-l, --list-sheets', 'list sheet names and exit')
.option('-F, --formulae', 'print formulae')
.option('-S, --formulae', 'print formulae')
.option('-j, --json', 'emit formatted JSON rather than CSV (all fields text)')
.option('-J, --raw-js', 'emit raw JS object rather than CSV (raw numbers)')
.option('-F, --field-sep <sep>', 'CSV field separator', ",")
.option('-R, --row-sep <sep>', 'CSV row separator', "\n")
.option('--dev', 'development mode')
.option('--read', 'read but do not print out contents')
.option('-q, --quiet', 'quiet mode')
Expand Down Expand Up @@ -66,4 +70,6 @@ try {

if(!program.quiet) console.error(target_sheet);
if(program.formulae) console.log(XLS.utils.get_formulae(ws).join("\n"));
else console.log(XLS.utils.make_csv(ws));
else if(program.json) console.log(JSON.stringify(XLS.utils.sheet_to_row_object_array(ws)));
else if(program.rawJs) console.log(JSON.stringify(XLS.utils.sheet_to_row_object_array(ws,{raw:true})));
else console.log(XLS.utils.make_csv(ws, {FS:program.fieldSep, RS:program.rowSep}));
2 changes: 1 addition & 1 deletion bits/00_header.js
@@ -1,4 +1,4 @@
/* xls.js (C) 2013 SheetJS -- http://sheetjs.com */
/* xls.js (C) 2013-2014 SheetJS -- http://sheetjs.com */
/* vim: set ts=2: */
/*jshint eqnull:true, funcscope:true */
var XLS = {};
Expand Down
125 changes: 81 additions & 44 deletions bits/10_ssf.js
@@ -1,8 +1,7 @@
/* ssf.js (C) 2013 SheetJS -- http://sheetjs.com */
/* 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));}
Expand All @@ -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',
Expand Down Expand Up @@ -70,19 +70,20 @@ var frac = function frac(x, D, mixed) {
var B = x * sgn;
var P_2 = 0, P_1 = 1, P = 0;
var Q_2 = 1, Q_1 = 0, Q = 0;
var A = B|0;
var A = Math.floor(B);
while(Q_1 < D) {
A = B|0;
A = Math.floor(B);
P = A * P_1 + P_2;
Q = A * Q_1 + Q_2;
if((B - A) < 0.0000000001) break;
if((B - A) < 0.0000000005) break;
B = 1 / (B - A);
P_2 = P_1; P_1 = P;
Q_2 = Q_1; Q_1 = Q;
}
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;
var q = Math.floor(sgn * P/Q);
return [q, sgn*P - q*Q, Q];
};
Expand All @@ -96,13 +97,13 @@ var general_fmt = function(v) {
else if(V >= 0.0001 && V < 0.001) o = v.toPrecision(6);
else if(V >= Math.pow(10,10) && V < Math.pow(10,11)) o = v.toFixed(10).substr(0,12);
else if(V > Math.pow(10,-9) && V < Math.pow(10,11)) {
o = v.toFixed(12).replace(/(\.[0-9]*[1-9])0*$/,"$1").replace(/\.$/,"");
o = v.toFixed(12).replace(/(\.[0-9]*[1-9])0*$/,"$1").replace(/\.$/,"");
if(o.length > 11+(v<0?1:0)) o = v.toPrecision(10);
if(o.length > 11+(v<0?1:0)) o = v.toExponential(5);
}
else {
o = v.toFixed(11).replace(/(\.[0-9]*[1-9])0*$/,"$1");
if(o.length > 11 + (v<0?1:0)) o = v.toPrecision(6);
if(o.length > 11 + (v<0?1:0)) o = v.toPrecision(6);
}
o = o.replace(/(\.[0-9]*[1-9])0+e/,"$1e").replace(/\.0*e/,"e");
return o.replace("e","E").replace(/\.0*$/,"").replace(/\.([0-9]*[^0])0*$/,".$1").replace(/(E[+-])([0-9])$/,"$1"+"0"+"$2");
Expand All @@ -112,9 +113,10 @@ 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.round(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;
if(date === 60) {dout = [1900,2,29]; dow=3;}
else if(date === 0) {dout = [1900,1,0]; dow=6;}
else {
Expand All @@ -124,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);
Expand All @@ -134,61 +136,68 @@ 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);
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;
} 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 'ss': return pad(val.S, 2);
case 'ss.0': return pad(val.S,2) + "." + Math.round(10*val.u);
case 's': return Math.round(val.S+val.u);
case 'ss': return pad(Math.round(val.S+val.u), 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(/\)/,"");
Expand All @@ -201,8 +210,15 @@ var write_num = function(type, fmt, val) {
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"; });
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 |" + o;
}
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");
Expand All @@ -215,8 +231,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(Math.abs(val)), fmt.length);
if(fmt.match(/^####*$/)) return "dafuq";
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);
Expand All @@ -225,12 +241,15 @@ 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);
case "# ? / ?": ff = frac(aval, 9, true); return sign + (ff[0]||"") + " " + (ff[1] === 0 ? " " : ff[1] + "/" + ff[2]);
case "# ?? / ??": ff = frac(aval, 99, true); return sign + (ff[0]||"") + " " + (ff[1] ? pad(ff[1],2," ") + "/" + rpad(ff[2],2," ") : " ");
case "# ??? / ???": ff = frac(aval, 999, true); return sign + (ff[0]||"") + " " + (ff[1] ? pad(ff[1],3," ") + "/" + rpad(ff[2],3," ") : " ");
case "# ? / ?": ff = frac(aval, 9, true); return sign + (ff[0]||(ff[1] ? "" : "0")) + " " + (ff[1] === 0 ? " " : ff[1] + "/" + ff[2]);
case "# ?? / ??": ff = frac(aval, 99, true); return sign + (ff[0]||(ff[1] ? "" : "0")) + " " + (ff[1] ? pad(ff[1],2," ") + "/" + rpad(ff[2],2," ") : " ");
case "# ??? / ???": ff = frac(aval, 999, true); return sign + (ff[0]||(ff[1] ? "" : "0")) + " " + (ff[1] ? pad(ff[1],3," ") + "/" + rpad(ff[2],3," ") : " ");
default:
}
throw new Error("unsupported format |" + fmt + "|");
Expand Down Expand Up @@ -258,6 +277,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;
Expand All @@ -267,25 +290,35 @@ 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);
o = fmt[i]; while(fmt[++i] === c) o+=c;
if(!dt) return "";
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);
if(!dt) return "";
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 '#':
Expand Down Expand Up @@ -325,45 +358,49 @@ function eval_fmt(fmt, v, opts, flen) {
out[i].t = 't'; break;
case 'n': case '(':
var jj = i+1;
while(out[jj] && ("? D".indexOf(out[jj].t) > -1 || out[i].t == '(' && (out[jj].t == ')' || out[jj].t == 'n') || out[jj].t == 't' && (out[jj].v == '/' || out[jj].v == '$' || (out[jj].v == ' ' && (out[jj+1]||{}).t == '?')))) {
while(out[jj] && ("?D".indexOf(out[jj].t) > -1 || (out[jj].t == " " && (out[jj+1]||{}).t === "?" ) || out[i].t == '(' && (out[jj].t == ')' || out[jj].t == 'n') || out[jj].t == 't' && (out[jj].v == '/' || out[jj].v == '$' || (out[jj].v == ' ' && (out[jj+1]||{}).t == '?')))) {
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;
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;
}
}

return out.map(function(x){return x.v;}).join("");
}
SSF._eval = eval_fmt;
function choose_fmt(fmt, v, o) {
if(typeof fmt === 'number') fmt = table_fmt[fmt];
if(typeof fmt === 'number') fmt = ((o&&o.table) ? o.table : table_fmt)[fmt];
if(typeof fmt === "string") fmt = split_fmt(fmt);
var l = fmt.length;
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 + "|";
}
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];
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]);
};

SSF._choose = choose_fmt;
SSF._table = table_fmt;
SSF.load = function(fmt, idx) { table_fmt[idx] = fmt; };
SSF.format = format;
SSF.get_table = function() { return table_fmt; };
SSF.load_table = function(tbl) { for(var i=0; i!=0x0188; ++i) if(tbl[i]) SSF.load(tbl[i], i); };
};
make_ssf(SSF);
2 changes: 1 addition & 1 deletion bits/30_bifffunc.js
Expand Up @@ -261,7 +261,7 @@ function parse_XF(blob, length) {
o.ifnt = read(2); o.ifmt = read(2); o.flags = read(2);
o.fStyle = (o.flags >> 2) & 0x01;
length -= 6;
o.data = o.fStyle ? parse_StyleXF(blob, length) : parse_CellXF(blob, length);
o.data = o.fStyle ? parse_StyleXF(blob, length) : parse_CellXF(blob, length);
return o;
}

Expand Down

0 comments on commit 8876109

Please sign in to comment.