Средство чтения файлов в формате CSV, в котором начальная строка указывает имена столбцов

#c# #filehelpers

Вопрос:

Для контекста я пытаюсь проанализировать файл локализации 7dtd. Содержимое файла выглядит примерно так:

 Key,File,Type,UsedInMainMenu,NoTranslate,english,Context / Alternate Text,german,latam,french,italian,japanese,koreana,polish,brazilian,russian,turkish,schinese,tchinese,spanish
meleeToolCrowbar,items,Tool,,,Crowbar,,,,,,,,,,,,,,
 

Проблема в том, что порядок представленных данных варьируется от файла к файлу, где в 1-й строке указывается размещение элементов для каждой записи в файле. Мне нужно прочитать этот файл и объединить его с несколькими другими файлами аналогичной конструкции.

Сначала я пытался просто прочитать и использовать разделенную строку на запятую для анализа, но, увы, в некоторых полях могут быть запятые (ограниченные кавычками). Поэтому, когда я начал исследовать потенциальные решения, появились файлообменники. Однако, из того, что я могу сказать, это использует статический столбец для определения свойств, и это не будет работать для моего контекста, поскольку а) порядок столбцов варьируется и б) присутствуют не все столбцы.

Любая помощь в поиске решения была бы очень признательна.

Комментарии:

1. Затем прочитайте файл, используя специальную библиотеку CSV; например, с помощью CSVHelper вы просто определяете класс со свойствами, которые вы хотите получить из файла, а затем указываете ему прочитать файл. Порядок столбцов не имеет значения. Прочтите это: joshclose.github.io/CsvHelper/getting-started/… — не изобретайте что-то новое, что не нуждается в изобретении (в миллионный раз)

2. Из того, что я вижу, это тоже не сработает, поскольку вы определяете свойство в классе, а затем связываете его с записью в столбце файла. Это не сработает, так как точно неизвестно, сколько столбцов и каков будет порядок столбцов.

3. Честно говоря, я бы не рекомендовал это, если бы не думал, что это сработает. Прямо сейчас передо мной открыт проект, который использует CSVH для чтения файла из полумиллиона строк и 37 столбцов, но меня интересуют только 7 из них. Свойства C# даже не имеют того же имени, что и заголовки;в большинстве заголовков есть пробелы, поэтому атрибуты используются для сопоставления, например, столбца «Код продукта» файла с моим свойством ProdCode

Ответ №1:

Если вы просмотрите часть библиотеки dynamic ClassBuilder, вы можете создать класс, который сопоставляется между столбцами и нужным классом, добавив поля. Затем используйте объект CreateRecordClass для создания фактического типа класса, который будет использоваться механизмом выполнения.

Вам нужно будет проанализировать первую строку, а затем определить, какие у вас есть столбцы. Я действительно считаю, что вы можете использовать механизм с разделителями для выполнения этой работы за себя, если вы не укажете никаких столбцов или типов.

Это более сложно с помощью FileHelpers, чем, скажем, с помощью CSVHelper, где вы создаете сопоставление простым способом. Если вы используете CsvDataReader, то вы используете ClassMap для перехода между заголовками столбцов и свойствами, даже указывая конкретные индексы для столбцов, которые вам действительно нужны.

 void Main()
{
    var config = new CsvConfiguration(CultureInfo.InvariantCulture)
    {
        HasHeaderRecord = false,
    };
    using (var reader = new StreamReader("path\to\file.csv"))
    using (var csv = new CsvReader(reader, config))
    {
        csv.Context.RegisterClassMap<FooMap>();
        csv.Context.RegisterClassMap<BarMap>();
        var fooRecords = new List<Foo>();
        var barRecords = new List<Bar>();
        while (csv.Read())
        {
            switch (csv.GetField(0))
            {
                case "A":
                    fooRecords.Add(csv.GetRecord<Foo>());
                    break;
                case "B":
                    barRecords.Add(csv.GetRecord<Bar>());
                    break;
                default:
                    throw new InvalidOperationException("Unknown record type.");
            }
        }
    }
}

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Bar
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

public sealed class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
        Map(m => m.Id).Index(1);
        Map(m => m.Name).Index(2);
    }
}

public sealed class BarMap : ClassMap<Bar>
{
    public BarMap()
    {
        Map(m => m.Id).Index(1);
        Map(m => m.Name).Index(2);
    }
}