3

假设我想询问用户他们希望某个输出采用什么格式,并且输出将包括填写字段。所以他们提供了类似这样的字符串:

"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}}”将被视为任何其他字符串,并被替换忽略。此外,如果他们决定将“{{{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

4 回答 4

3

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

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

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

关闭}只需要字段的结尾是一个简单的}. 语法中存在一些歧义,{FieldName1Value}}}可以将其解析为带有FieldName1Value(后跟文字})或的标记FieldName1Value}。正则表达式假定前者。(如果打算使用后者,则可以将其替换为}(?!}(}})*)

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

循环执行后,一个简单的:

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

可用于对文字{{}}字符进行转义。

于 2013-05-15T23:00:28.877 回答
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, "}});

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

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

于 2013-05-15T22:11:39.973 回答
1

我写了一个扩展方法,几乎​​可以满足您的要求,但是,虽然它确实使用双括号进行了转义,但它并没有像您建议的那样使用三重括号。这是方法(也在 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}}}}} 等呢?尽管这些情况不太可能发生,但仍需要有目的地处理。

于 2013-05-15T22:35:38.377 回答
0

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

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

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

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

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

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

于 2013-05-15T22:11:11.793 回答