Allow nested msg properties in msg/flow/global expressions (#2822)

* Allow nested msg properties in msg/flow/global expressions

* Remove typo in RED.utils

Co-authored-by: Nick O'Leary <knolleary@users.noreply.github.com>
This commit is contained in:
Nick O'Leary
2021-01-27 20:32:52 +00:00
committed by GitHub
parent 34ef055d7b
commit 438d51d26e
6 changed files with 342 additions and 31 deletions

View File

@@ -189,11 +189,17 @@ function createError(code, message) {
*
* For example, `a["b"].c` returns `['a','b','c']`
*
* If `msg` is provided, any internal cross-references will be evaluated against that
* object. Otherwise, it will return a nested set of properties
*
* For example, without msg set, 'a[msg.foo]' returns `['a', [ 'msg', 'foo'] ]`
* But if msg is set to '{"foo": "bar"}', 'a[msg.foo]' returns `['a', 'bar' ]`
*
* @param {String} str - the property expression
* @return {Array} the normalised expression
* @memberof @node-red/util_util
*/
function normalisePropertyExpression(str) {
function normalisePropertyExpression(str, msg, toString) {
// This must be kept in sync with validatePropertyExpression
// in editor/js/ui/utils.js
@@ -205,6 +211,7 @@ function normalisePropertyExpression(str) {
var start = 0;
var inString = false;
var inBox = false;
var boxExpression = false;
var quoteChar;
var v;
for (var i=0;i<length;i++) {
@@ -247,8 +254,54 @@ function normalisePropertyExpression(str) {
if (i===length-1) {
throw createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
// Next char is either a quote or a number
if (!/["'\d]/.test(str[i+1])) {
// Start of a new expression. If it starts with msg it is a nested expression
// Need to scan ahead to find the closing bracket
if (/^msg[.\[]/.test(str.substring(i+1))) {
var depth = 1;
var inLocalString = false;
var localStringQuote;
for (var j=i+1;j<length;j++) {
if (/["']/.test(str[j])) {
if (inLocalString) {
if (str[j] === localStringQuote) {
inLocalString = false
}
} else {
inLocalString = true;
localStringQuote = str[j]
}
}
if (str[j] === '[') {
depth++;
} else if (str[j] === ']') {
depth--;
}
if (depth === 0) {
try {
if (msg) {
var crossRefProp = getMessageProperty(msg, str.substring(i+1,j));
if (crossRefProp === undefined) {
throw createError("INVALID_EXPR","Invalid expression: undefined reference at position "+(i+1)+" : "+str.substring(i+1,j))
}
parts.push(crossRefProp)
} else {
parts.push(normalisePropertyExpression(str.substring(i+1,j), msg));
}
inBox = false;
i = j;
start = j+1;
break;
} catch(err) {
throw createError("INVALID_EXPR","Invalid expression started at position "+(i+1))
}
}
}
if (depth > 0) {
throw createError("INVALID_EXPR","Invalid property expression: unmatched '[' at position "+i);
}
continue;
} else if (!/["'\d]/.test(str[i+1])) {
// Next char is either a quote or a number
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;
@@ -294,6 +347,23 @@ function normalisePropertyExpression(str) {
if (start < length) {
parts.push(str.substring(start));
}
if (toString) {
var result = parts.shift();
while(parts.length > 0) {
var p = parts.shift();
if (typeof p === 'string') {
if (/"/.test(p)) {
p = "'"+p+"'";
} else {
p = '"'+p+'"';
}
}
result = result+"["+p+"]";
}
return result;
}
return parts;
}
@@ -340,8 +410,7 @@ function getMessageProperty(msg,expr) {
*/
function getObjectProperty(msg,expr) {
var result = null;
var msgPropParts = normalisePropertyExpression(expr);
var m;
var msgPropParts = normalisePropertyExpression(expr,msg);
msgPropParts.reduce(function(obj, key) {
result = (typeof obj[key] !== "undefined" ? obj[key] : undefined);
return result;
@@ -381,7 +450,7 @@ function setObjectProperty(msg,prop,value,createMissing) {
if (typeof createMissing === 'undefined') {
createMissing = (typeof value !== 'undefined');
}
var msgPropParts = normalisePropertyExpression(prop);
var msgPropParts = normalisePropertyExpression(prop, msg);
var depth = 0;
var length = msgPropParts.length;
var obj = msg;
@@ -553,6 +622,10 @@ function evaluateNodeProperty(value, type, node, msg, callback) {
}
} else if ((type === 'flow' || type === 'global') && node) {
var contextKey = parseContextStore(value);
if (/\[msg/.test(contextKey.key)) {
// The key has a nest msg. reference to evaluate first
contextKey.key = normalisePropertyExpression(contextKey.key, msg, true)
}
result = node.context()[type].get(contextKey.key,contextKey.store,callback);
if (callback) {
return;