npm package: @sinoform/flow-control-expression
Description: 工作流路由控制公式解释器。
Latest version: 1.18.3

# flow-control-expression

工作流路由控制公式解释器。

## 安装

```shell
yarn add @sinoform/flow-control-expression
```

如果在 IE 11 中使用，需要安装 `Map` polyfill：

```yarn
yarn add core-js
```

在代码中添加：

```ts
import 'core-js/es/map';
```

## 快速用法

控制公式的语法借鉴 JavaScript 表达式语法，形如：

```js
流程发送人 in ["张三", "李四"] && 填报人 like "张%"
```

示例公式的语义是： 流程发送人是 `张三` 或者 `李四`，并且填报人姓张。

在程序中，我们可以通过公式解释器计算公式的结果：

```ts
import interpreter from '@sinoform/flow-route-control-exprssion';

// 构建执行上下文。可以使用 [`@sinoform/flow-route-control-exec-context`模块](https://github.com/sinoui/sino-intellisense-form/tree/master/packages/flow-route-control-exec-context)快速构建基于表单数据的执行上下文
const testData = {
  流程发送人: '张三',
  填报人: '李四',
};
const execContext = {
  get(name: string) {
    return testData[name];
  },
  in(value: string, elements: string[]) {
    return elements && elements.includes(value);
  },
};

const expression = '流程发送人 in ["张三", "李四"] && 填报人 like "张%"';
const result = interpreter(expression, execContext);
```

在表单上下文中计算公式：

```ts
import interpreter from '@sinoform/flow-route-control-exprssion';
import buildExecContext from '@sinoform/flow-route-control-exec-context';

const expression = '流程发送人 in ["张三", "李四"] && 填报人 like "张%"';
const execContext = buildExecContext(formData);
const result = interpreter(expression, execContext);
```

## 控制公式语法

控制公式的语法在遵循 JavaScript 表达式语法子集的基础上扩展了关系运算符 `in` 和 `like`。

对于下面的公式：

```java
价格 > 10
```

其中价格需要通过`execContext.get("价格")`的方式从执行上下文中获取到变量的值。如果获取到的价格是 `11`，则公式执行结果是 `true`；如果是 `7`，则公式执行结果是`false`。

在表单上下文中，类似“价格”这样的标识符往往表示表单字段的英文名称或者中文标题，或者表示流程信息的字段。先通过示例了解一下大致的用法，然后会列举出支持的比较关系运算符和流程信息字段。

### 示例

价格大于 10 元：

```java
价格 > 10
```

价格在 10 到 20 元之间：

```java
价格 >= 10 && 价格 < 20
```

价格在 10 到 20 元范围之外：

```java
价格 < 10 || 价格 >= 20
```

> 支持`&&`、`||`这两个逻辑运算符，也支持`AND`、`OR`关键字（大小写无关）。
>
> 例如：
>
> ```java
> 价格 < 10 OR 价格 >= 20
> ```

填报人单位是`办公厅`或者`人事司`:

```java
填报人单位 in ["办公厅", "人事司"]
```

流程发起人是`张三`：

```java
流程发起人 in ["张三"]
```

流程发起人是`张三`或者填报人单位是`办公厅`，并且价格大于 100 元：

```java
(流程发起人 in ["张三"] || 填报人单位 in ["办公厅"]) && 价格 > 100
```

> 通过小括号`(`、`)`定义表达式之间的优先级。

流程发送人不是`张三`和`李四`的情况：

```java
流程发送人 not in ["张三", "李四"]
```

> 支持使用 `!` 和关键字 `not` 表示逻辑非运算。示例中是通过`not`关键字表达逻辑非，等价于：
>
> ```java
> !(流程发送人 in ["张三", "李四"])
> ```

上报内容包含`测试`关键字：

```ts
上报内容 like "%测试%"
```

> `like` 表示字符串匹配的比较关系。`%`表示任意一个字符。语法与 SQL 中的 like 关键字一致。

