#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 }
);
}