mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Add support for array-syntax in typedInput msg properties
This commit is contained in:
		| @@ -14,10 +14,79 @@ | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
| (function($) { | ||||
|     function validateExpression(str) { | ||||
|         var length = str.length; | ||||
|         var start = 0; | ||||
|         var inString = false; | ||||
|         var inBox = false; | ||||
|         var quoteChar; | ||||
|         var v; | ||||
|         for (var i=0;i<length;i++) { | ||||
|             var c = str[i]; | ||||
|             if (!inString) { | ||||
|                 if (c === "'" || c === '"') { | ||||
|                     if (!inBox) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     inString = true; | ||||
|                     quoteChar = c; | ||||
|                     start = i+1; | ||||
|                 } else if (c === '.') { | ||||
|                     if (i===length-1) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     // Next char is a-z | ||||
|                     if (!/[a-z0-9]/i.test(str[i+1])) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     start = i+1; | ||||
|                 } else if (c === '[') { | ||||
|                     if (i === 0) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     if (i===length-1) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     // Next char is either a quote or a number | ||||
|                     if (!/["'\d]/.test(str[i+1])) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     start = i+1; | ||||
|                     inBox = true; | ||||
|                 } else if (c === ']') { | ||||
|                     if (!inBox) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     if (start != i) { | ||||
|                         v = str.substring(start,i); | ||||
|                         if (!/^\d+$/.test(v)) { | ||||
|                             return false; | ||||
|                         } | ||||
|                     } | ||||
|                     start = i+1; | ||||
|                     inBox = false; | ||||
|                 } | ||||
|             } else { | ||||
|                 if (c === quoteChar) { | ||||
|                     // Next char must be a ] | ||||
|                     if (!/\]/.test(str[i+1])) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     start = i+1; | ||||
|                     inString = false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         if (inBox || inString) { | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|     var allOptions = { | ||||
|         msg: {value:"msg",label:"msg.",validate:/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i}, | ||||
|         flow: {value:"flow",label:"flow.",validate:/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i}, | ||||
|         global: {value:"global",label:"global.",validate:/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i}, | ||||
|         msg: {value:"msg",label:"msg.",validate:validateExpression}, | ||||
|         flow: {value:"flow",label:"flow.",validate:validateExpression}, | ||||
|         global: {value:"global",label:"global.",validate:validateExpression}, | ||||
|         str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"}, | ||||
|         num: {value:"num",label:"number",icon:"red/images/typedInput/09.png",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/}, | ||||
|         bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]}, | ||||
|   | ||||
| @@ -127,14 +127,103 @@ function compareObjects(obj1,obj2) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function normalisePropertyExpression(str) { | ||||
|     var length = str.length; | ||||
|     var parts = []; | ||||
|     var start = 0; | ||||
|     var inString = false; | ||||
|     var inBox = false; | ||||
|     var quoteChar; | ||||
|     var v; | ||||
|     for (var i=0;i<length;i++) { | ||||
|         var c = str[i]; | ||||
|         if (!inString) { | ||||
|             if (c === "'" || c === '"') { | ||||
|                 if (!inBox) { | ||||
|                     throw new Error("Invalid property expression: unexpected "+c+" at position "+i); | ||||
|                 } | ||||
|                 inString = true; | ||||
|                 quoteChar = c; | ||||
|                 start = i+1; | ||||
|             } else if (c === '.') { | ||||
|                 if (start != i) { | ||||
|                     v = str.substring(start,i); | ||||
|                     if (/^\d+$/.test(v)) { | ||||
|                         parts.push(parseInt(v)); | ||||
|                     } else { | ||||
|                         parts.push(v); | ||||
|                     } | ||||
|                 } | ||||
|                 if (i===length-1) { | ||||
|                     throw new Error("Invalid property expression: unterminated expression"); | ||||
|                 } | ||||
|                 // Next char is a-z | ||||
|                 if (!/[a-z0-9]/i.test(str[i+1])) { | ||||
|                     throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1)); | ||||
|                 } | ||||
|                 start = i+1; | ||||
|             } else if (c === '[') { | ||||
|                 if (i === 0) { | ||||
|                     throw new Error("Invalid property expression: unexpected "+c+" at position "+i); | ||||
|                 } | ||||
|                 if (start != i) { | ||||
|                     parts.push(str.substring(start,i)); | ||||
|                 } | ||||
|                 if (i===length-1) { | ||||
|                     throw new Error("Invalid property expression: unterminated expression"); | ||||
|                 } | ||||
|                 // Next char is either a quote or a number | ||||
|                 if (!/["'\d]/.test(str[i+1])) { | ||||
|                     throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1)); | ||||
|                 } | ||||
|                 start = i+1; | ||||
|                 inBox = true; | ||||
|             } else if (c === ']') { | ||||
|                 if (!inBox) { | ||||
|                     throw new Error("Invalid property expression: unexpected "+c+" at position "+i); | ||||
|                 } | ||||
|                 if (start != i) { | ||||
|                     v = str.substring(start,i); | ||||
|                     if (/^\d+$/.test(v)) { | ||||
|                         parts.push(parseInt(v)); | ||||
|                     } else { | ||||
|                         throw new Error("Invalid property expression: unexpected array expression at position "+start); | ||||
|                     } | ||||
|                 } | ||||
|                 start = i+1; | ||||
|                 inBox = false; | ||||
|             } | ||||
|         } else { | ||||
|             if (c === quoteChar) { | ||||
|                 parts.push(str.substring(start,i)); | ||||
|                 // Next char must be a ] | ||||
|                 if (!/\]/.test(str[i+1])) { | ||||
|                     throw new Error("Invalid property expression: unexpected array expression at position "+start); | ||||
|                 } | ||||
|                 start = i+1; | ||||
|                 inString = false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     if (inBox || inString) { | ||||
|         throw new Error("Invalid property expression: unterminated expression"); | ||||
|     } | ||||
|     if (start < length) { | ||||
|         parts.push(str.substring(start)); | ||||
|     } | ||||
|     return parts; | ||||
| } | ||||
|  | ||||
| function getMessageProperty(msg,expr) { | ||||
|     var result = null; | ||||
|     if (expr.indexOf('msg.')===0) { | ||||
|         expr = expr.substring(4); | ||||
|     } | ||||
|     var msgPropParts = expr.split("."); | ||||
|     msgPropParts.reduce(function(obj, i) { | ||||
|         result = (typeof obj[i] !== "undefined" ? obj[i] : undefined); | ||||
|     var msgPropParts = normalisePropertyExpression(expr); | ||||
|     var m; | ||||
|     msgPropParts.reduce(function(obj, key) { | ||||
|         result = (typeof obj[key] !== "undefined" ? obj[key] : undefined); | ||||
|         return result; | ||||
|     }, msg); | ||||
|     return result; | ||||
| @@ -147,30 +236,54 @@ function setMessageProperty(msg,prop,value,createMissing) { | ||||
|     if (prop.indexOf('msg.')===0) { | ||||
|         prop = prop.substring(4); | ||||
|     } | ||||
|     var msgPropParts = prop.split("."); | ||||
|     var msgPropParts = normalisePropertyExpression(prop); | ||||
|     var depth = 0; | ||||
|     msgPropParts.reduce(function(obj, i) { | ||||
|         if (obj === null) { | ||||
|             return null; | ||||
|         } | ||||
|         depth++; | ||||
|         if (depth === msgPropParts.length) { | ||||
|             if (typeof value === "undefined") { | ||||
|                 delete obj[i]; | ||||
|             } else { | ||||
|                 obj[i] = value; | ||||
|             } | ||||
|         } else { | ||||
|             if (!obj[i]) { | ||||
|                 if (createMissing) { | ||||
|                     obj[i] = {}; | ||||
|     var length = msgPropParts.length; | ||||
|     var obj = msg; | ||||
|     var key; | ||||
|     for (var i=0;i<length-1;i++) { | ||||
|         key = msgPropParts[i]; | ||||
|         if (typeof key === 'string' || (typeof key === 'number' && !Array.isArray(obj))) { | ||||
|             if (obj.hasOwnProperty(key)) { | ||||
|                 obj = obj[key]; | ||||
|             } else if (createMissing) { | ||||
|                 if (typeof msgPropParts[i+1] === 'string') { | ||||
|                     obj[key] = {}; | ||||
|                 } else { | ||||
|                     return null; | ||||
|                     obj[key] = []; | ||||
|                 } | ||||
|                 obj = obj[key]; | ||||
|             } else { | ||||
|                 return null; | ||||
|             } | ||||
|         } else if (typeof key === 'number') { | ||||
|             // obj is an array | ||||
|             if (obj[key] === undefined) { | ||||
|                 if (createMissing) { | ||||
|                     if (typeof msgPropParts[i+1] === 'string') { | ||||
|                         obj[key] = {}; | ||||
|                     } else { | ||||
|                         obj[key] = []; | ||||
|                     } | ||||
|                     obj = obj[key]; | ||||
|                 } else { | ||||
|                     return null | ||||
|                 } | ||||
|             } else { | ||||
|                 obj = obj[key]; | ||||
|             } | ||||
|             return obj[i]; | ||||
|         } | ||||
|     }, msg); | ||||
|     } | ||||
|     key = msgPropParts[length-1]; | ||||
|     if (typeof value === "undefined") { | ||||
|         if (typeof key === 'number' && Array.isArray(obj)) { | ||||
|             obj.splice(key,1); | ||||
|         } else { | ||||
|             delete obj[key] | ||||
|         } | ||||
|     } else { | ||||
|         obj[key] = value; | ||||
|     } | ||||
| } | ||||
|  | ||||
| function evaluateNodeProperty(value, type, node, msg) { | ||||
| @@ -205,5 +318,6 @@ module.exports = { | ||||
|     generateId: generateId, | ||||
|     getMessageProperty: getMessageProperty, | ||||
|     setMessageProperty: setMessageProperty, | ||||
|     evaluateNodeProperty: evaluateNodeProperty | ||||
|     evaluateNodeProperty: evaluateNodeProperty, | ||||
|     normalisePropertyExpression: normalisePropertyExpression | ||||
| }; | ||||
|   | ||||
| @@ -58,6 +58,7 @@ describe("red/util", function() { | ||||
|         it('Buffer', function() { | ||||
|             util.compareObjects(new Buffer("hello"),new Buffer("hello")).should.equal(true); | ||||
|             util.compareObjects(new Buffer("hello"),new Buffer("hello ")).should.equal(false); | ||||
|             util.compareObjects(new Buffer("hello"),"hello").should.equal(false); | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
| @@ -157,7 +158,16 @@ describe("red/util", function() { | ||||
|             (function() { | ||||
|                 util.getMessageProperty({a:"foo"},"msg.a.b.c"); | ||||
|             }).should.throw(); | ||||
|         }) | ||||
|         }); | ||||
|         it('retrieves a property with array syntax', function() { | ||||
|             var v = util.getMessageProperty({a:["foo","bar"]},"msg.a[0]"); | ||||
|             v.should.eql("foo"); | ||||
|             var v2 = util.getMessageProperty({a:[null,{b:"foo"}]},"a[1].b"); | ||||
|             v2.should.eql("foo"); | ||||
|             var v3 = util.getMessageProperty({a:[[["foo"]]]},"a[0][0][0]"); | ||||
|             v3.should.eql("foo"); | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     describe('setMessageProperty', function() { | ||||
| @@ -190,7 +200,48 @@ describe("red/util", function() { | ||||
|             var msg = {a:{}}; | ||||
|             util.setMessageProperty(msg,"msg.a.b.c",undefined); | ||||
|             should.not.exist(msg.a.b); | ||||
|         }); | ||||
|         it('sets a property with array syntax', function() { | ||||
|             var msg = {a:{b:["foo",{c:["",""]}]}}; | ||||
|             util.setMessageProperty(msg,"msg.a.b[1].c[1]","bar"); | ||||
|             msg.a.b[1].c[1].should.eql('bar'); | ||||
|         }); | ||||
|         it('creates missing array elements - final property', function() { | ||||
|             var msg = {a:[]}; | ||||
|             util.setMessageProperty(msg,"msg.a[2]","bar"); | ||||
|             msg.a.should.have.length(3); | ||||
|             msg.a[2].should.eql("bar"); | ||||
|         }); | ||||
|         it('creates missing array elements - mid property', function() { | ||||
|             var msg = {}; | ||||
|             util.setMessageProperty(msg,"msg.a[2].b","bar"); | ||||
|             msg.a.should.have.length(3); | ||||
|             msg.a[2].b.should.eql("bar"); | ||||
|         }); | ||||
|         it('creates missing array elements - multi-arrays', function() { | ||||
|             var msg = {}; | ||||
|             util.setMessageProperty(msg,"msg.a[2][2]","bar"); | ||||
|             msg.a.should.have.length(3); | ||||
|             msg.a.should.be.instanceOf(Array); | ||||
|             msg.a[2].should.have.length(3); | ||||
|             msg.a[2].should.be.instanceOf(Array); | ||||
|             msg.a[2][2].should.eql("bar"); | ||||
|         }); | ||||
|         it('does not create missing array elements - final property', function() { | ||||
|             var msg = {a:{}}; | ||||
|             util.setMessageProperty(msg,"msg.a.b[2]","bar",false); | ||||
|             should.not.exist(msg.a.b); | ||||
|             // check it has not been misinterpreted | ||||
|             msg.a.should.not.have.property("b[2]"); | ||||
|         }); | ||||
|         it('deletes property inside array if value is undefined', function() { | ||||
|             var msg = {a:[1,2,3]}; | ||||
|             util.setMessageProperty(msg,"msg.a[1]",undefined); | ||||
|             msg.a.should.have.length(2); | ||||
|             msg.a[0].should.eql(1); | ||||
|             msg.a[1].should.eql(3); | ||||
|         }) | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     describe('evaluateNodeProperty', function() { | ||||
| @@ -210,6 +261,16 @@ describe("red/util", function() { | ||||
|             var result = util.evaluateNodeProperty('^abc$','re'); | ||||
|             result.toString().should.eql("/^abc$/"); | ||||
|         }); | ||||
|         it('returns boolean',function() { | ||||
|             var result = util.evaluateNodeProperty('true','bool'); | ||||
|             result.should.be.true(); | ||||
|             result = util.evaluateNodeProperty('TrUe','bool'); | ||||
|             result.should.be.true(); | ||||
|             result = util.evaluateNodeProperty('false','bool'); | ||||
|             result.should.be.false(); | ||||
|             result = util.evaluateNodeProperty('','bool'); | ||||
|             result.should.be.false(); | ||||
|         }); | ||||
|         it('returns date',function() { | ||||
|             var result = util.evaluateNodeProperty('','date'); | ||||
|             (Date.now() - result).should.be.approximately(0,50); | ||||
| @@ -246,7 +307,45 @@ describe("red/util", function() { | ||||
|             },{}); | ||||
|             result.should.eql("123"); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('normalisePropertyExpression', function() { | ||||
|         function testABC(input,expected) { | ||||
|             var result = util.normalisePropertyExpression(input); | ||||
|             // console.log("+",input); | ||||
|             // console.log(result); | ||||
|             result.should.eql(expected); | ||||
|         } | ||||
|  | ||||
|     }) | ||||
|         function testInvalid(input) { | ||||
|             /*jshint immed: false */ | ||||
|             (function() { | ||||
|                 util.normalisePropertyExpression(input); | ||||
|             }).should.throw(); | ||||
|         } | ||||
|         it('pass a.b.c',function() { testABC('a.b.c',['a','b','c']); }) | ||||
|         it('pass a["b"]["c"]',function() { testABC('a["b"]["c"]',['a','b','c']); }) | ||||
|         it('pass a["b"].c',function() { testABC('a["b"].c',['a','b','c']); }) | ||||
|         it("pass a['b'].c",function() { testABC("a['b'].c",['a','b','c']); }) | ||||
|  | ||||
|         it("pass a[0].c",function() { testABC("a[0].c",['a',0,'c']); }) | ||||
|         it("pass a.0.c",function() { testABC("a.0.c",['a',0,'c']); }) | ||||
|         it("pass a['a.b[0]'].c",function() { testABC("a['a.b[0]'].c",['a','a.b[0]','c']); }) | ||||
|         it("pass a[0][0][0]",function() { testABC("a[0][0][0]",['a',0,0,0]); }) | ||||
|  | ||||
|         it("fail a'b'.c",function() { testInvalid("a'b'.c"); }) | ||||
|         it("fail a['b'.c",function() { testInvalid("a['b'.c"); }) | ||||
|         it("fail a[]",function() { testInvalid("a[]"); }) | ||||
|         it("fail a]",function() { testInvalid("a]"); }) | ||||
|         it("fail a[",function() { testInvalid("a["); }) | ||||
|         it("fail a[0d]",function() { testInvalid("a[0d]"); }) | ||||
|         it("fail a['",function() { testInvalid("a['"); }) | ||||
|         it("fail a[']",function() { testInvalid("a[']"); }) | ||||
|         it("fail a[0']",function() { testInvalid("a[0']"); }) | ||||
|         it("fail a.[0]",function() { testInvalid("a.[0]"); }) | ||||
|         it("fail [0]",function() { testInvalid("[0]"); }) | ||||
|         it("fail a[0",function() { testInvalid("a[0"); }) | ||||
|         it("fail a.",function() { testInvalid("a."); }) | ||||
|         it("fail a[0].[1]",function() { testInvalid("a[0].[1]"); }) | ||||
|     }); | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user