8

foreach で使用しようとすると、MatchCollection がプログラムをハングさせたように見えるコード例があります。

クラスCSSParserを使用してcssを解析しています:

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;
        }
    }
}

次のような入力で問題なく動作します。

        [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"));
        }

しかし、属性を含まないcss文字列で実行すると:

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

          var parser = new CSSParser(css);

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

プログラムは、CSSParser の次の行でハングしています。

foreach (Match item in matches)

デバッガーは現在実行中の行のマークを停止し、ループ ブロック自体に到達することはありません。

MatchCollection がプログラムをハングアップさせるのはなぜですか?

完全を期すために:

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

3 に答える 3

1

あなたの正規表現は非効率的でCPUを燃やしています。これを確認するには、a) 使用された CPU 時間を調べ、b) デバッガーを繰り返し一時停止し、スタック (正規表現エンジンの内部にあります) を調べます。

于 2013-08-02T12:23:46.890 に答える
1

私が知る限り、.net は、あなたが持っている正規表現 (GroupsPattern のもの) を使ってさまざまなアプローチを試みるため、永久ループに入ります - どこかで間違いを犯していると思います。私はこの正規表現を見てきましたが、私が知る限り、2つの\s*、つまりそれぞれ否定グループの前に[^,{]+あり[^}:]+、すでにスペースをキャプチャしているため、簡単に削除できます。

つまり、代わりに:

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

私が持っているだろう:

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

これは正規表現なので、何かを見落としている可能性がかなり高いです。また、これにより、名前付きキャプチャ グループの一部に余分なスペースが含まれている可能性もあると思います (ただし、とにかくそれらをトリミングしているようです)。

使えることを願っています。まだかなり時間がかかりますが、あなたが示した例で動作します。

于 2013-08-02T12:56:09.293 に答える
0

正規表現を次のように変更しました:

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

に:

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

実行時間は 22 秒から 1 ミリ秒に短縮されました。

于 2013-08-02T12:33:58.113 に答える