Update CSV node to support Property In/Out

This commit is contained in:
Steve-Mcl 2024-04-12 11:02:06 +01:00
parent 2621a3a628
commit 4874e64387
2 changed files with 61 additions and 41 deletions

View File

@ -1,5 +1,13 @@
<script type="text/html" data-template-name="csv"> <script type="text/html" data-template-name="csv">
<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>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-sign-in"></i> <span data-i18n="common.label.propertyIn"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-temp"><i class="fa fa-list"></i> <span data-i18n="csv.label.columns"></span></label> <label for="node-input-temp"><i class="fa fa-list"></i> <span data-i18n="csv.label.columns"></span></label>
<input type="text" id="node-input-temp" data-i18n="[placeholder]csv.placeholder.columns"> <input type="text" id="node-input-temp" data-i18n="[placeholder]csv.placeholder.columns">
@ -32,8 +40,8 @@
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-propertyOut"><i class="fa fa-sign-out"></i> <span data-i18n="common.label.propertyOut"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-propertyOut" style="width:70%;"/>
</div> </div>
<hr align="middle"/> <hr align="middle"/>
<div class="form-row"> <div class="form-row">
@ -102,7 +110,13 @@
skip: {value:"0"}, skip: {value:"0"},
strings: {value:true}, strings: {value:true},
include_empty_strings: {value:""}, include_empty_strings: {value:""},
include_null_values: {value:""} include_null_values: {value:""},
property: {value:"payload",required:true,
validate: RED.validators.typedInput({ type: 'msg', allowUndefined: true }),
label:RED._("node-red:common.label.propertyIn")},
propertyOut: {value:"payload",required:true,
validate: RED.validators.typedInput({ type: 'msg', allowUndefined: true}),
label:RED._("node-red:common.label.propertyOut")}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,

View File

