"use strict"; (function () { 'use strict'; angular.module('myitsmApp') .factory('expressionEvaluatorService', ['expressionSyntaxTreeBuilder', 'objectValueMapperService', 'keywordEvaluatorService', 'functionEvaluatorService', function (expressionSyntaxTreeBuilder, objectValueMapperService, keywordEvaluatorService, functionEvaluatorService) { var parsedExpressionCache = {}; function andHandler(a, b) { return Boolean(a) && Boolean(b); } function orHandler(a, b) { return Boolean(a) || Boolean(b); } function likeHandler(a, b) { if (_.isString(a) && _.isString(b)) { if (a.length > b.length) { return b && _.includes(a, b); } else { return a && _.includes(b, a); } } else if (_.isArray(a)) { return _.includes(a, b); } else if (_.isArray(b)) { return _.includes(b, a); } else { return false; } } function checkForNullValues(input) { if ((_.isString(input) && input.toUpperCase() === 'NULL') || input === null) { input = ''; } return input; } var operators = { '==': function (a, b) { return Boolean(checkForNullValues(a) === checkForNullValues(b)); }, '+': function (a, b) { if (_.isUndefined(a) || (a === null && _.isString(b))) { return b; } else if (_.isUndefined(b) || (b === null && _.isString(a))) { return a; } else if (a === null && b === null) { return null; } return a + b; }, '-': function (a, b) { if (_.isUndefined(a)) { a = 0; } if (_.isUndefined(b)) { b = 0; } return a - b; }, '*': function (a, b) { if (_.isUndefined(a)) { a = 0; } if (_.isUndefined(b)) { b = 0; } return a * b; }, '/': function (a, b) { if (_.isUndefined(a) || _.isUndefined(b)) { return 0; } return a / b; }, '<': function (a, b) { return a < b; }, '>': function (a, b) { return a > b; }, '>=': function (a, b) { return a >= b; }, '<=': function (a, b) { return a <= b; }, '!=': function (a, b) { return checkForNullValues(a) !== checkForNullValues(b); }, '%': function (a, b) { if (_.isUndefined(a) || _.isUndefined(b)) { return 0; } return a % b; }, '&&': andHandler, '||': orHandler, 'LIKE': likeHandler, 'like': likeHandler }; var unaryOperators = { '-': function (value) { if (_.isUndefined(value)) { return 0; } else { return -Number(value); } }, '!': function (value) { return !Boolean(value); } }; function parseExpression(expression) { if (!parsedExpressionCache[expression]) { var parsedExpression = expressionSyntaxTreeBuilder(expression); if (validateChildNodeTypes(parsedExpression)) { parsedExpressionCache[expression] = parsedExpression; } else { throw new Error('Invalid syntax found during parsing'); } } return parsedExpressionCache[expression]; } function validateChildNodeTypes(node) { var isValid = false; switch (node.type) { case 'LogicalExpression': case 'BinaryExpression': isValid = validateChildNodeTypes(node.left) && validateChildNodeTypes(node.right); break; case 'CallExpression': isValid = true; break; case 'UnaryExpression': isValid = validateChildNodeTypes(node.argument); break; case 'Identifier': case 'Literal': isValid = true; break; case 'ConditionalExpression': isValid = true; break; } return isValid; } function _evaluateNode(node) { var left, right, nodeName, fieldname; switch (node.type) { case 'CallExpression': var args = node.arguments, callee = node.callee.name, argsEvaluated = []; if (_.indexOf(['ISREQUIRED', 'ISREADONLY', 'ISHIDDEN', 'ONCHANGE'], callee) > -1) { _.each(args, function (arg) { if (arg.type === 'Literal') { if (_.startsWith(arg.raw, "'$")) { nodeName = arg.raw.replace(/['$]/g, ''); argsEvaluated.push(nodeName); } } else { if (_.startsWith(arg.name, '$')) { nodeName = arg.name.substr(1, arg.name.length - 1); argsEvaluated.push(nodeName); } else { throw new Error('Function ' + callee + ' has invalid arguments.'); } } }); } else { _.each(args, function (arg) { argsEvaluated.push(_evaluateNode(arg)); fieldname = arg.type === 'Identifier' ? arg.name : arg.value; if (callee.indexOf('SELECTION') !== -1 && fieldname) { var field = objectValueMapperService.getFieldByName(fieldname.substr(1)); if (field && field.options) { argsEvaluated.push(field.options); } } }); } return functionEvaluatorService.execute(callee, argsEvaluated); case 'ConditionalExpression': var test = _evaluateNode(node.test); if (test) { return _evaluateNode(node.consequent); } else { return _evaluateNode(node.alternate); } break; case 'LogicalExpression': left = _evaluateNode(node.left); right = _evaluateNode(node.right); if (operators[node.operator]) { var result = operators[node.operator](left, right); if (_.isNaN(result)) { throw new Error('Operator: ' + node.operator + ' has invalid arguments: ' + left + ', ' + right); } else { return result; } } else { throw new Error('Unknown Logical operator: ' + node.operator); } break; case 'BinaryExpression': left = _evaluateNode(node.left); right = _evaluateNode(node.right); if (operators[node.operator]) { var binaryResult = operators[node.operator](left, right); if (_.isNaN(binaryResult)) { throw new Error('Operator: ' + node.operator + ' has invalid arguments: ' + left + ', ' + right); } else { return binaryResult; } } else { throw new Error('Unknown binary operator: ' + node.operator); } break; case 'Literal': if (_.startsWith(node.raw, "'$")) { if (_.endsWith(node.raw, "$'")) { //'$KEYWORD$' nodeName = node.raw.replace(/['$]/g, ''); return keywordEvaluatorService.evaluate(nodeName); } else { //'$FIELD_VARIABLE' nodeName = node.raw.replace(/['$]/g, ''); return objectValueMapperService.getExactValueByFieldName(nodeName); } } else if (node.value === 0 || node.value === false) { return node.value; } else { return node.value || ''; } break; case 'Identifier': if (_.startsWith(node.name, '$')) { if (_.endsWith(node.name, '$')) { //$KEYWORD$ nodeName = node.name.substr(1, node.name.length - 2); return keywordEvaluatorService.evaluate(nodeName); } else { //$FIELD_VARIABLE return objectValueMapperService.getExactValueByFieldName(node.name.substr(1)); } } else { return node.name; } break; case 'UnaryExpression': if (unaryOperators[node.operator]) { var evaluatedResult = _evaluateNode(node.argument), unaryResult = unaryOperators[node.operator](evaluatedResult); if (_.isNaN(unaryResult)) { throw new Error('Operator: ' + node.operator + ' has invalid argument: ' + evaluatedResult); } else { return unaryResult; } } else { throw new Error('Unknown unary operator: ' + node.operator); } break; default: throw new Error('Invalid syntax'); } } function evaluate(expression) { var evaluatedExpression = expression; if (_.isString(expression)) { expression = _.trim(expression); if (expression) { //todo to have helper service to prepare so that all operators are normalized var preparedExpression = expression, parsedExpression; try { parsedExpression = parseExpression(preparedExpression); } catch (e) { throw new Error('Cannot parse expression "' + expression + '": ' + e.message + '.'); } try { evaluatedExpression = _evaluateNode(parsedExpression); } catch (e) { throw new Error('Cannot evaluate expression "' + expression + '": ' + e.message + '.'); } } else { evaluatedExpression = null; } } return evaluatedExpression; } return { evaluate: evaluate, parseExpression: parseExpression, validateChildNodeTypes: validateChildNodeTypes }; }]); })();