From 0b72cc592b4f500656a9ec5b1e732019aee50cd7 Mon Sep 17 00:00:00 2001
From: SheetJS <dev@sheetjs.com>
Date: Mon, 16 May 2022 21:26:22 -0400
Subject: [PATCH] wsl build sequence fixes

---
 .gitattributes     |   2 +
 Makefile           |   2 +-
 modules/Makefile   |   2 +-
 modules/reframe.sh |   6 +-
 test.js            |   4 +-
 test.mjs           | 152 ++++++++++++++++++++++++++++++++++++++++++---
 test.ts            | 115 +++++++++++++++++++++++++++++++++-
 tests/core.js      |  13 ++--
 xlsx.flow.js       |  80 +++++++++++++++++++++++-
 xlsx.js            |  80 +++++++++++++++++++++++-
 xlsx.mjs           |  80 +++++++++++++++++++++++-
 11 files changed, 510 insertions(+), 26 deletions(-)

diff --git a/.gitattributes b/.gitattributes
index 09ebf64..4b84d20 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -13,3 +13,5 @@ xlsx.js           linguist-generated=true binary
 xlsxworker.js     linguist-generated=true binary
 tests/core.js     linguist-generated=true binary
 tests/fixtures.js linguist-generated=true binary
+
+test.mjs    lingust-generated=false binary=false text eol=lf
diff --git a/Makefile b/Makefile
index c7cf2cf..b20f5c6 100644
--- a/Makefile
+++ b/Makefile
@@ -136,7 +136,7 @@ test mocha: test.js ## Run test suite
 
 .PHONY: test-esm
 test-esm: test.mjs ## Run Node ESM test suite
-	npx mocha -r esm -R spec -t 30000 $<
+	npx -y mocha@9 -R spec -t 30000 $<
 
 .PHONY: test-deno
 test-deno: test.ts ## Run Deno test suite
diff --git a/modules/Makefile b/modules/Makefile
index 796fb60..d492aa2 100644
--- a/modules/Makefile
+++ b/modules/Makefile
@@ -16,7 +16,7 @@ $(BAREJS): %.js: %.ts $(LIBFILES)
 
 %.node.js: %.node.ts $(LIBFILES) src/numbers.ts
 	npx esbuild $< --bundle --external:xlsx --outfile=$@ --platform=node
-	sed -i '' 's/ts-node/node/g' $@
+	sed -i '' 's/ts-node/node/g' $@ || sed -i'' 's/ts-node/node/g' $@ || { echo "sed failed"; exit 1; }
 
 %.js: %.ts $(LIBFILES)
 	npx esbuild $< --bundle --outfile=$@ --platform=browser --format=iife --global-name=$* --target=es5
diff --git a/modules/reframe.sh b/modules/reframe.sh
index 41117fd..51ab74e 100755
--- a/modules/reframe.sh
+++ b/modules/reframe.sh
@@ -4,13 +4,13 @@ INF=${1:-test.numbers}
 OUTF=${2:-reframed.numbers}
 make reframe.node.js
 node reframe.node.js "$INF" "$OUTF"
-# cat-numbers "$OUTF" 
+# cat-numbers "$OUTF"
 chmod a+w "$OUTF"
 sleep 0.1
 # open "$OUTF"
 unzip -l "$OUTF"
-base64 "$OUTF" > xlsx.zahl.js
-sed -i.bak 's/^/var XLSX_ZAHL_PAYLOAD = "/g;s/$/";/g' xlsx.zahl.js
+base64 "$OUTF" | tr -d '\n' > xlsx.zahl.js
+sed -i.bak 's/^/var XLSX_ZAHL_PAYLOAD = "/g;s/$/";\n/g' xlsx.zahl.js
 cp xlsx.zahl.js xlsx.zahl.mjs
 cat >> xlsx.zahl.js <<EOF
 if(typeof module !== "undefined") module.exports = XLSX_ZAHL_PAYLOAD;
diff --git a/test.js b/test.js
index b44a691..b8ce393 100644
--- a/test.js
+++ b/test.js
@@ -1869,7 +1869,7 @@ describe('json output', function() {
 		var ws2 = X.utils.aoa_to_sheet(data), json = X.utils.sheet_to_json(ws2);
 		assert.equal(json[0]["1"], true);
 		assert.equal(json[2]["3"], "qux");
-		ws2["!rows"] = [null,{hidden:true},null,null]; json = X.utils.sheet_to_json(ws2, {skipHidden: 1});
+		ws2["!rows"] = [null,{hidden:true},null,null]; json = X.utils.sheet_to_json(ws2, {skipHidden: true});
 		assert.equal(json[0]["1"], "foo");
 		assert.equal(json[1]["3"], "qux");
 	});
