Извлеките значение ключа из свободного текста с помощью регулярного выражения

#c# #regex

Вопрос:

Допустим, у меня есть этот бесплатный текст

 unitType:unit_failure;unitId:b7eb;unitTitle:L1-O VEN_ACC_SETTINGS>(30s)>EXCL(A);applicable:true;comment:Decline sender:SELLER;Decline reason:VEN_ACC_SETTINGS;triggered by user:495259708;Display reason: CON_FAILURE;modified by: log_res_mon
 

Я хочу извлечь значения в объект с одинаковыми именами свойств.

 public class Values
{
    public string UnitType { get; set; }
    public string UnitId { get; set; }
    public string UnitTitle { get; set; }
    public string Applicable { get; set; }
    public string DeclineSender { get; set; }
    public string DeclineReason { get; set; }
    public string TriggeredByUser { get; set; }
    public string DisplayReason { get; set; }
    public string ModifiedBy { get; set; }
}
 

до сих пор я пробовал этот код:

     string comment = "unitType:unit_failure;unitId:b7eb;unitTitle:L1-O VEN_ACC_SETTINGS>(30s)>EXCL(A);applicable:true;comment:Decline sender:SELLER;Decline reason:VEN_ACC_SETTINGS;triggered by user:495259708;Display reason: CON_FAILURE;modified by: log_res_mon";
    string regex = @"(.*?:s*)(w*);?";

    var matches = Regex.Matches(comment, regex);
 

Что почти сработало. но вы можете видеть, что unitTitle это было вырезано позже L1- .

Другое дело, comment: это исключение, которое может быть отброшено либо выражением, либо я могу просто отбросить его с помощью .Replace .

введите описание изображения здесь

  • Как исправить выражение, чтобы оно включало полное значение unitTitle и отбрасывалось comment: (вы можете сказать мне, что его проще использовать .Replace )?
  • Каков наилучший способ извлечь значения matches и заполнить их в объекте?

РЕДАКТИРОВАТЬ: Я пробовал это, но это действительно некрасиво, есть ли лучший способ? (Не обращайте внимания на то, что я использую анонимный объект, это сейчас только для тестирования).

     var obj = new
    {
        unitType = matches.Single(x => x.Groups[1].Value == "unitType").Groups[2].Value
    };
 

ПРАВКА: Много хороших ответов, но я могу выбрать только один, поэтому я выбираю тот, который с простым запросом регулярного выражения.

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

1. Пожалуйста, не публикуйте текстовые изображения.

2. @JoelCoehoorn Я думаю, очевидно, что изображение из кода VS, который показывает значения во время выполнения, и нет возможности скопировать и вставить их как есть. Плюс изображение предназначалось только для того, чтобы объяснить, как возникла проблема (значение разбито на два поля).

Ответ №1:

Ну, если comment: shoudl всегда нужно удалять, и это всегда так, вам вообще не нужно регулярное выражение, к тому времени вы можете разделить на ; : :

 string input = @"unitType:unit_failure;unitId:b7eb;unitTitle:L1-O VEN_ACC_SETTINGS>(30s)>EXCL(A);applicable:true;comment:Decline sender:SELLER;Decline reason:VEN_ACC_SETTINGS;triggered by user:495259708;Display reason: CON_FAILURE;modified by: log_res_mon";
var fields = input.Replace("comment:","")
    .Split(';')
    .Select(x => x.Split(':', 2))
    .ToDictionary(x => x[0],x => x[1]);

Values values = new Values
{
    UnitType = fields["unitType"],
    UnitId = fields["unitId"],
    UnitTitle = fields["unitTitle"],
    Applicable = fields["applicable"],
    DeclineSender = fields["Decline sender"],
    DeclineReason = fields["Decline reason"],
    TriggeredByUser = fields["triggered by user"],
    DisplayReason = fields["Display reason"],
    ModifiedBy = fields["modified by"]
};
 

Ответ №2:

Вы можете использовать шаблон регулярного выражения

 (?<name>(w|s) ):s*(?<value>[^;]*);?
 

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

 var comment = @"unitType:unit_failure;unitId:b7eb;unitTitle:L1-O VEN_ACC_SETTINGS>(30s)>EXCL(A);applicable:true;comment:Decline sender:SELLER;Decline reason:VEN_ACC_SETTINGS;triggered by user:495259708;Display reason: CON_FAILURE;modified by: log_res_mon";
var pattern = @"(?<name>(w|s) ):s*(?<value>[^;]*);?";

// Add ";" before "Decline sender:" to place "comment:" into another match.
comment = comment.Replace("Decline sender:", ";Decline sender:");

