Разбор текстового файла с пользовательским форматом в C #

1

У меня есть куча текстовых файлов, которые имеют собственный формат, выглядящий так:

App Name    
Export Layout

Produced at 24/07/2011 09:53:21


Field Name                             Length                                                       

NAME                                   100                                                           
FULLNAME1                              150                                                           
ADDR1                                  80                                                           
ADDR2                                  80          

Любые пробелы могут быть вкладками или пробелами. Файл может содержать любое количество имен полей и длины.

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

Я знаю, как пропускать строки с помощью ReadLine(). Я не знаю, как сказать: "Когда вы дойдете до строки, начинающейся с" Имя поля ", пропустите еще одну строку, затем, начиная со следующей строки, возьмите все слова в левом столбце и цифры на правой колонке ".

Я попробовал String.Trim(), но не удаляет промежутки между ними.

Заранее спасибо.

  • 1
    Гугл "парсинг рекурсивного спуска". У вас нет обычной грамматики, поэтому инструменты синтаксического анализа, основанные на грамматике, вряд ли помогут.
  • 0
    Исправлена ли позиция строки с Field Name ?
Показать ещё 1 комментарий
Теги:
parsing

3 ответа

5
Лучший ответ

Вы можете использовать SkipWhile(l => !l.TrimStart().StartsWith("Field Name")).Skip(1):

Dictionary<string, string> allFieldLengths = File.ReadLines("path")
    .SkipWhile(l => !l.TrimStart().StartsWith("Field Name")) // skips lines that don't start with "Field Name"
    .Skip(1)                                       // go to next line
    .SkipWhile(l => string.IsNullOrWhiteSpace(l))  // skip following empty line(s)
    .Select(l =>                                   
    {                                              // anonymous method to use "real code"
        var line = l.Trim();                       // remove spaces or tabs from start and end of line
        string[] token = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
        return new { line, token };                // return anonymous type from 
    })
    .Where(x => x.token.Length == 2)               // ignore all lines with more than two fields (invalid data)
    .Select(x => new { FieldName = x.token[0], Length = x.token[1] })
    .GroupBy(x => x.FieldName)                     // groups lines by FieldName, every group contains it Key + all anonymous types which belong to this group
    .ToDictionary(xg => xg.Key, xg => string.Join(",", xg.Select(x => x.Length)));

line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries) будет разделяться пробелом и вкладками и игнорирует все пустые пространства. Используйте GroupBy чтобы гарантировать, что все ключи уникальны в словаре. В случае дубликатов имен полей Length будет соединена с запятой.


Изменить: поскольку вы запросили версию, отличную от LINQ, вот она:

Dictionary<string, string> allFieldLengths = new Dictionary<string, string>();
bool headerFound = false;
bool dataFound = false;
foreach (string l in File.ReadLines("path"))
{
    string line = l.Trim();
    if (!headerFound && line.StartsWith("Field Name"))
    {
        headerFound = true;
        // skip this line:
        continue;
    }
    if (!headerFound)
        continue;
    if (!dataFound && line.Length > 0)
        dataFound = true;
    if (!dataFound)
        continue;
    string[] token = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
    if (token.Length != 2)
        continue;
    string fieldName = token[0];
    string length = token[1];
    string lengthInDict;
    if (allFieldLengths.TryGetValue(fieldName, out lengthInDict))
        // append this length
        allFieldLengths[fieldName] = lengthInDict + "," + length;
    else
        allFieldLengths.Add(fieldName, length);
}

Мне больше нравится версия LINQ, потому что она более читабельная и поддерживаемая (imo).

  • 1
    фантастический ответ!
  • 0
    @Terribad: я добавил несколько комментариев к встроенному коду, надеюсь, он даст достаточное объяснение. Иначе скажи, чего не понимаешь.
Показать ещё 9 комментариев
1

Исходя из предположения, что позиция строки заголовка фиксирована, мы можем рассмотреть фактические пары ключ-значение, чтобы начать с 9-й строки. Затем, используя метод ReadAllLines для возврата массива String из файла, мы просто начинаем обработку с индекса 8 и далее:

  string[] lines = File.ReadAllLines(filepath);
  Dictionary<string,int> pairs = new Dictionary<string,int>();

    for(int i=8;i<lines.Length;i++)
    {
        string[] pair = Regex.Replace(lines[i],"(\\s)+",";").Split(';');
        pairs.Add(pair[0],int.Parse(pair[1]));
    }

Это скелет, не учитывающий обработку исключений, но я предполагаю, что он должен вас начать.

0

Вы можете использовать String.StartsWith() для обнаружения "FieldName". Затем String.Split() с параметром null для разделения по пробелам. Это даст вам ваши имена полей и длины.

  • 0
    Я попробовал это, и он также получает все пробелы между двумя столбцами.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню