@xeplr/expression-handler 2.1.0 → 2.2.0

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/README.md CHANGED
@@ -171,7 +171,7 @@ const f = xf.compile('sumif(fytd(order_date), revenue)', {
171
171
  | `fiscalYearStart` | `4` (April) | `fytd` and any financial-year function |
172
172
  | `today` | now | `ytd`, `fytd` — pin it for tests and scheduled runs |
173
173
  | `missing` | `'null'` | `'error'` makes a name the row lacks an error |
174
- | `ignoreCase` | `false` | `true` makes `=`, `!=`, `in` and the text operators (`contains`, `startsWith`, `endsWith`, `notIn`, …) ignore case. Numbers and null compare exactly as before. BI's report formulas set it, so a formula and the SQL it is translated into agree whatever the database's collation; Workflow's conditions leave it off |
174
+ | `ignoreCase` | `false` | `true` makes `=`, `!=`, `in`, the text operators (`contains`, `startsWith`, `endsWith`, `notIn`, …) and the functions `contains()`, `startsWith()`, `endsWith()` ignore case. Numbers and null compare exactly as before. BI's report formulas set it, so a formula and the SQL it is translated into agree whatever the database's collation; Workflow's conditions leave it off |
175
175
  | `maxListItems` · `maxDistinct` · `maxSteps` · `maxLevels` | see [Limits](#limits) | |
176
176
  | `aggregate(name, args, { compile, level })` | none | supplies the rows for the group form of a total (BI) |
177
177
 
package/lib/compile.js CHANGED
@@ -50,7 +50,7 @@ function compileTree(tree, options) {
50
50
  var fy = Number(opts.fiscalYearStart);
51
51
  if (!(fy >= 1 && fy <= 12 && Math.floor(fy) === fy)) fail('fiscalYearStart must be a month, 1–12');
52
52
 
53
- var ctx = { now: dates.todayFor(opts), fiscalYearStart: fy };
53
+ var ctx = { now: dates.todayFor(opts), fiscalYearStart: fy, ignoreCase: Boolean(opts.ignoreCase) };
54
54
  // Equality for `=`, `in` and the word operators: exact, or folded when the
55
55
  // caller asked (opts.ignoreCase) — see operators.IGNORE_CASE.
56
56
  var eq = opts.ignoreCase ? operators.looseEqIgnoreCase : operators.looseEq;
package/lib/functions.js CHANGED
@@ -28,6 +28,9 @@ var asDate = dates.asDate;
28
28
 
29
29
  var CATEGORIES = ['Math', 'Text', 'Date', 'Logic', 'Aggregate'];
30
30
 
31
+ // Text for a comparison: lower-cased when the formula ignores case.
32
+ function fold(c, v) { var s = str(v); return c && c.ignoreCase ? s.toLowerCase() : s; }
33
+
31
34
  function str(v) { return v === null || v === undefined ? '' : String(v); }
32
35
  function num(v) { var n = Number(v); return isNaN(n) ? null : n; }
33
36
  function isEmpty(v) { return v === null || v === undefined || v === ''; }
@@ -121,15 +124,17 @@ var LIST = [
121
124
  { name: 'replace', category: 'Text', args: 'text, find, replace with', min: 3, max: 3,
122
125
  description: 'Swap one piece of text for another', example: 'replace(phone, "-", "")',
123
126
  fn: function (v, find, into) { return str(v).split(str(find)).join(str(into)); } },
124
- { name: 'contains', category: 'Text', args: 'text, find', min: 2, max: 2,
127
+ // context: the compile option ignoreCase folds both sides, as it does for
128
+ // = and in — one rule for every text comparison in a formula.
129
+ { name: 'contains', category: 'Text', args: 'text, find', min: 2, max: 2, context: true,
125
130
  description: 'True when the text contains it', example: 'contains(notes, "urgent")',
126
- fn: function (v, sub) { return str(v).indexOf(str(sub)) !== -1; } },
127
- { name: 'startsWith', category: 'Text', args: 'text, find', min: 2, max: 2,
131
+ fn: function (c, v, sub) { return fold(c, v).indexOf(fold(c, sub)) !== -1; } },
132
+ { name: 'startsWith', category: 'Text', args: 'text, find', min: 2, max: 2, context: true,
128
133
  description: 'True when the text begins with it', example: 'startsWith(sku, "IN")',
129
- fn: function (v, sub) { return str(v).startsWith(str(sub)); } },
130
- { name: 'endsWith', category: 'Text', args: 'text, find', min: 2, max: 2,
134
+ fn: function (c, v, sub) { return fold(c, v).startsWith(fold(c, sub)); } },
135
+ { name: 'endsWith', category: 'Text', args: 'text, find', min: 2, max: 2, context: true,
131
136
  description: 'True when the text ends with it', example: 'endsWith(file, ".pdf")',
132
- fn: function (v, sub) { return str(v).endsWith(str(sub)); } },
137
+ fn: function (c, v, sub) { return fold(c, v).endsWith(fold(c, sub)); } },
133
138
  { name: 'length', category: 'Text', args: 'text', min: 1, max: 1,
134
139
  description: 'How many characters', example: 'length(name)',
135
140
  fn: function (v) { return Array.isArray(v) ? v.length : str(v).length; } },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeplr/expression-handler",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "The one formula engine for xeplr: its own parser (no eval, no dependencies), one function list, compiled-once formulas, totals over lists, AND/OR conditions. Runs in Node and the browser.",
5
5
  "main": "index.js",
6
6
  "files": [