spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[ERRORTYPE, ERROR.TYPE] formula added and tested (mostly, still needs to be hooked into Parser/Sheet)
author
Ben Vogt <[email protected]>
date
2017-07-09 17:09:10
stats
10 file(s) changed, 268 insertions(+), 21 deletions(-)
files
DOCS.md
TODO.md
dist/Cell.js
dist/Formulas/AllFormulas.js
dist/Formulas/Info.js
dist/Sheet.js
dist/Utilities/TypeConverter.js
src/Formulas/AllFormulas.ts
src/Formulas/Info.ts
tests/Formulas/InfoTest.ts
  1diff --git a/DOCS.md b/DOCS.md
  2index 9635e49..8b21433 100644
  3--- a/DOCS.md
  4+++ b/DOCS.md
  5@@ -612,6 +612,23 @@
  6 @returns {number} 
  7 @constructor
  8 ```
  9+
 10+### ISREF 
 11+
 12+```
 13+  Tests if the content of one or several cells is a reference. Verifies the type of references in a cell or a range of cells. If an error occurs, the function returns a logical or numerical value. 
 14+@param value - The value to be tested, to determine whether it is a reference. 
 15+@returns {boolean} 
 16+@constructor
 17+```
 18+
 19+### ERRORTYPE 
 20+
 21+```
 22+  Returns the number corresponding to an error value occurring in a different cell. With the aid of this number, an error message text can be generated. If an error occurs, the function returns a logical or numerical value. 
 23+@param value - Contains either the addrereference of the cell in which the error occurs, or the error directly. Eg: `=ERRORTYPE(NA())` 
 24+@constructor TODO: This formula, while written correctly in javascript, needs to be called inside of a try-catch-block inside the ParserSheet. Otherwise the errors thrown by nested formulas break through. Eg: `=ERRORTYPE(NA())`, NA bubbles up. Once this is done, we should test it inside SheetFormulaTest.ts
 25+```
 26 ## Logical
 27 
 28 
 29diff --git a/TODO.md b/TODO.md
 30index c5bde3d..6a31622 100644
 31--- a/TODO.md
 32+++ b/TODO.md
 33@@ -31,10 +31,13 @@ We could give each function a type-array that matches the arguments, and could b
 34 ### Fix documentation regular expression, it is butchering URLs.
 35 
 36 
 37+### ERROR.TYPE, ISERR, ISERROR, ISNA need to be called inside a try-catch-block inside the Parser or Sheet.
 38+See documentation for ERROR.TYPE for more information.
 39+
 40+
 41 ### Meta-Formulas to write
 42 Many of these formulas can be written by allowing the Sheet and Parser to return Cell objects in addition to primitive types. There are some memory issues with doing this. If a user calls something like `ISNA(A1:A99999)` we really only need the first cell. So we should return cell objects in some cases, but it would be easier in most cases to have context aware formulas, so if they need a cell, or a reference, we simply skip looking up a reference, and instead return a reference, or just a single cell. One way to do this would be to have formula functions, and then on the side have formula args. So before we lookup a large range of cells, we can check to see if it needs all of them, or if it just cares about the first one. So for `ISNA` we could look at `FormulaArgs.ISNA[0]` to get `Value` so we know that it needs only a single argument that is not an array, so if we call it with `ISNA(A1:A99999)`, it would really only lookup `A1`. This might be premature optimization however.
 43 
 44-* ERROR.TYPE - Fetch actual cell to check for error, which requires changes in Parser and Sheet. If the returned cell doesn't have an error, then it should throw NA.
 45 * ISBLANK - Requires changes to Parser/Sheet. If we fetch a cell, and it is "blank", return true. Could be implemented by adding a field to cells indicating if they're blank. Right now empty cells are causing errors because they don't exist. We should allow users to fetch empty cells, they should just be considered blank, or undefined, or null.
 46 * ISERR - Requires changes to Parser/Sheet to either wrap enclosed functions in try-catch, or fetch actual cell value, and check it's error field.
 47 * ISERROR - See ISERR.
 48diff --git a/dist/Cell.js b/dist/Cell.js
 49index 58de5db..1ca488a 100644
 50--- a/dist/Cell.js
 51+++ b/dist/Cell.js
 52@@ -125,6 +125,20 @@ var Cell = (function () {
 53     Cell.prototype.getError = function () {
 54         return this.error;
 55     };
 56+    /**
 57+     * Easier way to check if this cell has an error.
 58+     * @returns {boolean}
 59+     */
 60+    Cell.prototype.hasError = function () {
 61+        return this.error !== null;
 62+    };
 63+    /**
 64+     * A cell is deemed blank if it contains no value, no error, and no typed value.
 65+     * @returns {boolean}
 66+     */
 67+    Cell.prototype.isBlank = function () {
 68+        return this.error === null && this.rawFormulaText === null && this.typedValue === null;
 69+    };
 70     /**
 71      * Returns the human-readable string representation of this cell, omitting some obvious fields.
 72      * @returns {string}
 73@@ -132,6 +146,18 @@ var Cell = (function () {
 74     Cell.prototype.toString = function () {
 75         return "id=" + this.id + ", value=" + this.typedValue + ", rawFormulaText=" + this.rawFormulaText + ", error=" + this.error;
 76     };
 77+    /**
 78+     * Build a cell with an id and value.
 79+     * @param id - A1-notation id or key.
 80+     * @param value - value of the cell.
 81+     * @returns {Cell}
 82+     * @constructor
 83+     */
 84+    Cell.BuildFrom = function (id, value) {
 85+        var cell = new Cell(id);
 86+        cell.setValue(value);
 87+        return cell;
 88+    };
 89     return Cell;
 90 }());
 91 exports.Cell = Cell;
 92diff --git a/dist/Formulas/AllFormulas.js b/dist/Formulas/AllFormulas.js
 93index fd2811d..f2f3312 100644
 94--- a/dist/Formulas/AllFormulas.js
 95+++ b/dist/Formulas/AllFormulas.js
 96@@ -88,6 +88,8 @@ exports.ISNONTEXT = Info_1.ISNONTEXT;
 97 exports.ISEMAIL = Info_1.ISEMAIL;
 98 exports.ISURL = Info_1.ISURL;
 99 exports.N = Info_1.N;
100+exports.ISREF = Info_1.ISREF;
101+exports.ERRORTYPE = Info_1.ERRORTYPE;
102 var Lookup_1 = require("./Lookup");
103 exports.CHOOSE = Lookup_1.CHOOSE;
104 var Logical_1 = require("./Logical");
105@@ -213,6 +215,7 @@ var __COMPLEX = {
106     "WORKDAY.INTL": Date_1.WORKDAY$INTL,
107     "POISSON.DIST": Statistical_1.POISSON,
108     "PERCENTRANK.INC": Statistical_1.PERCENTRANK,
109-    "PERCENTRANK.EXC": Statistical_1.PERCENTRANK$EXC
110+    "PERCENTRANK.EXC": Statistical_1.PERCENTRANK$EXC,
111+    "ERROR.TYPE": Info_1.ERRORTYPE
112 };
113 exports.__COMPLEX = __COMPLEX;
114diff --git a/dist/Formulas/Info.js b/dist/Formulas/Info.js
115index 588740f..3255d8a 100644
116--- a/dist/Formulas/Info.js
117+++ b/dist/Formulas/Info.js
118@@ -3,6 +3,7 @@ exports.__esModule = true;
119 var ArgsChecker_1 = require("../Utilities/ArgsChecker");
120 var Errors_1 = require("../Errors");
121 var TypeConverter_1 = require("../Utilities/TypeConverter");
122+var Cell_1 = require("../Cell");
123 /**
124  * Returns the "value not available" error, "#N/A".
125  * @constructor
126@@ -120,3 +121,60 @@ var N = function (value) {
127     return TypeConverter_1.TypeConverter.firstValueAsNumber(value);
128 };
129 exports.N = N;
130+/**
131+ * Tests if the content of one or several cells is a reference. Verifies the type of references in a cell or a range of
132+ * cells. If an error occurs, the function returns a logical or numerical value.
133+ * @param value - The value to be tested, to determine whether it is a reference.
134+ * @returns {boolean}
135+ * @constructor
136+ */
137+var ISREF = function (value) {
138+    ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "ISREF");
139+    return TypeConverter_1.TypeConverter.firstValue(value) instanceof Cell_1.Cell;
140+};
141+exports.ISREF = ISREF;
142+/**
143+ * Returns the number corresponding to an error value occurring in a different cell. With the aid of this number, an
144+ * error message text can be generated. If an error occurs, the function returns a logical or numerical value.
145+ * @param value - Contains either the address/reference of the cell in which the error occurs, or the error directly.
146+ * Eg: `=ERRORTYPE(NA())`
147+ * @constructor
148+ * TODO: This formula, while written correctly in javascript, needs to be called inside of a try-catch-block inside the
149+ * Parser/Sheet. Otherwise the errors thrown by nested formulas break through. Eg: `=ERRORTYPE(NA())`, NA bubbles up.
150+ * Once this is done, we should test it inside SheetFormulaTest.ts
151+ */
152+var ERRORTYPE = function (value) {
153+    value = TypeConverter_1.TypeConverter.firstValue(value);
154+    if (value instanceof Cell_1.Cell) {
155+        if (value.hasError()) {
156+            value = value.getError();
157+        }
158+        else {
159+            throw new Errors_1.NAError("Function ERROR.TYPE parameter 1 value is not an error.");
160+        }
161+    }
162+    if (value instanceof Error) {
163+        switch (value.name) {
164+            case Errors_1.NULL_ERROR:
165+                return 1;
166+            case Errors_1.DIV_ZERO_ERROR:
167+                return 2;
168+            case Errors_1.VALUE_ERROR:
169+                return 3;
170+            case Errors_1.REF_ERROR:
171+                return 4;
172+            case Errors_1.NAME_ERROR:
173+                return 5;
174+            case Errors_1.NUM_ERROR:
175+                return 6;
176+            case Errors_1.NA_ERROR:
177+                return 7;
178+            default:
179+                return 8;
180+        }
181+    }
182+    else {
183+        throw new Errors_1.NAError("Function ERROR.TYPE parameter 1 value is not an error.");
184+    }
185+};
186+exports.ERRORTYPE = ERRORTYPE;
187diff --git a/dist/Sheet.js b/dist/Sheet.js
188index 4181e58..34e0231 100644
189--- a/dist/Sheet.js
190+++ b/dist/Sheet.js
191@@ -59,12 +59,15 @@ var Sheet = (function () {
192             this.data = {};
193         }
194         /**
195-         * Gets the cell corresponding to the key. If the value is not defined, will return undefined.
196+         * Gets the cell corresponding to the key. If the value is undefined, will return blank cell..
197          * @param key to look up cell
198-         * @returns {Cell} to return, if it exists. Returns undefined if key not in matrix.
199+         * @returns {Cell} to return, if it exists. Returns blank cell if key not in matrix.
200          */
201         Matrix.prototype.getCell = function (key) {
202-            return this.data[key];
203+            if (key in this.data) {
204+                return this.data[key];
205+            }
206+            return new Cell_1.Cell(key);
207         };
208         /**
209          * Add cell to matrix. If it exists, update the necessary values. If it doesn't exist add it.
210@@ -383,7 +386,7 @@ var Sheet = (function () {
211         cellValue: function (cellId) {
212             var value, origin = this, cell = instance.matrix.getCell(cellId);
213             // get value, defaulting to undefined
214-            value = cell ? cell.getValue() : undefined;
215+            value = cell.isBlank() ? undefined : cell.getValue();
216             //update dependencies
217             instance.matrix.getCell(origin).updateDependencies([cellId]);
218             // check references error
219@@ -393,16 +396,17 @@ var Sheet = (function () {
220                 }
221             }
222             // check if any error occurs
223-            if (cell && cell.getError()) {
224+            if (!cell.isBlank() && cell.getError()) {
225                 throw cell.getError();
226             }
227+            if (cell.isBlank()) {
228+                return cell;
229+            }
230             // return value if is set
231             if (utils.isSet(value)) {
232                 var result = instance.helper.number(value);
233                 return !isNaN(result) ? result : value;
234             }
235-            // cell is not available
236-            return undefined;
237         },
238         cellRangeValue: function (start, end) {
239             var coordsStart = utils.cellCoords(start), coordsEnd = utils.cellCoords(end), origin = this;
240diff --git a/dist/Utilities/TypeConverter.js b/dist/Utilities/TypeConverter.js
241index 46cb3d3..4bad051 100644
242--- a/dist/Utilities/TypeConverter.js
243+++ b/dist/Utilities/TypeConverter.js
244@@ -4,6 +4,7 @@ exports.__esModule = true;
245 var moment = require("moment");
246 var Errors_1 = require("../Errors");
247 var DateRegExBuilder_1 = require("./DateRegExBuilder");
248+var Cell_1 = require("../Cell");
249 var YEAR_MONTHDIG_DAY = DateRegExBuilder_1.DateRegExBuilder.DateRegExBuilder()
250     .start()
251     .OPTIONAL_DAYNAME().OPTIONAL_COMMA().YYYY().FLEX_DELIMITER_LOOSEDOT().MM().FLEX_DELIMITER_LOOSEDOT().DD_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
252@@ -418,6 +419,17 @@ var TypeConverter = (function () {
253      * @returns {number} to return. Will always return a number or throw an error. Never returns undefined.
254      */
255     TypeConverter.valueToNumber = function (value) {
256+        if (value instanceof Cell_1.Cell) {
257+            if (value.isBlank()) {
258+                return 0;
259+            }
260+            else {
261+                if (value.hasError()) {
262+                    throw value.getError();
263+                }
264+                value = value.getValue();
265+            }
266+        }
267         if (typeof value === "number") {
268             return value;
269         }
270@@ -455,6 +467,17 @@ var TypeConverter = (function () {
271      * @returns {boolean} to return.
272      */
273     TypeConverter.valueToBoolean = function (value) {
274+        if (value instanceof Cell_1.Cell) {
275+            if (value.isBlank()) {
276+                return false;
277+            }
278+            else {
279+                if (value.hasError()) {
280+                    throw value.getError();
281+                }
282+                value = value.getValue();
283+            }
284+        }
285         if (typeof value === "number") {
286             return value !== 0;
287         }
288@@ -471,7 +494,18 @@ var TypeConverter = (function () {
289      * @returns {string} string representation of value
290      */
291     TypeConverter.valueToString = function (value) {
292-        if (typeof value === "number") {
293+        if (value instanceof Cell_1.Cell) {
294+            if (value.isBlank()) {
295+                return "";
296+            }
297+            else {
298+                if (value.hasError()) {
299+                    throw value.getError();
300+                }
301+                return value.getValue().toString();
302+            }
303+        }
304+        else if (typeof value === "number") {
305             return value.toString();
306         }
307         else if (typeof value === "string") {
308@@ -487,7 +521,18 @@ var TypeConverter = (function () {
309      * @returns {number} representing a time value
310      */
311     TypeConverter.valueToTimestampNumber = function (value) {
312-        if (typeof value === "number") {
313+        if (value instanceof Cell_1.Cell) {
314+            if (value.isBlank()) {
315+                return 0;
316+            }
317+            else {
318+                if (value.hasError()) {
319+                    throw value.getError();
320+                }
321+                return value.getValue();
322+            }
323+        }
324+        else if (typeof value === "number") {
325             return value;
326         }
327         else if (typeof value === "string") {
328@@ -523,7 +568,7 @@ var TypeConverter = (function () {
329      * @returns {boolean} if could be coerced to a number
330      */
331     TypeConverter.canCoerceToNumber = function (value) {
332-        if (typeof value === "number" || typeof value === "boolean") {
333+        if (typeof value === "number" || typeof value === "boolean" || value instanceof Cell_1.Cell) {
334             return true;
335         }
336         else if (typeof value === "string") {
337@@ -623,7 +668,18 @@ var TypeConverter = (function () {
338      * @returns {number} date
339      */
340     TypeConverter.valueToDateNumber = function (value, coerceBoolean) {
341-        if (typeof value === "number") {
342+        if (value instanceof Cell_1.Cell) {
343+            if (value.isBlank()) {
344+                return 0;
345+            }
346+            else {
347+                if (value.hasError()) {
348+                    throw value.getError();
349+                }
350+                return value.getValue();
351+            }
352+        }
353+        else if (typeof value === "number") {
354             return value;
355         }
356         else if (typeof value === "string") {
357diff --git a/src/Formulas/AllFormulas.ts b/src/Formulas/AllFormulas.ts
358index f25217e..8fc6d51 100644
359--- a/src/Formulas/AllFormulas.ts
360+++ b/src/Formulas/AllFormulas.ts
361@@ -88,7 +88,8 @@ import {
362   ISEMAIL,
363   ISURL,
364   N,
365-  ISREF
366+  ISREF,
367+  ERRORTYPE
368 } from "./Info";
369 import {
370   CHOOSE
371@@ -224,7 +225,8 @@ var __COMPLEX = {
372   "WORKDAY.INTL": WORKDAY$INTL,
373   "POISSON.DIST": POISSON,
374   "PERCENTRANK.INC": PERCENTRANK,
375-  "PERCENTRANK.EXC": PERCENTRANK$EXC
376+  "PERCENTRANK.EXC": PERCENTRANK$EXC,
377+  "ERROR.TYPE": ERRORTYPE
378 };
379 
380 export {
381@@ -426,5 +428,6 @@ export {
382   MULTINOMIAL,
383   BINOMDIST,
384   COVAR,
385-  ISREF
386+  ISREF,
387+  ERRORTYPE
388 }
389\ No newline at end of file
390diff --git a/src/Formulas/Info.ts b/src/Formulas/Info.ts
391index c4dee77..55bd0d3 100644
392--- a/src/Formulas/Info.ts
393+++ b/src/Formulas/Info.ts
394@@ -2,7 +2,8 @@ import {
395   ArgsChecker
396 } from "../Utilities/ArgsChecker";
397 import {
398-  NAError
399+  NAError, NullError, DivZeroError, ValueError, RefError, NameError, NumError, NULL_ERROR, DIV_ZERO_ERROR, VALUE_ERROR,
400+  REF_ERROR, NAME_ERROR, NUM_ERROR, NA_ERROR
401 } from "../Errors";
402 import {
403   TypeConverter
404@@ -148,6 +149,50 @@ var ISREF = function (value) {
405 };
406 
407 
408+/**
409+ * Returns the number corresponding to an error value occurring in a different cell. With the aid of this number, an
410+ * error message text can be generated. If an error occurs, the function returns a logical or numerical value.
411+ * @param value - Contains either the address/reference of the cell in which the error occurs, or the error directly.
412+ * Eg: `=ERRORTYPE(NA())`
413+ * @constructor
414+ * TODO: This formula, while written correctly in javascript, needs to be called inside of a try-catch-block inside the
415+ * Parser/Sheet. Otherwise the errors thrown by nested formulas break through. Eg: `=ERRORTYPE(NA())`, NA bubbles up.
416+ * Once this is done, we should test it inside SheetFormulaTest.ts
417+ */
418+var ERRORTYPE = function (value) {
419+  value = TypeConverter.firstValue(value);
420+  if (value instanceof Cell) {
421+    if (value.hasError()) {
422+      value = value.getError();
423+    } else {
424+      throw new NAError("Function ERROR.TYPE parameter 1 value is not an error.");
425+    }
426+  }
427+  if (value instanceof Error) {
428+    switch (value.name) {
429+      case NULL_ERROR:
430+        return 1;
431+      case DIV_ZERO_ERROR:
432+        return 2;
433+      case VALUE_ERROR:
434+        return 3;
435+      case REF_ERROR:
436+        return 4;
437+      case NAME_ERROR:
438+        return 5;
439+      case NUM_ERROR:
440+        return 6;
441+      case NA_ERROR:
442+        return 7;
443+      default:
444+        return 8;
445+    }
446+  } else {
447+    throw new NAError("Function ERROR.TYPE parameter 1 value is not an error.");
448+  }
449+};
450+
451+
452 export {
453   NA,
454   ISTEXT,
455@@ -157,5 +202,6 @@ export {
456   ISEMAIL,
457   ISURL,
458   N,
459-  ISREF
460+  ISREF,
461+  ERRORTYPE
462 }
463\ No newline at end of file
464diff --git a/tests/Formulas/InfoTest.ts b/tests/Formulas/InfoTest.ts
465index 6f6bbed..8a7ae71 100644
466--- a/tests/Formulas/InfoTest.ts
467+++ b/tests/Formulas/InfoTest.ts
468@@ -7,7 +7,8 @@ import {
469   ISEMAIL,
470   ISURL,
471   N,
472-  ISREF
473+  ISREF,
474+  ERRORTYPE
475 } from "../../src/Formulas/Info";
476 import * as ERRORS from "../../src/Errors";
477 import {
478@@ -18,6 +19,15 @@ import {
479 import {
480   Cell
481 } from "../../src/Cell";
482+import {
483+  RefError,
484+  NullError,
485+  NAError,
486+  DivZeroError,
487+  ValueError,
488+  NameError,
489+  NumError
490+} from "../../src/Errors";
491 
492 
493 test("NA", function(){
494@@ -121,3 +131,22 @@ test("ISREF", function(){
495     ISREF.apply(this, []);
496   }, ERRORS.NA_ERROR);
497 });
498+
499+test("ERRORTYPE", function(){
500+  var errorCell = new Cell("A1");
501+  errorCell.setError(new NAError("error"));
502+  assertEquals(ERRORTYPE(new NullError("error")), 1);
503+  assertEquals(ERRORTYPE(new DivZeroError("error")), 2);
504+  assertEquals(ERRORTYPE(new ValueError("error")), 3);
505+  assertEquals(ERRORTYPE(new RefError("error")), 4);
506+  assertEquals(ERRORTYPE(new NameError("error")), 5);
507+  assertEquals(ERRORTYPE(new NumError("error")), 6);
508+  assertEquals(ERRORTYPE(new NAError("error")), 7);
509+  assertEquals(ERRORTYPE(errorCell), 7);
510+  catchAndAssertEquals(function() {
511+    ERRORTYPE.apply(this, []);
512+  }, ERRORS.NA_ERROR);
513+  catchAndAssertEquals(function() {
514+    ERRORTYPE(10);
515+  }, ERRORS.NA_ERROR);
516+});