From cfc59f3ae032d36c59653986e848cb362bab414f Mon Sep 17 00:00:00 2001 From: Chesterkxng Date: Mon, 7 Apr 2025 02:41:20 +0200 Subject: [PATCH] fix: skip commas between quotes --- src/pages/tools/csv/csv-to-yaml/service.ts | 10 +++- src/utils/csv.ts | 70 ++++++++++++++++++++-- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/pages/tools/csv/csv-to-yaml/service.ts b/src/pages/tools/csv/csv-to-yaml/service.ts index 8947516..7d663fc 100644 --- a/src/pages/tools/csv/csv-to-yaml/service.ts +++ b/src/pages/tools/csv/csv-to-yaml/service.ts @@ -50,7 +50,8 @@ export function main(input: string, options: InitialValuesType): string { true, options.commentCharacter, options.emptyLines, - options.csvSeparator + options.csvSeparator, + options.quoteCharacter ); rows.forEach((row) => { @@ -60,7 +61,12 @@ export function main(input: string, options: InitialValuesType): string { }); if (options.headerRow) { - const headerRow = getCsvHeaders(input, options.csvSeparator); + const headerRow = getCsvHeaders( + input, + options.csvSeparator, + options.quoteCharacter, + options.commentCharacter + ); headerRow.forEach((header, headerIndex) => { headerRow[headerIndex] = unquoteIfQuoted(header, options.quoteCharacter); }); diff --git a/src/utils/csv.ts b/src/utils/csv.ts index 8fcd52e..0aa81df 100644 --- a/src/utils/csv.ts +++ b/src/utils/csv.ts @@ -1,3 +1,41 @@ +/** + * Splits a CSV line into string[], handling quoted string. + * @param {string} input - The CSV input string. + * @param {string} delimiter - The character used to split csvlines. + * @param {string} quoteChar - The character used to quotes csv values. + * @returns {string[][]} - The CSV line as a 1D array. + */ +function splitCsvLine( + line: string, + delimiter: string = ',', + quoteChar: string = '"' +): string[] { + const result: string[] = []; + let current = ''; + let inQuotes = false; + + for (let i = 0; i < line.length; i++) { + const char = line[i]; + const nextChar = line[i + 1]; + + if (char === quoteChar) { + if (inQuotes && nextChar === quoteChar) { + current += quoteChar; + i++; // Skip the escaped quote + } else { + inQuotes = !inQuotes; + } + } else if (char === delimiter && !inQuotes) { + result.push(current.trim()); + current = ''; + } else { + current += char; + } + } + result.push(current.trim()); + return result; +} + /** * Splits a CSV string into rows, skipping any blank lines. * @param {string} input - The CSV input string. @@ -9,9 +47,12 @@ export function splitCsv( deleteComment: boolean, commentCharacter: string, deleteEmptyLines: boolean, - delimiter: string = ',' + delimiter: string = ',', + quoteChar: string = '"' ): string[][] { - let rows = input.split('\n').map((row) => row.split(delimiter)); + let rows = input + .split('\n') + .map((row) => splitCsvLine(row, delimiter, quoteChar)); // Remove comments if deleteComment is true if (deleteComment && commentCharacter) { @@ -30,12 +71,31 @@ export function splitCsv( * get the headers from a CSV string . * @param {string} input - The CSV input string. * @param {string} csvSeparator - The character used to separate values in the CSV. + * @param {string} quoteChar - The character used to quotes csv values. + * @param {string} commentCharacter - The character used to denote comments. * @returns {string[]} - The CSV header as a 1D array. */ export function getCsvHeaders( csvString: string, - csvSeparator: string = ',' + csvSeparator: string = ',', + quoteChar: string = '"', + commentCharacter?: string ): string[] { - const rows = csvString.split('\n').map((row) => row.split(csvSeparator)); - return rows.length > 0 ? rows[0].map((header) => header.trim()) : []; + const lines = csvString.split('\n'); + + for (const line of lines) { + const trimmed = line.trim(); + + if ( + trimmed === '' || + (commentCharacter && trimmed.startsWith(commentCharacter)) + ) { + continue; // skip empty or commented lines + } + + const headerLine = splitCsvLine(trimmed, csvSeparator, quoteChar); + return headerLine.map((h) => h.replace(/^\uFEFF/, '').trim()); + } + + return []; }