@devmm/puredocs-excel-formula 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -448,8 +448,11 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
448
448
  TokenType2[TokenType2["SpillReference"] = 24] = "SpillReference";
449
449
  TokenType2[TokenType2["Exclamation"] = 25] = "Exclamation";
450
450
  TokenType2[TokenType2["AtSign"] = 26] = "AtSign";
451
- TokenType2[TokenType2["ErrorLiteral"] = 27] = "ErrorLiteral";
452
- TokenType2[TokenType2["EOF"] = 28] = "EOF";
451
+ TokenType2[TokenType2["LeftBrace"] = 27] = "LeftBrace";
452
+ TokenType2[TokenType2["RightBrace"] = 28] = "RightBrace";
453
+ TokenType2[TokenType2["Semicolon"] = 29] = "Semicolon";
454
+ TokenType2[TokenType2["ErrorLiteral"] = 30] = "ErrorLiteral";
455
+ TokenType2[TokenType2["EOF"] = 31] = "EOF";
453
456
  return TokenType2;
454
457
  })(TokenType || {});
455
458
  function makeToken(type, value2, position) {
@@ -463,6 +466,8 @@ var FormulaException = class extends Error {
463
466
  this.name = "FormulaException";
464
467
  }
465
468
  };
469
+ var UNDERSCORE = 95;
470
+ var RESERVED_PREFIXES = ["_XLFN.", "_XLWS.", "_XLPM."];
466
471
  var KNOWN_ERRORS = /* @__PURE__ */ new Set([
467
472
  "#NULL!",
468
473
  "#DIV/0!",
@@ -474,7 +479,7 @@ var KNOWN_ERRORS = /* @__PURE__ */ new Set([
474
479
  "#CALC!",
475
480
  "#SPILL!"
476
481
  ]);
477
- var _formula, _pos, _FormulaLexer_instances, readNextToken_fn, readString_fn, readErrorLiteral_fn, readQuotedSheetRef_fn, readNumber_fn, readIdentifier_fn, readCellRefChars_fn, readOperator_fn, skipWhitespace_fn;
482
+ var _formula, _pos, _FormulaLexer_instances, readNextToken_fn, readString_fn, readErrorLiteral_fn, readQuotedSheetRef_fn, readNumber_fn, readIdentifier_fn, stripReservedPrefixes_fn, readCellRefChars_fn, readOperator_fn, skipWhitespace_fn;
478
483
  var FormulaLexer = class {
479
484
  constructor(formula) {
480
485
  __privateAdd(this, _FormulaLexer_instances);
@@ -492,7 +497,7 @@ var FormulaLexer = class {
492
497
  const token = __privateMethod(this, _FormulaLexer_instances, readNextToken_fn).call(this);
493
498
  if (token) tokens.push(token);
494
499
  }
495
- tokens.push(makeToken(28 /* EOF */, "", __privateGet(this, _pos)));
500
+ tokens.push(makeToken(31 /* EOF */, "", __privateGet(this, _pos)));
496
501
  return tokens;
497
502
  }
498
503
  };
@@ -546,7 +551,7 @@ readErrorLiteral_fn = function() {
546
551
  if (!KNOWN_ERRORS.has(upper2)) {
547
552
  throw new FormulaException(`Unknown error literal "${raw}" at position ${start}.`);
548
553
  }
549
- return makeToken(27 /* ErrorLiteral */, upper2, start);
554
+ return makeToken(30 /* ErrorLiteral */, upper2, start);
550
555
  };
551
556
  readQuotedSheetRef_fn = function() {
552
557
  const start = __privateWrapper(this, _pos)._++;
@@ -609,10 +614,8 @@ readIdentifier_fn = function() {
609
614
  } else break;
610
615
  }
611
616
  let upper2 = raw.toUpperCase();
612
- if (upper2.startsWith("_XLFN.")) {
613
- raw = raw.slice(6);
614
- upper2 = upper2.slice(6);
615
- }
617
+ raw = __privateMethod(this, _FormulaLexer_instances, stripReservedPrefixes_fn).call(this, raw, upper2);
618
+ if (raw.length !== upper2.length) upper2 = upper2.slice(upper2.length - raw.length);
616
619
  if (upper2 === "TRUE") return makeToken(2 /* Boolean */, "TRUE", start);
617
620
  if (upper2 === "FALSE") return makeToken(2 /* Boolean */, "FALSE", start);
618
621
  if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "!") {
@@ -634,6 +637,30 @@ readIdentifier_fn = function() {
634
637
  }
635
638
  return makeToken(23 /* NamedRange */, raw, start);
636
639
  };
640
+ /**
641
+ * Drops Excel's storage-level name decorations. They carry no semantics — the
642
+ * writer stamps them so an older reader fails loudly instead of silently
643
+ * mis-evaluating — and they *stack*: `_xlfn._xlws.FILTER` wears two. Stripping
644
+ * one prefix once, as this used to, leaves `_XLWS.FILTER` unresolvable.
645
+ *
646
+ * Every identifier in every formula passes through here, so the common case is
647
+ * gated on a single char-code test: a name not starting with '_' returns
648
+ * untouched without so much as a comparison against the prefix table.
649
+ */
650
+ stripReservedPrefixes_fn = function(raw, upper2) {
651
+ if (raw.charCodeAt(0) !== UNDERSCORE) return raw;
652
+ let cut = 0;
653
+ outer: for (; ; ) {
654
+ for (const prefix of RESERVED_PREFIXES) {
655
+ if (upper2.startsWith(prefix, cut)) {
656
+ cut += prefix.length;
657
+ continue outer;
658
+ }
659
+ }
660
+ break;
661
+ }
662
+ return cut > 0 && cut < raw.length ? raw.slice(cut) : raw;
663
+ };
637
664
  readCellRefChars_fn = function() {
638
665
  let ref = "";
639
666
  while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
@@ -677,6 +704,14 @@ readOperator_fn = function() {
677
704
  return makeToken(26 /* AtSign */, "@", start);
678
705
  case "!":
679
706
  return makeToken(25 /* Exclamation */, "!", start);
707
+ case "{":
708
+ return makeToken(27 /* LeftBrace */, "{", start);
709
+ case "}":
710
+ return makeToken(28 /* RightBrace */, "}", start);
711
+ // Inside an array constant ';' separates rows. Excel always stores ',' as
712
+ // the argument separator regardless of locale, so ';' has no other role.
713
+ case ";":
714
+ return makeToken(29 /* Semicolon */, ";", start);
680
715
  case "<":
681
716
  if (__privateGet(this, _formula)[__privateGet(this, _pos)] === ">") {
682
717
  __privateWrapper(this, _pos)._++;
@@ -759,6 +794,19 @@ var ErrorNode = class extends FormulaNode {
759
794
  return this.errorValue;
760
795
  }
761
796
  };
797
+ var _value;
798
+ var ArrayLiteralNode = class extends FormulaNode {
799
+ constructor(array) {
800
+ super();
801
+ this.array = array;
802
+ __privateAdd(this, _value);
803
+ __privateSet(this, _value, FormulaValue.array(array));
804
+ }
805
+ evaluate() {
806
+ return __privateGet(this, _value);
807
+ }
808
+ };
809
+ _value = new WeakMap();
762
810
  var BlankNode = class extends FormulaNode {
763
811
  evaluate() {
764
812
  return FormulaValue.blank;
@@ -1036,7 +1084,7 @@ var CallNode = class extends FormulaNode {
1036
1084
  };
1037
1085
 
1038
1086
  // src/formula-parser.ts
1039
- var _tokens, _pos2, _FormulaParser_instances, current_get, advance_fn, expect_fn, parseComparison_fn, parseConcatenation_fn, parseAddSub_fn, parseMulDiv_fn, parseUnary_fn, parsePower_fn, parsePercent_fn, parsePostfix_fn, parsePrimary_fn, tryParseFullRange_fn, parseFunction_fn, parseArgList_fn;
1087
+ var _tokens, _pos2, _FormulaParser_instances, current_get, advance_fn, expect_fn, parseComparison_fn, parseConcatenation_fn, parseAddSub_fn, parseMulDiv_fn, parseUnary_fn, parsePower_fn, parsePercent_fn, parsePostfix_fn, parsePrimary_fn, tryParseFullRange_fn, parseArrayLiteral_fn, parseArrayElement_fn, parseFunction_fn, parseArgList_fn;
1040
1088
  var FormulaParser = class {
1041
1089
  constructor(tokens) {
1042
1090
  __privateAdd(this, _FormulaParser_instances);
@@ -1047,7 +1095,7 @@ var FormulaParser = class {
1047
1095
  parse() {
1048
1096
  __privateSet(this, _pos2, 0);
1049
1097
  const node = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
1050
- if (__privateGet(this, _FormulaParser_instances, current_get).type !== 28 /* EOF */) {
1098
+ if (__privateGet(this, _FormulaParser_instances, current_get).type !== 31 /* EOF */) {
1051
1099
  throw new FormulaException(
1052
1100
  `Unexpected token '${__privateGet(this, _FormulaParser_instances, current_get).value}' at position ${__privateGet(this, _FormulaParser_instances, current_get).position}.`
1053
1101
  );
@@ -1059,7 +1107,7 @@ _tokens = new WeakMap();
1059
1107
  _pos2 = new WeakMap();
1060
1108
  _FormulaParser_instances = new WeakSet();
1061
1109
  current_get = function() {
1062
- return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 28 /* EOF */, value: "", position: -1 };
1110
+ return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 31 /* EOF */, value: "", position: -1 };
1063
1111
  };
1064
1112
  advance_fn = function() {
1065
1113
  const t = __privateGet(this, _FormulaParser_instances, current_get);
@@ -1174,7 +1222,7 @@ parsePrimary_fn = function() {
1174
1222
  case 2 /* Boolean */:
1175
1223
  __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1176
1224
  return new BooleanNode(token.value === "TRUE");
1177
- case 27 /* ErrorLiteral */:
1225
+ case 30 /* ErrorLiteral */:
1178
1226
  __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1179
1227
  return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
1180
1228
  case 3 /* CellReference */: {
@@ -1213,6 +1261,8 @@ parsePrimary_fn = function() {
1213
1261
  return new SpillReferenceNode(token.value);
1214
1262
  case 5 /* Function */:
1215
1263
  return __privateMethod(this, _FormulaParser_instances, parseFunction_fn).call(this);
1264
+ case 27 /* LeftBrace */:
1265
+ return __privateMethod(this, _FormulaParser_instances, parseArrayLiteral_fn).call(this);
1216
1266
  case 6 /* LeftParen */: {
1217
1267
  __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1218
1268
  const expr = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
@@ -1247,6 +1297,79 @@ tryParseFullRange_fn = function(startText, sheetName) {
1247
1297
  const hi = Math.max(start.index, end.index);
1248
1298
  return start.kind === "col" ? new FullRangeReferenceNode(lo, hi, null, null, sheetName) : new FullRangeReferenceNode(null, null, lo, hi, sheetName);
1249
1299
  };
1300
+ /**
1301
+ * Parses an array constant: `{1,2;3,4}` — ',' separates columns, ';' rows.
1302
+ *
1303
+ * Excel permits only literals inside one (no references, no calls, not even
1304
+ * arithmetic beyond a leading sign), so the value is fully determined here and
1305
+ * is materialised once into the node instead of being rebuilt on every eval.
1306
+ *
1307
+ * A ragged constant is rejected rather than padded: Excel refuses to accept
1308
+ * one at entry, so a file cannot legitimately contain it, and silently filling
1309
+ * the gaps would invent data the author never wrote.
1310
+ */
1311
+ parseArrayLiteral_fn = function() {
1312
+ const open = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1313
+ const rows2 = [];
1314
+ let row2 = [];
1315
+ for (; ; ) {
1316
+ row2.push(__privateMethod(this, _FormulaParser_instances, parseArrayElement_fn).call(this));
1317
+ if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
1318
+ __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1319
+ continue;
1320
+ }
1321
+ if (__privateGet(this, _FormulaParser_instances, current_get).type === 29 /* Semicolon */) {
1322
+ __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1323
+ rows2.push(row2);
1324
+ row2 = [];
1325
+ continue;
1326
+ }
1327
+ break;
1328
+ }
1329
+ rows2.push(row2);
1330
+ __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 28 /* RightBrace */);
1331
+ const cols = rows2[0].length;
1332
+ if (rows2.some((r) => r.length !== cols)) {
1333
+ throw new FormulaException(
1334
+ `Array constant at position ${open.position} has rows of differing lengths.`
1335
+ );
1336
+ }
1337
+ const array = new ArrayValue(rows2.length, cols);
1338
+ for (let r = 0; r < rows2.length; r++) {
1339
+ for (let c = 0; c < cols; c++) array.set(r, c, rows2[r][c]);
1340
+ }
1341
+ return new ArrayLiteralNode(array);
1342
+ };
1343
+ /** One element of an array constant: a literal, optionally signed. */
1344
+ parseArrayElement_fn = function() {
1345
+ let negate = false;
1346
+ let signed = false;
1347
+ while (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */ || __privateGet(this, _FormulaParser_instances, current_get).type === 9 /* Plus */) {
1348
+ if (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */) negate = !negate;
1349
+ signed = true;
1350
+ __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1351
+ }
1352
+ const token = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1353
+ switch (token.type) {
1354
+ case 0 /* Number */: {
1355
+ const n = parseFloat(token.value);
1356
+ if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
1357
+ return FormulaValue.number(negate ? -n : n);
1358
+ }
1359
+ case 1 /* String */:
1360
+ if (signed) break;
1361
+ return FormulaValue.text(token.value);
1362
+ case 2 /* Boolean */:
1363
+ if (signed) break;
1364
+ return FormulaValue.boolean(token.value === "TRUE");
1365
+ case 30 /* ErrorLiteral */:
1366
+ if (signed) break;
1367
+ return FormulaValue.error(FormulaValue.errorFromString(token.value));
1368
+ }
1369
+ throw new FormulaException(
1370
+ `Array constants accept only literal values; got '${token.value}' at position ${token.position}.`
1371
+ );
1372
+ };
1250
1373
  parseFunction_fn = function() {
1251
1374
  const nameToken = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1252
1375
  __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 6 /* LeftParen */);
@@ -4063,22 +4186,46 @@ function unique(a, c) {
4063
4186
  const out = fromRows(result);
4064
4187
  return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
4065
4188
  }
4189
+ var numVector = (a, c, i, dflt) => {
4190
+ if (i >= a.length) return { ok: true, v: [dflt] };
4191
+ const val = a[i].evaluate(c);
4192
+ if (val.isError) return { ok: false, e: val };
4193
+ const out = [];
4194
+ for (const item of val.isArray ? val.arrayVal.values() : [val]) {
4195
+ if (item.isBlank) continue;
4196
+ const d = item.tryAsDouble();
4197
+ if (!d.ok) return { ok: false, e: FormulaValue.errorValue };
4198
+ out.push(d.value);
4199
+ }
4200
+ return out.length > 0 ? { ok: true, v: out } : { ok: true, v: [dflt] };
4201
+ };
4066
4202
  function sort(a, c) {
4067
4203
  const v = a[0].evaluate(c);
4068
4204
  if (v.isError) return v;
4069
- const sortIndex = num4(a, c, 1, 1);
4070
- if (!sortIndex.ok) return sortIndex.e;
4071
- const sortOrder = num4(a, c, 2, 1);
4072
- if (!sortOrder.ok) return sortOrder.e;
4205
+ const indices = numVector(a, c, 1, 1);
4206
+ if (!indices.ok) return indices.e;
4207
+ const orders = numVector(a, c, 2, 1);
4208
+ if (!orders.ok) return orders.e;
4073
4209
  const byCol2 = a.length > 3 ? a[3].evaluate(c).coerceToBool().booleanValue : false;
4210
+ if (orders.v.length !== 1 && orders.v.length !== indices.v.length) {
4211
+ return FormulaValue.errorValue;
4212
+ }
4074
4213
  const base = byCol2 ? asArray2(v).transpose() : asArray2(v);
4075
4214
  const rows2 = toRows(base);
4076
- const idx = Math.trunc(sortIndex.n) - 1;
4077
- const dir = sortOrder.n < 0 ? -1 : 1;
4078
- if (rows2.length > 0 && (idx < 0 || idx >= rows2[0].length)) return FormulaValue.errorValue;
4215
+ const keys = indices.v.map((n, i) => ({
4216
+ col: Math.trunc(n) - 1,
4217
+ dir: (orders.v.length === 1 ? orders.v[0] : orders.v[i]) < 0 ? -1 : 1
4218
+ }));
4219
+ const width = rows2.length > 0 ? rows2[0].length : 0;
4220
+ if (rows2.length > 0 && keys.some((k) => k.col < 0 || k.col >= width)) {
4221
+ return FormulaValue.errorValue;
4222
+ }
4079
4223
  const sorted = rows2.map((r, i) => ({ r, i })).sort((x, y) => {
4080
- const cmp = FormulaValue.compare(x.r[idx], y.r[idx]) * dir;
4081
- return cmp !== 0 ? cmp : x.i - y.i;
4224
+ for (const k of keys) {
4225
+ const cmp = FormulaValue.compare(x.r[k.col], y.r[k.col]) * k.dir;
4226
+ if (cmp !== 0) return cmp;
4227
+ }
4228
+ return x.i - y.i;
4082
4229
  }).map((o) => o.r);
4083
4230
  const out = fromRows(sorted);
4084
4231
  return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;