From 9fd929ac1e7df2ee2315b3f600e0c888298354a7 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 17 Oct 2023 21:12:13 +0100 Subject: [PATCH] let split node specify property to split on and let join auto join the correct property or manually the specified one. --- .../nodes/core/sequence/17-split.html | 18 +++- .../@node-red/nodes/core/sequence/17-split.js | 79 ++++++++------ .../nodes/locales/en-US/messages.json | 2 +- test/nodes/core/sequence/17-split_spec.js | 102 ++++++++++++++++++ 4 files changed, 161 insertions(+), 40 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.html b/packages/node_modules/@node-red/nodes/core/sequence/17-split.html index 72ab55335..053c534c4 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.html +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.html @@ -15,7 +15,11 @@ --> @@ -57,7 +60,8 @@ arraySplt: {value:1}, arraySpltType: {value:"len"}, stream: {value:false}, - addname: {value:""} + addname: {value:""}, + property: {value:"payload",required:true} }, inputs:1, outputs:1, @@ -69,6 +73,10 @@ return this.name?"node_label_italic":""; }, oneditprepare: function() { + if (this.property === undefined) { + $("#node-input-property").val("payload"); + } + $("#node-input-property").typedInput({default:'msg',types:['msg']}); $("#node-input-splt").typedInput({ default: 'str', typeField: $("#node-input-spltType"), diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js index 10c696b76..e9b54dff8 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js @@ -19,13 +19,13 @@ module.exports = function(RED) { function sendArray(node,msg,array,send) { for (var i = 0; i < array.length-1; i++) { - msg.payload = array[i]; + RED.util.setMessageProperty(msg,node.property,array[i]); msg.parts.index = node.c++; if (node.stream !== true) { msg.parts.count = array.length; } send(RED.util.cloneMessage(msg)); } if (node.stream !== true) { - msg.payload = array[i]; + RED.util.setMessageProperty(msg,node.property,array[i]); msg.parts.index = node.c++; msg.parts.count = array.length; send(RED.util.cloneMessage(msg)); @@ -40,10 +40,12 @@ module.exports = function(RED) { node.stream = n.stream; node.spltType = n.spltType || "str"; node.addname = n.addname || ""; + node.property = n.property||"payload"; try { if (node.spltType === "str") { this.splt = (n.splt || "\\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0"); - } else if (node.spltType === "bin") { + } + else if (node.spltType === "bin") { var spltArray = JSON.parse(n.splt); if (Array.isArray(spltArray)) { this.splt = Buffer.from(spltArray); @@ -51,7 +53,8 @@ module.exports = function(RED) { throw new Error("not an array"); } this.spltBuffer = spltArray; - } else if (node.spltType === "len") { + } + else if (node.spltType === "len") { this.splt = parseInt(n.splt); if (isNaN(this.splt) || this.splt < 1) { throw new Error("invalid split length: "+n.splt); @@ -69,18 +72,23 @@ module.exports = function(RED) { node.buffer = Buffer.from([]); node.pendingDones = []; this.on("input", function(msg, send, done) { - if (msg.hasOwnProperty("payload")) { + var value = RED.util.getMessageProperty(msg,node.property); + if (value !== undefined) { + RED.util.setMessageProperty(msg,node.property,undefined); if (msg.hasOwnProperty("parts")) { msg.parts = { parts:msg.parts }; } // push existing parts to a stack else { msg.parts = {}; } msg.parts.id = RED.util.generateId(); // generate a random id + if (node.property !== "payload") { + msg.parts.property = node.property; + } delete msg._msgid; - if (typeof msg.payload === "string") { // Split String into array - msg.payload = (node.remainder || "") + msg.payload; + if (typeof value === "string") { // Split String into array + value = (node.remainder || "") + value; msg.parts.type = "string"; if (node.spltType === "len") { msg.parts.ch = ""; msg.parts.len = node.splt; - var count = msg.payload.length/node.splt; + var count = value.length/node.splt; if (Math.floor(count) !== count) { count = Math.ceil(count); } @@ -89,9 +97,9 @@ module.exports = function(RED) { node.c = 0; } var pos = 0; - var data = msg.payload; + var data = value; for (var i=0; i d()); @@ -119,47 +127,48 @@ module.exports = function(RED) { if (!node.spltBufferString) { node.spltBufferString = node.splt.toString(); } - a = msg.payload.split(node.spltBufferString); + a = value.split(node.spltBufferString); msg.parts.ch = node.spltBuffer; // pass the split char to other end for rejoin } else if (node.spltType === "str") { - a = msg.payload.split(node.splt); + a = value.split(node.splt); msg.parts.ch = node.splt; // pass the split char to other end for rejoin } sendArray(node,msg,a,send); done(); } } - else if (Array.isArray(msg.payload)) { // then split array into messages + else if (Array.isArray(value)) { // then split array into messages msg.parts.type = "array"; - var count = msg.payload.length/node.arraySplt; + var count = value.length/node.arraySplt; if (Math.floor(count) !== count) { count = Math.ceil(count); } msg.parts.count = count; var pos = 0; - var data = msg.payload; + var data = value; msg.parts.len = node.arraySplt; for (var i=0; i d()); @@ -230,7 +239,7 @@ module.exports = function(RED) { var i = 0, p = 0; pos = buff.indexOf(node.splt); while (pos > -1) { - msg.payload = buff.slice(p,pos); + RED.util.setMessageProperty(msg,node.property,buff.slice(p,pos)); msg.parts.index = node.c++; send(RED.util.cloneMessage(msg)); i++; @@ -242,7 +251,7 @@ module.exports = function(RED) { node.pendingDones = []; } if ((node.stream !== true) && (p < buff.length)) { - msg.payload = buff.slice(p,buff.length); + RED.util.setMessageProperty(msg,node.property,buff.slice(p,buff.length)); msg.parts.index = node.c++; msg.parts.count = node.c++; send(RED.util.cloneMessage(msg)); @@ -298,7 +307,6 @@ module.exports = function(RED) { return exp } - function reduceMessageGroup(node,msgInfos,exp,fixup,count,accumulator,done) { var msgInfo = msgInfos.shift(); exp.assign("I", msgInfo.msg.parts.index); @@ -515,13 +523,13 @@ module.exports = function(RED) { if (typeof group.joinChar !== 'string') { groupJoinChar = group.joinChar.toString(); } - RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar)); + RED.util.setMessageProperty(group.msg,group?.prop||"payload",group.payload.join(groupJoinChar)); } else { if (node.propertyType === 'full') { group.msg = RED.util.cloneMessage(group.msg); } - RED.util.setMessageProperty(group.msg,node.property,group.payload); + RED.util.setMessageProperty(group.msg,group?.prop||"payload",group.payload); } if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) { group.msg.parts = group.msg.parts.parts; @@ -589,7 +597,7 @@ module.exports = function(RED) { } if (node.mode === 'auto' && (!msg.hasOwnProperty("parts")||!msg.parts.hasOwnProperty("id"))) { - // if a blank reset messag erest it all. + // if a blank reset message reset it all. if (msg.hasOwnProperty("reset")) { if (inflight && inflight.hasOwnProperty("partId") && inflight[partId].timeout) { clearTimeout(inflight[partId].timeout); @@ -618,6 +626,7 @@ module.exports = function(RED) { propertyKey = msg.parts.key; arrayLen = msg.parts.len; propertyIndex = msg.parts.index; + property = RED.util.getMessageProperty(msg,msg.parts.property||"payload"); } else if (node.mode === 'reduce') { return processReduceMessageQueue({msg, send, done}); @@ -719,6 +728,8 @@ module.exports = function(RED) { completeSend(partId) }, node.timer) } + if (node.mode === "auto") { inflight[partId].prop = msg.parts.property; } + else { inflight[partId].prop = node.property; } } inflight[partId].dones.push(done); diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index a7b583878..634432e37 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -1001,7 +1001,7 @@ "tip": "Tip: The filename should be an absolute path, otherwise it will be relative to the working directory of the Node-RED process." }, "split": { - "split": "split", + "split": "Split", "intro": "Split msg.payload based on type:", "object": "Object", "objectSend": "Send a message for each key/value pair", diff --git a/test/nodes/core/sequence/17-split_spec.js b/test/nodes/core/sequence/17-split_spec.js index 370e0cda4..a64f6e078 100644 --- a/test/nodes/core/sequence/17-split_spec.js +++ b/test/nodes/core/sequence/17-split_spec.js @@ -66,6 +66,27 @@ describe('SPLIT node', function() { }); }); + it('should split an array on a sub-property into multiple messages', function(done) { + var flow = [{id:"sn1", type:"split", property:"foo", wires:[["sn2"]]}, + {id:"sn2", type:"helper"}]; + helper.load(splitNode, flow, function() { + var sn1 = helper.getNode("sn1"); + var sn2 = helper.getNode("sn2"); + sn2.on("input", function(msg) { + msg.should.have.property("parts"); + msg.parts.should.have.property("count",4); + msg.parts.should.have.property("type","array"); + msg.parts.should.have.property("index"); + msg.parts.should.have.property("property","foo"); + if (msg.parts.index === 0) { msg.foo.should.equal(1); } + if (msg.parts.index === 1) { msg.foo.should.equal(2); } + if (msg.parts.index === 2) { msg.foo.should.equal(3); } + if (msg.parts.index === 3) { msg.foo.should.equal(4); done(); } + }); + sn1.receive({foo:[1,2,3,4]}); + }); + }); + it('should split an array into multiple messages of a specified size', function(done) { var flow = [{id:"sn1", type:"split", wires:[["sn2"]], arraySplt:3, arraySpltType:"len"}, {id:"sn2", type:"helper"}]; @@ -108,6 +129,31 @@ describe('SPLIT node', function() { }); }); + it('should split an object sub property into pieces', function(done) { + var flow = [{id:"sn1", type:"split", property:"foo.bar",wires:[["sn2"]]}, + {id:"sn2", type:"helper"}]; + helper.load(splitNode, flow, function() { + var sn1 = helper.getNode("sn1"); + var sn2 = helper.getNode("sn2"); + var count = 0; + sn2.on("input", function(msg) { + msg.should.have.property("foo"); + msg.foo.should.have.property("bar"); + msg.should.have.property("parts"); + msg.parts.should.have.property("type","object"); + msg.parts.should.have.property("key"); + msg.parts.should.have.property("count"); + msg.parts.should.have.property("index"); + msg.parts.should.have.property("property","foo.bar"); + msg.topic.should.equal("foo"); + if (msg.parts.index === 0) { msg.foo.bar.should.equal(1); } + if (msg.parts.index === 1) { msg.foo.bar.should.equal("2"); } + if (msg.parts.index === 2) { msg.foo.bar.should.equal(true); done(); } + }); + sn1.receive({topic:"foo",foo:{bar:{a:1,b:"2",c:true}}}); + }); + }); + it('should split an object into pieces and overwrite their topics', function(done) { var flow = [{id:"sn1", type:"split", addname:"topic", wires:[["sn2"]]}, {id:"sn2", type:"helper"}]; @@ -516,6 +562,7 @@ describe('JOIN node', function() { n1.receive({payload:{a:1}}); }); }); + it('should join things into an array ignoring msg.parts.index in manual mode', function(done) { var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joiner:",",mode:"custom"}, {id:"n2", type:"helper"}]; @@ -562,6 +609,32 @@ describe('JOIN node', function() { }); }); + it('should join things into an array on a sub property in auto mode', function(done) { + var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joiner:",", mode:"auto"}, + {id:"n2", type:"helper"}]; + helper.load(joinNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("foo"); + msg.foo.should.have.property("bar"); + msg.foo.bar.should.be.an.Array(); + msg.foo.bar[0].should.equal("A"); + msg.foo.bar[1].should.equal("B"); + //msg.payload[2].a.should.equal(1); + done(); + } + catch(e) {done(e);} + }); + n1.receive({foo:{bar:"A"}, parts:{id:1, type:"array", len:1, index:0, count:4, property:"foo.bar"}}); + n1.receive({foo:{bar:"B"}, parts:{id:1, type:"array", len:1, index:1, count:4, property:"foo.bar"}}); + n1.receive({foo:{bar:"C"}, parts:{id:1, type:"array", len:1, index:2, count:4, property:"foo.bar"}}); + n1.receive({foo:{bar:"D"}, parts:{id:1, type:"array", len:1, index:3, count:4, property:"foo.bar"}}); + }); + }); + + it('should join strings into a buffer after a count', function(done) { var flow = [{id:"n1", type:"join", wires:[["n2"]], count:2, build:"buffer", joinerType:"bin", joiner:"", mode:"custom"}, {id:"n2", type:"helper"}]; @@ -639,6 +712,35 @@ describe('JOIN node', function() { }); }); + it('should merge sub property objects', function(done) { + var flow = [{id:"n1", type:"join", wires:[["n2"]], count:5, property:"foo.bar", build:"merged", mode:"custom"}, + {id:"n2", type:"helper"}]; + helper.load(joinNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("foo"); + msg.foo.should.have.property("bar"); + msg.foo.bar.should.have.property("a",1); + msg.foo.bar.should.have.property("b",2); + msg.foo.bar.should.have.property("c",3); + msg.foo.bar.should.have.property("d",4); + msg.foo.bar.should.have.property("e",5); + done(); + } + catch(e) { done(e)} + }); + n1.receive({foo:{bar:{a:9}, topic:"f"}}); + n1.receive({foo:{bar:{a:1}, topic:"a"}}); + n1.receive({foo:{bar:{b:9}, topic:"b"}}); + n1.receive({foo:{bar:{b:2}, topic:"b"}}); + n1.receive({foo:{bar:{c:3}, topic:"c"}}); + n1.receive({foo:{bar:{d:4}, topic:"d"}}); + n1.receive({foo:{bar:{e:5}, topic:"e"}}); + }); + }); + it('should merge full msg objects', function(done) { var flow = [{id:"n1", type:"join", wires:[["n2"]], count:6, build:"merged", mode:"custom", propertyType:"full", property:""}, {id:"n2", type:"helper"}];