/**
* This base class contains utility methods for dealing with formats such as CSV (Comma
* Separated Values) as specified in <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
*
* The base class implements the mechanics and is governed by these config options:
*
* * `{@link #delimiter}`
* * `{@link #lineBreak}`
* * `{@link #quote}`
*
* These options affect the `{@link #method-encode}` and `{@link #method-decode}` methods.
* When *decoding*, however, `{@link #lineBreak}` is ignored and instead each line can
* be separated by any standard line terminator character or character sequence:
*
* * ```\u000a```
* * ```\u000d```
* * ```\u000d\u000a```
*
* Strings which contain the {@link #delimiter} character are quoted using the
* {@link #quote} character, and any internal {@link #quote} characters are doubled.
*
* *Important*
* While the primary use case is to encode strings, other atomic data types can be encoded
* as values within a line such as:
*
* * Number
* * Boolean
* * Date (encoded as an <a href="http://www.iso.org/iso/home/standards/iso8601.htm">ISO 8601</a> date string.)
* * null (encoded as an empty string.)
* * undefined (encoded as an empty string.)
*
* Not that when *decoding*, all data is read as strings. This class does not convert
* incoming data. To do that, use an {@link Ext.data.reader.Array ArrayReader}.
*
* See `{@link Ext.util.CSV}` and `{@link Ext.util.TSV}` for pre-configured instances.
*
* @since 5.1.0
*/
Ext.define('Ext.util.DelimitedValue', {
/**
* @cfg {String} dateFormat
* The {@link Ext.Date#format format} to use for dates
*/
dateFormat: 'C',
/**
* @cfg {String} delimiter
* The string used to separate the values in a row. Common values for this config
* are comma (",") and tab ("\t"). See `{@link Ext.util.CSV}` and `{@link Ext.util.TSV}`
* for pre-configured instances of these formats.
*/
delimiter: '\t',
/**
* @cfg {String} lineBreak
* The string used by `{@link #encode}` to separate each row. The `{@link #decode}`
* method accepts all forms of line break.
*/
lineBreak: '\n',
/**
* @cfg {String} quote
* The character to use as to quote values that contain the special `delimiter`
* or `{@link #lineBreak}` characters.
*/
quote: '"',
lineBreakRe: /\r?\n/g,
// match line break at end of input
lastLineBreakRe: /(\r?\n|\r)$/,
constructor: function(config) {
if (config) {
Ext.apply(this, config);
}
this.parseREs = {};
this.quoteREs = {};
},
/* eslint-disable max-len */
/**
* Decodes a string of encoded values into an array of rows. Each row is an array of
* strings.
*
* Note that this function does not convert the string values in each column into
* other data types. To do that, use an {@link Ext.data.reader.Array ArrayReader}.
*
* For example:
*
* Ext.util.CSV.decode('"foo ""bar"", bletch",Normal String,2010-01-01T21:45:32.004Z\u000a3.141592653589793,1,false');
*
* produces the following array of string arrays:
*
* [
* ['foo "bar", bletch','Normal String', '2010-01-01T21:45:32.004Z'],
* ['3.141592653589793', '1', 'false']
* ]
*
* @param {String} input The string to parse.
*
* @param {String} [delimiter] The column delimiter to use if the default value
* of {@link #cfg-delimiter delimiter} is not desired.
*
* @param {String} [quoteChar] The character used to quote fields. Defaults to
* double quote ("). Pass `null` to suppress field quoting.
*
* @return {String[][]} An array of rows where each row is an array of Strings.
*/
decode: function(input, delimiter, quoteChar) {
/* eslint-enable max-len */
if (!input) {
return [];
}
// eslint-disable-next-line vars-on-top
var me = this,
// Check to see if the column delimiter is defined. If not,
// then default to comma.
row = [],
result = [row],
quote = quoteChar !== undefined ? quoteChar : me.quote,
quoteREs = me.quoteREs,
parseREs = me.parseREs,
parseRE, dblQuoteRE, arrMatches, strMatchedDelimiter, strMatchedValue;
delimiter = delimiter || me.delimiter;
// Create a regular expression to parse the CSV values unless we already have
// one for this delimiter/quoteChar.
parseRE = parseREs[delimiter] || new RegExp(
// Delimiters.
'(\\' + delimiter + '|\\r?\\n|\\r|^)' +
// (Optionally) Quoted fields.
'(?:\\' + quote + '([^\\' + quote + ']*(?:\\' + quote + '\\' + quote +
'[^\\' + quote + ']*)*)\\' + quote + '|' +
// Standard fields.
'([^\\' + delimiter + '\\r\\n]*))',
'gi');
dblQuoteRE = quoteREs[quote] ||
(quoteREs[quote] = new RegExp('\\' + quote + '\\' + quote, 'g'));
// Keep looping over the regular expression matches
// until we can no longer find a match.
while ((arrMatches = parseRE.exec(input))) {
strMatchedDelimiter = arrMatches[1];
// Check to see if the given delimiter has a length
// (is not the start of string) and if it matches
// field delimiter. If id does not, then we know
// that this delimiter is a row delimiter.
if (strMatchedDelimiter.length && strMatchedDelimiter !== delimiter) {
// Since we have reached a new row of data,
// add an empty row to our data array.
result.push(row = []);
}
// we need to account for the first value being empty
if (!arrMatches.index && arrMatches[0].charAt(0) === delimiter) {
row.push('');
}
// Now that we have our delimiter out of the way,
// let's check to see which kind of value we
// captured (quoted or unquoted).
if (arrMatches[2]) {
// We found a quoted value. When we capture
// this value, unescape any double quotes.
strMatchedValue = arrMatches[2].replace(dblQuoteRE, quote);
}
else {
// We found a non-quoted value.
strMatchedValue = arrMatches[3];
}
row.push(strMatchedValue);
}
return result;
},
/* eslint-disable max-len */
/**
* Converts a two-dimensional array into an encoded string.
*
* For example:
*
* Ext.util.CSV.encode([
* ['foo "bar", bletch', 'Normal String', new Date()],
* [Math.PI, 1, false]
* ]);
*
* The above produces the following string:
*
* '"foo ""bar"", bletch",Normal String,2010-01-01T21:45:32.004Z\u000a3.141592653589793,1,false'
*
* @param {Mixed[][]} input An array of row data arrays.
*
* @param {String} [delimiter] The column delimiter to use if the default value
* of {@link #cfg-delimiter delimiter} is not desired.
*
* @param {String} [quoteChar] The character used to quote fields. Defaults to
* double quote ("). Pass `null` to suppress field quoting.
*
* @return {String} A string in which data items are separated by {@link #delimiter}
* characters, and rows are separated by {@link #lineBreak} characters.
*/
encode: function(input, delimiter, quoteChar) {
/* eslint-enable max-len */
var me = this,
delim = delimiter || me.delimiter,
dateFormat = me.dateFormat,
quote = quoteChar !== undefined ? quoteChar : me.quote,
twoQuotes = quote + quote,
rowIndex = input.length,
lineBreakRe = me.lineBreakRe,
result = [],
outputRow = [],
col, columnIndex, inputRow;
while (rowIndex-- > 0) {
inputRow = input[rowIndex];
outputRow.length = columnIndex = inputRow.length;
while (columnIndex-- > 0) {
col = inputRow[columnIndex];
if (col == null) { // == null || === undefined
col = '';
}
else if (typeof col === 'string') {
if (col && quote !== null) {
// If the value contains quotes, double them up, and wrap with quotes
if (col.indexOf(quote) > -1) {
col = quote + col.split(quote).join(twoQuotes) + quote;
}
else if (col.indexOf(delim) > -1 || lineBreakRe.test(col)) {
col = quote + col + quote;
}
}
}
else if (Ext.isDate(col)) {
col = Ext.Date.format(col, dateFormat);
}
//<debug>
else if (col && (isNaN(col) || Ext.isArray(col))) {
Ext.raise('Cannot serialize ' + Ext.typeOf(col) + ' into CSV');
}
//</debug>
outputRow[columnIndex] = col;
}
result[rowIndex] = outputRow.join(delim);
}
return result.join(me.lineBreak);
}
});