/* jQuery Parse Plugin v0.5.9 https://github.com/mholt/jquery.parse */ (function($) { "use strict"; var defaults = { delimiter: ",", header: true, dynamicTyping: false }; $.parse = function(input, options) { options = verifyOptions(options); var parser = new Parser(input, options); return { results: parser.parse(), errors: parser.getErrors() }; }; function verifyOptions(opt) { opt.delimiter = opt.delimiter || defaults.delimiter; opt.header = typeof opt.header === 'undefined' ? defaults.header : opt.header; opt.dynamicTyping = typeof opt.dynamicTyping === 'undefined' ? defaults.dynamicTyping : opt.dynamicTyping; if (opt.delimiter == '"' || opt.delimiter == "\n") opt.delimiter = defaults.delimiter; if (opt.delimiter.length > 1) opt.delimiter = opt.delimiter[0]; return opt; } function Parser(input, config) { var self = this; var _input = input; var _config = config; var _errors = []; var _regex = { floats: /^-?\d+(\.\d+)?$/, empty: /^\s*$/ } var _state = emptyState(); this.parse = function(arg) { if (typeof arg === 'object') self.setConfig(arg) else if (typeof arg === 'string') self.setInput(arg); _errors = []; _state = emptyState(); for (_state.i = 0; _state.i < _input.length; _state.i++) { _state.ch = _input[_state.i]; _state.line += _state.ch; if (_state.ch == '"') handleQuote(); else if (_state.inQuotes) inQuotes(); else notInQuotes(); } // End of input is also end of the last row endRow(); if (_state.inQuotes) addError("Unescaped or mismatched quotes"); return self.getParsed(); }; this.getDelimiter = function() { return config.delimiter; }; this.setDelimiter = function(delim) { var comma = ","; delim = delim ? (delim == '"' || delim == "\n" ? comma : delim) : comma; _config.delimiter = delim[0]; }; this.setConfig = function(opt) { if ((typeof opt.header !== 'undefined' && opt.header != config.header) || (typeof opt.delimiter !== 'undefined' && opt.delimiter != config.delimiter)) { _state.parsed = emptyParsed(opt.header); } _config = opt; } this.getInput = function() { return _input; } this.setInput = function(input) { _input = input; } this.getParsed = function() { return _state.parsed; } this.getErrors = function() { return _errors; } function emptyParsed(header) { return header ? { fields: [], rows: [] } : [ [] ]; } function emptyState() { return { i: 0, lineNum: 1, field: 0, fieldVal: "", line: "", ch: "", inQuotes: false, parsed: emptyParsed(config.header) }; } function handleQuote() { var delimBefore = (_state.i > 0 && isBoundary(_input[_state.i-1])) || _state.i == 0; var delimAfter = (_state.i < _input.length - 1 && isBoundary(_input[_state.i+1])) || _state.i == _input.length - 1; var escaped = _state.i < _input.length - 1 && _input[_state.i+1] == '"'; if (_state.inQuotes && escaped) { _state.fieldVal += '"'; _state.i++; } else if (delimBefore || delimAfter) { _state.inQuotes = !_state.inQuotes; } else { addError("Unexpected quotes"); } } function inQuotes() { appendCharToField(); } function appendCharToField() { _state.fieldVal += _state.ch; } function notInQuotes() { if (_state.ch == _config.delimiter) saveField(); else if (_state.ch == "\r" && _state.i < _input.length - 1 && _input[_state.i+1] == "\n") { newRow(); _state.i++; } else if (_state.ch == "\n") newRow(); else appendCharToField(); } function isBoundary(ch) { return ch == _config.delimiter || ch == "\n"; } function saveField() { if (_config.header) { if (_state.lineNum == 1) _state.parsed.fields.push(_state.fieldVal) else { var currentRow = _state.parsed.rows[_state.parsed.rows.length - 1]; var fieldName = _state.parsed.fields[_state.field]; if (fieldName) { if (_config.dynamicTyping) _state.fieldVal = tryParseFloat(_state.fieldVal); currentRow[fieldName] = _state.fieldVal; } else { if (typeof currentRow.__parsed_extra === 'undefined') currentRow.__parsed_extra = []; currentRow.__parsed_extra.push(_state.fieldVal); addError("Too many fields; expected " + _state.parsed.fields.length + " fields, found extra value: '" + _state.fieldVal + "'"); } } } else { if (_config.dynamicTyping) _state.fieldVal = tryParseFloat(_state.fieldVal); _state.parsed[_state.parsed.length - 1].push(_state.fieldVal); } _state.fieldVal = ""; _state.field ++; } function endRow() { saveField(); var emptyLine = trimEmptyLine(); if (!emptyLine && _config.header) inspectFieldCount(); } function newRow() { endRow(); if (_config.header && _state.lineNum > 0) _state.parsed.rows.push({}); else _state.parsed.push([]); _state.lineNum ++; _state.line = ""; _state.field = 0; } function tryParseFloat(num) { var isNumber = _regex.floats.test(num); return isNumber ? parseFloat(num) : num; } function trimEmptyLine() { if (_regex.empty.test(_state.line)) { if (_config.header) { if (_state.lineNum == 1) { _state.parsed.fields = []; _state.lineNum --; } else _state.parsed.rows.splice(_state.parsed.rows.length - 1, 1); } else _state.parsed.splice(_state.parsed.length - 1, 1); return true; } return false; } function inspectFieldCount() { if (!_config.header) return true; if (_state.parsed.rows.length == 0) return true; var expected = _state.parsed.fields.length; // Actual field count tabulated manually because IE<9 doesn't support Object.keys var actual = 0; var lastRow = _state.parsed.rows[_state.parsed.rows.length - 1]; for (var prop in lastRow) if (lastRow.hasOwnProperty(prop)) actual ++; if (expected != actual) return addError("Too few fields; expected " + expected + " fields, parsed " + actual); return true; } function addError(msg) { _errors.push({ message: msg, line: _state.lineNum, row: _config.header ? _state.parsed.rows.length - 1 : _state.parsed.length - 1, index: _state.i }); return false; } } })(jQuery);