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 Parse(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() != 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(); 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(IEnumerable data, string filePath, bool includeHeader = true, string delimiter = ",") { var properties = typeof(TClass).GetProperties() .Where(p => p.GetCustomAttribute() != null) .OrderBy(p => p?.GetCustomAttribute()?.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()?.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()); } } } /// /// 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(); } 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("\"", "\"\"")}\""; } } }