Синтаксический анализ в C # со словарем

#c# #.net #text-parsing

#c# #.net #синтаксический анализ текста

Вопрос:

Я новичок в программировании и изо всех сил пытался разобрать файл. Изначально я пытался проанализировать его определенным образом, но в конечном итоге это не сработало корректно. Я хочу разобрать следующую строку в словаре<строка,string> .

Установлена сетевая карта (ы): 7 сетевых адаптеров.

                        [01]: Broadcom 
                             Connection Name: Local Area Connection
                             DHCP Enabled:    No
                             IP address(es)
                             [01]: abc.de.xyz.
                       [02]: Broadcom 
                             Connection Name: eth1
                             Status:          Media disconnected
                       [03]: Broadcom 
                             Connection Name: eth0
                             Status:          Media disconnected
                       [04]: Broadcom 
                             Connection Name: eth3
                             Status:          Media disconnected
                       [05]: Mellanox 
                             Connection Name: Local Area Connection 5
                             Status:          Hardware not present
                       [06]: Mellanox 
                             Connection Name: Local Area Connection 6
                             Status:          Media disconnected
                       [07]: Mellanox 
                             Connection Name: Local Area Connection 7
                             DHCP Enabled:    No
                             IP address(es)
                             [01]: mno.pqr.stu.vwx
  

Я хочу, чтобы [01] Broadcom был ключом к словарю, а имя соединения: Локальное подключение DHCP включено: Нет IP-адресов [01]: abc.de.xyz в качестве значения и так далее для остальных шести. Спасибо за помощь. Действительно ценю это. Любая помощь о том, как это сделать, будет отличной, поскольку я сошел с ума, читая о разделении строк и пытаясь выяснить, как заставить словарь хранить значение.

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

1. Будет ли когда-либо разрешено указывать имя сетевой карты a . (точка) в названии? Если это так, может быть сложно провести различие между [01]: NIC_Name и [01]: mno.pqr.stu.vwx.

2. в конце сетевой карты нет точки. и я читаю это из файла. Я читал его и сохранял файл в виде массива строк.

3. вы можете сделать это обычным способом (прочитайте строку, проверьте наличие ‘[]’ и поместите ее в dic)

4. Я просто имел в виду, существует ли когда-либо возможность наличия в имени сетевой карты точки. Например, [01]: Пример. Имя… Если это так, было бы трудно провести различие между именем и ip-адресом. Если у вас никогда не будет точки в имени, то вы можете определить разницу между строкой имени сетевой карты и строкой ip-адреса, проверив наличие какой-либо точки.

5. @Rev: Это невозможно с _sub_line, подобной ключу. Например. [01]: mno.pqr.stu.vwx

Ответ №1:

Вот решение, которое не использует регулярные выражения, если вы не хотите использовать этот маршрут. Этот код был протестирован.

 using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace NicParser
{
    public class NicFileParser
    {
        private readonly string _file;
        private readonly Dictionary<string, string> _nics;

        public NicFileParser(string file)
        {
            _file = file;
            _nics = new Dictionary<string, string>();
        }

        public void Parse()
        {
            var key = string.Empty;
            var value = new StringBuilder();

            try
            {
                using (var rdr = new StreamReader(_file))
                {
                    var firstTime = true;

                    while (rdr.Peek() > 0)
                    {
                        var line = rdr.ReadLine().Trim();

                        if (IsKey(line))
                        {
                            // Once a key is hit, add the previous 
                            // key and values (except the first time).
                            if (!firstTime)
                            {
                                _nics.Add(key, value.ToString());
                            }
                            else
                            {
                                firstTime = false;
                            }

                            // Assign the key, and clear the previous values.
                            key = line;
                            value.Length = 0;
                        }
                        else
                        {
                            // Add to the values for this nic card.
                            value.AppendLine(line);
                        }
                    }

                    // Final line of the file has been read. 
                    // Add the last nic card.
                    _nics.Add(key, value.ToString());
                }
            }
            catch (Exception ex)
            {
                // Handle your exceptions however you like...
            }
        }

        private static bool IsKey(string line)
        {
            return (!String.IsNullOrEmpty(line)
                 amp;amp; line.StartsWith("[") 
                 amp;amp; !line.Contains("."));
        }

        // Use this to access the NIC information.
        public Dictionary<string, string> Cards
        {
            get { return _nics; }
        }
    }
}
  

Ответ №2:

Простите за плохой синтаксис C # — я привык к VB .NET. Не смейтесь.

Сначала я бы прочитал строки текста файла в массив строк.

 foreach (string line in File.ReadLines("path-to-file")) {

}
  

Для каждой строки вы находитесь либо в строке «ключ», либо в строке «значение». Ключевые строки выглядят следующим образом:

 [01]: Broadcom
  

