|
@@ -0,0 +1,292 @@
|
|
|
|
|
+import { DataObjectUtils } from "@imengyu/js-request-transform";
|
|
|
|
|
+import { doSerializeInternalVar } from "./CommonCategoryDynamicData";
|
|
|
|
|
+import { DateUtils } from "@imengyu/imengyu-utils";
|
|
|
|
|
+
|
|
|
|
|
+export interface IDynamicCompareExpressionContext {
|
|
|
|
|
+ sourceData: {
|
|
|
|
|
+ main: Record<string, any>,
|
|
|
|
|
+ customData: Record<string, Record<string, any>>,
|
|
|
|
|
+ },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function parseMapExpression(mapExpression: string) {
|
|
|
|
|
+ const arr = mapExpression.split('#');
|
|
|
|
|
+ const map = {} as Record<string, any>;
|
|
|
|
|
+ for (let i = 0; i < arr.length; i += 2) {
|
|
|
|
|
+ map[arr[i]] = arr[i + 1];
|
|
|
|
|
+ }
|
|
|
|
|
+ return map;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 评估动态取值表达式
|
|
|
|
|
+ *
|
|
|
|
|
+ * 取值格式: 下方表达式中以 [R/RW/W] 代表槽位:读取、读写、写入
|
|
|
|
|
+ * [K#key] : 从sourceData中取值,键值为key
|
|
|
|
|
+ * [M#MAP] : 处理映射,格式为:key#value#key#value#...
|
|
|
|
|
+ * [R] : 从上一步表达式中结果
|
|
|
|
|
+ * [value] : 静态数据,可选 type#value 格式,type为类型,value为值,默认 string
|
|
|
|
|
+ *
|
|
|
|
|
+ * 格式:
|
|
|
|
|
+ * (最后一位表示是否存储结果至上一步表达式结果)
|
|
|
|
|
+ * A,[R] [Y]: 从sourceData中取值,键值为key
|
|
|
|
|
+ * M,[R],[M#MAP] [Y]: 从customData中取值,并使用MAP中的数据映射,MAP为自定义数据映射
|
|
|
|
|
+ * R [Y]: 从上一步表达式中结果
|
|
|
|
|
+ * S,[W],[R] [N]: 执行设置操作,R为从上一步表达式中结果,value为设置值
|
|
|
|
|
+ * ST,[key],[R] [N]: 暂存变量:设置
|
|
|
|
|
+ * RT,[key],[W] [Y]: 暂存变量:取值
|
|
|
|
|
+ * CT,[key] [N]: 暂存变量:删除
|
|
|
|
|
+ * OP,[R],[OP],[R] [Y]: 执行操作,A为操作数,OP为操作符,B为操作数
|
|
|
|
|
+ *
|
|
|
|
|
+ * 多个表达式用 / 分隔,会按顺序依次评估,并返回最后一个表达式的结果
|
|
|
|
|
+ * @param expression
|
|
|
|
|
+ * @returns
|
|
|
|
|
+ */
|
|
|
|
|
+export function doEvaluateDynamicDataExpression(expressions: string, context: IDynamicCompareExpressionContext) {
|
|
|
|
|
+ const expressionArr = expressions.split('/');
|
|
|
|
|
+ const tempData = new Map<string, any>();
|
|
|
|
|
+
|
|
|
|
|
+ function evaluateExpression(expression: string, prevResult: any) : any {
|
|
|
|
|
+ const arr = expression.split(',');
|
|
|
|
|
+ if (arr.length < 2)
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ const type = arr[0];
|
|
|
|
|
+ const key = arr[1];
|
|
|
|
|
+
|
|
|
|
|
+ function accessDynamicKeyOrPrevResult(key: string, write: boolean, setValue?: any) {
|
|
|
|
|
+ //基础取值
|
|
|
|
|
+ if (key ==='R') {
|
|
|
|
|
+ if (write) {
|
|
|
|
|
+ prevResult = setValue;
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+ return prevResult;
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (key.startsWith('M#')) {
|
|
|
|
|
+ if (write) {
|
|
|
|
|
+ console.error('doEvaluateDynamicDataExpression: M#MAP is not supported in write mode');
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+ return parseMapExpression(key.substring(2));
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (key.startsWith('K#')) {
|
|
|
|
|
+ key = key.substring(2);
|
|
|
|
|
+ const data = context.sourceData as Record<string, any>;
|
|
|
|
|
+
|
|
|
|
|
+ //对象访问
|
|
|
|
|
+ if (write) {
|
|
|
|
|
+ return DataObjectUtils.accessObjectByString(data, key, true);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const preSerialize = doSerializeInternalVar(key);
|
|
|
|
|
+ if (preSerialize.prased)
|
|
|
|
|
+ return preSerialize.value;
|
|
|
|
|
+ else if (typeof data !== 'object') {
|
|
|
|
|
+ if (key)
|
|
|
|
|
+ throw new Error('doEvaluateDynamicDataExpression: data is not an object: ' + key);
|
|
|
|
|
+ return data;
|
|
|
|
|
+ }
|
|
|
|
|
+ return DataObjectUtils.accessObjectByString(data, key, false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ //原始值
|
|
|
|
|
+ if (write)
|
|
|
|
|
+ throw new Error('doEvaluateDynamicDataExpression: is not writable: ' + key);
|
|
|
|
|
+
|
|
|
|
|
+ const preSerialize = doSerializeInternalVar(key);
|
|
|
|
|
+ if (preSerialize.prased)
|
|
|
|
|
+ return preSerialize.value;
|
|
|
|
|
+ let type = 'string';
|
|
|
|
|
+ let dat = '';
|
|
|
|
|
+ const sp = key.split('#');
|
|
|
|
|
+ if (sp.length > 1) {
|
|
|
|
|
+ type = sp[0];
|
|
|
|
|
+ dat = sp[1];
|
|
|
|
|
+ }
|
|
|
|
|
+ switch (type) {
|
|
|
|
|
+ case 'string': return dat as string;
|
|
|
|
|
+ case 'number': return Number(dat);
|
|
|
|
|
+ case 'boolean': return Boolean(dat);
|
|
|
|
|
+ case 'date': return new Date(dat);
|
|
|
|
|
+ case 'array': return dat.split(',');
|
|
|
|
|
+ case 'object': return JSON.parse(dat);
|
|
|
|
|
+ default: throw new Error('doEvaluateDynamicDataExpression: unknown type: ' + type);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ switch (type) {
|
|
|
|
|
+ case 'A':
|
|
|
|
|
+ return accessDynamicKeyOrPrevResult(key, false);
|
|
|
|
|
+ case 'M': {
|
|
|
|
|
+ if (arr.length < 3)
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ const map = accessDynamicKeyOrPrevResult(arr[2], false);
|
|
|
|
|
+ return map[accessDynamicKeyOrPrevResult(key, false)];
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'R': return prevResult;
|
|
|
|
|
+ case 'S': {
|
|
|
|
|
+ if (arr.length < 3)
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ accessDynamicKeyOrPrevResult(
|
|
|
|
|
+ accessDynamicKeyOrPrevResult(arr[1], false),
|
|
|
|
|
+ true,
|
|
|
|
|
+ accessDynamicKeyOrPrevResult(arr[2], false)
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'ST': {
|
|
|
|
|
+ if (arr.length < 3)
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ tempData.set(arr[1], accessDynamicKeyOrPrevResult(arr[2], false));
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'RT': {
|
|
|
|
|
+ if (arr.length < 3)
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ return tempData.get(arr[1]);
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'CT': {
|
|
|
|
|
+ if (arr.length < 2)
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ tempData.delete(arr[1]);
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'OP': {
|
|
|
|
|
+ if (arr.length < 4)
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ const a = accessDynamicKeyOrPrevResult(arr[1], false);
|
|
|
|
|
+ const op = arr[2];
|
|
|
|
|
+ const b = accessDynamicKeyOrPrevResult(arr[3], false);
|
|
|
|
|
+ switch (op) {
|
|
|
|
|
+ case '+': return a + b;
|
|
|
|
|
+ case '-': return a - b;
|
|
|
|
|
+ case '*': return a * b;
|
|
|
|
|
+ case '/': return a / b;
|
|
|
|
|
+ case '%': return a % b;
|
|
|
|
|
+ case '**': return a ** b;
|
|
|
|
|
+ case '==': return a == b;
|
|
|
|
|
+ case '!=': return a != b;
|
|
|
|
|
+ case '>': return a > b;
|
|
|
|
|
+ case '>=': return a >= b;
|
|
|
|
|
+ case '<': return a < b;
|
|
|
|
|
+ case '<=': return a <= b;
|
|
|
|
|
+ case '&&': return a && b;
|
|
|
|
|
+ case '||': return a || b;
|
|
|
|
|
+ case '!': return !a;
|
|
|
|
|
+ default: {
|
|
|
|
|
+ if (op.startsWith('arr#')) {
|
|
|
|
|
+ if (Array.isArray(a)) {
|
|
|
|
|
+ switch (op) {
|
|
|
|
|
+ case 'pop': return a.pop();
|
|
|
|
|
+ case 'shift': return a.shift();
|
|
|
|
|
+ case 'unshift': return a.unshift(b);
|
|
|
|
|
+ case 'join': return a.join(b);
|
|
|
|
|
+ case 'push': return a.push(b);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ throw new Error('doEvaluateDynamicDataExpression: push is not an array: ' + a);
|
|
|
|
|
+ } else if (op.startsWith('obj#')) {
|
|
|
|
|
+ if (typeof a === 'object' && a !== null) {
|
|
|
|
|
+ switch (op) {
|
|
|
|
|
+ case 'keys': return Object.keys(a);
|
|
|
|
|
+ case 'values': return Object.values(a);
|
|
|
|
|
+ case 'entries': return Object.entries(a);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (op.startsWith('str#')) {
|
|
|
|
|
+ if (typeof a === 'string') {
|
|
|
|
|
+ switch (op) {
|
|
|
|
|
+ case 'split': return a.split(b);
|
|
|
|
|
+ case 'replace': return a.replace(b, arr[3]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (op.startsWith('num#')) {
|
|
|
|
|
+ if (typeof a === 'number') {
|
|
|
|
|
+ switch (op) {
|
|
|
|
|
+ case 'round': return Math.round(a);
|
|
|
|
|
+ case 'ceil': return Math.ceil(a);
|
|
|
|
|
+ case 'floor': return Math.floor(a);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (op.startsWith('date#')) {
|
|
|
|
|
+ if (a instanceof Date) {
|
|
|
|
|
+ switch (op) {
|
|
|
|
|
+ case 'format': return DateUtils.formatDate(a, b as string);
|
|
|
|
|
+ case 'getYear': return a.getFullYear();
|
|
|
|
|
+ case 'getMonth': return a.getMonth();
|
|
|
|
|
+ case 'getDate': return a.getDate();
|
|
|
|
|
+ case 'getDay': return a.getDay();
|
|
|
|
|
+ case 'getHours': return a.getHours();
|
|
|
|
|
+ case 'getMinutes': return a.getMinutes();
|
|
|
|
|
+ case 'getSeconds': return a.getSeconds();
|
|
|
|
|
+ case 'getMilliseconds': return a.getMilliseconds();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new Error('doEvaluateDynamicDataExpression: unknown operator ' + op);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let result = null;
|
|
|
|
|
+ for (const expression of expressionArr)
|
|
|
|
|
+ result = evaluateExpression(expression, result);
|
|
|
|
|
+
|
|
|
|
|
+ //console.log('doEvaluateDynamicDataExpression: ', expressions, 'result: ', result);
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+/**
|
|
|
|
|
+ * 评估动态比较表达式
|
|
|
|
|
+ *
|
|
|
|
|
+ * 格式:
|
|
|
|
|
+ * 取值:符号:值
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param expression
|
|
|
|
|
+ * @returns
|
|
|
|
|
+ */
|
|
|
|
|
+export function doEvaluateDynamicCompareExpression(expression: string, context: IDynamicCompareExpressionContext) : boolean {
|
|
|
|
|
+ const arr = expression.split(':');
|
|
|
|
|
+ if (arr.length !== 3)
|
|
|
|
|
+ return false;
|
|
|
|
|
+ const key = arr[0];
|
|
|
|
|
+ const symbol = arr[1];
|
|
|
|
|
+ const value = doEvaluateDynamicDataExpression(arr[2], context);
|
|
|
|
|
+ const data = doEvaluateDynamicDataExpression(key, context);
|
|
|
|
|
+ const negate = symbol.charAt(0) === 'n';
|
|
|
|
|
+ switch (symbol) {
|
|
|
|
|
+ case 'nempty':
|
|
|
|
|
+ case 'empty': {
|
|
|
|
|
+ let compareValue = data === undefined || data === null || data === '';
|
|
|
|
|
+ if (Array.isArray(data))
|
|
|
|
|
+ compareValue = data.length === 0;
|
|
|
|
|
+ return negate ? !compareValue : compareValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'eq':
|
|
|
|
|
+ case 'neq': {
|
|
|
|
|
+ let compareValue = data == value;
|
|
|
|
|
+ return negate ? !compareValue : compareValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'gt':
|
|
|
|
|
+ case 'gte':
|
|
|
|
|
+ case 'lt':
|
|
|
|
|
+ case 'lte': {
|
|
|
|
|
+ let compareValue = false;
|
|
|
|
|
+ if (symbol.startsWith('g'))
|
|
|
|
|
+ compareValue = data > value;
|
|
|
|
|
+ if (symbol.endsWith('e'))
|
|
|
|
|
+ compareValue = data >= value;
|
|
|
|
|
+ if (symbol.startsWith('l'))
|
|
|
|
|
+ compareValue = data < value;
|
|
|
|
|
+ if (symbol.startsWith('l'))
|
|
|
|
|
+ compareValue = data <= value;
|
|
|
|
|
+ return negate ? !compareValue : compareValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ default: {
|
|
|
|
|
+ throw new Error('doEvaluateDynamicCompareExpression: unknown symbol: ' + symbol);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|