I'm writing a class called StringTemplate
, which allows to format objects like with String.Format
, but with names instead of indexes for placeholders. Here's an example :
string s = StringTemplate.Format("Hello {Name}. Today is {Date:D}, and it is {Date:T}.",
new { Name = "World", Date = DateTime.Now });
To achieve this result, I look for placeholders and replace them with indexes. I then pass the resulting format string to String.Format
.
This works fine, except when there are doubled braces, which are an escape sequence. The desired behavior (which is the same as String.Format
) is described below :
- "Hello {Name}" should be formatted as "Hello World"
- "Hello {{Name}}" should be formatted as "Hello {Name}"
- "Hello {{{Name}}}" should be formatted as "Hello {World}"
- "Hello {{{{Name}}}}" should be formatted as "Hello {{Name}}"
And so on...
But my current regular expression doesn't detect the escape sequence, and always considers the substring between brackets as a placeholder, so I get things like "Hello {0}".
Here's my current regular expression :
private static Regex _regex = new Regex(@"{(?<key>\w+)(?<format>:[^}]+)?}", RegexOptions.Compiled);
How can I modify this regular expression to ignore escaped braces ? What seems really hard is that I should detect placeholders depending on whether the number of brackets is odd or even... I can't think of a simple way to do it with a regular expression, is it even possible ?
For completeness, here's the full code of the StringTemplate
class :
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);
}
}