Дана строка формата сообщения, такая как str
ниже. Я хочу иметь возможность получать значения «уведомления» и «имя», которые используются для отображения текстовых значений.
var str = @"You have {notifications, plural,
zero {no notifications}
one {one notification}
=42 {a universal amount of notifications}
other {# notifications}
}. Have a nice day, {name}!";
Я пробовал использовать регулярное выражение, например:
var matches = Regex.Matches(str, @"{(.*?)}");
//var matches = Regex.Matches(str, @"(?<=\{)[^}{]*(?=\})");
var results = matches.Cast<Match>().Select(m => m.Groups[1].Value).Distinct().ToList();
Но вышесказанное не принимает во внимание, что {notifications,..
сам заключен в фигурные скобки и включает в себя ненужные внутренние значения, которые также заключены в фигурные скобки.
Итак, вкратце, я просто хочу иметь возможность анализировать строку, такую как str
выше, и получать notifications
и name
по возвращаемым значениям.
Строка, такая как var str2 = @"Hello {name}"
, должна просто возвращать name
в качестве значения.
РЕДАКТИРОВАТЬ
Значения notifications
и name
не будут известны заранее — я только что использовал это в качестве примера для значений, которые мне нужно вернуть из строки.
Один из способов сделать это — написать метод, который будет форматировать строку для вас на основе ввода count
и формы единственного (и множественного числа) строки:
private static string FormatWord(int count, string singluar)
{
return Format(count, singluar, singluar + "s");
}
private static string FormatWord(int count, string singular, string plural)
{
return count == 0 ? "no " + plural
: count == 1 ? "one " + singular
: count == 42 ? "a universal number of " + plural
: count + " " + plural;
}
Тогда при использовании это может выглядеть так:
private static void Main()
{
var name = "User";
while (true)
{
var count = GetIntFromUser("Enter notification count: ");
Console.WriteLine($"You have {FormatWord(count, "notification")}. " +
$"Have a nice day, {name}");
}
}
Обратите внимание, что этот метод также использует вспомогательный метод для получения строго типизированного целого числа от пользователя:
private static int GetIntFromUser(string prompt, Func<int, bool> validator = null)
{
int result;
var cursorTop = Console.CursorTop;
do
{
ClearSpecificLineAndWrite(cursorTop, prompt);
} while (!int.TryParse(Console.ReadLine(), out result) ||
!(validator?.Invoke(result) ?? true));
return result;
}
private static void ClearSpecificLineAndWrite(int cursorTop, string message)
{
Console.SetCursorPosition(0, cursorTop);
Console.Write(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(0, cursorTop);
Console.Write(message);
}
var str = @"You have {notifications, plural,
zero {no notifications}
one {one notification}
=42 {a universal amount of notifications}
other {# notifications}
}. Have a nice day, {name}!";
// get matches skipping nested curly braces
var matches =
Regex.Matches(str, @"{((?:[^{}]|(?<counter>{)|(?<-counter>}))+(?(counter)(?!)))}");
var results = matches.Cast<Match>().Select(m => m.Groups[1].Value).Distinct()
.Select(v => Regex.Match(v, @"^\w+").Value) // take 1st word
.ToList();
что приводит к (скопировано из окна Visual Studio Locals во время отладки)
results Count = 2 System.Collections.Generic.List<string>
[0] "notifications"
[1] "name"
... исходный ответ следует ...
Следует отметить одну вещь о текущем решении в исходном вопросе:
.
не соответствует разрывам строк, поэтому это одна из причин, по которой в настоящее время оно соответствует вложенным значениям (см. этот источник)(в этой статье рассматривается основная проблема, отмеченная в исходном вопросе — вложенные фигурные скобки)
var str = @"You have {notifications, plural,
zero {no notifications}
one {one notification}
=42 {a universal amount of notifications}
other {# notifications}
}. Have a nice day, {name}!";
// get matches skipping nested curly braces
var matches =
Regex.Matches(str, @"{((?:[^{}]|(?<counter>{)|(?<-counter>}))+(?(counter)(?!)))}");
var results = matches.Cast<Match>().Select(m => m.Groups[1].Value).Distinct().ToList();
что приводит к (скопировано из окна Visual Studio Locals во время отладки)
results Count = 2 System.Collections.Generic.List<string>
[0] "notifications, plural,\r\n zero {no notifications}\r\n one {one notification}\r\n =42 {a universal amount of notifications}\r\n other {# notifications}\r\n "
[1] "name"
(или если бы вы выводили эти результаты на консоль):
// Result 0 would look like:
notifications, plural,
zero {no notifications}
one {one notification}
=42 {a universal amount of notifications}
other {# notifications}
// Result 1 would look like:
name
Я вернулся к этому и понял, что вопрос требует только отдельных слов в качестве результатов.
(Я повторяю приведенный выше фрагмент с дополнительным оператором выбора, чтобы показать полное решение)
var str = @"You have {notifications, plural,
zero {no notifications}
one {one notification}
=42 {a universal amount of notifications}
other {# notifications}
}. Have a nice day, {name}!";
// get matches skipping nested curly braces
var matches =
Regex.Matches(str, @"{((?:[^{}]|(?<counter>{)|(?<-counter>}))+(?(counter)(?!)))}");
var results = matches.Cast<Match>().Select(m => m.Groups[1].Value).Distinct()
.Select(v => Regex.Match(v, @"^\w+").Value) // take 1st word
.ToList();
что приводит к (скопировано из окна Visual Studio Locals во время отладки)
results Count = 2 System.Collections.Generic.List<string>
[0] "notifications"
[1] "name"
(Я просто нашел это интересным и потратил немного больше времени на исследования/обучение и подумал, что стоит включить дополнительную связанную информацию)
Беседы здесь и здесь включают некоторые мнения за и против использования регулярных выражений для этого типа проблем.
Независимо от приведенных выше мнений, создатели .NET сочли целесообразным реализовать определения групп балансировки — функциональность, которую использует этот ответ: