|
|
|
/*
|
|
|
|
jQuery Parse Plugin
|
|
|
|
v0.5.8
|
|
|
|
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 == "\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);
|