keeweb/app/scripts/util/data/csv-parser.js

109 lines
3.2 KiB
JavaScript

class CsvParser {
next;
csv;
index;
line = [];
lines = [];
value = '';
error = undefined;
parse(csv) {
this.csv = csv.trim().replace(/\r\n/g, '\n');
this.result = [];
this.next = this.handleBeforeValue;
this.index = 0;
while (this.next && this.index <= this.csv.length) {
this.next = this.next(this);
}
if (this.lines.length <= 1) {
throw new Error('Empty CSV');
}
return { headers: this.lines[0], rows: this.lines.slice(1) };
}
handleBeforeValue() {
const isQuoted = this.csv[this.index] === '"';
if (isQuoted) {
this.index++;
this.value = '';
return this.handleQuotedValue;
}
return this.handleUnquotedValue;
}
handleUnquotedValue() {
const commaIndex = this.csv.indexOf(',', this.index);
const newLineIndex = this.csv.indexOf('\n', this.index);
let nextIndex;
if (commaIndex >= 0 && (newLineIndex < 0 || commaIndex < newLineIndex)) {
nextIndex = commaIndex;
} else if (newLineIndex >= 0) {
nextIndex = newLineIndex;
} else {
nextIndex = this.csv.length;
}
const value = this.csv.substr(this.index, nextIndex - this.index);
this.line.push(value);
this.index = nextIndex;
return this.handleAfterValue;
}
handleQuotedValue() {
const nextQuoteIndex = this.csv.indexOf('"', this.index);
const nextBackslashIndex = this.csv.indexOf('\\', this.index);
if (nextQuoteIndex < 0) {
this.index = this.csv.length;
this.error = 'Quoted value not closed';
return this.handleError;
}
if (nextBackslashIndex > 0 && nextBackslashIndex < nextQuoteIndex) {
const charAfterBackslash = this.csv[nextBackslashIndex + 1];
if (charAfterBackslash === '"' || charAfterBackslash === '\\') {
this.value +=
this.csv.substr(this.index, nextBackslashIndex - this.index) +
charAfterBackslash;
this.index = nextBackslashIndex + 2;
} else {
this.value += this.csv.substr(this.index, nextBackslashIndex - this.index + 1);
this.index = nextBackslashIndex + 1;
}
return this.handleQuotedValue;
}
if (this.csv[nextQuoteIndex + 1] === '"') {
this.value += this.csv.substr(this.index, nextQuoteIndex - this.index + 1);
this.index = nextQuoteIndex + 2;
return this.handleQuotedValue;
}
this.value += this.csv.substr(this.index, nextQuoteIndex - this.index);
this.index = nextQuoteIndex + 1;
this.line.push(this.value);
this.value = '';
return this.handleAfterValue;
}
handleAfterValue() {
const hasNextValueOnThisLine = this.csv[this.index] === ',';
this.index++;
if (!hasNextValueOnThisLine) {
this.lines.push(this.line);
this.line = [];
}
return this.handleBeforeValue;
}
handleError() {
throw new Error(this.error);
}
}
export { CsvParser };