237 lines
8.1 KiB
Markdown
237 lines
8.1 KiB
Markdown
|
# [Android Components](../../../README.md) > Libraries > JEXL
|
||
|
|
||
|
Javascript Expression Language: Powerful context-based expression parser and evaluator.
|
||
|
|
||
|
This implementation is based on [Mozjexl](https://github.com/mozilla/mozjexl), a fork of Jexl (designed and created at TechnologyAdvice) for use at Mozilla, specifically as a part of SHIELD and Normandy.
|
||
|
|
||
|
Features not supported yet:
|
||
|
|
||
|
* JavaScript object properties (e.g. [String.length](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length))
|
||
|
* Adding custom operators (binary/unary)
|
||
|
|
||
|
Other implementations:
|
||
|
|
||
|
* [JavaScript](https://github.com/mozilla/mozjexl)
|
||
|
* [Python](https://github.com/mozilla/pyjexl)
|
||
|
|
||
|
## Usage
|
||
|
|
||
|
### Setting up the dependency
|
||
|
|
||
|
Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)):
|
||
|
|
||
|
```Groovy
|
||
|
implementation "org.mozilla.components:jexl:{latest-version}
|
||
|
```
|
||
|
|
||
|
### Evaluating expressions
|
||
|
|
||
|
```Kotlin
|
||
|
val jexl = Jexl()
|
||
|
|
||
|
val result = jexl.evaluate("75 > 42")
|
||
|
|
||
|
// evaluate() returns an object of type JexlValue. Calling toKotlin() converts this
|
||
|
// into a matching Kotlin type (in this case a Boolean).
|
||
|
println(result.value) // Prints "true"
|
||
|
```
|
||
|
|
||
|
Often expressions should return a `Boolean`value. In this case `evaluateBooleanExpression` is a helper that always returns a Kotlin `Boolean` and never throws an exception (Returns false).
|
||
|
|
||
|
```Kotlin
|
||
|
val jexl = Jexl()
|
||
|
|
||
|
// "result" has type Boolean and value "true"
|
||
|
val result = jexl.evaluateBooleanExpression("42 + 23 > 50", defaultValue = false)
|
||
|
```
|
||
|
|
||
|
|
||
|
### Unary Operators
|
||
|
|
||
|
| Operation | Symbol |
|
||
|
|-----------|:------:|
|
||
|
| Negate | ! |
|
||
|
|
||
|
### Binary Operators
|
||
|
|
||
|
| Operation | Symbol |
|
||
|
|------------------|:----------------:|
|
||
|
| Add, Concat | + |
|
||
|
| Subtract | - |
|
||
|
| Multiply | * |
|
||
|
| Divide | / |
|
||
|
| Divide and floor | // |
|
||
|
| Modulus | % |
|
||
|
| Power of | ^ |
|
||
|
| Logical AND | && |
|
||
|
| Logical OR | || |
|
||
|
|
||
|
### Comparison
|
||
|
|
||
|
| Comparison | Symbol |
|
||
|
|----------------------------|:------:|
|
||
|
| Equal | == |
|
||
|
| Not equal | != |
|
||
|
| Greater than | > |
|
||
|
| Greater than or equal | >= |
|
||
|
| Less than | < |
|
||
|
| Less than or equal | <= |
|
||
|
| Element in array or string | in |
|
||
|
|
||
|
### Ternary operator
|
||
|
|
||
|
Conditional expressions check to see if the first segment evaluates to a truthy
|
||
|
value. If so, the consequent segment is evaluated. Otherwise, the alternate
|
||
|
is. If the consequent section is missing, the test result itself will be used
|
||
|
instead.
|
||
|
|
||
|
| Expression | Result |
|
||
|
|-------------------------------------|--------|
|
||
|
| `"" ? "Full" : "Empty"` | Empty |
|
||
|
| `"foo" in "foobar" ? "Yes" : "No"` | Yes |
|
||
|
| `{agent: "Archer"}.agent ?: "Kane"` | Archer |
|
||
|
|
||
|
### Native Types
|
||
|
|
||
|
| Type | Examples |
|
||
|
|-----------|:------------------------------:|
|
||
|
| Booleans | `true`, `false` |
|
||
|
| Strings | "Hello \"user\"", 'Hey there!' |
|
||
|
| Integers | 6, -7, 5, -3 |
|
||
|
| Doubles | -7.2, -3.14159 |
|
||
|
| Objects | {hello: "world!"} |
|
||
|
| Arrays | ['hello', 'world!'] |
|
||
|
| Undefined | `undefined` |
|
||
|
|
||
|
The JavaScript implementation of Jexl uses a `Numeric` type. This implementation dynamically casts between `Integer` and `Double` as needed.
|
||
|
|
||
|
### Groups
|
||
|
|
||
|
Parentheses work just how you'd expect them to:
|
||
|
|
||
|
| Expression | Result |
|
||
|
|---------------------------------------|:-------|
|
||
|
| `(83 + 1) / 2` | 42 |
|
||
|
| `1 < 3 && (4 > 2 || 2 > 4)` | true |
|
||
|
|
||
|
### Identifiers
|
||
|
|
||
|
Access variables in the context object by just typing their name. Objects can
|
||
|
be traversed with dot notation, or by using brackets to traverse to a dynamic
|
||
|
property name.
|
||
|
|
||
|
Example context:
|
||
|
|
||
|
```javascript
|
||
|
{
|
||
|
name: {
|
||
|
first: "Malory",
|
||
|
last: "Archer"
|
||
|
},
|
||
|
exes: [
|
||
|
"Nikolai Jakov",
|
||
|
"Len Trexler",
|
||
|
"Burt Reynolds"
|
||
|
],
|
||
|
lastEx: 2
|
||
|
}
|
||
|
```
|
||
|
|
||
|
| Expression | Result |
|
||
|
|---------------------|---------------|
|
||
|
| `name.first` | Malory |
|
||
|
| `name['la' + 'st']` | Archer |
|
||
|
| `exes[2]` | Burt Reynolds |
|
||
|
| `exes[lastEx - 1]` | Len Trexler |
|
||
|
|
||
|
### Collections
|
||
|
|
||
|
Collections, or arrays of objects, can be filtered by including a filter
|
||
|
expression in brackets. Properties of each collection can be referenced by
|
||
|
prefixing them with a leading dot. The result will be an array of the objects
|
||
|
for which the filter expression resulted in a truthy value.
|
||
|
|
||
|
Example context:
|
||
|
|
||
|
```javascript
|
||
|
{
|
||
|
employees: [
|
||
|
{first: 'Sterling', last: 'Archer', age: 36},
|
||
|
{first: 'Malory', last: 'Archer', age: 75},
|
||
|
{first: 'Lana', last: 'Kane', age: 33},
|
||
|
{first: 'Cyril', last: 'Figgis', age: 45},
|
||
|
{first: 'Cheryl', last: 'Tunt', age: 28}
|
||
|
],
|
||
|
retireAge: 62
|
||
|
}
|
||
|
```
|
||
|
|
||
|
| Expression | Result |
|
||
|
|-------------------------------------------------|---------------------------------------------------------------------------------------|
|
||
|
| `employees[.first == 'Sterling']` | [{first: 'Sterling', last: 'Archer', age: 36}] |
|
||
|
| `employees[.last == 'Tu' + 'nt'].first` | Cheryl |
|
||
|
| `employees[.age >= 30 && .age < 40]` | [{first: 'Sterling', last: 'Archer', age: 36},{first: 'Lana', last: 'Kane', age: 33}] |
|
||
|
| `employees[.age >= 30 && .age < 40][.age < 35]` | [{first: 'Lana', last: 'Kane', age: 33}] |
|
||
|
| `employees[.age >= retireAge].first` | Malory |
|
||
|
|
||
|
### Transforms
|
||
|
|
||
|
The power of Jexl is in transforming data. Transform functions take one or more arguments: The value to be transformed, followed by anything else passed to it in the expression.
|
||
|
|
||
|
```Kotlin
|
||
|
val jexl = Jexl()
|
||
|
|
||
|
jexl.addTransform("split") { value, arguments ->
|
||
|
value.toString().split(arguments.first().toString()).toJexlArray()
|
||
|
}
|
||
|
|
||
|
jexl.addTransform("lower") { value, _ ->
|
||
|
value.toString().toLowerCase().toJexl()
|
||
|
}
|
||
|
|
||
|
jexl.addTransform("last") { value, _ ->
|
||
|
(value as JexlArray).values.last()
|
||
|
}
|
||
|
```
|
||
|
|
||
|
| Expression | Result |
|
||
|
|-------------------------------------------------|-----------------------|
|
||
|
| `"Pam Poovey"|lower|split(' ')|first` | poovey |
|
||
|
| `"password==guest"|split('=' + '=')` | ['password', 'guest'] |
|
||
|
|
||
|
### Context
|
||
|
|
||
|
Variable contexts are straightforward Objects that can be accessed
|
||
|
in the expression.
|
||
|
|
||
|
```Kotlin
|
||
|
val context = Context(
|
||
|
"employees" to JexlArray(
|
||
|
JexlObject(
|
||
|
"first" to "Sterling".toJexl(),
|
||
|
"last" to "Archer".toJexl(),
|
||
|
"age" to 36.toJexl()),
|
||
|
JexlObject(
|
||
|
"first" to "Malory".toJexl(),
|
||
|
"last" to "Archer".toJexl(),
|
||
|
"age" to 75.toJexl()),
|
||
|
JexlObject(
|
||
|
"first" to "Malory".toJexl(),
|
||
|
"last" to "Archer".toJexl(),
|
||
|
"age" to 33.toJexl())
|
||
|
)
|
||
|
)
|
||
|
```
|
||
|
|
||
|
| Expression | Result |
|
||
|
|-------------------------------------------------|------------------------------------------------------------------------------|
|
||
|
| `employees[.age >= 30 && .age < 40]` | [{first=Sterling, last=Archer, age=36}, {first=Malory, last=Archer, age=33}] |
|
||
|
| `employees[.age >= 30 && .age < 90][.age < 37]` | [{first=Malory, last=Archer, age=33}] |
|
||
|
|
||
|
|
||
|
## License
|
||
|
|
||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/
|