diff --git a/README.md b/README.md
index 3b171ab..73fd798 100644
--- a/README.md
+++ b/README.md
@@ -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.
 
diff --git a/bits/10_ssf.js b/bits/10_ssf.js
index bca57a7..715a869 100644
--- a/bits/10_ssf.js
+++ b/bits/10_ssf.js
@@ -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#]+))+$/))) {
diff --git a/bits/65_fods.js b/bits/65_fods.js
index fee62c9..ec9bdf1 100644
--- a/bits/65_fods.js
+++ b/bits/65_fods.js
@@ -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,";");
 }
diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js
index 6ef817e..2218da9 100644
--- a/bits/67_wsxml.js
+++ b/bits/67_wsxml.js
@@ -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]);
diff --git a/bits/75_xlml.js b/bits/75_xlml.js
index 351d4fb..992c9a8 100644
--- a/bits/75_xlml.js
+++ b/bits/75_xlml.js
@@ -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);
 }
diff --git a/bits/76_xls.js b/bits/76_xls.js
index 370c8ca..5fff527 100644
--- a/bits/76_xls.js
+++ b/bits/76_xls.js
@@ -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;
diff --git a/bits/78_writebiff.js b/bits/78_writebiff.js
index 4b67c2c..6e4b439 100644
--- a/bits/78_writebiff.js
+++ b/bits/78_writebiff.js
@@ -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*/) {
diff --git a/bits/80_parseods.js b/bits/80_parseods.js
index 71cdbc0..6a6af23 100644
--- a/bits/80_parseods.js
+++ b/bits/80_parseods.js
@@ -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;
diff --git a/bits/81_writeods.js b/bits/81_writeods.js
index 7d47f3a..cd23d56 100644
--- a/bits/81_writeods.js
+++ b/bits/81_writeods.js
@@ -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));
diff --git a/bits/90_utils.js b/bits/90_utils.js
index b1f3b89..fccdd0c 100644
--- a/bits/90_utils.js
+++ b/bits/90_utils.js
@@ -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;
diff --git a/multiformat.lst b/multiformat.lst
index 1befc01..64d2900 100644
--- a/multiformat.lst
+++ b/multiformat.lst
@@ -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
diff --git a/test.js b/test.js
index 30b72e5..4c55568 100644
--- a/test.js
+++ b/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;
 	}});
 });
 });
diff --git a/test_files b/test_files
index 8ab3308..e0b7060 160000
--- a/test_files
+++ b/test_files
@@ -1 +1 @@
-Subproject commit 8ab3308622f83ac47a0ee584d61e11357b01f925
+Subproject commit e0b7060b10ef972ad48869faa4efffd089e1a428
diff --git a/tests.lst b/tests.lst
index 1e096d3..691e217 100644
--- a/tests.lst
+++ b/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
+
diff --git a/tests/write.js b/tests/write.js
index 73a267b..08cff7a 100644
--- a/tests/write.js
+++ b/tests/write.js
@@ -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');
diff --git a/xlsx.flow.js b/xlsx.flow.js
index 3f56588..bff77fc 100644
--- a/xlsx.flow.js
+++ b/xlsx.flow.js
@@ -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";
-						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': {
@@ -10952,6 +10996,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;
@@ -12602,20 +12647,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*/) {
@@ -12761,6 +12806,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;
@@ -13108,11 +13154,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);
@@ -13127,7 +13184,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));
@@ -13749,9 +13806,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;
diff --git a/xlsx.js b/xlsx.js
index 3d88210..1684831 100644
--- a/xlsx.js
+++ b/xlsx.js
@@ -403,7 +403,18 @@ function hashq(str) {
 	return o;
 }
 function rnd(val, d) { var dd = Math.pow(10,d); return ""+(Math.round(val * dd)/dd); }
-function dec(val, d) { return Math.round((val-Math.floor(val))*Math.pow(10,d)); }
+function dec(val, d) {
+	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, d) {
+	if (d < ('' + Math.round((val-Math.floor(val))*Math.pow(10,d))).length) {
+		return 1;
+	}
+	return 0;
+}
 function flr(val) { if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0)); return ""+Math.floor(val); }
 function write_num_flt(type, fmt, val) {
 	if(type.charCodeAt(0) === 40 && !fmt.match(closeparen)) {
@@ -425,7 +436,6 @@ function write_num_flt(type, fmt, val) {
 	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); });
 		return fmt.indexOf("0.") !== -1 ? o : o.replace(/^0\./,".");
 	}
@@ -435,7 +445,7 @@ function write_num_flt(type, fmt, val) {
 	}
 	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#]+))+$/))) {
@@ -8148,8 +8158,18 @@ function ods_to_csf_formula(f) {
 		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) {
+	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
@@ -8337,7 +8357,7 @@ function write_ws_xml_cols(ws, cols) {
 }
 
 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) {
@@ -8363,7 +8383,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;
@@ -8371,6 +8391,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);
 }
 
@@ -8426,7 +8450,8 @@ return function parse_ws_xml_data(sdata, s, opts, guess) {
 			if((cref=d.match(match_v))!= null && cref[1] !== '') p.v=unescapexml(cref[1]);
 			if(opts.cellFormula) {
 				if((cref=d.match(match_f))!= null && cref[1] !== '') {
-					p.f=unescapexml(utf8read(cref[1]));
+					/* TODO: match against XLSXFutureFunctions */
+					p.f=unescapexml(utf8read(cref[1])).replace(/_xlfn\./,"");
 					if(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]);
@@ -9787,7 +9812,7 @@ function parse_xlml_data(xml, ss, data, cell, base, styles, csty, row, arrayf, o
 		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':
@@ -9928,6 +9953,7 @@ for(var cma = c; cma <= cc; ++cma) {
 				sheetname = unescapexml(tmp.Name);
 				cursheet = {};
 				mergecells = [];
+				arrayf = [];
 			}
 			break;
 		case 'Table':
@@ -10436,10 +10462,14 @@ function write_sty_xlml(wb, opts) {
 }
 /* TODO */
 function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr){
-	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'];
@@ -10457,9 +10487,9 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr){
 		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);
 }
@@ -10642,6 +10672,19 @@ function parse_workbook(blob, options) {
 			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;
 	};
@@ -10794,7 +10837,9 @@ function parse_workbook(blob, options) {
 					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);
@@ -10819,44 +10864,43 @@ function parse_workbook(blob, options) {
 					}
 				} 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});
-							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});
+					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'});
+						temp_val = ({v:val, ixfe:last_formula.cell.ixfe, t:'s'});
 						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': {
@@ -10895,6 +10939,7 @@ function parse_workbook(blob, options) {
 					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;
@@ -12545,20 +12590,20 @@ function write_BIFF2LABEL(r, c, val) {
 }
 
 function write_ws_biff_cell(ba, cell, R, C, 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, ws, idx, opts, wb) {
@@ -12704,6 +12749,7 @@ var parse_content_xml = (function() {
 					ctag = parsexmltag(Rn[0], false);
 					q = ({t:ctag['数据类型'] || ctag['value-type'], v:null});
 					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;
@@ -13051,11 +13097,22 @@ var write_content_xml = (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);
@@ -13070,7 +13127,7 @@ var write_content_xml = (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));
@@ -13684,9 +13741,15 @@ function sheet_to_csv(sheet, opts) {
 		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;