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