2009-09-18 30 views
2

我写了一个名为StringTemplate类,允许像String.Format格式的对象,但其名称,而不是占位符索引。这里有一个例子:正则表达式的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

这工作得很好,当有一倍大括号,这是一个转义序列除外。期望的行为(其是相同的String.Format)如下所述:

  • “你好{名称}”应该格式化为的 “Hello World”
  • “你好{{名称}} “格式应为的 ”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); 
    } 
} 
+0

我最近自己走了这条路,最后建立一个解析占位符的简单状态机要比找出如何获得正则表达式更容易。另外,当我最终不得不将我的分隔符更改为方括号时(因为'FlowDocument'有一些没有很好记录的行为,而且我已经开始使用大括号),所以有很多问题我没有遇到如果我使用正则表达式,我会有。 – 2010-03-03 20:47:03

+0

是的,我应该使用状态机来代替......但无论如何,我的代码现在可以按照我的想法工作,所以我不会改变它,除非我必须;) – 2010-03-03 22:52:48

+0

@ThomasLevesque,你会发布你使用的代码是什么? – smartcaveman 2013-02-26 03:40:16

回答

1

您可以使用正则表达式匹配平衡对,然后找出如何处理大括号。请记住,.NET正则表达式不是“常规”的。

class Program { 
    static void Main(string[] args) { 
     var d = new Dictionary<string, string> { { "Name", "World" } }; 
     var t = new Test(); 
     Console.WriteLine(t.Replace("Hello {Name}", d)); 
     Console.WriteLine(t.Replace("Hello {{Name}}", d)); 
     Console.WriteLine(t.Replace("Hello {{{Name}}}", d)); 
     Console.WriteLine(t.Replace("Hello {{{{Name}}}}", d)); 
     Console.ReadKey(); 
    } 
} 

class Test { 

    private Regex MatchNested = new Regex(
     @"\{ (?> 
       ([^{}]+) 
       | \{ (?<D>) 
       | \} (?<-D>) 
      )* 
       (?(D)(?!)) 
      \}", 
      RegexOptions.IgnorePatternWhitespace 
      | RegexOptions.Compiled 
      | RegexOptions.Singleline); 

    public string Replace(string input, Dictionary<string, string> vars) { 
     Matcher matcher = new Matcher(vars); 
     return MatchNested.Replace(input, matcher.Replace); 
    } 

    private class Matcher { 

     private Dictionary<string, string> Vars; 

     public Matcher(Dictionary<string, string> vars) { 
      Vars = vars; 
     } 

     public string Replace(Match m) { 
      string name = m.Groups[1].Value; 
      int length = (m.Groups[0].Length - name.Length)/2; 
      string inner = (length % 2) == 0 ? name : Vars[name]; 
      return MakeString(inner, length/2); 
     } 

     private string MakeString(string inner, int braceCount) { 
      StringBuilder sb = new StringBuilder(inner.Length + (braceCount * 2)); 
      sb.Append('{', braceCount); 
      sb.Append(inner); 
      sb.Append('}', braceCount); 
      return sb.ToString(); 
     } 

    } 

} 
+0

