为用户提供转义字符串的最佳方法

时间:2013-05-15 21:36:56

标签: c# regex string escaping

假设我想询问用户他们想要某个输出的格式,输出将包括填充字段。所以他们提供了类似这样的字符串:

"Output text including some field {FieldName1Value} and another {FieldName2Value} and so on..."

{}绑定的任何内容都应该是表中的列名,它们将用我正在编写的代码替换为存储的值。看起来很简单,我可以在任何匹配模式“{”+ FieldName +“}”的实例上执行string.Replace。但是,如果我还想让用户选择使用转义,那么他们可以像任何其他字符串一样使用括号。我在想他们提供“{{”或“}}”来逃避这个支架 - 对他们来说很容易。所以,他们可以提供类似的东西:

"Output text including some field {FieldName1Value} and another {FieldName2Value} but not this {{FieldName2Value}}"

但现在“{{FieldName2Value}}”将被视为任何其他字符串,并被Replace忽略。此外,如果他们决定将“{{{FieldName2Value}}}”这样的内容放在三个括号中,那么代码会将其解释为用括号括起来的字段值等等。

这是我被卡住的地方。我正在尝试使用RegEx,并想出了这个:

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
    string format = (string)values[0];
    ObservableCollection<CalloutFieldAliasMap> oc = (ObservableCollection<CalloutFieldAliasMap>)values[1];

    foreach (CalloutFieldMap map in oc)
        format = Regex.Replace(format, @"(?<!{){" + map.FieldName + "(?<!})}", " " + map.FieldAlias + " ", RegexOptions.IgnoreCase);

    return format;
}

这适用于双括号{{}}的情况,但如果有三个,即{{{}}},则无效。三个括号在被视为{FieldValue}时被视为字符串。

感谢您的帮助。

4 个答案:

答案 0 :(得分:3)

通过扩展正则表达式,可以适应文字的存在。

 format = Regex.Replace(format, 
      @"(?<!([^{]|^){(?:{{)*){" + Regex.Escape(map.FieldName) + "}", 
      String.Format(" {0} ", map.FieldAlias),
      RegexOptions.IgnoreCase | RegexOptions.Compiled);

表达式的第一部分(?<!([^{]|^){(?:{{)*){指定{前面必须有偶数{个字符,以便标记字段标记的开头。因此,{FieldName}{{{FieldName}将表示字段名称的开头,而{{FieldName}{{{{FieldName}则不会。

结束}只需要字段的结尾为简单}。语法中存在一些含糊之处,因为{FieldName1Value}}}可以被解析为带有FieldName1Value的标记(后跟文字})或FieldName1Value}。正则表达式假设前者。 (如果是后者,则可以用}(?!}(}})*)替换它。

其他一些说明。我添加了Regex.Escape(map.FieldName),以便字段名称中的所有字符都被视为文字;并添加了RegexOptions.Compiled标志。 (因为这是一个复杂的表达式并在循环中执行,所以它是编译的好选择。)

循环执行后,简单:

format = format.Replace("{{", "{").Replace("}}", "}")

可用于取消文字{{}}字符。

答案 1 :(得分:1)

最简单的方法是使用String.Replace将双括号替换为用户不能(或几乎肯定不会)输入的字符序列。然后替换你的字段,最后将替换转换回双括号。

例如,给定:

string replaceOpen = "{x"; // 'x' should be something like \u00ff, for example
string replaceClose = "x}";

string template = "Replace {ThisField} but not {{ThatField}}";

string temp = template.Replace("{{", replaceOpen).Replace("}}", replaceClose);
string converted = temp.Replace("{ThisField}", "Foo");

string final = converted.Replace(replaceOpen, "{{").Replace(replaceClose, "}});

它不是特别漂亮,但它很有效。

你如何去做将在很大程度上取决于你多久调用一次,以及你真正需要多快的速度。

答案 2 :(得分:1)

我有一个我写的扩展方法几乎可以满足您的要求,但是,虽然它确实使用双括号进行转义,但它不会像您建议的那样执行三重括号。这是方法(也在https://github.com/benallred/Icing/blob/master/Icing/Icing.Core/StringExtensions.cs的GitHub上):

private const string FormatTokenGroupName = "token";
private static readonly Regex FormatRegex = new Regex(@"(?<!\{)\{(?<" + FormatTokenGroupName + @">\w+)\}(?!\})", RegexOptions.Compiled);
public static string Format(this string source, IDictionary<string, string> replacements)
{
    if (string.IsNullOrWhiteSpace(source) || replacements == null)
    {
        return source;
    }

    string replaced = replacements.Aggregate(source,
        (current, pair) =>
            FormatRegex.Replace(current,
                new MatchEvaluator(match =>
                    (match.Groups[FormatTokenGroupName].Value == pair.Key
                        ? pair.Value : match.Value))));

    return replaced.Replace("{{", "{").Replace("}}", "}");
}

用法:

"This is my {FieldName}".Format(new Dictionary<string, string>() { { "FieldName", "value" } });

如果你添加这个更容易:

public static string Format(this string source, object replacements)
{
    if (string.IsNullOrWhiteSpace(source) || replacements == null)
    {
        return source;
    }

    IDictionary<string, string> replacementsDictionary = new Dictionary<string, string>();

    foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(replacements))
    {
        string token = propertyDescriptor.Name;
        object value = propertyDescriptor.GetValue(replacements);

        replacementsDictionary.Add(token, (value != null ? value.ToString() : String.Empty));
    }

    return Format(source, replacementsDictionary);
}

用法:

"This is my {FieldName}".Format(new { FieldName = "value" });

此方法的单元测试位于https://github.com/benallred/Icing/blob/master/Icing/Icing.Tests/Core/TestOf_StringExtensions.cs

如果这不起作用,那么您的理想解决方案对三个以上的括号有什么作用?换句话说,如果{{{FieldName}}}变为{value},那么{{} {{FieldName}}}}成为?那么{{{{{FieldName}}}}}等等呢?虽然这些情况不太可能,但仍需要有目的地处理。

答案 3 :(得分:0)

RegEx不会执行您想要的操作,因为它只知道它的当前状态以及可用的转换。它没有记忆的概念。您尝试解析的语言不规则,因此您永远无法编写RegEx来处理一般情况。您需要i个表达式,其中i是匹配大括号的数量。

这背后有很多理论,如果你很好奇,我会在底部提供一些链接。但基本上你要解析的语言是无上下文的,并且为了实现一般解决方案,你需要建模一个下推自动机,它使用一个堆栈来确保一个开括号具有匹配的右括号(是的,这是为什么大多数语言都有匹配的大括号。)

每次遇到{时,都会把它放在堆栈上。如果遇到},则从堆栈中弹出。清空堆栈时,您将知道已到达字段的末尾。当然,这是问题的一个主要简化,但如果你正在寻找一个通用的解决方案,它应该让你朝着正确的方向前进。

http://en.wikipedia.org/wiki/Regular_language

http://en.wikipedia.org/wiki/Context-free_language

http://en.wikipedia.org/wiki/Pushdown_automaton