spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[Sheet] adding documentation
author
Ben Vogt <[email protected]>
date
2017-12-20 02:21:45
stats
4 file(s) changed, 105 insertions(+), 314 deletions(-)
files
src/Parser/HelperUtils.ts
src/Parser/Parser.ts
src/Sheet.ts
src/Utilities/MoreUtils.ts
  1diff --git a/src/Parser/HelperUtils.ts b/src/Parser/HelperUtils.ts
  2deleted file mode 100644
  3index 54f83cd..0000000
  4--- a/src/Parser/HelperUtils.ts
  5+++ /dev/null
  6@@ -1,283 +0,0 @@
  7-import {TypeConverter} from "../Utilities/TypeConverter";
  8-import {DivZeroError, NameError, RefError} from "../Errors";
  9-import {Formulas} from "../Formulas";
 10-
 11-
 12-class HelperUtils {
 13-  public dataStore: any; // TODO: make this not any.
 14-
 15-  constructor(dataStore: any) {
 16-    this.dataStore = dataStore;
 17-  }
 18-
 19-  /**
 20-   * Is the value a number or can the value be interpreted as a number
 21-   */
 22-  number(x) {
 23-    return TypeConverter.valueToNumber(x);
 24-  }
 25-
 26-  string(str) {
 27-    return str.substring(1, str.length - 1);
 28-  }
 29-
 30-  numberInverted(num) {
 31-    return this.number(num) * (-1);
 32-  }
 33-
 34-  specialMatch(type, exp1, exp2) {
 35-    let result;
 36-    switch (type) {
 37-      case '&':
 38-        result = exp1.toString() + exp2.toString();
 39-        break;
 40-    }
 41-    return result;
 42-  }
 43-
 44-  logicMatch(type, exp1, exp2) {
 45-    let result;
 46-    switch (type) {
 47-      case '=':
 48-        result = (exp1 === exp2);
 49-        break;
 50-      case '>':
 51-        result = (exp1 > exp2);
 52-        break;
 53-      case '<':
 54-        result = (exp1 < exp2);
 55-        break;
 56-      case '>=':
 57-        result = (exp1 >= exp2);
 58-        break;
 59-      case '<=':
 60-        result = (exp1 <= exp2);
 61-        break;
 62-      case '<>':
 63-        result = (exp1 != exp2);
 64-        break;
 65-    }
 66-    return result;
 67-  }
 68-
 69-  mathMatch(type, number1, number2) {
 70-    let result;
 71-    number1 = this.number(number1);
 72-    number2 = this.number(number2);
 73-    switch (type) {
 74-      case '+':
 75-        result = number1 + number2;
 76-        break;
 77-      case '-':
 78-        result = number1 - number2;
 79-        break;
 80-      case '/':
 81-        if (number2 === 0) {
 82-          throw new DivZeroError("Evaluation caused divide by zero error.");
 83-        }
 84-        if (number2 !== 0 && number1 === 0) {
 85-          result = 0;
 86-        }
 87-        result = number1 / number2;
 88-        if (result == Infinity) {
 89-          throw new DivZeroError("Evaluation caused divide by zero error.");
 90-        } else if (isNaN(result)) {
 91-          throw new DivZeroError("Evaluation caused divide by zero error.");
 92-        }
 93-        break;
 94-      case '*':
 95-        result = number1 * number2;
 96-        break;
 97-      case '^':
 98-        result = Math.pow(number1, number2);
 99-        break;
100-    }
101-    return result;
102-  }
103-
104-  callFunction (fn, args) {
105-    fn = fn.toUpperCase();
106-    args = args || [];
107-    if (Formulas.exists(fn)) {
108-      return Formulas.get(fn).apply(this, args);
109-    }
110-    throw new NameError("Unknown function: '" + fn + "'.");
111-  }
112-
113-  callVariable (args) {
114-    args = args || [];
115-    let str = args.shift(); // the first in args is the name of the function to call.
116-    if (str) {
117-      str = str.toUpperCase();
118-      if (Formulas.exists(str)) {
119-        return Formulas.get(str).apply(this, args);
120-      }
121-    }
122-    throw new NameError("Unknown variable: '" + str + "'.");
123-  }
124-
125-  cellValue (origin, cellId) {
126-    let cell = this.dataStore.getCell(cellId);
127-
128-    //update dependencies
129-    this.dataStore.getCell(origin).updateDependencies([cellId]);
130-    // check references error
131-    if (cell && cell.getDependencies()) {
132-      if (cell.getDependencies().indexOf(cellId) !== -1) {
133-        throw new RefError("Reference does not exist.");
134-      }
135-    }
136-    return cell;
137-  }
138-
139-  cellRangeValue (origin, start: string, end: string) {
140-    let coordsStart = this.cellCoords(start);
141-    let coordsEnd = this.cellCoords(end);
142-
143-    // iterate cells to get values and indexes
144-    let cells = this.iterateCells(origin, coordsStart, coordsEnd),
145-      result = [];
146-    //update dependencies
147-    this.dataStore.getCell(origin).updateDependencies(cells.index);
148-
149-    result.push(cells.value);
150-    return result;
151-  }
152-
153-  fixedCellValue (origin, id) {
154-    id = id.replace(/\$/g, '');
155-    return this.cellValue(origin, id);
156-  }
157-
158-  fixedCellRangeValue (origin, start, end) {
159-    start = start.replace(/\$/g, '');
160-    end = end.replace(/\$/g, '');
161-
162-    return this.cellRangeValue(origin, start, end);
163-  }
164-
165-  static isArray(value) {
166-    return value instanceof Array;
167-  }
168-
169-  static isFunction(value) {
170-    return value instanceof Function;
171-  }
172-
173-  static toNum(chr) {
174-    chr = HelperUtils.clearFormula(chr);
175-    let base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', i, j, result = 0;
176-
177-    for (i = 0, j = chr.length - 1; i < chr.length; i += 1, j -= 1) {
178-      result += Math.pow(base.length, j) * (base.indexOf(chr[i]) + 1);
179-    }
180-
181-    if (result) {
182-      --result;
183-    }
184-
185-    return result;
186-  }
187-
188-  toChar(num) {
189-    let s = '';
190-
191-    while (num >= 0) {
192-      s = String.fromCharCode(num % 26 + 97) + s;
193-      num = Math.floor(num / 26) - 1;
194-    }
195-
196-    return s.toUpperCase();
197-  }
198-
199-  XYtoA1(x, y) {
200-    function numberToLetters(num) {
201-      let mod = num % 26,
202-        pow = num / 26 | 0,
203-        out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
204-      return pow ? numberToLetters(pow) + out : out;
205-    }
206-    return numberToLetters(x+1) + (y+1).toString();
207-  }
208-
209-  cellCoords(cell) {
210-    let num = cell.match(/\d+$/),
211-      alpha = cell.replace(num, '');
212-
213-    return {
214-      row: parseInt(num[0], 10) - 1,
215-      col: HelperUtils.toNum(alpha)
216-    };
217-  }
218-
219-  static clearFormula(formula) {
220-    return formula.replace(/\$/g, '');
221-  }
222-
223-  iterateCells(origin, startCell, endCell, callback?) {
224-    let result = {
225-      index: [], // list of cell index: A1, A2, A3
226-      value: []  // list of cell value
227-    };
228-
229-    let cols = {
230-      start: 0,
231-      end: 0
232-    };
233-
234-    if (endCell.col >= startCell.col) {
235-      cols = {
236-        start: startCell.col,
237-        end: endCell.col
238-      };
239-    } else {
240-      cols = {
241-        start: endCell.col,
242-        end: startCell.col
243-      };
244-    }
245-
246-    let rows = {
247-      start: 0,
248-      end: 0
249-    };
250-
251-    if (endCell.row >= startCell.row) {
252-      rows = {
253-        start: startCell.row,
254-        end: endCell.row
255-      };
256-    } else {
257-      rows = {
258-        start: endCell.row,
259-        end: startCell.row
260-      };
261-    }
262-
263-    for (let column = cols.start; column <= cols.end; column++) {
264-      for (let row = rows.start; row <= rows.end; row++) {
265-        let cellIndex = this.toChar(column) + (row + 1),
266-          cellValue = this.cellValue(origin, cellIndex);
267-
268-        result.index.push(cellIndex);
269-        result.value.push(cellValue);
270-      }
271-    }
272-
273-    if (HelperUtils.isFunction(callback)) {
274-      return callback.apply(callback, [result]);
275-    } else {
276-      return result;
277-    }
278-  }
279-
280-  static sort(rev?) {
281-    return function (a, b) {
282-      return ((a < b) ? -1 : ((a > b) ? 1 : 0)) * (rev ? -1 : 1);
283-    }
284-  }
285-}
286-
287-export {
288-  HelperUtils
289-}
290\ No newline at end of file
291diff --git a/src/Parser/Parser.ts b/src/Parser/Parser.ts
292index 577d6cb..bc0aefc 100644
293--- a/src/Parser/Parser.ts
294+++ b/src/Parser/Parser.ts
295@@ -25,8 +25,18 @@ import {
296   string
297 } from "../Utilities/MoreUtils";
298 import {TypeConverter} from "../Utilities/TypeConverter";
299-import {DIVIDE, EQ, GT, GTE, LT, LTE, MINUS, MULTIPLY, POWER, SUM} from "../Formulas/Math";
300-
301+import {
302+  DIVIDE,
303+  EQ,
304+  GT,
305+  GTE,
306+  LT,
307+  LTE,
308+  MINUS,
309+  MULTIPLY,
310+  POWER,
311+  SUM
312+} from "../Formulas/Math";
313 
314 let Parser = (function () {
315   let parser = {
316diff --git a/src/Sheet.ts b/src/Sheet.ts
317index 98168dc..8d592d8 100644
318--- a/src/Sheet.ts
319+++ b/src/Sheet.ts
320@@ -4,19 +4,16 @@ import {DataStore} from "./Parser/DataStore";
321 import {FormulaParser} from "./Parser/Parser";
322 import {Formulas} from "./Formulas";
323 import {
324-  isFunction, characterToNumber, numberToCharacter, convertXYtoA1Coordinates,
325+  numberToCharacter,
326+  convertXYtoA1Coordinates,
327   A1toRowColCoordinates
328 } from "./Utilities/MoreUtils";
329 
330-// TODO: Document.
331+/**
332+ * Represents a spreadsheet parser and data-store that act together as a functional spreadsheet.
333+ */
334 class Sheet {
335-  private parser  = {
336-    yy:{
337-      obj: undefined
338-    },
339-    setObj: function (obj: string) {},
340-    parse: function (formula: string) {}
341-  };
342+  private parser;
343   private dataStore : DataStore;
344 
345   constructor() {
346@@ -24,17 +21,22 @@ class Sheet {
347     this.dataStore = new DataStore();
348   }
349 
350-  iterateCells (origin, startCell, endCell, callback?) {
351+  /**
352+   * Iterate through cells in the data-store, returning the collected cells in the range.
353+   * @param origin
354+   * @param startCell
355+   * @param endCell
356+   * @returns {{index: Array; value: Array}}
357+   */
358+  iterateCells (origin, startCell, endCell) {
359     let result = {
360       index: [], // list of cell index: A1, A2, A3
361       value: []  // list of cell value
362     };
363-
364     let cols = {
365       start: 0,
366       end: 0
367     };
368-
369     if (endCell.col >= startCell.col) {
370       cols = {
371         start: startCell.col,
372@@ -46,12 +48,10 @@ class Sheet {
373         end: startCell.col
374       };
375     }
376-
377     let rows = {
378       start: 0,
379       end: 0
380     };
381-
382     if (endCell.row >= startCell.row) {
383       rows = {
384         start: startCell.row,
385@@ -63,7 +63,6 @@ class Sheet {
386         end: startCell.row
387       };
388     }
389-
390     for (let column = cols.start; column <= cols.end; column++) {
391       for (let row = rows.start; row <= rows.end; row++) {
392         let cellIndex = numberToCharacter(column) + (row + 1),
393@@ -73,14 +72,15 @@ class Sheet {
394         result.value.push(cellValue);
395       }
396     }
397-
398-    if (isFunction(callback)) {
399-      return callback.apply(callback, [result]);
400-    } else {
401-      return result;
402-    }
403+    return result;
404   }
405 
406+  /**
407+   * Call function with given arguments. Used for calling formulas.
408+   * @param fn
409+   * @param args
410+   * @returns {any}
411+   */
412   callFunction(fn, args) {
413     fn = fn.toUpperCase();
414     args = args || [];
415@@ -91,6 +91,11 @@ class Sheet {
416     throw new NameError("Unknown function: '" + fn + "'.");
417   }
418 
419+  /**
420+   * Call variable, which could include calling a function.
421+   * @param args
422+   * @returns {any}
423+   */
424   callVariable(args) {
425     args = args || [];
426     let str = args.shift(); // the first in args is the name of the function to call.
427@@ -105,6 +110,12 @@ class Sheet {
428     throw new NameError("Unknown variable: '" + str + "'.");
429   };
430 
431+  /**
432+   * Fetch cell, updating dependencies in process.
433+   * @param origin
434+   * @param cellId
435+   * @returns {Cell}
436+   */
437   cellValue(origin, cellId) {
438     let cell = this.dataStore.getCell(cellId);
439 
440@@ -119,6 +130,13 @@ class Sheet {
441     return cell;
442   }
443 
444+  /**
445+   * Get a range of cells.
446+   * @param origin - the cell id in A1 notation from which this range is being referenced.
447+   * @param {string} start - first cell coordinate (in A1 notation) in iteration
448+   * @param {string} end - final cell coordinate (in A1 notation) in iteration
449+   * @returns {Array}
450+   */
451   cellRangeValue(origin, start: string, end: string) {
452     let coordsStart = A1toRowColCoordinates(start),
453       coordsEnd = A1toRowColCoordinates(end);
454@@ -133,11 +151,24 @@ class Sheet {
455     return result;
456   }
457 
458+  /**
459+   * Get a fixed cell value.
460+   * @param origin
461+   * @param id
462+   * @returns {Cell}
463+   */
464   fixedCellValue (origin, id) {
465     id = id.replace(/\$/g, '');
466     return this.cellValue(origin, id);
467   };
468 
469+  /**
470+   * Get a fixed cell value range.
471+   * @param origin
472+   * @param start
473+   * @param end
474+   * @returns {Array}
475+   */
476   fixedCellRangeValue(origin, start, end) {
477     start = start.replace(/\$/g, '');
478     end = end.replace(/\$/g, '');
479@@ -145,6 +176,10 @@ class Sheet {
480     return this.cellRangeValue(origin, start, end);
481   };
482 
483+  /**
484+   * Recalculate dependencies for a cell.
485+   * @param {Cell} cell
486+   */
487   private recalculateCellDependencies(cell: Cell) {
488     let allDependencies = this.dataStore.getDependencies(cell.getId());
489 
490@@ -156,6 +191,11 @@ class Sheet {
491     }
492   }
493 
494+  /**
495+   * Executes the formula in a cell.
496+   * @param {Cell} cell
497+   * @returns {{error: Error; result: any} | {error: any; result: any}}
498+   */
499   private calculateCellFormula(cell: Cell) {
500     // to avoid double translate formulas, update cell data in parser
501     let parsed = this.parse(cell.getFormula(), cell.getId());
502@@ -166,6 +206,10 @@ class Sheet {
503     return parsed;
504   }
505 
506+  /**
507+   * Add a cell to the data-store, recording and updating dependencies if necessary.
508+   * @param {Cell} cell
509+   */
510   private registerCellInDataStore(cell: Cell) {
511     this.dataStore.addCell(cell);
512     if (cell.hasFormula()) {
513@@ -173,6 +217,12 @@ class Sheet {
514     }
515   }
516 
517+  /**
518+   * Parse a formula for a given cellId. This involves all calculations and look-ups.
519+   * @param formula
520+   * @param cellId
521+   * @returns {any}
522+   */
523   public parse(formula, cellId) {
524     let result = null;
525     let error = null;
526@@ -206,6 +256,11 @@ class Sheet {
527     }
528   }
529 
530+  /**
531+   * Set a cell's value, by id.
532+   * @param {string} id
533+   * @param {string} value
534+   */
535   public setCell(id: string, value: string) {
536     let cell = new Cell(id);
537     cell.setValue(value.toString());
538@@ -213,6 +268,11 @@ class Sheet {
539     this.recalculateCellDependencies(cell);
540   }
541 
542+  /**
543+   * Get a cell from the data-store, returning null if a cell is undefined.
544+   * @param {string} id
545+   * @returns {Cell}
546+   */
547   public getCell(id: string) : Cell {
548     let cell = this.dataStore.getCell(id);
549     if (cell === undefined) {
550@@ -221,6 +281,10 @@ class Sheet {
551     return cell;
552   }
553 
554+  /**
555+   * Load an a matrix of cells into the data-store.
556+   * @param {Array<Array<any>>} input
557+   */
558   public load(input: Array<Array<any>>) {
559     for (let y = 0; y < input.length; y++) {
560       for (let x = 0; x < input[0].length; x++) {
561diff --git a/src/Utilities/MoreUtils.ts b/src/Utilities/MoreUtils.ts
562index a5bb31f..71c4b96 100644
563--- a/src/Utilities/MoreUtils.ts
564+++ b/src/Utilities/MoreUtils.ts
565@@ -25,15 +25,6 @@ function isArray(value) : boolean {
566   return value instanceof Array;
567 }
568 
569-/**
570- * Returns true if value is instance of a Function.
571- * @param value
572- * @returns {boolean}
573- */
574-function isFunction(value) : boolean {
575-  return value instanceof Function;
576-}
577-
578 /**
579  * Alphabetical character to number.
580  * @param chr
581@@ -305,7 +296,6 @@ export {
582   isDefined,
583   isUndefined,
584   isArray,
585-  isFunction,
586   string,
587   numberToCharacter,
588   convertXYtoA1Coordinates,