Я пишу класс с именем StringTemplate
, который позволяет форматировать объекты как с String.Format
, но с именами вместо индексов для заполнителей. Вот пример:
string s = StringTemplate.Format("Hello {Name}. Today is {Date:D}, and it is {Date:T}.",
new { Name = "World", Date = DateTime.Now });
Для достижения этого результата я ищу заполнители и заменяю их индексами. Затем я передаю полученную строку формата на String.Format
.
Это прекрасно работает, за исключением случаев, когда есть двойные скобки, которые являются escape-последовательностью. Желаемое поведение (которое совпадает с String.Format
) описано ниже:
- "Hello {Name}" следует отформатировать как "Hello World"
- "Hello {{Name}}" должен быть отформатирован как "Hello {Name}"
- "Hello {{{Name}}}" следует отформатировать как "Hello {World}"
- "Hello {{{{Name}}}" " должен быть отформатирован как " Hello {{Name}} "
И так далее ...
Но мое текущее регулярное выражение не определяет escape-последовательность и всегда рассматривает подстроку в скобках как заполнитель, поэтому я получаю такие вещи, как "Hello {0}" .
Вот мое текущее регулярное выражение:
private static Regex _regex = new Regex(@"{(?<key>\w+)(?<format>:[^}]+)?}", RegexOptions.Compiled);
Как я могу изменить это регулярное выражение, чтобы игнорировать экранированные скобки? Что кажется действительно сложным, так это то, что я должен определять заполнители в зависимости от того, является ли число скобок нечетным или четным ... Я не могу думать простого способа сделать это с помощью регулярного выражения, это вообще возможно?
Для полноты вот полный код класса StringTemplate
:
public class StringTemplate
{
private string _template;
private static Regex _regex = new Regex(@"{(?<key>\w+)(?<format>:[^}]+)?}", RegexOptions.Compiled);
public StringTemplate(string template)
{
if (template == null)
throw new ArgumentNullException("template");
this._template = template;
}
public static implicit operator StringTemplate(string s)
{
return new StringTemplate(s);
}
public override string ToString()
{
return _template;
}
public string Format(IDictionary<string, object> values)
{
if (values == null)
{
throw new ArgumentNullException("values");
}
Dictionary<string, int> indexes = new Dictionary<string, int>();
object[] array = new object[values.Count];
int i = 0;
foreach (string key in values.Keys)
{
array[i] = values[key];
indexes.Add(key, i++);
}
MatchEvaluator evaluator = (m) =>
{
if (m.Success)
{
string key = m.Groups["key"].Value;
string format = m.Groups["format"].Value;
int index = -1;
if (indexes.TryGetValue(key, out index))
{
return string.Format("{{{0}{1}}}", index, format);
}
}
return string.Format("{{{0}}}", m.Value);
};
string templateWithIndexes = _regex.Replace(_template, evaluator);
return string.Format(templateWithIndexes, array);
}
private static IDictionary<string, object> MakeDictionary(object obj)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
foreach (var prop in obj.GetType().GetProperties())
{
dict.Add(prop.Name, prop.GetValue(obj, null));
}
return dict;
}
public string Format(object values)
{
return Format(MakeDictionary(values));
}
public static string Format(string template, IDictionary<string, object> values)
{
return new StringTemplate(template).Format(values);
}
public static string Format(string template, object values)
{
return new StringTemplate(template).Format(values);
}
}