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

本文关键字:字符串 最佳 方式 转义字符 转义 用户 | 更新日期: 2023-09-27 18:13:52

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

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

任何由{}绑定的东西都应该是表中的列名,它们将被替换为我正在编写的代码的存储值。看起来很简单,我可以做一个字符串。替换任何匹配模式"{" + FieldName + "}"的实例。但是,如果我还想给用户提供使用转义的选项,这样他们就可以像使用其他字符串一样使用括号。我认为它们提供了"{{"或"}}"来转义括号——这对它们来说既好又容易。因此,他们可以提供如下内容:

"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}处理时,它被当作字符串处理。

谢谢你的帮助。

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

通过展开正则表达式,可以容纳文字的存在。

 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("}}", "}")

可用于取消文字{{}}字符的转义。

最简单的方法是使用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, "}});

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

你怎么做在很大程度上取决于你调用它的频率,以及你真正需要它有多快。

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

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}}}}}等等呢?虽然这些情况不太可能发生,但仍需要有目的地处理。

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