CsvParserUtility.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. using System.Text;
  2. using System.Reflection;
  3. using System.Globalization;
  4. namespace qdr.app.studiou.orders2printpack.Extensions
  5. {
  6. [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
  7. public class CsvColumnAttribute : Attribute
  8. {
  9. public string Name { get; }
  10. public int Index { get; set; }
  11. public CsvColumnAttribute(string name)
  12. {
  13. Name = name;
  14. Index = -1; // Default to -1 to indicate that index is not set
  15. }
  16. }
  17. public class CsvParser
  18. {
  19. public static IEnumerable<TClass> Parse<TClass>(string filePath, bool hasHeaderRow = true, string delimiter = ",")
  20. where TClass : new()
  21. {
  22. using (var reader = new StreamReader(filePath))
  23. {
  24. var properties = typeof(TClass).GetProperties()
  25. .Where(p => p.GetCustomAttribute<CsvColumnAttribute>() != null)
  26. .ToList();
  27. string[]? headers = null;
  28. if (hasHeaderRow)
  29. {
  30. var headerLine = reader.ReadLine();
  31. headers = ParseCsvLine(headerLine, delimiter);
  32. }
  33. while (!reader.EndOfStream)
  34. {
  35. var line = reader.ReadLine();
  36. var values = ParseCsvLine(line, delimiter);
  37. if (values == null || values.Length == 0)
  38. continue;
  39. var item = new TClass();
  40. foreach (var prop in properties)
  41. {
  42. var attr = prop.GetCustomAttribute<CsvColumnAttribute>();
  43. int? index = attr?.Index;
  44. if ((index == null || index == -1) && headers != null)
  45. {
  46. index = Array.IndexOf(headers, attr?.Name);
  47. }
  48. if (index != null && index != -1 && index < values.Length)
  49. {
  50. SetPropertyValue(item, prop, values[index.Value]);
  51. }
  52. }
  53. yield return item;
  54. }
  55. }
  56. }
  57. public static void Save<TClass>(IEnumerable<TClass> data, string filePath, bool includeHeader = true, string delimiter = ",")
  58. {
  59. var properties = typeof(TClass).GetProperties()
  60. .Where(p => p.GetCustomAttribute<CsvColumnAttribute>() != null)
  61. .OrderBy(p => p?.GetCustomAttribute<CsvColumnAttribute>()?.Index)
  62. .ToList();
  63. using (var writer = new StreamWriter(filePath, false, Encoding.UTF8))
  64. {
  65. // Write header
  66. if (includeHeader)
  67. {
  68. var headerLine = string.Join(delimiter.ToString(), properties.Select(p => QuoteField(p?.GetCustomAttribute<CsvColumnAttribute>()?.Name)));
  69. writer.WriteLine(headerLine);
  70. }
  71. // Write data
  72. foreach (var item in data)
  73. {
  74. var line = new StringBuilder();
  75. foreach (var prop in properties)
  76. {
  77. if (line.Length > 0)
  78. line.Append(delimiter);
  79. var value = prop.GetValue(item);
  80. line.Append(QuoteField(FormatValue(value)));
  81. }
  82. writer.WriteLine(line.ToString());
  83. }
  84. }
  85. }
  86. /// <summary>
  87. /// Parses a single CSV line, handling quoted values and delimiters within quotes.
  88. /// </summary>
  89. /// <param name="line">The CSV line to parse.</param>
  90. /// <param name="delimiter">The delimiter used in the CSV line.</param>
  91. /// <returns>An array of field values.</returns>
  92. private static string[]? ParseCsvLine(string? line, string delimiter)
  93. {
  94. if (string.IsNullOrEmpty(line))
  95. return null;
  96. var result = new List<string>();
  97. var currentField = new StringBuilder();
  98. bool inQuotes = false;
  99. for (int i = 0; i < line.Length; i++)
  100. {
  101. if (inQuotes)
  102. {
  103. if (line[i] == '"' && (i + 1 == line.Length || line[i + 1] != '"'))
  104. {
  105. // End of quoted field
  106. inQuotes = false;
  107. }
  108. else if (line[i] == '"' && i + 1 < line.Length && line[i + 1] == '"')
  109. {
  110. // Escaped quote
  111. currentField.Append('"');
  112. i++; // Skip next quote
  113. }
  114. else
  115. {
  116. currentField.Append(line[i]);
  117. }
  118. }
  119. else
  120. {
  121. if (line[i] == '"')
  122. {
  123. inQuotes = true;
  124. }
  125. else if (line.Substring(i).StartsWith(delimiter))
  126. {
  127. result.Add(currentField.ToString().Trim());
  128. currentField.Clear();
  129. i += delimiter.Length - 1; // Skip the rest of the delimiter
  130. }
  131. else
  132. {
  133. currentField.Append(line[i]);
  134. }
  135. }
  136. }
  137. result.Add(currentField.ToString().Trim());
  138. return result.ToArray();
  139. }
  140. private static void SetPropertyValue(object obj, PropertyInfo prop, string value)
  141. {
  142. if (string.IsNullOrWhiteSpace(value))
  143. return;
  144. try
  145. {
  146. object convertedValue;
  147. if (prop.PropertyType == typeof(string))
  148. {
  149. convertedValue = value;
  150. }
  151. else if (prop.PropertyType == typeof(int))
  152. {
  153. convertedValue = int.Parse(value);
  154. }
  155. else if (prop.PropertyType == typeof(double))
  156. {
  157. convertedValue = double.Parse(value, CultureInfo.InvariantCulture);
  158. }
  159. else if (prop.PropertyType == typeof(DateTime))
  160. {
  161. convertedValue = DateTime.Parse(value);
  162. }
  163. else if (prop.PropertyType == typeof(bool))
  164. {
  165. convertedValue = bool.Parse(value);
  166. }
  167. else
  168. {
  169. convertedValue = Convert.ChangeType(value, prop.PropertyType);
  170. }
  171. prop.SetValue(obj, convertedValue);
  172. }
  173. catch (Exception ex)
  174. {
  175. Console.WriteLine($"Error setting property {prop.Name}: {ex.Message}");
  176. }
  177. }
  178. private static string? FormatValue(object? value)
  179. {
  180. if (value == null)
  181. return string.Empty;
  182. if (value is DateTime dateTime)
  183. return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
  184. if (value is IFormattable formattable)
  185. return formattable.ToString(null, CultureInfo.InvariantCulture);
  186. return value?.ToString();
  187. }
  188. private static string EscapeField(string? field)
  189. {
  190. if (string.IsNullOrEmpty(field))
  191. return string.Empty;
  192. bool needsQuoting = field.Contains(',') || field.Contains('"') || field.Contains('\n');
  193. if (!needsQuoting)
  194. return field;
  195. return QuoteField(field);
  196. }
  197. private static string QuoteField(string? field)
  198. {
  199. if (string.IsNullOrEmpty(field))
  200. return "\"\"";
  201. return $"\"{field.Replace("\"", "\"\"")}\"";
  202. }
  203. }
  204. }