Regex regex = new Regex(pattern);       
var matches = regex.Matches(comment);
foreach (Match m in matches) {
    string name = m.Groups["name"].Value;
    string value = m.Groups["value"].Value;

    Console.WriteLine($"{name}={value}");
}
 

Доходность:

 unitType=unit_failure
unitId=b7eb
unitTitle=L1-O VEN_ACC_SETTINGS>(30s)>EXCL(A)
applicable=true
comment=
Decline sender=SELLER
Decline reason=VEN_ACC_SETTINGS
triggered by user=495259708
Display reason=CON_FAILURE
modified by=log_res_mon
 

Если comment: всегда пусто, то comment = comment.Replace(";comment:", ";"); уберем его. Если, однако, комментарий может иметь значение, я бы заменил "Decline sender:" его на ";Decline sender:" . Это переводит комментарий в другое соответствие.

См.: https://dotnetfiddle.net/PROf3F

Объяснение того, что (?<name>(w|s) ):s*(?<value>[^;]*);? :

  • (?<name>expression) это именованная группа.
  • (w|s) выражение именованной группы «имя». Один или несколько символов слова или пробелов.
  • :s* двоеточие и необязательные пробелы между именем переменной и значением.
  • [^;]* выражение именованной группы «значение». Любое количество символов, кроме ; .
  • ;? необязательная точка с запятой.

Вы можете легко поместить значения в объект с помощью

 var values = new Values(); // Create object before the foreach-loop.

// In the matches loop:
switch (name) {
    "unitType":
        values.UnitType = value;
        break;
    "unitId":
        values.UnitId = value;
        break;
    ...
}
 

Или добавьте пары «имя/значение Dictionary<string,string> » в «с dict[name] = value; «, чтобы обеспечить простой поиск значения по имени.

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

1. Большое спасибо за простой запрос регулярного выражения, хотя я выбрал несколько иной подход к коду.

Ответ №3:

Вы также можете исключить совпадение ; и : для имени свойства, а также исключить совпадение ; в качестве значения свойства.

 ([^:;] ):s*([^;] )
 

Демонстрация регулярных выражений

Или если для ключа и значения должен быть хотя бы один символ слова, а не захват comment:

 bcomment:s*[^;w]*w[^;]*|([^:;w]*w[^:;]*):s*([^;w]*w[^;]*)
 

Шаблон совпадает:

  • bcomment:s*[^;w]*w[^;]* Сопоставьте комментарий, который вам не нужен
  • | или
  • ( Группа захвата 1
    • [^:;w]* При необходимости сопоставьте любой символ, кроме : ; или символ слова
    • w Сопоставьте один символ слова
    • [^:;]* При необходимости повторите сопоставление любого символа, кроме : и ;
  • ) , сопоставьте хотя бы одно слово char
  • :s* Совпадающие : и необязательные символы пробелов
  • ([^;w]*w[^;]*) Захват группы 2 Тот же подход, что и в группе 1, только за исключением ;

Демонстрация регулярных выражений

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

1. Это потрясающе. Спасибо, не могли бы вы ответить на второй вопрос? Я отредактирую вопрос, чтобы добавить пример кода, который я использовал, но он уродлив.

2. @Nour В шаблоне нет именованных групп захвата, поэтому вы можете проверить значение группы 1, чтобы получить сопутствующее значение для этого свойства. Всегда ли строка находится в таком порядке и всегда ли существуют свойства?

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

Ответ №4:

Вы можете использовать

 b(w (?:s w )*):s*(.*?)(?=s*;w (?:s w )*:|$)
 

Смотрите демонстрацию регулярных выражений. Подробные сведения:

  • b — граница слов
  • (w (?:s w )*) — Группа 1: один или несколько символов слов, за которыми следует ноль или несколько вхождений одного или нескольких пробелов и один или несколько символов слов
  • : — двоеточие
  • s* — ноль или более символов пробелов
  • (.*?) — Группа 2:
  • (?=s*;w (?:s w )*:|$) — положительный внешний вид, который требует, чтобы его шаблон немедленно соответствовал справа от текущего местоположения:
    • s*;w (?:s w )*: — ноль или более пробелов, ; а затем один или более символов слов, за которыми следует ноль или более вхождений одного или более пробелов и одного или более символов слов
    • | — или
    • $ — конец веревки.

Смотрите демонстрационную версию C# :

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

public class Test
{
    public class Values
    {
        public string UnitType { get; set; }
        public string UnitId { get; set; }
        public string UnitTitle { get; set; }
        public string Applicable { get; set; }
        public string DeclineSender { get; set; }
        public string DeclineReason { get; set; }
        public string TriggeredByUser { get; set; }
        public string DisplayReason { get; set; }
        public string ModifiedBy { get; set; }
        