@ -26,6 +26,9 @@ module.exports = function(RED) {
node.status({}) // clear status node.status({}) // clear status
node.property = n.property||"payload";
node.propertyOut = n.propertyOut||node.property;
if (legacyMode) { if (legacyMode) {
this.template = (n.temp || ""); this.template = (n.temp || "");
this.sep = (n.sep || ',').replace(/\\t/g,"\t").replace(/\\n/g,"\n").replace(/\\r/g,"\r"); this.sep = (n.sep || ',').replace(/\\t/g,"\t").replace(/\\n/g,"\n").replace(/\\r/g,"\r");
@ -66,43 +69,44 @@ module.exports = function(RED) {
if (msg.hasOwnProperty("reset")) { if (msg.hasOwnProperty("reset")) {
node.hdrSent = false; node.hdrSent = false;
} }
if (msg.hasOwnProperty("payload")) { if (msg.hasOwnProperty(node.property)) {
if (typeof msg.payload == "object") { // convert object to CSV string let inputData = RED.util.getMessageProperty(msg, node.property)
if (typeof inputData == "object") { // convert object to CSV string
try { try {
if (!(notemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) { if (!(notemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) {
template = clean(node.template); template = clean(node.template);
} }
const ou = []; const ou = [];
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; } if (!Array.isArray(inputData)) { inputData = [ inputData ]; }
if (node.hdrout !== "none" && node.hdrSent === false) { if (node.hdrout !== "none" && node.hdrSent === false) {
if ((template.length === 1) && (template[0] === '')) { if ((template.length === 1) && (template[0] === '')) {
if (msg.hasOwnProperty("columns")) { if (msg.hasOwnProperty("columns")) {
template = clean(msg.columns || "",","); template = clean(msg.columns || "",",");
} }
else { else {
template = Object.keys(msg.payload[0]); template = Object.keys(inputData[0]);
} }
} }
ou.push(template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep)); ou.push(template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep));
if (node.hdrout === "once") { node.hdrSent = true; } if (node.hdrout === "once") { node.hdrSent = true; }
} }
for (var s = 0; s < msg.payload.length; s++) { for (var s = 0; s < inputData.length; s++) {
if ((Array.isArray(msg.payload[s])) || (typeof msg.payload[s] !== "object")) { if ((Array.isArray(inputData[s])) || (typeof inputData[s] !== "object")) {
if (typeof msg.payload[s] !== "object") { msg.payload = [ msg.payload ]; } if (typeof inputData[s] !== "object") { inputData = [ inputData ]; }
for (var t = 0; t < msg.payload[s].length; t++) { for (var t = 0; t < inputData[s].length; t++) {
if (msg.payload[s][t] === undefined) { msg.payload[s][t] = ""; } if (inputData[s][t] === undefined) { inputData[s][t] = ""; }
if (msg.payload[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes if (inputData[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes
msg.payload[s][t] = msg.payload[s][t].toString().replace(/"/g, '""'); inputData[s][t] = inputData[s][t].toString().replace(/"/g, '""');
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo; inputData[s][t] = node.quo + inputData[s][t].toString() + node.quo;
} }
else if (msg.payload[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas" else if (inputData[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas"
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo; inputData[s][t] = node.quo + inputData[s][t].toString() + node.quo;
} }
else if (msg.payload[s][t].toString().indexOf("\n") !== -1) { // add quotes if any "\n" else if (inputData[s][t].toString().indexOf("\n") !== -1) { // add quotes if any "\n"
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo; inputData[s][t] = node.quo + inputData[s][t].toString() + node.quo;
} }
} }
ou.push(msg.payload[s].join(node.sep)); ou.push(inputData[s].join(node.sep));
} }
else { else {
if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) { if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
@ -115,16 +119,16 @@ module.exports = function(RED) {
tmpwarn = false; tmpwarn = false;
} }
const row = []; const row = [];
for (var p in msg.payload[0]) { for (var p in inputData[0]) {
/* istanbul ignore else */ /* istanbul ignore else */
if (msg.payload[s].hasOwnProperty(p)) { if (inputData[s].hasOwnProperty(p)) {
/* istanbul ignore else */ /* istanbul ignore else */
if (typeof msg.payload[s][p] !== "object") { if (typeof inputData[s][p] !== "object") {
// Fix to honour include null values flag // Fix to honour include null values flag
//if (typeof msg.payload[s][p] !== "object" || (node.include_null_values === true && msg.payload[s][p] === null)) { //if (typeof inputData[s][p] !== "object" || (node.include_null_values === true && inputData[s][p] === null)) {
var q = ""; var q = "";
if (msg.payload[s][p] !== undefined) { if (inputData[s][p] !== undefined) {
q += msg.payload[s][p]; q += inputData[s][p];
} }
if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
q = q.replace(/"/g, '""'); q = q.replace(/"/g, '""');
@ -149,7 +153,7 @@ module.exports = function(RED) {
var tt = template[t]; var tt = template[t];
if (template[t].indexOf('"') >=0 ) { tt = "'"+tt+"'"; } if (template[t].indexOf('"') >=0 ) { tt = "'"+tt+"'"; }
else { tt = '"'+tt+'"'; } else { tt = '"'+tt+'"'; }
var p = RED.util.getMessageProperty(msg,'payload["'+s+'"]['+tt+']'); var p = RED.util.getMessageProperty(msg, node.property + '["'+s+'"]['+tt+']');
/* istanbul ignore else */ /* istanbul ignore else */
if (p === undefined) { p = ""; } if (p === undefined) { p = ""; }
// fix to honour include null values flag // fix to honour include null values flag
@ -170,16 +174,17 @@ module.exports = function(RED) {
} }
} }
// join lines, don't forget to add the last new line // join lines, don't forget to add the last new line
msg.payload = ou.join(node.ret) + node.ret; inputData = ou.join(node.ret) + node.ret;
RED.util.setMessageProperty(msg, node.propertyOut, inputData)
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(','); msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(',');
if (msg.payload !== '') { if (inputData !== '') {
send(msg); send(msg);
} }
done(); done();
} }
catch(e) { done(e); } catch(e) { done(e); }
} }
else if (typeof msg.payload == "string") { // convert CSV string to object else if (typeof inputData == "string") { // convert CSV string to object
try { try {
var f = true; // flag to indicate if inside or outside a pair of quotes true = outside. var f = true; // flag to indicate if inside or outside a pair of quotes true = outside.
var j = 0; // pointer into array of template items var j = 0; // pointer into array of template items
@ -188,7 +193,7 @@ module.exports = function(RED) {
var a = []; // output array is needed for multiline option var a = []; // output array is needed for multiline option
var first = true; // is this the first line var first = true; // is this the first line
var last = false; var last = false;
var line = msg.payload; var line = inputData;
var linecount = 0; var linecount = 0;
var tmp = ""; var tmp = "";
var has_parts = msg.hasOwnProperty("parts"); var has_parts = msg.hasOwnProperty("parts");
@ -282,13 +287,13 @@ module.exports = function(RED) {
} }
if (node.multi !== "one") { if (node.multi !== "one") {
msg.payload = a; inputData = a;
if (has_parts && nocr <= 1) { if (has_parts && nocr <= 1) {
if (JSON.stringify(o) !== "{}") { if (JSON.stringify(o) !== "{}") {
node.store.push(o); node.store.push(o);
} }
if (msg.parts.index + 1 === msg.parts.count) { if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store; inputData = node.store;
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(','); msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
delete msg.parts; delete msg.parts;
send(msg); send(msg);
@ -305,7 +310,7 @@ module.exports = function(RED) {
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
var newMessage = RED.util.cloneMessage(msg); var newMessage = RED.util.cloneMessage(msg);
newMessage.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(','); newMessage.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
newMessage.payload = a[i]; RED.util.setMessageProperty(newMessage, node.propertyOut, a[i])
if (!has_parts) { if (!has_parts) {
newMessage.parts = { newMessage.parts = {
id: msg._msgid, id: msg._msgid,
@ -411,8 +416,8 @@ module.exports = function(RED) {
if (msg.hasOwnProperty("reset")) { if (msg.hasOwnProperty("reset")) {
node.hdrSent = false node.hdrSent = false
} }
if (msg.hasOwnProperty("payload")) { if (msg.hasOwnProperty(node.property)) {
let inputData = msg.payload let inputData = RED.util.getMessageProperty(msg, node.property)
if (typeof inputData == "object") { // convert object to CSV string if (typeof inputData == "object") { // convert object to CSV string
try { try {
// first determine the payload kind. Array or objects? Array of primitives? Array of arrays? Just an object? // first determine the payload kind. Array or objects? Array of primitives? Array of arrays? Just an object?
@ -517,9 +522,10 @@ module.exports = function(RED) {
} }
// join lines, don't forget to add the last new line // join lines, don't forget to add the last new line
msg.payload = stringBuilder.join(node.ret) + node.ret const result = stringBuilder.join(node.ret) + node.ret
RED.util.setMessageProperty(msg, node.propertyOut, result)
msg.columns = templateArrayToColumnString(template) msg.columns = templateArrayToColumnString(template)
if (msg.payload !== '') { send(msg) } if (result !== '') { send(msg) }
done() done()
} }
catch (e) { catch (e) {
@ -614,7 +620,7 @@ module.exports = function(RED) {
node.store.push(...data) node.store.push(...data)
} }
if (msg.parts.index + 1 === msg.parts.count) { if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store RED.util.setMessageProperty(msg, node.propertyOut, node.store)
msg.columns = csvParseResult.header msg.columns = csvParseResult.header
// msg._mode = 'RFC4180 mode' // msg._mode = 'RFC4180 mode'
delete msg.parts delete msg.parts
@ -625,7 +631,7 @@ module.exports = function(RED) {
else { else {
msg.columns = csvParseResult.header msg.columns = csvParseResult.header
// msg._mode = 'RFC4180 mode' // msg._mode = 'RFC4180 mode'
msg.payload = data RED.util.setMessageProperty(msg, node.propertyOut, data)
send(msg); // finally send the array send(msg); // finally send the array
} }
} }
@ -634,7 +640,7 @@ module.exports = function(RED) {
for (let row = 0; row < len; row++) { for (let row = 0; row < len; row++) {
const newMessage = RED.util.cloneMessage(msg) const newMessage = RED.util.cloneMessage(msg)
newMessage.columns = csvParseResult.header newMessage.columns = csvParseResult.header
newMessage.payload = data[row] RED.util.setMessageProperty(newMessage, node.propertyOut, data[row])
if (!has_parts) { if (!has_parts) {
newMessage.parts = { newMessage.parts = {
id: msg._msgid, id: msg._msgid,