122 lines
3.4 KiB
JavaScript
122 lines
3.4 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* A Class to parse CSV files
|
|
*/
|
|
|
|
const QUOTATION_MARK = '"';
|
|
const LINE_BREAKS = ["\r", "\n"];
|
|
const EOL = {};
|
|
|
|
class ParsingFailedException extends Error {
|
|
constructor(message) {
|
|
super(message ? message : `Stopped parsing because of wrong csv format`);
|
|
}
|
|
}
|
|
|
|
export class CSV {
|
|
/**
|
|
* Parses a csv formated string into rows split into [headerLine, parsedLines].
|
|
* The csv string format has to follow RFC 4180, otherwise the parsing process is stopped and a ParsingFailedException is thrown, e.g.:
|
|
* (wrong format => right format):
|
|
* 'abc"def' => 'abc""def'
|
|
* abc,def => "abc,def"
|
|
*
|
|
* @param {string} text
|
|
* @param {string} delimiter a comma for CSV files and a tab for TSV files
|
|
* @returns {Array[]} headerLine: column names (first line of text), parsedLines: Array of Login Objects with column name as properties and login data as values.
|
|
*/
|
|
static parse(text, delimiter) {
|
|
let headerline = [];
|
|
let parsedLines = [];
|
|
|
|
for (let row of this.mapValuesToRows(this.readCSV(text, delimiter))) {
|
|
if (!headerline.length) {
|
|
headerline = row;
|
|
} else {
|
|
let login = {};
|
|
row.forEach((attr, i) => (login[headerline[i]] = attr));
|
|
parsedLines.push(login);
|
|
}
|
|
}
|
|
return [headerline, parsedLines];
|
|
}
|
|
static *readCSV(text, delimiter) {
|
|
function maySkipMultipleLineBreaks() {
|
|
while (LINE_BREAKS.includes(text[current])) {
|
|
current++;
|
|
}
|
|
}
|
|
function readUntilSingleQuote() {
|
|
const start = ++current;
|
|
while (current < text.length) {
|
|
if (text[current] === QUOTATION_MARK) {
|
|
if (text[current + 1] !== QUOTATION_MARK) {
|
|
const result = text.slice(start, current).replaceAll('""', '"');
|
|
current++;
|
|
return result;
|
|
}
|
|
current++;
|
|
}
|
|
current++;
|
|
}
|
|
throw new ParsingFailedException();
|
|
}
|
|
function readUntilDelimiterOrNewLine() {
|
|
const start = current;
|
|
while (current < text.length) {
|
|
if (text[current] === delimiter) {
|
|
const result = text.slice(start, current);
|
|
current++;
|
|
return result;
|
|
} else if (LINE_BREAKS.includes(text[current])) {
|
|
const result = text.slice(start, current);
|
|
return result;
|
|
}
|
|
current++;
|
|
}
|
|
return text.slice(start);
|
|
}
|
|
let current = 0;
|
|
maySkipMultipleLineBreaks();
|
|
|
|
while (current < text.length) {
|
|
if (LINE_BREAKS.includes(text[current])) {
|
|
maySkipMultipleLineBreaks();
|
|
yield EOL;
|
|
}
|
|
|
|
let quotedValue = "";
|
|
let value = "";
|
|
|
|
if (text[current] === QUOTATION_MARK) {
|
|
quotedValue = readUntilSingleQuote();
|
|
}
|
|
|
|
value = readUntilDelimiterOrNewLine();
|
|
|
|
if (quotedValue && value) {
|
|
throw new ParsingFailedException();
|
|
}
|
|
|
|
yield quotedValue ? quotedValue : value;
|
|
}
|
|
}
|
|
|
|
static *mapValuesToRows(values) {
|
|
let row = [];
|
|
for (const value of values) {
|
|
if (value === EOL) {
|
|
yield row;
|
|
row = [];
|
|
} else {
|
|
row.push(value);
|
|
}
|
|
}
|
|
if (!(row.length === 1 && row[0] === "") && row.length) {
|
|
yield row;
|
|
}
|
|
}
|
|
}
|