Comparing version 0.1.0 to 0.2.0
881
babyparse.js
/* | ||
Baby Parse | ||
v0.1.0 | ||
v0.2.0 | ||
https://github.com/Rich-Harris/BabyParse | ||
based on: | ||
Papa Parse | ||
v2.0.1 | ||
https://github.com/mholt/jquery.parse | ||
based on Papa Parse v3.0.1 | ||
https://github.com/mholt/PapaParse | ||
*/ | ||
@@ -16,108 +13,315 @@ | ||
function Parser(config) | ||
// A configuration object from which to draw default settings | ||
var DEFAULTS = { | ||
delimiter: "", // empty: auto-detect | ||
header: false, | ||
dynamicTyping: false, | ||
preview: 0, | ||
step: undefined, | ||
comments: false, | ||
complete: undefined, | ||
keepEmptyRows: false | ||
}; | ||
var Baby = {}; | ||
Baby.parse = CsvToJson; | ||
Baby.unparse = JsonToCsv; | ||
Baby.RECORD_SEP = String.fromCharCode(30); | ||
Baby.UNIT_SEP = String.fromCharCode(31); | ||
Baby.BYTE_ORDER_MARK = "\ufeff"; | ||
Baby.BAD_DELIMITERS = ["\r", "\n", "\"", Baby.BYTE_ORDER_MARK]; | ||
function CsvToJson(_input, _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 | ||
}; | ||
var _regex = { | ||
floats: /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i, | ||
empty: /^\s*$/ | ||
}; | ||
var config = copyAndValidateConfig(_config); | ||
var ph = new ParserHandle(config); | ||
var results = ph.parse(_input); | ||
if (isFunction(config.complete)) | ||
config.complete(results); | ||
return results; | ||
} | ||
config = validConfig(config); | ||
_config = { | ||
delimiter: config.delimiter, | ||
header: config.header, | ||
dynamicTyping: config.dynamicTyping, | ||
preview: config.preview, | ||
step: config.step | ||
}; | ||
this.parse = function(input) | ||
function JsonToCsv(_input, _config) | ||
{ | ||
var _output = ""; | ||
var _fields = []; | ||
// Default configuration | ||
var _quotes = false; // whether to surround every datum with quotes | ||
var _delimiter = ","; // delimiting character | ||
var _newline = "\r\n"; // newline character(s) | ||
unpackConfig(); | ||
if (typeof _input === 'string') | ||
_input = JSON.parse(_input); | ||
if (_input instanceof Array) | ||
{ | ||
if (typeof input !== 'string') | ||
return returnable(); | ||
if (!_input.length || _input[0] instanceof Array) | ||
return serialize(null, _input); | ||
else if (typeof _input[0] === 'object') | ||
return serialize(objectKeys(_input[0]), _input); | ||
} | ||
else if (typeof _input === 'object') | ||
{ | ||
if (typeof _input.data === 'string') | ||
_input.data = JSON.parse(_input.data); | ||
reset(input); | ||
if (_input.data instanceof Array) | ||
{ | ||
if (!_input.fields) | ||
_input.fields = _input.data[0] instanceof Array | ||
? _input.fields | ||
: objectKeys(_input.data[0]); | ||
if (!_config.delimiter && !guessDelimiter(input)) | ||
if (!(_input.data[0] instanceof Array) && typeof _input.data[0] !== 'object') | ||
_input.data = [_input.data]; // handles input like [1,2,3] or ["asdf"] | ||
} | ||
return serialize(_input.fields || [], _input.data || []); | ||
} | ||
// Default (any valid paths should return before this) | ||
throw "exception: Unable to serialize unrecognized input"; | ||
function unpackConfig() | ||
{ | ||
if (typeof _config !== 'object') | ||
return; | ||
if (typeof _config.delimiter === 'string' | ||
&& _config.delimiter.length == 1 | ||
&& Baby.BAD_DELIMITERS.indexOf(_config.delimiter) == -1) | ||
{ | ||
addError("Delimiter", "UndetectableDelimiter", "Unable to auto-detect delimiting character; defaulted to comma", "config"); | ||
_config.delimiter = ","; | ||
_delimiter = _config.delimiter; | ||
} | ||
for (_state.i = 0; _state.i < _input.length; _state.i++) | ||
if (typeof _config.quotes === 'boolean' | ||
|| _config.quotes instanceof Array) | ||
_quotes = _config.quotes; | ||
if (typeof _config.newline === 'string') | ||
_newline = _config.newline; | ||
} | ||
// Turns an object's keys into an array | ||
function objectKeys(obj) | ||
{ | ||
if (typeof obj !== 'object') | ||
return []; | ||
var keys = []; | ||
for (var key in obj) | ||
keys.push(key); | ||
return keys; | ||
} | ||
// The double for loop that iterates the data and writes out a CSV string including header row | ||
function serialize(fields, data) | ||
{ | ||
var csv = ""; | ||
if (typeof fields === 'string') | ||
fields = JSON.parse(fields); | ||
if (typeof data === 'string') | ||
data = JSON.parse(data); | ||
var hasHeader = fields instanceof Array && fields.length > 0; | ||
var dataKeyedByField = !(data[0] instanceof Array); | ||
// If there a header row, write it first | ||
if (hasHeader) | ||
{ | ||
if (_abort || (_config.preview > 0 && _state.lineNum > _config.preview)) | ||
break; | ||
for (var i = 0; i < fields.length; i++) | ||
{ | ||
if (i > 0) | ||
csv += _delimiter; | ||
csv += safe(fields[i], i); | ||
} | ||
if (data.length > 0) | ||
csv += _newline; | ||
} | ||
_state.ch = _input[_state.i]; | ||
_state.line += _state.ch; | ||
// Then write out the data | ||
for (var row = 0; row < data.length; row++) | ||
{ | ||
var maxCol = hasHeader ? fields.length : data[row].length; | ||
if (_state.ch == '"') | ||
handleQuote(); | ||
else if (_state.inQuotes) | ||
inQuotes(); | ||
for (var col = 0; col < maxCol; col++) | ||
{ | ||
if (col > 0) | ||
csv += _delimiter; | ||
var colIdx = hasHeader && dataKeyedByField ? fields[col] : col; | ||
csv += safe(data[row][colIdx], col); | ||
} | ||
if (row < data.length - 1) | ||
csv += _newline; | ||
} | ||
return csv; | ||
} | ||
// Encloses a value around quotes if needed (makes a value safe for CSV insertion) | ||
function safe(str, col) | ||
{ | ||
if (typeof str === "undefined") | ||
return ""; | ||
str = str.toString().replace(/"/g, '""'); | ||
var needsQuotes = (typeof _quotes === 'boolean' && _quotes) | ||
|| (_quotes instanceof Array && _quotes[col]) | ||
|| hasAny(str, Baby.BAD_DELIMITERS) | ||
|| str.indexOf(_delimiter) > -1 | ||
|| str.charAt(0) == ' ' | ||
|| str.charAt(str.length - 1) == ' '; | ||
return needsQuotes ? '"' + str + '"' : str; | ||
} | ||
function hasAny(str, substrings) | ||
{ | ||
for (var i = 0; i < substrings.length; i++) | ||
if (str.indexOf(substrings[i]) > -1) | ||
return true; | ||
return false; | ||
} | ||
} | ||
// Use one ParserHandle per entire CSV file or string | ||
function ParserHandle(_config) | ||
{ | ||
// One goal is to minimize the use of regular expressions... | ||
var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i; | ||
var _delimiterError; // Temporary state between delimiter detection and processing results | ||
var _fields = []; // Fields are from the header row of the input, if there is one | ||
var _results = { // The last results returned from the parser | ||
data: [], | ||
errors: [], | ||
meta: {} | ||
}; | ||
_config = copy(_config); | ||
this.parse = function(input) | ||
{ | ||
_delimiterError = false; | ||
if (!_config.delimiter) | ||
{ | ||
var delimGuess = guessDelimiter(input); | ||
if (delimGuess.successful) | ||
_config.delimiter = delimGuess.bestDelimiter; | ||
else | ||
notInQuotes(); | ||
{ | ||
_delimiterError = true; // add error after parsing (otherwise it would be overwritten) | ||
_config.delimiter = ","; | ||
} | ||
_results.meta.delimiter = _config.delimiter; | ||
} | ||
if (_abort) | ||
addError("Abort", "ParseAbort", "Parsing was aborted by the user's step function", "abort"); | ||
else | ||
if (isFunction(_config.step)) | ||
{ | ||
endRow(); // End of input is also end of the last row | ||
if (_state.inQuotes) | ||
addError("Quotes", "MissingQuotes", "Unescaped or mismatched quotes"); | ||
var userStep = _config.step; | ||
_config.step = function(results, parser) | ||
{ | ||
_results = results; | ||
if (needsHeaderRow()) | ||
processResults(); | ||
else | ||
userStep(processResults(), parser); | ||
}; | ||
} | ||
return returnable(); | ||
_results = new Parser(_config).parse(input); | ||
return processResults(); | ||
}; | ||
this.getOptions = function() | ||
function processResults() | ||
{ | ||
return { | ||
delimiter: _config.delimiter, | ||
header: _config.header, | ||
dynamicTyping: _config.dynamicTyping, | ||
preview: _config.preview, | ||
step: _config.step | ||
}; | ||
}; | ||
if (_results && _delimiterError) | ||
{ | ||
addError("Delimiter", "UndetectableDelimiter", "Unable to auto-detect delimiting character; defaulted to comma"); | ||
_delimiterError = false; | ||
} | ||
function validConfig(config) | ||
if (needsHeaderRow()) | ||
fillHeaderFields(); | ||
return applyHeaderAndDynamicTyping(); | ||
} | ||
function needsHeaderRow() | ||
{ | ||
if (typeof config !== 'object') | ||
config = {}; | ||
return _config.header && _fields.length == 0; | ||
} | ||
if (typeof config.delimiter !== 'string' | ||
|| config.delimiter.length != 1) | ||
config.delimiter = _defaultConfig.delimiter; | ||
function fillHeaderFields() | ||
{ | ||
if (!_results) | ||
return; | ||
for (var i = 0; needsHeaderRow() && i < _results.data.length; i++) | ||
for (var j = 0; j < _results.data[i].length; j++) | ||
_fields.push(_results.data[i][j]); | ||
_results.data.splice(0, 1); | ||
} | ||
if (config.deimiter == '"' || config.delimiter == "\n") | ||
config.delimiter = _defaultConfig.delimiter; | ||
function applyHeaderAndDynamicTyping() | ||
{ | ||
if (!_results || (!_config.header && !_config.dynamicTyping)) | ||
return _results; | ||
if (typeof config.header !== 'boolean') | ||
config.header = _defaultConfig.header; | ||
for (var i = 0; i < _results.data.length; i++) | ||
{ | ||
var row = {}; | ||
for (var j = 0; j < _results.data[i].length; j++) | ||
{ | ||
if (_config.dynamicTyping) | ||
{ | ||
var value = _results.data[i][j]; | ||
if (value == "true") | ||
_results.data[i][j] = true; | ||
else if (value == "false") | ||
_results.data[i][j] = false; | ||
else | ||
_results.data[i][j] = tryParseFloat(value); | ||
} | ||
if (typeof config.dynamicTyping !== 'boolean') | ||
config.dynamicTyping = _defaultConfig.dynamicTyping; | ||
if (_config.header) | ||
{ | ||
if (j >= _fields.length) | ||
{ | ||
if (!row["__parsed_extra"]) | ||
row["__parsed_extra"] = []; | ||
row["__parsed_extra"].push(_results.data[i][j]); | ||
} | ||
row[_fields[j]] = _results.data[i][j]; | ||
} | ||
} | ||
if (typeof config.preview !== 'number') | ||
config.preview = _defaultConfig.preview; | ||
if (_config.header) | ||
{ | ||
_results.data[i] = row; | ||
if (j > _fields.length) | ||
addError("FieldMismatch", "TooManyFields", "Too many fields: expected " + _fields.length + " fields but parsed " + j, i); | ||
else if (j < _fields.length) | ||
addError("FieldMismatch", "TooFewFields", "Too few fields: expected " + _fields.length + " fields but parsed " + j, i); | ||
} | ||
} | ||
if (typeof config.step !== 'function') | ||
config.step = _defaultConfig.step; | ||
if (_config.header && _results.meta); | ||
_results.meta.fields = _fields; | ||
return config; | ||
return _results; | ||
} | ||
@@ -127,20 +331,19 @@ | ||
{ | ||
var delimiters = [",", "\t", "|", ";"]; | ||
var delimChoices = [",", "\t", "|", ";", Baby.RECORD_SEP, Baby.UNIT_SEP]; | ||
var bestDelim, bestDelta, fieldCountPrevRow; | ||
for (var i in delimiters) | ||
for (var i = 0; i < delimChoices.length; i++) | ||
{ | ||
var delim = delimiters[i]; | ||
var delim = delimChoices[i]; | ||
var delta = 0, avgFieldCount = 0; | ||
fieldCountPrevRow = undefined; | ||
var preview = new Parser({ | ||
delimiter: delim, | ||
header: false, | ||
dynamicTyping: false, | ||
preview: 10 | ||
}).parse(input); | ||
for (var j in preview.results) | ||
for (var j = 0; j < preview.data.length; j++) | ||
{ | ||
var fieldCount = preview.results[j].length; | ||
var fieldCount = preview.data[j].length; | ||
avgFieldCount += fieldCount; | ||
@@ -160,3 +363,3 @@ | ||
avgFieldCount /= preview.results.length; | ||
avgFieldCount /= preview.data.length; | ||
@@ -173,114 +376,198 @@ if ((typeof bestDelta === 'undefined' || delta < bestDelta) | ||
return !!bestDelim; | ||
return { | ||
successful: !!bestDelim, | ||
bestDelimiter: bestDelim | ||
} | ||
} | ||
function handleQuote() | ||
function tryParseFloat(val) | ||
{ | ||
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] == '"'; | ||
var isNumber = FLOAT.test(val); | ||
return isNumber ? parseFloat(val) : val; | ||
} | ||
if (_state.inQuotes && escaped) | ||
function addError(type, code, msg, row) | ||
{ | ||
_results.errors.push({ | ||
type: type, | ||
code: code, | ||
message: msg, | ||
row: row | ||
}); | ||
} | ||
} | ||
function Parser(config) | ||
{ | ||
var self = this; | ||
var EMPTY = /^\s*$/; | ||
var _input; // The input text being parsed | ||
var _delimiter; // The delimiting character | ||
var _comments; // Comment character (default '#') or boolean | ||
var _step; // The step (streaming) function | ||
var _callback; // The callback to invoke when finished | ||
var _preview; // Maximum number of lines (not rows) to parse | ||
var _ch; // Current character | ||
var _i; // Current character's positional index | ||
var _inQuotes; // Whether in quotes or not | ||
var _lineNum; // Current line number (1-based indexing) | ||
var _data; // Parsed data (results) | ||
var _errors; // Parse errors | ||
var _rowIdx; // Current row index within results (0-based) | ||
var _colIdx; // Current col index within result row (0-based) | ||
var _runningRowIdx; // Cumulative row index, used by the preview feature | ||
var _aborted = false; // Abort flag | ||
var _paused = false; // Pause flag | ||
// Unpack the config object | ||
config = config || {}; | ||
_delimiter = config.delimiter; | ||
_comments = config.comments; | ||
_step = config.step; | ||
_preview = config.preview; | ||
// Delimiter integrity check | ||
if (typeof _delimiter !== 'string' | ||
|| _delimiter.length != 1 | ||
|| Baby.BAD_DELIMITERS.indexOf(_delimiter) > -1) | ||
_delimiter = ","; | ||
// Comment character integrity check | ||
if (_comments === true) | ||
_comments = "#"; | ||
else if (typeof _comments !== 'string' | ||
|| _comments.length != 1 | ||
|| Baby.BAD_DELIMITERS.indexOf(_comments) > -1 | ||
|| _comments == _delimiter) | ||
_comments = false; | ||
this.parse = function(input) | ||
{ | ||
if (typeof input !== 'string') | ||
throw "Input must be a string"; | ||
reset(input); | ||
return parserLoop(); | ||
}; | ||
this.abort = function() | ||
{ | ||
_aborted = true; | ||
}; | ||
function parserLoop() | ||
{ | ||
while (_i < _input.length) | ||
{ | ||
_state.fieldVal += '"'; | ||
_state.i++; | ||
if (_aborted) break; | ||
if (_preview > 0 && _runningRowIdx >= _preview) break; | ||
if (_paused) return finishParsing(); | ||
if (_ch == '"') | ||
parseQuotes(); | ||
else if (_inQuotes) | ||
parseInQuotes(); | ||
else | ||
parseNotInQuotes(); | ||
nextChar(); | ||
} | ||
else if (delimBefore || delimAfter) | ||
_state.inQuotes = !_state.inQuotes; | ||
else | ||
addError("Quotes", "UnexpectedQuotes", "Unexpected quotes"); | ||
return finishParsing(); | ||
} | ||
function inQuotes() | ||
function nextChar() | ||
{ | ||
appendCharToField(); | ||
_i++; | ||
_ch = _input[_i]; | ||
} | ||
function appendCharToField() | ||
function finishParsing() | ||
{ | ||
_state.fieldVal += _state.ch; | ||
if (_aborted) | ||
addError("Abort", "ParseAbort", "Parsing was aborted by the user's step function"); | ||
if (_inQuotes) | ||
addError("Quotes", "MissingQuotes", "Unescaped or mismatched quotes"); | ||
endRow(); // End of input is also end of the last row | ||
if (!isFunction(_step)) | ||
return returnable(); | ||
} | ||
function notInQuotes() | ||
function parseQuotes() | ||
{ | ||
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")) | ||
if (quotesOnBoundary() && !quotesEscaped()) | ||
_inQuotes = !_inQuotes; | ||
else | ||
{ | ||
newRow(); | ||
_state.i++; | ||
saveChar(); | ||
if (_inQuotes && quotesEscaped()) | ||
_i++ | ||
else | ||
addError("Quotes", "UnexpectedQuotes", "Unexpected quotes"); | ||
} | ||
else if (_state.ch == "\n" || _state.ch == "\r") | ||
newRow(); | ||
else | ||
appendCharToField(); | ||
} | ||
function isBoundary(i) | ||
function parseInQuotes() | ||
{ | ||
if (i >= _input.length) | ||
return false; | ||
if (twoCharLineBreak(_i) || oneCharLineBreak(_i)) | ||
_lineNum++; | ||
saveChar(); | ||
} | ||
var ch = _input[i]; | ||
if (ch == _config.delimiter | ||
|| ch == "\n" | ||
|| (ch == "\r" && i < _input.length - 1 && _input[i+1] == "\n")) | ||
return true; | ||
function parseNotInQuotes() | ||
{ | ||
if (_ch == _delimiter) | ||
newField(); | ||
else if (twoCharLineBreak(_i)) | ||
{ | ||
newRow(); | ||
nextChar(); | ||
} | ||
else if (oneCharLineBreak(_i)) | ||
newRow(); | ||
else if (isCommentStart()) | ||
skipLine(); | ||
else | ||
return false; | ||
saveChar(); | ||
} | ||
function isLineEnding(i) | ||
function isCommentStart() | ||
{ | ||
if (i >= _input.length) | ||
if (!_comments) | ||
return false; | ||
if (i < _input.length - 1) | ||
return _input[i] == "\n" || (_input[i] == "\r" && _input[i+1] == "\n"); | ||
else | ||
return _input[i] == "\n"; | ||
var firstCharOfLine = _i == 0 | ||
|| oneCharLineBreak(_i-1) | ||
|| twoCharLineBreak(_i-2); | ||
return firstCharOfLine && _input[_i] === _comments; | ||
} | ||
function saveValue() | ||
function skipLine() | ||
{ | ||
if (_config.header) | ||
while (!twoCharLineBreak(_i) | ||
&& !oneCharLineBreak(_i) | ||
&& _i < _input.length) | ||
{ | ||
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); | ||
} | ||
} | ||
nextChar(); | ||
} | ||
else | ||
{ | ||
if (_config.dynamicTyping) | ||
_state.fieldVal = tryParseFloat(_state.fieldVal); | ||
_state.parsed[_state.parsed.length - 1].push(_state.fieldVal); | ||
} | ||
} | ||
_state.fieldVal = ""; | ||
_state.field ++; | ||
function saveChar() | ||
{ | ||
_data[_rowIdx][_colIdx] += _ch; | ||
} | ||
function newField() | ||
{ | ||
_data[_rowIdx].push(""); | ||
_colIdx = _data[_rowIdx].length - 1; | ||
} | ||
function newRow() | ||
@@ -290,179 +577,169 @@ { | ||
if (streaming()) | ||
_lineNum++; | ||
_runningRowIdx++; | ||
_data.push([]); | ||
_rowIdx = _data.length - 1; | ||
newField(); | ||
} | ||
function endRow() | ||
{ | ||
trimEmptyLastRow(); | ||
if (isFunction(_step)) | ||
{ | ||
_state.errors = {}; | ||
_state.errors.length = 0; | ||
if (_data[_rowIdx]) | ||
_step(returnable(), self); | ||
clearErrorsAndData(); | ||
} | ||
} | ||
if (_config.header && _state.lineNum > 0) | ||
function trimEmptyLastRow() | ||
{ | ||
if (_data[_rowIdx].length == 1 && EMPTY.test(_data[_rowIdx][0])) | ||
{ | ||
if (streaming()) | ||
_state.parsed.rows = [ {} ]; | ||
if (config.keepEmptyRows) | ||
_data[_rowIdx].splice(0, 1); // leave row, but no fields | ||
else | ||
_state.parsed.rows.push({}); | ||
_data.splice(_rowIdx, 1); // cut out row entirely | ||
_rowIdx = _data.length - 1; | ||
} | ||
else | ||
{ | ||
if (streaming()) | ||
_state.parsed = [ [] ]; | ||
else | ||
_state.parsed.push([]); | ||
} | ||
_state.lineNum ++; | ||
_state.line = ""; | ||
_state.field = 0; | ||
} | ||
function endRow() | ||
function twoCharLineBreak(i) | ||
{ | ||
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; | ||
} | ||
return i < _input.length - 1 && | ||
((_input[i] == "\r" && _input[i+1] == "\n") | ||
|| (_input[i] == "\n" && _input[i+1] == "\r")) | ||
} | ||
function streaming() | ||
function oneCharLineBreak(i) | ||
{ | ||
return typeof _config.step === 'function'; | ||
return _input[i] == "\r" || _input[i] == "\n"; | ||
} | ||
function tryParseFloat(num) | ||
function quotesEscaped() | ||
{ | ||
var isNumber = _regex.floats.test(num); | ||
return isNumber ? parseFloat(num) : num; | ||
// Quotes as data cannot be on boundary, for example: ,"", are not escaped quotes | ||
return !quotesOnBoundary() && _i < _input.length - 1 && _input[_i+1] == '"'; | ||
} | ||
function trimEmptyLine() | ||
function quotesOnBoundary() | ||
{ | ||
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; | ||
return (!_inQuotes && isBoundary(_i-1)) || isBoundary(_i+1); | ||
} | ||
function inspectFieldCount() | ||
function isBoundary(i) | ||
{ | ||
if (!_config.header) | ||
return true; | ||
if (typeof i != 'number') | ||
i = _i; | ||
if (_state.parsed.rows.length == 0) | ||
return true; | ||
var ch = _input[i]; | ||
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; | ||
return (i <= -1 || i >= _input.length) | ||
|| (ch == _delimiter | ||
|| ch == "\r" | ||
|| ch == "\n"); | ||
} | ||
function addError(type, code, msg, errKey) | ||
function addError(type, code, msg) | ||
{ | ||
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({ | ||
_errors.push({ | ||
type: type, | ||
code: code, | ||
message: msg, | ||
line: _state.lineNum, | ||
row: row, | ||
index: _state.i + _chunkOffset | ||
line: _lineNum, | ||
row: _rowIdx, | ||
index: _i | ||
}); | ||
_state.errors.length ++; | ||
return false; | ||
} | ||
function returnable() | ||
function reset(input) | ||
{ | ||
return { | ||
results: _state.parsed, | ||
errors: _state.errors | ||
}; | ||
_input = input; | ||
_inQuotes = false; | ||
_i = 0, _runningRowIdx = 0, _lineNum = 1; | ||
clearErrorsAndData(); | ||
_data = [ [""] ]; // starting parsing requires an empty field | ||
_ch = _input[_i]; | ||
} | ||
function reset(input) | ||
function clearErrorsAndData() | ||
{ | ||
_invocations++; | ||
if (_invocations > 1 && streaming()) | ||
_chunkOffset += input.length; | ||
_state = freshState(); | ||
_input = input; | ||
_data = []; | ||
_errors = []; | ||
_rowIdx = 0; | ||
_colIdx = 0; | ||
} | ||
function freshState() | ||
function returnable() | ||
{ | ||
// 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 } | ||
data: _data, | ||
errors: _errors, | ||
meta: { | ||
lines: _lineNum, | ||
delimiter: _delimiter, | ||
aborted: _aborted | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
// Replaces bad config values with good, default ones | ||
function copyAndValidateConfig(origConfig) | ||
{ | ||
if (typeof origConfig !== 'object') | ||
origConfig = {}; | ||
var config = copy(origConfig); | ||
if (typeof config.delimiter !== 'string' | ||
|| config.delimiter.length != 1 | ||
|| Baby.BAD_DELIMITERS.indexOf(config.delimiter) > -1) | ||
config.delimiter = DEFAULTS.delimiter; | ||
if (typeof config.header !== 'boolean') | ||
config.header = DEFAULTS.header; | ||
if (typeof config.dynamicTyping !== 'boolean') | ||
config.dynamicTyping = DEFAULTS.dynamicTyping; | ||
if (typeof config.preview !== 'number') | ||
config.preview = DEFAULTS.preview; | ||
if (typeof config.step !== 'function') | ||
config.step = DEFAULTS.step; | ||
if (typeof config.complete !== 'function') | ||
config.complete = DEFAULTS.complete; | ||
if (typeof config.keepEmptyRows !== 'boolean') | ||
config.keepEmptyRows = DEFAULTS.keepEmptyRows; | ||
return config; | ||
} | ||
function copy(obj) | ||
{ | ||
if (typeof obj !== 'object') | ||
return obj; | ||
var cpy = obj instanceof Array ? [] : {}; | ||
for (var key in obj) | ||
cpy[key] = copy(obj[key]); | ||
return cpy; | ||
} | ||
function isFunction(func) | ||
{ | ||
return typeof func === 'function'; | ||
} | ||
// export to Node... | ||
if ( typeof module !== 'undefined' && module.exports ) { | ||
module.exports = Parser; | ||
module.exports = Baby; | ||
} | ||
@@ -472,3 +749,3 @@ | ||
else if ( typeof define === 'function' && define.amd ) { | ||
define( function () { return Parser; }); | ||
define( function () { return Baby; }); | ||
} | ||
@@ -478,5 +755,7 @@ | ||
else { | ||
global.Parser = Parser; | ||
global.Baby = Baby; | ||
} | ||
}( typeof window !== 'undefined' ? window : this )); |
{ | ||
"name": "babyparse", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"title": "BabyParse", | ||
@@ -31,4 +31,3 @@ "description": "Fast and reliable CSV parser based on PapaParse", | ||
} | ||
], | ||
"main": "babyparse.js" | ||
] | ||
} |
@@ -16,10 +16,7 @@ BabyParse | ||
```js | ||
// see http://papaparse.com/docs.html for config options | ||
parser = new Parser( config ); | ||
// pass in the contents of a csv file | ||
parsed = parser.parse( csv ); | ||
parsed = Baby.parse( csv ); | ||
// voila | ||
rows = parser.results.rows; | ||
rows = parsed.data; | ||
``` | ||
@@ -26,0 +23,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
46707
10
1639
28
1