| | | 1 | | namespace ArturRios.Util.IO; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// Turns the lines of a delimited text file into a column-per-header dictionary. |
| | | 5 | | /// </summary> |
| | | 6 | | /// <remarks> |
| | | 7 | | /// Shared by <see cref="FileReader"/> and <see cref="FileReaderAsync"/> so the two cannot drift apart. |
| | | 8 | | /// </remarks> |
| | | 9 | | internal static class DelimitedText |
| | | 10 | | { |
| | | 11 | | /// <summary> |
| | | 12 | | /// Maps each header in the first line to the values found beneath it. |
| | | 13 | | /// </summary> |
| | | 14 | | /// <param name="lines">The lines of the file, header first.</param> |
| | | 15 | | /// <param name="separator">Delimiter separating fields.</param> |
| | | 16 | | /// <exception cref="InvalidOperationException"> |
| | | 17 | | /// Fewer than two lines were supplied, or a header name occurs more than once. |
| | | 18 | | /// </exception> |
| | | 19 | | internal static Dictionary<string, string[]> ToDictionary(string[] lines, char separator) |
| | | 20 | | { |
| | 22 | 21 | | if (lines.Length < 2) |
| | | 22 | | { |
| | 4 | 23 | | throw new InvalidOperationException("File must have at least a header and one data line"); |
| | | 24 | | } |
| | | 25 | | |
| | 18 | 26 | | var headers = lines[0].Split(separator); |
| | | 27 | | |
| | | 28 | | // Later columns would otherwise overwrite earlier ones, quietly returning fewer columns than the |
| | | 29 | | // file has. |
| | 18 | 30 | | var duplicates = headers |
| | 44 | 31 | | .GroupBy(header => header, StringComparer.Ordinal) |
| | 42 | 32 | | .Where(group => group.Count() > 1) |
| | 2 | 33 | | .Select(group => $"'{group.Key}'") |
| | 18 | 34 | | .ToArray(); |
| | | 35 | | |
| | 18 | 36 | | if (duplicates.Length > 0) |
| | | 37 | | { |
| | 2 | 38 | | throw new InvalidOperationException( |
| | 2 | 39 | | $"Header row contains duplicate column names: {string.Join(", ", duplicates)}"); |
| | | 40 | | } |
| | | 41 | | |
| | 16 | 42 | | var columnLists = new List<string>[headers.Length]; |
| | | 43 | | |
| | 108 | 44 | | for (var i = 0; i < headers.Length; i++) |
| | | 45 | | { |
| | 38 | 46 | | columnLists[i] = new List<string>(lines.Length - 1); |
| | | 47 | | } |
| | | 48 | | |
| | 84 | 49 | | for (var row = 1; row < lines.Length; row++) |
| | | 50 | | { |
| | 26 | 51 | | var values = lines[row].Split(separator); |
| | | 52 | | |
| | 176 | 53 | | for (var col = 0; col < headers.Length; col++) |
| | | 54 | | { |
| | 62 | 55 | | columnLists[col].Add(col < values.Length ? values[col] : string.Empty); |
| | | 56 | | } |
| | | 57 | | } |
| | | 58 | | |
| | 16 | 59 | | var dictionary = new Dictionary<string, string[]>(headers.Length); |
| | | 60 | | |
| | 108 | 61 | | for (var i = 0; i < headers.Length; i++) |
| | | 62 | | { |
| | 38 | 63 | | dictionary[headers[i]] = [.. columnLists[i]]; |
| | | 64 | | } |
| | | 65 | | |
| | 16 | 66 | | return dictionary; |
| | | 67 | | } |
| | | 68 | | } |