        public override string ToString() 
        {
            return $"UnitType: {UnitType}nUnitId: {UnitId}nUnitTitle: {UnitTitle}nApplicable: {Applicable}nDeclineSender: {DeclineSender}nDeclineReason: {DeclineReason}nTriggeredByUser: {TriggeredByUser}nDisplayReason: {DisplayReason}nModifiedBy: {ModifiedBy}";
        }
        
    }
    
    public static void Main()
    {
        var pattern = @"b(w (?:s w )*):s*(.*?)(?=s*;w (?:s w )*:|$)";
        var text = "unitType:unit_failure;unitId:b7eb;unitTitle:L1-O VEN_ACC_SETTINGS>(30s)>EXCL(A);applicable:true;comment:Decline sender:SELLER;Decline reason:VEN_ACC_SETTINGS;triggered by user:495259708;Display reason: CON_FAILURE;modified by: log_res_mon";
        text = Regex.Replace(text, @"(?<![^;])comment:(?=(?:unitType|unitId|unitTitle|applicable|Decline sender|Decline reason|triggered by user|Display reason|modified by):)", string.Empty, RegexOptions.IgnoreCase);
        var vals = new List<Values>();
        var v = new Values();
        foreach (Match m in Regex.Matches(text, pattern))
        {
            if (m.Groups[1].Value == "unitType") v.UnitType=m.Groups[2].Value;
            if (m.Groups[1].Value == "unitId") v.UnitId=m.Groups[2].Value;
            if (m.Groups[1].Value == "unitTitle") v.UnitTitle=m.Groups[2].Value;
            if (m.Groups[1].Value == "applicable") v.Applicable=m.Groups[2].Value;
            if (m.Groups[1].Value == "Decline sender") v.DeclineSender=m.Groups[2].Value;
            if (m.Groups[1].Value == "Decline reason") v.DeclineReason=m.Groups[2].Value;
            if (m.Groups[1].Value == "triggered by user") v.TriggeredByUser=m.Groups[2].Value;
            if (m.Groups[1].Value == "Display reason") v.DisplayReason=m.Groups[2].Value;
            if (m.Groups[1].Value == "modified by") v.ModifiedBy=m.Groups[2].Value;
            
        }
        Console.WriteLine(v.ToString());
    }
}
 

Выход:

 UnitType: unit_failure
UnitId: b7eb
UnitTitle: L1-O VEN_ACC_SETTINGS>(30s)>EXCL(A)
Applicable: true
DeclineSender: SELLER
DeclineReason: VEN_ACC_SETTINGS
TriggeredByUser: 495259708
DisplayReason: CON_FAILURE
ModifiedBy: log_res_mon
 

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

1. Хорошо, как ты это сделал! 😁

2. @Nour Подождите, почему нет ; пустого значения комментария в comment:Decline sender:SELLER; ? Разве ввод не должен быть таким, как здесь, regex101.com/r/XCPAhL/2 ?

3. Спасибо за ответ. Я упомянул, что «комментарий:» является исключением и его необходимо исключить из текста. Не волнуйся, я буду использовать . Замените, чтобы удалить его в любом случае.

4. @Nour Теперь все исправлено. Вам нужно сопоставление без учета регистра?

Ответ №5:

Мой подход также отдает предпочтение разделению, тодикционному, а не регулярному выражению, для этого случая использования. И проверка значений «по имени» с помощью отражения.

 var str = "unitType:unit_failure;unitId:b7eb;unitTitle:L1-O VEN_ACC_SETTINGS>(30s)>EXCL(A);applicable:true;comment:Decline sender:SELLER;Decline reason:VEN_ACC_SETTINGS;triggered by user:495259708;Display reason: CON_FAILURE;modified by: log_res_mon";

        var nameValueMap = str.Replace("comment:", "")
                     .Split(";")
                     .Select(token => token.Split(":"))
                     .ToDictionary(arr => arr[0].Replace(" ", "").Trim(), 
                                   arr => arr[1].Trim());

        var instance = new Values();
        var type     = instance.GetType();
        foreach (var kv in nameValueMap) {
            type.InvokeMember(
                               kv.Key,
                               BindingFlags.Instance
                             | BindingFlags.SetProperty
                             | BindingFlags.IgnoreCase
                             | BindingFlags.Public,
                               null,
                               instance,
                               new object[] { kv.Value }
                              );
        }
 

Результат:
Значения в quot;экземпляреquot;: