I am writing a class called StringTemplate that allows you to format objects such as String.Format , but with names instead of indexes for placeholders. Here is 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. Then I pass the resulting string format String.Format .
This works fine except when there are double curly braces that are escape sequences. The desired behavior (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}}}" must be formatted as "Hello {World}"
- Hello {{{{Name}}}} should be formatted as Hello {{Name}}
And so on...
But my current regex does not detect the escape sequence and always takes the substring between the brackets as a placeholder, so I get things like "Hello {0}".
Here is my current regex:
private static Regex _regex = new Regex(@"{(?<key>\w+)(?<format>:[^}]+)?}", RegexOptions.Compiled);
How can I change this regex to ignore escaped curly braces? What seems very difficult is that I have to detect placeholders depending on whether the number of brackets is odd or even ... I can't think of a simple way to do this with a regular expression, is this possible?
For completeness, here is 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); } }