1 /*!
  2  * UCSV 1.1.0
  3  * Provided under MIT License.
  4  *
  5  * Copyright 2010-2012, Peter Johnson
  6  * http://www.uselesscode.org/javascript/csv/
  7  */
  8 
  9 /* jsLint stuff */
 10 /*global exports */
 11 /*members apply, arrayToCsv, charAt, csvToArray, length, prototype, push,
 12  replace, substring, test, toString, trim
 13 */
 14 
 15 /**
 16  * Namespace for CSV functions
 17  * @namespace
 18  */
 19 var CSV = (function () {
 20 	"use strict";
 21 
 22 	var rxIsInt = /^\d+$/,
 23 		rxIsFloat = /^\d*\.\d+$|^\d+\.\d*$/,
 24 		// If a string has leading or trailing space,
 25 		// contains a comma double quote or a newline
 26 		// it needs to be quoted in CSV output
 27 		rxNeedsQuoting = /^\s|\s$|,|"|\n/,
 28 		trim = (function () {
 29 			// Fx 3.1 has a native trim function, it's about 10x faster, use it if it exists
 30 			if (String.prototype.trim) {
 31 				return function (s) {
 32 					return s.trim();
 33 				};
 34 			} else {
 35 				return function (s) {
 36 					return s.replace(/^\s*/, '').replace(/\s*$/, '');
 37 				};
 38 			}
 39 		}());
 40 
 41 	function isNumber(o) {
 42 		return Object.prototype.toString.apply(o) === '[object Number]';
 43 	}
 44 
 45 	function isString(o) {
 46 		return Object.prototype.toString.apply(o) === '[object String]';
 47 	}
 48 
 49 	function chomp(s) {
 50 		if (s.charAt(s.length - 1) !== "\n") {
 51 			// Does not end with \n, just return string
 52 			return s;
 53 		} else {
 54 			// Remove the \n
 55 			return s.substring(0, s.length - 1);
 56 		}
 57 	}
 58 
 59  /**
 60 	* Converts an array into a Comma Separated Values list.
 61 	* Each item in the array should be an array that represents one line in the CSV.
 62 	* Nulls are interpreted as empty fields.
 63 	*
 64 	* @param {String} a The array to convert
 65 	*
 66 	* @returns A CSV representation of the provided array.
 67 	* @type string
 68 	* @public
 69 	* @static
 70 	* @example
 71 	* var csvArray = [
 72 	* ['Leno, Jay', 10],
 73 	* ['Conan "Conando" O\'Brien', '11:35' ],
 74 	* ['Fallon, Jimmy', '12:35' ]
 75 	* ];
 76 	* CSV.arrayToCsv(csvArray);
 77 	* // Outputs a string containing:
 78 	* // "Leno, Jay",10
 79 	* // "Conan ""Conando"" O'Brien",11:35
 80 	* // "Fallon, Jimmy",12:35
 81 	*/
 82 	function arrayToCsv(a) {
 83 		var cur,
 84 			out = '',
 85 			row,
 86 			i,
 87 			j;
 88 
 89 		for (i = 0; i < a.length; i += 1) {
 90 			row = a[i];
 91 			for (j = 0; j < row.length; j += 1) {
 92 				cur = row[j];
 93 
 94 				if (isString(cur)) {
 95 					// Escape any " with double " ("")
 96 					cur = cur.replace(/"/g, '""');
 97 
 98 					// If the field starts or ends with whitespace, contains " or , or is a string representing a number
 99 					if (rxNeedsQuoting.test(cur) || rxIsInt.test(cur) || rxIsFloat.test(cur)) {
100 						cur = '"' + cur + '"';
101 					// quote empty strings
102 					} else if (cur === "") {
103 						cur = '""';
104 					}
105 				} else if (isNumber(cur)) {
106 					cur = cur.toString(10);
107 				} else if (cur === null) {
108 					cur = '';
109 				} else {
110 					cur = cur.toString();
111 				}
112 
113 				out += j < row.length - 1 ? cur + ',' : cur;
114 			}
115 			// End record
116 			out += "\n";
117 		}
118 
119 		return out;
120 	}
121 
122 	/**
123 	 * Converts a Comma Separated Values string into an array of arrays.
124 	 * Each line in the CSV becomes an array.
125 	 * Empty fields are converted to nulls and non-quoted numbers are converted to integers or floats.
126 	 *
127 	 * @return The CSV parsed as an array
128 	 * @type Array
129 	 * 
130 	 * @param {String} s The string to convert
131 	 * @param {Boolean} [trm=false] If set to True leading and trailing whitespace is stripped off of each non-quoted field as it is imported
132 	 * @public
133 	 * @static
134 	 * @example
135 	 * var csv = '"Leno, Jay",10' + "\n" +
136 	 * '"Conan ""Conando"" O\'Brien",11:35' + "\n" +
137 	 * '"Fallon, Jimmy",12:35' + "\n";
138 	 *
139 	 * var array = CSV.csvToArray(csv);
140 	 * 
141 	 * // array is now
142 	 * // [
143 	 * // ['Leno, Jay', 10],
144 	 * // ['Conan "Conando" O\'Brien', '11:35' ],
145 	 * // ['Fallon, Jimmy', '12:35' ]
146 	 * // ];
147 	 */
148 	function csvToArray(s, trm) {
149 		// Get rid of any trailing \n
150 		s = chomp(s);
151 
152 		var cur = '', // The character we are currently processing.
153 			inQuote = false,
154 			fieldQuoted = false,
155 			field = '', // Buffer for building up the current field
156 			row = [],
157 			out = [],
158 			i,
159 			processField;
160 
161 		processField = function (field) {
162 			if (fieldQuoted !== true) {
163 				// If field is empty set to null
164 				if (field === '') {
165 					field = null;
166 				// If the field was not quoted and we are trimming fields, trim it
167 				} else if (trm === true) {
168 					field = trim(field);
169 				}
170 
171 				// Convert unquoted numbers to their appropriate types
172 				if (rxIsInt.test(field)) {
173 					field = parseInt(field, 10);
174 				} else if (rxIsFloat.test(field)) {
175 					field = parseFloat(field, 10);
176 				}
177 			}
178 			return field;
179 		};
180 
181 		for (i = 0; i < s.length; i += 1) {
182 			cur = s.charAt(i);
183 
184 			// If we are at a EOF or EOR
185 			if (inQuote === false && (cur === ',' || cur === "\n")) {
186 				field = processField(field);
187 				// Add the current field to the current row
188 				row.push(field);
189 				// If this is EOR append row to output and flush row
190 				if (cur === "\n") {
191 					out.push(row);
192 					row = [];
193 				}
194 				// Flush the field buffer
195 				field = '';
196 				fieldQuoted = false;
197 			} else {
198 				// If it's not a ", add it to the field buffer
199 				if (cur !== '"') {
200 					field += cur;
201 				} else {
202 					if (!inQuote) {
203 						// We are not in a quote, start a quote
204 						inQuote = true;
205 						fieldQuoted = true;
206 					} else {
207 						// Next char is ", this is an escaped "
208 						if (s.charAt(i + 1) === '"') {
209 							field += '"';
210 							// Skip the next char
211 							i += 1;
212 						} else {
213 							// It's not escaping, so end quote
214 							inQuote = false;
215 						}
216 					}
217 				}
218 			}
219 		}
220 
221 		// Add the last field
222 		field = processField(field);
223 		row.push(field);
224 		out.push(row);
225 
226 		return out;
227 	}
228 
229 	// Add support for use as a CommonJS module.
230 	// This allows use as a library with the Mozilla Add-On SDK
231 	// or a module in Node.js via a call to require().
232 	if (typeof exports === "object") {
233 		exports.arrayToCsv = arrayToCsv;
234 		exports.csvToArray = csvToArray;
235 	}
236 
237 	return {
238 		arrayToCsv: arrayToCsv,
239 		csvToArray: csvToArray
240 	};
241 }());
242