【问题标题】:c# replace custom tagsc#替换自定义标签
【发布时间】:2011-09-21 10:53:43
【问题描述】:

我有一个类似于堆栈溢出的文本编辑器。我正在处理 c# 中的文本字符串,但也允许用户使用自定义标记来格式化其中的文本。比如……

<year /> will output the current year.
"Hello <year /> World" would render Hello 2012 World

我想做的是创建一个正则表达式来搜索字符串中是否出现&lt;year /&gt; 并替换它。除此之外,我还想为标签添加属性并能够将它们提取为&lt;year offset="2" format="5" /&gt;。我不擅长 RegEx,但希望有人知道如何做到这一点?

谢谢

【问题讨论】:

  • 您的文件实际上是 XML 吗?这样会容易很多...
  • 你需要转义字符,你的标记没有通过。
  • 正则表达式恰好是解决方案的一部分这一次并不意味着它应该是问题的一部分。正则表达式是 BAD BAD BAD BAD。 不要再认为正则表达式是一种解决方案,“它从来都不是——除非它是唯一的解决方案”。下次请以更一般的方式提出问题 - 并且可能会说您不能使用 XmlReader 等。通常,您应该更害怕正则表达式,而不是强盗用上膛的枪指着您的头。好吗?
  • 谢谢乔纳森。我认为这将是镇上唯一可以做我想做的事情的解决方案。我想我应该问什么是格式化文本以传递属性和提取值的最佳方法。感谢您的建议。
  • Regex 会让你继续前进,但最终我的回答确实有一些有趣的边界情况(如果你使用 &lt;year&gt;&lt;foo /&gt;&lt;/year&gt; 会发生什么?);并且手写的解析器/替换器将更加健壮、可靠和可预测。

标签: c# regex tags string-formatting


【解决方案1】:

理想情况下,您不应该为此使用正则表达式;但是看到 Html Agility Pack 没有 HtmlReader 我想你必须这样做。

话虽如此,看看其他标记解决方案,他们经常使用正则表达式模式列表和相关替换 - 所以我们不应该写一个“一般”案例(例如 &lt;([A-Z][A-Z0-9]*)&gt;.*?&lt;/\1&gt; 在这里做错了, 而我们想要&lt;year&gt;.*?&lt;/year&gt;)。

最初您可能会创建一个类来保存有关已识别令牌的信息,例如:

public class Token
{
    private Dictionary<string, string> _attributes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    public string InnerText { get; private set; }

    public string this[string attributeName]
    {
        get
        {
            string val;
            _attributes.TryGetValue(attributeName, out val);
            return val;
        }
    }

    public Token(string innerText, IEnumerable<KeyValuePair<string, string>> values)
    {
        InnerText = innerText;
        foreach (var item in values)
        {
            _attributes.Add(item.Key, item.Value);
        }
    }

    public int GetInteger(string name, int defaultValue)
    {
        string val;
        int result;
        if (_attributes.TryGetValue(name, out val) && int.TryParse(val, out result))
            return result;
        return defaultValue;
    }
}

现在我们需要创建正则表达式。例如,匹配 year 元素的正则表达式如下所示:

<Year(?>\s*(?<aname>\w*?)\s*=\s*"(?<aval>[^"]*)"\s*)*>(?<itext>.*?)</Year>

所以我们可以将其概括为:

<{0}\s*(?>(?<aname>\w*?)\s*=\s*"(?<aval>[^"]*)"\s*)*>(?<itext>.*?)</{0}>
<{0}\s*(?>(?<aname>\w*?)\s*=\s*"(?<aval>[^"]*)"\s*)*/>

鉴于这些通用标签正则表达式,我们可以编写标记类:

public class MyMarkup
{
    // These are used to build up the regex.
    const string RegexInnerText = @"<{0}\s*(?>(?<aname>\w*?)\s*=\s*""(?<aval>[^""]*)""\s*)*>(?<itext>.*?)</{0}>";
    const string RegexNoInnerText = @"<{0}\s*(?>(?<aname>\w*?)\s*=\s*""(?<aval>[^""]*)""\s*)*/>";

    private static LinkedList<Tuple<Regex, MatchEvaluator>> _replacers = new LinkedList<Tuple<Regex, MatchEvaluator>>();

    static MyMarkup()
    {
        Register("year", false, tok =>
        {
            var count = tok.GetInteger("digits", 4);
            var yr = DateTime.Now.Year.ToString();
            if (yr.Length > count)
                yr = yr.Substring(yr.Length - count);
            return yr;
        });
    }

    private static void Register(string tagName, bool supportsInnerText, Func<Token, string> replacement)
    {
        var eval = CreateEvaluator(replacement);

        // Add the no inner text variant.
        _replacers.AddLast(Tuple.Create(CreateRegex(tagName, RegexNoInnerText), eval));
        // Add the inner text variant.
        if (supportsInnerText)
            _replacers.AddLast(Tuple.Create(CreateRegex(tagName, RegexInnerText), eval));
    }

    private static Regex CreateRegex(string tagName, string format)
    {
        return new Regex(string.Format(format, Regex.Escape(tagName)), RegexOptions.Compiled | RegexOptions.IgnoreCase);
    }

    public static string Execute(string input)
    {
        foreach (var replacer in _replacers)
            input = replacer.Item1.Replace(input, replacer.Item2);
        return input;
    }

    private static MatchEvaluator CreateEvaluator(Func<Token, string> replacement)
    {
        return match =>
        {
            // Grab the groups/values.
            var aname = match.Groups["aname"];
            var aval = match.Groups["aval"];
            var itext = match.Groups["itext"].Value;

            // Turn aname and aval into a KeyValuePair.
            var attrs = Enumerable.Range(0, aname.Captures.Count)
                .Select(i => new KeyValuePair<string, string>(aname.Captures[i].Value, aval.Captures[i].Value));

            return replacement(new Token(itext, attrs));
        };
    }
}

这都是非常艰巨的工作,但它应该让您对自己应该做什么有一个很好的了解。

【讨论】:

    【解决方案2】:

    string.Replace 足以满足第一个要求 - 不需要 RegEx。

    string.Replace(myString, "<year />", @"<year offset=""2"" /">")
    

    为了提取属性值——你可以split on ":

    var val = @"<year offset=""2"" /">".Split('"')[1];
    

    更新(跟随 cmets):

    您可以尝试使用Html Agility Pack 来解析和操作文本。它在 HTML 片段上运行良好 - 良好且格式错误,但我不确定它如何处理自定义标签(值得一试)。 可能有点过头了。

    【讨论】:

    • 我认为他不需要用另一种语法替换一种语法,而是要同时处理它们(如果存在,则在第二种语法中提取参数):)
    • 是的,我也需要提取属性值
    • 我还需要为标签添加多个属性。
    • @codemonkey - 请注意问题中所有您的要求...我们不会读心术。
    • 直到我看到你的解决方案,它只适用于一个属性,我才想到添加它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-11
    • 1970-01-01
    • 2019-04-20
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 2020-03-21
    相关资源
    最近更新 更多