Merge branch 'dev' into export-module-info

This commit is contained in:
Nick O'Leary
2025-03-17 16:51:44 +00:00
committed by GitHub
138 changed files with 4146 additions and 2156 deletions

View File

@@ -46,12 +46,12 @@
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
<span data-i18n="inject.every"></span>
<input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<input id="inject-time-interval-count" class="inject-time-count" value="1">
<select style="width:100px" id="inject-time-interval-units">
<option value="s" data-i18n="inject.seconds"></option>
<option value="m" data-i18n="inject.minutes"></option>
<option value="h" data-i18n="inject.hours"></option>
</select><br/>
</select><br>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
@@ -68,46 +68,46 @@
<option value="20">20</option>
<option value="30">30</option>
<option value="0">60</option>
</select> <span data-i18n="inject.minutes"></span><br/>
</select> <span data-i18n="inject.minutes"></span><br>
<span data-i18n="inject.between"></span> <select id="inject-time-interval-time-start" class="inject-time-times"></select>
<span data-i18n="inject.and"></span> <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
<span data-i18n="inject.and"></span> <select id="inject-time-interval-time-end" class="inject-time-times"></select><br>
<div id="inject-time-interval-time-days" class="inject-time-days" style="margin-top:5px">
<div style="display:inline-block; vertical-align:top; margin-right:5px;" data-i18n="inject.on">on</div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
<label><input type='checkbox' checked value='1'> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'> <span data-i18n="inject.days.2"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
<label><input type='checkbox' checked value='4'> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'> <span data-i18n="inject.days.5"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
<label><input type='checkbox' checked value='0'> <span data-i18n="inject.days.6"></span></label>
</div>
</div>
</div>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
<span data-i18n="inject.at"></span> <input type="text" id="inject-time-time" value="12:00"></input><br/>
<span data-i18n="inject.at"></span> <input type="text" id="inject-time-time" value="12:00"><br>
<div id="inject-time-time-days" class="inject-time-days">
<div style="display:inline-block; vertical-align:top; margin-right:5px;" data-i18n="inject.on"></div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
<label><input type='checkbox' checked value='1'> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'> <span data-i18n="inject.days.2"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
<label><input type='checkbox' checked value='4'> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'> <span data-i18n="inject.days.5"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
<label><input type='checkbox' checked value='0'> <span data-i18n="inject.days.6"></span></label>
</div>
</div>
</div>

View File

@@ -148,7 +148,7 @@ module.exports = function(RED) {
var st = (typeof output === 'string') ? output : util.inspect(output);
var fill = "grey";
var shape = "dot";
if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
if (typeof output === 'object' && output?.fill && output?.shape && output?.text) {
fill = output.fill;
shape = output.shape;
st = output.text;

View File

@@ -511,9 +511,10 @@ RED.debug = (function() {
typeHint: format,
hideKey: false,
path: path,
sourceId: sourceNode&&sourceNode.id,
sourceId: sourceNode && sourceNode.id,
rootPath: path,
nodeSelector: config.messageSourceClick,
enablePinning: true
});
// Do this in a separate step so the element functions aren't stripped
debugMessage.appendTo(el);

View File

@@ -197,14 +197,6 @@
// object that maps from library name to its descriptor
var allLibs = [];
function moduleName(module) {
var match = /^([^@]+)@(.+)/.exec(module);
if (match) {
return [match[1], match[2]];
}
return [module, undefined];
}
function getAllUsedModules() {
var moduleSet = new Set();
for (var id in knownFunctionNodes) {
@@ -302,7 +294,7 @@
if (val === "_custom_") {
val = $(this).val();
}
var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/\.].?/g, function(v) { return v[1]?v[1].toUpperCase():"" });
var varName = val.trim().replace(/^node:/,"").replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/\.].?/g, function(v) { return v[1]?v[1].toUpperCase():"" });
fvar.val(varName);
fvar.trigger("change");

View File

@@ -111,8 +111,6 @@ module.exports = function(RED) {
throw new Error(RED._("function.error.externalModuleNotAllowed"));
}
var functionText = "var results = null;"+
"results = (async function(msg,__send__,__done__){ "+
"var __msgid__ = msg._msgid;"+
@@ -166,7 +164,13 @@ module.exports = function(RED) {
Buffer:Buffer,
Date: Date,
RED: {
util: RED.util
util: {
...RED.util,
getSetting: function (_node, name, _flow) {
// Ensure `node` argument is the Function node and do not allow flow to be overridden.
return RED.util.getSetting(node, name);
}
}
},
__node__: {
id: node.id,

View File

@@ -21,8 +21,8 @@
</div>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="switch.label.property"></span></label>
<input type="text" id="node-input-property" style="width: calc(100% - 105px)"/>
<input type="hidden" id="node-input-outputs"/>
<input type="text" id="node-input-property" style="width: calc(100% - 105px)">
<input type="hidden" id="node-input-outputs">
</div>
<div class="form-row node-input-rule-container-row">
<ol id="node-input-rule-container"></ol>
@@ -35,7 +35,7 @@
</div>
<div class="form-row">
<input type="checkbox" id="node-input-repair" style="display: inline-block; width: auto; vertical-align: top;">
<label style="width: auto;" for="node-input-repair"><span data-i18n="switch.label.repair"></span></label></input>
<label style="width: auto;" for="node-input-repair"><span data-i18n="switch.label.repair"></span></label>
</div>
</script>

View File

@@ -352,7 +352,9 @@ module.exports = function(RED) {
if (msgs.length === 0) {
done()
} else {
drainMessageGroup(msgs,count,done);
setImmediate(() => {
drainMessageGroup(msgs,count,done);
})
}
}
})
@@ -505,7 +507,9 @@ module.exports = function(RED) {
if (err) {
node.error(err,nextMsg);
}
processMessageQueue()
setImmediate(() => {
processMessageQueue()
})
});
}

View File

@@ -2,7 +2,7 @@
<script type="text/html" data-template-name="range">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:calc(70% - 1px)"/>
<input type="text" id="node-input-property" style="width:calc(70% - 1px)">
</div>
<div class="form-row">
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="range.label.action"></span></label>
@@ -13,23 +13,23 @@
<option value="drop" data-i18n="range.scale.drop"></option>
</select>
</div>
<br/>
<br>
<div class="form-row"><i class="fa fa-sign-in"></i> <span data-i18n="range.label.inputrange"></span>:</div>
<div class="form-row"><label></label>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minin" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxin" data-i18n="[placeholder]range.placeholder.maxin" style="width:100px;"/>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minin" data-i18n="[placeholder]range.placeholder.min" style="width:100px;">
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxin" data-i18n="[placeholder]range.placeholder.maxin" style="width:100px;">
</div>
<div class="form-row"><i class="fa fa-sign-out"></i> <span data-i18n="range.label.resultrange"></span>:</div>
<div class="form-row"><label></label>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minout" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxout" data-i18n="[placeholder]range.placeholder.maxout" style="width:100px;"/>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minout" data-i18n="[placeholder]range.placeholder.min" style="width:100px;">
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxout" data-i18n="[placeholder]range.placeholder.maxout" style="width:100px;">
</div>
<br/>
<br>
<div class="form-row"><label></label>
<input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;">
<label style="width: auto;" for="node-input-round"><span data-i18n="range.label.roundresult"></span></label></input>
<label style="width: auto;" for="node-input-round"><span data-i18n="range.label.roundresult"></span></label>
</div>
<br/>
<br>
<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">

View File

@@ -253,7 +253,13 @@ module.exports = function(RED) {
if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate))) {
node.rate = m.rate;
}
send(m);
if (msg.hasOwnProperty("reset")) {
if (msg.hasOwnProperty("flush")) {
node.buffer.push({msg: m, send: send, done: done});
}
}
else { send(m); }
node.reportDepth();
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
done();
@@ -285,42 +291,23 @@ module.exports = function(RED) {
}
}
else if (!msg.hasOwnProperty("reset")) {
if (maxKeptMsgsCount(node) > 0) {
if (node.intervalID === -1) {
node.send(msg);
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
} else {
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
node.rate = msg.rate;
clearInterval(node.intervalID);
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
}
if (node.buffer.length < _maxKeptMsgsCount) {
var m = RED.util.cloneMessage(msg);
node.buffer.push({msg: m, send: send, done: done});
} else {
node.trace("dropped due to buffer overflow. msg._msgid = " + msg._msgid);
node.droppedMsgs++;
}
}
} else {
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
node.rate = msg.rate;
}
var timeSinceLast;
if (node.lastSent) {
timeSinceLast = process.hrtime(node.lastSent);
}
if (!node.lastSent) { // ensuring that we always send the first message
node.lastSent = process.hrtime();
send(msg);
}
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
node.lastSent = process.hrtime();
send(msg);
} else if (node.outputs === 2) {
send([null,msg])
}
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
node.rate = msg.rate;
}
var timeSinceLast;
if (node.lastSent) {
timeSinceLast = process.hrtime(node.lastSent);
}
if (!node.lastSent) { // ensuring that we always send the first message
node.lastSent = process.hrtime();
send(msg);
}
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
node.lastSent = process.hrtime();
send(msg);
}
else if (node.outputs === 2) {
send([null,msg])
}
done();
}

View File

@@ -63,7 +63,7 @@
<li><span data-i18n="trigger.label.resetPayload"></span> <input type="text" id="node-input-reset" style="width:150px" data-i18n="[placeholder]trigger.label.resetprompt"></li>
</ul>
</div>
<br/>
<br>
<div class="form-row">
<label data-i18n="trigger.for" for="node-input-bytopic"></label>
<select id="node-input-bytopic" style="width:120px;">
@@ -71,12 +71,12 @@
<option value="topic" data-i18n="trigger.bytopics"></option>
</select>
<span class="form-row" id="node-stream-topic">
<input type="text" id="node-input-topic" style="width:46%;"/>
<input type="text" id="node-input-topic" style="width:46%;">
</span>
</div>
<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"></input>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
<input type="hidden" id="node-input-outputs" value="1">
</div>
</script>

View File

@@ -24,6 +24,14 @@ module.exports = function(RED) {
this.op2 = n.op2 || "0";
this.op1type = n.op1type || "str";
this.op2type = n.op2type || "str";
// If the op1/2type is 'date', then we need to leave op1/2 alone so that
// evaluateNodeProperty works as expected.
if (this.op1type === 'date' && this.op1 === '1') {
this.op1 = ''
}
if (this.op2type === 'date' && this.op2 === '0') {
this.op2 = ''
}
this.second = (n.outputs == 2) ? true : false;
this.topic = n.topic || "topic";
@@ -193,7 +201,7 @@ module.exports = function(RED) {
if (node.op2type !== "nul") {
var promise = Promise.resolve();
msg2 = RED.util.cloneMessage(msg);
if (node.op2type === "flow" || node.op2type === "global") {
if (node.op2type === "flow" || node.op2type === "global" || node.op2type === "date") {
promise = new Promise((resolve,reject) => {
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
if (err) {
@@ -213,7 +221,6 @@ module.exports = function(RED) {
}
else {
msg2.payload = node.topics[topic].m2;
if (node.op2type === "date") { msg2.payload = Date.now(); }
if (node.second === true) { msgInfo.send([null,msg2]); }
else { msgInfo.send(msg2); }
}

View File

@@ -25,7 +25,7 @@
</div>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
<input type="text" id="node-input-property" style="width:70%;">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label> </label>
@@ -34,7 +34,7 @@
</div>
<div class="form-row">
<label> </label>
<input type="text" id="node-input-topi" style="width:70%;"/>
<input type="text" id="node-input-topi" style="width:70%;">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="rbe.label.name"></span></label>

View File

@@ -104,14 +104,14 @@ module.exports = function(RED) {
if (this.credentials && this.credentials.passphrase) {
opts.passphrase = this.credentials.passphrase;
}
if (this.servername) {
opts.servername = this.servername;
}
if (this.alpnprotocol) {
opts.ALPNProtocols = [this.alpnprotocol];
}
opts.rejectUnauthorized = this.verifyservercert;
}
if (this.servername) {
opts.servername = this.servername;
}
if (this.alpnprotocol) {
opts.ALPNProtocols = [this.alpnprotocol];
}
opts.rejectUnauthorized = this.verifyservercert;
return opts;
}

View File

@@ -158,9 +158,16 @@ module.exports = function(RED) {
if(!keys || !keys.length) return null;
keys.forEach(key => {
let val = srcUserProperties[key];
if(typeof val == "string") {
if(typeof val === "string") {
count++;
_clone[key] = val;
} else if (val !== undefined && val !== null) {
try {
_clone[key] = JSON.stringify(val)
count++;
} catch (err) {
// Silently drop property
}
}
});
if(count) properties.userProperties = _clone;
@@ -673,6 +680,8 @@ module.exports = function(RED) {
delete node.options.protocolId; //V4+ default
delete node.options.protocolVersion; //V4 default
delete node.options.properties;//V5 only
if (node.compatmode == "true" || node.compatmode === true || node.protocolVersion == 3) {
node.options.protocolId = 'MQIsdp';//V3 compat only
node.options.protocolVersion = 3;
@@ -691,6 +700,21 @@ module.exports = function(RED) {
setIntProp(node,node.options.properties,"sessionExpiryInterval");
}
}
// Ensure will payload, if set, is a string
if (node.options.will && Object.hasOwn(node.options.will, 'payload')) {
let payload = node.options.will.payload
if (payload === null || typeof payload === 'undefined') {
payload = "";
} else if (!Buffer.isBuffer(payload)) {
if (typeof payload === "object") {
payload = JSON.stringify(payload);
} else if (typeof payload !== "string") {
payload = "" + payload;
}
}
node.options.will.payload = payload
}
if (node.usetls && n.tls) {
var tlsNode = RED.nodes.getNode(n.tls);
if (tlsNode) {
@@ -725,6 +749,7 @@ module.exports = function(RED) {
};
node.deregister = function(mqttNode, done, autoDisconnect) {
setStatusDisconnected(mqttNode, false);
delete node.users[mqttNode.id];
if (autoDisconnect && !node.closing && node.connected && Object.keys(node.users).length === 0) {
node.disconnect(done);

View File

@@ -50,7 +50,7 @@
</div>
<div id="node-row-newline" class="form-row hidden" style="padding-left:110px;">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br>
<input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
</div>
@@ -317,7 +317,7 @@
<span id="node-units"></span>
</div>
<div id="node-row-newline" class="form-row hidden" style="padding-left:162px;">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br>
<input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
</div>
<div class="form-row">

View File

@@ -35,21 +35,21 @@
<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">
</div>
<hr align="middle"/>
<hr align="middle">
<div class="form-row">
<label style="width:100%;"><span data-i18n="csv.label.c2o"></span></label>
</div>
<div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label>
<span data-i18n="csv.label.skip-s"></span>&nbsp;<input type="text" id="node-input-skip" style="width:40px; height:25px;"/>&nbsp;<span data-i18n="csv.label.skip-e"></span><br/>
<span data-i18n="csv.label.skip-s"></span>&nbsp;<input type="text" id="node-input-skip" style="width:40px; height:25px;">&nbsp;<span data-i18n="csv.label.skip-e"></span><br>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br/>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-strings"><label style="width:auto; margin-top:7px;" for="node-input-strings"><span data-i18n="csv.label.usestrings"></span></label><br/>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-strings"><label style="width:auto; margin-top:7px;" for="node-input-strings"><span data-i18n="csv.label.usestrings"></span></label><br>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_empty_strings"><label style="width:auto; margin-top:7px;" for="node-input-include_empty_strings"><span data-i18n="csv.label.include_empty_strings"></span></label><br/>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_empty_strings"><label style="width:auto; margin-top:7px;" for="node-input-include_empty_strings"><span data-i18n="csv.label.include_empty_strings"></span></label><br>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_null_values"><label style="width:auto; margin-top:7px;" for="node-input-include_null_values"><span data-i18n="csv.label.include_null_values"></span></label><br/>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_null_values"><label style="width:auto; margin-top:7px;" for="node-input-include_null_values"><span data-i18n="csv.label.include_null_values"></span></label><br>
</div>
<div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>

View File

@@ -367,20 +367,21 @@ module.exports = function(RED) {
const sendHeadersAlways = node.hdrout === "all"
const sendHeaders = !dontSendHeaders && (sendHeadersOnce || sendHeadersAlways)
const quoteables = [node.sep, node.quo, "\n", "\r"]
const templateQuoteables = [',', '"', "\n", "\r"]
const templateQuoteables = [node.sep, node.quo, "\n", "\r"]
const templateQuoteablesStrict = [',', '"', "\n", "\r"]
let badTemplateWarnOnce = true
const columnStringToTemplateArray = function (col, sep) {
// NOTE: enforce strict column template parsing in RFC4180 mode
const parsed = csv.parse(col, { separator: sep, quote: node.quo, outputStyle: 'array', strict: true })
if (parsed.headers.length > 0) { node.goodtmpl = true } else { node.goodtmpl = false }
return parsed.headers.length ? parsed.headers : null
if (parsed.data?.length === 1) { node.goodtmpl = true } else { node.goodtmpl = false }
return node.goodtmpl ? parsed.data[0] : null
}
const templateArrayToColumnString = function (template, keepEmptyColumns) {
// NOTE: enforce strict column template parsing in RFC4180 mode
const parsed = csv.parse('', {headers: template, headersOnly:true, separator: ',', quote: node.quo, outputStyle: 'array', strict: true })
const templateArrayToColumnString = function (template, keepEmptyColumns, separator = ',', quotables = templateQuoteablesStrict) {
// NOTE: defaults to strict column template parsing (commas and double quotes)
const parsed = csv.parse('', {headers: template, headersOnly:true, separator, quote: node.quo, outputStyle: 'array', strict: true })
return keepEmptyColumns
? parsed.headers.map(e => addQuotes(e || '', { separator: ',', quoteables: templateQuoteables}))
? parsed.headers.map(e => addQuotes(e || '', { separator, quoteables: quotables })).join(separator)
: parsed.header // exclues empty columns
// TODO: resolve inconsistency between CSV->JSON and JSON->CSV
// CSV->JSON: empty columns are excluded
@@ -447,7 +448,7 @@ module.exports = function(RED) {
template = Object.keys(inputData[0]) || ['']
}
}
stringBuilder.push(templateArrayToColumnString(template, true))
stringBuilder.push(templateArrayToColumnString(template, true, node.sep, templateQuoteables)) // use user set separator for output data.
if (sendHeadersOnce) { node.hdrSent = true }
}
@@ -483,6 +484,7 @@ module.exports = function(RED) {
node.warn(RED._("csv.errors.obj_csv"))
badTemplateWarnOnce = false
}
template = Object.keys(row) || ['']
const rowData = []
for (let header in inputData[0]) {
if (row.hasOwnProperty(header)) {
@@ -518,7 +520,7 @@ module.exports = function(RED) {
// join lines, don't forget to add the last new line
msg.payload = stringBuilder.join(node.ret) + node.ret
msg.columns = templateArrayToColumnString(template)
msg.columns = templateArrayToColumnString(template) // always strict commas + double quotes for
if (msg.payload !== '') { send(msg) }
done()
}
@@ -615,16 +617,15 @@ module.exports = function(RED) {
}
if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store
msg.columns = csvParseResult.header
// msg._mode = 'RFC4180 mode'
// msg.columns = csvParseResult.header
msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
delete msg.parts
send(msg)
node.store = []
}
}
else {
msg.columns = csvParseResult.header
// msg._mode = 'RFC4180 mode'
msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
msg.payload = data
send(msg); // finally send the array
}
@@ -633,7 +634,8 @@ module.exports = function(RED) {
const len = data.length
for (let row = 0; row < len; row++) {
const newMessage = RED.util.cloneMessage(msg)
newMessage.columns = csvParseResult.header
// newMessage.columns = csvParseResult.header
newMessage.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
newMessage.payload = data[row]
if (!has_parts) {
newMessage.parts = {

View File

@@ -33,7 +33,7 @@
<label for="node-input-chr" style="width: 230px;"><i class="fa fa-tag"></i> <span data-i18n="html.label.prefix"></span></label>
<input type="text" id="node-input-chr" style="text-align:center; width: 40px;" placeholder="_">
</div>
<br/>
<br>
<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" style="width:70%" data-i18n="[placeholder]common.label.name">

View File

@@ -10,13 +10,13 @@
</div>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="json.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
<input type="text" id="node-input-property" style="width:70%;">
</div>
<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">
</div>
<hr align="middle"/>
<hr align="middle">
<div class="form-row node-json-to-json-options">
<label style="width:100%;"><span data-i18n="json.label.o2j"></span></label>
</div>

View File

@@ -2,13 +2,13 @@
<script type="text/html" data-template-name="xml">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
<input type="text" id="node-input-property" style="width:70%;">
</div>
<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">
</div>
<hr align="middle"/>
<hr align="middle">
<div class="form-row">
<label style="width:100%;"><span data-i18n="xml.label.x2o"></span></label>
</div>

View File

@@ -2,7 +2,7 @@
<script type="text/html" data-template-name="yaml">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
<input type="text" id="node-input-property" style="width:70%;">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>

View File

@@ -22,7 +22,7 @@
</div>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-forward"></i> <span data-i18n="split.splitThe"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
<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">
@@ -202,7 +202,7 @@
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-reduceRight" style="display:inline-block; width:auto; vertical-align:top; margin-left:10px;">
<label for="node-input-reduceRight" style="width:70%;" data-i18n="join.reduce.right" style="margin-left:10px;"/>
<label for="node-input-reduceRight" data-i18n="join.reduce.right" style="width:70%; margin-left:10px;"></label>
</div>
</div>
<div class="form-tips form-tips-auto hide" data-i18n="[html]join.tip"></div>

View File

@@ -339,7 +339,7 @@ module.exports = function(RED) {
}
else {
msg.filename = filename;
var lines = Buffer.from([]);
const bufferArray = [];
var spare = "";
var count = 0;
var type = "buffer";
@@ -397,7 +397,7 @@ module.exports = function(RED) {
}
}
else {
lines = Buffer.concat([lines,chunk]);
bufferArray.push(chunk);
}
}
})
@@ -413,10 +413,11 @@ module.exports = function(RED) {
})
.on('end', function() {
if (node.chunk === false) {
const buffer = Buffer.concat(bufferArray);
if (node.format === "utf8") {
msg.payload = decode(lines, node.encoding);
msg.payload = decode(buffer, node.encoding);
}
else { msg.payload = lines; }
else { msg.payload = buffer; }
nodeSend(msg);
}
else if (node.format === "lines") {