| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- using System.Text;
- using System.Reflection;
- using System.Globalization;
- namespace qdr.app.studiou.orders2printpack.Extensions
- {
- [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
- public class CsvColumnAttribute : Attribute
- {
- public string Name { get; }
- public int Index { get; set; }
- public CsvColumnAttribute(string name)
- {
- Name = name;
- Index = -1; // Default to -1 to indicate that index is not set
- }
- }
- public class CsvParser
- {
- public static IEnumerable<TClass> Parse<TClass>(string filePath, bool hasHeaderRow = true, string delimiter = ",")
- where TClass : new()
- {
- using (var reader = new StreamReader(filePath))
- {
- var properties = typeof(TClass).GetProperties()
- .Where(p => p.GetCustomAttribute<CsvColumnAttribute>() != null)
- .ToList();
- string[]? headers = null;
- if (hasHeaderRow)
- {
- var headerLine = reader.ReadLine();
- headers = ParseCsvLine(headerLine, delimiter);
- }
- while (!reader.EndOfStream)
- {
- var line = reader.ReadLine();
- var values = ParseCsvLine(line, delimiter);
- if (values == null || values.Length == 0)
- continue;
- var item = new TClass();
- foreach (var prop in properties)
- {
- var attr = prop.GetCustomAttribute<CsvColumnAttribute>();
- int? index = attr?.Index;
- if ((index == null || index == -1) && headers != null)
- {
- index = Array.IndexOf(headers, attr?.Name);
- }
- if (index != null && index != -1 && index < values.Length)
- {
- SetPropertyValue(item, prop, values[index.Value]);
- }
- }
- yield return item;
- }
- }
- }
- public static void Save<TClass>(IEnumerable<TClass> data, string filePath, bool includeHeader = true, string delimiter = ",")
- {
- var properties = typeof(TClass).GetProperties()
- .Where(p => p.GetCustomAttribute<CsvColumnAttribute>() != null)
- .OrderBy(p => p?.GetCustomAttribute<CsvColumnAttribute>()?.Index)
- .ToList();
- using (var writer = new StreamWriter(filePath, false, Encoding.UTF8))
- {
- // Write header
- if (includeHeader)
- {
- var headerLine = string.Join(delimiter.ToString(), properties.Select(p => QuoteField(p?.GetCustomAttribute<CsvColumnAttribute>()?.Name)));
- writer.WriteLine(headerLine);
- }
- // Write data
- foreach (var item in data)
- {
- var line = new StringBuilder();
- foreach (var prop in properties)
- {
- if (line.Length > 0)
- line.Append(delimiter);
- var value = prop.GetValue(item);
- line.Append(QuoteField(FormatValue(value)));
- }
- writer.WriteLine(line.ToString());
- }
- }
- }
- /// <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();
- }
- private static void SetPropertyValue(object obj, PropertyInfo prop, string value)
- {
- if (string.IsNullOrWhiteSpace(value))
- return;
- try
- {
- object convertedValue;
- if (prop.PropertyType == typeof(string))
- {
- convertedValue = value;
- }
- else if (prop.PropertyType == typeof(int))
- {
- convertedValue = int.Parse(value);
- }
- else if (prop.PropertyType == typeof(double))
- {
- convertedValue = double.Parse(value, CultureInfo.InvariantCulture);
- }
- else if (prop.PropertyType == typeof(DateTime))
- {
- convertedValue = DateTime.Parse(value);
- }
- else if (prop.PropertyType == typeof(bool))
- {
- convertedValue = bool.Parse(value);
- }
- else
- {
- convertedValue = Convert.ChangeType(value, prop.PropertyType);
- }
- prop.SetValue(obj, convertedValue);
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Error setting property {prop.Name}: {ex.Message}");
- }
- }
- private static string? FormatValue(object? value)
- {
- if (value == null)
- return string.Empty;
- if (value is DateTime dateTime)
- return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
- if (value is IFormattable formattable)
- return formattable.ToString(null, CultureInfo.InvariantCulture);
- return value?.ToString();
- }
- private static string EscapeField(string? field)
- {
- if (string.IsNullOrEmpty(field))
- return string.Empty;
- bool needsQuoting = field.Contains(',') || field.Contains('"') || field.Contains('\n');
- if (!needsQuoting)
- return field;
- return QuoteField(field);
- }
- private static string QuoteField(string? field)
- {
- if (string.IsNullOrEmpty(field))
- return "\"\"";
- return $"\"{field.Replace("\"", "\"\"")}\"";
- }
- }
- }
|