/** * 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: '"', parseREs: {}, quoteREs: {}, lineBreakRe: /\r?\n/g, // match line break at end of input lastLineBreakRe: /(\r?\n|\r)$/, constructor: function (config) { if (config) { Ext.apply(this, config); } }, /** * 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. * * @return {String[][]} An array of rows where each row is an array of Strings. */ decode: function (input, delimiter) { if (!input) { return []; } var me = this, // Check to see if the column delimiter is defined. If not, // then default to comma. delim = (delimiter || me.delimiter), row = [], result = [row], quote = me.quote, quoteREs = me.quoteREs, parseREs = me.parseREs, // Create a regular expression to parse the CSV values unless we already have // one for this delimiter. parseRE = parseREs[delim] || (parseREs[delim] = new RegExp( // Delimiters. "(\\" + delim + "|\\r?\\n|\\r|^)" + // Quoted fields. "(?:\\" + quote + "([^\\" + quote + "]*(?:\\" + quote + "\\" + quote + "[^\\" + quote + "]*)*)\\" + quote + "|" + // Standard fields. "([^\"\\" + delim + "\\r\\n]*))", "gi")), dblQuoteRE = quoteREs[quote] || (quoteREs[quote] = new RegExp('\\' + quote + '\\' + quote, 'g')), arrMatches, strMatchedDelimiter, strMatchedValue; input = input.replace(me.lastLineBreakRe, ''); // 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 !== delim) { // 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) === delim) { 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, '"'); } else { // We found a non-quoted value. strMatchedValue = arrMatches[3]; } row.push(strMatchedValue); } return result; }, /** * 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. * * @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) { var me = this, delim = delimiter || me.delimiter, dateFormat = me.dateFormat, quote = 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) { // 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.Error.raise('Cannot serialize ' + Ext.typeOf(col) + ' into CSV'); } //</debug> outputRow[columnIndex] = col; } result[rowIndex] = outputRow.join(delim); } return result.join(me.lineBreak); }});