Чтобы определить, находитесь ли вы в строке «ключ», вы могли бы попробовать что-то вроде line.Trim().StartsWith("[") , но это не будет работать надежно, потому что у вас есть другие строки, которые выглядят как [01]: abc.def.ghi.jkl , которые являются IP-адресами, а не ключами. Поэтому вам нужно быть немного умнее в этом вопросе и, возможно, даже использовать регулярное выражение, чтобы определить, просматриваете ли вы IP-адрес или сетевую карту. Я не знаю точных спецификаций файла, который вы просматриваете, но вы также могли бы использовать начальные пробелы / табуляции, чтобы помочь вам определить, находитесь ли вы в строке «ключ» или «значение».

Тогда ваш код выглядел бы примерно так:

 var networkCards = new Dictionary<String, String>();
string currentKey = String.Empty;

foreach (string line in File.ReadLines("path-to-file")) {
  if ( IsKeyLine( line ) ) {
    currentKey = line.Trim();
    networkCards.Add(currentKey, "");
  } else {
    networkCards[currentKey]  = line.Trim()   " ";
  }
}
  

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

 public bool IsKeyLine(string line) {
  if (!String.IsNullOrEmpty(line)) {
    //run two regexes - one to see if the line is of the general pattern of a "key" line
    //the second reg ex makes sure there isn't an ip address in the line, which would indicate that the line is part of the "value" and not the "key"
    return System.Text.RegularExpressions.RegEx.IsMatch(line, @"^s*[d{0,2}]: ")
      amp;amp; !System.Text.RegularExpressions.RegEx.IsMatch(line, @"d{1,3}.d{1,3}.d{1,3}.d{1,3}");
  }

  return false;
}
  

Итак, я не тратил время на тестирование чего-либо из этого кода — это не укладывается у меня в голове. Но это должно, по крайней мере, помочь вам двигаться в общем правильном направлении. Однако самое важное, что нужно определить, — это стандарт для формата файла. Это даст вам подсказки, чтобы пойти по правильному пути. Возможно, вам даже не понадобятся регулярные выражения (что было бы предпочтительнее, поскольку выполнение регулярных выражений обычно обходится дорого).

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

1. Я немного изменил ваш метод IsKeyLine, чтобы убедиться, что он компилируется. Вам нужно сделать выражения регулярных выражений дословными строками или экранировать каждую обратную косую черту. Кроме того, если строка равна нулю или пуста, вы не возвращали значение, когда требуется bool (или может быть выдано исключение). Я добавил исключение ArgumentNullException.

2. Спасибо Джейсону. Да, не привык к C # — обычно я пишу на VB. Я всегда забываю избегать обратной косой черты. Хотя я не думаю, что было бы хорошо создавать исключение. Я собираюсь изменить его, чтобы вместо этого возвращать false .

Ответ №3:

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

Ответ №4:

Рассмотрите возможность использования начального пробела для определения «роли», которую играет строка (эй, Python делает ;-). Затем файл может быть проанализирован построчно с использованием простого конечного автомата.

Я подозреваю, что, поскольку это сгенерированный вывод, этот метод можно использовать надежно. Если это так, это значительно упрощает правила и синтаксический анализ.

Удачного кодирования.


Вот небольшое подтверждение концепции для определения «роли» строки.

 using (var inp = ...) {
    string line;
    while ((line = inp.ReadLine()) != null) {
        // normalize to our world of 8-space tabs                        
        line = line.Replace("t", "        ");
        var lineDepth = line.Length - line.TrimStart().Length;
        if (lineDepth < 65) {
            // is potential "heading line"
        } else { // >= 65
            // is "property line"
        }
    }
}
  

Ответ №5:

Я знаю, что этот вопрос касается C #, а не powershell, и уже есть несколько хороших ответов на C #, тем не менее, я хотел бы внести свой вклад в решение для powershell, как что-то для рассмотрения. Это может оказаться проще, чем код на c #, но это зависит от точки зрения:

 $networkCards = systeminfo | ForEach-Object {$a=0} {
    if ($_.startswith("Network Card(s)")) {$a=1} else {if ($a) {$_}}
}

$networkCards | ForEach-Object {$data=@{}} { 
    if ($_.trim().startswith("[")) {
        $c = $_.trim(); $data[$c] = @()} else {$data[$c]  = $_.trim()
    } 
}

#Now we have a hash table with the keys as requested in the question 
#and the values are lists of separate strings, but those can be easily 
#concatenated if needed. Let's display it:
$data
  

Если у вас установлен powershell (сейчас это часть Windows 7), вы просто можете открыть его и вставить приведенный выше код в командную строку, и вы сможете сразу увидеть результат.

Ответ №6:

Может быть, это упростится, если вы поместите это в вывод csv.

 Systeminfo /fo csv