UNPKG

@borgar/fx

Version:

Utilities for working with Excel formulas

192 lines (188 loc) 4.68 kB
/* ** RC notation works differently from A1 in that we can't merge static ** references joined by `:`. Merging can only work between references ** that are relative/absolute on the same axes, so: ** - R1C1:R2C2 will work, ** - R[1]C1:R[2]C2 will also work, but ** - R[1]C[1]:R2C2 doesn't have a direct rectangle represention without context. */ import type { RangeR1C1 } from './types.ts'; function trimDirection (head: boolean, tail: boolean): 'both' | 'head' | 'tail' | undefined { if (head && tail) { return 'both'; } if (head) { return 'head'; } if (tail) { return 'tail'; } } function parseR1C1Part (ref: string): [number, number, boolean, boolean] { let r0 = null; let c0 = null; let $r0 = null; let $c0 = null; // R part const rm = /^R(?:\[([+-]?\d+)\]|(\d+))?/.exec(ref); if (rm) { if (rm[1]) { r0 = parseInt(rm[1], 10); $r0 = false; } else if (rm[2]) { r0 = parseInt(rm[2], 10) - 1; $r0 = true; } else { r0 = 0; $r0 = false; } ref = ref.slice(rm[0].length); } // C part const cm = /^C(?:\[([+-]?\d+)\]|(\d+))?/.exec(ref); if (cm) { if (cm[1]) { c0 = parseInt(cm[1], 10); $c0 = false; } else if (cm[2]) { c0 = parseInt(cm[2], 10) - 1; $c0 = true; } else { c0 = 0; $c0 = false; } ref = ref.slice(cm[0].length); } // must have at least one part (and nothing more) if ((!rm && !cm) || ref.length) { return null; } return [ r0, c0, $r0, $c0 ]; } /** * Parse R1C1-style range string into a RangeR1C1 object. * * @param rangeString R1C1-style range string. * @return A reference object. */ export function parseR1C1Range (rangeString: string): RangeR1C1 | null { let final: RangeR1C1 | null = undefined; const [ part1, op, part2, overflow ] = rangeString.split(/(\.?:\.?)/); if (overflow) { return null; } const range = parseR1C1Part(part1); // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with const trim = trimDirection(!!op && op[0] === '.', !!op && op[op.length - 1] === '.'); if (range) { const [ r0, c0, $r0, $c0 ] = range; if (part2) { const extendTo = parseR1C1Part(part2); if (extendTo) { final = {}; const [ r1, c1, $r1, $c1 ] = extendTo; // rows if (r0 != null && r1 != null) { final.r0 = $r0 === $r1 ? Math.min(r0, r1) : r0; final.$r0 = $r0; final.r1 = $r0 === $r1 ? Math.max(r0, r1) : r1; final.$r1 = $r1; } else if (r0 != null && r1 == null) { // partial RC:C final.r0 = r0; final.$r0 = $r0; final.r1 = null; final.$r1 = $r0; } else if (r0 == null && r1 != null) { // partial C:RC final.r0 = r1; final.$r0 = $r1; final.r1 = null; final.$r1 = $r1; } else if (r0 == null && r1 == null) { // C:C final.r0 = null; final.$r0 = false; final.r1 = null; final.$r1 = false; } // columns if (c0 != null && c1 != null) { final.c0 = $c0 === $c1 ? Math.min(c0, c1) : c0; final.$c0 = $c0; final.c1 = $c0 === $c1 ? Math.max(c0, c1) : c1; final.$c1 = $c1; } else if (c0 != null && c1 == null) { final.c0 = c0; final.$c0 = $c0; final.c1 = null; final.$c1 = $c0; } else if (c0 == null && c1 != null) { final.c0 = c1; final.$c0 = $c1; final.c1 = null; final.$c1 = $c1; } else if (c0 == null && c1 == null) { final.c0 = null; final.$c0 = false; final.c1 = null; final.$c1 = false; } } else { return null; } } // range only - no second part else if (r0 != null && c0 == null) { final = { r0: r0, c0: null, r1: r0, c1: null, $r0: $r0, $c0: false, $r1: $r0, $c1: false }; } else if (r0 == null && c0 != null) { final = { r0: null, c0: c0, r1: null, c1: c0, $r0: false, $c0: $c0, $r1: false, $c1: $c0 }; } else { final = { r0: r0 || 0, c0: c0 || 0, r1: r0 || 0, c1: c0 || 0, $r0: $r0 || false, $c0: $c0 || false, $r1: $r0 || false, $c1: $c0 || false }; } } if (final && trim) { final.trim = trim; } return final; }