diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 8b1378917..809ed9467 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1 +1 @@ - +- ADD: add locale string number jexl function (#1737) diff --git a/doc/api.md b/doc/api.md index 0040aaf2a..0944a698c 100644 --- a/doc/api.md +++ b/doc/api.md @@ -279,12 +279,16 @@ IOTAgent, you can create it in advance using the Context Broker ## Entity creation when `cmdMode` is `notification` -Even when an entity should not be created (see [above section](#device-autoprovision-and-entity-creation)), when the device uses `cmdMode` set to `notification` an entity is created. In particular: +Even when an entity should not be created (see [above section](#device-autoprovision-and-entity-creation)), when the +device uses `cmdMode` set to `notification` an entity is created. In particular: -* An entity is created at device provision time -* That entity is created with an attribute corresponding to each commands. The value of that attribute at creation time is `null` (specifying in some way that the command has not been triggered yet). Note that if the attribute doesn't have any command, the entity is not created (even if `cmdMode` is `notification`). +- An entity is created at device provision time +- That entity is created with an attribute corresponding to each commands. The value of that attribute at creation + time is `null` (specifying in some way that the command has not been triggered yet). Note that if the attribute + doesn't have any command, the entity is not created (even if `cmdMode` is `notification`). -For instance, if device has commands `ping` and `switch` the entity corresponding to that device will be created at provising time with the following attributes: +For instance, if device has commands `ping` and `switch` the entity corresponding to that device will be created at +provising time with the following attributes: ``` ... @@ -300,7 +304,8 @@ For instance, if device has commands `ping` and `switch` the entity correspondin } ``` -**NOTE:** `command` is the usual type for attributes associated to commands, but the one used at provisioning time (`"command": [ ...]` field) will be actually used. +**NOTE:** `command` is the usual type for attributes associated to commands, but the one used at provisioning time +(`"command": [ ...]` field) will be actually used. ## Entity Name expression support @@ -708,52 +713,53 @@ to incorporate new transformations from the IoT Agent configuration file in a fa Current common transformation set: -| JEXL Transformation | Equivalent JavaScript Function | -| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | -| jsonparse: (str) | `JSON.parse(str);` | -| jsonstringify: (obj) | `JSON.stringify(obj);` | -| indexOf: (val, char) | `String(val).indexOf(char);` | -| length: (val) | `String(val).length;` | -| trim: (val) | `String(val).trim();` | -| substr: (val, int1, int2) | `String(val).substr(int1, int2);` | -| addreduce: (arr) | arr.reduce((i, v) | i + v)); | -| lengtharray: (arr) | `arr.length;` | -| typeof: (val) | `typeof val;` | -| isarray: (arr) | `Array.isArray(arr);` | -| isnan: (val) | `isNaN(val);` | -| parseint: (val) | `parseInt(val);` | -| parsefloat: (val) | `parseFloat(val);` | -| toisodate: (val) | `new Date(val).toISOString();` | -| timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` | -| tostring: (val) | `val.toString();` | -| urlencode: (val) | `encodeURI(val);` | -| urldecode: (val) | `decodeURI(val);` | -| replacestr: (str, from, to) | `str.replace(from, to);` | -| replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` | -| replaceallstr: (str, from, to) | `str.replaceAll(from, to);` | -| replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` | -| split: (str, ch) | `str.split(ch);` | -| joinarrtostr: (arr, ch) | `arr.join(ch);` | -| concatarr: (arr, arr2) | `arr.concat(arr2);` | -| mapper: (val, values, choices) | choices[values.findIndex((target) | target == val)]); | -| thmapper: (val, values, choices) | choices[values.reduce((acc,curr,i,arr) | (acc==0)||acc?acc:val<=curr?acc=i:acc=null,null)]; | -| bitwisemask: (i,mask,op,shf) | (op==="&"?parseInt(i)&mask: op==="|"?parseInt(i)|mask: op==="^"?parseInt(i)^mask:i)>>shf; | -| slice: (arr, init, end) | `arr.slice(init,end);` | -| addset: (arr, x) | { return Array.from((new Set(arr)).add(x)) } | -| removeset: (arr, x) | { let s = new Set(arr); s.delete(x); return Array.from(s) } | -| touppercase: (val) | `String(val).toUpperCase()` | -| tolowercase: (val) | `String(val).toLowerCase()` | -| round: (val) | `Math.round(val)` | -| floor: (val) | `Math.floor(val)` | -| ceil: (val) | `Math.ceil(val)` | -| tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` | -| gettime: (d) | `new Date(d).getTime()` | -| toisostring: (d) | `new Date(d).toISOString()` | -| localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` | -| now: () | `Date.now()` | -| hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` | -| valuePicker: (val,pick) | valuePicker: (val,pick) => Object.entries(val).filter(([_, v]) => v === pick).map(([k, _]) => k) | -| valuePickerMulti: (val,pick) | valuePickerMulti: (val,pick) => Object.entries(val).filter(([_, v]) => pick.includes(v)).map(([k, _]) => k) | +| JEXL Transformation | Equivalent JavaScript Function | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| jsonparse: (str) | `JSON.parse(str);` | +| jsonstringify: (obj) | `JSON.stringify(obj);` | +| indexOf: (val, char) | `String(val).indexOf(char);` | +| length: (val) | `String(val).length;` | +| trim: (val) | `String(val).trim();` | +| substr: (val, int1, int2) | `String(val).substr(int1, int2);` | +| addreduce: (arr) | arr.reduce((i, v) | i + v)); | +| lengtharray: (arr) | `arr.length;` | +| typeof: (val) | `typeof val;` | +| isarray: (arr) | `Array.isArray(arr);` | +| isnan: (val) | `isNaN(val);` | +| parseint: (val) | `parseInt(val);` | +| parsefloat: (val) | `parseFloat(val);` | +| toisodate: (val) | `new Date(val).toISOString();` | +| timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` | +| tostring: (val) | `val.toString();` | +| urlencode: (val) | `encodeURI(val);` | +| urldecode: (val) | `decodeURI(val);` | +| replacestr: (str, from, to) | `str.replace(from, to);` | +| replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` | +| replaceallstr: (str, from, to) | `str.replaceAll(from, to);` | +| replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` | +| split: (str, ch) | `str.split(ch);` | +| joinarrtostr: (arr, ch) | `arr.join(ch);` | +| concatarr: (arr, arr2) | `arr.concat(arr2);` | +| mapper: (val, values, choices) | choices[values.findIndex((target) | target == val)]); | +| thmapper: (val, values, choices) | choices[values.reduce((acc,curr,i,arr) | (acc==0)||acc?acc:val<=curr?acc=i:acc=null,null)]; | +| bitwisemask: (i,mask,op,shf) | (op==="&"?parseInt(i)&mask: op==="|"?parseInt(i)|mask: op==="^"?parseInt(i)^mask:i)>>shf; | +| slice: (arr, init, end) | `arr.slice(init,end);` | +| addset: (arr, x) | { return Array.from((new Set(arr)).add(x)) } | +| removeset: (arr, x) | { let s = new Set(arr); s.delete(x); return Array.from(s) } | +| touppercase: (val) | `String(val).toUpperCase()` | +| tolowercase: (val) | `String(val).toLowerCase()` | +| round: (val) | `Math.round(val)` | +| floor: (val) | `Math.floor(val)` | +| ceil: (val) | `Math.ceil(val)` | +| tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` | +| gettime: (d) | `new Date(d).getTime()` | +| toisostring: (d) | `new Date(d).toISOString()` | +| localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` | +| localestringnumber: (n, locale, options) | `n.toLocaleStringNumber(locale, options)` | +| now: () | `Date.now()` | +| hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` | +| valuePicker: (val,pick) | valuePicker: (val,pick) => Object.entries(val).filter(([_, v]) => v === pick).map(([k, _]) => k) | +| valuePickerMulti: (val,pick) | valuePickerMulti: (val,pick) => Object.entries(val).filter(([_, v]) => pick.includes(v)).map(([k, _]) => k) | You have available this [JEXL interactive playground][99] with all the transformations already loaded, in which you can test all the functions described above. @@ -1349,7 +1355,8 @@ as `command` in the [config group](#config-group-datamodel) or in the [device pr attributes are created using `command` as attribute type. Also, you can define the protocol you want the commands to be sent (HTTP/MQTT) with the `transport` parameter at the provisioning process. -**NOTE**: in `advancedNotification` mode the command is not triggered updating an attribute, but creating a command execution entity. However, this mode has not been implemented yet so details are to be clarified. +**NOTE**: in `advancedNotification` mode the command is not triggered updating an attribute, but creating a command +execution entity. However, this mode has not been implemented yet so details are to be clarified. For a given device provisioned with a `ping` command defined, any update on this attribute "ping" at the NGSI entity in the Context Broker will send a command to your device. For instance, to send the `ping` command with value @@ -1812,7 +1819,7 @@ Config group is represented by a JSON object with the following fields: | `endpoint` | ✓ | `string` | | Endpoint where the group of device is going to receive commands, if any. | | `storeLastMeasure` | ✓ | `boolean` | | Store in device last measure received. See more info [in this section](admin.md#storelastmeasure). False by default | | `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default | -| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md). | +| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md). | ### Config group operations @@ -2036,7 +2043,7 @@ the API resource fields and the same fields in the database model. | `storeLastMeasure` | ✓ | `boolean` | | Store in device last measure received. Useful just for debugging purpose. See more info [in this section](admin.md#storelastmeasure). False by default. | | `lastMeasure` | ✓ | `object` | | last measure stored on device when `storeLastMeasure` is enabled. See more info [in this section](admin.md#storelastmeasure). This field can be cleared using `{}` in a device update request. In that case, `lastMeasure` is removed from device (until a next measure is received and `lastMesuare` gets created again). | | `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default. | -| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md). | +| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md). | ### Device operations @@ -2493,4 +2500,4 @@ updateEntityRequestsError 5 [18]: https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22value%22%20%3A%206%2C%0A%20%20%22ts%22%3A%201637245214901%2C%0A%20%22name%22%3A%20%22DevId629%22%2C%0A%20%22object%22%3A%7Bname%3A%20%22John%22%2C%20surname%3A%20%22Doe%22%7D%2C%0A%20%20%22array%22%3A%5B1%2C3%5D%0A%7D&input=name%7Csubstr(0%2Cname%7CindexOf(%22e%22)%2B1)&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%0A%7D [99]: - https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%20%7B%0A%20%20%20%20jsonparse%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.parse)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20jsonstringify%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.stringify)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v))(arr)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20Array.isArray%2C%0A%20%20%20%20isnan%3A%20isNaN%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber((val)%20%3D%3E%20parseInt(val%2C%2010))(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber(parseFloat)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20timeoffset%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTimezoneOffset())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20val.toString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20urlencode%3A%20encodeURI%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(decodeURI)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg%2C%20'g')%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20joinarrtostr%3A%20(arr%2C%20ch)%20%3D%3E%20arr.join(ch)%2C%0A%20%20%20%20concatarr%3A%20(arr%2C%20arr2)%20%3D%3E%20arr.concat(arr2)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5Bvalues.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20!%3D%3D%20null%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20i%20%3A%20null)%2C%20null)%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%2C%0A%20%20%20%20addset%3A%20(arr%2C%20x)%20%3D%3E%20Array.from(new%20Set(arr).add(x))%2C%0A%20%20%20%20removeset%3A%20(arr%2C%20x)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20s%20%3D%20new%20Set(arr)%3B%0A%20%20%20%20%20%20%20%20s.delete(x)%3B%0A%20%20%20%20%20%20%20%20return%20Array.from(s)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20touppercase%3A%20(val)%20%3D%3E%20String(val).toUpperCase()%2C%0A%20%20%20%20tolowercase%3A%20(val)%20%3D%3E%20String(val).toLowerCase()%2C%0A%20%20%20%20floor%3A%20Math.floor%2C%0A%20%20%20%20ceil%3A%20Math.ceil%2C%0A%20%20%20%20round%3A%20Math.round%2C%0A%20%20%20%20tofixed%3A%20(val%2C%20decimals)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20num%20%3D%20Number.parseFloat(val)%3B%0A%20%20%20%20%20%20%20%20const%20dec%20%3D%20Number.parseInt(decimals)%3B%0A%20%20%20%20%20%20%20%20return%20isNaN(num)%20%7C%7C%20isNaN(dec)%20%3F%20null%20%3A%20num.toFixed(dec)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20gettime%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTime())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20localestring%3A%20(d%2C%20timezone%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((d%2C%20timezone%2C%20options)%20%3D%3E%20new%20Date(d).toLocaleString(timezone%2C%20options))(d%2C%20timezone%2C%20options)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20now%3A%20Date.now%2C%0A%20%20%20%20hextostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(typeof%20val%20!%3D%3D%20'string'%20%7C%7C%20!%2F%5E%5B0-9a-fA-F%5D%2B%24%2F.test(val)%20%7C%7C%20val.length%20%25%202%20!%3D%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20new%20TextDecoder().decode(new%20Uint8Array(val.match(%2F.%7B1%2C2%7D%2Fg).map((byte)%20%3D%3E%20parseInt(byte%2C%2016))))%3B%0A%20%20%20%20%20%20%20%20%7D)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20valuePicker%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20v%20%3D%3D%3D%20pick)%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%2C%0A%20%20%20%20valuePickerMulti%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20pick.includes(v))%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%0A%7D + https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%20%7B%0A%20%20%20%20jsonparse%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.parse)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20jsonstringify%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.stringify)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v))(arr)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20Array.isArray%2C%0A%20%20%20%20isnan%3A%20isNaN%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber((val)%20%3D%3E%20parseInt(val%2C%2010))(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber(parseFloat)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20timeoffset%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTimezoneOffset())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20val.toString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20urlencode%3A%20encodeURI%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(decodeURI)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg%2C%20'g')%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20joinarrtostr%3A%20(arr%2C%20ch)%20%3D%3E%20arr.join(ch)%2C%0A%20%20%20%20concatarr%3A%20(arr%2C%20arr2)%20%3D%3E%20arr.concat(arr2)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5Bvalues.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20!%3D%3D%20null%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20i%20%3A%20null)%2C%20null)%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%2C%0A%20%20%20%20addset%3A%20(arr%2C%20x)%20%3D%3E%20Array.from(new%20Set(arr).add(x))%2C%0A%20%20%20%20removeset%3A%20(arr%2C%20x)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20s%20%3D%20new%20Set(arr)%3B%0A%20%20%20%20%20%20%20%20s.delete(x)%3B%0A%20%20%20%20%20%20%20%20return%20Array.from(s)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20touppercase%3A%20(val)%20%3D%3E%20String(val).toUpperCase()%2C%0A%20%20%20%20tolowercase%3A%20(val)%20%3D%3E%20String(val).toLowerCase()%2C%0A%20%20%20%20floor%3A%20Math.floor%2C%0A%20%20%20%20ceil%3A%20Math.ceil%2C%0A%20%20%20%20round%3A%20Math.round%2C%0A%20%20%20%20tofixed%3A%20(val%2C%20decimals)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20num%20%3D%20Number.parseFloat(val)%3B%0A%20%20%20%20%20%20%20%20const%20dec%20%3D%20Number.parseInt(decimals)%3B%0A%20%20%20%20%20%20%20%20return%20isNaN(num)%20%7C%7C%20isNaN(dec)%20%3F%20null%20%3A%20num.toFixed(dec)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20gettime%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTime())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20localestring%3A%20(d%2C%20timezone%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((d%2C%20timezone%2C%20options)%20%3D%3E%20new%20Date(d).toLocaleString(timezone%2C%20options))(d%2C%20timezone%2C%20options)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20localestringnumber%3A%20(n%2C%20locale%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%0A%20%20%20%20%20%20%20%20%20%20%20%20(fn)%20%3D%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((n%2C%20locale%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(typeof%20n%20!%3D%3D%20'number'%20%7C%7C%20isNaN(n))%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20n.toLocaleString(locale%2C%20options)%3B%0A%20%20%20%20%20%20%20%20%7D)(n%2C%20locale%2C%20options)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20now%3A%20Date.now%2C%0A%20%20%20%20hextostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(typeof%20val%20!%3D%3D%20'string'%20%7C%7C%20!%2F%5E%5B0-9a-fA-F%5D%2B%24%2F.test(val)%20%7C%7C%20val.length%20%25%202%20!%3D%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20new%20TextDecoder().decode(new%20Uint8Array(val.match(%2F.%7B1%2C2%7D%2Fg).map((byte)%20%3D%3E%20parseInt(byte%2C%2016))))%3B%0A%20%20%20%20%20%20%20%20%7D)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20valuePicker%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20v%20%3D%3D%3D%20pick)%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%2C%0A%20%20%20%20valuePickerMulti%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20pick.includes(v))%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%0A%7D diff --git a/lib/jexlTranformsMap.js b/lib/jexlTranformsMap.js index 85a2a00ab..dfde74527 100644 --- a/lib/jexlTranformsMap.js +++ b/lib/jexlTranformsMap.js @@ -204,6 +204,21 @@ const map = { options ); }, + localestringnumber: (n, locale, options) => { + const safeOperation = + (fn) => + (...args) => { + try { + return fn(...args); + } catch (e) { + return null; + } + }; + return safeOperation((n, locale, options) => { + if (typeof n !== 'number' || isNaN(n)) return null; + return n.toLocaleString(locale, options); + })(n, locale, options); + }, now: Date.now, hextostring: (val) => { const safeOperation = diff --git a/test/unit/expressions/jexlExpression-test.js b/test/unit/expressions/jexlExpression-test.js index 79ec7582b..bb7078506 100644 --- a/test/unit/expressions/jexlExpression-test.js +++ b/test/unit/expressions/jexlExpression-test.js @@ -398,6 +398,36 @@ describe('Jexl expression interpreter', function () { }); }); + describe('When number localization functions are executed', function () { + it('should return the localized string representation of the number', function (done) { + const scope = { + theNumber: 1234567.89, + locale: 'es-ES', + options: { minimumFractionDigits: 2, maximumFractionDigits: 2 } + }; + + expressionParser.parse('theNumber|localestringnumber(locale, options)', scope, function (error, result) { + should.not.exist(error); + result.should.equal('1.234.567,89'); + done(); + }); + }); + + it('should return null for invalid number input', function (done) { + const scope = { + theNumber: 'not_a_number', + locale: 'en-US', + options: {} + }; + + expressionParser.parse('theNumber|localestringnumber(locale, options)', scope, function (error, result) { + should.not.exist(error); + should.equal(result, null); + done(); + }); + }); + }); + // Check errors describe('When invalid inputs are used', function () { describe('When an invalid JSON string is parsed', function () {