2009-09-18 65 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。我做了幾個單元測試,到目前爲止它似乎工作正常...

謝謝大家的幫助!