| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- 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
- {
- /// <summary>
- /// Read CSV file and creates DataTable with filled content, handling quoted values.
- /// </summary>
- /// <param name="fs">Used filesystem.</param>
- /// <param name="csvFile">Full path to the CSV file.</param>
- /// <param name="delimiter">The delimiter used in the CSV file.</param>
- /// <param name="hasHeaderRow">Indicates whether the CSV file has a header row.</param>
- /// <returns>New created DataTable with content.</returns>
- 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;
- }
- /// <summary>
- /// Parses a single CSV line, handling quoted values and delimiters within quotes.
- /// </summary>
- /// <param name="line">The CSV line to parse.</param>
- /// <param name="delimiter">The delimiter used in the CSV line.</param>
- /// <returns>An array of field values.</returns>
- private static string[]? ParseCsvLine(string? line, string delimiter)
- {
- if (string.IsNullOrEmpty(line))
- return null;
- var result = new List<string>();
- 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();
- }
- /// <summary>
- /// Writes a DataTable to a CSV file, properly handling quoting.
- /// </summary>
- /// <param name="fs">Used filesystem.</param>
- /// <param name="dataTable">The DataTable to write.</param>
- /// <param name="filePath">The full path of the output CSV file.</param>
- /// <param name="delimiter">The delimiter to use in the CSV file.</param>
- 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<DataColumn>().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))));
- }
- }
- }
- /// <summary>
- /// Reads a CSV file and returns it as a list of dictionaries.
- /// </summary>
- /// <param name="fs">Used filesystem.</param>
- /// <param name="csvFile">Full path to the CSV file.</param>
- /// <param name="delimiter">The delimiter used in the CSV file.</param>
- /// <returns>List of dictionaries representing the CSV data.</returns>
- public static List<Dictionary<string, string>> CsvToDictionaryList(IFileSystem fs, string csvFile, string delimiter = ",")
- {
- if (!fs.File.Exists(csvFile)) throw new FileNotFoundException($"File '{csvFile}' not found!", csvFile);
- var result = new List<Dictionary<string, string>>();
- 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<string, string>();
- 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;
- }
- }
- }
|