### 比较关系运算符

支持以下关系运算符：

- `like` - 字符串匹配（字符串包含）
- `in` - 数组包含
- `>`
- `>=`
- `<`
- `<=`
- `==`
- `!=`
- `is` - 严格相等
- `is not` - 严格不相等
- `is null` - 属性值为空（null、undefined）判断
- `is empty` - 字符串或者数组为空的判断
- `is not null` - 判断不为空

#### `in` 数组包含

```java
value in [1, 2, 3]
```

value 可以是数字，也可以是数组：

- `value = 1`时，公式返回 `true`
- `value = 4`时，公式返回 `false`
- `value = [1, 7, 8]`时，公式返回 `true`
- `value = [8, 9]`时，公式返回 `false`

#### `like` 字符串匹配

```java
userName like "张%"
```

规则如下：

```java
"张三" like "张%"     ====> TRUE
"李张三" like "张%"   ====> FALSE
"李张三" like "%张%"  ====> TRUE
"张三" like "张_"     ====> TRUE
"张三2" like "张_"    ====> FALSE
"张三2" like "张%"    ====> TRUE
```

- `_` 表示匹配一个任意字符
- `%` 表示匹配零个、一个或者多个任意字符

### 逻辑关系运算符

支持以下关系运算符：

- `&&`、`AND`
- `||`、`OR`
- `!`、`NOT`

### 字面量

支持三种字面量。

`字符串`：

```java
"字符串值"
```

> 注意：必须使用双引号`"`表达字符串字面量。**字符串不支持单引号**。

`数字`：

```java
10
20.1
-10.5
.5
+0.15
```

`数组`：

```java
[1, "2", [3, "4"]]
```

### 内置的流程属性

- 填报人
- 填报人单位
- 填报时间
- 流程发起人
- 流程发起人单位
- 流程发送时间

它们的数据示例：

```json
{
  "填报人": "张三",
  "填报人单位": ["公文处", "办公厅"],
  "填报时间": "2020-02-10",
  "流程发起人": "李四",
  "流程发起人单位": ["公文处", "办公厅"],
  "流程发送时间": "2020-02-18"
}
```

### 属性访问表达式

公式中的属性访问与 JavaScript 属性访问表达式一致的语法，但是目前不支持 `?.` 和 `??` 语法。

针对这样的数据：

```js
const contextData = {
  子表单: [
    {
      标题: '标题1',
    },
    {
      标题: '标题2',
    },
    {
      标题: '标题3',
    },
  ],
};
```

| 属性访问表达式                | 输出结果                      |
| ----------------------------- | ----------------------------- |
| `子表单[0].标题`              | `"标题1"`                     |
| `子表单[2].标题`              | `"标题3"`                     |
| `子表单[10].标题`             | `null`                        |
| `子表单[0].不存在的属性.标题` | `null`                        |
| `子表单.标题`                 | `['标题1', '标题2', '标题3']` |

从上面的表达式示例（最后两个表达式示例）可以看出，相对于 JavaScript 属性访问规则，有两个改进之处：

第一个：内置支持 [可选链式调用](https://github.com/tc39/proposal-optional-chaining) 特性。比如：`obj.prop1` 是 `null` 或者 `undefined`，则 `obj.prop1.prop2` 不会抛出异常，而是会返回 `null`。相当于：

```ts
obj?.prop1?.prop2;
```

第二个：支持快速获取对象数组中的属性。上面的 `子表单.标题` 示例就是这样的情况。如果某个子项不是对象或者是对象中没有此属性，则略过，如下所示：

```ts
const contextData = {
  子表单: [
    {
      age: 10,
    },
    {
      标题: '标题1',
    },
    4,
    {
      标题: '标题2',
    },
    {
      标题: '标题3',
    },
  ],
};
```

则 `子表单.标题` 返回的数据是 `['标题1', '标题2', '标题3']`。