18 changed files with 3237 additions and 1533 deletions
@ -1,2 +1,3 @@ |
|||||||
bower_components/* |
_gitignore/ |
||||||
node_modules/* |
bower_components/ |
||||||
|
node_modules/ |
@ -1,677 +0,0 @@ |
|||||||
/* |
|
||||||
Papa Parse |
|
||||||
v2.1.4 |
|
||||||
https://github.com/mholt/jquery.parse
|
|
||||||
*/ |
|
||||||
|
|
||||||
(function($) |
|
||||||
{ |
|
||||||
"use strict"; |
|
||||||
|
|
||||||
$.fn.parse = function(options) |
|
||||||
{ |
|
||||||
var config = options.config || {}; |
|
||||||
var queue = []; |
|
||||||
|
|
||||||
this.each(function(idx) |
|
||||||
{ |
|
||||||
var supported = $(this).prop('tagName').toUpperCase() == "INPUT" |
|
||||||
&& $(this).attr('type') == "file" |
|
||||||
&& window.FileReader; |
|
||||||
|
|
||||||
if (!supported) |
|
||||||
return true; // continue to next input element
|
|
||||||
|
|
||||||
var instanceConfig = $.extend({}, config); // This copy is very important
|
|
||||||
|
|
||||||
if (!this.files || this.files.length == 0) |
|
||||||
{ |
|
||||||
error("NoFileError", undefined, this); |
|
||||||
return true; // continue to next input element
|
|
||||||
} |
|
||||||
|
|
||||||
for (var i = 0; i < this.files.length; i++) |
|
||||||
queue.push({ |
|
||||||
file: this.files[i], |
|
||||||
inputElem: this, |
|
||||||
instanceConfig: instanceConfig |
|
||||||
}); |
|
||||||
|
|
||||||
if (queue.length > 0) |
|
||||||
parseFile(queue[0]); |
|
||||||
}); |
|
||||||
|
|
||||||
return this; |
|
||||||
|
|
||||||
|
|
||||||
function parseFile(f) |
|
||||||
{ |
|
||||||
var completeFunc = complete, errorFunc; |
|
||||||
|
|
||||||
if (isFunction(options.error)) |
|
||||||
errorFunc = function() { options.error(reader.error, f.file, f.inputElem); }; |
|
||||||
if (isFunction(options.complete)) |
|
||||||
completeFunc = function(results, file, inputElem, event) { options.complete(results, file, inputElem, event); complete(); }; |
|
||||||
|
|
||||||
if (isFunction(options.before)) |
|
||||||
{ |
|
||||||
var returned = options.before(f.file, f.inputElem); |
|
||||||
|
|
||||||
if (typeof returned === 'object') |
|
||||||
f.instanceConfig = $.extend(f.instanceConfig, returned); |
|
||||||
else if (returned === "skip") |
|
||||||
return complete(); // Proceeds to next file
|
|
||||||
else if (returned === false) |
|
||||||
{ |
|
||||||
error("AbortError", f.file, f.inputElem); |
|
||||||
return; // Aborts all queued files immediately
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (f.instanceConfig.step) |
|
||||||
{ |
|
||||||
var streamer = new Streamer(f.file, { |
|
||||||
inputElem: f.inputElem, |
|
||||||
config: $.extend({}, f.instanceConfig) // This copy is very important
|
|
||||||
}); |
|
||||||
streamer.stream(completeFunc, errorFunc); |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
var reader = new FileReader(); |
|
||||||
reader.onerror = errorFunc; |
|
||||||
reader.onload = function(event) |
|
||||||
{ |
|
||||||
var text = event.target.result; |
|
||||||
var results = $.parse(text, f.instanceConfig); |
|
||||||
completeFunc(results, f.file, f.inputElem, event); |
|
||||||
}; |
|
||||||
reader.readAsText(f.file, f.instanceConfig.encoding); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function error(name, file, elem) |
|
||||||
{ |
|
||||||
if (isFunction(options.error)) |
|
||||||
options.error({name: name}, file, elem); |
|
||||||
} |
|
||||||
|
|
||||||
function complete() |
|
||||||
{ |
|
||||||
queue.splice(0, 1); |
|
||||||
if (queue.length > 0) |
|
||||||
parseFile(queue[0]); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
$.parse = function(input, options) |
|
||||||
{ |
|
||||||
var parser = new Parser(options); |
|
||||||
return parser.parse(input); |
|
||||||
}; |
|
||||||
|
|
||||||
function isFunction(func) { return typeof func === 'function'; } |
|
||||||
|
|
||||||
// Streamer is a wrapper over Parser to handle chunking the input file
|
|
||||||
function Streamer(file, settings) |
|
||||||
{ |
|
||||||
if (!settings) |
|
||||||
settings = {}; |
|
||||||
|
|
||||||
if (!settings.chunkSize) |
|
||||||
settings.chunkSize = 1024 * 1024 * 5; // 5 MB
|
|
||||||
|
|
||||||
if (settings.config.step) // it had better be there...!
|
|
||||||
{ |
|
||||||
var userStep = settings.config.step; |
|
||||||
settings.config.step = function(data) { return userStep(data, file, settings.inputElem); }; |
|
||||||
} |
|
||||||
|
|
||||||
var start = 0; |
|
||||||
var aggregate = ""; |
|
||||||
var partialLine = ""; |
|
||||||
var parser = new Parser(settings.config); |
|
||||||
var reader = new FileReader(); |
|
||||||
|
|
||||||
reader.onload = blobLoaded; |
|
||||||
reader.onerror = blobError; |
|
||||||
|
|
||||||
this.stream = function(completeCallback, fileErrorCallback) |
|
||||||
{ |
|
||||||
settings.onComplete = completeCallback; |
|
||||||
settings.onFileError = fileErrorCallback; |
|
||||||
nextChunk(); |
|
||||||
}; |
|
||||||
|
|
||||||
function blobLoaded(event) |
|
||||||
{ |
|
||||||
aggregate += partialLine + event.target.result; |
|
||||||
partialLine = ""; |
|
||||||
|
|
||||||
if (start < file.size) |
|
||||||
{ |
|
||||||
var lastLineEnd = aggregate.lastIndexOf("\n"); |
|
||||||
|
|
||||||
if (lastLineEnd < 0) |
|
||||||
lastLineEnd = aggregate.lastIndexOf("\r"); |
|
||||||
|
|
||||||
if (lastLineEnd > -1) |
|
||||||
{ |
|
||||||
partialLine = aggregate.substring(lastLineEnd + 1); // skip the line ending character
|
|
||||||
aggregate = aggregate.substring(0, lastLineEnd); |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
// For chunk sizes smaller than a line (a line could not fit in a single chunk)
|
|
||||||
// we simply build our aggregate by reading in the next chunk, until we find a newline
|
|
||||||
nextChunk(); |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var results = parser.parse(aggregate); |
|
||||||
aggregate = ""; |
|
||||||
|
|
||||||
if (start >= file.size) |
|
||||||
return done(event); |
|
||||||
else if (results.errors.abort) |
|
||||||
return; |
|
||||||
else |
|
||||||
nextChunk(); |
|
||||||
} |
|
||||||
|
|
||||||
function done(event) |
|
||||||
{ |
|
||||||
if (typeof settings.onComplete === 'function') |
|
||||||
settings.onComplete(undefined, file, settings.inputElem, event); |
|
||||||
} |
|
||||||
|
|
||||||
function blobError() |
|
||||||
{ |
|
||||||
if (typeof settings.onFileError === 'function') |
|
||||||
settings.onFileError(reader.error, file, settings.inputElem); |
|
||||||
} |
|
||||||
|
|
||||||
function nextChunk() |
|
||||||
{ |
|
||||||
if (start < file.size) |
|
||||||
{ |
|
||||||
var end = Math.min(start + settings.chunkSize, file.size); |
|
||||||
|
|
||||||
if (file.slice) { |
|
||||||
reader.readAsText(file.slice(start, end), settings.config.encoding); |
|
||||||
} |
|
||||||
else if (file.webkitSlice) { |
|
||||||
reader.readAsText(file.webkitSlice(start, end), settings.config.encoding); |
|
||||||
} |
|
||||||
|
|
||||||
start += settings.chunkSize; |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// Parser is the actual parsing component.
|
|
||||||
// It is under test and does not depend on jQuery.
|
|
||||||
// You could rip this entire function out of the plugin
|
|
||||||
// and use it independently (with attribution).
|
|
||||||
function Parser(config) |
|
||||||
{ |
|
||||||
var self = this; |
|
||||||
var _invocations = 0; |
|
||||||
var _input = ""; |
|
||||||
var _chunkOffset = 0; |
|
||||||
var _abort = false; |
|
||||||
var _config = {}; |
|
||||||
var _state = freshState(); |
|
||||||
var _defaultConfig = { |
|
||||||
delimiter: "", |
|
||||||
header: true, |
|
||||||
dynamicTyping: true, |
|
||||||
preview: 0, |
|
||||||
commentChar: false |
|
||||||
}; |
|
||||||
var _regex = { |
|
||||||
floats: /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i, |
|
||||||
empty: /^\s*$/ |
|
||||||
}; |
|
||||||
|
|
||||||
config = validConfig(config); |
|
||||||
_config = { |
|
||||||
delimiter: config.delimiter, |
|
||||||
header: config.header, |
|
||||||
dynamicTyping: config.dynamicTyping, |
|
||||||
preview: config.preview, |
|
||||||
step: config.step, |
|
||||||
commentChar: config.commentChar |
|
||||||
}; |
|
||||||
|
|
||||||
this.parse = function(input) |
|
||||||
{ |
|
||||||
if (typeof input !== 'string') |
|
||||||
return returnable(); |
|
||||||
|
|
||||||
reset(input); |
|
||||||
|
|
||||||
if (!_config.delimiter && !guessDelimiter(input)) |
|
||||||
{ |
|
||||||
addError("Delimiter", "UndetectableDelimiter", "Unable to auto-detect delimiting character; defaulted to comma", "config"); |
|
||||||
_config.delimiter = ","; |
|
||||||
} |
|
||||||
|
|
||||||
for (_state.i = 0; _state.i < _input.length; _state.i++) |
|
||||||
{ |
|
||||||
if (_abort || (_config.preview > 0 && _state.lineNum > _config.preview)) |
|
||||||
break; |
|
||||||
|
|
||||||
_state.ch = _input[_state.i]; |
|
||||||
|
|
||||||
if (_config.commentChar) { |
|
||||||
// Check if line begins with a commentChar
|
|
||||||
if (_state.line == "" &&_state.ch == _config.commentChar) { |
|
||||||
newRow(); |
|
||||||
|
|
||||||
// skip to next row
|
|
||||||
while (true) { |
|
||||||
++_state.i |
|
||||||
if (_input[_state.i] == "\r" || _input[_state.i] == "\n") |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
_state.ch = _input[_state.i]; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
_state.line += _state.ch; |
|
||||||
|
|
||||||
if (_state.ch == '"') |
|
||||||
handleQuote(); |
|
||||||
else if (_state.inQuotes) |
|
||||||
inQuotes(); |
|
||||||
else |
|
||||||
notInQuotes(); |
|
||||||
} |
|
||||||
|
|
||||||
if (_abort) |
|
||||||
addError("Abort", "ParseAbort", "Parsing was aborted by the user's step function", "abort"); |
|
||||||
else |
|
||||||
{ |
|
||||||
endRow(); // End of input is also end of the last row
|
|
||||||
if (_state.inQuotes) |
|
||||||
addError("Quotes", "MissingQuotes", "Unescaped or mismatched quotes"); |
|
||||||
} |
|
||||||
|
|
||||||
return returnable(); |
|
||||||
}; |
|
||||||
|
|
||||||
this.getOptions = function() |
|
||||||
{ |
|
||||||
return { |
|
||||||
delimiter: _config.delimiter, |
|
||||||
header: _config.header, |
|
||||||
dynamicTyping: _config.dynamicTyping, |
|
||||||
preview: _config.preview, |
|
||||||
step: _config.step |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
function validConfig(config) |
|
||||||
{ |
|
||||||
if (typeof config !== 'object') |
|
||||||
config = {}; |
|
||||||
|
|
||||||
if (typeof config.delimiter !== 'string' |
|
||||||
|| config.delimiter.length != 1) |
|
||||||
config.delimiter = _defaultConfig.delimiter; |
|
||||||
|
|
||||||
if (config.delimiter == '"' || config.delimiter == "\n") |
|
||||||
config.delimiter = _defaultConfig.delimiter; |
|
||||||
|
|
||||||
if (typeof config.header !== 'boolean') |
|
||||||
config.header = _defaultConfig.header; |
|
||||||
|
|
||||||
if (typeof config.dynamicTyping !== 'boolean') |
|
||||||
config.dynamicTyping = _defaultConfig.dynamicTyping; |
|
||||||
|
|
||||||
if (typeof config.preview !== 'number') |
|
||||||
config.preview = _defaultConfig.preview; |
|
||||||
|
|
||||||
if (typeof config.step !== 'function') |
|
||||||
config.step = _defaultConfig.step; |
|
||||||
|
|
||||||
if (config.commentChar === true) |
|
||||||
config.commentChar = '#'; |
|
||||||
|
|
||||||
if (typeof config.commentChar !== 'string' && config.commentChar !== false) |
|
||||||
config.commentChar = false; |
|
||||||
|
|
||||||
return config; |
|
||||||
} |
|
||||||
|
|
||||||
function guessDelimiter(input) |
|
||||||
{ |
|
||||||
var recordSep = String.fromCharCode(30); |
|
||||||
var unitSep = String.fromCharCode(31); |
|
||||||
var delimiters = [",", "\t", "|", ";", recordSep, unitSep]; |
|
||||||
var bestDelim, bestDelta, fieldCountPrevRow; |
|
||||||
|
|
||||||
for (var i = 0; i < delimiters.length; i++) |
|
||||||
{ |
|
||||||
var delim = delimiters[i]; |
|
||||||
var delta = 0, avgFieldCount = 0; |
|
||||||
var fieldCountPrevRow = undefined; |
|
||||||
|
|
||||||
var preview = new Parser({ |
|
||||||
delimiter: delim, |
|
||||||
header: false, |
|
||||||
dynamicTyping: false, |
|
||||||
preview: 10 |
|
||||||
}).parse(input); |
|
||||||
|
|
||||||
for (var j = 0; j < preview.results.length; j++) |
|
||||||
{ |
|
||||||
var fieldCount = preview.results[j].length; |
|
||||||
avgFieldCount += fieldCount; |
|
||||||
|
|
||||||
if (typeof fieldCountPrevRow === 'undefined') |
|
||||||
{ |
|
||||||
fieldCountPrevRow = fieldCount; |
|
||||||
continue; |
|
||||||
} |
|
||||||
else if (fieldCount > 1) |
|
||||||
{ |
|
||||||
delta += Math.abs(fieldCount - fieldCountPrevRow); |
|
||||||
fieldCountPrevRow = fieldCount; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
avgFieldCount /= preview.results.length; |
|
||||||
|
|
||||||
if ((typeof bestDelta === 'undefined' || delta < bestDelta) |
|
||||||
&& avgFieldCount > 1.99) |
|
||||||
{ |
|
||||||
bestDelta = delta; |
|
||||||
bestDelim = delim; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
_config.delimiter = bestDelim; |
|
||||||
|
|
||||||
return !!bestDelim; |
|
||||||
} |
|
||||||
|
|
||||||
function handleQuote() |
|
||||||
{ |
|
||||||
var delimBefore = (_state.i > 0 && isBoundary(_state.i-1)) |
|
||||||
|| _state.i == 0; |
|
||||||
var delimAfter = (_state.i < _input.length - 1 && isBoundary(_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("Quotes", "UnexpectedQuotes", "Unexpected quotes"); |
|
||||||
} |
|
||||||
|
|
||||||
function inQuotes() |
|
||||||
{ |
|
||||||
appendCharToField(); |
|
||||||
} |
|
||||||
|
|
||||||
function appendCharToField() |
|
||||||
{ |
|
||||||
_state.fieldVal += _state.ch; |
|
||||||
} |
|
||||||
|
|
||||||
function notInQuotes() |
|
||||||
{ |
|
||||||
if (_state.ch == _config.delimiter) |
|
||||||
saveValue(); |
|
||||||
else if ((_state.ch == "\r" && _state.i < _input.length - 1 |
|
||||||
&& _input[_state.i+1] == "\n") |
|
||||||
|| (_state.ch == "\n" && _state.i < _input.length - 1 |
|
||||||
&& _input[_state.i+1] == "\r")) |
|
||||||
{ |
|
||||||
newRow(); |
|
||||||
_state.i++; |
|
||||||
} |
|
||||||
else if (_state.ch == "\r" || _state.ch == "\n") |
|
||||||
newRow(); |
|
||||||
else |
|
||||||
appendCharToField(); |
|
||||||
} |
|
||||||
|
|
||||||
function isBoundary(i) |
|
||||||
{ |
|
||||||
return _input[i] == _config.delimiter |
|
||||||
|| _input[i] == "\n" |
|
||||||
|| _input[i] == "\r"; |
|
||||||
} |
|
||||||
|
|
||||||
function saveValue() |
|
||||||
{ |
|
||||||
if (_config.header) |
|
||||||
{ |
|
||||||
if (_state.lineNum == 1 && _invocations == 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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
if (_config.dynamicTyping) |
|
||||||
_state.fieldVal = tryParseFloat(_state.fieldVal); |
|
||||||
_state.parsed[_state.parsed.length - 1].push(_state.fieldVal); |
|
||||||
} |
|
||||||
|
|
||||||
_state.fieldVal = ""; |
|
||||||
_state.field ++; |
|
||||||
} |
|
||||||
|
|
||||||
function newRow() |
|
||||||
{ |
|
||||||
endRow(); |
|
||||||
|
|
||||||
if (streaming()) |
|
||||||
{ |
|
||||||
_state.errors = {}; |
|
||||||
_state.errors.length = 0; |
|
||||||
} |
|
||||||
|
|
||||||
if (_config.header) |
|
||||||
{ |
|
||||||
if (_state.lineNum > 0) |
|
||||||
{ |
|
||||||
if (streaming()) |
|
||||||
_state.parsed.rows = [ {} ]; |
|
||||||
else |
|
||||||
_state.parsed.rows.push({}); |
|
||||||
} |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
if (streaming()) |
|
||||||
_state.parsed = [ [] ]; |
|
||||||
else if (!_config.header) |
|
||||||
_state.parsed.push([]); |
|
||||||
} |
|
||||||
|
|
||||||
_state.lineNum++; |
|
||||||
_state.line = ""; |
|
||||||
_state.field = 0; |
|
||||||
} |
|
||||||
|
|
||||||
function endRow() |
|
||||||
{ |
|
||||||
if (_abort) |
|
||||||
return; |
|
||||||
|
|
||||||
saveValue(); |
|
||||||
|
|
||||||
var emptyLine = trimEmptyLine(); |
|
||||||
|
|
||||||
if (!emptyLine && _config.header) |
|
||||||
inspectFieldCount(); |
|
||||||
|
|
||||||
if (streaming() && (!_config.header || |
|
||||||
(_config.header && _state.parsed.rows.length > 0))) |
|
||||||
{ |
|
||||||
var keepGoing = _config.step(returnable()); |
|
||||||
if (keepGoing === false) |
|
||||||
_abort = true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function streaming() |
|
||||||
{ |
|
||||||
return typeof _config.step === 'function'; |
|
||||||
} |
|
||||||
|
|
||||||
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 (actual < expected) |
|
||||||
return addError("FieldMismatch", "TooFewFields", "Too few fields: expected " + expected + " fields but parsed " + actual); |
|
||||||
else if (actual > expected) |
|
||||||
return addError("FieldMismatch", "TooManyFields", "Too many fields: expected " + expected + " fields but parsed " + actual); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
function addError(type, code, msg, errKey) |
|
||||||
{ |
|
||||||
var row = _config.header |
|
||||||
? (_state.parsed.rows.length ? _state.parsed.rows.length - 1 : undefined) |
|
||||||
: _state.parsed.length - 1; |
|
||||||
var key = errKey || row; |
|
||||||
|
|
||||||
if (typeof _state.errors[key] === 'undefined') |
|
||||||
_state.errors[key] = []; |
|
||||||
|
|
||||||
_state.errors[key].push({ |
|
||||||
type: type, |
|
||||||
code: code, |
|
||||||
message: msg, |
|
||||||
line: _state.lineNum, |
|
||||||
row: row, |
|
||||||
index: _state.i + _chunkOffset |
|
||||||
}); |
|
||||||
|
|
||||||
_state.errors.length ++; |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
function returnable() |
|
||||||
{ |
|
||||||
return { |
|
||||||
results: _state.parsed, |
|
||||||
errors: _state.errors, |
|
||||||
meta: { |
|
||||||
delimiter: _config.delimiter |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
function reset(input) |
|
||||||
{ |
|
||||||
_invocations++; |
|
||||||
if (_invocations > 1 && streaming()) |
|
||||||
_chunkOffset += input.length; |
|
||||||
_state = freshState(); |
|
||||||
_input = input; |
|
||||||
} |
|
||||||
|
|
||||||
function freshState() |
|
||||||
{ |
|
||||||
// If streaming, and thus parsing the input in chunks, this
|
|
||||||
// is careful to preserve what we've already got, when necessary.
|
|
||||||
var parsed; |
|
||||||
if (_config.header) |
|
||||||
{ |
|
||||||
parsed = { |
|
||||||
fields: streaming() ? _state.parsed.fields || [] : [], |
|
||||||
rows: streaming() && _invocations > 1 ? [ {} ] : [] |
|
||||||
}; |
|
||||||
} |
|
||||||
else |
|
||||||
parsed = [ [] ]; |
|
||||||
|
|
||||||
return { |
|
||||||
i: 0, |
|
||||||
lineNum: streaming() ? _state.lineNum : 1, |
|
||||||
field: 0, |
|
||||||
fieldVal: "", |
|
||||||
line: "", |
|
||||||
ch: "", |
|
||||||
inQuotes: false, |
|
||||||
parsed: parsed, |
|
||||||
errors: { length: 0 } |
|
||||||
}; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
})(jQuery); |
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,53 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Papa Parse Player</title> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<link rel="stylesheet" href="player.css"> |
||||||
|
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> |
||||||
|
<script src="../papaparse.js"></script> |
||||||
|
<script src="player.js"></script> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<h1><a href="http://papaparse.com">Papa Parse</a> Player</h1> |
||||||
|
|
||||||
|
<div class="grid-container"> |
||||||
|
|
||||||
|
<div class="grid-25"> |
||||||
|
<label><input type="checkbox" id="download"> Download</label> |
||||||
|
<label><input type="checkbox" id="stream"> Stream</label> |
||||||
|
<label><input type="checkbox" id="worker"> Worker thread</label> |
||||||
|
<label><input type="checkbox" id="header"> Header row</label> |
||||||
|
<label><input type="checkbox" id="dynamicTyping"> Dynamic typing</label> |
||||||
|
<label>Preview: <input type="number" min="0" max="1000" placeholder="default" id="preview"></label> |
||||||
|
<label>Encoding: <input type="text" id="encoding" placeholder="default" size="10"></label> |
||||||
|
<label>Comment char: <input type="text" size="5" maxlength="1" placeholder="default" id="comments"></label> |
||||||
|
<label>Delimiter: <input type="text" size="5" maxlength="1" placeholder="auto" id="delimiter"> <a href="javascript:" id="insert-tab">tab</a></label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="grid-75 text-center"> |
||||||
|
|
||||||
|
<textarea id="input" placeholder="Input">Column 1,Column 2,Column 3,Column 4 |
||||||
|
1-1,1-2,1-3,1-4 |
||||||
|
2-1,2-2,2-3,2-4 |
||||||
|
3-1,3-2,3-3,3-4 |
||||||
|
4,5,6,7</textarea> |
||||||
|
|
||||||
|
<br> |
||||||
|
<b>or</b> |
||||||
|
<br> |
||||||
|
|
||||||
|
<input type="file" id="files" multiple> |
||||||
|
|
||||||
|
<br><br> |
||||||
|
|
||||||
|
<button id="submit">Parse</button> |
||||||
|
|
||||||
|
<br><br> |
||||||
|
|
||||||
|
<i>Open the Console in your browser's inspector tools to see results.</i> |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,70 @@ |
|||||||
|
var stepped = 0; |
||||||
|
var start, end; |
||||||
|
|
||||||
|
$(function() |
||||||
|
{ |
||||||
|
$('#submit').click(function() |
||||||
|
{ |
||||||
|
stepped = 0; |
||||||
|
var txt = $('#input').val(); |
||||||
|
var files = $('#files')[0].files; |
||||||
|
var config = buildConfig(); |
||||||
|
|
||||||
|
if (files.length > 0) |
||||||
|
{ |
||||||
|
start = performance.now(); |
||||||
|
|
||||||
|
$('#files').parse({ |
||||||
|
config: config, |
||||||
|
before: function(file, inputElem) |
||||||
|
{ |
||||||
|
console.log("Parsing file:", file); |
||||||
|
}, |
||||||
|
complete: function() |
||||||
|
{ |
||||||
|
console.log("Done with all files."); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
start = performance.now(); |
||||||
|
var results = Papa.parse(txt, config); |
||||||
|
console.log("Synchronous parse results:", results); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
$('#insert-tab').click(function() |
||||||
|
{ |
||||||
|
$('#delimiter').val('\t'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function buildConfig() |
||||||
|
{ |
||||||
|
return { |
||||||
|
delimiter: $('#delimiter').val(), |
||||||
|
header: $('#header').prop('checked'), |
||||||
|
dynamicTyping: $('#dynamicTyping').prop('checked'), |
||||||
|
preview: parseInt($('#preview').val() || 0), |
||||||
|
step: $('#stream').prop('checked') ? stepFn : undefined, |
||||||
|
encoding: $('#encoding').val(), |
||||||
|
worker: $('#worker').prop('checked'), |
||||||
|
comments: $('#comments').val(), |
||||||
|
complete: completeFn, |
||||||
|
download: $('#download').prop('checked') |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function stepFn(results, parser) |
||||||
|
{ |
||||||
|
stepped++; |
||||||
|
} |
||||||
|
|
||||||
|
function completeFn() |
||||||
|
{ |
||||||
|
end = performance.now(); |
||||||
|
console.log("Finished input. Time:", end-start, arguments); |
||||||
|
} |
@ -1,127 +0,0 @@ |
|||||||
<!DOCTYPE html> |
|
||||||
<html> |
|
||||||
<head> |
|
||||||
<title>Parser Tests</title> |
|
||||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> |
|
||||||
<script src="jquery.parse.js"></script> |
|
||||||
<script src="tests.js"></script> |
|
||||||
<style> |
|
||||||
body { |
|
||||||
font-family: sans-serif; |
|
||||||
} |
|
||||||
|
|
||||||
#tmp { |
|
||||||
white-space: pre; |
|
||||||
font-family: 'Menlo', 'Monaco', 'Courier New', monospace; |
|
||||||
font-size: 10px; |
|
||||||
} |
|
||||||
|
|
||||||
#results { |
|
||||||
border-collapse: collapse; |
|
||||||
width: 100%; |
|
||||||
} |
|
||||||
|
|
||||||
#results td { |
|
||||||
vertical-align: top; |
|
||||||
padding: 10px; |
|
||||||
border-bottom: 10px solid white; |
|
||||||
} |
|
||||||
|
|
||||||
#results td div { |
|
||||||
overflow-x: auto; |
|
||||||
} |
|
||||||
|
|
||||||
.count { |
|
||||||
background: #333; |
|
||||||
color: #DDD; |
|
||||||
width: 2em; |
|
||||||
text-align: center; |
|
||||||
} |
|
||||||
|
|
||||||
.input, |
|
||||||
.output { |
|
||||||
width: 25%; |
|
||||||
} |
|
||||||
|
|
||||||
.input { |
|
||||||
background: #DDD; |
|
||||||
} |
|
||||||
|
|
||||||
.config { |
|
||||||
background: #CCC; |
|
||||||
} |
|
||||||
|
|
||||||
.output { |
|
||||||
background: #EEE; |
|
||||||
} |
|
||||||
|
|
||||||
.input code, |
|
||||||
.config code, |
|
||||||
.output code { |
|
||||||
font: 12px/1.5em 'Menlo', 'Monaco', 'Courier New', monospace; |
|
||||||
display: block; |
|
||||||
white-space: pre; |
|
||||||
} |
|
||||||
|
|
||||||
.clr-green, |
|
||||||
.passing { |
|
||||||
color: #475B15; |
|
||||||
} |
|
||||||
|
|
||||||
.clr-red, |
|
||||||
.failing { |
|
||||||
color: #AA0000; |
|
||||||
} |
|
||||||
|
|
||||||
.passing { |
|
||||||
background: #ECF9CC; |
|
||||||
color: #475B15; |
|
||||||
} |
|
||||||
|
|
||||||
.failing { |
|
||||||
background: #FFE8E8; |
|
||||||
} |
|
||||||
|
|
||||||
.failing code { |
|
||||||
font-weight: bold; |
|
||||||
} |
|
||||||
|
|
||||||
hr { |
|
||||||
border: 0; |
|
||||||
background: 0; |
|
||||||
clear: both; |
|
||||||
} |
|
||||||
|
|
||||||
.clr-green { |
|
||||||
color: #79A01E; |
|
||||||
} |
|
||||||
|
|
||||||
.clr-red { |
|
||||||
color: #AA0000; |
|
||||||
} |
|
||||||
|
|
||||||
#pass-count, |
|
||||||
#fail-count { |
|
||||||
font-weight: bold; |
|
||||||
} |
|
||||||
</style> |
|
||||||
</head> |
|
||||||
<body> |
|
||||||
SUMMARY |
|
||||||
|
|
||||||
<span class="clr-green"><span id="pass-count">-</span> PASS</span> |
|
||||||
|
|
||||||
<span class="clr-red"><span id="fail-count">-</span> FAIL</span> |
|
||||||
<br><br> |
|
||||||
<table id="results"> |
|
||||||
<tr> |
|
||||||
<th></th> |
|
||||||
<th>Input</th> |
|
||||||
<th>Config</th> |
|
||||||
<th>Expected</th> |
|
||||||
<th>Actual</th> |
|
||||||
</tr> |
|
||||||
</table> |
|
||||||
<div id="output"></div> |
|
||||||
</body> |
|
||||||
</html> |
|
@ -1,693 +0,0 @@ |
|||||||
var passCount = 0, failCount = 0; |
|
||||||
var passing = "passing"; |
|
||||||
var failing = "failing"; |
|
||||||
|
|
||||||
var recordSep = String.fromCharCode(30); |
|
||||||
var unitSep = String.fromCharCode(31); |
|
||||||
|
|
||||||
var resultSet1 = [ |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: true, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": "V1", |
|
||||||
"F2": 2, |
|
||||||
"F3": "V3" |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: false, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": [ |
|
||||||
[ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V1", |
|
||||||
2, |
|
||||||
"V3" |
|
||||||
] |
|
||||||
], |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: false, dynamicTyping: false }, |
|
||||||
expected: { |
|
||||||
"results": [ |
|
||||||
[ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V1", |
|
||||||
"2", |
|
||||||
"V3" |
|
||||||
] |
|
||||||
], |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: true, dynamicTyping: false }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": "V1", |
|
||||||
"F2": "2", |
|
||||||
"F3": "V3" |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
config: { delimiter: "", header: true, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": "V1", |
|
||||||
"F2": 2, |
|
||||||
"F3": "V3" |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
]; |
|
||||||
|
|
||||||
var tests = [ |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\nV1,2,V3", |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\r\nV1,2,V3", |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,\"F2\",F3\r\nV1,2,\"V3\"", |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\n\nV1,2,V3", |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\r\n\r\nV1,2,V3", |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\n\rV1,2,V3", |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\rV1,2,V3", |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\r\n \r\nV1,2,V3", |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "\r\nF1,F2,F3\r\nV1,2,V3", |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: 'F1,F2,"F3"\n"V1","2",V3', |
|
||||||
cases: resultSet1 |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\nV1,2,V3\nV4,V5,V6", |
|
||||||
cases: [ |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: true, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": "V1", |
|
||||||
"F2": 2, |
|
||||||
"F3": "V3" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"F1": "V4", |
|
||||||
"F2": "V5", |
|
||||||
"F3": "V6" |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: false, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": [ |
|
||||||
[ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V1", |
|
||||||
2, |
|
||||||
"V3" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V4", |
|
||||||
"V5", |
|
||||||
"V6" |
|
||||||
] |
|
||||||
], |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: false, dynamicTyping: false }, |
|
||||||
expected: { |
|
||||||
"results": [ |
|
||||||
[ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V1", |
|
||||||
"2", |
|
||||||
"V3" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V4", |
|
||||||
"V5", |
|
||||||
"V6" |
|
||||||
] |
|
||||||
], |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: true, dynamicTyping: false }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": "V1", |
|
||||||
"F2": "2", |
|
||||||
"F3": "V3" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"F1": "V4", |
|
||||||
"F2": "V5", |
|
||||||
"F3": "V6" |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\n,2,V3\nV4,V5,V6", |
|
||||||
cases: [ |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: true, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": "", |
|
||||||
"F2": 2, |
|
||||||
"F3": "V3" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"F1": "V4", |
|
||||||
"F2": "V5", |
|
||||||
"F3": "V6" |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\n,2,V3,V4\nV5,V6,V7", |
|
||||||
cases: [ |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: true, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": "", |
|
||||||
"F2": 2, |
|
||||||
"F3": "V3", |
|
||||||
"__parsed_extra": [ |
|
||||||
"V4" |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
"F1": "V5", |
|
||||||
"F2": "V6", |
|
||||||
"F3": "V7" |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"0": [ |
|
||||||
{ |
|
||||||
"type": "FieldMismatch", |
|
||||||
"code": "TooManyFields", |
|
||||||
"message": "Too many fields: expected 3 fields but parsed 4", |
|
||||||
"line": 2, |
|
||||||
"row": 0, |
|
||||||
"index": 17 |
|
||||||
} |
|
||||||
], |
|
||||||
"length": 1 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\nV1,2.0,-3.01, V4\n\rV5,\"V\n6\",V7\r,,", |
|
||||||
cases: [ |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: true, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": "V1", |
|
||||||
"F2": 2, |
|
||||||
"F3": -3.01, |
|
||||||
"__parsed_extra": [ |
|
||||||
" V4" |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
"F1": "V5", |
|
||||||
"F2": "V\n6", |
|
||||||
"F3": "V7" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"F1": "", |
|
||||||
"F2": "", |
|
||||||
"F3": "" |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"0": [ |
|
||||||
{ |
|
||||||
"type": "FieldMismatch", |
|
||||||
"code": "TooManyFields", |
|
||||||
"message": "Too many fields: expected 3 fields but parsed 4", |
|
||||||
"line": 2, |
|
||||||
"row": 0, |
|
||||||
"index": 25 |
|
||||||
} |
|
||||||
], |
|
||||||
"length": 1 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\nV1,V2,V3\nV5,\"V6,V7", |
|
||||||
cases: [ |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: true, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": "V1", |
|
||||||
"F2": "V2", |
|
||||||
"F3": "V3" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"F1": "V5", |
|
||||||
"F2": "V6,V7" |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"1": [ |
|
||||||
{ |
|
||||||
"type": "FieldMismatch", |
|
||||||
"code": "TooFewFields", |
|
||||||
"message": "Too few fields: expected 3 fields but parsed 2", |
|
||||||
"line": 3, |
|
||||||
"row": 1, |
|
||||||
"index": 27 |
|
||||||
}, |
|
||||||
{ |
|
||||||
"type": "Quotes", |
|
||||||
"code": "MissingQuotes", |
|
||||||
"message": "Unescaped or mismatched quotes", |
|
||||||
"line": 3, |
|
||||||
"row": 1, |
|
||||||
"index": 27 |
|
||||||
} |
|
||||||
], |
|
||||||
"length": 2 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1,F2,F3\n2,-2, 2\n 2. ,.2, .2 \n-2., -2.0e-5, -.4 ", |
|
||||||
cases: [ |
|
||||||
{ |
|
||||||
config: { delimiter: ",", header: true, dynamicTyping: true }, |
|
||||||
expected: { |
|
||||||
"results": { |
|
||||||
"fields": [ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
"rows": [ |
|
||||||
{ |
|
||||||
"F1": 2, |
|
||||||
"F2": -2, |
|
||||||
"F3": 2 |
|
||||||
}, |
|
||||||
{ |
|
||||||
"F1": 2, |
|
||||||
"F2": 0.2, |
|
||||||
"F3": 0.2 |
|
||||||
}, |
|
||||||
{ |
|
||||||
"F1": -2, |
|
||||||
"F2": -0.00002, |
|
||||||
"F3": -0.4 |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "F1\nV1\nV2\nV3\nV4", |
|
||||||
cases: [ |
|
||||||
{ |
|
||||||
config: { delimiter: "", header: false, dynamicTyping: false }, |
|
||||||
expected: { |
|
||||||
"results": [ |
|
||||||
[ |
|
||||||
"F1" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V1" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V2" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V3" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V4" |
|
||||||
] |
|
||||||
], |
|
||||||
"errors": { |
|
||||||
"length": 1, |
|
||||||
"config": [ |
|
||||||
{ |
|
||||||
"type": "Delimiter", |
|
||||||
"code": "UndetectableDelimiter", |
|
||||||
"message": "Unable to auto-detect delimiting character; defaulted to comma", |
|
||||||
"line": 1, |
|
||||||
"row": 0, |
|
||||||
"index": 0 |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "," |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: ["F1","F2","F3\r\nV1","V2","V3"].join(recordSep), |
|
||||||
cases: [ |
|
||||||
{ |
|
||||||
config: { delimiter: "", header: false, dynamicTyping: false }, |
|
||||||
expected: { |
|
||||||
"results": [ |
|
||||||
[ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V1", |
|
||||||
"V2", |
|
||||||
"V3" |
|
||||||
], |
|
||||||
], |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "\u001e" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: ["F1","F2","F3\r\nV1","V2","V3"].join(unitSep), |
|
||||||
cases: [ |
|
||||||
{ |
|
||||||
config: { delimiter: "", header: false, dynamicTyping: false }, |
|
||||||
expected: { |
|
||||||
"results": [ |
|
||||||
[ |
|
||||||
"F1", |
|
||||||
"F2", |
|
||||||
"F3" |
|
||||||
], |
|
||||||
[ |
|
||||||
"V1", |
|
||||||
"V2", |
|
||||||
"V3" |
|
||||||
], |
|
||||||
], |
|
||||||
"errors": { |
|
||||||
"length": 0 |
|
||||||
}, |
|
||||||
"meta": { |
|
||||||
"delimiter": "\u001f" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
]; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$(function() |
|
||||||
{ |
|
||||||
var counter = 0; |
|
||||||
for (var i = 0; i < tests.length; i++) |
|
||||||
{ |
|
||||||
var test = tests[i]; |
|
||||||
var input = test.input; |
|
||||||
for (var j = 0; j < test.cases.length; j++) |
|
||||||
{ |
|
||||||
counter++; |
|
||||||
var testCase = test.cases[j]; |
|
||||||
var actual = doTest(input, testCase.config); |
|
||||||
var status = equal(actual, testCase.expected) ? passing : failing; |
|
||||||
render(input, testCase.expected, actual, testCase.config, counter, status); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
$('#pass-count').text(passCount); |
|
||||||
$('#fail-count').text(failCount); |
|
||||||
}); |
|
||||||
|
|
||||||
function doTest(input, config) |
|
||||||
{ |
|
||||||
// try
|
|
||||||
// {
|
|
||||||
return $.parse(input, config); |
|
||||||
// }
|
|
||||||
// catch (e)
|
|
||||||
// {
|
|
||||||
// return {exception: e.message, error: e, note: "See console to inspect stack"};
|
|
||||||
// }
|
|
||||||
} |
|
||||||
|
|
||||||
function render(input, expected, actual, config, count, status) |
|
||||||
{ |
|
||||||
if (status == passing) |
|
||||||
passCount++; |
|
||||||
else |
|
||||||
{ |
|
||||||
console.log("TEST " + count +" FAILED."); |
|
||||||
console.log(" Expected:", expected); |
|
||||||
console.log(" Actual:", actual); |
|
||||||
console.log(" Config:", config); |
|
||||||
failCount++; |
|
||||||
} |
|
||||||
|
|
||||||
var html = '<tr>' + |
|
||||||
'<td class="count">'+count+'</td>' + |
|
||||||
'<td class="input"><div><code>'+string(input)+'</code></div></td>' + |
|
||||||
'<td class="config"><div><code>'+string(config)+'</code></div></td>' + |
|
||||||
'<td class="output"><div><code>'+string(expected)+'</code></div></td>' + |
|
||||||
'<td class="output '+status+'"><div><code>'+string(actual)+'</code></div></td>' + |
|
||||||
'</tr>'; |
|
||||||
$('#results').append(html); |
|
||||||
} |
|
||||||
|
|
||||||
function string(obj) |
|
||||||
{ |
|
||||||
return typeof obj === "string" ? obj : JSON.stringify(obj, undefined, 2); |
|
||||||
} |
|
||||||
|
|
||||||
function equal(actual, expected) |
|
||||||
{ |
|
||||||
return string(actual) === string(expected); |
|
||||||
} |
|
@ -0,0 +1,401 @@ |
|||||||
|
// TODO: Add tests for unparse:
|
||||||
|
// If fields is omitted, write a CSV string without a header row
|
||||||
|
// If delimiter is omitted, choose comma by default
|
||||||
|
// If data is omitted, do nothing... maybe if fields IS specified, write just the header row?
|
||||||
|
|
||||||
|
var RECORD_SEP = String.fromCharCode(30); |
||||||
|
var UNIT_SEP = String.fromCharCode(31); |
||||||
|
|
||||||
|
var TESTS = [ |
||||||
|
{ |
||||||
|
input: 'A,b,c', |
||||||
|
description: "One row", |
||||||
|
expected: { |
||||||
|
data: [['A', 'b', 'c']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,b,c\r\nd,E,f', |
||||||
|
description: "Two rows", |
||||||
|
expected: { |
||||||
|
data: [['A', 'b', 'c'], ['d', 'E', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,b,c\rd,E,f', |
||||||
|
description: "Two rows, just \\r", |
||||||
|
expected: { |
||||||
|
data: [['A', 'b', 'c'], ['d', 'E', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,b,c\nd,E,f', |
||||||
|
description: "Two rows, just \\n", |
||||||
|
expected: { |
||||||
|
data: [['A', 'b', 'c'], ['d', 'E', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a, b ,c', |
||||||
|
description: "Whitespace at edges of unquoted field", |
||||||
|
notes: "Extra whitespace should graciously be preserved", |
||||||
|
expected: { |
||||||
|
data: [['a', ' b ', 'c']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,"B",C', |
||||||
|
description: "Quoted field", |
||||||
|
expected: { |
||||||
|
data: [['A', 'B', 'C']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A," B ",C', |
||||||
|
description: "Quoted field with extra whitespace on edges", |
||||||
|
expected: { |
||||||
|
data: [['A', ' B ', 'C']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,"B,B",C', |
||||||
|
description: "Quoted field with delimiter", |
||||||
|
expected: { |
||||||
|
data: [['A', 'B,B', 'C']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,"B\r\nB",C', |
||||||
|
description: "Quoted field with \\r\\n", |
||||||
|
expected: { |
||||||
|
data: [['A', 'B\r\nB', 'C']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,"B\rB",C', |
||||||
|
description: "Quoted field with \\r", |
||||||
|
expected: { |
||||||
|
data: [['A', 'B\rB', 'C']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,"B\nB",C', |
||||||
|
description: "Quoted field with \\n", |
||||||
|
expected: { |
||||||
|
data: [['A', 'B\nB', 'C']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,"B""B""B",C', |
||||||
|
description: "Quoted field with escaped quotes", |
||||||
|
expected: { |
||||||
|
data: [['A', 'B"B"B', 'C']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A,"""B""",C', |
||||||
|
description: "Quoted field with escaped quotes at boundaries", |
||||||
|
expected: { |
||||||
|
data: [['A', '"B"', 'C']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'A, "B" ,C', |
||||||
|
description: "Quoted field with whitespace around quotes", |
||||||
|
notes: "This is malformed input, but it should be parsed gracefully (with errors)", |
||||||
|
expected: { |
||||||
|
data: [['A', ' "B" ', 'C']], |
||||||
|
errors: [ |
||||||
|
{"type": "Quotes", "code": "UnexpectedQuotes", "message": "Unexpected quotes", "line": 1, "row": 0, "index": 3}, |
||||||
|
{"type": "Quotes", "code": "UnexpectedQuotes", "message": "Unexpected quotes", "line": 1, "row": 0, "index": 5} |
||||||
|
] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a\tb\tc\r\nd\te\tf', |
||||||
|
config: { delimiter: "\t" }, |
||||||
|
description: "Tab delimiter", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a|b|c\r\nd|e|f', |
||||||
|
config: { delimiter: "|" }, |
||||||
|
description: "Pipe delimiter", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a'+RECORD_SEP+'b'+RECORD_SEP+'c\r\nd'+RECORD_SEP+'e'+RECORD_SEP+'f', |
||||||
|
config: { delimiter: RECORD_SEP }, |
||||||
|
description: "ASCII 30 delimiter", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a'+UNIT_SEP+'b'+UNIT_SEP+'c\r\nd'+UNIT_SEP+'e'+UNIT_SEP+'f', |
||||||
|
config: { delimiter: UNIT_SEP }, |
||||||
|
description: "ASCII 31 delimiter", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c', |
||||||
|
config: { delimiter: "DELIM" }, |
||||||
|
description: "Bad delimiter", |
||||||
|
notes: "Should silently default to comma", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: '# Comment!\r\na,b,c', |
||||||
|
config: { comments: true }, |
||||||
|
description: "Commented line at beginning (comments: true)", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\n# Comment\r\nd,e,f', |
||||||
|
config: { comments: true }, |
||||||
|
description: "Commented line in middle (comments: true)", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\n# Comment', |
||||||
|
config: { comments: true }, |
||||||
|
description: "Commented line at end (comments: true)", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\n!Comment goes here\r\nd,e,f', |
||||||
|
config: { comments: '!' }, |
||||||
|
description: "Comment with non-default character (comments: '!')", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\n=N(Comment)\r\nd,e,f', |
||||||
|
config: { comments: '=N(' }, |
||||||
|
description: "Comment, but bad char specified (comments: \"=N(\")", |
||||||
|
notes: "Should silently disable comment parsing", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['=N(Comment)'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: '#commented line\r\n', |
||||||
|
config: { comments: true, delimiter: ',' }, |
||||||
|
description: "Input with only a commented line (comments: true)", |
||||||
|
expected: { |
||||||
|
data: [], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: '#commented line', |
||||||
|
config: { delimiter: ',' }, |
||||||
|
description: "Input with comment without comments enabled", |
||||||
|
expected: { |
||||||
|
data: [['#commented line']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a\r\n b\r\nc', |
||||||
|
config: { delimiter: ',' }, |
||||||
|
description: "Input without comments with line starting with whitespace", |
||||||
|
notes: "\" \" == false, but \" \" !== false, so === comparison is required", |
||||||
|
expected: { |
||||||
|
data: [['a'], [' b'], ['c']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a#b#c\r\n# Comment', |
||||||
|
config: { delimiter: '#', comments: '#' }, |
||||||
|
description: "Comment char same as delimiter", |
||||||
|
notes: "Comment parsing should automatically be silently disabled in this case", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['', ' Comment']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: '\r\na,b,c\r\nd,e,f', |
||||||
|
description: "Blank line at beginning", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\n\r\nd,e,f', |
||||||
|
description: "Blank line in middle", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\nd,e,f\r\n\r\n', |
||||||
|
description: "Blank lines at end", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\n \r\nd,e,f', |
||||||
|
description: "Blank line in middle with whitespace", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\n,e,f', |
||||||
|
description: "First field of a line is empty", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\n,e,f', |
||||||
|
description: "First field of a line is empty", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,,c\r\n,,', |
||||||
|
description: "Other fields are empty", |
||||||
|
expected: { |
||||||
|
data: [['a', '', 'c'], ['', '', '']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: '', |
||||||
|
description: "Empty input string", |
||||||
|
expected: { |
||||||
|
data: [], |
||||||
|
errors: [{ |
||||||
|
"type": "Delimiter", |
||||||
|
"code": "UndetectableDelimiter", |
||||||
|
"message": "Unable to auto-detect delimiting character; defaulted to comma" |
||||||
|
}] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: ',', |
||||||
|
description: "Input is just the delimiter (2 empty fields)", |
||||||
|
expected: { |
||||||
|
data: [['', '']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'Abc def', |
||||||
|
description: "Input is just a string (a single field)", |
||||||
|
expected: { |
||||||
|
data: [['Abc def']], |
||||||
|
errors: [ |
||||||
|
{ |
||||||
|
"type": "Delimiter", |
||||||
|
"code": "UndetectableDelimiter", |
||||||
|
"message": "Unable to auto-detect delimiting character; defaulted to comma" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\nd,e,f\r\ng,h,i', |
||||||
|
config: { preview: 0 }, |
||||||
|
description: "Preview 0 rows should default to parsing all", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\nd,e,f\r\ng,h,i', |
||||||
|
config: { preview: 1 }, |
||||||
|
description: "Preview 1 row", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\nd,e,f\r\ng,h,i', |
||||||
|
config: { preview: 2 }, |
||||||
|
description: "Preview 2 rows", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\nd,e,f\r\ng,h,i', |
||||||
|
config: { preview: 3 }, |
||||||
|
description: "Preview all (3) rows", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\nd,e,f\r\ng,h,i', |
||||||
|
config: { preview: 4 }, |
||||||
|
description: "Preview more rows than input has", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
input: 'a,b,c\r\nd,e,"f\r\nf",g,h,i', |
||||||
|
config: { preview: 2 }, |
||||||
|
description: "Preview should count rows, not lines", |
||||||
|
expected: { |
||||||
|
data: [['a', 'b', 'c'], ['d', 'e', 'f\r\nf', 'g', 'h', 'i']], |
||||||
|
errors: [] |
||||||
|
} |
||||||
|
} |
||||||
|
]; |
@ -0,0 +1,171 @@ |
|||||||
|
var passCount = 0; |
||||||
|
var failCount = 0; |
||||||
|
|
||||||
|
$(function() |
||||||
|
{ |
||||||
|
// First, wireup!
|
||||||
|
$('#results').on('click', 'td.rvl', function() |
||||||
|
{ |
||||||
|
var tr = $(this).closest('tr'); |
||||||
|
if (tr.hasClass('collapsed')) |
||||||
|
{ |
||||||
|
$('.revealer', tr).hide(); |
||||||
|
$('.hidden', tr).show(); |
||||||
|
$(this).html("-"); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
$('.revealer', tr).show(); |
||||||
|
$('.hidden', tr).hide(); |
||||||
|
$(this).html("+"); |
||||||
|
} |
||||||
|
tr.toggleClass('collapsed expanded'); |
||||||
|
}); |
||||||
|
|
||||||
|
$('#expand-all').click(function() |
||||||
|
{ |
||||||
|
$('.collapsed .rvl').click(); |
||||||
|
}); |
||||||
|
|
||||||
|
$('#collapse-all').click(function() |
||||||
|
{ |
||||||
|
$('.expanded .rvl').click(); |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Next, run tests and render results!
|
||||||
|
for (var i = 0; i < TESTS.length; i++) |
||||||
|
{ |
||||||
|
var test = TESTS[i]; |
||||||
|
var passed = runTest(test, i); |
||||||
|
if (passed) |
||||||
|
passCount++; |
||||||
|
else |
||||||
|
failCount++; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Finally, show the overall status.
|
||||||
|
if (failCount == 0) |
||||||
|
$('#status').addClass('status-pass').html("All <b>"+passCount+"</b> test"+(passCount == 1 ? "" : "s")+" passed"); |
||||||
|
else |
||||||
|
$('#status').addClass('status-fail').html("<b>"+failCount+"</b> test"+(failCount == 1 ? "" : "s")+" failed; <b>"+passCount+"</b> passed"); |
||||||
|
}); |
||||||
|
|
||||||
|
function runTest(test, num) |
||||||
|
{ |
||||||
|
var actual = Papa.parse(test.input, test.config); |
||||||
|
|
||||||
|
var results = compare(actual.data, actual.errors, test.expected); |
||||||
|
|
||||||
|
var testDescription = (test.description || ""); |
||||||
|
if (testDescription.length > 0) |
||||||
|
testDescription += '<br>'; |
||||||
|
if (test.notes) |
||||||
|
testDescription += '<span class="notes">' + test.notes + '</span>'; |
||||||
|
|
||||||
|
var tr = '<tr class="collapsed" id="test-'+num+'">' |
||||||
|
+ '<td class="rvl">+</td>' |
||||||
|
+ '<td>' + testDescription + '</td>' |
||||||
|
+ passOrFailTd(results.data) |
||||||
|
+ passOrFailTd(results.errors) |
||||||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">' + JSON.stringify(test.config, null, 2) + '</div></td>' |
||||||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">' + revealChars(test.input) + '</div></td>' |
||||||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">data: ' + JSON.stringify(test.expected.data, null, 4) + '\r\nerrors: ' + JSON.stringify(test.expected.errors, null, 4) + '</div></td>' |
||||||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">data: ' + JSON.stringify(actual.data, null, 4) + '\r\nerrors: ' + JSON.stringify(actual.errors, null, 4) + '</div></td>' |
||||||
|
+ '</tr>'; |
||||||
|
|
||||||
|
$('#results').append(tr); |
||||||
|
|
||||||
|
if (!results.data.passed || !results.errors.passed) |
||||||
|
$('#test-'+num+' td.rvl').click(); |
||||||
|
|
||||||
|
return results.data.passed && results.errors.passed |
||||||
|
} |
||||||
|
|
||||||
|
function compare(actualData, actualErrors, expected) |
||||||
|
{ |
||||||
|
var data = compareData(actualData, expected.data); |
||||||
|
var errors = compareErrors(actualErrors, expected.errors); |
||||||
|
return { |
||||||
|
data: data, |
||||||
|
errors: errors |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function compareData(actual, expected) |
||||||
|
{ |
||||||
|
var passed = true; |
||||||
|
|
||||||
|
if (actual.length != expected.length) |
||||||
|
passed = false; |
||||||
|
|
||||||
|
for (var row = 0; row < expected.length; row++) |
||||||
|
{ |
||||||
|
if (actual.length != expected.length) |
||||||
|
{ |
||||||
|
passed = false; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
for (var col = 0; col < expected[row].length; col++) |
||||||
|
{ |
||||||
|
if (actual[row].length != expected[row].length) |
||||||
|
{ |
||||||
|
passed = false; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
var expectedVal = expected[row][col]; |
||||||
|
var actualVal = actual[row][col]; |
||||||
|
|
||||||
|
if (actualVal !== expectedVal) |
||||||
|
{ |
||||||
|
passed = false; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// We pass back an object right now, even though it only contains
|
||||||
|
// one value, because we might add details to the test results later
|
||||||
|
// (same with compareErrors below)
|
||||||
|
return { |
||||||
|
passed: passed |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function compareErrors(actual, expected) |
||||||
|
{ |
||||||
|
var passed = JSON.stringify(actual) == JSON.stringify(expected); |
||||||
|
|
||||||
|
return { |
||||||
|
passed: passed |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function passOrFailTd(result) |
||||||
|
{ |
||||||
|
if (result.passed) |
||||||
|
return '<td class="ok">OK</td>'; |
||||||
|
else |
||||||
|
return '<td class="fail">FAIL</td>'; |
||||||
|
} |
||||||
|
|
||||||
|
function revealChars(txt) |
||||||
|
{ |
||||||
|
// Make spaces and tabs more obvious when glancing
|
||||||
|
txt = txt.replace(/( |\t)/ig, '<span class="whitespace-char">$1</span>'); |
||||||
|
|
||||||
|
txt = txt.replace(/(\r\n|\n\r|\r|\n)/ig, '<span class="whitespace-char special-char">$1</span>$1'); |
||||||
|
|
||||||
|
// Now make the line breaks within the spans actually appear on the page
|
||||||
|
txt = txt.replace(/">\r\n<\/span>/ig, '">\\r\\n</span>'); |
||||||
|
txt = txt.replace(/">\n\r<\/span>/ig, '">\\n\\r</span>'); |
||||||
|
txt = txt.replace(/">\r<\/span>/ig, '">\\r</span>'); |
||||||
|
txt = txt.replace(/">\n<\/span>/ig, '">\\n</span>'); |
||||||
|
|
||||||
|
return txt; |
||||||
|
} |
@ -0,0 +1,159 @@ |
|||||||
|
/* Eric Meyer's Reset CSS v2.0 */ |
||||||
|
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0} |
||||||
|
|
||||||
|
body { |
||||||
|
background: #F0F0F0; |
||||||
|
font: 14px 'Helvetica Neue', sans-serif; |
||||||
|
color: #333; |
||||||
|
padding: 30px 15px; |
||||||
|
} |
||||||
|
|
||||||
|
a { |
||||||
|
color: rgb(0, 142, 236); |
||||||
|
} |
||||||
|
|
||||||
|
a:hover { |
||||||
|
color: rgb(82, 186, 255); |
||||||
|
} |
||||||
|
|
||||||
|
b { |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
i { |
||||||
|
font-style: italic; |
||||||
|
} |
||||||
|
|
||||||
|
h1 { |
||||||
|
text-align: center; |
||||||
|
font-weight: bold; |
||||||
|
font-size: 62px; |
||||||
|
margin-bottom: 30px; |
||||||
|
} |
||||||
|
|
||||||
|
.status-pass, |
||||||
|
.status-fail { |
||||||
|
padding: 10px; |
||||||
|
margin-bottom: 30px; |
||||||
|
color: #FFF; |
||||||
|
text-align: center; |
||||||
|
text-transform: uppercase; |
||||||
|
font-size: 18px; |
||||||
|
letter-spacing: 1px; |
||||||
|
font-weight: 100; |
||||||
|
} |
||||||
|
|
||||||
|
.status-pass { |
||||||
|
background: rgb(3, 168, 3); |
||||||
|
} |
||||||
|
|
||||||
|
.status-fail { |
||||||
|
background: #BB0000; |
||||||
|
} |
||||||
|
|
||||||
|
table { |
||||||
|
width: 100%; |
||||||
|
border-collapse: collapse; |
||||||
|
margin-top: 5px; |
||||||
|
} |
||||||
|
|
||||||
|
table th, |
||||||
|
table td { |
||||||
|
padding: 5px; |
||||||
|
border: 1px solid #BBB; |
||||||
|
} |
||||||
|
|
||||||
|
table th { |
||||||
|
color: #000; |
||||||
|
background: #DDD; |
||||||
|
font-weight: bold; |
||||||
|
padding: 10px 5px; |
||||||
|
text-transform: uppercase; |
||||||
|
} |
||||||
|
|
||||||
|
table td { |
||||||
|
background: #FFF; |
||||||
|
color: #555; |
||||||
|
font-size: 14px; |
||||||
|
} |
||||||
|
|
||||||
|
td.ok, |
||||||
|
td.fail { |
||||||
|
text-transform: uppercase; |
||||||
|
font-weight: 300; |
||||||
|
vertical-align: middle; |
||||||
|
text-align: center; |
||||||
|
width: 80px; |
||||||
|
} |
||||||
|
|
||||||
|
td.ok { |
||||||
|
background: rgb(204, 250, 144); |
||||||
|
} |
||||||
|
|
||||||
|
td.fail { |
||||||
|
background: rgb(255, 192, 192); |
||||||
|
} |
||||||
|
|
||||||
|
td.rvl { |
||||||
|
background: #444; |
||||||
|
color: #999; |
||||||
|
vertical-align: middle; |
||||||
|
text-align: center; |
||||||
|
cursor: pointer; |
||||||
|
width: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
td.rvl:hover { |
||||||
|
color: #FFF; |
||||||
|
} |
||||||
|
|
||||||
|
tr.collapsed td.revealable { |
||||||
|
background: #ECECEC; |
||||||
|
vertical-align: middle; |
||||||
|
text-align: center; |
||||||
|
font-family: 'Helvetica Neue', sans-serif; |
||||||
|
text-transform: lowercase; |
||||||
|
color: #AAA; |
||||||
|
} |
||||||
|
|
||||||
|
tr.expanded .revealer { |
||||||
|
font-family: 'Helvetica Neue', sans-serif; |
||||||
|
text-transform: lowercase; |
||||||
|
font-size: 10px; |
||||||
|
background: #FFF; |
||||||
|
position: absolute; |
||||||
|
display: block; |
||||||
|
padding: 3px; |
||||||
|
top: -5px; |
||||||
|
right: -5px; |
||||||
|
} |
||||||
|
|
||||||
|
td .container { |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.notes { |
||||||
|
color: #888; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.pre { |
||||||
|
font-family: Menlo, Monaco, monospace; |
||||||
|
white-space: pre-wrap; |
||||||
|
} |
||||||
|
|
||||||
|
td.pre { |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.hidden { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
.special-char { |
||||||
|
color: #78B7E7; |
||||||
|
} |
||||||
|
|
||||||
|
.whitespace-char { |
||||||
|
background: #D5FCFA; |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Papa Parse Tests</title> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<link rel="stylesheet" href="tests.css"> |
||||||
|
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> |
||||||
|
<script src="../papaparse.js"></script> |
||||||
|
<script src="test-cases.js"></script> |
||||||
|
<script src="test-runner.js"></script> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<h1>Papa Parse Tests</h1> |
||||||
|
|
||||||
|
<div id="status"></div> |
||||||
|
|
||||||
|
<a href="javascript:" id="expand-all">Expand all</a> |
||||||
|
· |
||||||
|
<a href="javascript:" id="collapse-all">Collapse all</a> |
||||||
|
<br> |
||||||
|
|
||||||
|
<table id="results"> |
||||||
|
<tr> |
||||||
|
<th colspan="2">Test Case</th> |
||||||
|
<th>Data</th> |
||||||
|
<th>Errors</th> |
||||||
|
<th>Config</th> |
||||||
|
<th>Input</th> |
||||||
|
<th>Expected</th> |
||||||
|
<th>Actual</th> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
</body> |
||||||
|
</html> |
Loading…
Reference in new issue