From 5c02c819f60989cbc6d1ecf10783b548f26d4a3f Mon Sep 17 00:00:00 2001
From: ThibautSF <tsimonfine@gmail.com>
Date: Wed, 13 Oct 2021 06:43:07 +0200
Subject: [PATCH] x-spreadsheet demo merges and formulae [ci skip]

Co-authored-by: SheetJS <dev@sheetjs.com>
---
 demos/xspreadsheet/README.md     |  43 +-------
 demos/xspreadsheet/xlsxspread.js | 168 +++++++++++++++++++++++++------
 2 files changed, 141 insertions(+), 70 deletions(-)

diff --git a/demos/xspreadsheet/README.md b/demos/xspreadsheet/README.md
index 529eb09..f529d14 100644
--- a/demos/xspreadsheet/README.md
+++ b/demos/xspreadsheet/README.md
@@ -35,56 +35,21 @@ var grid = x_spreadsheet(document.getElementById("gridctr"));
 The following function converts data from SheetJS to x-spreadsheet:
 
 ```js
-function stox(wb) {
-  var out = [];
-  wb.SheetNames.forEach(function(name) {
-    var o = {name:name, rows:{}};
-    var ws = wb.Sheets[name];
-    var aoa = XLSX.utils.sheet_to_json(ws, {raw: false, header:1});
-    aoa.forEach(function(r, i) {
-      var cells = {};
-      r.forEach(function(c, j) { cells[j] = ({ text: c }); });
-      o.rows[i] = { cells: cells };
-    })
-    out.push(o);
-  });
-  return out;
-}
-
 /* load data */
 grid.loadData(stox(workbook_object));
 ```
 
+`stox` is defined in [xlsxspread.js](./xlsxspread.js)
+
 ## Editing
 
-`x-spreadsheet` handles the entire edit cycle.  No intervention is necessary.
+`x-spreadsheet` handles the entire edit cycle. No intervention is necessary.
 
 ## Saving Data
 
 `grid.getData()` returns an object that can be converted back to a worksheet:
 
 ```js
-function xtos(sdata) {
-  var out = XLSX.utils.book_new();
-  sdata.forEach(function(xws) {
-    var aoa = [[]];
-    var rowobj = xws.rows;
-    for(var ri = 0; ri < rowobj.len; ++ri) {
-      var row = rowobj[ri];
-      if(!row) continue;
-      aoa[ri] = [];
-      Object.keys(row.cells).forEach(function(k) {
-        var idx = +k;
-        if(isNaN(idx)) return;
-        aoa[ri][idx] = row.cells[k].text;
-      });
-    }
-    var ws = XLSX.utils.aoa_to_sheet(aoa);
-    XLSX.utils.book_append_sheet(out, ws, xws.name);
-  });
-  return out;
-}
-
 /* build workbook from the grid data */
 var new_wb = xtos(xspr.getData());
 
@@ -92,6 +57,8 @@ var new_wb = xtos(xspr.getData());
 XLSX.writeFile(new_wb, "SheetJS.xlsx");
 ```
 
+`stox` is defined in [xlsxspread.js](./xlsxspread.js)
+
 ## Additional Features
 
 This demo barely scratches the surface.  The underlying grid component includes
diff --git a/demos/xspreadsheet/xlsxspread.js b/demos/xspreadsheet/xlsxspread.js
index d1119bf..2aec5b6 100644
--- a/demos/xspreadsheet/xlsxspread.js
+++ b/demos/xspreadsheet/xlsxspread.js
@@ -1,37 +1,141 @@
+/*! xlsxspread.js (C) SheetJS LLC -- https://sheetjs.com/ */
+/* eslint-env browser */
+/*global XLSX */
+/*exported stox, xtos */
+
+/**
+ * Converts data from SheetJS to x-spreadsheet
+ *
+ * @param  {Object} wb SheetJS workbook object
+ *
+ * @returns {Object[]} An x-spreadsheet data
+ */
 function stox(wb) {
-	var out = [];
-	wb.SheetNames.forEach(function(name) {
-		var o = {name:name, rows:{}};
-		var ws = wb.Sheets[name];
-		var aoa = XLSX.utils.sheet_to_json(ws, {raw: false, header:1});
-		aoa.forEach(function(r, i) {
-			var cells = {};
-			r.forEach(function(c, j) { cells[j] = ({ text: c }); });
-			o.rows[i] = { cells: cells };
-		})
-		out.push(o);
-	});
-	return out;
+  var out = [];
+  wb.SheetNames.forEach(function (name) {
+    var o = { name: name, rows: {} };
+    var ws = wb.Sheets[name];
+    var range = XLSX.utils.decode_range(ws['!ref']);
+    // sheet_to_json will lost empty row and col at begin as default
+    range.s = { r: 0, c: 0 };
+    var aoa = XLSX.utils.sheet_to_json(ws, {
+      raw: false,
+      header: 1,
+      range: range
+    });
+
+    aoa.forEach(function (r, i) {
+      var cells = {};
+      r.forEach(function (c, j) {
+        cells[j] = { text: c };
+
+        var cellRef = XLSX.utils.encode_cell({ r: i, c: j });
+
+        if ( ws[cellRef] != null && ws[cellRef].f != null) {
+          cells[j].text = "=" + ws[cellRef].f;
+        }
+      });
+      o.rows[i] = { cells: cells };
+    });
+
+    o.merges = [];
+    (ws["!merges"]||[]).forEach(function (merge, i) {
+      //Needed to support merged cells with empty content
+      if (o.rows[merge.s.r] == null) {
+        o.rows[merge.s.r] = { cells: {} };
+      }
+      if (o.rows[merge.s.r].cells[merge.s.c] == null) {
+        o.rows[merge.s.r].cells[merge.s.c] = {};
+      }
+
+      o.rows[merge.s.r].cells[merge.s.c].merge = [
+        merge.e.r - merge.s.r,
+        merge.e.c - merge.s.c
+      ];
+
+      o.merges[i] = XLSX.utils.encode_range(merge);
+    });
+
+    out.push(o);
+  });
+
+  return out;
 }
 
