csv-for-you
Version:
CSV parser. Supports all nodeJS versions.
235 lines (208 loc) • 9.78 kB
JavaScript
const fs = require('fs');
const NoFileError = require('../errors/NoFileError');
module.exports.parse = (
filePath,
tempOptions = { arraySeparator: ';', objectSeparator: ';', lineAsArray: true, fileAsArray: true, returnAsString: [], innerCallbacks: true },
tempCallbacks = { lineCallback: '', fileCallback: '', arrayCallback: '', objectCallback: '', numberCallback: '', stringCallback: '' },
) => {
let remaining = '', fileData, props, lineCounter = 1;
const callbacks = updateObjects({ lineCallback: '', fileCallback: '', arrayCallback: '', objectCallback: '', numberCallback: '', stringCallback: '' }, tempCallbacks);
const options = updateObjects({ arraySeparator: ';', objectSeparator: ';', lineAsArray: true, fileAsArray: true, returnAsString: [], innerCallbacks: true }, tempOptions, true);
if (options.fileAsArray) fileData = []
else fileData = {};
const hasLetters = (variable) => /[a-zA-Z]/.test(variable);
const hasNumbersOnly = (variable, ignore = '') => {
if (variable !== '') {
const numbers = "1234567890. ";
for (let char of variable) {
if (!numbers.includes(char) && !ignore.includes(char)) {
return false;
}
}
return true;
}
return false;
};
const parseObject = (str) => {
str = str.slice(1, -1);
let obj = {};
let value = '';
let stack = [];
let currentObj = obj;
let currentKey = '';
let isReadingKey = true;
const findClosingBracket = (str, startIndex, openBracket, closeBracket) => {
let depth = 0;
for (let i = startIndex; i < str.length; i++) {
if (str[i] === openBracket) depth++;
if (str[i] === closeBracket) depth--;
if (depth === 0) return i;
}
return -1; // If no matching bracket is found
};
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (char === '{') {
stack.push(currentObj);
currentObj[currentKey.trim()] = {};
currentObj = currentObj[currentKey.trim()];
currentKey = '';
isReadingKey = true;
} else if (char === '}') {
if (currentKey) {
currentObj[currentKey.trim()] = hasNumbersOnly(value) ? Number(value.trim()) : value.trim();
currentKey = '';
value = '';
}
currentObj = stack.pop();
} else if (char === '[') {
let arrayEndIndex = findClosingBracket(str, i, '[', ']');
let arrayString = str.slice(i, arrayEndIndex + 1);
currentObj[currentKey.trim()] = parseArray(arrayString);
i = arrayEndIndex;
currentKey = '';
value = '';
isReadingKey = true;
} else if (char === ':') {
isReadingKey = false;
} else if (char === ';') {
if (currentKey) {
currentObj[currentKey.trim()] = hasNumbersOnly(value) ? Number(value.trim()) : value.trim();
currentKey = '';
value = '';
}
isReadingKey = true;
} else {
if (isReadingKey) {
currentKey += char;
} else {
value += char;
}
}
}
if (currentKey) {
currentObj[currentKey.trim()] = hasNumbersOnly(value) ? Number(value.trim()) : value.trim();
}
return obj;
};
const parseArray = (str) => {
str = str.slice(1, -1);
const result = [];
let temp = '';
let nestedLevel = 0;
for (let char of str) {
if (char === '[' || char === '{') {
nestedLevel++;
temp += char;
} else if (char === ']' || char === '}') {
nestedLevel--;
temp += char;
} else if (char === ';' && nestedLevel === 0) {
result.push(temp);
temp = '';
} else {
temp += char;
}
}
if (temp) result.push(temp);
return result.map((item) => {
if (item.startsWith('[') && item.endsWith(']')) return parseArray(item)
else if (item.startsWith('{') && item.endsWith('}')) return parseObject(item)
else if (hasNumbersOnly(item)) return Number(item)
else if (hasLetters(item)) return item
});
};
const processLine = (line) => {
let temp = options.lineAsArray ? [] : {};
for (let i = 0; i < line.length; i++) {
const prop = props[i];
const value = line[i];
if (options.returnAsString.includes(prop)) {
temp[options.lineAsArray ? temp.length : prop] = value;
} else if (value.startsWith('{') && value.endsWith('}')) {
if (typeof callbacks.objectCallback === 'function' && options.innerCallbacks)
temp[options.lineAsArray ? temp.length : prop] = callbacks.objectCallback(parseObject(value));
else temp[options.lineAsArray ? temp.length : prop] = parseObject(value);
} else if (value.startsWith('[') && value.endsWith(']')) {
if (typeof callbacks.arrayCallback === 'function')
temp[options.lineAsArray ? temp.length : prop] = callbacks.arrayCallback(parseArray(value))
else temp[options.lineAsArray ? temp.length : prop] = parseArray(value);
} else if (hasLetters(value)) {
if (typeof callbacks.stringCallback === 'function')
temp[options.lineAsArray ? temp.length : prop] = callbacks.stringCallback(value);
else temp[options.lineAsArray ? temp.length : prop] = value;
} else if (hasNumbersOnly(value)) {
if (typeof callbacks.numberCallback === 'function')
temp[options.lineAsArray ? temp.length : prop] = callbacks.numberCallback(Number(value));
else temp[options.lineAsArray ? temp.length : prop] = Number(value);
} else if (value === '') {
temp[options.lineAsArray ? temp.length : prop] = null;
}
}
return temp;
};
function updateObjects(options, update, isOptions = false) {
if (isOptions && Object.keys(options).indexOf('innerCallbacks') !== -1) {
function isAnyCallbacksNotLine() {
const keys = Object.keys(callbacks);
for (let i = 0; i < keys.length; i++) {
if (keys[i] !== 'fileCallback' && keys[i] !== 'lineCallback') {
if (typeof callbacks[keys[i]] === 'function') {
return true;
}
}
}
return false;
};
if (typeof callbacks.lineCallback === 'function' || typeof callbacks.fileCallback === 'function') {
if (isAnyCallbacksNotLine() && update.innerCallbacks) {
options['innerCallbacks'] = update['innerCallbacks'];
}
}
}
if (Object.keys(update).length > 0) {
const keys = Object.keys(update);
for (let key of keys) {
if (key !== 'innerCallbacks' && options[key] !== update[key]) {
options[key] = update[key];
}
}
}
return options;
};
return new Promise((resolve, reject) => {
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) return reject(new NoFileError(`Error reading file: ${filePath}\nError: ${err}`));
const fileStream = fs.createReadStream(filePath, 'utf8');
fileStream.on('data', (chunk) => {
remaining += chunk;
props = remaining.substring(0, remaining.indexOf('\n')).replace('\r', '').split(',');
let last = remaining.indexOf('\n') + 1;
let index = remaining.indexOf('\n', last);
while (index > -1) {
const line = remaining.substring(last, index).replace('\r', '').split(',');
last = index + 1;
let temp = typeof callbacks.lineCallback === 'function' ? callbacks.lineCallback(processLine(line)) : processLine(line);
if (options.fileAsArray) fileData.push(temp)
else fileData[String(lineCounter)] = temp;
lineCounter += 1;
index = remaining.indexOf('\n', last);
}
remaining = remaining.substring(last);
});
fileStream.on('end', () => {
if (remaining !== '') {
remaining = remaining.split(',');
const temp = processLine(remaining);
if (options.fileAsArray) fileData.push(temp)
else fileData[String(lineCounter)] = temp;
lineCounter += 1;
}
resolve(fileData);
});
fileStream.on('error', (err) => {
reject(err);
});
});
});
};