title: Regular expression for String.Format-like utility
body: <p>I'm writing a class called <code>StringTemplate</code>, which allows to format objects like with <code>String.Format</code>, but with names instead of indexes for placeholders. Here's an example :</p>
  
  <pre><code>string s = StringTemplate.Format("Hello {Name}. Today is {Date:D}, and it is {Date:T}.",
                                   new { Name = "World", Date = DateTime.Now });
  </code></pre>
  
  <p>To achieve this result, I look for placeholders and replace them with indexes. I then pass the resulting format string to <code>String.Format</code>.</p>
  
  <p>This works fine, except when there are doubled braces, which are an escape sequence. The desired behavior (which is the same as <code>String.Format</code>) is described below :</p>
  
  <ul>
  <li><em>"Hello {Name}"</em> should be formatted as <em>"Hello World"</em></li>
  <li><em>"Hello {{Name}}"</em> should be formatted as <em>"Hello {Name}"</em></li>
  <li><em>"Hello {{{Name}}}"</em> should be formatted as <em>"Hello {World}"</em></li>
  <li><em>"Hello {{{{Name}}}}"</em> should be formatted as <em>"Hello {{Name}}"</em></li>
  </ul>
  
  <p>And so on...</p>
  
  <p>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 <em>"Hello {0}"</em>.</p>
  
  <p>Here's my current regular expression :</p>
  
  <pre><code>private static Regex _regex = new Regex(@"{(?&lt;key&gt;\w+)(?&lt;format&gt;:[^}]+)?}", RegexOptions.Compiled);
  </code></pre>
  
  <p><strong>How can I modify this regular expression to ignore escaped braces ?</strong> 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 ?</p>
  
  <hr>
  
  <p>For completeness, here's the full code of the <code>StringTemplate</code> class :</p>
  
  <pre><code>public class StringTemplate
  {
      private string _template;
      private static Regex _regex = new Regex(@"{(?&lt;key&gt;\w+)(?&lt;format&gt;:[^}]+)?}", 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&lt;string, object&gt; values)
      {
          if (values == null)
          {
              throw new ArgumentNullException("values");
          }
  
          Dictionary&lt;string, int&gt; indexes = new Dictionary&lt;string, int&gt;();
          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) =&gt;
          {
              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&lt;string, object&gt; MakeDictionary(object obj)
      {
          Dictionary&lt;string, object&gt; dict = new Dictionary&lt;string, object&gt;();
          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&lt;string, object&gt; values)
      {
          return new StringTemplate(template).Format(values);
      }
  
  
      public static string Format(string template, object values)
      {
          return new StringTemplate(template).Format(values);
      }
  }
  </code></pre>