| /* | 
 | * Licensed to the Apache Software Foundation (ASF) under one | 
 | * or more contributor license agreements.  See the NOTICE file | 
 | * distributed with this work for additional information | 
 | * regarding copyright ownership.  The ASF licenses this file | 
 | * to you under the Apache License, Version 2.0 (the | 
 | * "License"); you may not use this file except in compliance | 
 | * with the License.  You may obtain a copy of the License at | 
 | * | 
 | *   http://www.apache.org/licenses/LICENSE-2.0 | 
 | * | 
 | * Unless required by applicable law or agreed to in writing, | 
 | * software distributed under the License is distributed on an | 
 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
 | * KIND, either express or implied.  See the License for the | 
 | * specific language governing permissions and limitations | 
 | * under the License. | 
 | */ | 
 |  | 
 | import { OptionDataValue, DimensionLoose, Dictionary } from './types'; | 
 | import { | 
 |     keys, isArray, map, isObject, isString, HashMap, isRegExp, isArrayLike, hasOwn, isNumber | 
 | } from 'zrender/src/core/util'; | 
 | import { throwError, makePrintable } from './log'; | 
 | import { | 
 |     RawValueParserType, getRawValueParser, | 
 |     RelationalOperator, FilterComparator, createFilterComparator | 
 | } from '../data/helper/dataValueHelper'; | 
 |  | 
 |  | 
 | // PENDING: | 
 | // (1) Support more parser like: `parser: 'trim'`, `parser: 'lowerCase'`, `parser: 'year'`, `parser: 'dayOfWeek'`? | 
 | // (2) Support piped parser? | 
 | // (3) Support callback parser or callback condition? | 
 | // (4) At present do not support string expression yet but only structured expression. | 
 |  | 
 |  | 
 | /** | 
 |  * The structured expression considered: | 
 |  * (1) Literal simplicity | 
 |  * (2) Semantic displayed clearly | 
 |  * | 
 |  * Semantic supports: | 
 |  * (1) relational expression | 
 |  * (2) logical expression | 
 |  * | 
 |  * For example: | 
 |  * ```js | 
 |  * { | 
 |  *     and: [{ | 
 |  *         or: [{ | 
 |  *             dimension: 'Year', gt: 2012, lt: 2019 | 
 |  *         }, { | 
 |  *             dimension: 'Year', '>': 2002, '<=': 2009 | 
 |  *         }] | 
 |  *     }, { | 
 |  *         dimension: 'Product', eq: 'Tofu' | 
 |  *     }] | 
 |  * } | 
 |  * | 
 |  * { dimension: 'Product', eq: 'Tofu' } | 
 |  * | 
 |  * { | 
 |  *     or: [ | 
 |  *         { dimension: 'Product', value: 'Tofu' }, | 
 |  *         { dimension: 'Product', value: 'Biscuit' } | 
 |  *     ] | 
 |  * } | 
 |  * | 
 |  * { | 
 |  *     and: [true] | 
 |  * } | 
 |  * ``` | 
 |  * | 
 |  * [PARSER] | 
 |  * In an relation expression object, we can specify some built-in parsers: | 
 |  * ```js | 
 |  * // Trim if string | 
 |  * { | 
 |  *     parser: 'trim', | 
 |  *     eq: 'Flowers' | 
 |  * } | 
 |  * // Parse as time and enable arithmetic relation comparison. | 
 |  * { | 
 |  *     parser: 'time', | 
 |  *     lt: '2012-12-12' | 
 |  * } | 
 |  * // Normalize number-like string and make '-' to Null. | 
 |  * { | 
 |  *     parser: 'time', | 
 |  *     lt: '2012-12-12' | 
 |  * } | 
 |  * // Normalize to number: | 
 |  * // + number-like string (like '  123  ') can be converted to a number. | 
 |  * // + where null/undefined or other string will be converted to NaN. | 
 |  * { | 
 |  *     parser: 'number', | 
 |  *     eq: 2011 | 
 |  * } | 
 |  * // RegExp, include the feature in SQL: `like '%xxx%'`. | 
 |  * { | 
 |  *     reg: /^asdf$/ | 
 |  * } | 
 |  * { | 
 |  *     reg: '^asdf$' // Serializable reg exp, will be `new RegExp(...)` | 
 |  * } | 
 |  * ``` | 
 |  * | 
 |  * | 
 |  * [EMPTY_RULE] | 
 |  * (1) If a relational expression set value as `null`/`undefined` like: | 
 |  * `{ dimension: 'Product', lt: undefined }`, | 
 |  * The result will be `false` rather than `true`. | 
 |  * Consider the case like "filter condition", return all result when null/undefined | 
 |  * is probably not expected and even dangours. | 
 |  * (2) If a relational expression has no operator like: | 
 |  * `{ dimension: 'Product' }`, | 
 |  * An error will be thrown. Because it is probably a mistake. | 
 |  * (3) If a logical expression has no children like | 
 |  * `{ and: undefined }` or `{ and: [] }`, | 
 |  * An error will be thrown. Because it is probably an mistake. | 
 |  * (4) If intending have a condition that always `true` or always `false`, | 
 |  * Use `true` or `flase`. | 
 |  * The entire condition can be `true`/`false`, | 
 |  * or also can be `{ and: [true] }`, `{ or: [false] }` | 
 |  */ | 
 |  | 
 |  | 
 | // -------------------------------------------------- | 
 | // --- Relational Expression -------------------------- | 
 | // -------------------------------------------------- | 
 |  | 
 | /** | 
 |  * Date string and ordinal string can be accepted. | 
 |  */ | 
 | interface RelationalExpressionOptionByOp extends Record<RelationalOperator, OptionDataValue> { | 
 |     reg?: RegExp | string; // RegExp | 
 | }; | 
 | const RELATIONAL_EXPRESSION_OP_ALIAS_MAP = { | 
 |     value: 'eq', | 
 |  | 
 |     // PENDING: not good for literal semantic? | 
 |     '<': 'lt', | 
 |     '<=': 'lte', | 
 |     '>': 'gt', | 
 |     '>=': 'gte', | 
 |     '=': 'eq', | 
 |     '!=': 'ne', | 
 |     '<>': 'ne' | 
 |  | 
 |     // Might be misleading for sake of the difference between '==' and '===', | 
 |     // so don't support them. | 
 |     // '==': 'eq', | 
 |     // '===': 'seq', | 
 |     // '!==': 'sne' | 
 |  | 
 |     // PENDING: Whether support some common alias "ge", "le", "neq"? | 
 |     // ge: 'gte', | 
 |     // le: 'lte', | 
 |     // neq: 'ne', | 
 | } as const; | 
 | type RelationalExpressionOptionByOpAlias = Record<keyof typeof RELATIONAL_EXPRESSION_OP_ALIAS_MAP, OptionDataValue>; | 
 |  | 
 | interface RelationalExpressionOption extends | 
 |         RelationalExpressionOptionByOp, RelationalExpressionOptionByOpAlias { | 
 |     dimension?: DimensionLoose; | 
 |     parser?: RawValueParserType; | 
 | } | 
 |  | 
 | // type RelationalExpressionOpEvaluate = (tarVal: unknown, condVal: unknown) => boolean; | 
 |  | 
 |  | 
 | class RegExpEvaluator implements FilterComparator { | 
 |     private _condVal: RegExp; | 
 |  | 
 |     constructor(rVal: unknown) { | 
 |         // Support condVal: RegExp | string | 
 |         const condValue = this._condVal = isString(rVal) ? new RegExp(rVal) | 
 |             : isRegExp(rVal) ? rVal as RegExp | 
 |             : null; | 
 |         if (condValue == null) { | 
 |             let errMsg = ''; | 
 |             if (__DEV__) { | 
 |                 errMsg = makePrintable('Illegal regexp', rVal, 'in'); | 
 |             } | 
 |             throwError(errMsg); | 
 |         } | 
 |     } | 
 |  | 
 |     evaluate(lVal: unknown): boolean { | 
 |         const type = typeof lVal; | 
 |         return isString(type) ? this._condVal.test(lVal as string) | 
 |             : isNumber(type) ? this._condVal.test(lVal + '') | 
 |             : false; | 
 |     } | 
 | } | 
 |  | 
 |  | 
 |  | 
 |  | 
 | // -------------------------------------------------- | 
 | // --- Logical Expression --------------------------- | 
 | // -------------------------------------------------- | 
 |  | 
 |  | 
 | interface LogicalExpressionOption { | 
 |     and?: LogicalExpressionSubOption[]; | 
 |     or?: LogicalExpressionSubOption[]; | 
 |     not?: LogicalExpressionSubOption; | 
 | } | 
 | type LogicalExpressionSubOption = | 
 |     LogicalExpressionOption | RelationalExpressionOption | TrueFalseExpressionOption; | 
 |  | 
 |  | 
 |  | 
 | // ----------------------------------------------------- | 
 | // --- Conditional Expression -------------------------- | 
 | // ----------------------------------------------------- | 
 |  | 
 |  | 
 | export type TrueExpressionOption = true; | 
 | export type FalseExpressionOption = false; | 
 | export type TrueFalseExpressionOption = TrueExpressionOption | FalseExpressionOption; | 
 |  | 
 | export type ConditionalExpressionOption = | 
 |     LogicalExpressionOption | 
 |     | RelationalExpressionOption | 
 |     | TrueFalseExpressionOption; | 
 |  | 
 | type ValueGetterParam = Dictionary<unknown>; | 
 | export interface ConditionalExpressionValueGetterParamGetter<VGP extends ValueGetterParam = ValueGetterParam> { | 
 |     (relExpOption: RelationalExpressionOption): VGP | 
 | } | 
 | export interface ConditionalExpressionValueGetter<VGP extends ValueGetterParam = ValueGetterParam> { | 
 |     (param: VGP): OptionDataValue | 
 | } | 
 |  | 
 | interface ParsedConditionInternal { | 
 |     evaluate(): boolean; | 
 | } | 
 | class ConstConditionInternal implements ParsedConditionInternal { | 
 |     value: boolean; | 
 |     evaluate(): boolean { | 
 |         return this.value; | 
 |     } | 
 | } | 
 | class AndConditionInternal implements ParsedConditionInternal { | 
 |     children: ParsedConditionInternal[]; | 
 |     evaluate() { | 
 |         const children = this.children; | 
 |         for (let i = 0; i < children.length; i++) { | 
 |             if (!children[i].evaluate()) { | 
 |                 return false; | 
 |             } | 
 |         } | 
 |         return true; | 
 |     } | 
 | } | 
 | class OrConditionInternal implements ParsedConditionInternal { | 
 |     children: ParsedConditionInternal[]; | 
 |     evaluate() { | 
 |         const children = this.children; | 
 |         for (let i = 0; i < children.length; i++) { | 
 |             if (children[i].evaluate()) { | 
 |                 return true; | 
 |             } | 
 |         } | 
 |         return false; | 
 |     } | 
 | } | 
 | class NotConditionInternal implements ParsedConditionInternal { | 
 |     child: ParsedConditionInternal; | 
 |     evaluate() { | 
 |         return !this.child.evaluate(); | 
 |     } | 
 | } | 
 | class RelationalConditionInternal implements ParsedConditionInternal { | 
 |     valueGetterParam: ValueGetterParam; | 
 |     valueParser: ReturnType<typeof getRawValueParser>; | 
 |     // If no parser, be null/undefined. | 
 |     getValue: ConditionalExpressionValueGetter; | 
 |     subCondList: FilterComparator[]; | 
 |  | 
 |     evaluate() { | 
 |         const needParse = !!this.valueParser; | 
 |         // Call getValue with no `this`. | 
 |         const getValue = this.getValue; | 
 |         const tarValRaw = getValue(this.valueGetterParam); | 
 |         const tarValParsed = needParse ? this.valueParser(tarValRaw) : null; | 
 |  | 
 |         // Relational cond follow "and" logic internally. | 
 |         for (let i = 0; i < this.subCondList.length; i++) { | 
 |             if (!this.subCondList[i].evaluate(needParse ? tarValParsed : tarValRaw)) { | 
 |                 return false; | 
 |             } | 
 |         } | 
 |         return true; | 
 |     } | 
 | } | 
 |  | 
 | function parseOption( | 
 |     exprOption: ConditionalExpressionOption, | 
 |     getters: ConditionalGetters | 
 | ): ParsedConditionInternal { | 
 |     if (exprOption === true || exprOption === false) { | 
 |         const cond = new ConstConditionInternal(); | 
 |         cond.value = exprOption as boolean; | 
 |         return cond; | 
 |     } | 
 |  | 
 |     let errMsg = ''; | 
 |     if (!isObjectNotArray(exprOption)) { | 
 |         if (__DEV__) { | 
 |             errMsg = makePrintable( | 
 |                 'Illegal config. Expect a plain object but actually', exprOption | 
 |             ); | 
 |         } | 
 |         throwError(errMsg); | 
 |     } | 
 |  | 
 |     if ((exprOption as LogicalExpressionOption).and) { | 
 |         return parseAndOrOption('and', exprOption as LogicalExpressionOption, getters); | 
 |     } | 
 |     else if ((exprOption as LogicalExpressionOption).or) { | 
 |         return parseAndOrOption('or', exprOption as LogicalExpressionOption, getters); | 
 |     } | 
 |     else if ((exprOption as LogicalExpressionOption).not) { | 
 |         return parseNotOption(exprOption as LogicalExpressionOption, getters); | 
 |     } | 
 |  | 
 |     return parseRelationalOption(exprOption as RelationalExpressionOption, getters); | 
 | } | 
 |  | 
 | function parseAndOrOption( | 
 |     op: 'and' | 'or', | 
 |     exprOption: LogicalExpressionOption, | 
 |     getters: ConditionalGetters | 
 | ): ParsedConditionInternal { | 
 |     const subOptionArr = exprOption[op] as ConditionalExpressionOption[]; | 
 |     let errMsg = ''; | 
 |     if (__DEV__) { | 
 |         errMsg = makePrintable( | 
 |             '"and"/"or" condition should only be `' + op + ': [...]` and must not be empty array.', | 
 |             'Illegal condition:', exprOption | 
 |         ); | 
 |     } | 
 |     if (!isArray(subOptionArr)) { | 
 |         throwError(errMsg); | 
 |     } | 
 |     if (!(subOptionArr as []).length) { | 
 |         throwError(errMsg); | 
 |     } | 
 |     const cond = op === 'and' ? new AndConditionInternal() : new OrConditionInternal(); | 
 |     cond.children = map(subOptionArr, subOption => parseOption(subOption, getters)); | 
 |     if (!cond.children.length) { | 
 |         throwError(errMsg); | 
 |     } | 
 |     return cond; | 
 | } | 
 |  | 
 | function parseNotOption( | 
 |     exprOption: LogicalExpressionOption, | 
 |     getters: ConditionalGetters | 
 | ): ParsedConditionInternal { | 
 |     const subOption = exprOption.not as ConditionalExpressionOption; | 
 |     let errMsg = ''; | 
 |     if (__DEV__) { | 
 |         errMsg = makePrintable( | 
 |             '"not" condition should only be `not: {}`.', | 
 |             'Illegal condition:', exprOption | 
 |         ); | 
 |     } | 
 |     if (!isObjectNotArray(subOption)) { | 
 |         throwError(errMsg); | 
 |     } | 
 |     const cond = new NotConditionInternal(); | 
 |     cond.child = parseOption(subOption, getters); | 
 |     if (!cond.child) { | 
 |         throwError(errMsg); | 
 |     } | 
 |     return cond; | 
 | } | 
 |  | 
 | function parseRelationalOption( | 
 |     exprOption: RelationalExpressionOption, | 
 |     getters: ConditionalGetters | 
 | ): ParsedConditionInternal { | 
 |     let errMsg = ''; | 
 |  | 
 |     const valueGetterParam = getters.prepareGetValue(exprOption); | 
 |  | 
 |     const subCondList = [] as RelationalConditionInternal['subCondList']; | 
 |     const exprKeys = keys(exprOption); | 
 |  | 
 |     const parserName = exprOption.parser; | 
 |     const valueParser = parserName ? getRawValueParser(parserName) : null; | 
 |  | 
 |     for (let i = 0; i < exprKeys.length; i++) { | 
 |         const keyRaw = exprKeys[i]; | 
 |         if (keyRaw === 'parser' || getters.valueGetterAttrMap.get(keyRaw)) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         const op: keyof RelationalExpressionOptionByOp = hasOwn(RELATIONAL_EXPRESSION_OP_ALIAS_MAP, keyRaw) | 
 |             ? RELATIONAL_EXPRESSION_OP_ALIAS_MAP[keyRaw as keyof RelationalExpressionOptionByOpAlias] | 
 |             : (keyRaw as keyof RelationalExpressionOptionByOp); | 
 |         const condValueRaw = exprOption[keyRaw]; | 
 |         const condValueParsed = valueParser ? valueParser(condValueRaw) : condValueRaw; | 
 |         const evaluator = createFilterComparator(op, condValueParsed) | 
 |             || (op === 'reg' && new RegExpEvaluator(condValueParsed)); | 
 |  | 
 |         if (!evaluator) { | 
 |             if (__DEV__) { | 
 |                 errMsg = makePrintable( | 
 |                     'Illegal relational operation: "' + keyRaw + '" in condition:', exprOption | 
 |                 ); | 
 |             } | 
 |             throwError(errMsg); | 
 |         } | 
 |  | 
 |         subCondList.push(evaluator); | 
 |     } | 
 |  | 
 |     if (!subCondList.length) { | 
 |         if (__DEV__) { | 
 |             errMsg = makePrintable( | 
 |                 'Relational condition must have at least one operator.', | 
 |                 'Illegal condition:', exprOption | 
 |             ); | 
 |         } | 
 |         // No relational operator always disabled in case of dangers result. | 
 |         throwError(errMsg); | 
 |     } | 
 |  | 
 |     const cond = new RelationalConditionInternal(); | 
 |     cond.valueGetterParam = valueGetterParam; | 
 |     cond.valueParser = valueParser; | 
 |     cond.getValue = getters.getValue; | 
 |     cond.subCondList = subCondList; | 
 |  | 
 |     return cond; | 
 | } | 
 |  | 
 | function isObjectNotArray(val: unknown): boolean { | 
 |     return isObject(val) && !isArrayLike(val); | 
 | } | 
 |  | 
 |  | 
 | class ConditionalExpressionParsed { | 
 |  | 
 |     private _cond: ParsedConditionInternal; | 
 |  | 
 |     constructor( | 
 |         exprOption: ConditionalExpressionOption, | 
 |         getters: ConditionalGetters | 
 |     ) { | 
 |         this._cond = parseOption(exprOption, getters); | 
 |     } | 
 |  | 
 |     evaluate(): boolean { | 
 |         return this._cond.evaluate(); | 
 |     } | 
 | }; | 
 |  | 
 | interface ConditionalGetters<VGP extends ValueGetterParam = ValueGetterParam> { | 
 |     prepareGetValue: ConditionalExpressionValueGetterParamGetter<VGP>; | 
 |     getValue: ConditionalExpressionValueGetter<VGP>; | 
 |     valueGetterAttrMap: HashMap<boolean, string>; | 
 | } | 
 |  | 
 | export function parseConditionalExpression<VGP extends ValueGetterParam = ValueGetterParam>( | 
 |     exprOption: ConditionalExpressionOption, | 
 |     getters: ConditionalGetters<VGP> | 
 | ): ConditionalExpressionParsed { | 
 |     return new ConditionalExpressionParsed(exprOption, getters); | 
 | } | 
 |  |