using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Text;
namespace Quadarax.Foundation.Core.Data
{
public static class CsvHelper
{
///
/// Read CSV file and creates DataTable with filled content, handling quoted values.
///
/// Used filesystem.
/// Full path to the CSV file.
/// The delimiter used in the CSV file.
/// Indicates whether the CSV file has a header row.
/// New created DataTable with content.
public static DataTable CsvToDataTable(IFileSystem fs, string csvFile, string delimiter = ",", bool hasHeaderRow = true)
{
if (!fs.File.Exists(csvFile)) throw new FileNotFoundException($"File '{csvFile}' not found!", csvFile);
var dt = new DataTable();
using (var sr = fs.File.OpenText(csvFile))
{
string[]? headers = null;
if (hasHeaderRow)
{
headers = ParseCsvLine(sr.ReadLine(), delimiter);
if (headers == null) return dt;
foreach (var header in headers)
{
dt.Columns.Add(header.Trim());
}
}
while (!sr.EndOfStream)
{
var rows = ParseCsvLine(sr.ReadLine(), delimiter);
if (rows == null) continue;
if (headers == null)
{
headers = new string[rows.Length];
for (int i = 0; i < rows.Length; i++)
{
headers[i] = $"Column{i + 1}";
dt.Columns.Add(headers[i]);
}
}
var dr = dt.NewRow();
for (var i = 0; i < Math.Min(headers.Length, rows.Length); i++)
{
dr[i] = rows[i];
}
dt.Rows.Add(dr);
}
}
return dt;
}
///
/// Parses a single CSV line, handling quoted values and delimiters within quotes.
///
/// The CSV line to parse.
/// The delimiter used in the CSV line.
/// An array of field values.
private static string[]? ParseCsvLine(string? line, string delimiter)
{
if (string.IsNullOrEmpty(line))
return null;
var result = new List();
var currentField = new StringBuilder();
bool inQuotes = false;
for (int i = 0; i < line.Length; i++)
{
if (inQuotes)
{
if (line[i] == '"' && (i + 1 == line.Length || line[i + 1] != '"'))
{
// End of quoted field
inQuotes = false;
}
else if (line[i] == '"' && i + 1 < line.Length && line[i + 1] == '"')
{
// Escaped quote
currentField.Append('"');
i++; // Skip next quote
}
else
{
currentField.Append(line[i]);
}
}
else
{
if (line[i] == '"')
{
inQuotes = true;
}
else if (line.Substring(i).StartsWith(delimiter))
{
result.Add(currentField.ToString().Trim());
currentField.Clear();
i += delimiter.Length - 1; // Skip the rest of the delimiter
}
else
{
currentField.Append(line[i]);
}
}
}
result.Add(currentField.ToString().Trim());
return result.ToArray();
}
///
/// Writes a DataTable to a CSV file, properly handling quoting.
///
/// Used filesystem.
/// The DataTable to write.
/// The full path of the output CSV file.
/// The delimiter to use in the CSV file.
public static void DataTableToCsv(IFileSystem fs,DataTable dataTable, string filePath, string delimiter = ",")
{
using (var sw = fs.File.CreateText(filePath))
{
// Write headers
sw.WriteLine(string.Join(delimiter, dataTable.Columns.Cast().Select(column => QuoteValue(column.ColumnName, delimiter))));
// Write rows
foreach (DataRow row in dataTable.Rows)
{
sw.WriteLine(string.Join(delimiter, row.ItemArray.Select(field => QuoteValue(field?.ToString(), delimiter))));
}
}
}
///
/// Reads a CSV file and returns it as a list of dictionaries.
///
/// Used filesystem.
/// Full path to the CSV file.
/// The delimiter used in the CSV file.
/// List of dictionaries representing the CSV data.
public static List> CsvToDictionaryList(IFileSystem fs, string csvFile, string delimiter = ",")
{
if (!fs.File.Exists(csvFile)) throw new FileNotFoundException($"File '{csvFile}' not found!", csvFile);
var result = new List>();
using (var sr = new StreamReader(csvFile))
{
var headers = ParseCsvLine(sr.ReadLine(), delimiter);
if (headers == null) return result;
while (!sr.EndOfStream)
{
var rows = ParseCsvLine(sr.ReadLine(), delimiter);
if (rows == null) continue;
var dict = new Dictionary();
for (var i = 0; i < headers.Length; i++)
{
dict[headers[i].Trim()] = i < rows.Length ? rows[i] : string.Empty;
}
result.Add(dict);
}
}
return result;
}
private static string QuoteValue(string? value, string delimiter)
{
if (string.IsNullOrEmpty(value))
return string.Empty;
bool needsQuoting = value.Contains(delimiter) || value.Contains("\"") || value.Contains("\r") || value.Contains("\n");
if (needsQuoting)
{
return $"\"{value.Replace("\"", "\"\"")}\"";
}
return value;
}
}
}