谢谢加文!我知道平衡团队,但他们在这里不是必要的,因为开头和结束括号的数量不一定是相同的。这是一个合法的格式字符串:“Hello {{{Name}”;它将被格式化为“Hello {World”。然而,你检查替换代码中大括号的想法是相当不错的,我会研究它。 – 2009-09-18 20:27:59

+0

我最终使用了一种类似于你的技术,所以我接受你的答案。谢谢 ! – 2009-09-18 22:45:47

3

这可能是可能的正则表达式 - 但我并不相信,这将是最简单的解决方案保持。鉴于你真的只有感兴趣的大括号和冒号在这里(我认为),我会亲自避免使用正则表达式。

我构造令牌的序列,每一个要么是文字或格式字符串。通过沿着绳子走路并注意开启和关闭支架来构造这一点。然后评估序列只是一个串联令牌的问题,在适当的时候对每个令牌进行格式化。

再说我从来没有去过太多的正则表达式的粉丝 - 只是偶尔他们是美好的,但很多时候,他们觉得自己大材小用。也许有一些聪明的方法让他们做你想做的事情......

顺便说一句,你需要定义你想在大括号不适当匹配的情况下发生什么,例如,

{{Name} foo 
+0

感谢您的回答Jon!如果可能的话,我想坚持使用正则表达式,但我可能必须最终做出你的建议......关于无与伦比的大括号,我需要与String.Format相同的行为:在开始和结束时使用大括号占位符并不需要相同,但是奇偶校验必须相同 – 2009-09-18 20:12:22

3

奇偶校验通常很容易使用正则表达式来决定。例如,这是任何字符串以偶数A s比的表达,但不为奇数:

(AA)* 

因此,所有你需要做的是发现,只有奇数{匹配的表达s和} s。

{({{)* 
}(}})* 

(逃脱字符尽管)。所以加入这个想法,你目前的表现会产生像

{({{)*(?<key>\w+)(?<format>:[^}]+)?}(}})* 

东西但是,这不符合双方括号的基数。换句话说,{{{将匹配},因为它们都是奇怪的。正则表达式不能计算事物,因此您无法像找到想要的那样找到与基数相匹配的表达式。

真的,你应该做的是分析与自定义解析器读取字符串并在另一侧计数的{{{而不是实例的实例,以配合他们反对}实例,但不}}字符串。我想你会发现这是.NET中的字符串格式化程序在后台工作的原因,因为正则表达式不适合解析任何类型的嵌套结构。

或者您可以同时使用这两个想法:将潜在令牌与正则表达式匹配,然后使用快速检查结果匹配来验证其大括号余额。不过,这可能最终会变得混乱和间接。编写自己的解析器对于这种场景通常会更好。

+0

感谢Welbog,这看起来很有趣......您建议的正则表达式实际上不起作用(当它看到“Hello {{Name}}”时它只是匹配“{Name}”,忽略额外的大括号),但我认为你在正确的轨道上...我需要看看正则表达式文档,我可能会找到一个选项使其工作 – 2009-09-18 20:16:24

+0

哦,顺便说一句,正则表达式*可以*计数...有一个名为“平衡组”的构造,允许匹配嵌套模式。它的文档记录很差,但MSDN中有一个示例:http://msdn.microsoft.com/zh-cn/library/bs2twtah.aspx#BalancingGroupDefinitionExample。无论如何,这并不重要,在我的情况下,我只需要双方的平价相同 – 2009-09-18 20:22:02

0

我最终使用了一种类似于Gavin建议的技术。

我改变了正则表达式,使其匹配周围的占位符所有括号:

private static Regex _regex = new Regex(@"(?<open>{+)(?<key>\w+)(?<format>:[^}]+)?(?<close>}+)", RegexOptions.Compiled); 

我改变了逻辑MatchEvaluator所以其处理转义括号正确:

 MatchEvaluator evaluator = (m) => 
     { 
      if (m.Success) 
      { 
       string open = m.Groups["open"].Value; 
       string close = m.Groups["close"].Value; 
       string key = m.Groups["key"].Value; 
       string format = m.Groups["format"].Value; 

       if (open.Length % 2 == 0) 
        return m.Value; 

       open = RemoveLastChar(open); 
       close = RemoveLastChar(close); 

       int index = -1; 
       if (indexes.TryGetValue(key, out index)) 
       { 
        return string.Format("{0}{{{1}{2}}}{3}", open, index, format, close); 
       } 
       else 
       { 
        return string.Format("{0}{{{{{1}}}{2}}}{3}", open, key, format, close); 
       } 
      } 
      return m.Value; 
     }; 

我靠如果需要的话String.Format掷出FormatException。我做了几个单元测试,到目前为止它似乎工作正常...

谢谢大家的帮助!