I have a (large) template and want to replace multiple values. The replacement needs to be done case insensitive. It must also be possible to have keys that does not exist in the template.
For example:
[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.");
}
Note that if I have a list of 30 parameters and a large template, I do not want 30 large strings in memory. Using a StringBuilder seems to be an option, but other solutions are also welcome.
Solution I tried but did not work
Solution found here (C# String replace with dictionary) throws an exception when a key is not in the colletion, but our users makes mistakes and in that case I want to just leave the wromg key in the text. Example:
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]);
}
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);
}
And here is a 5000 iterations test benchmark, have not looked at memory or anything else.

Replacement function is the one you have checked as the accepted answer.
Using a StringBuilder seems to be an option, but other solutions are also welcome.
Since you want case insensitive, I'd suggest (non 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;
}
I think I might have something you could try. I used something similar to it for email templates
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;
}
Inside of your template you can have tags such as <% %> as place holders for the values you want
Hope this helps!
The answer of Marc Gravell: C# String replace with dictionary can be changed an little bit so it does not throws an exception when the match can not be found. In this case it simply does not replace the match.
In case the string to be replace is tokenized, this is the solution:
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;
}
I have tested the solution with a 100.000 bytes string, 7 keys and hundreds of replacements. It uses 7 times more memory then the lenght of the string. And it took only 0.002 seconds.
来源:https://stackoverflow.com/questions/25574832/how-to-do-a-multiple-case-insensitive-replace-using-a-stringbuilder