+/**
+ * Converts data from x-spreadsheet to SheetJS
+ *
+ * @param  {Object[]} sdata An x-spreadsheet data object
+ *
+ * @returns {Object} A SheetJS workbook object
+ */
 function xtos(sdata) {
-	var out = XLSX.utils.book_new();
-	sdata.forEach(function(xws) {
-		var aoa = [[]];
-		var rowobj = xws.rows;
-		for(var ri = 0; ri < rowobj.len; ++ri) {
-			var row = rowobj[ri];
-			if(!row) continue;
-			aoa[ri] = [];
-			Object.keys(row.cells).forEach(function(k) {
-				var idx = +k;
-				if(isNaN(idx)) return;
-				aoa[ri][idx] = row.cells[k].text;
-			});
-		}
-		var ws = XLSX.utils.aoa_to_sheet(aoa);
-		XLSX.utils.book_append_sheet(out, ws, xws.name);
-	});
-	return out;
-}
+  var out = XLSX.utils.book_new();
+  sdata.forEach(function (xws) {
+    var ws = {};
+    var rowobj = xws.rows;
+    for (var ri = 0; ri < rowobj.len; ++ri) {
+      var row = rowobj[ri];
+      if (!row) continue;
 
+      var minCoord, maxCoord;
+      Object.keys(row.cells).forEach(function (k) {
+        var idx = +k;
+        if (isNaN(idx)) return;
+
+        var lastRef = XLSX.utils.encode_cell({ r: ri, c: idx });
+        if (minCoord == null) {
+          minCoord = { r: ri, c: idx };
+        } else {
+          if (ri < minCoord.r) minCoord.r = ri;
+          if (idx < minCoord.c) minCoord.c = idx;
+        }
+        if (maxCoord == undefined) {
+          maxCoord = { r: ri, c: idx };
+        } else {
+          if (ri > maxCoord.r) maxCoord.r = ri;
+          if (idx > maxCoord.c) maxCoord.c = idx;
+        }
+
+        var cellText = row.cells[k].text, type = "s";
+        if (!cellText) {
+          cellText = "";
+          type = "z";
+        } else if (!isNaN(parseFloat(cellText))) {
+          cellText = parseFloat(cellText);
+          type = "n";
+        } else if (cellText.toLowerCase() === "true" || cellText.toLowerCase() === "false") {
+          cellText = Boolean(cellText);
+          type = "b";
+        }
+
+        ws[lastRef] = { v: cellText, t: type };
+
+        if (type == "s" && cellText[0] == "=") {
+          ws[lastRef].f = cellText.slice(1);
+        }
+
+        if (row.cells[k].merge != null) {
+          if (ws["!merges"] == null) ws["!merges"] = [];
+
+          ws["!merges"].push({
+            s: { r: ri, c: idx },
+            e: {
+              r: ri + row.cells[k].merge[0],
+              c: idx + row.cells[k].merge[1]
+            }
+          });
+        }
+      });
+
+      ws["!ref"] = XLSX.utils.encode_range({
+        s: { r: minCoord.r, c: minCoord.c },
+        e: { r: maxCoord.r, c: maxCoord.c }
+      });
+    }
+
+    XLSX.utils.book_append_sheet(out, ws, xws.name);
+  });
+
+  return out;
+}