1/** 2 * jQuery-csv (jQuery Plugin) 3 * 4 * This document is licensed as free software under the terms of the 5 * MIT License: http://www.opensource.org/licenses/mit-license.php 6 * 7 * Acknowledgements: 8 * The original design and influence to implement this library as a jquery 9 * plugin is influenced by jquery-json (http://code.google.com/p/jquery-json/). 10 * If you're looking to use native JSON.Stringify but want additional backwards 11 * compatibility for browsers that don't support it, I highly recommend you 12 * check it out. 13 * 14 * A special thanks goes out to [email protected] for providing a lot of valuable 15 * feedback to the project including the core for the new FSM 16 * (Finite State Machine) parsers. If you're looking for a stable TSV parser 17 * be sure to take a look at jquery-tsv (http://code.google.com/p/jquery-tsv/). 18 19 * For legal purposes I'll include the "NO WARRANTY EXPRESSED OR IMPLIED. 20 * USE AT YOUR OWN RISK.". Which, in 'layman's terms' means, by using this 21 * library you are accepting responsibility if it breaks your code. 22 * 23 * Legal jargon aside, I will do my best to provide a useful and stable core 24 * that can effectively be built on. 25 * 26 * Copyrighted 2012 by Evan Plaice. 27 */ 28 29RegExp.escape= function(s) { 30 return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); 31}; 32 33(function (undefined) { 34 'use strict'; 35 36 var $; 37 38 // to keep backwards compatibility 39 if (typeof jQuery !== 'undefined' && jQuery) { 40 $ = jQuery; 41 } else { 42 $ = {}; 43 } 44 45 46 /** 47 * jQuery.csv.defaults 48 * Encapsulates the method paramater defaults for the CSV plugin module. 49 */ 50 51 $.csv = { 52 defaults: { 53 separator:',', 54 delimiter:'"', 55 headers:true 56 }, 57 58 hooks: { 59 castToScalar: function(value, state) { 60 var hasDot = /\./; 61 if (isNaN(value)) { 62 return value; 63 } else { 64 if (hasDot.test(value)) { 65 return parseFloat(value); 66 } else { 67 var integer = parseInt(value); 68 if(isNaN(integer)) { 69 return null; 70 } else { 71 return integer; 72 } 73 } 74 } 75 } 76 }, 77 78 parsers: { 79 parse: function(csv, options) { 80 // cache settings 81 var separator = options.separator; 82 var delimiter = options.delimiter; 83 84 // set initial state if it's missing 85 if(!options.state.rowNum) { 86 options.state.rowNum = 1; 87 } 88 if(!options.state.colNum) { 89 options.state.colNum = 1; 90 } 91 92 // clear initial state 93 var data = []; 94 var entry = []; 95 var state = 0; 96 var value = ''; 97 var exit = false; 98 99 function endOfEntry() { 100 // reset the state 101 state = 0; 102 value = ''; 103 104 // if 'start' hasn't been met, don't output 105 if(options.start && options.state.rowNum < options.start) { 106 // update global state 107 entry = []; 108 options.state.rowNum++; 109 options.state.colNum = 1; 110 return; 111 } 112 113 if(options.onParseEntry === undefined) { 114 // onParseEntry hook not set 115 data.push(entry); 116 } else { 117 var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook 118 // false skips the row, configurable through a hook 119 if(hookVal !== false) { 120 data.push(hookVal); 121 } 122 } 123 //console.log('entry:' + entry); 124 125 // cleanup 126 entry = []; 127 128 // if 'end' is met, stop parsing 129 if(options.end && options.state.rowNum >= options.end) { 130 exit = true; 131 } 132 133 // update global state 134 options.state.rowNum++; 135 options.state.colNum = 1; 136 } 137 138 function endOfValue() { 139 if(options.onParseValue === undefined) { 140 // onParseValue hook not set 141 entry.push(value); 142 } else { 143 var hook = options.onParseValue(value, options.state); // onParseValue Hook 144 // false skips the row, configurable through a hook 145 if(hook !== false) { 146 entry.push(hook); 147 } 148 } 149 //console.log('value:' + value); 150 // reset the state 151 value = ''; 152 state = 0; 153 // update global state 154 options.state.colNum++; 155 } 156 157 // escape regex-specific control chars 158 var escSeparator = RegExp.escape(separator); 159 var escDelimiter = RegExp.escape(delimiter); 160 161 // compile the regEx str using the custom delimiter/separator 162 var match = /(D|S|\r\n|\n|\r|[^DS\r\n]+)/; 163 var matchSrc = match.source; 164 matchSrc = matchSrc.replace(/S/g, escSeparator); 165 matchSrc = matchSrc.replace(/D/g, escDelimiter); 166 match = new RegExp(matchSrc, 'gm'); 167 168 // put on your fancy pants... 169 // process control chars individually, use look-ahead on non-control chars 170 csv.replace(match, function (m0) { 171 if(exit) { 172 return; 173 } 174 switch (state) { 175 // the start of a value 176 case 0: 177 // null last value 178 if (m0 === separator) { 179 value += ''; 180 endOfValue(); 181 break; 182 } 183 // opening delimiter 184 if (m0 === delimiter) { 185 state = 1; 186 break; 187 } 188 // null last value 189 if (/^(\r\n|\n|\r)$/.test(m0)) { 190 endOfValue(); 191 endOfEntry(); 192 break; 193 } 194 // un-delimited value 195 value += m0; 196 state = 3; 197 break; 198 199 // delimited input 200 case 1: 201 // second delimiter? check further 202 if (m0 === delimiter) { 203 state = 2; 204 break; 205 } 206 // delimited data 207 value += m0; 208 state = 1; 209 break; 210 211 // delimiter found in delimited input 212 case 2: 213 // escaped delimiter? 214 if (m0 === delimiter) { 215 value += m0; 216 state = 1; 217 break; 218 } 219 // null value 220 if (m0 === separator) { 221 endOfValue(); 222 break; 223 } 224 // end of entry 225 if (/^(\r\n|\n|\r)$/.test(m0)) { 226 endOfValue(); 227 endOfEntry(); 228 break; 229 } 230 // broken paser? 231 throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); 232 233 // un-delimited input 234 case 3: 235 // null last value 236 if (m0 === separator) { 237 endOfValue(); 238 break; 239 } 240 // end of entry 241 if (/^(\r\n|\n|\r)$/.test(m0)) { 242 endOfValue(); 243 endOfEntry(); 244 break; 245 } 246 if (m0 === delimiter) { 247 // non-compliant data 248 throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); 249 } 250 // broken parser? 251 throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); 252 default: 253 // shenanigans 254 throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); 255 } 256 //console.log('val:' + m0 + ' state:' + state); 257 }); 258 259 // submit the last entry 260 // ignore null last line 261 if(entry.length !== 0) { 262 endOfValue(); 263 endOfEntry(); 264 } 265 266 return data; 267 }, 268 269 // a csv-specific line splitter 270 splitLines: function(csv, options) { 271 // cache settings 272 var separator = options.separator; 273 var delimiter = options.delimiter; 274 275 // set initial state if it's missing 276 if(!options.state.rowNum) { 277 options.state.rowNum = 1; 278 } 279 280 // clear initial state 281 var entries = []; 282 var state = 0; 283 var entry = ''; 284 var exit = false;
285 286 function endOfLine() { 287 // reset the state 288 state = 0; 289 290 // if 'start' hasn't been met, don't output 291 if(options.start && options.state.rowNum < options.start) { 292 // update global state 293 entry = ''; 294 options.state.rowNum++; 295 return; 296 } 297 298 if(options.onParseEntry === undefined) { 299 // onParseEntry hook not set 300 entries.push(entry); 301 } else { 302 var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook 303 // false skips the row, configurable through a hook 304 if(hookVal !== false) { 305 entries.push(hookVal); 306 } 307 } 308 309 // cleanup 310 entry = ''; 311 312 // if 'end' is met, stop parsing 313 if(options.end && options.state.rowNum >= options.end) { 314 exit = true; 315 } 316 317 // update global state 318 options.state.rowNum++; 319 } 320 321 // escape regex-specific control chars 322 var escSeparator = RegExp.escape(separator); 323 var escDelimiter = RegExp.escape(delimiter); 324 325 // compile the regEx str using the custom delimiter/separator 326 var match = /(D|S|\n|\r|[^DS\r\n]+)/; 327 var matchSrc = match.source; 328 matchSrc = matchSrc.replace(/S/g, escSeparator); 329 matchSrc = matchSrc.replace(/D/g, escDelimiter); 330 match = new RegExp(matchSrc, 'gm'); 331 332 // put on your fancy pants... 333 // process control chars individually, use look-ahead on non-control chars 334 csv.replace(match, function (m0) { 335 if(exit) { 336 return; 337 } 338 switch (state) { 339 // the start of a value/entry 340 case 0: 341 // null value 342 if (m0 === separator) { 343 entry += m0; 344 state = 0; 345 break; 346 } 347 // opening delimiter 348 if (m0 === delimiter) { 349 entry += m0; 350 state = 1; 351 break; 352 } 353 // end of line 354 if (m0 === '\n') { 355 endOfLine(); 356 break; 357 } 358 // phantom carriage return 359 if (/^\r$/.test(m0)) { 360 break; 361 } 362 // un-delimit value 363 entry += m0; 364 state = 3; 365 break; 366 367 // delimited input 368 case 1: 369 // second delimiter? check further 370 if (m0 === delimiter) { 371 entry += m0; 372 state = 2; 373 break; 374 } 375 // delimited data 376 entry += m0; 377 state = 1; 378 break; 379 380 // delimiter found in delimited input 381 case 2: 382 // escaped delimiter? 383 var prevChar = entry.substr(entry.length - 1); 384 if (m0 === delimiter && prevChar === delimiter) { 385 entry += m0; 386 state = 1; 387 break; 388 } 389 // end of value 390 if (m0 === separator) { 391 entry += m0; 392 state = 0; 393 break; 394 } 395 // end of line 396 if (m0 === '\n') { 397 endOfLine(); 398 break; 399 } 400 // phantom carriage return 401 if (m0 === '\r') { 402 break; 403 } 404 // broken paser? 405 throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']'); 406 407 // un-delimited input 408 case 3: 409 // null value 410 if (m0 === separator) { 411 entry += m0; 412 state = 0; 413 break; 414 } 415 // end of line 416 if (m0 === '\n') { 417 endOfLine(); 418 break; 419 } 420 // phantom carriage return 421 if (m0 === '\r') { 422 break; 423 } 424 // non-compliant data 425 if (m0 === delimiter) { 426 throw new Error('CSVDataError: Illegal quote [Row:' + options.state.rowNum + ']'); 427 } 428 // broken parser? 429 throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']'); 430 default: 431 // shenanigans 432 throw new Error('CSVDataError: Unknown state [Row:' + options.state.rowNum + ']'); 433 } 434 //console.log('val:' + m0 + ' state:' + state); 435 }); 436 437 // submit the last entry 438 // ignore null last line 439 if(entry !== '') { 440 endOfLine(); 441 } 442 443 return entries; 444 }, 445 446 // a csv entry parser 447 parseEntry: function(csv, options) { 448 // cache settings 449 var separator = options.separator; 450 var delimiter = options.delimiter; 451 452 // set initial state if it's missing 453 if(!options.state.rowNum) { 454 options.state.rowNum = 1; 455 } 456 if(!options.state.colNum) { 457 options.state.colNum = 1; 458 } 459 460 // clear initial state 461 var entry = []; 462 var state = 0; 463 var value = ''; 464 465 function endOfValue() { 466 if(options.onParseValue === undefined) { 467 // onParseValue hook not set 468 entry.push(value); 469 } else { 470 var hook = options.onParseValue(value, options.state); // onParseValue Hook 471 // false skips the value, configurable through a hook 472 if(hook !== false) { 473 entry.push(hook); 474 } 475 } 476 // reset the state 477 value = ''; 478 state = 0; 479 // update global state 480 options.state.colNum++; 481 } 482 483 // checked for a cached regEx first 484 if(!options.match) { 485 // escape regex-specific control chars 486 var escSeparator = RegExp.escape(separator); 487 var escDelimiter = RegExp.escape(delimiter); 488 489 // compile the regEx str using the custom delimiter/separator 490 var match = /(D|S|\n|\r|[^DS\r\n]+)/; 491 var matchSrc = match.source; 492 matchSrc = matchSrc.replace(/S/g, escSeparator); 493 matchSrc = matchSrc.replace(/D/g, escDelimiter); 494 options.match = new RegExp(matchSrc, 'gm'); 495 } 496 497 // put on your fancy pants... 498 // process control chars individually, use look-ahead on non-control chars 499 csv.replace(options.match, function (m0) { 500 switch (state) { 501 // the start of a value 502 case 0: 503 // null last value 504 if (m0 === separator) { 505 value += ''; 506 endOfValue(); 507 break; 508 } 509 // opening delimiter 510 if (m0 === delimiter) { 511 state = 1; 512 break; 513 } 514 // skip un-delimited new-lines 515 if (m0 === '\n' || m0 === '\r') { 516 break; 517 } 518 // un-delimited value 519 value += m0; 520 state = 3; 521 break; 522 523 // delimited input 524 case 1: 525 // second delimiter? check further 526 if (m0 === delimiter) { 527 state = 2; 528 break; 529 } 530 // delimited data 531 value += m0; 532 state = 1; 533 break; 534 535 // delimiter found in delimited input 536 case 2: 537 // escaped delimiter? 538 if (m0 === delimiter) { 539 value += m0; 540 state = 1; 541 break; 542 } 543 // null value 544 if (m0 === separator) { 545 endOfValue(); 546 break; 547 } 548 // skip un-delimited new-lines 549 if (m0 === '\n' || m0 === '\r') { 550 break; 551 } 552 // broken paser? 553 throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); 554 555 // un-delimited input 556 case 3: 557 // null last value 558 if (m0 === separator) { 559 endOfValue(); 560 break; 561 } 562 // skip un-delimited new-lines 563 if (m0 === '\n' || m0 === '\r') { 564 break; 565 } 566 // non-compliant data 567 if (m0 === delimiter) { 568 throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); 569 } 570 // broken parser? 571 throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); 572 default: 573 // shenanigans 574 throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); 575 } 576 //console.log('val:' + m0 + ' state:' + state); 577 }); 578 579 // submit the last value 580 endOfValue(); 581 582 return entry; 583 } 584 }, 585 586 helpers: { 587 588 /** 589 * $.csv.helpers.collectPropertyNames(objectsArray) 590 * Collects all unique property names from all passed objects. 591 * 592 * @param {Array} objects Objects to collect properties from. 593 * 594 * Returns an array of property names (array will be empty, 595 * if objects have no own properties). 596 */ 597 collectPropertyNames: function (objects) { 598 599 var o, propName, props = []; 600 for (o in objects) { 601 for (propName in objects[o]) { 602 if ((objects[o].hasOwnProperty(propName)) && 603 (props.indexOf(propName) < 0) && 604 (typeof objects[o][propName] !== 'function')) { 605 606 props.push(propName); 607 } 608 } 609 } 610 return props; 611 } 612 }, 613 614 /** 615 * $.csv.toArray(csv) 616 * Converts a CSV entry string to a javascript array. 617 * 618 * @param {Array} csv The string containing the CSV data. 619 * @param {Object}
619 [options] An object containing user-defined options. 620 * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). 621 * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). 622 * 623 * This method deals with simple CSV strings only. It's useful if you only 624 * need to parse a single entry. If you need to parse more than one line, 625 * use $.csv2Array instead. 626 */ 627 toArray: function(csv, options, callback) { 628 options = (options !== undefined ? options : {}); 629 var config = {}; 630 config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false); 631 config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; 632 config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; 633 var state = (options.state !== undefined ? options.state : {}); 634 635 // setup 636 options = { 637 delimiter: config.delimiter, 638 separator: config.separator, 639 onParseEntry: options.onParseEntry, 640 onParseValue: options.onParseValue, 641 state: state 642 }; 643 644 var entry = $.csv.parsers.parseEntry(csv, options); 645 646 // push the value to a callback if one is defined 647 if(!config.callback) { 648 return entry; 649 } else { 650 config.callback('', entry); 651 } 652 }, 653 654 /** 655 * $.csv.toArrays(csv) 656 * Converts a CSV string to a javascript array. 657 * 658 * @param {String} csv The string containing the raw CSV data. 659 * @param {Object} [options] An object containing user-defined options. 660 * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). 661 * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). 662 * 663 * This method deals with multi-line CSV. The breakdown is simple. The first 664 * dimension of the array represents the line (or entry/row) while the second 665 * dimension contains the values (or values/columns). 666 */ 667 toArrays: function(csv, options, callback) { 668 options = (options !== undefined ? options : {}); 669 var config = {}; 670 config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false); 671 config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; 672 config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; 673 674 // setup 675 var data = []; 676 options = { 677 delimiter: config.delimiter, 678 separator: config.separator, 679 onPreParse: options.onPreParse, 680 onParseEntry: options.onParseEntry, 681 onParseValue: options.onParseValue, 682 onPostParse: options.onPostParse, 683 start: options.start, 684 end: options.end, 685 state: { 686 rowNum: 1, 687 colNum: 1 688 } 689 }; 690 691 // onPreParse hook 692 if(options.onPreParse !== undefined) { 693 options.onPreParse(csv, options.state); 694 } 695 696 // parse the data 697 data = $.csv.parsers.parse(csv, options); 698 699 // onPostParse hook 700 if(options.onPostParse !== undefined) { 701 options.onPostParse(data, options.state); 702 } 703 704 // push the value to a callback if one is defined 705 if(!config.callback) { 706 return data; 707 } else { 708 config.callback('', data); 709 } 710 }, 711 712 /** 713 * $.csv.toObjects(csv) 714 * Converts a CSV string to a javascript object. 715 * @param {String} csv The string containing the raw CSV data. 716 * @param {Object} [options] An object containing user-defined options. 717 * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). 718 * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). 719 * @param {Boolean} [headers] Indicates whether the data contains a header line. Defaults to true. 720 * 721 * This method deals with multi-line CSV strings. Where the headers line is 722 * used as the key for each value per entry. 723 */ 724 toObjects: function(csv, options, callback) { 725 options = (options !== undefined ? options : {}); 726 var config = {}; 727 config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false); 728 config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; 729 config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; 730 config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers; 731 options.start = 'start' in options ? options.start : 1; 732 733 // account for headers 734 if(config.headers) { 735 options.start++; 736 } 737 if(options.end && config.headers) { 738 options.end++; 739 } 740 741 // setup 742 var lines = []; 743 var data = []; 744 745 options = { 746 delimiter: config.delimiter, 747 separator: config.separator, 748 onPreParse: options.onPreParse, 749 onParseEntry: options.onParseEntry, 750 onParseValue: options.onParseValue, 751 onPostParse: options.onPostParse, 752 start: options.start, 753 end: options.end, 754 state: { 755 rowNum: 1, 756 colNum: 1 757 }, 758 match: false, 759 transform: options.transform 760 }; 761 762 // fetch the headers 763 var headerOptions = { 764 delimiter: config.delimiter, 765 separator: config.separator, 766 start: 1, 767 end: 1, 768 state: { 769 rowNum:1, 770 colNum:1 771 } 772 }; 773 774 // onPreParse hook 775 if(options.onPreParse !== undefined) { 776 options.onPreParse(csv, options.state); 777 } 778 779 // parse the csv 780 var headerLine = $.csv.parsers.splitLines(csv, headerOptions); 781 var headers = $.csv.toArray(headerLine[0], options); 782 783 // fetch the data 784 lines = $.csv.parsers.splitLines(csv, options); 785 786 // reset the state for re-use 787 options.state.colNum = 1; 788 if(headers){ 789 options.state.rowNum = 2; 790 } else { 791 options.state.rowNum = 1; 792 } 793 794 // convert data to objects 795 for(var i=0, len=lines.length; i<len; i++) { 796 var entry = $.csv.toArray(lines[i], options); 797 var object = {}; 798 for(var j=0; j <headers.length; j++) { 799 object[headers[j]] = entry[j]; 800 } 801 if (options.transform !== undefined) { 802 data.push(options.transform.call(undefined, object)); 803 } else { 804 data.push(object); 805 } 806 807 // update row state 808 options.state.rowNum++; 809 } 810 811 // onPostParse hook 812 if(options.onPostParse !== undefined) { 813 options.onPostParse(data, options.state); 814 } 815 816 // push the value to a callback if one is defined 817 if(!config.callback) { 818 return data; 819 } else { 820 config.callback('', data); 821 } 822 }, 823 824 /** 825 * $.csv.fromArrays(arrays) 826 * Converts a javascript array to a CSV String. 827 * 828 * @param {Array} arrays An array containing an array of CSV entries. 829 * @param {Object}
829 [options] An object containing user-defined options. 830 * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). 831 * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). 832 * 833 * This method generates a CSV file from an array of arrays (representing entries). 834 */ 835 fromArrays: function(arrays, options, callback) { 836 options = (options !== undefined ? options : {}); 837 var config = {}; 838 config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false); 839 config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; 840 config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; 841 842 var output = '', 843 line, 844 lineValues, 845 i, j; 846 847 for (i = 0; i < arrays.length; i++) { 848 line = arrays[i]; 849 lineValues = []; 850 for (j = 0; j < line.length; j++) { 851 var strValue = (line[j] === undefined || line[j] === null) ? '' : line[j].toString(); 852 if (strValue.indexOf(config.delimiter) > -1) { 853 strValue = strValue.replace(config.delimiter, config.delimiter + config.delimiter); 854 } 855 856 var escMatcher = '\n|\r|S|D'; 857 escMatcher = escMatcher.replace('S', config.separator); 858 escMatcher = escMatcher.replace('D', config.delimiter); 859 860 if (strValue.search(escMatcher) > -1) { 861 strValue = config.delimiter + strValue + config.delimiter; 862 } 863 lineValues.push(strValue); 864 } 865 output += lineValues.join(config.separator) + '\r\n'; 866 } 867 868 // push the value to a callback if one is defined 869 if(!config.callback) { 870 return output; 871 } else { 872 config.callback('', output); 873 } 874 }, 875 876 /** 877 * $.csv.fromObjects(objects) 878 * Converts a javascript dictionary to a CSV string. 879 * 880 * @param {Object} objects An array of objects containing the data. 881 * @param {Object} [options] An object containing user-defined options. 882 * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). 883 * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). 884 * @param {Character} [sortOrder] Sort order of columns (named after 885 * object properties). Use 'alpha' for alphabetic. Default is 'declare', 886 * which means, that properties will _probably_ appear in order they were 887 * declared for the object. But without any guarantee. 888 * @param {Character or Array} [manualOrder] Manually order columns. May be 889 * a strin in a same csv format as an output or an array of header names 890 * (array items won't be parsed). All the properties, not present in 891 * `manualOrder` will be appended to the end in accordance with `sortOrder` 892 * option. So the `manualOrder` always takes preference, if present. 893 * 894 * This method generates a CSV file from an array of objects (name:value pairs). 895 * It starts by detecting the headers and adding them as the first line of 896 * the CSV file, followed by a structured dump of the data. 897 */ 898 fromObjects: function(objects, options, callback) { 899 options = (options !== undefined ? options : {}); 900 var config = {}; 901 config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false); 902 config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; 903 config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; 904 config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers; 905 config.sortOrder = 'sortOrder' in options ? options.sortOrder : 'declare'; 906 config.manualOrder = 'manualOrder' in options ? options.manualOrder : []; 907 config.transform = options.transform; 908 909 if (typeof config.manualOrder === 'string') { 910 config.manualOrder = $.csv.toArray(config.manualOrder, config); 911 } 912 913 if (config.transform !== undefined) { 914 var origObjects = objects; 915 objects = []; 916 917 var i; 918 for (i = 0; i < origObjects.length; i++) { 919 objects.push(config.transform.call(undefined, origObjects[i])); 920 } 921 } 922 923 var props = $.csv.helpers.collectPropertyNames(objects); 924 925 if (config.sortOrder === 'alpha') { 926 props.sort(); 927 } // else {} - nothing to do for 'declare' order 928 929 if (config.manualOrder.length > 0) { 930 931 var propsManual = [].concat(config.manualOrder); 932 var p; 933 for (p = 0; p < props.length; p++) { 934 if (propsManual.indexOf( props[p] ) < 0) { 935 propsManual.push( props[p] ); 936 } 937 } 938 props = propsManual; 939 } 940 941 var o, p, line, output = [], propName; 942 if (config.headers) { 943 output.push(props); 944 } 945 946 for (o = 0; o < objects.length; o++) { 947 line = []; 948 for (p = 0; p < props.length; p++) { 949 propName = props[p]; 950 if (propName in objects[o] && typeof objects[o][propName] !== 'function') { 951 line.push(objects[o][propName]); 952 } else { 953 line.push(''); 954 } 955 } 956 output.push(line); 957 } 958 959 // push the value to a callback if one is defined 960 return $.csv.fromArrays(output, options, config.callback); 961 } 962 }; 963 964 // Maintenance code to maintain backward-compatibility 965 // Will be removed in release 1.0 966 $.csvEntry2Array = $.csv.toArray; 967 $.csv2Array = $.csv.toArrays; 968 $.csv2Dictionary = $.csv.toObjects; 969 970 // CommonJS module is defined 971 if (typeof module !== 'undefined' && module.exports) { 972 module.exports = $.csv; 973 } 974 975}).call( this );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.