]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * This base class contains utility methods for dealing with formats such as CSV (Comma\r | |
3 | * Separated Values) as specified in <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.\r | |
4 | *\r | |
5 | * The base class implements the mechanics and is governed by these config options:\r | |
6 | *\r | |
7 | * * `{@link #delimiter}`\r | |
8 | * * `{@link #lineBreak}`\r | |
9 | * * `{@link #quote}`\r | |
10 | *\r | |
11 | * These options affect the `{@link #method-encode}` and `{@link #method-decode}` methods.\r | |
12 | * When *decoding*, however, `{@link #lineBreak}` is ignored and instead each line can\r | |
13 | * be separated by any standard line terminator character or character sequence:\r | |
14 | *\r | |
15 | * * ```\u000a```\r | |
16 | * * ```\u000d```\r | |
17 | * * ```\u000d\u000a```\r | |
18 | *\r | |
19 | * Strings which contain the {@link #delimiter} character are quoted using the\r | |
20 | * {@link #quote} character, and any internal {@link #quote} characters are doubled.\r | |
21 | *\r | |
22 | * *Important*\r | |
23 | * While the primary use case is to encode strings, other atomic data types can be encoded\r | |
24 | * as values within a line such as:\r | |
25 | *\r | |
26 | * * Number\r | |
27 | * * Boolean\r | |
28 | * * Date (encoded as an <a href="http://www.iso.org/iso/home/standards/iso8601.htm">ISO 8601</a> date string.)\r | |
29 | * * null (encoded as an empty string.)\r | |
30 | * * undefined (encoded as an empty string.)\r | |
31 | *\r | |
32 | * Not that when *decoding*, all data is read as strings. This class does not convert\r | |
33 | * incoming data. To do that, use an {@link Ext.data.reader.Array ArrayReader}.\r | |
34 | *\r | |
35 | * See `{@link Ext.util.CSV}` and `{@link Ext.util.TSV}` for pre-configured instances.\r | |
36 | *\r | |
37 | * @since 5.1.0\r | |
38 | */\r | |
39 | Ext.define('Ext.util.DelimitedValue', {\r | |
40 | /**\r | |
41 | * @cfg {String} dateFormat\r | |
42 | * The {@link Ext.Date#format format} to use for dates\r | |
43 | */\r | |
44 | dateFormat: 'C',\r | |
45 | \r | |
46 | /**\r | |
47 | * @cfg {String} delimiter\r | |
48 | * The string used to separate the values in a row. Common values for this config\r | |
49 | * are comma (",") and tab ("\t"). See `{@link Ext.util.CSV}` and `{@link Ext.util.TSV}`\r | |
50 | * for pre-configured instances of these formats.\r | |
51 | */\r | |
52 | delimiter: '\t',\r | |
53 | \r | |
54 | /**\r | |
55 | * @cfg {String} lineBreak\r | |
56 | * The string used by `{@link #encode}` to separate each row. The `{@link #decode}`\r | |
57 | * method accepts all forms of line break.\r | |
58 | */\r | |
59 | lineBreak: '\n',\r | |
60 | \r | |
61 | /**\r | |
62 | * @cfg {String} quote\r | |
63 | * The character to use as to quote values that contain the special `delimiter`\r | |
64 | * or `{@link #lineBreak}` characters.\r | |
65 | */\r | |
66 | quote: '"',\r | |
67 | \r | |
68 | parseREs: {},\r | |
69 | quoteREs: {},\r | |
70 | \r | |
71 | lineBreakRe: /\r?\n/g,\r | |
72 | \r | |
73 | constructor: function (config) {\r | |
74 | if (config) {\r | |
75 | Ext.apply(this, config);\r | |
76 | }\r | |
77 | },\r | |
78 | \r | |
79 | /**\r | |
80 | * Decodes a string of encoded values into an array of rows. Each row is an array of\r | |
81 | * strings.\r | |
82 | *\r | |
83 | * Note that this function does not convert the string values in each column into\r | |
84 | * other data types. To do that, use an {@link Ext.data.reader.Array ArrayReader}.\r | |
85 | *\r | |
86 | * For example:\r | |
87 | *\r | |
88 | * Ext.util.CSV.decode('"foo ""bar"", bletch",Normal String,2010-01-01T21:45:32.004Z\u000a3.141592653589793,1,false');\r | |
89 | *\r | |
90 | * produces the following array of string arrays:\r | |
91 | *\r | |
92 | * [\r | |
93 | * ['foo "bar", bletch','Normal String', '2010-01-01T21:45:32.004Z'],\r | |
94 | * ['3.141592653589793', '1', 'false']\r | |
95 | * ]\r | |
96 | *\r | |
97 | * @param {String} input The string to parse.\r | |
98 | *\r | |
99 | * @param {String} [delimiter] The column delimiter to use if the default value\r | |
100 | * of {@link #cfg-delimiter delimiter} is not desired.\r | |
101 | *\r | |
102 | * @return {String[][]} An array of rows where each row is an array of Strings.\r | |
103 | */\r | |
104 | decode: function (input, delimiter) {\r | |
105 | var me = this,\r | |
106 | // Check to see if the column delimiter is defined. If not,\r | |
107 | // then default to comma.\r | |
108 | delim = (delimiter || me.delimiter),\r | |
109 | row = [],\r | |
110 | result = [row],\r | |
111 | quote = me.quote,\r | |
112 | quoteREs = me.quoteREs,\r | |
113 | parseREs = me.parseREs,\r | |
114 | \r | |
115 | // Create a regular expression to parse the CSV values unless we already have\r | |
116 | // one for this delimiter.\r | |
117 | parseRE = parseREs[delim] ||\r | |
118 | (parseREs[delim] = new RegExp(\r | |
119 | // Delimiters.\r | |
120 | "(\\" + delim + "|\\r?\\n|\\r|^)" +\r | |
121 | \r | |
122 | // Quoted fields.\r | |
123 | "(?:\\" + quote + "([^\\" + quote + "]*(?:\\" + quote + "\\" + quote +\r | |
124 | "[^\\" + quote + "]*)*)\\" + quote + "|" +\r | |
125 | \r | |
126 | // Standard fields.\r | |
127 | "([^\"\\" + delim + "\\r\\n]*))",\r | |
128 | "gi")),\r | |
129 | \r | |
130 | dblQuoteRE = quoteREs[quote] ||\r | |
131 | (quoteREs[quote] = new RegExp('\\' + quote + '\\' + quote, 'g')),\r | |
132 | \r | |
133 | arrMatches, strMatchedDelimiter, strMatchedValue;\r | |
134 | \r | |
135 | // Keep looping over the regular expression matches\r | |
136 | // until we can no longer find a match.\r | |
137 | while (arrMatches = parseRE.exec(input)) {\r | |
138 | strMatchedDelimiter = arrMatches[1];\r | |
139 | \r | |
140 | // Check to see if the given delimiter has a length\r | |
141 | // (is not the start of string) and if it matches\r | |
142 | // field delimiter. If id does not, then we know\r | |
143 | // that this delimiter is a row delimiter.\r | |
144 | if (strMatchedDelimiter.length && strMatchedDelimiter !== delim) {\r | |
145 | // Since we have reached a new row of data,\r | |
146 | // add an empty row to our data array.\r | |
147 | result.push(row = []);\r | |
148 | }\r | |
149 | \r | |
150 | // Now that we have our delimiter out of the way,\r | |
151 | // let's check to see which kind of value we\r | |
152 | // captured (quoted or unquoted).\r | |
153 | if (arrMatches[2]) {\r | |
154 | // We found a quoted value. When we capture\r | |
155 | // this value, unescape any double quotes.\r | |
156 | strMatchedValue = arrMatches[2].replace(dblQuoteRE, '"');\r | |
157 | } else {\r | |
158 | // We found a non-quoted value.\r | |
159 | strMatchedValue = arrMatches[3];\r | |
160 | }\r | |
161 | \r | |
162 | row.push(strMatchedValue);\r | |
163 | }\r | |
164 | \r | |
165 | return result;\r | |
166 | },\r | |
167 | \r | |
168 | /**\r | |
169 | * Converts a two-dimensional array into an encoded string.\r | |
170 | *\r | |
171 | * For example:\r | |
172 | *\r | |
173 | * Ext.util.CSV.encode([\r | |
174 | * ['foo "bar", bletch', 'Normal String', new Date()],\r | |
175 | * [Math.PI, 1, false]\r | |
176 | * ]);\r | |
177 | *\r | |
178 | * The above produces the following string:\r | |
179 | *\r | |
180 | * '"foo ""bar"", bletch",Normal String,2010-01-01T21:45:32.004Z\u000a3.141592653589793,1,false'\r | |
181 | *\r | |
182 | * @param {Mixed[][]} input An array of row data arrays.\r | |
183 | *\r | |
184 | * @param {String} [delimiter] The column delimiter to use if the default value\r | |
185 | * of {@link #cfg-delimiter delimiter} is not desired.\r | |
186 | *\r | |
187 | * @return {String} A string in which data items are separated by {@link #delimiter}\r | |
188 | * characters, and rows are separated by {@link #lineBreak} characters.\r | |
189 | */\r | |
190 | encode: function (input, delimiter) {\r | |
191 | var me = this,\r | |
192 | delim = delimiter || me.delimiter,\r | |
193 | dateFormat = me.dateFormat,\r | |
194 | quote = me.quote,\r | |
195 | twoQuotes = quote + quote,\r | |
196 | rowIndex = input.length,\r | |
197 | lineBreakRe = me.lineBreakRe,\r | |
198 | result = [],\r | |
199 | outputRow = [],\r | |
200 | col, columnIndex, inputRow;\r | |
201 | \r | |
202 | while (rowIndex-- > 0) {\r | |
203 | inputRow = input[rowIndex];\r | |
204 | outputRow.length = columnIndex = inputRow.length;\r | |
205 | \r | |
206 | while (columnIndex-- > 0) {\r | |
207 | col = inputRow[columnIndex];\r | |
208 | \r | |
209 | if (col == null) { // == null || === undefined\r | |
210 | col = '';\r | |
211 | } else if (typeof col === 'string') {\r | |
212 | if (col) {\r | |
213 | // If the value contains quotes, double them up, and wrap with quotes\r | |
214 | if (col.indexOf(quote) > -1) {\r | |
215 | col = quote + col.split(quote).join(twoQuotes) + quote;\r | |
216 | } else if (col.indexOf(delim) > -1 || lineBreakRe.test(col)) {\r | |
217 | col = quote + col + quote;\r | |
218 | }\r | |
219 | }\r | |
220 | } else if (Ext.isDate(col)) {\r | |
221 | col = Ext.Date.format(col, dateFormat);\r | |
222 | }\r | |
223 | //<debug>\r | |
224 | else if (col && (isNaN(col) || Ext.isArray(col))) {\r | |
225 | Ext.raise('Cannot serialize ' + Ext.typeOf(col) + ' into CSV');\r | |
226 | }\r | |
227 | //</debug>\r | |
228 | \r | |
229 | outputRow[columnIndex] = col;\r | |
230 | }\r | |
231 | \r | |
232 | result[rowIndex] = outputRow.join(delim);\r | |
233 | }\r | |
234 | \r | |
235 | return result.join(me.lineBreak);\r | |
236 | }\r | |
237 | }); |