Formula write support

- XLSX/XLML/ODS/CSV formula write
- XLS Array/Formula record parsing
- ODS to/from CSF formula conversion
- XLML parse negative dates
- formula multiformat and roundtrip tests
- update SSF

Closes issues:
- closes #549 h/t @omershelef
- closes #306 h/t @akaustel
- closes #300 h/t @wawanopoulos
- closes #152 h/t @florf
- closes #103 h/t @christocracy
- closes #112 h/t @johnyesberg
- closes #307 h/t @alexanderknapstein
This commit is contained in:
SheetJS 2017-03-17 20:45:06 -04:00
parent ea1873e572
commit 5a3774e36e
17 changed files with 576 additions and 233 deletions

@ -484,7 +484,7 @@ will not be generated; the parser `sheetStubs` option must be set to `true`.
### Formulae
The A1-style formula string is stored in the `f` field. Even though different
file formats store the formulae in different ways, the formats are converted.
file formats store the formulae in different ways, the formats are translated.
Shared formulae are decompressed and each cell has the correct formula.

@ -310,7 +310,18 @@ function hashq(str/*:string*/)/*:string*/ {
return o;
}
function rnd(val/*:number*/, d/*:number*/)/*:string*/ { var dd = Math.pow(10,d); return ""+(Math.round(val * dd)/dd); }
function dec(val/*:number*/, d/*:number*/)/*:number*/ { return Math.round((val-Math.floor(val))*Math.pow(10,d)); }
function dec(val/*:number*/, d/*:number*/)/*:number*/ {
if (d < ('' + Math.round((val-Math.floor(val))*Math.pow(10,d))).length) {
return 0;
}
return Math.round((val-Math.floor(val))*Math.pow(10,d));
}
function carry(val/*:number*/, d/*:number*/)/*:number*/ {
if (d < ('' + Math.round((val-Math.floor(val))*Math.pow(10,d))).length) {
return 1;
}
return 0;
}
function flr(val/*:number*/)/*:string*/ { if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0)); return ""+Math.floor(val); }
function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/ {
if(type.charCodeAt(0) === 40 && !fmt.match(closeparen)) {
@ -332,8 +343,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
if((r = fmt.match(frac1))) return write_num_f1(r, aval, sign);
if(fmt.match(/^#+0+$/)) return sign + pad0r(aval,fmt.length - fmt.indexOf("0"));
if((r = fmt.match(dec1))) {
// $FlowIgnore
o = rnd(val, r[1].length).replace(/^([^\.]+)$/,"$1."+r[1]).replace(/\.$/,"."+r[1]).replace(/\.(\d*)$/,function($$, $1) { return "." + $1 + fill("0", r[1].length-$1.length); });
o = rnd(val, r[1].length).replace(/^([^\.]+)$/,"$1."+r[1]).replace(/\.$/,"."+r[1]).replace(/\.(\d*)$/,function($$, $1) { return "." + $1 + fill("0", /*::(*/r/*::||[""])*/[1].length-$1.length); });
return fmt.indexOf("0.") !== -1 ? o : o.replace(/^0\./,".");
}
fmt = fmt.replace(/^#+([0.])/, "$1");
@ -342,7 +352,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
}
if((r = fmt.match(/^#,##0(\.?)$/))) return sign + commaify(pad0r(aval,0));
if((r = fmt.match(/^#,##0\.([#0]*0)$/))) {
return val < 0 ? "-" + write_num_flt(type, fmt, -val) : commaify(""+(Math.floor(val))) + "." + pad0(dec(val, r[1].length),r[1].length);
return val < 0 ? "-" + write_num_flt(type, fmt, -val) : commaify(""+(Math.floor(val) + carry(val, r[1].length))) + "." + pad0(dec(val, r[1].length),r[1].length);
}
if((r = fmt.match(/^#,#*,#0/))) return write_num_flt(type,fmt.replace(/^#,#*,/,""),val);
if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {

@ -6,6 +6,16 @@ function ods_to_csf_formula(f/*:string*/)/*:string*/ {
f = f.substr(1);
if(f.charCodeAt(0) == 61) f = f.substr(1);
}
f = f.replace(/COM\.MICROSOFT\./g, "");
/* Part 3 Section 5.8 References */
return f.replace(/\[((?:\.[A-Z]+[0-9]+)(?::\.[A-Z]+[0-9]+)?)\]/g, "$1").replace(/\./g, "");
f = f.replace(/\[((?:\.[A-Z]+[0-9]+)(?::\.[A-Z]+[0-9]+)?)\]/g, function($$, $1) { return $1.replace(/\./g,""); });
/* TODO: something other than this */
f = f.replace(/\[.(#[A-Z]*[?!])\]/g, "$1");
return f.replace(/[;~]/g,",").replace(/\|/g,";");
}
function csf_to_ods_formula(f/*:string*/)/*:string*/ {
var o = "of:=" + f.replace(crefregex, "$1[.$2$3$4$5]").replace(/\]:\[/g,":");
/* TODO: something other than this */
return o.replace(/;/g, "|").replace(/,/g,";");
}

@ -126,7 +126,7 @@ function write_ws_xml_cols(ws, cols)/*:string*/ {
}
function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
if(cell.v === undefined || cell.t === 'z') return "";
if(cell.v === undefined && cell.f === undefined || cell.t === 'z') return "";
var vv = "";
var oldt = cell.t, oldv = cell.v;
switch(cell.t) {
@ -152,7 +152,7 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
case 'd': o.t = "d"; break;
case 'b': o.t = "b"; break;
case 'e': o.t = "e"; break;
default:
default: if(cell.v == null) { delete cell.t; break; }
if(opts.bookSST) {
v = writetag('v', ''+get_sst_id(opts.Strings, cell.v));
o.t = "s"; break;
@ -160,6 +160,10 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
o.t = "str"; break;
}
if(cell.t != oldt) { cell.t = oldt; cell.v = oldv; }
if(cell.f) {
var ff = cell.F && cell.F.substr(0, ref.length) == ref ? {t:"array", ref:cell.F} : null;
v = writextag('f', escapexml(cell.f), ff) + (cell.v != null ? v : "");
}
return writextag('c', v, o);
}
@ -215,7 +219,8 @@ return function parse_ws_xml_data(sdata, s, opts, guess) {
if((cref=d.match(match_v))!= null && /*::cref != null && */cref[1] !== '') p.v=unescapexml(cref[1]);
if(opts.cellFormula) {
if((cref=d.match(match_f))!= null && /*::cref != null && */cref[1] !== '') {
p.f=unescapexml(utf8read(cref[1]));
/* TODO: match against XLSXFutureFunctions */
p.f=unescapexml(utf8read(cref[1])).replace(/_xlfn\./,"");
if(/*::cref != null && cref[0] != null && */cref[0].indexOf('t="array"') > -1) {
p.F = (d.match(refregex)||[])[1];
if(p.F.indexOf(":") > -1) arrayf.push([safe_decode_range(p.F), p.F]);

@ -108,7 +108,7 @@ function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, a
case 'DateTime':
cell.v = (Date.parse(xml) - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
if(cell.v !== cell.v) cell.v = unescapexml(xml);
else if(cell.v >= 1 && cell.v<60) cell.v = cell.v -1;
else if(cell.v<60) cell.v = cell.v -1;
if(!nf || nf == "General") nf = "yyyy-mm-dd";
/* falls through */
case 'Number':
@ -250,6 +250,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
sheetname = unescapexml(tmp.Name);
cursheet = {};
mergecells = [];
arrayf = [];
}
break;
case 'Table':
@ -759,10 +760,14 @@ function write_sty_xlml(wb, opts)/*:string*/ {
}
/* TODO */
function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
if(!cell || cell.v === undefined) return "<Cell></Cell>";
if(!cell || cell.v == undefined && cell.f == undefined) return "<Cell></Cell>";
var attr = {};
if(cell.f) attr["ss:Formula"] = "=" + escapexml(a1_to_rc(cell.f, addr));
if(cell.F && cell.F.substr(0, ref.length) == ref) {
var end = decode_cell(cell.F.substr(ref.length + 1));
attr["ss:ArrayRange"] = "RC:R" + (end.r == addr.r ? "" : "[" + (end.r - addr.r) + "]") + "C" + (end.c == addr.c ? "" : "[" + (end.c - addr.c) + "]");
}
if(ws['!merges']) {
var marr = ws['!merges'];
@ -780,9 +785,9 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break;
case 'e': t = 'Error'; p = BErr[cell.v]; break;
case 'd': t = 'DateTime'; p = new Date(cell.v).toISOString(); break;
case 's': t = 'String'; p = escapexml(cell.v||""); break;
case 's': t = 'String'; p = escapexml(cell.v||""); break;
}
var m = '<Data ss:Type="' + t + '">' + p + '</Data>';
var m = '<Data ss:Type="' + t + '">' + (cell.v != null ? p : "") + '</Data>';
return writextag("Cell", m, attr);
}

@ -122,6 +122,19 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(cell.r + 1 > range.e.r) range.e.r = cell.r + 1;
if(cell.c + 1 > range.e.c) range.e.c = cell.c + 1;
}
if(options.cellFormula && line.f) {
for(var afi = 0; afi < array_formulae.length; ++afi) {
if(array_formulae[afi][0].s.c > cell.c) continue;
if(array_formulae[afi][0].s.r > cell.r) continue;
if(array_formulae[afi][0].e.c < cell.c) continue;
if(array_formulae[afi][0].e.r < cell.r) continue;
line.F = encode_range(array_formulae[afi][0]);
if(array_formulae[afi][0].s.c != cell.c) delete line.f;
if(array_formulae[afi][0].s.r != cell.r) delete line.f;
if(line.f) line.f = "" + stringify_formula(array_formulae[afi][1], range, cell, supbooks, opts);
break;
}
}
if(options.sheetRows && lastcell.r >= options.sheetRows) cell_valid = false;
else out[last_cell] = line;
};
@ -274,7 +287,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
else cur_sheet = (Directory[s] || {name:""}).name;
mergecells = [];
objects = [];
array_formulae = []; opts.arrayf = array_formulae;
} break;
case 'Number': case 'BIFF2NUM': case 'BIFF2INT': {
temp_val = {ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.val, t:'n'};
safe_format_xf(temp_val, options, wb.opts.Date1904);
@ -299,44 +314,43 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
}
} break;
case 'Formula': {
switch(val.val) {
case 'String': last_formula = val; break;
case 'Array Formula': throw "Array Formula unsupported";
default:
temp_val = ({v:val.val, ixfe:val.cell.ixfe, t:val.tt}/*:any*/);
temp_val.XF = XFs[temp_val.ixfe];
if(options.cellFormula) {
var _f = val.formula;
if(_f && _f[0] && _f[0][0] && _f[0][0][0] == 'PtgExp') {
var _fr = _f[0][0][1][0], _fc = _f[0][0][1][1];
var _fe = encode_cell({r:_fr, c:_fc});
if(shared_formulae[_fe]) temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts);
else temp_val.F = (out[_fe] || {}).F;
} else temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts);
}
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell(val.cell, temp_val, options);
last_formula = val;
if(val.val == 'String') { last_formula = val; break; }
temp_val = ({v:val.val, ixfe:val.cell.ixfe, t:val.tt}/*:any*/);
temp_val.XF = XFs[temp_val.ixfe];
if(options.cellFormula) {
var _f = val.formula;
if(_f && _f[0] && _f[0][0] && _f[0][0][0] == 'PtgExp') {
var _fr = _f[0][0][1][0], _fc = _f[0][0][1][1];
var _fe = encode_cell({r:_fr, c:_fc});
if(shared_formulae[_fe]) temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts);
else temp_val.F = (out[_fe] || {}).F;
} else temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts);
}
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell(val.cell, temp_val, options);
last_formula = val;
} break;
case 'String': {
if(last_formula) {
if(last_formula) { /* technically always true */
last_formula.val = val;
temp_val = ({v:last_formula.val, ixfe:last_formula.cell.ixfe, t:'s'}/*:any*/);
temp_val = ({v:val, ixfe:last_formula.cell.ixfe, t:'s'}/*:any*/);
temp_val.XF = XFs[temp_val.ixfe];
if(options.cellFormula) temp_val.f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts);
if(options.cellFormula) {
temp_val.f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts);
}
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell(last_formula.cell, temp_val, options);
last_formula = null;
}
} else throw new Error("String record expects Formula");
} break;
case 'Array': {
array_formulae.push(val);
if(options.cellFormula && out[last_cell]) {
var _arraystart = encode_cell(val[0].s);
if(options.cellFormula && out[_arraystart]) {
if(!last_formula) break; /* technically unreachable */
if(!last_cell || !out[last_cell]) break; /* technically unreachable */
out[last_cell].f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts);
out[last_cell].F = encode_range(val[0]);
if(!_arraystart || !out[_arraystart]) break;
out[_arraystart].f = ""+stringify_formula(val[1], range, val[0], supbooks, opts);
out[_arraystart].F = encode_range(val[0]);
}
} break;
case 'ShrFmla': {
@ -375,6 +389,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
break;
case 'Dimensions': {
if(file_depth === 1) range = val; /* TODO: stack */
} break;

@ -57,20 +57,20 @@ function write_BIFF2LABEL(r, c, val) {
}
function write_ws_biff_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts) {
switch(cell.t) {
if(cell.v != null) switch(cell.t) {
case 'n':
if((cell.v == (cell.v|0)) && (cell.v >= 0) && (cell.v < 65536))
write_biff_rec(ba, 0x0002, write_BIFF2INT(R, C, cell.v));
else
write_biff_rec(ba, 0x0003, write_BIFF2NUMBER(R,C, cell.v));
break;
case 'b': case 'e': write_biff_rec(ba, 0x0005, write_BIFF2BERR(R, C, cell.v, cell.t)); break;
return;
case 'b': case 'e': write_biff_rec(ba, 0x0005, write_BIFF2BERR(R, C, cell.v, cell.t)); return;
/* TODO: codepage, sst */
case 's': case 'str':
write_biff_rec(ba, 0x0004, write_BIFF2LABEL(R, C, cell.v));
break;
default: write_biff_rec(ba, 0x0001, write_BIFF2Cell(null, R, C));
return;
}
write_biff_rec(ba, 0x0001, write_BIFF2Cell(null, R, C));
}
function write_biff_ws(ba/*:BufArray*/, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbook*/) {

@ -78,6 +78,7 @@ var parse_content_xml = (function() {
ctag = parsexmltag(Rn[0], false);
q = ({t:ctag['数据类型'] || ctag['value-type'], v:null/*:: , z:null, w:""*/}/*:any*/);
if(opts.cellFormula) {
if(ctag.formula) ctag.formula = unescapexml(ctag.formula);
if(ctag['number-matrix-columns-spanned'] && ctag['number-matrix-rows-spanned']) {
mR = parseInt(ctag['number-matrix-rows-spanned'],10) || 0;
mC = parseInt(ctag['number-matrix-columns-spanned'],10) || 0;

@ -27,11 +27,22 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() {
}
if(skip) { o.push(covered_cell_xml); continue; }
var ref = encode_cell({r:R, c:C}), cell = ws[ref];
var fmla = "";
if(cell && cell.f) {
fmla = ' table:formula="' + escapexml(csf_to_ods_formula(cell.f)) + '"';
if(cell.F) {
if(cell.F.substr(0, ref.length) == ref) {
var _Fref = decode_range(cell.F);
fmla += ' table:number-matrix-columns-spanned="' + (_Fref.e.c - _Fref.s.c + 1)+ '"';
fmla += ' table:number-matrix-rows-spanned="' + (_Fref.e.r - _Fref.s.r + 1) + '"';
} else fmla = "";
}
}
if(cell) switch(cell.t) {
case 'b': o.push(cell_begin + mxml + vt + '"boolean" office:boolean-value="' + (cell.v ? 'true' : 'false') + '">' + p_begin + (cell.v ? 'TRUE' : 'FALSE') + p_end + cell_end); break;
case 'n': o.push(cell_begin + mxml + vt + '"float" office:value="' + cell.v + '">' + p_begin + (cell.w||cell.v) + p_end + cell_end); break;
case 's': case 'str': o.push(cell_begin + mxml + vt + '"string">' + p_begin + escapexml(cell.v) + p_end + cell_end); break;
case 'd': o.push(cell_begin + mxml + vt + '"date" office:date-value="' + (new Date(cell.v).toISOString()) + '">' + p_begin + (cell.w||(new Date(cell.v).toISOString())) + p_end + cell_end); break;
case 'b': o.push(cell_begin + mxml + vt + '"boolean" office:boolean-value="' + (cell.v ? 'true' : 'false') + '"' + fmla + '>' + p_begin + (cell.v ? 'TRUE' : 'FALSE') + p_end + cell_end); break;
case 'n': o.push(cell_begin + mxml + vt + '"float" office:value="' + cell.v + '"' + fmla + '>' + p_begin + (cell.w||cell.v) + p_end + cell_end); break;
case 's': case 'str': o.push(cell_begin + mxml + vt + '"string"' + fmla + '>' + p_begin + escapexml(cell.v) + p_end + cell_end); break;
case 'd': o.push(cell_begin + mxml + vt + '"date" office:date-value="' + (new Date(cell.v).toISOString()) + '"' + fmla + '>' + p_begin + (cell.w||(new Date(cell.v).toISOString())) + p_end + cell_end); break;
//case 'e':
default: o.push(null_cell_xml);
} else o.push(null_cell_xml);
@ -46,7 +57,7 @@ var write_content_xml/*:{(wb:any, opts:any):string}*/ = (function() {
var o = [XML_HEADER];
/* 3.1.3.2 */
if(opts.bookType == "fods") o.push('<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">');
else o.push('<office:document-content office:version="1.2" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">\n'); // TODO
else o.push('<office:document-content office:version="1.2" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2">\n'); // TODO
o.push(' <office:body>\n');
o.push(' <office:spreadsheet>\n');
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));

@ -156,9 +156,15 @@ function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
rr = encode_row(R);
for(C = r.s.c; C <= r.e.c; ++C) {
val = sheet[cols[C] + rr];
txt = val !== undefined ? ''+format_cell(val) : "";
for(i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34) {
txt = "\"" + txt.replace(qreg, '""') + "\""; break; }
if(val == null) txt = "";
else if(val.v != null) {
txt = ''+format_cell(val);
for(i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34) {
txt = "\"" + txt.replace(qreg, '""') + "\""; break; }
} else if(val.f != null && !val.F) {
txt = '=' + val.f; if(txt.indexOf(",") >= 0) txt = '"' + txt.replace(qreg, '""') + '"';
} else txt = "";
/* NOTE: Excel CSV does not support array formulae */
row += (C === r.s.c ? "" : FS) + txt;
}
out += row + RS;

@ -10,13 +10,19 @@ cell_style_simple .xls .xlsb .xlsx .xml
comments_stress_test .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
# yes-csv
custom_properties .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
# no-formula (defined names)
defined_names_simple .xls .xlsb .xlsx .xml
#formula_stress_test .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
# yes-formula
# no-csv (randbetween) note: ODS does not support many XLSX functions
formula_stress_test .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
# yes-csv
formulae_test_simple .xls .xlsb .xlsx .xml
hyperlink_stress_test_2011 .xls .xlsb .xlsx .xml
#large_strings .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
merge_cells .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
# no-formula (defined names)
named_ranges_2011 .xls .xlsb .xlsx .xls.xml .xlsb.xml .xlsx.xml
# yes-formula
# no-csv (macro serialization in xml)
number_format .xls .xlsb .xlsm .xls.xml .xlsb.xml .xlsm.xml
number_format_entities .xls .xlsb .xlsx .xml

34
test.js

@ -838,9 +838,7 @@ function seq(end, start) {
}
describe('roundtrip features', function() {
var bef = (function() {
X = require(modp);
});
var bef = (function() { X = require(modp); });
if(typeof before != 'undefined') before(bef);
else it('before', bef);
describe('should parse core properties and custom properties', function() {
@ -907,14 +905,18 @@ describe('roundtrip features', function() {
});
});
describe('xls to xlsx conversions', function() { [
['XLS', 'formula_stress_test.xls'],
['XML', 'formula_stress_test.xls.xml']
describe('should preserve formulae', function() { [
['xlml', paths.fstxml],
['xlsx', paths.fstxlsx],
['ods', paths.fstods]
].forEach(function(w) {
it('should be able to write ' + w[0] + ' files', function() {
var xls = X.readFile('./test_files/' + w[1], {cellNF:true});
X.writeFile(xls, './tmp/' + w[1] + '.xlsx', {bookSST:true});
X.writeFile(xls, './tmp/' + w[1] + '.xlsb', {bookSST:true});
it(w[0], function() {
var wb1 = X.readFile(w[1], {cellFormula:true});
if(w[0] == 'ods') X.writeFile(wb1, "./tmp/_.ods", {bookType:"ods"});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"buffer"}), {cellFormula:true, type:"buffer"});
wb1.SheetNames.forEach(function(n) {
assert.equal( X.utils.sheet_to_formulae(wb1.Sheets[n]).sort().join("\n"), X.utils.sheet_to_formulae(wb2.Sheets[n]).sort().join("\n") );
});
});
});
});
@ -1209,7 +1211,7 @@ describe('encryption', function() {
describe('multiformat tests', function() {
var mfopts = opts;
var mft = fs.readFileSync('multiformat.lst','utf-8').split("\n");
var csv = true;
var csv = true, formulae = false;
mft.forEach(function(x) {
if(x[0]!="#") describe('MFT ' + x, function() {
var fil = {}, f = [], r = x.split(/\s+/);
@ -1239,10 +1241,20 @@ mft.forEach(function(x) {
cmparr(f.map(function(x) { return X.utils.sheet_to_csv(x.Sheets[name]); }));
});
} : null);
it('should have the same formulae', formulae ? function() {
cmparr(f.map(function(x) { return x.SheetNames; }));
var names = f[0].SheetNames;
names.forEach(function(name) {
cmparr(f.map(function(x) { return X.utils.sheet_to_formulae(x.Sheets[name]).sort(); }));
});
} : null);
});
else x.split(/\s+/).forEach(function(w) { switch(w) {
case "no-csv": csv = false; break;
case "yes-csv": csv = true; break;
case "no-formula": formulae = false; break;
case "yes-formula": formulae = true; break;
}});
});
});

@ -1 +1 @@
Subproject commit 8ab3308622f83ac47a0ee584d61e11357b01f925
Subproject commit e0b7060b10ef972ad48869faa4efffd089e1a428

245
tests.lst

@ -29,17 +29,6 @@ sushi.xlsb
text_and_numbers.xlsb
time_stress_test_1.xlsb.pending
xlsx-stream-d-date-cell.xlsb
2013/apachepoi_29982.xls.xlsb
2013/apachepoi_43251.xls.xlsb
2013/apachepoi_44593.xls.xlsb ## xlsb loop timeout
2013/apachepoi_44643.xls.xlsb
2013/apachepoi_44958.xls.xlsb
2013/apachepoi_46136-NoWarnings.xls.xlsb
2013/apachepoi_48968.xls.xlsb
2013/apachepoi_50939.xls.xlsb
2013/apachepoi_54016.xls.xlsb
2013/apachepoi_ReadOnlyRecommended.xls.xlsb
2013/apachepoi_testArraysAndTables.xls.xlsb
AutoFilter.xlsx
ErrorTypes.xlsx
LONumbers-2010.xlsx
@ -199,6 +188,7 @@ apachepoi_DataTableCities.xlsx
apachepoi_DataValidationEvaluations.xlsx
apachepoi_DataValidations-49244.xlsx
# apachepoi_DateFormatTests.xlsx # xlml
apachepoi_DateFormatTests.xlsx.xlsx
apachepoi_ElapsedFormatTests.xlsx
apachepoi_ExcelTables.xlsx
apachepoi_ForShifting.xlsx
@ -249,6 +239,7 @@ apachepoi_WithTwoCharts.xlsx
apachepoi_WithVariousData.xlsx
apachepoi_XSSFSheet.copyRows.xlsx
apachepoi_atp.xlsx
apachepoi_bug60858.xlsx
apachepoi_chartTitle_noTitle.xlsx
apachepoi_chartTitle_withTitle.xlsx
apachepoi_chart_sheet.xlsx.pending
@ -362,6 +353,8 @@ openpyxl_r_null_file.xlsx.pending
phonetic_text.xlsx
pivot_table_named_range.xlsx
rich_text_stress.xlsx
roo-xls_numbers1.xlsx
roo-xls_type_excel.xlsx
roo_1900_base.xlsx
roo_1904_base.xlsx
roo_Bibelbund.xlsx
@ -404,6 +397,44 @@ roo_whitespace.xlsx
roo_x000D.xlsx
roo_zero-padded-number.xlsx
smart_tags_2007.xlsx
spout-xlsx_attack_billion_laughs.xlsx
spout-xlsx_attack_quadratic_blowup.xlsx
# spout-xlsx_file_corrupted.xlsx
spout-xlsx_file_with_no_sheets_in_workbook_xml.xlsx
# spout-xlsx_file_with_sheet_xml_not_matching_content_types.xlsx
spout-xlsx_one_sheet_with_inline_strings.xlsx
spout-xlsx_one_sheet_with_invalid_xml_characters.xlsx
spout-xlsx_one_sheet_with_shared_multiline_strings.xlsx
spout-xlsx_one_sheet_with_shared_strings.xlsx
spout-xlsx_one_sheet_with_shared_strings_containing_text_and_hyperlink_in_same_cell.xlsx
spout-xlsx_one_sheet_with_shared_strings_missing_unique_count.xlsx
spout-xlsx_one_sheet_with_shared_strings_missing_unique_count_and_count.xlsx
spout-xlsx_sheet_with_all_cell_types.xlsx
spout-xlsx_sheet_with_custom_date_formats_and_no_apply_number_format.xlsx
spout-xlsx_sheet_with_dates_and_times.xlsx
spout-xlsx_sheet_with_different_numeric_value_dates.xlsx
spout-xlsx_sheet_with_different_numeric_value_times.xlsx
spout-xlsx_sheet_with_dimensions_and_empty_cells.xlsx
spout-xlsx_sheet_with_empty_cells.xlsx
spout-xlsx_sheet_with_empty_rows_and_missing_row_index.xlsx
spout-xlsx_sheet_with_empty_shared_string.xlsx
spout-xlsx_sheet_with_formulas.xlsx
spout-xlsx_sheet_with_lots_of_shared_strings.xlsx
spout-xlsx_sheet_with_missing_cell_reference.xlsx
spout-xlsx_sheet_with_no_cells.xlsx
spout-xlsx_sheet_with_no_shared_strings_file.xlsx
spout-xlsx_sheet_with_prefixed_xml_files.xlsx
spout-xlsx_sheet_with_preserve_space_shared_strings.xlsx
spout-xlsx_sheet_with_pronunciation.xlsx
spout-xlsx_sheet_with_same_numeric_value_date_formatted_differently.xlsx
spout-xlsx_sheet_with_untrimmed_inline_strings.xlsx
spout-xlsx_sheet_with_zeros_in_row.xlsx
spout-xlsx_sheet_without_dimensions_and_empty_cells.xlsx
spout-xlsx_sheet_without_dimensions_but_spans_and_empty_cells.xlsx
spout-xlsx_two_sheets_with_custom_names.xlsx
spout-xlsx_two_sheets_with_inline_strings.xlsx
spout-xlsx_two_sheets_with_shared_strings.xlsx
spout-xlsx_two_sheets_with_sheets_definition_in_reverse_order.xlsx
spreadsheet-parsexlsx_Test.xlsx
spreadsheet-parsexlsx_bug-10.xlsx
spreadsheet-parsexlsx_bug-11.xlsx
@ -426,7 +457,7 @@ spreadsheet-parsexlsx_bug-5.xlsx
spreadsheet-parsexlsx_bug-57.xlsx
spreadsheet-parsexlsx_bug-6-2.xlsx
spreadsheet-parsexlsx_bug-6.xlsx
spreadsheet-parsexlsx_bug-61.xlsx
# spreadsheet-parsexlsx_bug-61.xlsx
spreadsheet-parsexlsx_bug-7.xlsx
spreadsheet-parsexlsx_bug-8.xlsx
spreadsheet-parsexlsx_bug-lock.xlsx
@ -469,9 +500,9 @@ hyperlink_no_rels.xlsm
number_format.xlsm
number_format_russian.xlsm
numfmt_1_russian.xlsm
openpyxl_r_vba+comments.xlsm
openpyxl_r_vba-comments-saved.xlsm
openpyxl_r_vba-test.xlsm
# openpyxl_r_vba+comments.xlsm
# openpyxl_r_vba-comments-saved.xlsm
# openpyxl_r_vba-test.xlsm
# pivot_table_test.xlsm # xlml
roo_1900_base.xlsm
roo_1904_base.xlsm
@ -507,6 +538,8 @@ formula_stress_test.ods
merge_cells.ods
number_format.ods
rich_text_stress.ods
roo-xls_numbers1.ods
roo-xls_type_excel.ods
roo_Bibelbund.ods
roo_Bibelbund1.ods
roo_advanced_header.ods
@ -534,6 +567,32 @@ roo_time-test.ods
roo_type_excel.ods
roo_type_excelx.ods
roo_whitespace.ods
spout-ods_attack_billion_laughs.ods
spout-ods_attack_quadratic_blowup.ods
# spout-ods_file_corrupted.ods
spout-ods_file_generated_by_excel_2010_windows.ods
spout-ods_file_generated_by_excel_office_online.ods
spout-ods_file_generated_by_libre_office.ods
spout-ods_one_sheet_with_strings.ods
spout-ods_sheet_with_all_cell_types.ods
spout-ods_sheet_with_dates_and_times.ods
spout-ods_sheet_with_empty_cells.ods
spout-ods_sheet_with_empty_rows.ods
spout-ods_sheet_with_formulas.ods
spout-ods_sheet_with_hyperlinks.ods
spout-ods_sheet_with_inline_font_formatting.ods
spout-ods_sheet_with_invalid_date_time.ods.pending
spout-ods_sheet_with_multiline_string.ods
spout-ods_sheet_with_no_cells.ods
spout-ods_sheet_with_number_columns_repeated.ods
spout-ods_sheet_with_number_rows_repeated.ods
spout-ods_sheet_with_only_one_cell.ods
spout-ods_sheet_with_undefined_value_type.ods
spout-ods_sheet_with_untrimmed_strings.ods
spout-ods_sheet_with_various_spaces.ods
spout-ods_sheet_with_zeros_in_row.ods
spout-ods_two_sheets_with_custom_names.ods
spout-ods_two_sheets_with_strings.ods
sushi.ods
biff5/NumberFormatCondition.xls
biff5/RkNumber.xls
@ -612,6 +671,7 @@ apachepoi_33082.xls
apachepoi_34775.xls
apachepoi_35564.xls
apachepoi_35565.xls
# apachepoi_35897-type4.xls ## password
apachepoi_36947.xls
apachepoi_37376.xls
apachepoi_37630.xls
@ -649,6 +709,7 @@ apachepoi_44840.xls
apachepoi_44861.xls
apachepoi_44891.xls
apachepoi_44958.xls
apachepoi_44958_1.xls
apachepoi_45129.xls
apachepoi_45290.xls
apachepoi_45322.xls
@ -659,6 +720,7 @@ apachepoi_45538_classic_Footer.xls
apachepoi_45538_classic_Header.xls
apachepoi_45538_form_Footer.xls
apachepoi_45538_form_Header.xls
apachepoi_45565.xls
apachepoi_45672.xls
apachepoi_45720.xls
apachepoi_45761.xls
@ -669,12 +731,14 @@ apachepoi_46137.xls
apachepoi_46250.xls
apachepoi_46368.xls
apachepoi_46445.xls
apachepoi_46515.xls
apachepoi_46670_http.xls
apachepoi_46670_local.xls
apachepoi_46670_ref_airline.xls
apachepoi_46904.xls
apachepoi_47034.xls
apachepoi_47154.xls
apachepoi_47245_test.xls
apachepoi_47251.xls
apachepoi_47251_1.xls
apachepoi_47701.xls
@ -690,6 +754,7 @@ apachepoi_49096.xls
apachepoi_49185.xls
apachepoi_49219.xls
apachepoi_49237.xls
apachepoi_49423.xls
apachepoi_49524.xls
apachepoi_49529.xls
apachepoi_49581.xls
@ -717,6 +782,7 @@ apachepoi_51832.xls.pending
apachepoi_52527.xls
apachepoi_52575_main.xls
apachepoi_52575_source.xls
apachepoi_53109.xls
apachepoi_53404.xls
apachepoi_53433.xls
apachepoi_53446.xls
@ -730,24 +796,44 @@ apachepoi_54206.xls
apachepoi_54500.xls
apachepoi_54686_fraction_formats.xls
apachepoi_55341_CellStyleBorder.xls
apachepoi_55668.xls
apachepoi_55906-MultiSheetRefs.xls
apachepoi_55982.xls
apachepoi_56325.xls
apachepoi_56325a.xls
apachepoi_56450.xls
apachepoi_56482.xls
apachepoi_56563a.xls
apachepoi_56563b.xls
apachepoi_56737.xls
apachepoi_57003-FixedFunctionTestCaseData.xls
apachepoi_57074.xls
apachepoi_57163.xls
apachepoi_57231_MixedGasReport.xls.pending
apachepoi_57456.xls.pending
apachepoi_57798.xls
apachepoi_57925.xls
apachepoi_59074.xls
apachepoi_59264.xls
apachepoi_59830.xls
apachepoi_59858.xls
apachepoi_60273.xls
# apachepoi_60284.xls
apachepoi_AbnormalSharedFormulaFlag.xls
apachepoi_AreaErrPtg.xls
apachepoi_BOOK_in_capitals.xls
apachepoi_Basic_Expense_Template_2011.xls
apachepoi_CodeFunctionTestCaseData.xls
apachepoi_ColumnStyle1dp.xls
apachepoi_ColumnStyle1dpColoured.xls
apachepoi_ColumnStyleNone.xls
apachepoi_ComplexFunctionTestCaseData.xls
apachepoi_ConditionalFormattingSamples.xls
apachepoi_ContinueRecordProblem.xls
apachepoi_DBCSHeader.xls
apachepoi_DBCSSheetName.xls
apachepoi_DGet.xls
apachepoi_DStar.xls
apachepoi_DateFormats.xls
apachepoi_DeltaFunctionTestCaseData.xls
apachepoi_DrawingAndComments.xls
@ -758,6 +844,7 @@ apachepoi_ErrPtg.xls
apachepoi_FactDoubleFunctionTestCaseData.xls
apachepoi_ForShifting.xls
apachepoi_FormatChoiceTests.xls
apachepoi_FormatKM.xls
apachepoi_Formatting.xls
apachepoi_FormulaEvalTestData.xls
apachepoi_FormulaRefs.xls
@ -775,6 +862,7 @@ apachepoi_IrrNpvTestCaseData.xls
apachepoi_MRExtraLines.xls
apachepoi_MatchFunctionTestCaseData.xls
apachepoi_MissingBits.xls
apachepoi_NewStyleConditionalFormattings.xls
apachepoi_NoGutsRecords.xls
apachepoi_OddStyleRecord.xls
apachepoi_PercentPtg.xls
@ -791,7 +879,9 @@ apachepoi_SheetWithDrawing.xls
apachepoi_ShrinkToFit.xls
apachepoi_Simple.xls
apachepoi_SimpleChart.xls
apachepoi_SimpleMacro.xls
apachepoi_SimpleMultiCell.xls
apachepoi_SimpleScatterChart.xls
apachepoi_SimpleWithAutofilter.xls
apachepoi_SimpleWithChoose.xls
apachepoi_SimpleWithColours.xls
@ -811,6 +901,7 @@ apachepoi_StringContinueRecords.xls
apachepoi_StringFormulas.xls
apachepoi_SubtotalsNested.xls
apachepoi_TestRandBetween.xls
apachepoi_Themes2.xls
apachepoi_TwoSheetsNoneHidden.xls
apachepoi_TwoSheetsOneHidden.xls
# apachepoi_UncalcedRecord.xls # xlml
@ -831,10 +922,13 @@ apachepoi_WithTwoHyperLinks.xls
apachepoi_WrongFormulaRecordType.xls
apachepoi_XRefCalc.xls
apachepoi_XRefCalcData.xls
apachepoi_ar.org.apsme.www_Form%20Inscripcion%20Curso%20NO%20Socios.xls
apachepoi_at.gv.land-oberoesterreich.www_cps_rde_xbcr_SID-4A1B954F-5C07F98E_ooe_stat_download_bp10.xls
apachepoi_atp.xls
apachepoi_blankworkbook.xls
apachepoi_bug_42794.xls
apachepoi_colwidth.xls
apachepoi_com.aida-tour.www_SPO_files_maldives%20august%20october.xls
apachepoi_comments.xls
apachepoi_countblankExamples.xls
apachepoi_countifExamples.xls
@ -861,24 +955,32 @@ apachepoi_excel_with_embeded.xls
apachepoi_excelant.xls.pending
apachepoi_externalFunctionExample.xls
# apachepoi_finance.xls # xlml
apachepoi_florida_data.ashx.xls
apachepoi_intercept.xls
apachepoi_mirrTest.xls
apachepoi_missingFuncs44675.xls
apachepoi_mortgage-calculation.xls
apachepoi_multibookFormulaA.xls
apachepoi_multibookFormulaB.xls
apachepoi_named-cell-in-formula-test.xls
apachepoi_named-cell-test.xls
apachepoi_namedinput.xls
apachepoi_noHeaderFooter47244.xls
apachepoi_ole2-embedding.xls
apachepoi_overlapSharedFormula.xls
apachepoi_password.xls.pending
apachepoi_rank.xls
apachepoi_resize_compare.xls
apachepoi_rk.xls
apachepoi_shared_formulas.xls
apachepoi_sumifformula.xls
# apachepoi_sumifs.xls # xlml
apachepoi_templateExcelWithAutofilter.xls
apachepoi_testArraysAndTables.xls
apachepoi_testEXCEL_3.xls
apachepoi_testEXCEL_4.xls
apachepoi_testEXCEL_5.xls
apachepoi_testEXCEL_95.xls
apachepoi_testNames.xls
apachepoi_testRRaC.xls
apachepoi_testRVA.xls
@ -1091,40 +1193,41 @@ pyExcelerator_mini-mini.xls
pyExcelerator_mini.xls
pyExcelerator_oo14.xls
rich_text_stress.xls
roo_1900_base.xls
roo_1904_base.xls
roo_Bibelbund.xls
roo_bad_excel_date.xls
roo_bbu.xls
roo_boolean.xls
roo_borders.xls
roo_bug-row-column-fixnum-float.xls
roo_comments.xls
roo_datetime.xls
roo_datetime_floatconv.xls
roo_emptysheets.xls
roo_false_encoding.xls
roo_formula.xls
roo_formula_parse_error.xls
roo_link.xls
roo_matrix.xls
roo_named_cells.xls
roo_numbers1.xls
roo_only_one_sheet.xls
roo_paragraph.xls
roo_prova.xls
roo_simple_spreadsheet.xls
roo_simple_spreadsheet_from_italo.xls
roo_style.xls
roo_time-test.xls
roo_type_excelx.xls
roo_type_openoffice.xls
roo-xls_1900_base.xls
roo-xls_1904_base.xls
roo-xls_Bibelbund.xls
roo-xls_bad_excel_date.xls
roo-xls_bbu.xls
roo-xls_boolean.xls
roo-xls_borders.xls
roo-xls_bug-row-column-fixnum-float.xls
roo-xls_comments.xls
roo-xls_datetime.xls
roo-xls_datetime_floatconv.xls
roo-xls_emptysheets.xls
roo-xls_false_encoding.xls
roo-xls_formula.xls
roo-xls_formula_parse_error.xls
roo-xls_link.xls
roo-xls_matrix.xls
roo-xls_named_cells.xls
roo-xls_numbers1.xls
roo-xls_only_one_sheet.xls
roo-xls_paragraph.xls
roo-xls_prova.xls
roo-xls_simple_spreadsheet.xls
roo-xls_simple_spreadsheet_from_italo.xls
roo-xls_style.xls
roo-xls_time-test.xls
roo-xls_type_excelx.xls
roo-xls_type_openoffice.xls
roo_whitespace.xls
smart_tags_2007.xls
sushi.xls
text_and_numbers.xls
write.xls
xlrd_Formate.xls
# xlrd_biff4_no_format_no_window2.xls
xlrd_formula_test_names.xls
xlrd_formula_test_sjmachin.xls
xlrd_issue20.xls
@ -1135,7 +1238,6 @@ xlrd_xf_class.xls
xlsx-stream-d-date-cell.xls
AutoFilter.xml
BlankSheetTypes.xml
ErrorTypes.xml
LONumbers-2010.xls.xml
LONumbers-2010.xlsx.xml
LONumbers-2011.xls.xml
@ -1145,6 +1247,7 @@ NumberFormatCondition.xml
RkNumber.xls.xml
RkNumber.xlsb.xml
RkNumber.xlsx.xml
apachepoi_SampleSS.xml
calendar_stress_test.xml.pending
cell_style_simple.xml
comments_stress_test.xls.xml
@ -1197,26 +1300,28 @@ protect_stress_test_xml.xml
rich_text_stress.xls.xml
rich_text_stress.xlsb.xml
rich_text_stress.xlsx.xml
roo_Bibelbund.xml
roo_bbu.xml
roo_boolean.xml
roo_borders.xml
roo_bug-row-column-fixnum-float.xml
roo_datetime.xml
roo_datetime_floatconv.xml
roo_emptysheets.xml
roo_excel2003.xml
roo_false_encoding.xml
roo_formula.xml
roo_formula_parse_error.xml
roo_numbers1.xml
roo_only_one_sheet.xml
roo_paragraph.xml
roo_simple_spreadsheet.xml
roo_simple_spreadsheet_from_italo.xml
roo_style.xml
roo_time-test.xml
roo_whitespace.xml
roo-xls_Bibelbund.xml
roo-xls_bbu.xml
roo-xls_boolean.xml
roo-xls_borders.xml
roo-xls_bug-row-column-fixnum-float.xml
roo-xls_datetime.xml
roo-xls_datetime_floatconv.xml
roo-xls_emptysheets.xml
roo-xls_excel2003.xml
roo-xls_excel2003_namespace.xml
roo-xls_false_encoding.xml
roo-xls_formula.xml
roo-xls_formula_parse_error.xml
roo-xls_numbers1.xml
roo-xls_only_one_sheet.xml
roo-xls_paragraph.xml
roo-xls_simple_spreadsheet.xml
roo-xls_simple_spreadsheet_from_italo.xml
roo-xls_style.xml
roo-xls_time-test.xml
roo-xls_whitespace.xml
# roo_sheet1.xml
smart_tags_2007.xml
sushi.xml
text_and_numbers.xml
@ -1229,3 +1334,15 @@ xlsx-stream-d-date-cell.xlsx.xml
2011/apachepoi_styles.xlsx.xml
2011/openpyxl_r_conditional-formatting.xlsx.xls
2011/roo_file_item_error.xlsx.xml
2013/apachepoi_29982.xls.xlsb
2013/apachepoi_43251.xls.xlsb
2013/apachepoi_44593.xls.xlsb ## xlsb loop timeout
2013/apachepoi_44643.xls.xlsb
2013/apachepoi_44958.xls.xlsb
2013/apachepoi_46136-NoWarnings.xls.xlsb
2013/apachepoi_48968.xls.xlsb
2013/apachepoi_50939.xls.xlsb
2013/apachepoi_54016.xls.xlsb
2013/apachepoi_ReadOnlyRecommended.xls.xlsb
2013/apachepoi_testArraysAndTables.xls.xlsb

@ -79,14 +79,32 @@ var ws = sheet_from_array_of_arrays(data);
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
/* TEST: simple formula */
ws['C1'].f = "A1+B1";
ws['C2'] = {t:'n', f:"A1+B1"};
/* TEST: single-cell array formula */
ws['D1'] = {t:'n', f:"SUM(A1:C1*A1:C1)", F:"D1:D1"};
/* TEST: multi-cell array formula */
ws['E1'] = {t:'n', f:"TRANSPOSE(A1:D1)", F:"E1:E4"};
ws['E2'] = {t:'n', F:"E1:E4"};
ws['E3'] = {t:'n', F:"E1:E4"};
ws['E4'] = {t:'n', F:"E1:E4"};
ws["!ref"] = "A1:E4";
/* TEST: column widths */
ws['!cols'] = wscols;
console.log("JSON Data: "); console.log(XLSX.utils.sheet_to_json(ws, {header:1}));
/* write file */
XLSX.writeFile(wb, 'sheetjs.xlsx');
XLSX.writeFile(wb, 'sheetjs.xlsx', {bookSST:true});
XLSX.writeFile(wb, 'sheetjs.xlsm');
XLSX.writeFile(wb, 'sheetjs.xlsb');
XLSX.writeFile(wb, 'sheetjs.xls', {bookType:'biff2'});
XLSX.writeFile(wb, 'sheetjs.xlsb'); // no formula
XLSX.writeFile(wb, 'sheetjs.xls', {bookType:'biff2'}); // no formula
XLSX.writeFile(wb, 'sheetjs.xml.xls', {bookType:'xlml'});
XLSX.writeFile(wb, 'sheetjs.ods');
XLSX.writeFile(wb, 'sheetjs.fods');
XLSX.writeFile(wb, 'sheetjs.csv');
@ -96,6 +114,7 @@ XLSX.readFile('sheetjs.xlsx');
XLSX.readFile('sheetjs.xlsm');
XLSX.readFile('sheetjs.xlsb');
XLSX.readFile('sheetjs.xls');
XLSX.readFile('sheetjs.xml.xls');
XLSX.readFile('sheetjs.ods');
XLSX.readFile('sheetjs.fods');
//XLSX.readFile('sheetjs.csv');

@ -422,7 +422,18 @@ function hashq(str/*:string*/)/*:string*/ {
return o;
}
function rnd(val/*:number*/, d/*:number*/)/*:string*/ { var dd = Math.pow(10,d); return ""+(Math.round(val * dd)/dd); }
function dec(val/*:number*/, d/*:number*/)/*:number*/ { return Math.round((val-Math.floor(val))*Math.pow(10,d)); }
function dec(val/*:number*/, d/*:number*/)/*:number*/ {
if (d < ('' + Math.round((val-Math.floor(val))*Math.pow(10,d))).length) {
return 0;
}
return Math.round((val-Math.floor(val))*Math.pow(10,d));
}
function carry(val/*:number*/, d/*:number*/)/*:number*/ {
if (d < ('' + Math.round((val-Math.floor(val))*Math.pow(10,d))).length) {
return 1;
}
return 0;
}
function flr(val/*:number*/)/*:string*/ { if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0)); return ""+Math.floor(val); }
function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/ {
if(type.charCodeAt(0) === 40 && !fmt.match(closeparen)) {
@ -444,8 +455,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
if((r = fmt.match(frac1))) return write_num_f1(r, aval, sign);
if(fmt.match(/^#+0+$/)) return sign + pad0r(aval,fmt.length - fmt.indexOf("0"));
if((r = fmt.match(dec1))) {
// $FlowIgnore
o = rnd(val, r[1].length).replace(/^([^\.]+)$/,"$1."+r[1]).replace(/\.$/,"."+r[1]).replace(/\.(\d*)$/,function($$, $1) { return "." + $1 + fill("0", r[1].length-$1.length); });
o = rnd(val, r[1].length).replace(/^([^\.]+)$/,"$1."+r[1]).replace(/\.$/,"."+r[1]).replace(/\.(\d*)$/,function($$, $1) { return "." + $1 + fill("0", /*::(*/r/*::||[""])*/[1].length-$1.length); });
return fmt.indexOf("0.") !== -1 ? o : o.replace(/^0\./,".");
}
fmt = fmt.replace(/^#+([0.])/, "$1");
@ -454,7 +464,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
}
if((r = fmt.match(/^#,##0(\.?)$/))) return sign + commaify(pad0r(aval,0));
if((r = fmt.match(/^#,##0\.([#0]*0)$/))) {
return val < 0 ? "-" + write_num_flt(type, fmt, -val) : commaify(""+(Math.floor(val))) + "." + pad0(dec(val, r[1].length),r[1].length);
return val < 0 ? "-" + write_num_flt(type, fmt, -val) : commaify(""+(Math.floor(val) + carry(val, r[1].length))) + "." + pad0(dec(val, r[1].length),r[1].length);
}
if((r = fmt.match(/^#,#*,#0/))) return write_num_flt(type,fmt.replace(/^#,#*,/,""),val);
if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {
@ -8201,8 +8211,18 @@ function ods_to_csf_formula(f/*:string*/)/*:string*/ {
f = f.substr(1);
if(f.charCodeAt(0) == 61) f = f.substr(1);
}
f = f.replace(/COM\.MICROSOFT\./g, "");
/* Part 3 Section 5.8 References */
return f.replace(/\[((?:\.[A-Z]+[0-9]+)(?::\.[A-Z]+[0-9]+)?)\]/g, "$1").replace(/\./g, "");
f = f.replace(/\[((?:\.[A-Z]+[0-9]+)(?::\.[A-Z]+[0-9]+)?)\]/g, function($$, $1) { return $1.replace(/\./g,""); });
/* TODO: something other than this */
f = f.replace(/\[.(#[A-Z]*[?!])\]/g, "$1");
return f.replace(/[;~]/g,",").replace(/\|/g,";");
}
function csf_to_ods_formula(f/*:string*/)/*:string*/ {
var o = "of:=" + f.replace(crefregex, "$1[.$2$3$4$5]").replace(/\]:\[/g,":");
/* TODO: something other than this */
return o.replace(/;/g, "|").replace(/,/g,";");
}
var strs = {}; // shared strings
var _ssfopts = {}; // spreadsheet formatting options
@ -8390,7 +8410,7 @@ function write_ws_xml_cols(ws, cols)/*:string*/ {
}
function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
if(cell.v === undefined || cell.t === 'z') return "";
if(cell.v === undefined && cell.f === undefined || cell.t === 'z') return "";
var vv = "";
var oldt = cell.t, oldv = cell.v;
switch(cell.t) {
@ -8416,7 +8436,7 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
case 'd': o.t = "d"; break;
case 'b': o.t = "b"; break;
case 'e': o.t = "e"; break;
default:
default: if(cell.v == null) { delete cell.t; break; }
if(opts.bookSST) {
v = writetag('v', ''+get_sst_id(opts.Strings, cell.v));
o.t = "s"; break;
@ -8424,6 +8444,10 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
o.t = "str"; break;
}
if(cell.t != oldt) { cell.t = oldt; cell.v = oldv; }
if(cell.f) {
var ff = cell.F && cell.F.substr(0, ref.length) == ref ? {t:"array", ref:cell.F} : null;
v = writextag('f', escapexml(cell.f), ff) + (cell.v != null ? v : "");
}
return writextag('c', v, o);
}
@ -8479,7 +8503,8 @@ return function parse_ws_xml_data(sdata, s, opts, guess) {
if((cref=d.match(match_v))!= null && /*::cref != null && */cref[1] !== '') p.v=unescapexml(cref[1]);
if(opts.cellFormula) {
if((cref=d.match(match_f))!= null && /*::cref != null && */cref[1] !== '') {
p.f=unescapexml(utf8read(cref[1]));
/* TODO: match against XLSXFutureFunctions */
p.f=unescapexml(utf8read(cref[1])).replace(/_xlfn\./,"");
if(/*::cref != null && cref[0] != null && */cref[0].indexOf('t="array"') > -1) {
p.F = (d.match(refregex)||[])[1];
if(p.F.indexOf(":") > -1) arrayf.push([safe_decode_range(p.F), p.F]);
@ -9842,7 +9867,7 @@ function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, a
case 'DateTime':
cell.v = (Date.parse(xml) - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
if(cell.v !== cell.v) cell.v = unescapexml(xml);
else if(cell.v >= 1 && cell.v<60) cell.v = cell.v -1;
else if(cell.v<60) cell.v = cell.v -1;
if(!nf || nf == "General") nf = "yyyy-mm-dd";
/* falls through */
case 'Number':
@ -9984,6 +10009,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
sheetname = unescapexml(tmp.Name);
cursheet = {};
mergecells = [];
arrayf = [];
}
break;
case 'Table':
@ -10493,10 +10519,14 @@ function write_sty_xlml(wb, opts)/*:string*/ {
}
/* TODO */
function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
if(!cell || cell.v === undefined) return "<Cell></Cell>";
if(!cell || cell.v == undefined && cell.f == undefined) return "<Cell></Cell>";
var attr = {};
if(cell.f) attr["ss:Formula"] = "=" + escapexml(a1_to_rc(cell.f, addr));
if(cell.F && cell.F.substr(0, ref.length) == ref) {
var end = decode_cell(cell.F.substr(ref.length + 1));
attr["ss:ArrayRange"] = "RC:R" + (end.r == addr.r ? "" : "[" + (end.r - addr.r) + "]") + "C" + (end.c == addr.c ? "" : "[" + (end.c - addr.c) + "]");
}
if(ws['!merges']) {
var marr = ws['!merges'];
@ -10514,9 +10544,9 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break;
case 'e': t = 'Error'; p = BErr[cell.v]; break;
case 'd': t = 'DateTime'; p = new Date(cell.v).toISOString(); break;
case 's': t = 'String'; p = escapexml(cell.v||""); break;
case 's': t = 'String'; p = escapexml(cell.v||""); break;
}
var m = '<Data ss:Type="' + t + '">' + p + '</Data>';
var m = '<Data ss:Type="' + t + '">' + (cell.v != null ? p : "") + '</Data>';
return writextag("Cell", m, attr);
}
@ -10699,6 +10729,19 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(cell.r + 1 > range.e.r) range.e.r = cell.r + 1;
if(cell.c + 1 > range.e.c) range.e.c = cell.c + 1;
}
if(options.cellFormula && line.f) {
for(var afi = 0; afi < array_formulae.length; ++afi) {
if(array_formulae[afi][0].s.c > cell.c) continue;
if(array_formulae[afi][0].s.r > cell.r) continue;
if(array_formulae[afi][0].e.c < cell.c) continue;
if(array_formulae[afi][0].e.r < cell.r) continue;
line.F = encode_range(array_formulae[afi][0]);
if(array_formulae[afi][0].s.c != cell.c) delete line.f;
if(array_formulae[afi][0].s.r != cell.r) delete line.f;
if(line.f) line.f = "" + stringify_formula(array_formulae[afi][1], range, cell, supbooks, opts);
break;
}
}
if(options.sheetRows && lastcell.r >= options.sheetRows) cell_valid = false;
else out[last_cell] = line;
};
@ -10851,7 +10894,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
else cur_sheet = (Directory[s] || {name:""}).name;
mergecells = [];
objects = [];
array_formulae = []; opts.arrayf = array_formulae;
} break;
case 'Number': case 'BIFF2NUM': case 'BIFF2INT': {
temp_val = {ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.val, t:'n'};
safe_format_xf(temp_val, options, wb.opts.Date1904);
@ -10876,44 +10921,43 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
}
} break;
case 'Formula': {
switch(val.val) {
case 'String': last_formula = val; break;
case 'Array Formula': throw "Array Formula unsupported";