let split node specify property to split on

and let join auto join the correct property
or manually the specified one.
This commit is contained in:
Dave Conway-Jones 2023-10-17 21:12:13 +01:00
parent eb940d6d57
commit 9fd929ac1e
No known key found for this signature in database
GPG Key ID: 1DDB0E91A28C2643
4 changed files with 161 additions and 40 deletions

View File

@ -15,7 +15,11 @@
-->
<script type="text/html" data-template-name="split">
<div class="form-row"><span data-i18n="[html]split.intro"></span></div>
<!-- <div class="form-row"><span data-i18n="[html]split.intro"></span></div> -->
<div class="form-row">
<label for="node-input-property"><i class="fa fa-forward"></i> <span data-i18n="split.split"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row"><span data-i18n="[html]split.strBuff"></span></div>
<div class="form-row">
<label for="node-input-splt" style="padding-left:10px; margin-right:-10px;" data-i18n="split.splitUsing"></label>
@ -39,10 +43,9 @@
<label for="node-input-addname-cb" style="width:auto;" data-i18n="split.addname"></label>
<input type="text" id="node-input-addname" style="width:70%">
</div>
<hr/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
</div>
</script>
@ -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"),

View File

@ -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<count-1; i++) {
msg.payload = data.substring(pos,pos+node.splt);
RED.util.setMessageProperty(msg,node.property,data.substring(pos,pos+node.splt));
msg.parts.index = node.c++;
pos += node.splt;
send(RED.util.cloneMessage(msg));
@ -102,7 +110,7 @@ module.exports = function(RED) {
}
node.remainder = data.substring(pos);
if ((node.stream !== true) || (node.remainder.length === node.splt)) {
msg.payload = node.remainder;
RED.util.setMessageProperty(msg,node.property,node.remainder);
msg.parts.index = node.c++;
send(RED.util.cloneMessage(msg));
node.pendingDones.forEach(d => 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<count; i++) {
msg.payload = data.slice(pos,pos+node.arraySplt);
var m = data.slice(pos,pos+node.arraySplt);
if (node.arraySplt === 1) {
msg.payload = msg.payload[0];
m = m[0];
}
RED.util.setMessageProperty(msg,node.property,m);
msg.parts.index = i;
pos += node.arraySplt;
send(RED.util.cloneMessage(msg));
}
done();
}
else if ((typeof msg.payload === "object") && !Buffer.isBuffer(msg.payload)) {
else if ((typeof value === "object") && !Buffer.isBuffer(value)) {
var j = 0;
var l = Object.keys(msg.payload).length;
var pay = msg.payload;
var l = Object.keys(value).length;
var pay = value;
msg.parts.type = "object";
for (var p in pay) {
if (pay.hasOwnProperty(p)) {
msg.payload = pay[p];
RED.util.setMessageProperty(msg,node.property,pay[p]);
if (node.addname !== "") {
msg[node.addname] = p;
RED.util.setMessageProperty(msg,node.addname,p);
}
msg.parts.key = p;
msg.parts.index = j;
@ -170,9 +179,9 @@ module.exports = function(RED) {
}
done();
}
else if (Buffer.isBuffer(msg.payload)) {
var len = node.buffer.length + msg.payload.length;
var buff = Buffer.concat([node.buffer, msg.payload], len);
else if (Buffer.isBuffer(value)) {
var len = node.buffer.length + value.length;
var buff = Buffer.concat([node.buffer, value], len);
msg.parts.type = "buffer";
if (node.spltType === "len") {
var count = buff.length/node.splt;
@ -186,7 +195,7 @@ module.exports = function(RED) {
var pos = 0;
msg.parts.len = node.splt;
for (var i=0; i<count-1; i++) {
msg.payload = buff.slice(pos,pos+node.splt);
RED.util.setMessageProperty(msg,node.property,buff.slice(pos,pos+node.splt));
msg.parts.index = node.c++;
pos += node.splt;
send(RED.util.cloneMessage(msg));
@ -197,7 +206,7 @@ module.exports = function(RED) {
}
node.buffer = buff.slice(pos);
if ((node.stream !== true) || (node.buffer.length === node.splt)) {
msg.payload = node.buffer;
RED.util.setMessageProperty(msg,node.property,node.buffer);
msg.parts.index = node.c++;
send(RED.util.cloneMessage(msg));
node.pendingDones.forEach(d => 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);

View File

@ -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 <code>msg.payload</code> based on type:",
"object": "<b>Object</b>",
"objectSend": "Send a message for each key/value pair",

View File

@ -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"}];