Files
plastic-hub-dev-node-saturn 538369cff7 latest
2021-05-12 18:35:18 +02:00

178 lines
5.8 KiB
JavaScript

define("dstore/Csv", [
'dojo/_base/lang',
'dojo/_base/declare'
], function (lang, declare) {
// originally from https://github.com/kfranqueiro/dojo-smore/blob/master/Csv.js
var quoteRx = /^\s*"([\S\s]*)"\s*$/,
doubleQuoteRx = /""/g,
singleQuoteRx = /"/g;
function arrays2hash(keys, values) {
// Takes 2 arrays and builds a hash where the keys are from the first array,
// and the values are from the second.
var obj = {},
len = keys.length,
i;
for (i = 0; i < len; i++) {
obj[keys[i]] = values[i];
}
return obj;
}
return declare(null, {
// summary:
// A store mixin for supporting CSV format.
// fieldNames: Array?
// If specified, indicates names of fields in the order they appear in
// CSV records. If unspecified, the first line of the CSV will be treated
// as a header row, and field names will be populated from there.
fieldNames: null,
// delimiter: String
// Delimiter between fields; default is a comma.
delimiter: ',',
// newline: String
// Character sequence to consider a newline.
// Defaults to '\r\n' (CRLF) as per RFC 4180.
newline: '\r\n',
// trim: Boolean
// If true, leading/trailing space will be trimmed from any unquoted values.
trim: false,
parse: function (str) {
// handles the parsing of the incoming data as CSV.
var data = [],
lines = str.split(this.newline),
fieldNames = this.fieldNames,
numquotes = 0, // tracks number of " characters encountered
values = [], // records values in the current record
value = '',
prefix = '', // used to re-add delimiters and newlines to a spanning value
parts, part, numlines, numparts, match,
i, j, k;
// Outer loop iterates over lines. It's labeled so that inner loop
// can jump out if an invalid value is encountered.
lineloop:
for (i = 0, numlines = lines.length; i < numlines; i++) {
if (!lang.trim(lines[i])) { continue; } // ignore blank lines
parts = lines[i].split(this.delimiter);
// Inner loop iterates over "parts" (pieces of the line, split by
// the configured delimiter).
for (j = 0, numparts = parts.length; j < numparts; j++) {
part = parts[j];
k = -1;
// Apply any leftovers in prefix before the next part, then clear it.
value += prefix + part;
prefix = '';
// Count number of quotes in part to see whether we have a matching set.
while ((k = part.indexOf('"', k + 1)) >= 0) { numquotes++; }
if (numquotes % 2 === 0) {
// Even number of quotes: we're done with this value.
if (numquotes > 0) {
match = quoteRx.exec(value);
if (match) {
// Good quoted string; unescape any quotes within.
values.push(match[1].replace(doubleQuoteRx, '"'));
} else {
// If the completed value didn't match the RegExp, it's invalid
// (e.g. quotes were inside the value but not surrounding it).
// Jump out of the outer loop and start fresh on the next line.
0 && console.warn('Csv: discarding row with invalid value: ' + value);
values = [];
value = '';
numquotes = 0;
continue lineloop;
}
} else {
// No quotes; push value as-is or trimmed.
// (If this is the header row, trim regardless of setting.)
values.push(this.trim || !fieldNames ? lang.trim(value) : value);
}
value = '';
numquotes = 0;
} else {
// Open quoted value: add delimiter to current value on next run.
// (i.e., we split on an instance of the delimiter character that is
// actually *inside* a quoted value.)
prefix = this.delimiter;
}
} // End of inner loop (delimited parts)
if (numquotes === 0) {
// Line ended cleanly, push values and reset.
if (!fieldNames) {
// We don't know any field names yet, so pick them up from the
// first row of data.
fieldNames = this.fieldNames = values;
} else {
data.push(arrays2hash(fieldNames, values));
}
values = [];
} else {
// We're in the middle of a quoted value with a newline in it,
// so add a newline to it on the next iteration.
prefix = this.newline;
}
} // End of outer loop (lines)
// The data is assembled; return
return data;
},
toCsv: function (options) {
// summary:
// Returns data from Memory store, re-exported to CSV format.
return this.stringify(this.data, options);
},
stringify: function (data, options) {
// summary:
// Serializes data as CSV
// options: Object?
// Optional object specifying options affecting the CSV output.
// * alwaysQuote: if true (default), all values will be quoted;
// if false, values will be quoted only if they need to be.
// * trailingNewline: if true, a newline will be included at the end
// of the string (after the last record). Default is false.
options = options || {};
var alwaysQuote = options.alwaysQuote,
fieldNames = this.fieldNames,
delimiter = this.delimiter,
newline = this.newline,
output = '',
i, j, value, needsQuotes;
// Process header row first (-1 case), then all data rows.
for (i = -1; i < data.length; i++) {
if (i > -1) { output += newline; }
for (j = 0; j < fieldNames.length; j++) {
value = i < 0 ? fieldNames[j] : data[i][fieldNames[j]];
if (value === null || value === undefined) {
value = '';
}
if (typeof value !== 'string') {
value = value.toString();
}
needsQuotes = alwaysQuote ||
value.indexOf('"') >= 0 || value.indexOf(delimiter) >= 0;
output += (j > 0 ? delimiter : '') +
(needsQuotes ? '"' + value.replace(singleQuoteRx, '""') + '"' : value);
}
}
if (options.trailingNewline) { output += newline; }
return output;
}
});
});