2013-08-02 9 views
7

Mam przykład kodu, w którym MatchCollection wydaje się zawiesić program podczas próby użycia go z foreach.Czy MatchCollection może zawiesić program podczas próby iteracji?

ja parsowania css za pomocą klasy CSSParser:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text.RegularExpressions; 
using Helpers.Extensions; 

namespace Helpers.Utils 
{ 
    public class CSSParser 
    { 
     private readonly Dictionary<string, Dictionary<string, string>> 
      _dict = new Dictionary<string, Dictionary<string, string>>(); 

     private const string SelectorKey = "selector"; 
     private const string NameKey = "name"; 
     private const string ValueKey = "value"; 

     private const string GroupsPattern 
      = @"(?<selector>(?:(?:[^,{]+)\s*,?\s*)+)\{(?:(?<name>[^}:]+)\s*:\s*(?<value>[^};]+);?\s*)*\}"; 

     private const string CommentsPattern 
      = @"(?<!"")\/\*.+?\*\/(?!"")"; 

     private readonly Regex _pattern 
      = new Regex(GroupsPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline); 

     public CSSParser(string cssString) 
     { 
      var noCommentsString = Regex.Replace(cssString, CommentsPattern, ""); 
      var matches = _pattern.Matches(noCommentsString); 

      foreach (Match item in matches) 
      { 
       var selector = item.Groups[SelectorKey].Captures[0].Value.Trim(); 

       var selectorParts = selector.Split(',').Select(s=>s.Trim()); 
       foreach(var part in selectorParts) 
       { 
        if (!_dict.ContainsKey(part)) 
         _dict[part] = new Dictionary<string, string>(); 
       } 

       var classNameCaptures = item.Groups[NameKey].Captures; 
       var valueCaptures = item.Groups[ValueKey].Captures; 

       var count = item.Groups[NameKey].Captures.Count; 

       for (var i = 0; i < count; i++) 
       { 
        var className = classNameCaptures[i].Value.TrimIfNotNull(); 
        var value = valueCaptures[i].Value.TrimIfNotNull(); 

        foreach(var part in selectorParts) 
        { 
         _dict[part][className] = value; 
        } 
       } 
      } 
     } 

     public IEnumerable<KeyValuePair<string,string>> LookupValues(string selector) 
     { 
      IEnumerable<KeyValuePair<string,string>> result 
       = new KeyValuePair<string,string>[]{}; 
      if (_dict.ContainsKey(selector)) 
      { 
       var subdict = _dict[selector]; 

       result = subdict.ToList(); 
      } 

      return result; 
     } 

     public string LookupValue(string selector, string style) 
     { 
      string result = null; 
      if (_dict.ContainsKey(selector)) 
      { 
       var subdict = _dict[selector]; 

       if (subdict.ContainsKey(style)) 
        result = subdict[style]; 
      } 

      return result; 
     } 
    } 
} 

i współpracuje z wejściem jak ten:

 [TestMethod] 
     public void TestParseMultipleElementNames() 
     { 
      const string css = @"h1, h2, h3, h4, h5, h6 
{ 
    font-family: Georgia, 'Times New Roman', serif; 
    color: #006633; 
    line-height: 1.2em; 
    font-weight: normal; 
} 
"; 

      var parser = new CSSParser(css); 

      Assert.AreEqual("normal", parser.LookupValue("h4", "font-weight")); 
     } 

ale gdy uruchomię go z ciągiem css nie zawierające atrybuty:

 [TestMethod] 
     public void TestParseNoAttributesStyle() 
     { 
      const string css = @" 
#submenu-container 
{ 
} 
"; 

      var parser = new CSSParser(css); 

      Assert.IsFalse(parser.LookupValues("#submenu-container").Any()); 
     } 

program wisi na tej linii w CSSParser:

foreach (Match item in matches) 

Debuger przestaje oznaczać aktualnie uruchomioną linię, sam blok pętli nigdy nie zostanie osiągnięty.

Dlaczego MatchCollection zawiesza się w moim programie?

Dla kompletności:

namespace Helpers.Extensions 
{ 
    public static class StringExtension 
    { 
    public static string TrimIfNotNull(this string input) 
    { 
     return input != null ? input.Trim() : null; 
    } 
    } 
} 
+2

