spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← All files
name: src/Cell.ts
-rw-r--r--
5755
  1const CELL_ID_ERROR = "CELL_ID_ERROR";
  2
  3/**
  4 * Represents a cell id error, and is thrown when a cells id does not conform to A1 notation.
  5 */
  6class CellIdError extends Error {
  7  constructor(msg: string) {
  8    super();
  9    this.message = msg;
 10    this.name = CELL_ID_ERROR;
 11  }
 12}
 13
 14/**
 15 * Cell represents a cell in the spreadsheet. It contains a nullable rawFormulaText, and a value, which is not nullable unless
 16 * the parsing of the rawFormulaText results in an error.
 17 */
 18class Cell {
 19  /**
 20   * The raw formula text that can be parse, excluding the proceeding =
 21   * E.g: SUM(A2:A4, 10)
 22   */
 23  private rawFormulaText: string = null;
 24  private typedValue: any = null;
 25  private dependencies: Array<string> = [];
 26  private error: Error = null;
 27  private id: string;
 28  private row: number;
 29  private col: number;
 30
 31  /**
 32   * Creates an empty cell with an id.
 33   * @param id key of the cell in A1-format.
 34   */
 35  constructor(id: string) {
 36    if (!id.match(/^(?:[A-Za-z]+[0-9]+)$/)) {
 37      throw new CellIdError("Cell id " + id + " not valid");
 38    }
 39    let key = parseKey(id);
 40
 41    this.id = id;
 42    this.row = key.y;
 43    this.col = key.x;
 44  }
 45
 46  /**
 47   * Update this cell's dependencies, where `dependencies` is a unique list of A1-format cell IDs.
 48   * @param dependencies to merge with existing dependencies.
 49   */
 50  updateDependencies(dependencies: Array<string>) {
 51    for (let index in dependencies) {
 52      if (this.dependencies.indexOf(dependencies[index]) === -1) {
 53        this.dependencies.push(dependencies[index]);
 54      }
 55    }
 56  }
 57
 58  /**
 59   * Return a list of dependencies in A1-format cell IDs, in no particular order, but likely in order of occurrence in
 60   * rawFormulaText.
 61   * @returns {Array<string>} list of dependencies in A1-format
 62   */
 63  getDependencies() : Array<string> {
 64    return this.dependencies;
 65  }
 66
 67  /**
 68   * Return the zero-indexed column number of this cell.
 69   * @returns {number} column
 70   */
 71  getColumn() : number {
 72    return this.col;
 73  }
 74
 75  /**
 76   * Return the zero-indexed row number of this cell.
 77   * @returns {number} row
 78   */
 79  getRow() : number {
 80    return this.row;
 81  }
 82
 83  /**
 84   * Get the A1-format ID of this cell.
 85   * @returns {string} cell ID
 86   */
 87  getId() : string {
 88    return this.id;
 89  }
 90
 91  /**
 92   * Get the rawFormulaText of this cell if set. Defaults to null, so should be used in combination with hasFormula().
 93   * @returns {string} rawFormulaText of this cell, if set. Nullable.
 94   */
 95  getFormula() : string {
 96    return this.rawFormulaText;
 97  }
 98
 99  /**
100   * Returns true if this cell has a formula to be parsed.
101   * @returns {boolean}
102   */
103  hasFormula() : boolean {
104    return this.rawFormulaText !== null;
105  }
106
107  /**
108   * Sets the value or rawFormulaText for this cell. If the input begins with =, then it is considered to be a rawFormulaText. If it
109   * is not, then it is a value, and set as the raw value for this cell.
110   * @param rawFormula
111   */
112  setValue(rawFormula: string) {
113    if (typeof rawFormula === "string" && rawFormula.charAt(0) === "=") {
114      this.rawFormulaText = rawFormula.substr(1);
115    } else {
116      this.typedValue = rawFormula;
117    }
118  }
119
120  /**
121   * Gets the rawFormulaText for this cell, which is either null or a string.
122   * @returns {string}
123   */
124  getRawFormulaText() : string | null {
125    return this.rawFormulaText;
126  }
127
128
129  /**
130   * Get the value of this cell if a value is present. If this cell was given a formula but not a value, this may return
131   * null.
132   * @returns {any}
133   */
134  getValue() : any {
135    return this.typedValue;
136  }
137
138  /**
139   * CLears a cells value.
140   */
141  clearValue() {
142    this.typedValue = null;
143  }
144
145  /**
146   * Set error for this cell. Usually in the case of a parse error when parsing the rawFormulaText.
147   * @param error to set.
148   */
149  setError(error: Error) {
150    this.error = error;
151  }
152
153  /**
154   * Get the error for this cell. If the rawFormulaText is not parsed properly, or is null, this could be null.
155   * @returns {Error} error to return, could be null.
156   */
157  getError() : Error {
158    return this.error;
159  }
160
161  /**
162   * Easier way to check if this cell has an error.
163   * @returns {boolean}
164   */
165  hasError() : boolean {
166    return this.error !== null;
167  }
168
169  /**
170   * A cell is deemed blank if it contains no value, no error, and no typed value.
171   * @returns {boolean}
172   */
173  isBlank(): boolean {
174    return this.error === null && this.rawFormulaText === null && this.typedValue === null;
175  }
176
177  /**
178   * Returns the human-readable string representation of this cell, omitting some obvious fields.
179   * @returns {string}
180   */
181  toString() : string {
182    return "id=" + this.id + ", value=" + this.typedValue + ", rawFormulaText=" + this.rawFormulaText + ", error=" + this.error;
183  }
184
185  /**
186   * Comparing two cells.
187   * @param other
188   * @returns {boolean}
189   */
190  equals(other : Cell) : boolean {
191    return this.toString() === other.toString();
192  }
193
194  /**
195   * Build a cell with an id and value.
196   * @param id - A1-notation id or key.
197   * @param value - value of the cell as a string
198   * @returns {Cell}
199   * @constructor
200   */
201  static BuildFrom(id: string, value: any) : Cell {
202    let cell = new Cell(id);
203    cell.setValue(value);
204    return cell;
205  }
206}
207
208function toNum(chr) {
209  chr = chr.replace(/\$/g, '');
210  let base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', i, j, result = 0;
211  for (i = 0, j = chr.length - 1; i < chr.length; i += 1, j -= 1) {
212    result += Math.pow(base.length, j) * (base.indexOf(chr[i]) + 1);
213  }
214  if (result) {
215    --result;
216  }
217  return result;
218}
219
220function parseKey(cell) {
221  let num = cell.match(/\d+$/),
222    alpha = cell.replace(num, '');
223
224  return {
225    x: toNum(alpha),
226    y: parseInt(num[0], 10) - 1
227  };
228}
229
230export {
231  Cell,
232  CellIdError,
233  CELL_ID_ERROR
234}