diff --git a/conf/requires/config.js b/conf/requires/config.js index ff02a5060239f3c9bb4d0259004ef3ec350e3792..b0a1e942349940d7a023e69735488e3bd7bab04d 100644 --- a/conf/requires/config.js +++ b/conf/requires/config.js @@ -57,6 +57,7 @@ var oApplicationFiles = { 'modules/vmap/javascript/externs/html2canvas/html2canvas.js', 'modules/vmap/javascript/externs/jspdf/jspdf.debug.js', // Studio + 'javascript/externs/jmespath/jmespath.min.js', 'javascript/externs/bootbox/bootbox.min.js', 'javascript/externs/codemirror/codemirror.min.js', 'javascript/externs/codemirror/htmlmixed.js', @@ -87,8 +88,8 @@ var oApplicationFiles = { 'javascript/externs/viewer/viewer.min.js', 'javascript/externs/alasql/alasql.min.js', 'javascript/externs/alasql/xlsx.core.min.js' - + ] }, 'vitisModuleDependencies': ['vmap', 'ui.codemirror', 'ui.tinymce', 'vfb', 'ui.grid.draggable-rows' ,'ui.grid.exporter'] -}; \ No newline at end of file +}; diff --git a/src/vitis/client/javascript/externs/formReader/formReaderCtrl.js b/src/vitis/client/javascript/externs/formReader/formReaderCtrl.js index 581ea7fd7ba8f4fdfc9cb6a28069485242482abb..a2701448c3261af9e41f6e331b0212e10612d3a5 100644 --- a/src/vitis/client/javascript/externs/formReader/formReaderCtrl.js +++ b/src/vitis/client/javascript/externs/formReader/formReaderCtrl.js @@ -514,10 +514,14 @@ formReader.formReaderController.prototype.extractFormDefinitionInfos = function if (goog.isDef(elem['datasource']['datasource_id'])) { if (goog.isDef(oDataSources)) { if (goog.isDef(oDataSources[elem['datasource']['datasource_id']])) { - // Datasource web_service ? + // Datasource web_service if (oDataSources[elem['datasource']['datasource_id']]['type'] === 'web_service') { oWebService = this_.extractElemWebService(elem, oDataSources); } + // Datasource external_API + if (oDataSources[elem['datasource']['datasource_id']]['type'] === 'external_API') { + oWebService = angular.copy(oDataSources[elem['datasource']['datasource_id']]); + } // Datasource object else if (oDataSources[elem['datasource']['datasource_id']]['type'] === 'object') { aOptions = this_.extractElemOptions(elem, oDataSources); diff --git a/src/vitis/client/javascript/externs/formReader/formReaderSrvc.js b/src/vitis/client/javascript/externs/formReader/formReaderSrvc.js index 613652f9c35ccf385e3b72c1c26432d8e0e8a09f..f174cfef86ee19958d0c70f808509c9881e49bae 100644 --- a/src/vitis/client/javascript/externs/formReader/formReaderSrvc.js +++ b/src/vitis/client/javascript/externs/formReader/formReaderSrvc.js @@ -158,8 +158,8 @@ formReader.formReaderService = function ($translate, $rootScope, $q, $log, $time var j, oNotSelectedOptions, oSelectedOptions, sFromSelectName = "", sToSelectName; // Clés label / valeur du web service pour les <options>. - var sLabelKey = oWebService["label_key"]; - var sValueKey = oWebService["id_key"]; + var sLabelKey = goog.isDefAndNotNull(oWebService["label_key"]) ? oWebService["label_key"] : 'label'; + var sValueKey = goog.isDefAndNotNull(oWebService["id_key"]) ? oWebService["id_key"] : 'value'; // Id des <options> à garder. var aSelectedOptions = []; @@ -429,8 +429,9 @@ formReader.formReaderService = function ($translate, $rootScope, $q, $log, $time } } - // Charge la liste du menu déroulant. - ajaxRequest({ + + // Options requête + var oAjaxOptions = { "method": "POST", "url": oProperties["web_server_name"] + '/' + oProperties["services_alias"] + '/' + oWebService["ressource_id"], "data": oParams, @@ -438,7 +439,23 @@ formReader.formReaderService = function ($translate, $rootScope, $q, $log, $time "headers": { 'X-HTTP-Method-Override': 'GET', 'Accept': 'application/x-vm-json' - }, + } + } + if (oWebService['type'] === 'external_API') { + oAjaxOptions = { + "method": "GET", + "url": oWebService['api_url'], + "scope": $rootScope, + } + } + + // Charge la liste du menu déroulant. + ajaxRequest({ + "method": oAjaxOptions['method'], + "url": oAjaxOptions['url'], + "data": oAjaxOptions['data'], + "scope": oAjaxOptions['scope'], + "headers": oAjaxOptions['headers'], "success": function (response) { // Remet le filtre origiel if (goog.isDefAndNotNull(oWebService['parameters'])) @@ -446,21 +463,47 @@ formReader.formReaderService = function ($translate, $rootScope, $q, $log, $time // Récupération de la donnée var data = response['data']; - data['data'] = goog.isDef(data['data']) ? data['data'] : []; // Met à jour la liste des enregistrements. - if (data["status"] === 1) { + if (oWebService['type'] === 'external_API' && + goog.isDefAndNotNull(oWebService['api_key']) && + goog.isDefAndNotNull(oWebService['api_value'])) { + + if (!goog.isDefAndNotNull(jmespath)) { + console.error('jmespath not defined'); + } - // Extraction des données. var aData; - if (goog.isDefAndNotNull(oWebService['subObject'])) { - aData = []; - for (var i = 0; i < data['data'].length; i++) { - if (goog.isDefAndNotNull(data['data'][i][oWebService['subObject']])) - aData.push(data['data'][i][oWebService['subObject']]); + var aKeys = jmespath.search(data, oWebService['api_key']); + var aValues = jmespath.search(data, oWebService['api_value']); + if (goog.isArray(aKeys) && goog.isArray(aValues)) { + if (aKeys.length === aValues.length) { + aData = []; + for (var i = 0; i < aKeys.length; i++) { + aData.push({ + 'value': aKeys[i], + 'label': aValues[i] + }); + } + } else { + conole.error('Values number different than key\'s'); + } + } + } else { + data['data'] = goog.isDef(data['data']) ? data['data'] : []; + if (data["status"] === 1) { + + // Extraction des données. + var aData; + if (goog.isDefAndNotNull(oWebService['subObject'])) { + aData = []; + for (var i = 0; i < data['data'].length; i++) { + if (goog.isDefAndNotNull(data['data'][i][oWebService['subObject']])) + aData.push(data['data'][i][oWebService['subObject']]); + } + } else { + aData = goog.isDef(data['data']) ? data['data'] : []; } - } else { - aData = goog.isDef(data['data']) ? data['data'] : []; } } diff --git a/src/vitis/client/javascript/externs/jmespath/jmespath.js b/src/vitis/client/javascript/externs/jmespath/jmespath.js new file mode 100644 index 0000000000000000000000000000000000000000..f59e8e4b997510f4a7b420d7c2689a2e7c2de84f --- /dev/null +++ b/src/vitis/client/javascript/externs/jmespath/jmespath.js @@ -0,0 +1,1667 @@ +(function(exports) { + "use strict"; + + function isArray(obj) { + if (obj !== null) { + return Object.prototype.toString.call(obj) === "[object Array]"; + } else { + return false; + } + } + + function isObject(obj) { + if (obj !== null) { + return Object.prototype.toString.call(obj) === "[object Object]"; + } else { + return false; + } + } + + function strictDeepEqual(first, second) { + // Check the scalar case first. + if (first === second) { + return true; + } + + // Check if they are the same type. + var firstType = Object.prototype.toString.call(first); + if (firstType !== Object.prototype.toString.call(second)) { + return false; + } + // We know that first and second have the same type so we can just check the + // first type from now on. + if (isArray(first) === true) { + // Short circuit if they're not the same length; + if (first.length !== second.length) { + return false; + } + for (var i = 0; i < first.length; i++) { + if (strictDeepEqual(first[i], second[i]) === false) { + return false; + } + } + return true; + } + if (isObject(first) === true) { + // An object is equal if it has the same key/value pairs. + var keysSeen = {}; + for (var key in first) { + if (hasOwnProperty.call(first, key)) { + if (strictDeepEqual(first[key], second[key]) === false) { + return false; + } + keysSeen[key] = true; + } + } + // Now check that there aren't any keys in second that weren't + // in first. + for (var key2 in second) { + if (hasOwnProperty.call(second, key2)) { + if (keysSeen[key2] !== true) { + return false; + } + } + } + return true; + } + return false; + } + + function isFalse(obj) { + // From the spec: + // A false value corresponds to the following values: + // Empty list + // Empty object + // Empty string + // False boolean + // null value + + // First check the scalar values. + if (obj === "" || obj === false || obj === null) { + return true; + } else if (isArray(obj) && obj.length === 0) { + // Check for an empty array. + return true; + } else if (isObject(obj)) { + // Check for an empty object. + for (var key in obj) { + // If there are any keys, then + // the object is not empty so the object + // is not false. + if (obj.hasOwnProperty(key)) { + return false; + } + } + return true; + } else { + return false; + } + } + + function objValues(obj) { + var keys = Object.keys(obj); + var values = []; + for (var i = 0; i < keys.length; i++) { + values.push(obj[keys[i]]); + } + return values; + } + + function merge(a, b) { + var merged = {}; + for (var key in a) { + merged[key] = a[key]; + } + for (var key2 in b) { + merged[key2] = b[key2]; + } + return merged; + } + + var trimLeft; + if (typeof String.prototype.trimLeft === "function") { + trimLeft = function(str) { + return str.trimLeft(); + }; + } else { + trimLeft = function(str) { + return str.match(/^\s*(.*)/)[1]; + }; + } + + // Type constants used to define functions. + var TYPE_NUMBER = 0; + var TYPE_ANY = 1; + var TYPE_STRING = 2; + var TYPE_ARRAY = 3; + var TYPE_OBJECT = 4; + var TYPE_BOOLEAN = 5; + var TYPE_EXPREF = 6; + var TYPE_NULL = 7; + var TYPE_ARRAY_NUMBER = 8; + var TYPE_ARRAY_STRING = 9; + + var TOK_EOF = "EOF"; + var TOK_UNQUOTEDIDENTIFIER = "UnquotedIdentifier"; + var TOK_QUOTEDIDENTIFIER = "QuotedIdentifier"; + var TOK_RBRACKET = "Rbracket"; + var TOK_RPAREN = "Rparen"; + var TOK_COMMA = "Comma"; + var TOK_COLON = "Colon"; + var TOK_RBRACE = "Rbrace"; + var TOK_NUMBER = "Number"; + var TOK_CURRENT = "Current"; + var TOK_EXPREF = "Expref"; + var TOK_PIPE = "Pipe"; + var TOK_OR = "Or"; + var TOK_AND = "And"; + var TOK_EQ = "EQ"; + var TOK_GT = "GT"; + var TOK_LT = "LT"; + var TOK_GTE = "GTE"; + var TOK_LTE = "LTE"; + var TOK_NE = "NE"; + var TOK_FLATTEN = "Flatten"; + var TOK_STAR = "Star"; + var TOK_FILTER = "Filter"; + var TOK_DOT = "Dot"; + var TOK_NOT = "Not"; + var TOK_LBRACE = "Lbrace"; + var TOK_LBRACKET = "Lbracket"; + var TOK_LPAREN= "Lparen"; + var TOK_LITERAL= "Literal"; + + // The "&", "[", "<", ">" tokens + // are not in basicToken because + // there are two token variants + // ("&&", "[?", "<=", ">="). This is specially handled + // below. + + var basicTokens = { + ".": TOK_DOT, + "*": TOK_STAR, + ",": TOK_COMMA, + ":": TOK_COLON, + "{": TOK_LBRACE, + "}": TOK_RBRACE, + "]": TOK_RBRACKET, + "(": TOK_LPAREN, + ")": TOK_RPAREN, + "@": TOK_CURRENT + }; + + var operatorStartToken = { + "<": true, + ">": true, + "=": true, + "!": true + }; + + var skipChars = { + " ": true, + "\t": true, + "\n": true + }; + + + function isAlpha(ch) { + return (ch >= "a" && ch <= "z") || + (ch >= "A" && ch <= "Z") || + ch === "_"; + } + + function isNum(ch) { + return (ch >= "0" && ch <= "9") || + ch === "-"; + } + function isAlphaNum(ch) { + return (ch >= "a" && ch <= "z") || + (ch >= "A" && ch <= "Z") || + (ch >= "0" && ch <= "9") || + ch === "_"; + } + + function Lexer() { + } + Lexer.prototype = { + tokenize: function(stream) { + var tokens = []; + this._current = 0; + var start; + var identifier; + var token; + while (this._current < stream.length) { + if (isAlpha(stream[this._current])) { + start = this._current; + identifier = this._consumeUnquotedIdentifier(stream); + tokens.push({type: TOK_UNQUOTEDIDENTIFIER, + value: identifier, + start: start}); + } else if (basicTokens[stream[this._current]] !== undefined) { + tokens.push({type: basicTokens[stream[this._current]], + value: stream[this._current], + start: this._current}); + this._current++; + } else if (isNum(stream[this._current])) { + token = this._consumeNumber(stream); + tokens.push(token); + } else if (stream[this._current] === "[") { + // No need to increment this._current. This happens + // in _consumeLBracket + token = this._consumeLBracket(stream); + tokens.push(token); + } else if (stream[this._current] === "\"") { + start = this._current; + identifier = this._consumeQuotedIdentifier(stream); + tokens.push({type: TOK_QUOTEDIDENTIFIER, + value: identifier, + start: start}); + } else if (stream[this._current] === "'") { + start = this._current; + identifier = this._consumeRawStringLiteral(stream); + tokens.push({type: TOK_LITERAL, + value: identifier, + start: start}); + } else if (stream[this._current] === "`") { + start = this._current; + var literal = this._consumeLiteral(stream); + tokens.push({type: TOK_LITERAL, + value: literal, + start: start}); + } else if (operatorStartToken[stream[this._current]] !== undefined) { + tokens.push(this._consumeOperator(stream)); + } else if (skipChars[stream[this._current]] !== undefined) { + // Ignore whitespace. + this._current++; + } else if (stream[this._current] === "&") { + start = this._current; + this._current++; + if (stream[this._current] === "&") { + this._current++; + tokens.push({type: TOK_AND, value: "&&", start: start}); + } else { + tokens.push({type: TOK_EXPREF, value: "&", start: start}); + } + } else if (stream[this._current] === "|") { + start = this._current; + this._current++; + if (stream[this._current] === "|") { + this._current++; + tokens.push({type: TOK_OR, value: "||", start: start}); + } else { + tokens.push({type: TOK_PIPE, value: "|", start: start}); + } + } else { + var error = new Error("Unknown character:" + stream[this._current]); + error.name = "LexerError"; + throw error; + } + } + return tokens; + }, + + _consumeUnquotedIdentifier: function(stream) { + var start = this._current; + this._current++; + while (this._current < stream.length && isAlphaNum(stream[this._current])) { + this._current++; + } + return stream.slice(start, this._current); + }, + + _consumeQuotedIdentifier: function(stream) { + var start = this._current; + this._current++; + var maxLength = stream.length; + while (stream[this._current] !== "\"" && this._current < maxLength) { + // You can escape a double quote and you can escape an escape. + var current = this._current; + if (stream[current] === "\\" && (stream[current + 1] === "\\" || + stream[current + 1] === "\"")) { + current += 2; + } else { + current++; + } + this._current = current; + } + this._current++; + return JSON.parse(stream.slice(start, this._current)); + }, + + _consumeRawStringLiteral: function(stream) { + var start = this._current; + this._current++; + var maxLength = stream.length; + while (stream[this._current] !== "'" && this._current < maxLength) { + // You can escape a single quote and you can escape an escape. + var current = this._current; + if (stream[current] === "\\" && (stream[current + 1] === "\\" || + stream[current + 1] === "'")) { + current += 2; + } else { + current++; + } + this._current = current; + } + this._current++; + var literal = stream.slice(start + 1, this._current - 1); + return literal.replace("\\'", "'"); + }, + + _consumeNumber: function(stream) { + var start = this._current; + this._current++; + var maxLength = stream.length; + while (isNum(stream[this._current]) && this._current < maxLength) { + this._current++; + } + var value = parseInt(stream.slice(start, this._current)); + return {type: TOK_NUMBER, value: value, start: start}; + }, + + _consumeLBracket: function(stream) { + var start = this._current; + this._current++; + if (stream[this._current] === "?") { + this._current++; + return {type: TOK_FILTER, value: "[?", start: start}; + } else if (stream[this._current] === "]") { + this._current++; + return {type: TOK_FLATTEN, value: "[]", start: start}; + } else { + return {type: TOK_LBRACKET, value: "[", start: start}; + } + }, + + _consumeOperator: function(stream) { + var start = this._current; + var startingChar = stream[start]; + this._current++; + if (startingChar === "!") { + if (stream[this._current] === "=") { + this._current++; + return {type: TOK_NE, value: "!=", start: start}; + } else { + return {type: TOK_NOT, value: "!", start: start}; + } + } else if (startingChar === "<") { + if (stream[this._current] === "=") { + this._current++; + return {type: TOK_LTE, value: "<=", start: start}; + } else { + return {type: TOK_LT, value: "<", start: start}; + } + } else if (startingChar === ">") { + if (stream[this._current] === "=") { + this._current++; + return {type: TOK_GTE, value: ">=", start: start}; + } else { + return {type: TOK_GT, value: ">", start: start}; + } + } else if (startingChar === "=") { + if (stream[this._current] === "=") { + this._current++; + return {type: TOK_EQ, value: "==", start: start}; + } + } + }, + + _consumeLiteral: function(stream) { + this._current++; + var start = this._current; + var maxLength = stream.length; + var literal; + while(stream[this._current] !== "`" && this._current < maxLength) { + // You can escape a literal char or you can escape the escape. + var current = this._current; + if (stream[current] === "\\" && (stream[current + 1] === "\\" || + stream[current + 1] === "`")) { + current += 2; + } else { + current++; + } + this._current = current; + } + var literalString = trimLeft(stream.slice(start, this._current)); + literalString = literalString.replace("\\`", "`"); + if (this._looksLikeJSON(literalString)) { + literal = JSON.parse(literalString); + } else { + // Try to JSON parse it as "<literal>" + literal = JSON.parse("\"" + literalString + "\""); + } + // +1 gets us to the ending "`", +1 to move on to the next char. + this._current++; + return literal; + }, + + _looksLikeJSON: function(literalString) { + var startingChars = "[{\""; + var jsonLiterals = ["true", "false", "null"]; + var numberLooking = "-0123456789"; + + if (literalString === "") { + return false; + } else if (startingChars.indexOf(literalString[0]) >= 0) { + return true; + } else if (jsonLiterals.indexOf(literalString) >= 0) { + return true; + } else if (numberLooking.indexOf(literalString[0]) >= 0) { + try { + JSON.parse(literalString); + return true; + } catch (ex) { + return false; + } + } else { + return false; + } + } + }; + + var bindingPower = {}; + bindingPower[TOK_EOF] = 0; + bindingPower[TOK_UNQUOTEDIDENTIFIER] = 0; + bindingPower[TOK_QUOTEDIDENTIFIER] = 0; + bindingPower[TOK_RBRACKET] = 0; + bindingPower[TOK_RPAREN] = 0; + bindingPower[TOK_COMMA] = 0; + bindingPower[TOK_RBRACE] = 0; + bindingPower[TOK_NUMBER] = 0; + bindingPower[TOK_CURRENT] = 0; + bindingPower[TOK_EXPREF] = 0; + bindingPower[TOK_PIPE] = 1; + bindingPower[TOK_OR] = 2; + bindingPower[TOK_AND] = 3; + bindingPower[TOK_EQ] = 5; + bindingPower[TOK_GT] = 5; + bindingPower[TOK_LT] = 5; + bindingPower[TOK_GTE] = 5; + bindingPower[TOK_LTE] = 5; + bindingPower[TOK_NE] = 5; + bindingPower[TOK_FLATTEN] = 9; + bindingPower[TOK_STAR] = 20; + bindingPower[TOK_FILTER] = 21; + bindingPower[TOK_DOT] = 40; + bindingPower[TOK_NOT] = 45; + bindingPower[TOK_LBRACE] = 50; + bindingPower[TOK_LBRACKET] = 55; + bindingPower[TOK_LPAREN] = 60; + + function Parser() { + } + + Parser.prototype = { + parse: function(expression) { + this._loadTokens(expression); + this.index = 0; + var ast = this.expression(0); + if (this._lookahead(0) !== TOK_EOF) { + var t = this._lookaheadToken(0); + var error = new Error( + "Unexpected token type: " + t.type + ", value: " + t.value); + error.name = "ParserError"; + throw error; + } + return ast; + }, + + _loadTokens: function(expression) { + var lexer = new Lexer(); + var tokens = lexer.tokenize(expression); + tokens.push({type: TOK_EOF, value: "", start: expression.length}); + this.tokens = tokens; + }, + + expression: function(rbp) { + var leftToken = this._lookaheadToken(0); + this._advance(); + var left = this.nud(leftToken); + var currentToken = this._lookahead(0); + while (rbp < bindingPower[currentToken]) { + this._advance(); + left = this.led(currentToken, left); + currentToken = this._lookahead(0); + } + return left; + }, + + _lookahead: function(number) { + return this.tokens[this.index + number].type; + }, + + _lookaheadToken: function(number) { + return this.tokens[this.index + number]; + }, + + _advance: function() { + this.index++; + }, + + nud: function(token) { + var left; + var right; + var expression; + switch (token.type) { + case TOK_LITERAL: + return {type: "Literal", value: token.value}; + case TOK_UNQUOTEDIDENTIFIER: + return {type: "Field", name: token.value}; + case TOK_QUOTEDIDENTIFIER: + var node = {type: "Field", name: token.value}; + if (this._lookahead(0) === TOK_LPAREN) { + throw new Error("Quoted identifier not allowed for function names."); + } else { + return node; + } + break; + case TOK_NOT: + right = this.expression(bindingPower.Not); + return {type: "NotExpression", children: [right]}; + case TOK_STAR: + left = {type: "Identity"}; + right = null; + if (this._lookahead(0) === TOK_RBRACKET) { + // This can happen in a multiselect, + // [a, b, *] + right = {type: "Identity"}; + } else { + right = this._parseProjectionRHS(bindingPower.Star); + } + return {type: "ValueProjection", children: [left, right]}; + case TOK_FILTER: + return this.led(token.type, {type: "Identity"}); + case TOK_LBRACE: + return this._parseMultiselectHash(); + case TOK_FLATTEN: + left = {type: TOK_FLATTEN, children: [{type: "Identity"}]}; + right = this._parseProjectionRHS(bindingPower.Flatten); + return {type: "Projection", children: [left, right]}; + case TOK_LBRACKET: + if (this._lookahead(0) === TOK_NUMBER || this._lookahead(0) === TOK_COLON) { + right = this._parseIndexExpression(); + return this._projectIfSlice({type: "Identity"}, right); + } else if (this._lookahead(0) === TOK_STAR && + this._lookahead(1) === TOK_RBRACKET) { + this._advance(); + this._advance(); + right = this._parseProjectionRHS(bindingPower.Star); + return {type: "Projection", + children: [{type: "Identity"}, right]}; + } else { + return this._parseMultiselectList(); + } + break; + case TOK_CURRENT: + return {type: TOK_CURRENT}; + case TOK_EXPREF: + expression = this.expression(bindingPower.Expref); + return {type: "ExpressionReference", children: [expression]}; + case TOK_LPAREN: + var args = []; + while (this._lookahead(0) !== TOK_RPAREN) { + if (this._lookahead(0) === TOK_CURRENT) { + expression = {type: TOK_CURRENT}; + this._advance(); + } else { + expression = this.expression(0); + } + args.push(expression); + } + this._match(TOK_RPAREN); + return args[0]; + default: + this._errorToken(token); + } + }, + + led: function(tokenName, left) { + var right; + switch(tokenName) { + case TOK_DOT: + var rbp = bindingPower.Dot; + if (this._lookahead(0) !== TOK_STAR) { + right = this._parseDotRHS(rbp); + return {type: "Subexpression", children: [left, right]}; + } else { + // Creating a projection. + this._advance(); + right = this._parseProjectionRHS(rbp); + return {type: "ValueProjection", children: [left, right]}; + } + break; + case TOK_PIPE: + right = this.expression(bindingPower.Pipe); + return {type: TOK_PIPE, children: [left, right]}; + case TOK_OR: + right = this.expression(bindingPower.Or); + return {type: "OrExpression", children: [left, right]}; + case TOK_AND: + right = this.expression(bindingPower.And); + return {type: "AndExpression", children: [left, right]}; + case TOK_LPAREN: + var name = left.name; + var args = []; + var expression, node; + while (this._lookahead(0) !== TOK_RPAREN) { + if (this._lookahead(0) === TOK_CURRENT) { + expression = {type: TOK_CURRENT}; + this._advance(); + } else { + expression = this.expression(0); + } + if (this._lookahead(0) === TOK_COMMA) { + this._match(TOK_COMMA); + } + args.push(expression); + } + this._match(TOK_RPAREN); + node = {type: "Function", name: name, children: args}; + return node; + case TOK_FILTER: + var condition = this.expression(0); + this._match(TOK_RBRACKET); + if (this._lookahead(0) === TOK_FLATTEN) { + right = {type: "Identity"}; + } else { + right = this._parseProjectionRHS(bindingPower.Filter); + } + return {type: "FilterProjection", children: [left, right, condition]}; + case TOK_FLATTEN: + var leftNode = {type: TOK_FLATTEN, children: [left]}; + var rightNode = this._parseProjectionRHS(bindingPower.Flatten); + return {type: "Projection", children: [leftNode, rightNode]}; + case TOK_EQ: + case TOK_NE: + case TOK_GT: + case TOK_GTE: + case TOK_LT: + case TOK_LTE: + return this._parseComparator(left, tokenName); + case TOK_LBRACKET: + var token = this._lookaheadToken(0); + if (token.type === TOK_NUMBER || token.type === TOK_COLON) { + right = this._parseIndexExpression(); + return this._projectIfSlice(left, right); + } else { + this._match(TOK_STAR); + this._match(TOK_RBRACKET); + right = this._parseProjectionRHS(bindingPower.Star); + return {type: "Projection", children: [left, right]}; + } + break; + default: + this._errorToken(this._lookaheadToken(0)); + } + }, + + _match: function(tokenType) { + if (this._lookahead(0) === tokenType) { + this._advance(); + } else { + var t = this._lookaheadToken(0); + var error = new Error("Expected " + tokenType + ", got: " + t.type); + error.name = "ParserError"; + throw error; + } + }, + + _errorToken: function(token) { + var error = new Error("Invalid token (" + + token.type + "): \"" + + token.value + "\""); + error.name = "ParserError"; + throw error; + }, + + + _parseIndexExpression: function() { + if (this._lookahead(0) === TOK_COLON || this._lookahead(1) === TOK_COLON) { + return this._parseSliceExpression(); + } else { + var node = { + type: "Index", + value: this._lookaheadToken(0).value}; + this._advance(); + this._match(TOK_RBRACKET); + return node; + } + }, + + _projectIfSlice: function(left, right) { + var indexExpr = {type: "IndexExpression", children: [left, right]}; + if (right.type === "Slice") { + return { + type: "Projection", + children: [indexExpr, this._parseProjectionRHS(bindingPower.Star)] + }; + } else { + return indexExpr; + } + }, + + _parseSliceExpression: function() { + // [start:end:step] where each part is optional, as well as the last + // colon. + var parts = [null, null, null]; + var index = 0; + var currentToken = this._lookahead(0); + while (currentToken !== TOK_RBRACKET && index < 3) { + if (currentToken === TOK_COLON) { + index++; + this._advance(); + } else if (currentToken === TOK_NUMBER) { + parts[index] = this._lookaheadToken(0).value; + this._advance(); + } else { + var t = this._lookahead(0); + var error = new Error("Syntax error, unexpected token: " + + t.value + "(" + t.type + ")"); + error.name = "Parsererror"; + throw error; + } + currentToken = this._lookahead(0); + } + this._match(TOK_RBRACKET); + return { + type: "Slice", + children: parts + }; + }, + + _parseComparator: function(left, comparator) { + var right = this.expression(bindingPower[comparator]); + return {type: "Comparator", name: comparator, children: [left, right]}; + }, + + _parseDotRHS: function(rbp) { + var lookahead = this._lookahead(0); + var exprTokens = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER, TOK_STAR]; + if (exprTokens.indexOf(lookahead) >= 0) { + return this.expression(rbp); + } else if (lookahead === TOK_LBRACKET) { + this._match(TOK_LBRACKET); + return this._parseMultiselectList(); + } else if (lookahead === TOK_LBRACE) { + this._match(TOK_LBRACE); + return this._parseMultiselectHash(); + } + }, + + _parseProjectionRHS: function(rbp) { + var right; + if (bindingPower[this._lookahead(0)] < 10) { + right = {type: "Identity"}; + } else if (this._lookahead(0) === TOK_LBRACKET) { + right = this.expression(rbp); + } else if (this._lookahead(0) === TOK_FILTER) { + right = this.expression(rbp); + } else if (this._lookahead(0) === TOK_DOT) { + this._match(TOK_DOT); + right = this._parseDotRHS(rbp); + } else { + var t = this._lookaheadToken(0); + var error = new Error("Sytanx error, unexpected token: " + + t.value + "(" + t.type + ")"); + error.name = "ParserError"; + throw error; + } + return right; + }, + + _parseMultiselectList: function() { + var expressions = []; + while (this._lookahead(0) !== TOK_RBRACKET) { + var expression = this.expression(0); + expressions.push(expression); + if (this._lookahead(0) === TOK_COMMA) { + this._match(TOK_COMMA); + if (this._lookahead(0) === TOK_RBRACKET) { + throw new Error("Unexpected token Rbracket"); + } + } + } + this._match(TOK_RBRACKET); + return {type: "MultiSelectList", children: expressions}; + }, + + _parseMultiselectHash: function() { + var pairs = []; + var identifierTypes = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER]; + var keyToken, keyName, value, node; + for (;;) { + keyToken = this._lookaheadToken(0); + if (identifierTypes.indexOf(keyToken.type) < 0) { + throw new Error("Expecting an identifier token, got: " + + keyToken.type); + } + keyName = keyToken.value; + this._advance(); + this._match(TOK_COLON); + value = this.expression(0); + node = {type: "KeyValuePair", name: keyName, value: value}; + pairs.push(node); + if (this._lookahead(0) === TOK_COMMA) { + this._match(TOK_COMMA); + } else if (this._lookahead(0) === TOK_RBRACE) { + this._match(TOK_RBRACE); + break; + } + } + return {type: "MultiSelectHash", children: pairs}; + } + }; + + + function TreeInterpreter(runtime) { + this.runtime = runtime; + } + + TreeInterpreter.prototype = { + search: function(node, value) { + return this.visit(node, value); + }, + + visit: function(node, value) { + var matched, current, result, first, second, field, left, right, collected, i; + switch (node.type) { + case "Field": + if (value === null ) { + return null; + } else if (isObject(value)) { + field = value[node.name]; + if (field === undefined) { + return null; + } else { + return field; + } + } else { + return null; + } + break; + case "Subexpression": + result = this.visit(node.children[0], value); + for (i = 1; i < node.children.length; i++) { + result = this.visit(node.children[1], result); + if (result === null) { + return null; + } + } + return result; + case "IndexExpression": + left = this.visit(node.children[0], value); + right = this.visit(node.children[1], left); + return right; + case "Index": + if (!isArray(value)) { + return null; + } + var index = node.value; + if (index < 0) { + index = value.length + index; + } + result = value[index]; + if (result === undefined) { + result = null; + } + return result; + case "Slice": + if (!isArray(value)) { + return null; + } + var sliceParams = node.children.slice(0); + var computed = this.computeSliceParams(value.length, sliceParams); + var start = computed[0]; + var stop = computed[1]; + var step = computed[2]; + result = []; + if (step > 0) { + for (i = start; i < stop; i += step) { + result.push(value[i]); + } + } else { + for (i = start; i > stop; i += step) { + result.push(value[i]); + } + } + return result; + case "Projection": + // Evaluate left child. + var base = this.visit(node.children[0], value); + if (!isArray(base)) { + return null; + } + collected = []; + for (i = 0; i < base.length; i++) { + current = this.visit(node.children[1], base[i]); + if (current !== null) { + collected.push(current); + } + } + return collected; + case "ValueProjection": + // Evaluate left child. + base = this.visit(node.children[0], value); + if (!isObject(base)) { + return null; + } + collected = []; + var values = objValues(base); + for (i = 0; i < values.length; i++) { + current = this.visit(node.children[1], values[i]); + if (current !== null) { + collected.push(current); + } + } + return collected; + case "FilterProjection": + base = this.visit(node.children[0], value); + if (!isArray(base)) { + return null; + } + var filtered = []; + var finalResults = []; + for (i = 0; i < base.length; i++) { + matched = this.visit(node.children[2], base[i]); + if (!isFalse(matched)) { + filtered.push(base[i]); + } + } + for (var j = 0; j < filtered.length; j++) { + current = this.visit(node.children[1], filtered[j]); + if (current !== null) { + finalResults.push(current); + } + } + return finalResults; + case "Comparator": + first = this.visit(node.children[0], value); + second = this.visit(node.children[1], value); + switch(node.name) { + case TOK_EQ: + result = strictDeepEqual(first, second); + break; + case TOK_NE: + result = !strictDeepEqual(first, second); + break; + case TOK_GT: + result = first > second; + break; + case TOK_GTE: + result = first >= second; + break; + case TOK_LT: + result = first < second; + break; + case TOK_LTE: + result = first <= second; + break; + default: + throw new Error("Unknown comparator: " + node.name); + } + return result; + case TOK_FLATTEN: + var original = this.visit(node.children[0], value); + if (!isArray(original)) { + return null; + } + var merged = []; + for (i = 0; i < original.length; i++) { + current = original[i]; + if (isArray(current)) { + merged.push.apply(merged, current); + } else { + merged.push(current); + } + } + return merged; + case "Identity": + return value; + case "MultiSelectList": + if (value === null) { + return null; + } + collected = []; + for (i = 0; i < node.children.length; i++) { + collected.push(this.visit(node.children[i], value)); + } + return collected; + case "MultiSelectHash": + if (value === null) { + return null; + } + collected = {}; + var child; + for (i = 0; i < node.children.length; i++) { + child = node.children[i]; + collected[child.name] = this.visit(child.value, value); + } + return collected; + case "OrExpression": + matched = this.visit(node.children[0], value); + if (isFalse(matched)) { + matched = this.visit(node.children[1], value); + } + return matched; + case "AndExpression": + first = this.visit(node.children[0], value); + + if (isFalse(first) === true) { + return first; + } + return this.visit(node.children[1], value); + case "NotExpression": + first = this.visit(node.children[0], value); + return isFalse(first); + case "Literal": + return node.value; + case TOK_PIPE: + left = this.visit(node.children[0], value); + return this.visit(node.children[1], left); + case TOK_CURRENT: + return value; + case "Function": + var resolvedArgs = []; + for (i = 0; i < node.children.length; i++) { + resolvedArgs.push(this.visit(node.children[i], value)); + } + return this.runtime.callFunction(node.name, resolvedArgs); + case "ExpressionReference": + var refNode = node.children[0]; + // Tag the node with a specific attribute so the type + // checker verify the type. + refNode.jmespathType = TOK_EXPREF; + return refNode; + default: + throw new Error("Unknown node type: " + node.type); + } + }, + + computeSliceParams: function(arrayLength, sliceParams) { + var start = sliceParams[0]; + var stop = sliceParams[1]; + var step = sliceParams[2]; + var computed = [null, null, null]; + if (step === null) { + step = 1; + } else if (step === 0) { + var error = new Error("Invalid slice, step cannot be 0"); + error.name = "RuntimeError"; + throw error; + } + var stepValueNegative = step < 0 ? true : false; + + if (start === null) { + start = stepValueNegative ? arrayLength - 1 : 0; + } else { + start = this.capSliceRange(arrayLength, start, step); + } + + if (stop === null) { + stop = stepValueNegative ? -1 : arrayLength; + } else { + stop = this.capSliceRange(arrayLength, stop, step); + } + computed[0] = start; + computed[1] = stop; + computed[2] = step; + return computed; + }, + + capSliceRange: function(arrayLength, actualValue, step) { + if (actualValue < 0) { + actualValue += arrayLength; + if (actualValue < 0) { + actualValue = step < 0 ? -1 : 0; + } + } else if (actualValue >= arrayLength) { + actualValue = step < 0 ? arrayLength - 1 : arrayLength; + } + return actualValue; + } + + }; + + function Runtime(interpreter) { + this._interpreter = interpreter; + this.functionTable = { + // name: [function, <signature>] + // The <signature> can be: + // + // { + // args: [[type1, type2], [type1, type2]], + // variadic: true|false + // } + // + // Each arg in the arg list is a list of valid types + // (if the function is overloaded and supports multiple + // types. If the type is "any" then no type checking + // occurs on the argument. Variadic is optional + // and if not provided is assumed to be false. + abs: {_func: this._functionAbs, _signature: [{types: [TYPE_NUMBER]}]}, + avg: {_func: this._functionAvg, _signature: [{types: [TYPE_ARRAY_NUMBER]}]}, + ceil: {_func: this._functionCeil, _signature: [{types: [TYPE_NUMBER]}]}, + contains: { + _func: this._functionContains, + _signature: [{types: [TYPE_STRING, TYPE_ARRAY]}, + {types: [TYPE_ANY]}]}, + "ends_with": { + _func: this._functionEndsWith, + _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]}, + floor: {_func: this._functionFloor, _signature: [{types: [TYPE_NUMBER]}]}, + length: { + _func: this._functionLength, + _signature: [{types: [TYPE_STRING, TYPE_ARRAY, TYPE_OBJECT]}]}, + map: { + _func: this._functionMap, + _signature: [{types: [TYPE_EXPREF]}, {types: [TYPE_ARRAY]}]}, + max: { + _func: this._functionMax, + _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]}, + "merge": { + _func: this._functionMerge, + _signature: [{types: [TYPE_OBJECT], variadic: true}] + }, + "max_by": { + _func: this._functionMaxBy, + _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}] + }, + sum: {_func: this._functionSum, _signature: [{types: [TYPE_ARRAY_NUMBER]}]}, + "starts_with": { + _func: this._functionStartsWith, + _signature: [{types: [TYPE_STRING]}, {types: [TYPE_STRING]}]}, + min: { + _func: this._functionMin, + _signature: [{types: [TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING]}]}, + "min_by": { + _func: this._functionMinBy, + _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}] + }, + type: {_func: this._functionType, _signature: [{types: [TYPE_ANY]}]}, + keys: {_func: this._functionKeys, _signature: [{types: [TYPE_OBJECT]}]}, + values: {_func: this._functionValues, _signature: [{types: [TYPE_OBJECT]}]}, + sort: {_func: this._functionSort, _signature: [{types: [TYPE_ARRAY_STRING, TYPE_ARRAY_NUMBER]}]}, + "sort_by": { + _func: this._functionSortBy, + _signature: [{types: [TYPE_ARRAY]}, {types: [TYPE_EXPREF]}] + }, + join: { + _func: this._functionJoin, + _signature: [ + {types: [TYPE_STRING]}, + {types: [TYPE_ARRAY_STRING]} + ] + }, + reverse: { + _func: this._functionReverse, + _signature: [{types: [TYPE_STRING, TYPE_ARRAY]}]}, + "to_array": {_func: this._functionToArray, _signature: [{types: [TYPE_ANY]}]}, + "to_string": {_func: this._functionToString, _signature: [{types: [TYPE_ANY]}]}, + "to_number": {_func: this._functionToNumber, _signature: [{types: [TYPE_ANY]}]}, + "not_null": { + _func: this._functionNotNull, + _signature: [{types: [TYPE_ANY], variadic: true}] + } + }; + } + + Runtime.prototype = { + callFunction: function(name, resolvedArgs) { + var functionEntry = this.functionTable[name]; + if (functionEntry === undefined) { + throw new Error("Unknown function: " + name + "()"); + } + this._validateArgs(name, resolvedArgs, functionEntry._signature); + return functionEntry._func.call(this, resolvedArgs); + }, + + _validateArgs: function(name, args, signature) { + // Validating the args requires validating + // the correct arity and the correct type of each arg. + // If the last argument is declared as variadic, then we need + // a minimum number of args to be required. Otherwise it has to + // be an exact amount. + var pluralized; + if (signature[signature.length - 1].variadic) { + if (args.length < signature.length) { + pluralized = signature.length === 1 ? " argument" : " arguments"; + throw new Error("ArgumentError: " + name + "() " + + "takes at least" + signature.length + pluralized + + " but received " + args.length); + } + } else if (args.length !== signature.length) { + pluralized = signature.length === 1 ? " argument" : " arguments"; + throw new Error("ArgumentError: " + name + "() " + + "takes " + signature.length + pluralized + + " but received " + args.length); + } + var currentSpec; + var actualType; + var typeMatched; + for (var i = 0; i < signature.length; i++) { + typeMatched = false; + currentSpec = signature[i].types; + actualType = this._getTypeName(args[i]); + for (var j = 0; j < currentSpec.length; j++) { + if (this._typeMatches(actualType, currentSpec[j], args[i])) { + typeMatched = true; + break; + } + } + if (!typeMatched) { + throw new Error("TypeError: " + name + "() " + + "expected argument " + (i + 1) + + " to be type " + currentSpec + + " but received type " + actualType + + " instead."); + } + } + }, + + _typeMatches: function(actual, expected, argValue) { + if (expected === TYPE_ANY) { + return true; + } + if (expected === TYPE_ARRAY_STRING || + expected === TYPE_ARRAY_NUMBER || + expected === TYPE_ARRAY) { + // The expected type can either just be array, + // or it can require a specific subtype (array of numbers). + // + // The simplest case is if "array" with no subtype is specified. + if (expected === TYPE_ARRAY) { + return actual === TYPE_ARRAY; + } else if (actual === TYPE_ARRAY) { + // Otherwise we need to check subtypes. + // I think this has potential to be improved. + var subtype; + if (expected === TYPE_ARRAY_NUMBER) { + subtype = TYPE_NUMBER; + } else if (expected === TYPE_ARRAY_STRING) { + subtype = TYPE_STRING; + } + for (var i = 0; i < argValue.length; i++) { + if (!this._typeMatches( + this._getTypeName(argValue[i]), subtype, + argValue[i])) { + return false; + } + } + return true; + } + } else { + return actual === expected; + } + }, + _getTypeName: function(obj) { + switch (Object.prototype.toString.call(obj)) { + case "[object String]": + return TYPE_STRING; + case "[object Number]": + return TYPE_NUMBER; + case "[object Array]": + return TYPE_ARRAY; + case "[object Boolean]": + return TYPE_BOOLEAN; + case "[object Null]": + return TYPE_NULL; + case "[object Object]": + // Check if it's an expref. If it has, it's been + // tagged with a jmespathType attr of 'Expref'; + if (obj.jmespathType === TOK_EXPREF) { + return TYPE_EXPREF; + } else { + return TYPE_OBJECT; + } + } + }, + + _functionStartsWith: function(resolvedArgs) { + return resolvedArgs[0].lastIndexOf(resolvedArgs[1]) === 0; + }, + + _functionEndsWith: function(resolvedArgs) { + var searchStr = resolvedArgs[0]; + var suffix = resolvedArgs[1]; + return searchStr.indexOf(suffix, searchStr.length - suffix.length) !== -1; + }, + + _functionReverse: function(resolvedArgs) { + var typeName = this._getTypeName(resolvedArgs[0]); + if (typeName === TYPE_STRING) { + var originalStr = resolvedArgs[0]; + var reversedStr = ""; + for (var i = originalStr.length - 1; i >= 0; i--) { + reversedStr += originalStr[i]; + } + return reversedStr; + } else { + var reversedArray = resolvedArgs[0].slice(0); + reversedArray.reverse(); + return reversedArray; + } + }, + + _functionAbs: function(resolvedArgs) { + return Math.abs(resolvedArgs[0]); + }, + + _functionCeil: function(resolvedArgs) { + return Math.ceil(resolvedArgs[0]); + }, + + _functionAvg: function(resolvedArgs) { + var sum = 0; + var inputArray = resolvedArgs[0]; + for (var i = 0; i < inputArray.length; i++) { + sum += inputArray[i]; + } + return sum / inputArray.length; + }, + + _functionContains: function(resolvedArgs) { + return resolvedArgs[0].indexOf(resolvedArgs[1]) >= 0; + }, + + _functionFloor: function(resolvedArgs) { + return Math.floor(resolvedArgs[0]); + }, + + _functionLength: function(resolvedArgs) { + if (!isObject(resolvedArgs[0])) { + return resolvedArgs[0].length; + } else { + // As far as I can tell, there's no way to get the length + // of an object without O(n) iteration through the object. + return Object.keys(resolvedArgs[0]).length; + } + }, + + _functionMap: function(resolvedArgs) { + var mapped = []; + var interpreter = this._interpreter; + var exprefNode = resolvedArgs[0]; + var elements = resolvedArgs[1]; + for (var i = 0; i < elements.length; i++) { + mapped.push(interpreter.visit(exprefNode, elements[i])); + } + return mapped; + }, + + _functionMerge: function(resolvedArgs) { + var merged = {}; + for (var i = 0; i < resolvedArgs.length; i++) { + var current = resolvedArgs[i]; + for (var key in current) { + merged[key] = current[key]; + } + } + return merged; + }, + + _functionMax: function(resolvedArgs) { + if (resolvedArgs[0].length > 0) { + var typeName = this._getTypeName(resolvedArgs[0][0]); + if (typeName === TYPE_NUMBER) { + return Math.max.apply(Math, resolvedArgs[0]); + } else { + var elements = resolvedArgs[0]; + var maxElement = elements[0]; + for (var i = 1; i < elements.length; i++) { + if (maxElement.localeCompare(elements[i]) < 0) { + maxElement = elements[i]; + } + } + return maxElement; + } + } else { + return null; + } + }, + + _functionMin: function(resolvedArgs) { + if (resolvedArgs[0].length > 0) { + var typeName = this._getTypeName(resolvedArgs[0][0]); + if (typeName === TYPE_NUMBER) { + return Math.min.apply(Math, resolvedArgs[0]); + } else { + var elements = resolvedArgs[0]; + var minElement = elements[0]; + for (var i = 1; i < elements.length; i++) { + if (elements[i].localeCompare(minElement) < 0) { + minElement = elements[i]; + } + } + return minElement; + } + } else { + return null; + } + }, + + _functionSum: function(resolvedArgs) { + var sum = 0; + var listToSum = resolvedArgs[0]; + for (var i = 0; i < listToSum.length; i++) { + sum += listToSum[i]; + } + return sum; + }, + + _functionType: function(resolvedArgs) { + switch (this._getTypeName(resolvedArgs[0])) { + case TYPE_NUMBER: + return "number"; + case TYPE_STRING: + return "string"; + case TYPE_ARRAY: + return "array"; + case TYPE_OBJECT: + return "object"; + case TYPE_BOOLEAN: + return "boolean"; + case TYPE_EXPREF: + return "expref"; + case TYPE_NULL: + return "null"; + } + }, + + _functionKeys: function(resolvedArgs) { + return Object.keys(resolvedArgs[0]); + }, + + _functionValues: function(resolvedArgs) { + var obj = resolvedArgs[0]; + var keys = Object.keys(obj); + var values = []; + for (var i = 0; i < keys.length; i++) { + values.push(obj[keys[i]]); + } + return values; + }, + + _functionJoin: function(resolvedArgs) { + var joinChar = resolvedArgs[0]; + var listJoin = resolvedArgs[1]; + return listJoin.join(joinChar); + }, + + _functionToArray: function(resolvedArgs) { + if (this._getTypeName(resolvedArgs[0]) === TYPE_ARRAY) { + return resolvedArgs[0]; + } else { + return [resolvedArgs[0]]; + } + }, + + _functionToString: function(resolvedArgs) { + if (this._getTypeName(resolvedArgs[0]) === TYPE_STRING) { + return resolvedArgs[0]; + } else { + return JSON.stringify(resolvedArgs[0]); + } + }, + + _functionToNumber: function(resolvedArgs) { + var typeName = this._getTypeName(resolvedArgs[0]); + var convertedValue; + if (typeName === TYPE_NUMBER) { + return resolvedArgs[0]; + } else if (typeName === TYPE_STRING) { + convertedValue = +resolvedArgs[0]; + if (!isNaN(convertedValue)) { + return convertedValue; + } + } + return null; + }, + + _functionNotNull: function(resolvedArgs) { + for (var i = 0; i < resolvedArgs.length; i++) { + if (this._getTypeName(resolvedArgs[i]) !== TYPE_NULL) { + return resolvedArgs[i]; + } + } + return null; + }, + + _functionSort: function(resolvedArgs) { + var sortedArray = resolvedArgs[0].slice(0); + sortedArray.sort(); + return sortedArray; + }, + + _functionSortBy: function(resolvedArgs) { + var sortedArray = resolvedArgs[0].slice(0); + if (sortedArray.length === 0) { + return sortedArray; + } + var interpreter = this._interpreter; + var exprefNode = resolvedArgs[1]; + var requiredType = this._getTypeName( + interpreter.visit(exprefNode, sortedArray[0])); + if ([TYPE_NUMBER, TYPE_STRING].indexOf(requiredType) < 0) { + throw new Error("TypeError"); + } + var that = this; + // In order to get a stable sort out of an unstable + // sort algorithm, we decorate/sort/undecorate (DSU) + // by creating a new list of [index, element] pairs. + // In the cmp function, if the evaluated elements are + // equal, then the index will be used as the tiebreaker. + // After the decorated list has been sorted, it will be + // undecorated to extract the original elements. + var decorated = []; + for (var i = 0; i < sortedArray.length; i++) { + decorated.push([i, sortedArray[i]]); + } + decorated.sort(function(a, b) { + var exprA = interpreter.visit(exprefNode, a[1]); + var exprB = interpreter.visit(exprefNode, b[1]); + if (that._getTypeName(exprA) !== requiredType) { + throw new Error( + "TypeError: expected " + requiredType + ", received " + + that._getTypeName(exprA)); + } else if (that._getTypeName(exprB) !== requiredType) { + throw new Error( + "TypeError: expected " + requiredType + ", received " + + that._getTypeName(exprB)); + } + if (exprA > exprB) { + return 1; + } else if (exprA < exprB) { + return -1; + } else { + // If they're equal compare the items by their + // order to maintain relative order of equal keys + // (i.e. to get a stable sort). + return a[0] - b[0]; + } + }); + // Undecorate: extract out the original list elements. + for (var j = 0; j < decorated.length; j++) { + sortedArray[j] = decorated[j][1]; + } + return sortedArray; + }, + + _functionMaxBy: function(resolvedArgs) { + var exprefNode = resolvedArgs[1]; + var resolvedArray = resolvedArgs[0]; + var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]); + var maxNumber = -Infinity; + var maxRecord; + var current; + for (var i = 0; i < resolvedArray.length; i++) { + current = keyFunction(resolvedArray[i]); + if (current > maxNumber) { + maxNumber = current; + maxRecord = resolvedArray[i]; + } + } + return maxRecord; + }, + + _functionMinBy: function(resolvedArgs) { + var exprefNode = resolvedArgs[1]; + var resolvedArray = resolvedArgs[0]; + var keyFunction = this.createKeyFunction(exprefNode, [TYPE_NUMBER, TYPE_STRING]); + var minNumber = Infinity; + var minRecord; + var current; + for (var i = 0; i < resolvedArray.length; i++) { + current = keyFunction(resolvedArray[i]); + if (current < minNumber) { + minNumber = current; + minRecord = resolvedArray[i]; + } + } + return minRecord; + }, + + createKeyFunction: function(exprefNode, allowedTypes) { + var that = this; + var interpreter = this._interpreter; + var keyFunc = function(x) { + var current = interpreter.visit(exprefNode, x); + if (allowedTypes.indexOf(that._getTypeName(current)) < 0) { + var msg = "TypeError: expected one of " + allowedTypes + + ", received " + that._getTypeName(current); + throw new Error(msg); + } + return current; + }; + return keyFunc; + } + + }; + + function compile(stream) { + var parser = new Parser(); + var ast = parser.parse(stream); + return ast; + } + + function tokenize(stream) { + var lexer = new Lexer(); + return lexer.tokenize(stream); + } + + function search(data, expression) { + var parser = new Parser(); + // This needs to be improved. Both the interpreter and runtime depend on + // each other. The runtime needs the interpreter to support exprefs. + // There's likely a clean way to avoid the cyclic dependency. + var runtime = new Runtime(); + var interpreter = new TreeInterpreter(runtime); + runtime._interpreter = interpreter; + var node = parser.parse(expression); + return interpreter.search(node, data); + } + + exports.tokenize = tokenize; + exports.compile = compile; + exports.search = search; + exports.strictDeepEqual = strictDeepEqual; +})(typeof exports === "undefined" ? this.jmespath = {} : exports); diff --git a/src/vitis/client/javascript/externs/jmespath/jmespath.min.js b/src/vitis/client/javascript/externs/jmespath/jmespath.min.js new file mode 100644 index 0000000000000000000000000000000000000000..ae76a6df3bdcc74574c5fee3db69207dd81be749 --- /dev/null +++ b/src/vitis/client/javascript/externs/jmespath/jmespath.min.js @@ -0,0 +1,2 @@ +/*! jmespath 2016-03-22 */ +!function(a){"use strict";function b(a){return null!==a?"[object Array]"===Object.prototype.toString.call(a):!1}function c(a){return null!==a?"[object Object]"===Object.prototype.toString.call(a):!1}function d(a,e){if(a===e)return!0;var f=Object.prototype.toString.call(a);if(f!==Object.prototype.toString.call(e))return!1;if(b(a)===!0){if(a.length!==e.length)return!1;for(var g=0;g<a.length;g++)if(d(a[g],e[g])===!1)return!1;return!0}if(c(a)===!0){var h={};for(var i in a)if(hasOwnProperty.call(a,i)){if(d(a[i],e[i])===!1)return!1;h[i]=!0}for(var j in e)if(hasOwnProperty.call(e,j)&&h[j]!==!0)return!1;return!0}return!1}function e(a){if(""===a||a===!1||null===a)return!0;if(b(a)&&0===a.length)return!0;if(c(a)){for(var d in a)if(a.hasOwnProperty(d))return!1;return!0}return!1}function f(a){for(var b=Object.keys(a),c=[],d=0;d<b.length;d++)c.push(a[b[d]]);return c}function g(a){return a>="a"&&"z">=a||a>="A"&&"Z">=a||"_"===a}function h(a){return a>="0"&&"9">=a||"-"===a}function i(a){return a>="a"&&"z">=a||a>="A"&&"Z">=a||a>="0"&&"9">=a||"_"===a}function j(){}function k(){}function l(a){this.runtime=a}function m(a){this.a=a,this.functionTable={abs:{b:this.c,d:[{types:[r]}]},avg:{b:this.e,d:[{types:[z]}]},ceil:{b:this.f,d:[{types:[r]}]},contains:{b:this.g,d:[{types:[t,u]},{types:[s]}]},ends_with:{b:this.h,d:[{types:[t]},{types:[t]}]},floor:{b:this.i,d:[{types:[r]}]},length:{b:this.j,d:[{types:[t,u,v]}]},map:{b:this.k,d:[{types:[x]},{types:[u]}]},max:{b:this.l,d:[{types:[z,A]}]},merge:{b:this.m,d:[{types:[v],variadic:!0}]},max_by:{b:this.n,d:[{types:[u]},{types:[x]}]},sum:{b:this.o,d:[{types:[z]}]},starts_with:{b:this.p,d:[{types:[t]},{types:[t]}]},min:{b:this.q,d:[{types:[z,A]}]},min_by:{b:this.r,d:[{types:[u]},{types:[x]}]},type:{b:this.s,d:[{types:[s]}]},keys:{b:this.t,d:[{types:[v]}]},values:{b:this.u,d:[{types:[v]}]},sort:{b:this.v,d:[{types:[A,z]}]},sort_by:{b:this.w,d:[{types:[u]},{types:[x]}]},join:{b:this.x,d:[{types:[t]},{types:[A]}]},reverse:{b:this.y,d:[{types:[t,u]}]},to_array:{b:this.z,d:[{types:[s]}]},to_string:{b:this.A,d:[{types:[s]}]},to_number:{b:this.B,d:[{types:[s]}]},not_null:{b:this.C,d:[{types:[s],variadic:!0}]}}}function n(a){var b=new k,c=b.parse(a);return c}function o(a){var b=new j;return b.tokenize(a)}function p(a,b){var c=new k,d=new m,e=new l(d);d.a=e;var f=c.parse(b);return e.search(f,a)}var q;q="function"==typeof String.prototype.trimLeft?function(a){return a.trimLeft()}:function(a){return a.match(/^\s*(.*)/)[1]};var r=0,s=1,t=2,u=3,v=4,w=5,x=6,y=7,z=8,A=9,B="EOF",C="UnquotedIdentifier",D="QuotedIdentifier",E="Rbracket",F="Rparen",G="Comma",H="Colon",I="Rbrace",J="Number",K="Current",L="Expref",M="Pipe",N="Or",O="And",P="EQ",Q="GT",R="LT",S="GTE",T="LTE",U="NE",V="Flatten",W="Star",X="Filter",Y="Dot",Z="Not",$="Lbrace",_="Lbracket",aa="Lparen",ba="Literal",ca={".":Y,"*":W,",":G,":":H,"{":$,"}":I,"]":E,"(":aa,")":F,"@":K},da={"<":!0,">":!0,"=":!0,"!":!0},ea={" ":!0," ":!0,"\n":!0};j.prototype={tokenize:function(a){var b=[];this.D=0;for(var c,d,e;this.D<a.length;)if(g(a[this.D]))c=this.D,d=this.E(a),b.push({type:C,value:d,start:c});else if(void 0!==ca[a[this.D]])b.push({type:ca[a[this.D]],value:a[this.D],start:this.D}),this.D++;else if(h(a[this.D]))e=this.F(a),b.push(e);else if("["===a[this.D])e=this.G(a),b.push(e);else if('"'===a[this.D])c=this.D,d=this.H(a),b.push({type:D,value:d,start:c});else if("'"===a[this.D])c=this.D,d=this.I(a),b.push({type:ba,value:d,start:c});else if("`"===a[this.D]){c=this.D;var f=this.J(a);b.push({type:ba,value:f,start:c})}else if(void 0!==da[a[this.D]])b.push(this.K(a));else if(void 0!==ea[a[this.D]])this.D++;else if("&"===a[this.D])c=this.D,this.D++,"&"===a[this.D]?(this.D++,b.push({type:O,value:"&&",start:c})):b.push({type:L,value:"&",start:c});else{if("|"!==a[this.D]){var i=new Error("Unknown character:"+a[this.D]);throw i.name="LexerError",i}c=this.D,this.D++,"|"===a[this.D]?(this.D++,b.push({type:N,value:"||",start:c})):b.push({type:M,value:"|",start:c})}return b},E:function(a){var b=this.D;for(this.D++;this.D<a.length&&i(a[this.D]);)this.D++;return a.slice(b,this.D)},H:function(a){var b=this.D;this.D++;for(var c=a.length;'"'!==a[this.D]&&this.D<c;){var d=this.D;"\\"!==a[d]||"\\"!==a[d+1]&&'"'!==a[d+1]?d++:d+=2,this.D=d}return this.D++,JSON.parse(a.slice(b,this.D))},I:function(a){var b=this.D;this.D++;for(var c=a.length;"'"!==a[this.D]&&this.D<c;){var d=this.D;"\\"!==a[d]||"\\"!==a[d+1]&&"'"!==a[d+1]?d++:d+=2,this.D=d}this.D++;var e=a.slice(b+1,this.D-1);return e.replace("\\'","'")},F:function(a){var b=this.D;this.D++;for(var c=a.length;h(a[this.D])&&this.D<c;)this.D++;var d=parseInt(a.slice(b,this.D));return{type:J,value:d,start:b}},G:function(a){var b=this.D;return this.D++,"?"===a[this.D]?(this.D++,{type:X,value:"[?",start:b}):"]"===a[this.D]?(this.D++,{type:V,value:"[]",start:b}):{type:_,value:"[",start:b}},K:function(a){var b=this.D,c=a[b];return this.D++,"!"===c?"="===a[this.D]?(this.D++,{type:U,value:"!=",start:b}):{type:Z,value:"!",start:b}:"<"===c?"="===a[this.D]?(this.D++,{type:T,value:"<=",start:b}):{type:R,value:"<",start:b}:">"===c?"="===a[this.D]?(this.D++,{type:S,value:">=",start:b}):{type:Q,value:">",start:b}:"="===c&&"="===a[this.D]?(this.D++,{type:P,value:"==",start:b}):void 0},J:function(a){this.D++;for(var b,c=this.D,d=a.length;"`"!==a[this.D]&&this.D<d;){var e=this.D;"\\"!==a[e]||"\\"!==a[e+1]&&"`"!==a[e+1]?e++:e+=2,this.D=e}var f=q(a.slice(c,this.D));return f=f.replace("\\`","`"),b=this.L(f)?JSON.parse(f):JSON.parse('"'+f+'"'),this.D++,b},L:function(a){var b='[{"',c=["true","false","null"],d="-0123456789";if(""===a)return!1;if(b.indexOf(a[0])>=0)return!0;if(c.indexOf(a)>=0)return!0;if(!(d.indexOf(a[0])>=0))return!1;try{return JSON.parse(a),!0}catch(e){return!1}}};var fa={};fa[B]=0,fa[C]=0,fa[D]=0,fa[E]=0,fa[F]=0,fa[G]=0,fa[I]=0,fa[J]=0,fa[K]=0,fa[L]=0,fa[M]=1,fa[N]=2,fa[O]=3,fa[P]=5,fa[Q]=5,fa[R]=5,fa[S]=5,fa[T]=5,fa[U]=5,fa[V]=9,fa[W]=20,fa[X]=21,fa[Y]=40,fa[Z]=45,fa[$]=50,fa[_]=55,fa[aa]=60,k.prototype={parse:function(a){this.M(a),this.index=0;var b=this.expression(0);if(this.N(0)!==B){var c=this.O(0),d=new Error("Unexpected token type: "+c.type+", value: "+c.value);throw d.name="ParserError",d}return b},M:function(a){var b=new j,c=b.tokenize(a);c.push({type:B,value:"",start:a.length}),this.tokens=c},expression:function(a){var b=this.O(0);this.P();for(var c=this.nud(b),d=this.N(0);a<fa[d];)this.P(),c=this.led(d,c),d=this.N(0);return c},N:function(a){return this.tokens[this.index+a].type},O:function(a){return this.tokens[this.index+a]},P:function(){this.index++},nud:function(a){var b,c,d;switch(a.type){case ba:return{type:"Literal",value:a.value};case C:return{type:"Field",name:a.value};case D:var e={type:"Field",name:a.value};if(this.N(0)===aa)throw new Error("Quoted identifier not allowed for function names.");return e;case Z:return c=this.expression(fa.Not),{type:"NotExpression",children:[c]};case W:return b={type:"Identity"},c=null,c=this.N(0)===E?{type:"Identity"}:this.Q(fa.Star),{type:"ValueProjection",children:[b,c]};case X:return this.led(a.type,{type:"Identity"});case $:return this.R();case V:return b={type:V,children:[{type:"Identity"}]},c=this.Q(fa.Flatten),{type:"Projection",children:[b,c]};case _:return this.N(0)===J||this.N(0)===H?(c=this.S(),this.T({type:"Identity"},c)):this.N(0)===W&&this.N(1)===E?(this.P(),this.P(),c=this.Q(fa.Star),{type:"Projection",children:[{type:"Identity"},c]}):this.U();case K:return{type:K};case L:return d=this.expression(fa.Expref),{type:"ExpressionReference",children:[d]};case aa:for(var f=[];this.N(0)!==F;)this.N(0)===K?(d={type:K},this.P()):d=this.expression(0),f.push(d);return this.V(F),f[0];default:this.W(a)}},led:function(a,b){var c;switch(a){case Y:var d=fa.Dot;return this.N(0)!==W?(c=this.X(d),{type:"Subexpression",children:[b,c]}):(this.P(),c=this.Q(d),{type:"ValueProjection",children:[b,c]});case M:return c=this.expression(fa.Pipe),{type:M,children:[b,c]};case N:return c=this.expression(fa.Or),{type:"OrExpression",children:[b,c]};case O:return c=this.expression(fa.And),{type:"AndExpression",children:[b,c]};case aa:for(var e,f,g=b.name,h=[];this.N(0)!==F;)this.N(0)===K?(e={type:K},this.P()):e=this.expression(0),this.N(0)===G&&this.V(G),h.push(e);return this.V(F),f={type:"Function",name:g,children:h};case X:var i=this.expression(0);return this.V(E),c=this.N(0)===V?{type:"Identity"}:this.Q(fa.Filter),{type:"FilterProjection",children:[b,c,i]};case V:var j={type:V,children:[b]},k=this.Q(fa.Flatten);return{type:"Projection",children:[j,k]};case P:case U:case Q:case S:case R:case T:return this.Y(b,a);case _:var l=this.O(0);return l.type===J||l.type===H?(c=this.S(),this.T(b,c)):(this.V(W),this.V(E),c=this.Q(fa.Star),{type:"Projection",children:[b,c]});default:this.W(this.O(0))}},V:function(a){if(this.N(0)!==a){var b=this.O(0),c=new Error("Expected "+a+", got: "+b.type);throw c.name="ParserError",c}this.P()},W:function(a){var b=new Error("Invalid token ("+a.type+'): "'+a.value+'"');throw b.name="ParserError",b},S:function(){if(this.N(0)===H||this.N(1)===H)return this.Z();var a={type:"Index",value:this.O(0).value};return this.P(),this.V(E),a},T:function(a,b){var c={type:"IndexExpression",children:[a,b]};return"Slice"===b.type?{type:"Projection",children:[c,this.Q(fa.Star)]}:c},Z:function(){for(var a=[null,null,null],b=0,c=this.N(0);c!==E&&3>b;){if(c===H)b++,this.P();else{if(c!==J){var d=this.N(0),e=new Error("Syntax error, unexpected token: "+d.value+"("+d.type+")");throw e.name="Parsererror",e}a[b]=this.O(0).value,this.P()}c=this.N(0)}return this.V(E),{type:"Slice",children:a}},Y:function(a,b){var c=this.expression(fa[b]);return{type:"Comparator",name:b,children:[a,c]}},X:function(a){var b=this.N(0),c=[C,D,W];return c.indexOf(b)>=0?this.expression(a):b===_?(this.V(_),this.U()):b===$?(this.V($),this.R()):void 0},Q:function(a){var b;if(fa[this.N(0)]<10)b={type:"Identity"};else if(this.N(0)===_)b=this.expression(a);else if(this.N(0)===X)b=this.expression(a);else{if(this.N(0)!==Y){var c=this.O(0),d=new Error("Sytanx error, unexpected token: "+c.value+"("+c.type+")");throw d.name="ParserError",d}this.V(Y),b=this.X(a)}return b},U:function(){for(var a=[];this.N(0)!==E;){var b=this.expression(0);if(a.push(b),this.N(0)===G&&(this.V(G),this.N(0)===E))throw new Error("Unexpected token Rbracket")}return this.V(E),{type:"MultiSelectList",children:a}},R:function(){for(var a,b,c,d,e=[],f=[C,D];;){if(a=this.O(0),f.indexOf(a.type)<0)throw new Error("Expecting an identifier token, got: "+a.type);if(b=a.value,this.P(),this.V(H),c=this.expression(0),d={type:"KeyValuePair",name:b,value:c},e.push(d),this.N(0)===G)this.V(G);else if(this.N(0)===I){this.V(I);break}}return{type:"MultiSelectHash",children:e}}},l.prototype={search:function(a,b){return this.visit(a,b)},visit:function(a,g){var h,i,j,k,l,m,n,o,p,q;switch(a.type){case"Field":return null===g?null:c(g)?(m=g[a.name],void 0===m?null:m):null;case"Subexpression":for(j=this.visit(a.children[0],g),q=1;q<a.children.length;q++)if(j=this.visit(a.children[1],j),null===j)return null;return j;case"IndexExpression":return n=this.visit(a.children[0],g),o=this.visit(a.children[1],n);case"Index":if(!b(g))return null;var r=a.value;return 0>r&&(r=g.length+r),j=g[r],void 0===j&&(j=null),j;case"Slice":if(!b(g))return null;var s=a.children.slice(0),t=this.computeSliceParams(g.length,s),u=t[0],v=t[1],w=t[2];if(j=[],w>0)for(q=u;v>q;q+=w)j.push(g[q]);else for(q=u;q>v;q+=w)j.push(g[q]);return j;case"Projection":var x=this.visit(a.children[0],g);if(!b(x))return null;for(p=[],q=0;q<x.length;q++)i=this.visit(a.children[1],x[q]),null!==i&&p.push(i);return p;case"ValueProjection":if(x=this.visit(a.children[0],g),!c(x))return null;p=[];var y=f(x);for(q=0;q<y.length;q++)i=this.visit(a.children[1],y[q]),null!==i&&p.push(i);return p;case"FilterProjection":if(x=this.visit(a.children[0],g),!b(x))return null;var z=[],A=[];for(q=0;q<x.length;q++)h=this.visit(a.children[2],x[q]),e(h)||z.push(x[q]);for(var B=0;B<z.length;B++)i=this.visit(a.children[1],z[B]),null!==i&&A.push(i);return A;case"Comparator":switch(k=this.visit(a.children[0],g),l=this.visit(a.children[1],g),a.name){case P:j=d(k,l);break;case U:j=!d(k,l);break;case Q:j=k>l;break;case S:j=k>=l;break;case R:j=l>k;break;case T:j=l>=k;break;default:throw new Error("Unknown comparator: "+a.name)}return j;case V:var C=this.visit(a.children[0],g);if(!b(C))return null;var D=[];for(q=0;q<C.length;q++)i=C[q],b(i)?D.push.apply(D,i):D.push(i);return D;case"Identity":return g;case"MultiSelectList":if(null===g)return null;for(p=[],q=0;q<a.children.length;q++)p.push(this.visit(a.children[q],g));return p;case"MultiSelectHash":if(null===g)return null;p={};var E;for(q=0;q<a.children.length;q++)E=a.children[q],p[E.name]=this.visit(E.value,g);return p;case"OrExpression":return h=this.visit(a.children[0],g),e(h)&&(h=this.visit(a.children[1],g)),h;case"AndExpression":return k=this.visit(a.children[0],g),e(k)===!0?k:this.visit(a.children[1],g);case"NotExpression":return k=this.visit(a.children[0],g),e(k);case"Literal":return a.value;case M:return n=this.visit(a.children[0],g),this.visit(a.children[1],n);case K:return g;case"Function":var F=[];for(q=0;q<a.children.length;q++)F.push(this.visit(a.children[q],g));return this.runtime.callFunction(a.name,F);case"ExpressionReference":var G=a.children[0];return G.jmespathType=L,G;default:throw new Error("Unknown node type: "+a.type)}},computeSliceParams:function(a,b){var c=b[0],d=b[1],e=b[2],f=[null,null,null];if(null===e)e=1;else if(0===e){var g=new Error("Invalid slice, step cannot be 0");throw g.name="RuntimeError",g}var h=0>e?!0:!1;return c=null===c?h?a-1:0:this.capSliceRange(a,c,e),d=null===d?h?-1:a:this.capSliceRange(a,d,e),f[0]=c,f[1]=d,f[2]=e,f},capSliceRange:function(a,b,c){return 0>b?(b+=a,0>b&&(b=0>c?-1:0)):b>=a&&(b=0>c?a-1:a),b}},m.prototype={callFunction:function(a,b){var c=this.functionTable[a];if(void 0===c)throw new Error("Unknown function: "+a+"()");return this.$(a,b,c.d),c.b.call(this,b)},$:function(a,b,c){var d;if(c[c.length-1].variadic){if(b.length<c.length)throw d=1===c.length?" argument":" arguments",new Error("ArgumentError: "+a+"() takes at least"+c.length+d+" but received "+b.length)}else if(b.length!==c.length)throw d=1===c.length?" argument":" arguments",new Error("ArgumentError: "+a+"() takes "+c.length+d+" but received "+b.length);for(var e,f,g,h=0;h<c.length;h++){g=!1,e=c[h].types,f=this._(b[h]);for(var i=0;i<e.length;i++)if(this.aa(f,e[i],b[h])){g=!0;break}if(!g)throw new Error("TypeError: "+a+"() expected argument "+(h+1)+" to be type "+e+" but received type "+f+" instead.")}},aa:function(a,b,c){if(b===s)return!0;if(b!==A&&b!==z&&b!==u)return a===b;if(b===u)return a===u;if(a===u){var d;b===z?d=r:b===A&&(d=t);for(var e=0;e<c.length;e++)if(!this.aa(this._(c[e]),d,c[e]))return!1;return!0}},_:function(a){switch(Object.prototype.toString.call(a)){case"[object String]":return t;case"[object Number]":return r;case"[object Array]":return u;case"[object Boolean]":return w;case"[object Null]":return y;case"[object Object]":return a.jmespathType===L?x:v}},p:function(a){return 0===a[0].lastIndexOf(a[1])},h:function(a){var b=a[0],c=a[1];return-1!==b.indexOf(c,b.length-c.length)},y:function(a){var b=this._(a[0]);if(b===t){for(var c=a[0],d="",e=c.length-1;e>=0;e--)d+=c[e];return d}var f=a[0].slice(0);return f.reverse(),f},c:function(a){return Math.abs(a[0])},f:function(a){return Math.ceil(a[0])},e:function(a){for(var b=0,c=a[0],d=0;d<c.length;d++)b+=c[d];return b/c.length},g:function(a){return a[0].indexOf(a[1])>=0},i:function(a){return Math.floor(a[0])},j:function(a){return c(a[0])?Object.keys(a[0]).length:a[0].length},k:function(a){for(var b=[],c=this.a,d=a[0],e=a[1],f=0;f<e.length;f++)b.push(c.visit(d,e[f]));return b},m:function(a){for(var b={},c=0;c<a.length;c++){var d=a[c];for(var e in d)b[e]=d[e]}return b},l:function(a){if(a[0].length>0){var b=this._(a[0][0]);if(b===r)return Math.max.apply(Math,a[0]);for(var c=a[0],d=c[0],e=1;e<c.length;e++)d.localeCompare(c[e])<0&&(d=c[e]);return d}return null},q:function(a){if(a[0].length>0){var b=this._(a[0][0]);if(b===r)return Math.min.apply(Math,a[0]);for(var c=a[0],d=c[0],e=1;e<c.length;e++)c[e].localeCompare(d)<0&&(d=c[e]);return d}return null},o:function(a){for(var b=0,c=a[0],d=0;d<c.length;d++)b+=c[d];return b},s:function(a){switch(this._(a[0])){case r:return"number";case t:return"string";case u:return"array";case v:return"object";case w:return"boolean";case x:return"expref";case y:return"null"}},t:function(a){return Object.keys(a[0])},u:function(a){for(var b=a[0],c=Object.keys(b),d=[],e=0;e<c.length;e++)d.push(b[c[e]]);return d},x:function(a){var b=a[0],c=a[1];return c.join(b)},z:function(a){return this._(a[0])===u?a[0]:[a[0]]},A:function(a){return this._(a[0])===t?a[0]:JSON.stringify(a[0])},B:function(a){var b,c=this._(a[0]);return c===r?a[0]:c!==t||(b=+a[0],isNaN(b))?null:b},C:function(a){for(var b=0;b<a.length;b++)if(this._(a[b])!==y)return a[b];return null},v:function(a){var b=a[0].slice(0);return b.sort(),b},w:function(a){var b=a[0].slice(0);if(0===b.length)return b;var c=this.a,d=a[1],e=this._(c.visit(d,b[0]));if([r,t].indexOf(e)<0)throw new Error("TypeError");for(var f=this,g=[],h=0;h<b.length;h++)g.push([h,b[h]]);g.sort(function(a,b){var g=c.visit(d,a[1]),h=c.visit(d,b[1]);if(f._(g)!==e)throw new Error("TypeError: expected "+e+", received "+f._(g));if(f._(h)!==e)throw new Error("TypeError: expected "+e+", received "+f._(h));return g>h?1:h>g?-1:a[0]-b[0]});for(var i=0;i<g.length;i++)b[i]=g[i][1];return b},n:function(a){for(var b,c,d=a[1],e=a[0],f=this.createKeyFunction(d,[r,t]),g=-(1/0),h=0;h<e.length;h++)c=f(e[h]),c>g&&(g=c,b=e[h]);return b},r:function(a){for(var b,c,d=a[1],e=a[0],f=this.createKeyFunction(d,[r,t]),g=1/0,h=0;h<e.length;h++)c=f(e[h]),g>c&&(g=c,b=e[h]);return b},createKeyFunction:function(a,b){var c=this,d=this.a,e=function(e){var f=d.visit(a,e);if(b.indexOf(c._(f))<0){var g="TypeError: expected one of "+b+", received "+c._(f);throw new Error(g)}return f};return e}},a.tokenize=o,a.compile=n,a.search=p,a.strictDeepEqual=d}("undefined"==typeof exports?this.jmespath={}:exports); \ No newline at end of file diff --git a/src/vitis/client/javascript/externs/studio/javascript/app/FormBuilder.js b/src/vitis/client/javascript/externs/studio/javascript/app/FormBuilder.js index c1aa72c7576c4b3d8b1b6f493aedfd37b4149aef..900ff40320ab0bc297f3d0f593fbb7267d4405f1 100755 --- a/src/vitis/client/javascript/externs/studio/javascript/app/FormBuilder.js +++ b/src/vitis/client/javascript/externs/studio/javascript/app/FormBuilder.js @@ -240,7 +240,7 @@ oVFB.cleanFormToSave = function(oJson){ if(oVFB.getApplication() !== "wab"){ return oJson; } - + var oForm; for (var sFormName in oJson) { @@ -485,7 +485,7 @@ oVFB.setTreeDnD = function (str) { /** * getter - * @return {string} Vitis's token + * @return {string} Vitis's token * @export */ oVFB.getToken = function () { @@ -502,7 +502,7 @@ oVFB['setToken'] = function (token) { /** * getter - * @return {string} Vitis's token + * @return {string} Vitis's token * @export */ oVFB.getId = function () { @@ -719,7 +719,7 @@ oVFB.studioMainController = function ($scope, $q, $timeout, $rootScope) { 'onLoad': function (cm_) { /*$scope.$watch("editedDatasource.dataType", function (value) { if (value === 'text') { - + $timeout(function () { $scope.refresh = true; $timeout(function () { @@ -852,7 +852,7 @@ oVFB.studioMainController = function ($scope, $q, $timeout, $rootScope) { $scope['sToken'] = sessionStorage['session_token']; /** - * + * */ $scope['wab'] = {}; @@ -1014,7 +1014,7 @@ oVFB.studioMainController = function ($scope, $q, $timeout, $rootScope) { } /** - * !Important! utilisation d'un pointeur permet que les changements + * !Important! utilisation d'un pointeur permet que les changements * de oTestTabFormDefinition affecteront getJsonOutput */ $scope['oTestTabFormDefinition'] = data; @@ -1061,7 +1061,7 @@ oVFB.studioMainController = function ($scope, $q, $timeout, $rootScope) { } /** - * !Important! utilisation d'un pointeur permet que les changements + * !Important! utilisation d'un pointeur permet que les changements * de oTestWabFormDefinition affecteront getJsonOutput */ $scope['oTestWabFormDefinition'] = data; @@ -1139,7 +1139,7 @@ oVFB.module.filter('sortStates', function(){ oVFB.studioMainController.prototype.showTabsManagerModal = function () { oVFB.log('studioMainController.showTabsManagerModal'); - // Modification du studio + // Modification du studio oVFB.Update = true; var $scope = this.$scope_; @@ -1214,7 +1214,7 @@ oVFB.studioMainController.prototype.generateWabMethod = function(){ }); // add label in lang.json string - oLangAddons[sLabelKeyMethod] = this.$scope_["aBoMethods"][i]["label"]; + oLangAddons[sLabelKeyMethod] = this.$scope_["aBoMethods"][i]["label"]; } } } @@ -1632,7 +1632,7 @@ oVFB.studioMainController.prototype.updateTestWabForm = function () { * @export */ oVFB.studioMainController.prototype.showDatasourcesModal = function () { - // Modification du studio + // Modification du studio oVFB.Update = true; oVFB.log('studioMainController.showDatasourcesModal'); @@ -2207,6 +2207,51 @@ oVFB.studioMainController.prototype.addSource = function (newSource) { this.saveDatasource(source); this.setStudioGridDatasources(); + } else if (newSource['dataType'] === 'externalAPI') { + + if (!goog.isDef(newSource['id'])) { + console.error('newSource.id not defined'); + return 0; + } + if (!goog.isDef(newSource['name'])) { + console.error('newSource.name not defined'); + return 0; + } + if (goog.isDef($scope['datasources'][newSource['id']])) { + bootbox.alert(this['text']['Element']['Component']['Select']['Datasource_Exists']); + return 0; + } + if (!goog.isDef(newSource['api_url'])) { + bootbox.alert(this['text']['Element']['Component']['Select']['API_URL_undefined']); + console.error('newSource.api_url not defined'); + return 0; + } + if (!goog.isDef(newSource['api_key'])) { + bootbox.alert(this['text']['Element']['Component']['Select']['API_key_undefined']); + console.error('newSource.api_key not defined'); + return 0; + } + if (!goog.isDef(newSource['api_value'])) { + bootbox.alert(this['text']['Element']['Component']['Select']['API_value_undefined']); + console.error('newSource.api_value not defined'); + return 0; + } + + var source = { + 'id': newSource['id'], + 'type': 'external_API', + 'dataType': newSource['dataType'], + 'name': newSource['name'], + 'description': newSource['description'], + 'api_url': newSource['api_url'], + 'api_key': newSource['api_key'], + 'api_value': newSource['api_value'] + }; + + $('#studio-datasource-add-modal').modal('hide'); + this.saveDatasource(source); + this.setStudioGridDatasources(); + } }; @@ -2321,6 +2366,18 @@ oVFB.studioMainController.prototype.saveDatasource = function (datasource) { $scope['datasources'][datasource['id']]['subObject'] = 'bo_' + datasource['businessObjectRequest']; $scope['datasources'][datasource['id']]['ressource_id'] = 'vmap/querys/' + datasource['businessObject']['business_object_id'] + '/' + datasource['businessObjectRequest']; } + } else if (datasource['type'] === 'external_API') { + + $scope['datasources'][datasource['id']] = { + 'type': datasource['type'], + 'dataType': datasource['dataType'], + 'name': datasource['name'], + 'description': datasource['description'], + 'api_url': datasource['api_url'], + 'api_key': datasource['api_key'], + 'api_value': datasource['api_value'] + }; + } if (datasource['dataType'] === 'externalDatabase') { @@ -2557,6 +2614,8 @@ oVFB.studioMainController.prototype.testDatasource = function (datasource, modal this.testWebService(datasource, modalName); } else if (datasource['dataType'] === 'businessObject') { this.testBusinessObject(datasource, modalName); + } else if (datasource['dataType'] === 'externalAPI') { + this.testExternalAPI(datasource, modalName); } }; @@ -2694,6 +2753,87 @@ oVFB.studioMainController.prototype.testRequestAPI = function (url, params, grid }); }; +/** + * Run a test for external API datasource + * + * @param {object} datasource + * @param {string} modalName + * @export + */ +oVFB.studioMainController.prototype.testExternalAPI = function (datasource, modalName) { + oVFB.log('studioMainController.testExternalAPI'); + + var this_ = this; + + datasource['api_result_options'] = []; + datasource['api_result_data'] = ''; + datasource['api_result_key'] = ''; + datasource['api_result_value'] = ''; + + if (!goog.isDefAndNotNull(jmespath)) { + console.error('jmespath not defined'); + return 0; + } + if (!goog.isObject(datasource)) { + console.error('datasource not defined'); + return 0; + } + if (!goog.isDefAndNotNull(datasource['parameters'])) { + console.error('datasource.parameters not defined'); + return 0; + } + if (!goog.isDefAndNotNull(datasource['api_url'])) { + console.error('datasource.api_url not defined'); + return 0; + } + + ajaxRequest({ + 'method': 'GET', + 'url': datasource['api_url'], + 'headers': { + 'Accept': 'application/x-vm-json' + }, + 'scope': this.$scope_, + 'success': function (response) { + + if (!goog.isDef(response['data'])) { + bootbox.alert(this_['text']['Element']['Component']['Select']['NoDataReturned']); + return 0; + } + + var aResult = response['data']; + + // Résultat requête + datasource['api_result_data'] = JSON.stringify(aResult, null, 4); + + // Résultat clé + if (goog.isDefAndNotNull(datasource['api_key'])) { + var aKeys = jmespath.search(aResult, datasource['api_key']); + datasource['api_result_key'] = JSON.stringify(aKeys, null, 4); + } + + // Résultat valeur + if (goog.isDefAndNotNull(datasource['api_value'])) { + var aValues = jmespath.search(aResult, datasource['api_value']); + datasource['api_result_value'] = JSON.stringify(aValues, null, 4); + } + + if (goog.isArray(aKeys) && goog.isArray(aValues)) { + if (aKeys.length === aValues.length) { + for (var i = 0; i < aKeys.length; i++) { + datasource['api_result_options'].push({ + 'value': aKeys[i], + 'label': aValues[i] + }); + } + } else { + $.notify(this_['text']['Element']['Component']['Select']['Key_Values_not_matching'], 'error'); + } + } + } + }); +} + /** * Clear the datasources grid * @export @@ -2901,7 +3041,7 @@ oVFB.studioMainController.prototype.moveBootboxModalToStudioContainer = function var aElement = oModal[0].parentNode.querySelectorAll(".modal-backdrop"); for (var i = 0; i < aElement.length; i++) { document.getElementById("studio_container").appendChild(aElement[i]); - } + } document.getElementById("studio_container").appendChild(oModal[0]); } }; @@ -2982,4 +3122,4 @@ oVFB.oVisualizer_ = new nsVFB.Visualizer(); /*********************************************************************************** * End * - **********************************************************************************/ \ No newline at end of file + **********************************************************************************/ diff --git a/src/vitis/client/javascript/externs/studio/lang/lang-en.json b/src/vitis/client/javascript/externs/studio/lang/lang-en.json index b6d34b3729e50d6508ea9665f3c1446ae7cec112..2a8607765144ea740a1a942c6b65707e030622e0 100755 --- a/src/vitis/client/javascript/externs/studio/lang/lang-en.json +++ b/src/vitis/client/javascript/externs/studio/lang/lang-en.json @@ -242,6 +242,16 @@ "Table": "Table", "Column": "Column", "Server": "Hostname of server", + "API_URL": "URL", + "API_Key": "Key", + "API_Value": "Value", + "API_Request_Data": "Request result", + "API_Request_Key": "Key result", + "API_Request_Value": "Value result", + "API_Key_Values_not_matching": "The number of keys is different than the number of values", + "API_URL_undefined": "API URL not defined", + "API_key_undefined": "API key not defined", + "API_value_undefined": "API value not defined", "Port": "Port", "SGBD": "SGBD", "Login": "Login", @@ -268,6 +278,7 @@ "Select_Webservice": "Web service", "Select_Table_Values": " Local table values", "Select_External_Database": "External database", + "Select_External_API": "External API", "Webservice_Class": "API", "Webservice": "Web Service", "Ressource": "Ressource", diff --git a/src/vitis/client/javascript/externs/studio/lang/lang-fr.json b/src/vitis/client/javascript/externs/studio/lang/lang-fr.json index ddaad2891c0fd4177698f0341e3e075d915290b3..bb90dd51670f0baf1ea619c0d44a06a62d86e815 100755 --- a/src/vitis/client/javascript/externs/studio/lang/lang-fr.json +++ b/src/vitis/client/javascript/externs/studio/lang/lang-fr.json @@ -250,6 +250,16 @@ "Table": "Table", "Column": "Column", "Server": "Adresse du serveur", + "API_URL": "URL", + "API_Key": "Clé", + "API_Value": "Valeur", + "API_Request_Data": "Résulstat requête", + "API_Request_Key": "Clé", + "API_Request_Value": "Valeur", + "Key_Values_not_matching": "Le nombre de clés est différent du combre de valeurs", + "API_URL_undefined": "URL non valide", + "API_key_undefined": "Clé non valide", + "API_value_undefined": "Valeur non valide", "Port": "Port", "SGBD": "SGBD", "Login": "Login", @@ -276,6 +286,7 @@ "Select_Webservice": "Service web", "Select_Table_Values": "Valeurs d'une table locale", "Select_External_Database": "Base de données externe", + "Select_External_API": "API externe", "Webservice_Class": "API", "Ressource": "Ressource", "Webservice": "Service web", diff --git a/src/vitis/client/javascript/externs/studio/templates/MainTemplate.html b/src/vitis/client/javascript/externs/studio/templates/MainTemplate.html index 2bc6e1b92a74a3879de8b3af5bee0c2cd7957289..6572d447c1734109166f928a077b031cab0dba78 100755 --- a/src/vitis/client/javascript/externs/studio/templates/MainTemplate.html +++ b/src/vitis/client/javascript/externs/studio/templates/MainTemplate.html @@ -41,16 +41,11 @@ <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label> <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled> </div> - <!--Description--> - <!--<div class="input-group element-margin input-group-xs width-100"> - <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label> - <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled> - </div>--> <!--Contenu--> <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Content}}"></label> <div> <i>{{::ctrl.text.Element.Component.Select.Content_description}}</i> - </div> + </div> <textarea style="width: 398px; height: 132px; cursor: not-allowed" ng-model="selectedDatasource.options_model" disabled></textarea> </form> </div> @@ -68,11 +63,6 @@ <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label> <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled> </div> - <!--Description--> - <!--<div class="input-group element-margin input-group-xs width-100"> - <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label> - <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled> - </div>--> <!--Database--> <div class="input-group element-margin input-group-xs width-100"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Database}}</label> @@ -104,11 +94,6 @@ <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label> <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled> </div> - <!--description--> - <!--<div class="input-group element-margin input-group-xs width-100"> - <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label> - <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled> - </div>--> <!--web service--> <div class="input-group element-margin input-group-xs width-100"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Webservice}}</label> @@ -135,11 +120,6 @@ <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label> <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled> </div> - <!--description--> - <!--<div class="input-group element-margin input-group-xs width-100"> - <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label> - <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled> - </div>--> <!--Objet métier--> <div class="input-group element-margin input-group-xs width-100"> <label class="control-label">{{::ctrl.text.Element.Component.Select.BusinessObject}}</label> @@ -166,11 +146,6 @@ <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label> <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled> </div> - <!--Description--> - <!--<div class="input-group element-margin input-group-xs width-100"> - <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label> - <input type="text" class="form-control" ng-model="selectedDatasource.description" disabled> - </div>--> <!--Server--> <div class="input-group element-margin input-group-xs width-100"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Server}}</label> @@ -195,7 +170,7 @@ <div class="input-group element-margin input-group-xs width-100"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Password}}</label> <input type="text" class="form-control" ng-model="selectedDatasource.parameters.password" disabled> - </div> + </div> <!--Database--> <div class="input-group element-margin input-group-xs width-100"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Database}}</label> @@ -218,6 +193,37 @@ </div> </form> </div> + <!-- externalAPI --> + <div ng-show="selectedDatasource.dataType === 'externalAPI'"> + <h4>{{::ctrl.text.Element.Component.Select.Select_External_API}}</h4> + <form class="element-margin form-group-xs"> + <!--ID--> + <div ng-show="false" class="input-group element-margin input-group-xs width-100"> + <label class="control-label">ID</label> + <input type="text" class="form-control" ng-model="selectedDatasource.id" disabled> + </div> + <!--Nom--> + <div class="input-group element-margin input-group-xs width-100"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.Name}}</label> + <input type="text" class="form-control" ng-model="selectedDatasource.name" disabled> + </div> + <!--URL--> + <div class="input-group element-margin input-group-xs width-100"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.API_URL}}</label> + <input type="text" class="form-control" ng-model="selectedDatasource.api_url" disabled> + </div> + <!--Key--> + <div class="input-group element-margin input-group-xs width-100"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Key}}</label> + <input type="text" class="form-control" ng-model="selectedDatasource.api_key" disabled> + </div> + <!--Value--> + <div class="input-group element-margin input-group-xs width-100"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Value}}</label> + <input type="text" class="form-control" ng-model="selectedDatasource.api_value" disabled> + </div> + </form> + </div> </div> <div class="col-sm-6"> <div style="height: 40px"> @@ -235,7 +241,7 @@ </div> </div> </div> - </div> + </div> </div> <!--Modale édition source de donnees--> <div id="studio-datasource-edit-modal" class="modal fade" role="dialog"> @@ -262,35 +268,29 @@ ng-model="editedDatasource.name" required> </div> - <!--Description--> - <!--<div class="input-group element-margin input-group-xs width-100"> - <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label> - <input type="text" class="form-control" - ng-model="editedDatasource.description"> - </div>--> <!--text--> - <div id="textDatasourceAddform" class="col-xs-12" ng-if="editedDatasource.dataType === 'text'"> + <div id="textDatasourceEditForm" class="col-xs-12" ng-if="editedDatasource.dataType === 'text'"> <!--Contenu--> <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Content}}"></label> <div> <i>{{::ctrl.text.Element.Component.Select.Content_description}}</i> - </div> - <ui-codemirror + </div> + <ui-codemirror id="textOptionsCodeMirrorEdit" ui-refresh="refresh" - ui-codemirror-opts="editorOptions" + ui-codemirror-opts="editorOptions" ng-model="editedDatasource.options_model" ng-change="ctrl.updateDatasourceTextContent(editedDatasource)"></ui-codemirror> </div> <!--tableValue--> - <div id="tableValueDatasourceAddform" ng-if="editedDatasource.dataType === 'tableValue'"> + <div id="tableValueDatasourceEditForm" ng-if="editedDatasource.dataType === 'tableValue'"> <!--Base--> <div class="element-margin form-group-xxs col-xs-5"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Database}}</label> <div class="row"> <div class="col-sm-8"> <select type="text" class="form-control" - ng-options="database.database for database in databases| orderBy: 'database' track by database.database" + ng-options="database.database for database in databases| orderBy: 'database' track by database.database" ng-model="$parent.databaseSelected" ng-change="editedDatasource.parameters.database = databaseSelected.database" required></select> </div> @@ -311,7 +311,7 @@ ng-model="editedDatasource.parameters.schema"> <select type="text" class="form-control" ng-show="schemas.length > 0" - ng-options="schema.schema_name for schema in schemas| orderBy: 'schema_name' track by schema.schema_name" + ng-options="schema.schema_name for schema in schemas| orderBy: 'schema_name' track by schema.schema_name" ng-model="$parent.schemaSelected" ng-change="editedDatasource.parameters.schema = schemaSelected.schema_name" required></select> </div> @@ -335,7 +335,7 @@ ng-options="table.table_name for table in tables| orderBy: 'table_name' track by table.table_name" ng-model="$parent.tableSelected" ng-change="editedDatasource.parameters.table = tableSelected.table_name" - required> + required> </select> </div> </div> @@ -395,13 +395,13 @@ </div> </div> <!--webService--> - <div id="webServiceDatasourceAddform" ng-if="editedDatasource.dataType === 'webService'"> + <div id="webServiceDatasourceEditForm" ng-if="editedDatasource.dataType === 'webService'"> <!--Webservice--> <div class="element-margin form-group-xxs col-xs-6"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Webservice}}</label> <select class="form-control element-margin" ng-model="editedDatasource.webservice" - ng-change="ctrl.updateRessources(editedDatasource.webservice)" + ng-change="ctrl.updateRessources(editedDatasource.webservice)" ng-options="webservice.name for webservice in webservices track by webservice.name" required></select> </div> @@ -409,18 +409,18 @@ <div class="element-margin form-group-xxs col-xs-6"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Ressource}}</label> <select class="form-control element-margin" - ng-model="editedDatasource.ressource" + ng-model="editedDatasource.ressource" ng-options="ressource.name for ressource in ressources track by ressource.name" required></select> </div> </div> <!--businessObject--> - <div id="businessObjectDatasourceAddform" ng-if="editedDatasource.dataType === 'businessObject'"> + <div id="businessObjectDatasourceEditForm" ng-if="editedDatasource.dataType === 'businessObject'"> <!--Business Object--> <div class="element-margin form-group-xxs col-xs-6"> <label class="control-label">{{::ctrl.text.Element.Component.Select.BusinessObject}}</label> <select class="form-control element-margin" - ng-model="editedDatasource.businessObject" + ng-model="editedDatasource.businessObject" ng-options="businessObjects.business_object_id for businessObjects in businessObjects track by businessObjects.business_object_id" required></select> </div> @@ -437,7 +437,7 @@ <textarea style="width:calc(100% - 30px);height: 63px;margin-left: 15px;margin-right: 15px;" ng-model="editedDatasource.businessObject['sql_' + editedDatasource.businessObjectRequest]" disabled></textarea> </div> <!--externalDatabase--> - <div id="externalDatabaseDatasourceAddform" ng-if="editedDatasource.dataType === 'externalDatabase'"> + <div id="externalDatabaseDatasourceEditForm" ng-if="editedDatasource.dataType === 'externalDatabase'"> <!--Serveur--> <div class="element-margin form-group-xxs col-xs-6"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Server}}</label> @@ -456,8 +456,8 @@ <div class="element-margin form-group-xxs col-xs-3"> <label class="control-label">{{::ctrl.text.Element.Component.Select.SGBD}}</label> <select class="form-control" ng-model="editedDatasource.parameters.sgbd" required> - <option value="pgsql" label="Postgres">Postgres</option> - <option value="oci" label="Oracle">Oracle</option> + <option value="pgsql" label="Postgres">Postgres</option> + <option value="oci" label="Oracle">Oracle</option> </select> </div> <!--Login--> @@ -567,6 +567,33 @@ </div> </div> </div> + <!-- externalAPI --> + <div id="externalAPIDatasourceEditForm" ng-if="editedDatasource.dataType === 'externalAPI'"> + <!-- URL --> + <div class="element-margin form-group-xxs col-xs-12"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.API_URL}}</label> + <input type="text" class="form-control" + ng-model="editedDatasource.api_url" + placeholder="https://api-adresse.data.gouv.fr/search/?q=Paris&limit=100" + required> + </div> + <!-- clé --> + <div class="element-margin form-group-xxs col-xs-6"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Key}} (<a href="http://jmespath.org" target="_blank">JMESPATH</a>)</label> + <input type="text" class="form-control" + ng-model="editedDatasource.api_key" + placeholder="features[*].properties.name" + required> + </div> + <!-- valeur --> + <div class="element-margin form-group-xxs col-xs-6"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Value}} (<a href="http://jmespath.org" target="_blank">JMESPATH</a>)</label> + <input type="text" class="form-control" + ng-model="editedDatasource.api_value" + placeholder="features[*].properties.name" + required> + </div> + </div> <!--Fenêtre de test--> <div class="col-xs-12" id="Layout_Preview"> <!--Text--> @@ -583,8 +610,8 @@ {{editedDatasource.test_result.value}} </div> <div class="col-xs-3"> - <button ng-if="editedDatasource.dataType === 'tableValue' || editedDatasource.dataType === 'webService' || editedDatasource.dataType === 'businessObject' || editedDatasource.dataType === 'externalDatabase' || editedDatasource.dataType === 'text'" - type="submit" + <button ng-if="editedDatasource.dataType === 'tableValue' || editedDatasource.dataType === 'webService' || editedDatasource.dataType === 'businessObject' || editedDatasource.dataType === 'externalDatabase' || editedDatasource.dataType === 'text'" + type="submit" class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button> </div> </div> @@ -594,8 +621,8 @@ <!--Test--> <div> <button type="button" class="btn btn-success element-margin" ng-click="ctrl.testDatasource(editedDatasource, 'studio-datasource-edit-modal')">Test</button> - <button ng-if="editedDatasource.dataType === 'tableValue' || editedDatasource.dataType === 'webService' || editedDatasource.dataType === 'businessObject' || editedDatasource.dataType === 'externalDatabase' || editedDatasource.dataType === 'text'" - type="submit" + <button ng-if="editedDatasource.dataType === 'tableValue' || editedDatasource.dataType === 'webService' || editedDatasource.dataType === 'businessObject' || editedDatasource.dataType === 'externalDatabase' || editedDatasource.dataType === 'text'" + type="submit" class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button> </div> <div><label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Result}}"></label></div> @@ -603,12 +630,53 @@ <div id="grid-studio-webService-test" ui-grid="studioEditModalTestGridOptions" style="width:856px;height: 350px;" class="grid element-margin"></div> </div> </div> + <!-- API externe --> + <div ng-show="editedDatasource.dataType === 'externalAPI'"> + <div> + <button type="button" class="btn btn-success element-margin" ng-click="ctrl.testDatasource(editedDatasource, 'studio-datasource-add-modal')">Test</button> + <button ng-if="editedDatasource.dataType === 'externalAPI'" + type="submit" + class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button> + </div> + + <div class="row" style="margin-top: -25px;"> + <div class="col-xs-offset-2 col-xs-8"> + <div class="col-xs-3 form-group-xxs text-right"> + <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Result}}"></label> + </div> + <div class="form-group-xxs col-xs-6"> + <select class="element-margin form-control" + ng-model="editedDatasource.api_test_result" + ng-options="option.label for option in editedDatasource.api_result_options track by option.value"> + </select> + </div> + <div class="col-xs-3"> + {{editedDatasource.api_test_result.value}} + </div> + </div> + </div> + + <div class=""> + <div class="col-xs-6 padding-sides-0"> + <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Data}}"></label> + <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="editedDatasource.api_result_data" disabled></textarea> + </div> + <div class="col-xs-3 padding-sides-0"> + <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Key}}"></label> + <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="editedDatasource.api_result_key" disabled></textarea> + </div> + <div class="col-xs-3 padding-sides-0"> + <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Value}}"></label> + <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="editedDatasource.api_result_value" disabled></textarea> + </div> + </div> + </div> </div> </form> </div> </div> </div> - </div> + </div> </div> <!--Modale ajout source de données--> <div id="studio-datasource-add-modal" class="modal fade" role="dialog"> @@ -631,6 +699,7 @@ <option value="webService">{{::ctrl.text.Element.Component.Select.Select_Webservice}}</option> <option ng-if="applicationName !== 'gtf'" value="businessObject">{{::ctrl.text.Element.Component.Select.Select_Business_Object}}</option> <option value="externalDatabase">{{::ctrl.text.Element.Component.Select.Select_External_Database}}</option> + <option value="externalAPI">{{::ctrl.text.Element.Component.Select.Select_External_API}}</option> </select> <hr> </div> @@ -651,23 +720,17 @@ ng-model="newSource.name" required> </div> - <!--Description--> - <!--<div class="input-group element-margin input-group-xs width-100"> - <label class="control-label">{{::ctrl.text.Element.Component.Select.Description}}</label> - <input type="text" class="form-control" - ng-model="newSource.description"> - </div>--> <!--text--> <div id="textDatasourceAddform" class="col-xs-12" ng-if="newSource.dataType === 'text'"> <!--Contenu--> <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Content}}"></label> <div> <i>{{::ctrl.text.Element.Component.Select.Content_description}}</i> - </div> - <ui-codemirror + </div> + <ui-codemirror id="textOptionsCodeMirrorAdd" ui-refresh="newSource.dataType === 'text'" - ui-codemirror-opts="editorOptions" + ui-codemirror-opts="editorOptions" ng-model="newSource.options_model" ng-change="ctrl.updateDatasourceTextContent(newSource)"></ui-codemirror> </div> @@ -680,7 +743,7 @@ <div class="col-sm-8"> <select type="text" class="form-control" ng-init="ctrl.setLocalDatabases(newSource.parameters.database)" - ng-options="database.database for database in databases| orderBy: 'database' track by database.database" + ng-options="database.database for database in databases| orderBy: 'database' track by database.database" ng-model="$parent.databaseSelected" ng-change="newSource.parameters.database = databaseSelected.database" required></select> </div> @@ -701,7 +764,7 @@ ng-model="newSource.parameters.schema"> <select type="text" class="form-control" ng-show="schemas.length > 0" - ng-options="schema.schema_name for schema in schemas| orderBy: 'schema_name' track by schema.schema_name" + ng-options="schema.schema_name for schema in schemas| orderBy: 'schema_name' track by schema.schema_name" ng-model="$parent.schemaSelected" ng-change="newSource.parameters.schema = schemaSelected.schema_name" required></select> </div> @@ -725,7 +788,7 @@ ng-options="table.table_name for table in tables| orderBy: 'table_name' track by table.table_name" ng-model="$parent.tableSelected" ng-change="newSource.parameters.table = tableSelected.table_name" - required> + required> </select> </div> </div> @@ -791,7 +854,7 @@ <label class="control-label">{{::ctrl.text.Element.Component.Select.Webservice}}</label> <select class="form-control element-margin" ng-model="newSource.webservice" - ng-change="ctrl.updateRessources(newSource.webservice)" + ng-change="ctrl.updateRessources(newSource.webservice)" ng-options="webservice.name for webservice in webservices track by webservice.name" required></select> </div> @@ -799,7 +862,7 @@ <div class="element-margin form-group-xxs col-xs-6"> <label class="control-label">{{::ctrl.text.Element.Component.Select.Ressource}}</label> <select class="form-control element-margin" - ng-model="newSource.ressource" + ng-model="newSource.ressource" ng-options="ressource.name for ressource in ressources track by ressource.name" required></select> </div> @@ -810,7 +873,7 @@ <div class="element-margin form-group-xxs col-xs-6"> <label class="control-label">{{::ctrl.text.Element.Component.Select.BusinessObject}}</label> <select class="form-control element-margin" - ng-model="newSource.businessObject" + ng-model="newSource.businessObject" ng-options="businessObjects.business_object_id for businessObjects in businessObjects track by businessObjects.business_object_id" required></select> </div> @@ -846,8 +909,8 @@ <div class="element-margin form-group-xxs col-xs-3"> <label class="control-label">{{::ctrl.text.Element.Component.Select.SGBD}}</label> <select class="form-control" ng-model="newSource.parameters.sgbd" required> - <option value="pgsql" label="Postgres">Postgres</option> - <option value="oci" label="Oracle">Oracle</option> + <option value="pgsql" label="Postgres">Postgres</option> + <option value="oci" label="Oracle">Oracle</option> </select> </div> <!--Login--> @@ -957,6 +1020,33 @@ </div> </div> </div> + <!-- externalAPI --> + <div id="externalAPIDatasourceAddform" ng-if="newSource.dataType === 'externalAPI'"> + <!-- URL --> + <div class="element-margin form-group-xxs col-xs-12"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.API_URL}}</label> + <input type="text" class="form-control" + ng-model="newSource.api_url" + placeholder="https://api-adresse.data.gouv.fr/search/?q=Paris&limit=100" + required> + </div> + <!-- clé --> + <div class="element-margin form-group-xxs col-xs-6"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Key}} (<a href="http://jmespath.org" target="_blank">JMESPATH</a>)</label> + <input type="text" class="form-control" + ng-model="newSource.api_key" + placeholder="features[*].properties.name" + required> + </div> + <!-- valeur --> + <div class="element-margin form-group-xxs col-xs-6"> + <label class="control-label">{{::ctrl.text.Element.Component.Select.API_Value}} (<a href="http://jmespath.org" target="_blank">JMESPATH</a>)</label> + <input type="text" class="form-control" + ng-model="newSource.api_value" + placeholder="features[*].properties.name" + required> + </div> + </div> <!--Fenêtre de test--> <div class="col-xs-12" id="Layout_Preview"> <!--Text--> @@ -973,10 +1063,10 @@ {{newSource.test_result.value}} </div> <div class="col-xs-3"> - <button ng-if="newSource.dataType === 'tableValue' || newSource.dataType === 'webService' || newSource.dataType === 'businessObject' || newSource.dataType === 'externalDatabase' || newSource.dataType === 'text'" - type="submit" + <button ng-if="newSource.dataType === 'tableValue' || newSource.dataType === 'webService' || newSource.dataType === 'businessObject' || newSource.dataType === 'externalDatabase' || newSource.dataType === 'text'" + type="submit" class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button> - </div> + </div> </div> </div> <!--Valeurs d'une table--> @@ -984,8 +1074,8 @@ <!--Test--> <div> <button type="button" class="btn btn-success element-margin" ng-click="ctrl.testDatasource(newSource, 'studio-datasource-add-modal')">Test</button> - <button ng-if="newSource.dataType === 'tableValue' || newSource.dataType === 'webService' || newSource.dataType === 'businessObject' || newSource.dataType === 'externalDatabase' || newSource.dataType === 'text'" - type="submit" + <button ng-if="newSource.dataType === 'tableValue' || newSource.dataType === 'webService' || newSource.dataType === 'businessObject' || newSource.dataType === 'externalDatabase' || newSource.dataType === 'text'" + type="submit" class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button> </div> <div><label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Result}}"></label></div> @@ -993,6 +1083,47 @@ <div id="grid-studio-webService-test" ui-grid="studioAddModalTestGridOptions" style="width:856px;height: 350px;" class="grid element-margin"></div> </div> </div> + <!-- API externe --> + <div ng-show="newSource.dataType === 'externalAPI'"> + <div> + <button type="button" class="btn btn-success element-margin" ng-click="ctrl.testDatasource(newSource, 'studio-datasource-add-modal')">Test</button> + <button ng-if="newSource.dataType === 'externalAPI'" + type="submit" + class="btn btn-primary right">{{::ctrl.text.Element.Component.Select.Save}}</button> + </div> + + <div class="row" style="margin-top: -25px;"> + <div class="col-xs-offset-2 col-xs-8"> + <div class="col-xs-3 form-group-xxs text-right"> + <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.Result}}"></label> + </div> + <div class="form-group-xxs col-xs-6"> + <select class="element-margin form-control" + ng-model="newSource.api_test_result" + ng-options="option.label for option in newSource.api_result_options track by option.value"> + </select> + </div> + <div class="col-xs-3"> + {{newSource.api_test_result.value}} + </div> + </div> + </div> + + <div class=""> + <div class="col-xs-6 padding-sides-0"> + <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Data}}"></label> + <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="newSource.api_result_data" disabled></textarea> + </div> + <div class="col-xs-3 padding-sides-0"> + <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Key}}"></label> + <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="newSource.api_result_key" disabled></textarea> + </div> + <div class="col-xs-3 padding-sides-0"> + <label class="control-label" data-translate="{{::ctrl.text.Element.Component.Select.API_Request_Value}}"></label> + <textarea style="width:calc(100% - 30px);height: 300px;margin-left: 15px;margin-right: 15px;" ng-model="newSource.api_result_value" disabled></textarea> + </div> + </div> + </div> </div> </form> </div> @@ -1019,7 +1150,7 @@ <div class="input-group input-group-xs margin-right-20" ng-show="tab.studio_mode === 'edit'"> <input type="text" class="form-control" ng-model="tab.label"> - <span class="input-group-addon btn btn-primary btn-xs glyphicon glyphicon-ok" + <span class="input-group-addon btn btn-primary btn-xs glyphicon glyphicon-ok" style="min-width: inherit;" ng-click="tab.studio_mode = 'display'"></span> </div> @@ -1077,7 +1208,7 @@ <td style="width:" ng-repeat="(tabIndex, tab) in tabs.list"> <div class="checkbox margin-0"> - <input id="studio-tabsmanager-modal-show-elem-checkbox-{{elemIndex}}-{{tabIndex}}" + <input id="studio-tabsmanager-modal-show-elem-checkbox-{{elemIndex}}-{{tabIndex}}" type="checkbox" ng-checked="tab.elements.indexOf(elem) !== -1" ng-click="ctrl.toggleElemOnTab(elem, tab)"> @@ -1091,9 +1222,9 @@ <h4 class="modal-title ng-binding" style="margin-bottom: 15px;">Prévisualisation</h4> <!--Prévisualisation--> <div id="studio_test_tabs_form_reader" - app-form-reader - app-form-definition-name="sTestTabFormDefinitionName" - app-form-values="oTestTabFormValues" + app-form-reader + app-form-definition-name="sTestTabFormDefinitionName" + app-form-values="oTestTabFormValues" app-form-definition="oTestTabFormDefinition" app-form-events-container="oFormScope" app-properties="oProperties" @@ -1107,7 +1238,7 @@ <button type="button" class="btn btn-default right margin-left-20" onclick="$('#studio-tabsmanager-modal').modal('hide');">{{::ctrl.text.Loader.Cancel}}</button> </div> </div> - </div> + </div> </div> <!--Modale gestion état wab--> <div id="studio-wabmanager-modal" class="modal fade" role="dialog"> @@ -1126,8 +1257,8 @@ </select> </div> <div class="transition-form-wab-collapser" ng-class="{'col-xs-2' : !wabVisualizer, 'col-xs-7' : wabVisualizer}"></div> - <div class="col-xs-2" style="text-align: right;"> - <span class=" collapser-form-wab-modal" ng-click="wabVisualizer = !wabVisualizer" ng-class="{'icon-keyboard_arrow_right' : !wabVisualizer, 'icon-keyboard_arrow_left' : wabVisualizer}"></span> + <div class="col-xs-2" style="text-align: right;"> + <span class=" collapser-form-wab-modal" ng-click="wabVisualizer = !wabVisualizer" ng-class="{'icon-keyboard_arrow_right' : !wabVisualizer, 'icon-keyboard_arrow_left' : wabVisualizer}"></span> </div> <div ng-show="!wabVisualizer" class="transition-form-wab-collapser margin-left-15" ng-class="{'col-xs-3' : !wabVisualizer}"> <label>{{::ctrl.text.Loader.WabState}}</label> @@ -1137,7 +1268,7 @@ </div> </div> <br> - <div class="row" + <div class="row" style="height: calc(100% - 70px);" ng-if="wabSelectedGroup !== ''"> <div class="height-100 table-responsive studio-table-fixed transition-form-wab-collapser" ng-class="{'col-xs-7' : !wabVisualizer, 'col-xs-12' : wabVisualizer}"> @@ -1158,7 +1289,7 @@ ng-repeat="(stateName, oState) in wab|sortStates:wabStates track by $index"> <span class="checkbox margin-0 left checkbox-info" title="{{::ctrl.text.Loader.AllowDisplayElement}}"> - <input id="studio-wabmanager-modal-display-elem-checkbox-{{elemIndex}}-{{oState.name}}" + <input id="studio-wabmanager-modal-display-elem-checkbox-{{elemIndex}}-{{oState.name}}" type="checkbox" ng-checked="oState[wabSelectedGroup][elem] === 'r' || oState[wabSelectedGroup][elem] === 'rw'" ng-click="ctrl.toggleElemOnWab(elem, oState, wabSelectedGroup, 'r')"> @@ -1166,7 +1297,7 @@ </span> <span class="checkbox margin-0 left checkbox-primary" title="{{::ctrl.text.Loader.AllowEditElement}}"> - <input id="studio-wabmanager-modal-edit-elem-checkbox-{{elemIndex}}-{{oState.name}}" + <input id="studio-wabmanager-modal-edit-elem-checkbox-{{elemIndex}}-{{oState.name}}" type="checkbox" ng-checked="oState[wabSelectedGroup][elem] === 'rw'" ng-click="ctrl.toggleElemOnWab(elem, oState, wabSelectedGroup, 'rw')"> @@ -1180,9 +1311,9 @@ <div ng-show="!wabVisualizer" class="height-100 transition-form-wab-collapser" style="overflow-x: auto" ng-class="{'col-xs-5' : !wabVisualizer}"> <!--Prévisualisation--> <div id="studio_test_wab_form_reader" - app-form-reader - app-form-definition-name="sTestWabFormDefinitionName" - app-form-values="oTestWabFormValues" + app-form-reader + app-form-definition-name="sTestWabFormDefinitionName" + app-form-values="oTestWabFormValues" app-form-definition="oTestWabFormDefinition" app-form-events-container="oTestWabFormScope" app-properties="oProperties" @@ -1197,7 +1328,7 @@ <button type="button" class="btn btn-default right margin-left-20" onclick="$('#studio-wabmanager-modal').modal('hide');">{{::ctrl.text.Loader.Cancel}}</button> </div> </div> - </div> + </div> </div> <!--Modale gestion méthode wab--> <div id="studio-wabmanager-method-modal" class="modal fade" role="dialog"> @@ -1211,7 +1342,7 @@ <div ng-repeat="oMethod in aBoMethods" class="row" style="margin-left: 5px;"> <span class="checkbox margin-0 left checkbox-primary col-xs-5" title="{{::ctrl.text.Loader.AllowEditElement}}"> - <input id="studio-wabmanager-modal-edit-method-checkbox-{{$index}}-{{oMethod.name}}" + <input id="studio-wabmanager-modal-edit-method-checkbox-{{$index}}-{{oMethod.name}}" type="checkbox" ng-model="oMethod['selected']"> <label for="studio-wabmanager-modal-edit-method-checkbox-{{$index}}-{{oMethod.name}}" class="control-label">{{oMethod.name}} ({{oMethod.tostate_name}})</label> @@ -1232,7 +1363,7 @@ <button type="button" class="btn btn-default right margin-left-20" onclick="$('#studio-wabmanager-method-modal').modal('hide');">{{::ctrl.text.Loader.Cancel}}</button> </div> </div> - </div> + </div> </div> </div> </div>