Nie jestem ekspertem od Regex, ale silnik Regex może utknąć lub zająć dużo czasu, jeśli ma wykonywać operacje z wyprzedzeniem i lookbehind. Projektowanie Regex w celu zminimalizowania tych pomoże. Być może ekspert Regex może ci pomóc w szczegółach. +1 =] – Sean

+1

Zatrzymaj debugger i spójrz na stos, aby ustalić, co się dzieje.Zaznacz "Pokaż kod zewnętrzny", aby zobaczyć wszystko. – usr

+1

a) Języki takie jak ten nie powinny * naprawdę * być przetwarzane przez Regex. b) dlaczego nie [użyć czegoś od ręki] (http://stackoverflow.com/q/512720/50776)? Prawdopodobnie będzie to znacznie szybsze i bardziej niezawodne i pozwoli Ci osiągnąć więcej w swoim projekcie niż zrobić to ręcznie. – casperOne

Odpowiedz

1

Twój Regex jest po prostu nieefektywny i spalenie procesora. Możesz to potwierdzić przez a) przyglądając się używanemu czasowi CPU i b) pauzując wielokrotnie debuggera i patrząc na stos (będzie w trzewiach silnika Regex).

+0

Dlaczego więc tylko puste bloki css są trudne do analizy? –

+1

Nie wiem. Naprawienie wyrażenia regularnego nie jest częścią pytania. Pytanie brzmi: "czy MatchCollection może się zawiesić". Odpowiedź: nie, tylko powoli. – usr

+1

Dlatego odpowiedź zostanie oznaczona jako rozwiązanie –

0

Zmieniłem regexp od:

private const string GroupsPattern 
    = @"(?<selector>(?:(?:[^,{]+)\s*,?\s*)+)\{(?:(?<name>[^}:]+)\s*:\s*(?<value>[^};]+);?\s*)*\}"; 

do:

private const string GroupsPattern 
    = @"(?<selector>(?:(?:[^,{]+)\s*,?\s*)+)\{\s*(?:(?<name>[^}:\s]+)\s*:\s*(?<value>[^};]+);?\s*)*\}"; 

i czas realizacji zszedł od 22 sekund do 1 ms.

+1

Pierwsze wyrażenie regularne nie jest takie samo, jak w oryginalnym wpisie - w grupie nazw znajduje się dodatkowe słowo "\ s". Nowe wyrażenie regularne przyniesie również inne wyniki niż oryginalne. Dla zabawy możesz też wypróbować go z następującym tekstem (dodano dodatkowe miejsce) 'h1, h2, h3, h4, h5, h6 {font-fa mily: Georgia, 'Times New Roman', serif ; kolor: # 006633; wysokość linii: 1.2em; font-weight: normal; } ' – Ykok

+0

Dzięki, zredagowałem post, aby wyświetlał poprawne informacje! Nigdy nie będę mieć spacji w nazwach atrybutów. –

1

O ile mogę powiedzieć .net przechodzi w wieczną pętlę, ponieważ próbuje różnych podejść z regexem, który posiadasz (the GroupsPattern) - Wierzę, że gdzieś się pomyli. Spojrzałem na to wyrażenie i o ile mogę powiedzieć, że można łatwo usunąć dwa z \s*, a mianowicie te, które pojawiają się przed grupami negacji odpowiednio [^,{]+ i [^}:]+, ponieważ już przechwytują spacje.

tak, aby zamiast:

private const string GroupsPattern = @"(?<selector>(?:(?:[^,{]+)\s*,?\s*)+)\{(?:(?<name>[^}:]+)\s*:\s*(?<value>[^};]+);?\s*)*\}"; 

Chciałbym mieć:

private const string GroupsPattern = @"(?<selector>(?:(?:[^,{]+),?\s*)+)\{(?:(?<name>[^}:]+):\s*(?<value>[^};]+);?\s*)*\}"; 

Teraz to wyrażenia regex, więc szanse na mnie o coś pomijane są raczej duże. Sądzę, że powoduje to również, że niektóre z nazwanych grup przechwytywania mogą mieć dodatkowe spacje (ale wydaje się, że i tak je przycinasz).

Mam nadzieję, że jest użyteczny. Mimo że zajmuje to trochę czasu, działa na podanym przykładzie.

+0

Dopasowanie wyrażeń regularnych było powolne, bez wiecznych pętli. Kiedy dokonałem pewnych modyfikacji, cssparser mógł przetworzyć plik css 10k w mgnieniu oka, które jest wystarczające dla moich potrzeb. Rozważę użycie twojego regexp. –

Powiązane problemy