Permalink
Browse files

version bump 0.7.2: bughunt

- read BOM, handle UTF16LE-encoded XML
- handle namespaces in [Content_Types].xml
- parse workbook rels to determine sheet files
- numbers OSX boolean support (apparently requires "0" or "1")
- XLSX force "General" style to be serialized, omit implied cell type and style
- updated SSF to 0.7.0 (h/t @sysarchitect)
- updated jszip to 2.2.2
- removed old tests/files path, replaced with test_files
- themes written
- ignore potential existence of thumbnail when calculating relationship ids
  • Loading branch information...
1 parent fbb2574 commit e1f8dbb863aa60bb7d01ff36c327e57758b17945 @SheetJSDev SheetJSDev committed May 22, 2014
View
@@ -1,3 +1,3 @@
-[submodule "tests/files"]
- path = tests/files
+[submodule "test_files"]
+ path = test_files
url = https://github.com/SheetJS/test_files
View
@@ -9,6 +9,6 @@ before_install:
- "npm install coveralls mocha-lcov-reporter"
before_script:
- "make init"
- - "cd tests/files; make all; cd -"
+ - "cd test_files; make all; cd -"
after_success:
- "make coveralls-spin"
View
@@ -56,6 +56,8 @@ Simple usage (walks through every cell of every sheet and dumps the values):
}
});
+An example of writing an array-of-arrays is available at <http://git.io/WEK88Q>
+
The node version installs a binary `xlsx` which can read XLSX/XLSM/XLSB
files and output the contents in various formats. The source is available at
`xlsx.njs` in the bin directory.
View
@@ -1 +1 @@
-XLSX.version = '0.7.1';
+XLSX.version = '0.7.2';
View
@@ -12,3 +12,11 @@ if(typeof cptable !== 'undefined') _getchar = function(x) {
if (current_cptable) return current_cptable.dec[x];
return cptable.utils.decode(current_codepage, [x%256,x>>8])[0];
};
+
+function char_codes(data) { return data.split("").map(function(x) { return x.charCodeAt(0); }); }
+function debom_xml(data) {
+ if(typeof cptable !== 'undefined') {
+ if(data.charCodeAt(0) === 0xFF && data.charCodeAt(1) === 0xFE) { return cptable.utils.decode(1200, char_codes(data.substr(2))); }
+ }
+ return data;
+}
View
@@ -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.6.5';
+SSF.version = '0.7.0';
/* Options */
var opts_fmt = {
date1904:0,
@@ -149,8 +149,8 @@ var parse_date_code = function parse_date_code(v,opts,b2) {
};
SSF.parse_date_code = parse_date_code;
/*jshint -W086 */
-var write_date = function(type, fmt, val) {
- var o, ss, y = val.y;
+var write_date = function(type, fmt, val, ss0) {
+ var o, ss, tt, y = val.y, sss0;
switch(type) {
case 'b': y = val.y + 543;
/* falls through */
@@ -187,11 +187,15 @@ var write_date = function(type, fmt, val) {
default: throw 'bad minute format: ' + fmt;
}
case 's': switch(fmt) { /* seconds */
- case 's': ss=Math.round(val.S+val.u); return ss >= 60 ? 0 : ss;
- case 'ss': ss=Math.round(val.S+val.u); if(ss>=60) ss=0; return pad(ss,2);
- case 'ss.0': ss=Math.round(10*(val.S+val.u)); if(ss>=600) ss = 0; o = pad(ss,3); return o.substr(0,2)+"." + o.substr(2);
- case 'ss.00': ss=Math.round(100*(val.S+val.u)); if(ss>=6000) ss = 0; o = pad(ss,4); return o.substr(0,2)+"." + o.substr(2);
- case 'ss.000': ss=Math.round(1000*(val.S+val.u)); if(ss>=60000) ss = 0; o = pad(ss,5); return o.substr(0,2)+"." + o.substr(2);
+ case 's': case 'ss': case '.0': case '.00': case '.000':
+ sss0 = ss0 || 0;
+ tt = Math.pow(10,sss0);
+ ss = Math.round((tt)*(val.S + val.u));
+ if(fmt === 's') return ss >= 60*tt ? 0 : ss/tt;
+ else if(fmt === 'ss') { if(ss>=60*tt) ss=0; return pad(ss,(2+sss0)).substr(0,2); }
+ if(ss >= 60*tt) ss = 0;
+ o = pad(ss,2 + sss0);
+ return "." + o.substr(2,fmt.length-1);
default: throw 'bad second format: ' + fmt;
}
case 'Z': switch(fmt) {
@@ -200,7 +204,6 @@ var write_date = function(type, fmt, val) {
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;
} return fmt.length === 3 ? o : pad(o, 2);
- /* TODO: handle the ECMA spec format ee -> yy */
case 'e': { return val.y; } break;
}
};
@@ -285,12 +288,22 @@ var write_num = function(type, fmt, val) {
ff = frac(aval, Math.pow(10,rr)-1, true);
return sign + (ff[0]||(ff[1] ? "" : "0")) + " " + (ff[1] ? pad(ff[1],rr," ") + r[2] + "/" + r[3] + rpad(ff[2],rr," "): fill(" ", 2*rr+1 + r[2].length + r[3].length));
}
+ if((r = fmt.match(/^[#0]+$/))) {
+ o = "" + Math.round(val);
+ if(fmt.length <= o.length) return o;
+ return fmt.substr(0,fmt.length - o.length).replace(/#/g,"") + o;
+ }
+ if((r = fmt.match(/^([#0]+)\.([#0]+)$/))) {
+ o = "" + val.toFixed(Math.min(r[2].length,10)).replace(/([^0])0+$/,"$1");
+ rr = o.indexOf(".");
+ var lres = fmt.indexOf(".") - rr, rres = fmt.length - o.length - lres;
+ return fmt.substr(0,lres).replace(/#/g,"") + o + fmt.substr(fmt.length-rres).replace(/#/g,"");
+ }
if((r = fmt.match(/^00,000\.([#0]*0)$/))) {
rr = val == Math.floor(val) ? 0 : Math.round((val-Math.floor(val))*Math.pow(10,r[1].length));
return val < 0 ? "-" + write_num(type, fmt, -val) : commaify(String(Math.floor(val))).replace(/^\d,\d{3}$/,"0$&").replace(/^\d*$/,function($$) { return "00," + ($$.length < 3 ? pad(0,3-$$.length) : "") + $$; }) + "." + pad(rr,r[1].length,0);
}
switch(fmt) {
- case "0": case "#0": return ""+Math.round(val);
case "#,###": var x = commaify(String(Math.round(aval))); return x !== "0" ? sign + x : "";
default:
}
@@ -313,7 +326,7 @@ function split_fmt(fmt) {
}
SSF._split = split_fmt;
function eval_fmt(fmt, v, opts, flen) {
- var out = [], o = "", i = 0, c = "", lst='t', q, dt;
+ var out = [], o = "", i = 0, c = "", lst='t', q, dt, j;
fixopts(opts = (opts || {}));
var hr='H';
/* Tokenize */
@@ -345,7 +358,6 @@ function eval_fmt(fmt, v, opts, flen) {
if(!dt) dt = parse_date_code(v, opts);
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();
@@ -369,7 +381,13 @@ function eval_fmt(fmt, v, opts, flen) {
} else { o=""; }
break;
/* Numbers */
- case '0': case '#': case '.':
+ case '.':
+ if(dt) {
+ o = c; while((c=fmt[++i]) === "0") o += c;
+ out.push({t:'s', v:o}); break;
+ }
+ /* falls through */
+ case '0': case '#':
o = c; while("0#?.,E+-%".indexOf(c=fmt[++i]) > -1 || c=='\\' && fmt[i+1] == "-" && "0#".indexOf(fmt[i+2])>-1) o += c;
out.push({t:'n', v:o}); break;
case '?':
@@ -387,11 +405,13 @@ function eval_fmt(fmt, v, opts, flen) {
out.push({t:'t', v:c}); ++i; break;
}
}
- var bt = 0;
+ var bt = 0, ss0 = 0, ssm;
for(i=out.length-1, lst='t'; i >= 0; --i) {
switch(out[i].t) {
case 'h': case 'H': out[i].t = hr; lst='h'; if(bt < 1) bt = 1; break;
- case 's': if(bt < 3) bt = 3;
+ case 's':
+ if((ssm=out[i].v.match(/\.0+$/))) ss0=Math.max(ss0,ssm[0].length-1);
+ if(bt < 3) bt = 3;
/* falls through */
case 'd': case 'y': case 'M': case 'e': lst=out[i].t; break;
case 'm': if(lst === 's') { out[i].t = 'M'; if(bt < 2) bt = 2; } break;
@@ -416,25 +436,77 @@ function eval_fmt(fmt, v, opts, flen) {
break;
}
/* replace fields */
+ var nstr = "", jj;
for(i=0; i < out.length; ++i) {
switch(out[i].t) {
case 't': case 'T': case ' ': case 'D': break;
case 'X': delete out[i]; break;
case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'b': case 'Z':
- out[i].v = write_date(out[i].t, out[i].v, dt);
+ out[i].v = write_date(out[i].t, out[i].v, dt, ss0);
out[i].t = 't'; break;
case 'n': case '(': case '?':
- var jj = i+1;
+ jj = i+1;
while(out[jj] && ("?D".indexOf(out[jj].t) > -1 || (" t".indexOf(out[jj].t) > -1 && "?t".indexOf((out[jj+1]||{}).t)>-1 && (out[jj+1].t == '?' || out[jj+1].v == '/')) || out[i].t == '(' && (")n ".indexOf(out[jj].t) > -1) || out[jj].t == 't' && (out[jj].v == '/' || '$€'.indexOf(out[jj].v) > -1 || (out[jj].v == ' ' && (out[jj+1]||{}).t == '?')))) {
out[i].v += out[jj].v;
delete out[jj]; ++jj;
}
- out[i].v = write_num(out[i].t, out[i].v, (flen >1 && v < 0 && i>0 && out[i-1].v == "-" ? -v:v));
- out[i].t = 't';
+ nstr += out[i].v;
i = jj-1; break;
case 'G': out[i].t = 't'; out[i].v = general_fmt(v,opts); break;
}
}
+ if(nstr) {
+ var ostr = write_num(nstr[0]=='(' ? '(' : 'n', nstr, (v<0&&nstr[0] == "-" ? -v : v));
+ jj=ostr.length-1;
+ var decpt = out.length;
+ for(i=0; i < out.length; ++i) if(out[i] && out[i].v.indexOf(".") > -1) { decpt = i; break; }
+ var lasti=out.length, vv;
+ if(decpt === out.length && !ostr.match(/E/)) {
+ for(i=out.length-1; i>= 0;--i) {
+ if(!out[i] || 'n?('.indexOf(out[i].t) === -1) continue;
+ vv = out[i].v.split("");
+ for(j=vv.length-1; j>=0; --j) {
+ if(jj>=0) vv[j] = ostr[jj--];
+ else vv[j] = "";
+ }
+ out[i].v = vv.join("");
+ out[i].t = 't';
+ lasti = i;
+ }
+ if(jj>=0 && lasti<out.length) out[lasti].v = ostr.substr(0,jj+1) + out[lasti].v;
+ }
+ else if(decpt !== out.length && !ostr.match(/E/)) {
+ jj = ostr.indexOf(".")-1;
+ for(i=decpt; i>= 0; --i) {
+ if(!out[i] || 'n?('.indexOf(out[i].t) === -1) continue;
+ vv = out[i].v.split("");
+ for(j=out[i].v.indexOf(".")>-1&&i==decpt?out[i].v.indexOf(".")-1:vv.length-1; j>=0; --j) {
+ if(jj>=0 && "0#".indexOf(vv[j])>-1) vv[j] = ostr[jj--];
+ else vv[j] = "";
+ }
+ out[i].v = vv.join("");
+ out[i].t = 't';
+ lasti = i;
+ }
+ if(jj>=0 && lasti<out.length) out[lasti].v = ostr.substr(0,jj+1) + out[lasti].v;
+ jj = ostr.indexOf(".")+1;
+ for(i=decpt; i<out.length; ++i) {
+ if(!out[i] || 'n?('.indexOf(out[i].t) === -1 && i != decpt ) continue;
+ vv = out[i].v.split("");
+ for(j=out[i].v.indexOf(".")>-1&&i==decpt?out[i].v.indexOf(".")+1:0; j<vv.length; ++j) {
+ if(jj<ostr.length) vv[j] = ostr[jj++];
+ else vv[j] = "";
+ }
+ out[i].v = vv.join("");
+ out[i].t = 't';
+ lasti = i;
+ }
+ }
+ }
+ for(i=0; i<out.length; ++i) if(out[i] && 'n(?'.indexOf(out[i].t)>-1) {
+ out[i].v = write_num(out[i].t, out[i].v, (flen >1 && v < 0 && i>0 && out[i-1].v == "-" ? -v:v));
+ out[i].t = 't';
+ }
return out.map(function(x){return x.v;}).join("");
}
SSF._eval = eval_fmt;
View
@@ -11,3 +11,10 @@ function evert(obj, arr) {
});
return o;
}
+
+/* TODO: date1904 logic */
+function datenum(v, date1904) {
+ if(date1904) v+=1462;
+ var epoch = Date.parse(v);
+ return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
+}
View
@@ -1,8 +1,8 @@
function getdata(data) {
if(!data) return null;
- if(data.data) return data.name.substr(-4) !== ".bin" ? data.data : data.data.split("").map(function(x) { return x.charCodeAt(0); });
+ if(data.data) return data.name.substr(-4) !== ".bin" ? debom_xml(data.data) : data.data.split("").map(function(x) { return x.charCodeAt(0); });
if(data.asNodeBuffer && typeof Buffer !== 'undefined' && data.name.substr(-4)===".bin") return data.asNodeBuffer();
- if(data.asBinary && data.name.substr(-4) !== ".bin") return data.asBinary();
+ if(data.asBinary && data.name.substr(-4) !== ".bin") return debom_xml(data.asBinary());
if(data._data && data._data.getContent) {
/* TODO: something far more intelligent */
if(data.name.substr(-4) === ".bin") return Array.prototype.slice.call(data._data.getContent());
View
@@ -181,9 +181,9 @@ function parse_ct(data, opts) {
TODO:[], rels:[], xmlns: "" };
(data.match(/<[^>]*>/g)||[]).forEach(function(x) {
var y = parsexmltag(x);
- switch(y[0]) {
+ switch(y[0].replace(/<\w*:/,"<")) {
case '<?xml': break;
- case '<Types': ct.xmlns = y.xmlns; break;
+ case '<Types': ct.xmlns = y['xmlns' + (y[0].match(/<(\w+):/)||["",""])[1] ]; break;
case '<Default': ctext[y.Extension] = y.ContentType; break;
case '<Override':
if(y.ContentType in ct2type)ct[ct2type[y.ContentType]].push(y.PartName);
@@ -57,8 +57,8 @@ function write_core_props(cp, opts) {
o.push(h ? writextag(f,g,h) : writetag(f,g));
};
- if(typeof cp.CreatedDate !== 'undefined') doit("dcterms:created", write_w3cdtf(cp.CreatedDate, opts.WTF), {"xsi:type":"dcterms:W3CDTF"});
- if(typeof cp.ModifiedDate !== 'undefined') doit("dcterms:modified", write_w3cdtf(cp.ModifiedDate, opts.WTF), {"xsi:type":"dcterms:W3CDTF"});
+ if(typeof cp.CreatedDate !== 'undefined') doit("dcterms:created", typeof cp.CreatedDate === "string" ? cp.CreatedDate : write_w3cdtf(cp.CreatedDate, opts.WTF), {"xsi:type":"dcterms:W3CDTF"});
+ if(typeof cp.ModifiedDate !== 'undefined') doit("dcterms:modified", typeof cp.ModifiedDate === "string" ? cp.ModifiedDate : write_w3cdtf(cp.ModifiedDate, opts.WTF), {"xsi:type":"dcterms:W3CDTF"});
CORE_PROPS.forEach(function(f) { doit(f[0], cp[f[1]]); });
if(o.length>2){ o.push('</cp:coreProperties>'); o[1]=o[1].replace("/>",">"); }
View
@@ -53,9 +53,10 @@ var EXT_PROPS_XML_ROOT = writextag('Properties', null, {
function write_ext_props(cp, opts) {
var o = [], p = {}, W = writextag;
+ if(!cp) cp = {};
+ cp.Application = "SheetJS";
o.push(XML_HEADER);
o.push(EXT_PROPS_XML_ROOT);
- if(!cp) return o.join("");
EXT_PROPS.forEach(function(f) {
if(typeof cp[f[1]] === 'undefined') return;
View
@@ -180,7 +180,19 @@ var write_sst_xml = function(sst, opts) {
count: sst.Count,
uniqueCount: sst.Unique
}));
- sst.forEach(function(s) { o.push("<si>" + (s.r ? s.r : "<t>" + escapexml(s.t) + "</t>") + "</si>"); });
+ sst.forEach(function(s) {
+ var sitag = "<si>";
+ if(s.r) sitag += s.r;
+ else {
+ sitag += "<t";
+ if(s.t.match(/^\s|\s$|[\t\n\r]/)) sitag += ' xml:space="preserve"';
+ sitag += ">";
+ sitag += escapexml(s.t);
+ sitag += "</t>";
+ }
+ sitag += "</si>";
+ o.push(sitag);
+ });
if(o.length>2){ o.push('</sst>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
};
View
@@ -5,9 +5,9 @@ function parse_numFmts(t, opts) {
t[0].match(/<[^>]*>/g).forEach(function(x) {
var y = parsexmltag(x);
switch(y[0]) {
- case '<numFmts': case '</numFmts>': case '<numFmts/>': break;
+ case '<numFmts': case '</numFmts>': case '<numFmts/>': case '<numFmts>': break;
case '<numFmt': {
- var f=utf8read(unescapexml(y.formatCode)), i=parseInt(y.numFmtId,10);
+ var f=unescapexml(y.formatCode), i=parseInt(y.numFmtId,10);
styles.NumberFmt[i] = f; if(i>0) SSF.load(f,i);
} break;
default: if(opts.WTF) throw 'unrecognized ' + y[0] + ' in numFmts';
@@ -33,7 +33,7 @@ function parse_cellXfs(t, opts) {
t[0].match(/<[^>]*>/g).forEach(function(x) {
var y = parsexmltag(x);
switch(y[0]) {
- case '<cellXfs': case '<cellXfs/>': case '</cellXfs>': break;
+ case '<cellXfs': case '<cellXfs>': case '<cellXfs/>': case '</cellXfs>': break;
/* 18.8.45 xf CT_Xf */
case '<xf': delete y[0];
View
@@ -9,7 +9,7 @@ function get_sst_id(sst, str) {
}
function get_cell_style(styles, cell, opts) {
- var z = opts.revssf[cell.z];
+ var z = opts.revssf[cell.z||"General"];
for(var i = 0; i != styles.length; ++i) if(styles[i].numFmtId === z) return i;
styles[styles.length] = {
numFmtId:z,
Oops, something went wrong.

0 comments on commit e1f8dbb

Please sign in to comment.