@@ -2526,7 +2526,7 @@ describe('corner cases', function() {
 			//assert.equal(d.y, 2018);
 		}
 		[true, false].forEach(function(cD) {
-			[null, 'yyyy-mm-dd'].forEach(function(dNF) {
+			[void 0, 'yyyy-mm-dd'].forEach(function(dNF) {
 				var ws1 = X.read(
 					'7,2018-03-24',
 					{cellDates: cD, dateNF: dNF, type:'string'}
diff --git a/test.mjs b/test.mjs
index aceb5a1..b632396 100644
--- a/test.mjs
+++ b/test.mjs
@@ -18,8 +18,9 @@ import * as assert from 'assert';
 
 import * as X from './xlsx.mjs';
 X.set_fs(fs);
-import * as cpexcel from './dist/cpexcel.full.mjs';
-X.set_cptable(cpexcel);
+import * as cptable from './dist/cpexcel.full.mjs';
+X.set_cptable(cptable);
+import XLSX_ZAHL from './dist/xlsx.zahl.mjs';
 var DIF_XL = true;
 
 var browser = typeof document !== 'undefined';
@@ -133,6 +134,7 @@ var paths = {
 	dnsxml: dir + 'defined_names_simple.xml',
 	dnsxlsx: dir + 'defined_names_simple.xlsx',
 	dnsxlsb: dir + 'defined_names_simple.xlsb',
+	dnsslk: dir + 'defined_names_simple.slk',
 
 	dnuxls: dir + 'defined_names_unicode.xls',
 	dnuxml: dir + 'defined_names_unicode.xml',
@@ -1173,15 +1175,19 @@ describe('parse features', function() {
 		['xlsx', paths.dnsxlsx,  true],
 		['xlsb', paths.dnsxlsb,  true],
 		['xls',  paths.dnsxls,   true],
-		['xlml', paths.dnsxml,  false]
+		['xlml', paths.dnsxml,  false],
+		['slk',  paths.dnsslk,  false]
 	].forEach(function(m) { it(m[0], function() {
 		var wb = X.read(fs.readFileSync(m[1]), {type:TYPE});
 		var names = wb.Workbook.Names;
+
+		if(m[0] != 'slk') {
 		for(var i = 0; i < names.length; ++i) if(names[i].Name == "SheetJS") break;
 		assert.ok(i < names.length, "Missing name");
 		assert.equal(names[i].Sheet, null);
 		assert.equal(names[i].Ref, "Sheet1!$A$1");
 		if(m[2]) assert.equal(names[i].Comment, "defined names just suck  excel formulae are bad  MS should feel bad");
+		}
 
 		for(i = 0; i < names.length; ++i) if(names[i].Name == "SHEETjs") break;
 		assert.ok(i < names.length, "Missing name");
@@ -1375,7 +1381,7 @@ describe('parse features', function() {
 	});
 
 	describe('data types formats', function() {[
-		['xlsx', paths.dtfxlsx]
+		['xlsx', paths.dtfxlsx],
 	].forEach(function(m) { it(m[0], function() {
 		var wb = X.read(fs.readFileSync(m[1]), {type: TYPE, cellDates: true});
 		var ws = wb.Sheets[wb.SheetNames[0]];
@@ -1530,9 +1536,9 @@ describe('roundtrip features', function() {
 	}); });
 
 	describe('should preserve merge cells', function() {
-		["xlsx", "xlsb", "xlml", "ods", "biff8"].forEach(function(f) { it(f, function() {
+		["xlsx", "xlsb", "xlml", "ods", "biff8", "numbers"].forEach(function(f) { it(f, function() {
 			var wb1 = X.read(fs.readFileSync(paths.mcxlsx), {type:TYPE});
-			var wb2 = X.read(X.write(wb1,{bookType:f,type:'binary'}),{type:'binary'});
+			var wb2 = X.read(X.write(wb1,{bookType:f,type:'binary',numbers:XLSX_ZAHL}),{type:'binary'});
 			var m1 = wb1.Sheets.Merge['!merges'].map(X.utils.encode_range);
 			var m2 = wb2.Sheets.Merge['!merges'].map(X.utils.encode_range);
 			assert.equal(m1.length, m2.length);
@@ -1727,6 +1733,8 @@ var password_files = [
 ];
 describe('invalid files', function() {
 	describe('parse', function() { [
+		['KEY files', 'numbers/Untitled.key'],
+		['PAGES files', 'numbers/Untitled.pages'],
 		['password', 'apachepoi_password.xls'],
 		['passwords', 'apachepoi_xor-encryption-abc.xls'],
 		['DOC files', 'word_doc.doc']
@@ -1781,7 +1789,7 @@ describe('json output', function() {
 	if(typeof before != 'undefined') before(bef);
 	else it('before', bef);
 	it('should use first-row headers and full sheet by default', function() {
-		var json = X.utils.sheet_to_json(ws, {raw: null});
+		var json = X.utils.sheet_to_json(ws, {raw: false});
 		assert.equal(json.length, data.length - 1);
 		assert.equal(json[0][1], "TRUE");
 		assert.equal(json[1][2], "bar");
@@ -1790,7 +1798,7 @@ describe('json output', function() {
 		assert.throws(function() { seeker(json, [1,2,3], "baz"); });
 	});
 	it('should create array of arrays if header == 1', function() {
-		var json = X.utils.sheet_to_json(ws, {header:1, raw:""});
+		var json = X.utils.sheet_to_json(ws, {header:1, raw:false});
 		assert.equal(json.length, data.length);
 		assert.equal(json[1][0], "TRUE");
 		assert.equal(json[2][1], "bar");
@@ -1848,7 +1856,7 @@ describe('json output', function() {
 		var ws2 = X.utils.aoa_to_sheet(data), json = X.utils.sheet_to_json(ws2);
 		assert.equal(json[0]["1"], true);
 		assert.equal(json[2]["3"], "qux");
-		ws2["!rows"] = []; ws2["!rows"][1] = {hidden:true}; json = X.utils.sheet_to_json(ws2, {skipHidden: true});
+		ws2["!rows"] = [null,{hidden:true},null,null]; json = X.utils.sheet_to_json(ws2, {skipHidden: true});
 		assert.equal(json[0]["1"], "foo");
 		assert.equal(json[1]["3"], "qux");
 	});
@@ -2167,6 +2175,19 @@ describe('sylk', function() {
 			}
 		} : null);
 	});
+	describe('date system', function() {
+		function make_slk(d1904) { return "ID;PSheetJS\nP;Pd\\/m\\/yy\nP;Pd\\/m\\/yyyy\n" + (d1904 != null ? "O;D;V" + d1904 : "") + "\nF;P0;FG0G;X1;Y1\nC;K1\nE"; }
+		it('should default to 1900', function() {
+			assert.equal(get_cell(X.read(make_slk(), {type: "binary"}).Sheets.Sheet1, "A1").v, 1);
+			assert.ok(get_cell(X.read(make_slk(), {type: "binary", cellDates: true}).Sheets.Sheet1, "A1").v.getFullYear() < 1902);
+			assert.equal(get_cell(X.read(make_slk(5), {type: "binary"}).Sheets.Sheet1, "A1").v, 1);
+			assert.ok(get_cell(X.read(make_slk(5), {type: "binary", cellDates: true}).Sheets.Sheet1, "A1").v.getFullYear() < 1902);
+		});
+		it('should use 1904 when specified', function() {
+			assert.ok(get_cell(X.read(make_slk(1), {type: "binary", cellDates: true}).Sheets.Sheet1, "A1").v.getFullYear() > 1902);
+			assert.ok(get_cell(X.read(make_slk(4), {type: "binary", cellDates: true}).Sheets.Sheet1, "A1").v.getFullYear() > 1902);
+		});
+	});
 });
 
 (typeof Uint8Array !== "undefined" ? describe : describe.skip)('numbers', function() {
@@ -2435,7 +2456,6 @@ describe('corner cases', function() {
 	});
 	if(typeof JSON !== 'undefined') it('SSF oddities', function() {
 		// $FlowIgnore
-		//var ssfdata = require('./misc/ssf.json');
 		var ssfdata = JSON.parse(fs.readFileSync('./misc/ssf.json', 'utf8'));
 		var cb = function(d, j) { return function() { return X.SSF.format(d[0], d[j][0]); }; };
 		ssfdata.forEach(function(d) {
@@ -2506,6 +2526,118 @@ describe('corner cases', function() {
 			});
 		});
 	});
+	it('should handle \\r and \\n', function() {
+		var base = "./test_files/crlf/";
+		[
+			"CRLFR9.123",
+			"CRLFR9.WK1",
+			"CRLFR9.WK3",
+			"CRLFR9.WK4",
+			"CRLFR9.XLS",
+			"CRLFR9_4.XLS",
+			"CRLFR9_5.XLS",
+			"CRLFX5_2.XLS",
+			"CRLFX5_3.XLS",
+			"CRLFX5_4.XLS",
+			"CRLFX5_5.XLS",
+			"crlf.csv",
+			"crlf.fods",
+			"crlf.htm",
+			"crlf.numbers",
+			"crlf.ods",
+			"crlf.rtf",
+			"crlf.slk",
+			"crlf.xls",
+			"crlf.xlsb",
+			"crlf.xlsx",
+			"crlf.xml",
+			"crlf5.xls",
+			"crlfq9.qpw",
+			"crlfq9.wb1",
+			"crlfq9.wb2",
+			"crlfq9.wb3",
+			"crlfq9.wk1",
+			"crlfq9.wk3",
+			"crlfq9.wk4",
+			"crlfq9.wks",
+			"crlfq9.wq1",
+			"crlfw4_2.wks",
+			"crlfw4_3.wks",
+			"crlfw4_4.wks"
+		].map(function(path) { return base + path; }).forEach(function(w) {
+			var wb = X.read(fs.readFileSync(w), {type:TYPE});
+			var ws = wb.Sheets[wb.SheetNames[0]];
+			var B1 = get_cell(ws, "B1"), B2 = get_cell(ws, "B2");
+			var lio = w.match(/\.[^\.]*$/).index, stem = w.slice(0, lio).toLowerCase(), ext = w.slice(lio + 1).toLowerCase();
+			switch(ext) {
+				case 'fm3': break;
+
+				case '123':
+					assert.equal(B1.v, "abc\ndef");
+					// TODO: parse formula // assert.equal(B1.v, "abc\r\ndef");
+					break;
+				case 'qpw':
+				case 'wb1':
+				case 'wb2':
+				case 'wb3':
+				case 'wk1':
+				case 'wk3':
+				case 'wk4':
+				case 'wq1':
+					assert.ok(B1.v == "abcdef" || B1.v == "abc\ndef");
+					// TODO: formula -> string values
+					if(B2 && B2.t != "e" && B2.v != "") assert.ok(B2.v == "abcdef" || B2.v == "abc\r\ndef");
+					break;
+
+				case 'wks':
+					if(stem.match(/w4/)) {
+						assert.equal(B1.v, "abc\ndef");
+						assert.ok(!B2 || B2.t == "z"); // Works4 did not support CODE / CHAR
+					} else if(stem.match(/q9/)) {
+						assert.equal(B1.v, "abcdef");
+						assert.equal(B2.v, "abc\r\ndef");
+					} else {
+						assert.equal(B1.v, "abc\ndef");
+						assert.equal(B2.v, "abc\r\ndef");
+					}
+					break;
+
+				case 'xls':
+					if(stem.match(/CRLFR9/i)) {
+						assert.equal(B1.v, "abc\r\ndef");
+					} else {
+						assert.equal(B1.v, "abc\ndef");
+					}
+					assert.equal(B2.v, "abc\r\ndef");
+					break;
+
+				case 'rtf':
+				case 'htm':
+					assert.equal(B1.v, "abc\ndef");
+					assert.equal(B2.v, "abc\n\ndef");
+					break;
+
+				case 'xlsx':
+				case 'xlsb':
+				case 'xml':
+				case 'slk':
+				case 'csv':
+					assert.equal(B1.v, "abc\ndef");
+					assert.equal(B2.v, "abc\r\ndef");
+					break;
+				case 'fods':
+				case 'ods':
+					assert.equal(B1.v, "abc\nDef");
+					assert.equal(B2.v, "abc\r\ndef");
+					break;
+				case 'numbers':
+					assert.equal(B1.v, "abc\ndef");
+					// TODO: B2 should be a formula error
+					break;
+				default: throw ext;
+			}
+		});
+	});
 });
 
 describe('encryption', function() {
diff --git a/test.ts b/test.ts
index e46509a..d5f7001 100644
--- a/test.ts
+++ b/test.ts
@@ -1346,7 +1346,7 @@ Deno.test('parse features', async function(t) {
 	});
 
 	await t.step('data types formats', async function(t) {var dtf = [
-		['xlsx', paths.dtfxlsx]
+		['xlsx', paths.dtfxlsx],
 	]; for(var j = 0; j < dtf.length; ++j) { var m = dtf[j]; await t.step(m[0], async function(t) {
 		var wb = X.read(fs.readFileSync(m[1]), {type: TYPE, cellDates: true});
 		var ws = wb.Sheets[wb.SheetNames[0]];
@@ -2303,7 +2303,6 @@ Deno.test('corner cases', async function(t) {
 	});
 	if(typeof JSON !== 'undefined') await t.step('SSF oddities', async function(t) {
 		// $FlowIgnore
-		//var ssfdata = require('./misc/ssf.json');
 		var ssfdata: Array<any> = JSON.parse(fs.readFileSync2('./misc/ssf.json', 'utf-8'));
 		var cb = function(d: any, j: any) { return function() { return X.SSF.format(d[0], d[j][0]); }; };
 		ssfdata.forEach(function(d) {
@@ -2374,6 +2373,118 @@ Deno.test('corner cases', async function(t) {
 			});
 		});
 	});
+	await t.step('should handle \\r and \\n', async function(t) {
+		var base = "./test_files/crlf/";
+		[
+			"CRLFR9.123",
+			"CRLFR9.WK1",
+			"CRLFR9.WK3",
+			"CRLFR9.WK4",
+			"CRLFR9.XLS",
+			"CRLFR9_4.XLS",
+			"CRLFR9_5.XLS",
+			"CRLFX5_2.XLS",
+			"CRLFX5_3.XLS",
+			"CRLFX5_4.XLS",
+			"CRLFX5_5.XLS",
+			"crlf.csv",
+			"crlf.fods",
+			"crlf.htm",
+			"crlf.numbers",
+			"crlf.ods",
+			"crlf.rtf",
+			"crlf.slk",
+			"crlf.xls",
+			"crlf.xlsb",
+			"crlf.xlsx",
+			"crlf.xml",
+			"crlf5.xls",
+			"crlfq9.qpw",
+			"crlfq9.wb1",
+			"crlfq9.wb2",
+			"crlfq9.wb3",
+			"crlfq9.wk1",
+			"crlfq9.wk3",
+			"crlfq9.wk4",
+			"crlfq9.wks",
+			"crlfq9.wq1",
+			"crlfw4_2.wks",
+			"crlfw4_3.wks",
+			"crlfw4_4.wks"
+		].map(function(path) { return base + path; }).forEach(function(w) {
+			var wb = X.read(fs.readFileSync(w), {type:TYPE});
+			var ws = wb.Sheets[wb.SheetNames[0]];
+			var B1 = get_cell(ws, "B1"), B2 = get_cell(ws, "B2");
+			var lio = w.match(/\.[^\.]*$/)?.index || 0, stem = w.slice(0, lio).toLowerCase(), ext = w.slice(lio + 1).toLowerCase();
+			switch(ext) {
+				case 'fm3': break;
+
+				case '123':
+					assert.equal(B1.v, "abc\ndef");
+					// TODO: parse formula // assert.equal(B1.v, "abc\r\ndef");
+					break;
+				case 'qpw':
+				case 'wb1':
+				case 'wb2':
+				case 'wb3':
+				case 'wk1':
+				case 'wk3':
+				case 'wk4':
+				case 'wq1':
+					assert.assert(B1.v == "abcdef" || B1.v == "abc\ndef");
+					// TODO: formula -> string values
+					if(B2 && B2.t != "e" && B2.v != "") assert.assert(B2.v == "abcdef" || B2.v == "abc\r\ndef");
+					break;
+
+				case 'wks':
+					if(stem.match(/w4/)) {
+						assert.equal(B1.v, "abc\ndef");
+						assert.assert(!B2 || B2.t == "z"); // Works4 did not support CODE / CHAR
+					} else if(stem.match(/q9/)) {
+						assert.equal(B1.v, "abcdef");
+						assert.equal(B2.v, "abc\r\ndef");
+					} else {
+						assert.equal(B1.v, "abc\ndef");
+						assert.equal(B2.v, "abc\r\ndef");
+					}
+					break;
+
+				case 'xls':
+					if(stem.match(/CRLFR9/i)) {
+						assert.equal(B1.v, "abc\r\ndef");
+					} else {
+						assert.equal(B1.v, "abc\ndef");
+					}
+					assert.equal(B2.v, "abc\r\ndef");
+					break;
+
+				case 'rtf':
+				case 'htm':
+					assert.equal(B1.v, "abc\ndef");
+					assert.equal(B2.v, "abc\n\ndef");
+					break;
+
+				case 'xlsx':
+				case 'xlsb':
+				case 'xml':
+				case 'slk':
+				case 'csv':
+					assert.equal(B1.v, "abc\ndef");
+					assert.equal(B2.v, "abc\r\ndef");
+					break;
+				case 'fods':
+				case 'ods':
+					assert.equal(B1.v, "abc\nDef");
+					assert.equal(B2.v, "abc\r\ndef");
+					break;
+				case 'numbers':
+					assert.equal(B1.v, "abc\ndef");
+					// TODO: B2 should be a formula error
+					break;
+				default: throw ext;
+			}
+		});
+	});
 });
 
 Deno.test('encryption', async function(t) {
diff --git a/tests/core.js b/tests/core.js
index 56f95a7..3a02d56 100644
--- a/tests/core.js
+++ b/tests/core.js
@@ -132,6 +132,7 @@ var paths = {
 	dnsxml: dir + 'defined_names_simple.xml',
 	dnsxlsx: dir + 'defined_names_simple.xlsx',
 	dnsxlsb: dir + 'defined_names_simple.xlsb',
+	dnsslk: dir + 'defined_names_simple.slk',
 
 	dnuxls: dir + 'defined_names_unicode.xls',
 	dnuxml: dir + 'defined_names_unicode.xml',
@@ -1183,15 +1184,19 @@ describe('parse features', function() {
 		['xlsx', paths.dnsxlsx,  true],
 		['xlsb', paths.dnsxlsb,  true],
 		['xls',  paths.dnsxls,   true],
-		['xlml', paths.dnsxml,  false]
+		['xlml', paths.dnsxml,  false],
+		['slk',  paths.dnsslk,  false]
 	].forEach(function(m) { it(m[0], function() {
 		var wb = X.read(fs.readFileSync(m[1]), {type:TYPE});
 		var names = wb.Workbook.Names;
+
+		if(m[0] != 'slk') {
 		for(var i = 0; i < names.length; ++i) if(names[i].Name == "SheetJS") break;
 		assert(i < names.length, "Missing name");
 		assert.equal(names[i].Sheet, null);
 		assert.equal(names[i].Ref, "Sheet1!$A$1");
 		if(m[2]) assert.equal(names[i].Comment, "defined names just suck  excel formulae are bad  MS should feel bad");
+		}
 
 		for(i = 0; i < names.length; ++i) if(names[i].Name == "SHEETjs") break;
 		assert(i < names.length, "Missing name");
@@ -1512,7 +1517,7 @@ function parseDate(str/*:string|Date*/)/*:Date*/ {
 var fixdate = browser ? parseDate("2014-02-19T14:30:00.000Z") : new Date("2014-02-19T14:30Z");
 
 describe('roundtrip features', function() {
-	var bef = (function() { X = require(modp); });
+	var bef = (function() { X = require(modp); XLSX_ZAHL=require("./dist/xlsx.zahl"); });
 	if(typeof before != 'undefined') before(bef);
 	else it('before', bef);
 	describe('should preserve core properties', function() { [
@@ -1864,7 +1869,7 @@ describe('json output', function() {
 		var ws2 = X.utils.aoa_to_sheet(data), json = X.utils.sheet_to_json(ws2);
 		assert.equal(json[0]["1"], true);
 		assert.equal(json[2]["3"], "qux");
-		ws2["!rows"] = [null,{hidden:true},null,null]; json = X.utils.sheet_to_json(ws2, {skipHidden: 1});
+		ws2["!rows"] = [null,{hidden:true},null,null]; json = X.utils.sheet_to_json(ws2, {skipHidden: true});
 		assert.equal(json[0]["1"], "foo");
 		assert.equal(json[1]["3"], "qux");
 	});
@@ -2521,7 +2526,7 @@ describe('corner cases', function() {
 			//assert.equal(d.y, 2018);
 		}
 		[true, false].forEach(function(cD) {
-			[null, 'yyyy-mm-dd'].forEach(function(dNF) {
+			[void 0, 'yyyy-mm-dd'].forEach(function(dNF) {
 				var ws1 = X.read(
 					'7,2018-03-24',
 					{cellDates: cD, dateNF: dNF, type:'string'}
diff --git a/xlsx.flow.js b/xlsx.flow.js
index add347c..9f29042 100644
--- a/xlsx.flow.js
+++ b/xlsx.flow.js
@@ -5339,10 +5339,10 @@ var RELS = ({
 	XLMETA: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
 	TCMNT: "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment",
 	PEOPLE: "http://schemas.microsoft.com/office/2017/10/relationships/person",
+	CONN: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections",
 	VBA: "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
 }/*:any*/);
 
-
 /* 9.3.3 Representing Relationships */
 function get_rels_path(file/*:string*/)/*:string*/ {
 	var n = file.lastIndexOf("/");
@@ -8997,16 +8997,88 @@ var WK_ = /*#__PURE__*/(function() {
 	]; */
 	/* TODO: flesh out */
 	var FuncTab = {
+		0x1F: ["NA", 0],
+		// 0x20: ["ERR", 0],
+		0x21: ["ABS", 1],
+		0x22: ["TRUNC", 1],
+		0x23: ["SQRT", 1],
+		0x24: ["LOG", 1],
+		0x25: ["LN", 1],
+		0x26: ["PI", 0],
+		0x27: ["SIN", 1],
+		0x28: ["COS", 1],
+		0x29: ["TAN", 1],
+		0x2A: ["ATAN2", 2],
+		0x2B: ["ATAN", 1],
+		0x2C: ["ASIN", 1],
+		0x2D: ["ACOS", 1],
+		0x2E: ["EXP", 1],
+		0x2F: ["MOD", 2],
+		// 0x30
+		0x31: ["ISNA", 1],
+		0x32: ["ISERR", 1],
 		0x33: ["FALSE", 0],
 		0x34: ["TRUE", 0],
+		0x35: ["RAND", 0],
+		// 0x36 DATE
+		// 0x37 NOW
+		// 0x38 PMT
+		// 0x39 PV
+		// 0x3A FV
+		// 0x3B IF
+		// 0x3C DAY
+		// 0x3D MONTH
+		// 0x3E YEAR
+		0x3F: ["ROUND", 2],
+		// 0x40 TIME
+		// 0x41 HOUR
+		// 0x42 MINUTE
+		// 0x43 SECOND
+		0x44: ["ISNUMBER", 1],
+		0x45: ["ISTEXT", 1],
 		0x46: ["LEN", 1],
+		0x47: ["VALUE", 1],
+		// 0x48: ["FIXED", ?? 1],
+		0x49: ["MID", 3],
 		0x4A: ["CHAR", 1],
+		// 0x4B
+		// 0x4C FIND
+		// 0x4D DATEVALUE
+		// 0x4E TIMEVALUE
+		// 0x4F CELL
 		0x50: ["SUM", 69],
 		0x51: ["AVERAGEA", 69],
 		0x52: ["COUNTA", 69],
 		0x53: ["MINA", 69],
 		0x54: ["MAXA", 69],
+		// 0x55 VLOOKUP
+		// 0x56 NPV
+		// 0x57 VAR
+		// 0x58 STD
+		// 0x59 IRR
+		// 0x5A HLOOKUP
+		// 0x5B DSUM
+		// 0x5C DAVERAGE
+		// 0x5D DCOUNTA
+		// 0x5E DMIN
+		// 0x5F DMAX
+		// 0x60 DVARP
+		// 0x61 DSTDEVP
+		// 0x62 INDEX
+		// 0x63 COLS
+		// 0x64 ROWS
+		// 0x65 REPEAT
+		0x66: ["UPPER", 1],
+		0x67: ["LOWER", 1],
+		// 0x68 LEFT
+		// 0x69 RIGHT
+		// 0x6A REPLACE
+		0x6B: ["PROPER", 1],
+		// 0x6C CELL
+		0x6D: ["TRIM", 1],
+		// 0x6E CLEAN
 		0x6F: ["T", 1]
+		// 0x70 V
 	};
 	var BinOpTab = [
 		  "",   "",   "",   "",   "",   "",   "",   "", // eslint-disable-line no-mixed-spaces-and-tabs
@@ -24680,6 +24752,12 @@ function sheet_set_array_formula(ws/*:Worksheet*/, range, formula/*:string*/, dy
 			if(dynamic) cell.D = true;
 		}
 	}
+	var wsr = decode_range(ws["!ref"]);
+	if(wsr.s.r > rng.s.r) wsr.s.r = rng.s.r;
+	if(wsr.s.c > rng.s.c) wsr.s.c = rng.s.c;
+	if(wsr.e.r < rng.e.r) wsr.e.r = rng.e.r;
+	if(wsr.e.c < rng.e.c) wsr.e.c = rng.e.c;
+	ws["!ref"] = encode_range(ws["!ref"]);
 	return ws;
 }
 
diff --git a/xlsx.js b/xlsx.js
index 5cc5758..25a2887 100644
--- a/xlsx.js
+++ b/xlsx.js
@@ -5254,10 +5254,10 @@ var RELS = ({
 	XLMETA: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
 	TCMNT: "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment",
 	PEOPLE: "http://schemas.microsoft.com/office/2017/10/relationships/person",
+	CONN: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections",
 	VBA: "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
 });
 
-
 /* 9.3.3 Representing Relationships */
 function get_rels_path(file) {
 	var n = file.lastIndexOf("/");
@@ -8907,16 +8907,88 @@ var WK_ = (function() {
 	]; */
 	/* TODO: flesh out */
 	var FuncTab = {
+		0x1F: ["NA", 0],
+		// 0x20: ["ERR", 0],
+		0x21: ["ABS", 1],
+		0x22: ["TRUNC", 1],
+		0x23: ["SQRT", 1],
+		0x24: ["LOG", 1],
+		0x25: ["LN", 1],
+		0x26: ["PI", 0],
+		0x27: ["SIN", 1],
+		0x28: ["COS", 1],
+		0x29: ["TAN", 1],
+		0x2A: ["ATAN2", 2],
+		0x2B: ["ATAN", 1],
+		0x2C: ["ASIN", 1],
+		0x2D: ["ACOS", 1],
+		0x2E: ["EXP", 1],
+		0x2F: ["MOD", 2],
+		// 0x30
+		0x31: ["ISNA", 1],
+		0x32: ["ISERR", 1],
 		0x33: ["FALSE", 0],
 		0x34: ["TRUE", 0],
+		0x35: ["RAND", 0],
+		// 0x36 DATE
+		// 0x37 NOW
+		// 0x38 PMT
+		// 0x39 PV
+		// 0x3A FV
+		// 0x3B IF
+		// 0x3C DAY
+		// 0x3D MONTH
+		// 0x3E YEAR
+		0x3F: ["ROUND", 2],
+		// 0x40 TIME
+		// 0x41 HOUR
+		// 0x42 MINUTE
+		// 0x43 SECOND
+		0x44: ["ISNUMBER", 1],
+		0x45: ["ISTEXT", 1],
 		0x46: ["LEN", 1],
+		0x47: ["VALUE", 1],
+		// 0x48: ["FIXED", ?? 1],
+		0x49: ["MID", 3],
 		0x4A: ["CHAR", 1],
+		// 0x4B
+		// 0x4C FIND
+		// 0x4D DATEVALUE
+		// 0x4E TIMEVALUE
+		// 0x4F CELL
 		0x50: ["SUM", 69],
 		0x51: ["AVERAGEA", 69],
 		0x52: ["COUNTA", 69],
 		0x53: ["MINA", 69],
 		0x54: ["MAXA", 69],
+		// 0x55 VLOOKUP
+		// 0x56 NPV
+		// 0x57 VAR
+		// 0x58 STD
+		// 0x59 IRR
+		// 0x5A HLOOKUP
+		// 0x5B DSUM
+		// 0x5C DAVERAGE
+		// 0x5D DCOUNTA
+		// 0x5E DMIN
+		// 0x5F DMAX
+		// 0x60 DVARP
+		// 0x61 DSTDEVP
+		// 0x62 INDEX
+		// 0x63 COLS
+		// 0x64 ROWS
+		// 0x65 REPEAT
+		0x66: ["UPPER", 1],
+		0x67: ["LOWER", 1],
+		// 0x68 LEFT
+		// 0x69 RIGHT
+		// 0x6A REPLACE
+		0x6B: ["PROPER", 1],
+		// 0x6C CELL
+		0x6D: ["TRIM", 1],
+		// 0x6E CLEAN
 		0x6F: ["T", 1]
+		// 0x70 V
 	};
 	var BinOpTab = [
 		  "",   "",   "",   "",   "",   "",   "",   "", // eslint-disable-line no-mixed-spaces-and-tabs
@@ -24559,6 +24631,12 @@ function sheet_set_array_formula(ws, range, formula, dynamic) {
 			if(dynamic) cell.D = true;
 		}
 	}
+	var wsr = decode_range(ws["!ref"]);
+	if(wsr.s.r > rng.s.r) wsr.s.r = rng.s.r;
+	if(wsr.s.c > rng.s.c) wsr.s.c = rng.s.c;
+	if(wsr.e.r < rng.e.r) wsr.e.r = rng.e.r;
+	if(wsr.e.c < rng.e.c) wsr.e.c = rng.e.c;
+	ws["!ref"] = encode_range(ws["!ref"]);
 	return ws;
 }
 
diff --git a/xlsx.mjs b/xlsx.mjs
index 7d8798f..fd00e4a 100644
--- a/xlsx.mjs
+++ b/xlsx.mjs
@@ -5327,10 +5327,10 @@ var RELS = ({
 	XLMETA: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
 	TCMNT: "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment",
 	PEOPLE: "http://schemas.microsoft.com/office/2017/10/relationships/person",
+	CONN: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections",
 	VBA: "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
 }/*:any*/);
 
-
 /* 9.3.3 Representing Relationships */
 function get_rels_path(file/*:string*/)/*:string*/ {
 	var n = file.lastIndexOf("/");
@@ -8985,16 +8985,88 @@ var WK_ = /*#__PURE__*/(function() {
 	]; */
 	/* TODO: flesh out */
 	var FuncTab = {
+		0x1F: ["NA", 0],
+		// 0x20: ["ERR", 0],
+		0x21: ["ABS", 1],
+		0x22: ["TRUNC", 1],
+		0x23: ["SQRT", 1],
+		0x24: ["LOG", 1],
+		0x25: ["LN", 1],
+		0x26: ["PI", 0],
+		0x27: ["SIN", 1],
+		0x28: ["COS", 1],
+		0x29: ["TAN", 1],
+		0x2A: ["ATAN2", 2],
+		0x2B: ["ATAN", 1],
+		0x2C: ["ASIN", 1],
+		0x2D: ["ACOS", 1],
+		0x2E: ["EXP", 1],
+		0x2F: ["MOD", 2],
+		// 0x30
+		0x31: ["ISNA", 1],
+		0x32: ["ISERR", 1],
 		0x33: ["FALSE", 0],
 		0x34: ["TRUE", 0],
+		0x35: ["RAND", 0],
+		// 0x36 DATE
+		// 0x37 NOW
+		// 0x38 PMT
+		// 0x39 PV
+		// 0x3A FV
+		// 0x3B IF
+		// 0x3C DAY
+		// 0x3D MONTH
+		// 0x3E YEAR
+		0x3F: ["ROUND", 2],
+		// 0x40 TIME
+		// 0x41 HOUR
+		// 0x42 MINUTE
+		// 0x43 SECOND
+		0x44: ["ISNUMBER", 1],
+		0x45: ["ISTEXT", 1],
 		0x46: ["LEN", 1],
+		0x47: ["VALUE", 1],
+		// 0x48: ["FIXED", ?? 1],
+		0x49: ["MID", 3],
 		0x4A: ["CHAR", 1],
+		// 0x4B
+		// 0x4C FIND
+		// 0x4D DATEVALUE
+		// 0x4E TIMEVALUE
+		// 0x4F CELL
 		0x50: ["SUM", 69],
 		0x51: ["AVERAGEA", 69],
 		0x52: ["COUNTA", 69],
 		0x53: ["MINA", 69],
 		0x54: ["MAXA", 69],
+		// 0x55 VLOOKUP
+		// 0x56 NPV
+		// 0x57 VAR
+		// 0x58 STD
+		// 0x59 IRR
+		// 0x5A HLOOKUP
+		// 0x5B DSUM
+		// 0x5C DAVERAGE
+		// 0x5D DCOUNTA
+		// 0x5E DMIN
+		// 0x5F DMAX
+		// 0x60 DVARP
+		// 0x61 DSTDEVP
+		// 0x62 INDEX
+		// 0x63 COLS
+		// 0x64 ROWS
+		// 0x65 REPEAT
+		0x66: ["UPPER", 1],
+		0x67: ["LOWER", 1],
+		// 0x68 LEFT
+		// 0x69 RIGHT
+		// 0x6A REPLACE
+		0x6B: ["PROPER", 1],
+		// 0x6C CELL
+		0x6D: ["TRIM", 1],
+		// 0x6E CLEAN
 		0x6F: ["T", 1]
+		// 0x70 V
 	};
 	var BinOpTab = [
 		  "",   "",   "",   "",   "",   "",   "",   "", // eslint-disable-line no-mixed-spaces-and-tabs
@@ -24668,6 +24740,12 @@ function sheet_set_array_formula(ws/*:Worksheet*/, range, formula/*:string*/, dy
 			if(dynamic) cell.D = true;
 		}
 	}
+	var wsr = decode_range(ws["!ref"]);
+	if(wsr.s.r > rng.s.r) wsr.s.r = rng.s.r;
+	if(wsr.s.c > rng.s.c) wsr.s.c = rng.s.c;
+	if(wsr.e.r < rng.e.r) wsr.e.r = rng.e.r;
+	if(wsr.e.c < rng.e.c) wsr.e.c = rng.e.c;
+	ws["!ref"] = encode_range(ws["!ref"]);
 	return ws;
 }