spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← All files
name: src/Utilities/MoreUtils.ts
-rw-r--r--
8616
  1/**
  2 * If the value is UNDEFINED, return true.
  3 * @param value - Value to check if undefined.
  4 * @returns {boolean}
  5 */
  6function isUndefined(value : any) : boolean {
  7  return value === undefined;
  8}
  9
 10/**
 11 * If the value is DEFINED, return true.
 12 * @param value - Value to check if is defined.
 13 * @returns {boolean}
 14 */
 15function isDefined(value : any) : boolean {
 16  return value !== undefined;
 17}
 18
 19/**
 20 * Returns true if value is an instance of a Array.
 21 * @param value
 22 * @returns {boolean}
 23 */
 24function isArray(value) : boolean {
 25  return value instanceof Array;
 26}
 27
 28/**
 29 * Alphabetical character to number.
 30 * @param chr
 31 * @returns {number}
 32 */
 33function characterToNumber(chr) {
 34  chr = chr.replace(/\$/g, '');
 35  let base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', i, j, result = 0;
 36
 37  for (i = 0, j = chr.length - 1; i < chr.length; i += 1, j -= 1) {
 38    result += Math.pow(base.length, j) * (base.indexOf(chr[i]) + 1);
 39  }
 40
 41  if (result) {
 42    --result;
 43  }
 44
 45  return result;
 46}
 47
 48/**
 49 * Converts a number to an alphabetical character.
 50 * @param num
 51 * @returns {string}
 52 */
 53function numberToCharacter(num) {
 54  let s = '';
 55
 56  while (num >= 0) {
 57    s = String.fromCharCode(num % 26 + 97) + s;
 58    num = Math.floor(num / 26) - 1;
 59  }
 60
 61  return s.toUpperCase();
 62}
 63
 64/**
 65 * Converts a quoted string to an un-quoted string: `"hey"` to `hey`
 66 * @param str
 67 * @returns {string}
 68 */
 69function string(str){
 70  return str.substring(1, str.length - 1);
 71}
 72
 73
 74/**
 75 * Converts XY coordinates (eg {0, 0}) to A1 coordinates (eg {A1}).
 76 * @param x
 77 * @param y
 78 * @returns {string}
 79 */
 80function convertXYtoA1Coordinates(x, y) {
 81  function numberToLetters(num) {
 82    let mod = num % 26,
 83      pow = num / 26 | 0,
 84      out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
 85    return pow ? numberToLetters(pow) + out : out;
 86  }
 87  return numberToLetters(x+1) + (y+1).toString();
 88}
 89
 90/**
 91 * Returns RowCol coordinates of an A1 cellId
 92 * @param cellId
 93 * @returns {Object}
 94 */
 95function A1toRowColCoordinates(cellId) {
 96  let num = cellId.match(/\d+$/),
 97    alpha = cellId.replace(num, '');
 98
 99  return {
100    row: parseInt(num[0], 10) - 1,
101    col: characterToNumber(alpha)
102  };
103}
104
105/**
106 * Class for building formatted strings with commas, forced number of leading and trailing zeros, and arbitrary leading
107 * and trailing strings.
108 */
109class NumberStringBuilder {
110  private n : number;
111  private shouldUseComma : boolean = false;
112  private integerZeroCount : number = 1; // e.g. default to "0.1"
113  private decimalZeroCount : number = 0; // e.g. default to "1"
114  private maxDecimalPlaces : number;
115  private headString : string = "";
116  private tailString : string = "";
117
118  /**
119   * Static builder, easier than `new`.
120   * @returns {NumberStringBuilder}
121   */
122  static start() : NumberStringBuilder {
123    return new NumberStringBuilder();
124  }
125
126  /**
127   * Pads a given string with "0" on the right or left side until it is a certain width.
128   * @param {string} str - String to pad.
129   * @param {number} width - Width to pad to. If this is less than the strings length, will do nothing.
130   * @param {string} type - "right" or "left" side to append zeroes.
131   * @returns {string}
132   */
133  private static pad(str : string, width : number, type : string) : string {
134    let z = '0';
135    str = str + '';
136    if (type === "left") {
137      return str.length >= width ? str : new Array(width - str.length + 1).join(z) + str;
138    } else {
139      return str.length >= width ? str : str + (new Array(width - str.length + 1).join(z));
140    }
141  }
142
143  /**
144   * Rounds a number n to a certain number of digits.
145   * @param n - Number to round.
146   * @param digits - Digits to round to.
147   * @returns {number}
148   */
149  private static round(n, digits) {
150    return Math.round(n * Math.pow(10, digits)) / Math.pow(10, digits);
151  }
152
153  /**
154   * Set the number that we'll be formatting.
155   * @param {number} n - Number.
156   * @returns {NumberStringBuilder}
157   */
158  public number(n : number) : NumberStringBuilder {
159    this.n = n;
160    return this;
161  }
162
163  /**
164   * The number of zeros to force on the left side of the decimal.
165   * @param {number} zeros
166   * @returns {NumberStringBuilder}
167   */
168  public integerZeros(zeros : number) : NumberStringBuilder {
169    this.integerZeroCount = zeros;
170    return this;
171  }
172
173  /**
174   * The number of zeros to force on the right side of the decimal.
175   * @param {number} zeros
176   * @returns {NumberStringBuilder}
177   */
178  public decimalZeros(zeros : number) : NumberStringBuilder {
179    this.decimalZeroCount = zeros;
180    return this;
181  }
182
183  /**
184   * If you would like to force the maximum number of decimal places, without padding with zeros, set this.
185   * WARNING: Should not be used in conjunction with decimalZeros().
186   * @param {number} maxDecimalPlaces
187   * @returns {NumberStringBuilder}
188   */
189  public maximumDecimalPlaces(maxDecimalPlaces: number) : NumberStringBuilder {
190    this.maxDecimalPlaces = maxDecimalPlaces;
191    return this;
192  }
193
194  /**
195   * Should digits to the left side of the decimal use comma-notation?
196   * @param {boolean} shouldUseComma
197   * @returns {NumberStringBuilder}
198   */
199  public commafy(shouldUseComma : boolean) : NumberStringBuilder {
200    this.shouldUseComma = shouldUseComma;
201    return this;
202  }
203
204  /**
205   * String to append to the beginning of the final formatted number.
206   * @param {string} head
207   * @returns {NumberStringBuilder}
208   */
209  public head(head : string) : NumberStringBuilder {
210    this.headString = head;
211    return this;
212  }
213
214  /**
215   * String to append to the end of the final formatted number.
216   * @param {string} tail
217   * @returns {NumberStringBuilder}
218   */
219  public tail(tail : string) : NumberStringBuilder {
220    this.tailString = tail;
221    return this;
222  }
223
224  /**
225   * Building the string using the rules set in this builder.
226   * @returns {string}
227   */
228  public build() : string {
229    let nStr = this.n.toString();
230    let isInt = this.n % 1 === 0;
231    let integerPart = isInt ? nStr : nStr.split(".")[0];
232    integerPart = integerPart.replace("-", "");
233    let decimalPart = isInt ? "" : nStr.split(".")[1];
234
235    // Building integer part
236    if (this.integerZeroCount > 1) {
237      integerPart = NumberStringBuilder.pad(integerPart, this.integerZeroCount, "left");
238    }
239
240    // Building decimal part
241    // If the decimal part is greater than the number of zeros we allow, then we have to round the number.
242    if (isDefined(this.maxDecimalPlaces)) {
243      let decimalAsFloat = NumberStringBuilder.round(parseFloat("0."+decimalPart), this.maxDecimalPlaces);
244      if (decimalAsFloat % 1 === 0) {
245        integerPart = Math.floor((parseInt(integerPart) + decimalAsFloat)).toString();
246        integerPart = NumberStringBuilder.pad(integerPart, this.integerZeroCount, "left");
247        decimalPart = "";
248      } else {
249        decimalPart = decimalAsFloat.toString().split(".")[1];
250      }
251    } else {
252      if (decimalPart.length > this.decimalZeroCount) {
253        let decimalAsFloat = NumberStringBuilder.round(parseFloat("0."+decimalPart), this.decimalZeroCount);
254        let roundedDecimalPart;
255
256        if (decimalAsFloat % 1 === 0) {
257          integerPart = Math.floor((parseInt(integerPart) + decimalAsFloat)).toString();
258          integerPart = NumberStringBuilder.pad(integerPart, this.integerZeroCount, "left");
259          roundedDecimalPart = "";
260        } else {
261          roundedDecimalPart = decimalAsFloat.toString().split(".")[1];
262        }
263        decimalPart = NumberStringBuilder.pad(roundedDecimalPart, this.decimalZeroCount, "right");
264      } else {
265        decimalPart = NumberStringBuilder.pad(decimalPart, this.decimalZeroCount, "right");
266      }
267    }
268
269    // Inserting commas if necessary.
270    if (this.shouldUseComma) {
271      integerPart = integerPart.split("").reverse().map(function (digit, index) {
272        if (index % 3 === 0 && index !== 0) {
273          return digit + ",";
274        }
275        return digit;
276      }).reverse().join("");
277    }
278
279    if (this.integerZeroCount === 0 && integerPart === "0") {
280      integerPart = "";
281    }
282
283    if (this.n === 0) {
284      return this.headString + "." + this.tailString;
285    }
286    let trueSign = this.n < 0 ? "-" : "";
287
288    if ((this.decimalZeroCount === 0 && isUndefined(this.maxDecimalPlaces)) || isDefined(this.maxDecimalPlaces) && decimalPart === "") {
289      return trueSign + this.headString + integerPart + this.tailString;
290    }
291    return trueSign + this.headString + integerPart + "." + decimalPart + this.tailString;
292  }
293}
294
295export {
296  isDefined,
297  isUndefined,
298  isArray,
299  string,
300  numberToCharacter,
301  convertXYtoA1Coordinates,
302  A1toRowColCoordinates,
303  characterToNumber,
304  NumberStringBuilder
305}