/
afanasevn
/
MaxSystems
Обзор
Документация
Войти
/
afanasevn
/
MaxSystems
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/MaxSystems.Infrastructure/Import/ImportFileParser.cs
124 строки
4 KB
IBS\NAfanasev
Integration Event Bus
22 июн 2026, 17:01
22 июн 2026, 17:01
83907da
Код
Авторство
О чём код?
using System.Globalization; using System.Text; using CsvHelper; using CsvHelper.Configuration; using ClosedXML.Excel; namespace MaxSystems.Infrastructure.Import; /// <summary>Разбор CSV и XLSX файлов импорта в строки словарей.</summary> public static class ImportFileParser { /// <summary>Выбирает парсер по расширению файла и возвращает строки с нормализованными заголовками.</summary> public static async Task<IReadOnlyList<Dictionary<string, string>>> ParseAsync( Stream stream, string fileName, CancellationToken ct = default) { var lowerName = fileName.ToLowerInvariant(); if (lowerName.EndsWith(".xlsx") || lowerName.EndsWith(".xls")) return ParseExcel(stream); return await ParseCsvAsync(stream, ct); } /// <summary>Читает CSV с автоопределением кодировки UTF-8 или Windows-1251.</summary> private static async Task<IReadOnlyList<Dictionary<string, string>>> ParseCsvAsync( Stream stream, CancellationToken ct) { using var memory = new MemoryStream(); await stream.CopyToAsync(memory, ct); var buffer = memory.ToArray(); var rows = ParseCsvText(DecodeUtf8(buffer)); if (rows.Count > 0) return rows; return ParseCsvText(DecodeWindows1251(buffer)); } /// <summary>Разбирает текст CSV через CsvHelper с автоопределением разделителя.</summary> private static IReadOnlyList<Dictionary<string, string>> ParseCsvText(string text) { var delimiter = text.Contains(';') && !text.Contains(',') ? ";" : ","; using var reader = new StringReader(text); using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture) { HasHeaderRecord = true, Delimiter = delimiter, MissingFieldFound = null, BadDataFound = null, TrimOptions = TrimOptions.Trim }); var result = new List<Dictionary<string, string>>(); if (!csv.Read() || !csv.ReadHeader()) return result; var headers = csv.HeaderRecord? .Select(h => h.Trim().ToLowerInvariant()) .ToArray() ?? []; while (csv.Read()) { var row = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); for (var i = 0; i < headers.Length; i++) { row[headers[i]] = csv.TryGetField<string>(i, out var value) ? value?.Trim() ?? string.Empty : string.Empty; } if (row.Values.Any(v => !string.IsNullOrWhiteSpace(v))) result.Add(row); } return result; } /// <summary>Разбирает первый лист XLSX в строки словарей.</summary> private static IReadOnlyList<Dictionary<string, string>> ParseExcel(Stream stream) { using var workbook = new XLWorkbook(stream); var sheet = workbook.Worksheet(1); var usedRange = sheet.RangeUsed(); if (usedRange is null) return []; var rows = usedRange.RowsUsed().ToList(); if (rows.Count == 0) return []; var headers = rows[0].Cells().Select(c => c.GetString().Trim().ToLowerInvariant()).ToArray(); var result = new List<Dictionary<string, string>>(); for (var r = 1; r < rows.Count; r++) { var row = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var cells = rows[r].Cells(1, headers.Length).ToArray(); for (var i = 0; i < headers.Length; i++) { row[headers[i]] = i < cells.Length ? cells[i].GetString().Trim() : string.Empty; } if (row.Values.Any(v => !string.IsNullOrWhiteSpace(v))) result.Add(row); } return result; } /// <summary>Декодирует байты как UTF-8.</summary> private static string DecodeUtf8(byte[] buffer) => Encoding.UTF8.GetString(buffer); /// <summary>Декодирует байты как Windows-1251 (типичная кодировка Excel CSV в Windows).</summary> private static string DecodeWindows1251(byte[] buffer) { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); return Encoding.GetEncoding(1251).GetString(buffer); } }