如何使用StringBuilder进行多个不区分大小写的替换

时间:2014-08-29 19:15:57

标签: c# stringbuilder

我有一个(大)模板,想要替换多个值。替换需要不区分大小写。还必须能够拥有模板中不存在的密钥。

例如:

[TestMethod]
public void ReplaceMultipleWithIgnoreCaseText()
{
    const string template = "My name is @Name@ and I like to read about @SUBJECT@ on @website@, tag  @subject@";  
    const string expected = "My name is Alex and I like to read about C# on stackoverflow.com, tag C#";
    var replaceParameters = new List<KeyValuePair<string, string>>
    {
        new KeyValuePair<string, string>("@name@","Alex"),
        new KeyValuePair<string, string>("@subject@","C#"),
        new KeyValuePair<string, string>("@website@","stackoverflow.com"),
        // Note: The next key does not exist in template 
        new KeyValuePair<string, string>("@country@","The Netherlands"), 
    };
    var actual = ReplaceMultiple(template, replaceParameters);
    Assert.AreEqual(expected, actual);
}

public string ReplaceMultiple(
                  string template, 
                  IEnumerable<KeyValuePair<string, string>> replaceParameters)
{
    throw new NotImplementedException(
                  "Implementation needed for many parameters and long text.");
}

请注意,如果我有30个参数和一个大模板的列表,我不希望内存中有30个大字符串。使用StringBuilder似乎是一种选择,但也欢迎其他解决方案。

解决方案我尝试了但没有工作

此处找到的解决方案(C# String replace with dictionary)会在密钥不在收集中时引发异常,但我们的用户会犯错误,在这种情况下,我只想将wromg密钥保留在文本中。例如:

static readonly Regex re = new Regex(@"\$(\w+)\$", RegexOptions.Compiled);
static void Main2()
{
    // "Name" is accidentally typed by a user as "nam". 
    string input = @"Dear $nam$, as of $date$ your balance is $amount$"; 

    var args = new Dictionary<string, string>(
        StringComparer.OrdinalIgnoreCase) {
    {"name", "Mr Smith"},
    {"date", "05 Aug 2009"},
    {"amount", "GBP200"}};


    // Works, but not case insensitive and 
    // uses a lot of memory when using a large template
    // ReplaceWithDictionary many args
    string output1 = input;
    foreach (var arg in args)
    {
        output1 = output1.Replace("$" + arg.Key +"$", arg.Value);
    }

    // Throws a KeyNotFoundException + Only works when data is tokenized
    string output2 = re.Replace(input, match => args[match.Groups[1].Value]);
}

4 个答案:

答案 0 :(得分:4)

  

使用StringBuilder似乎是一种选择,但也欢迎使用其他解决方案。

由于你想要不区分大小写,我建议(非StringBuilder):

public static string ReplaceMultiple(
              string template, 
              IEnumerable<KeyValuePair<string, string>> replaceParameters)
{
    var result = template;

    foreach(var replace in replaceParameters)
    {
        var templateSplit = Regex.Split(result, 
                                        replace.Key, 
                                        RegexOptions.IgnoreCase);
        result = string.Join(replace.Value, templateSplit);
    }

    return result;
}

DotNetFiddle Example

答案 1 :(得分:3)

This is based off of Marc's answer the only real change is the check during the replacement and the boundary regex rule:

static readonly Regex re = new Regex(@"\b(\w+)\b", RegexOptions.Compiled);
static void Main(string[] args)
{
    string input = @"Dear Name, as of dAte your balance is amounT!";
    var replacements = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
    {
        {"name", "Mr Smith"},
        {"date", "05 Aug 2009"},
        {"amount", "GBP200"}
    };
    string output = re.Replace(input, match => replacements.ContainsKey(match.Groups[1].Value) ? replacements[match.Groups[1].Value] : match.Groups[1].Value);
}

这是5000次迭代测试基准测试,没有查看内存或其他任何内容。

test

替换功能是您已检查为已接受答案的功能。

答案 2 :(得分:0)

我想我可能会尝试一些东西。我在电子邮件模板中使用了类似的东西

    public string replace()
    {
        string appPath = Request.PhysicalApplicationPath;
        StreamReader sr = new StreamReader(appPath + "EmailTemplates/NewMember.txt");

        string template = sr.ReadToEnd();

        template = template.Replace("<%Client_Name%>",
            first_name.Text + " " + middle_initial.Text + " " + last_name.Text);

        //Add Customer data
        template = template.Replace("<%Client_First_Name%>", first_name.Text);
        template = template.Replace("<%Client_MI%>", middle_initial.Text);
        template = template.Replace("<%Client_Last_Name%>", last_name.Text);
        template = template.Replace("<%Client_DOB%>", dob.Text);

        return template;

    }

在模板内部,您可以使用&lt; %%&gt;等标记。作为你想要的价值观的占位符

希望这有帮助!

答案 3 :(得分:0)

Marc Gravell的答案:C# String replace with dictionary可以稍微更改一下,以便在找不到匹配时不会抛出异常。在这种情况下,它根本不会取代匹配。

如果要替换的字符串是标记化的,这就是解决方案:

static readonly Regex RegExInstance = new Regex(@"\$(\w+)\$", RegexOptions.Compiled);
public string  ReplaceWithRegEx(string template, Dictionary<string, string> parameters)
{
    return RegExInstance.Replace(template, match => GetNewValue(parameters, match));
}

private string GetNewValue(Dictionary<string, string> parameters, Match match)
{
    var oldValue = match.Groups[1].Value;
    string newValue;
    var found = parameters.TryGetValue(oldValue, out newValue);
    if (found)
    {
        return newValue;
    }
    var originalValue = match.Groups[0].Value;
    return originalValue;
}

我已经使用100.000字节字符串,7个密钥和数百个替换项测试了该解决方案。它使用的内存比字符串的长度多7倍。它只花了0.002秒。