I have a string on which I need to do some replacements. I have a Dictionary
where I have search-replace pairs defined. I have created fol
when using Marc Gravell's RegEx solution, first check if a token is available using i.e. ContainsKey, this to prevent KeyNotFoundException errors :
string output = re.Replace(zpl, match => { return args.ContainsKey(match.Groups[1].Value) ? arg[match.Groups[1].Value] : match.Value; });
when using the following slightly modified sample code (1st parameter has different name):
var args = new Dictionary<string, string>(
StringComparer.OrdinalIgnoreCase)
{
{"nameWRONG", "Mr Smith"},
{"date", "05 Aug 2009"},
{"AMOUNT", "GBP200"}
};
this produces the following:
"Dear $name$, as of 05 Aug 2009 your balance is GBP200"
Why not just check whether such key exists?
if exists, then remove the pair, otherwise, skip this step;
add the same key, but now with newly desired value.
// say, you have the following collection
var fields = new Dictionary<string, string>();
fields.Add("key1", "value1");
fields.Add("key2", "value2");
fields.Add("key3", "value3");
// now, you want to add a pair "key2"/"value4"
// or replace current value of "key2" with "value4"
if (fields.ContainsKey("key2"))
{
fields.Remove("key2");
}
fields.Add("key2", "value4");
Seems reasonable to me, except for one thing: it's order-sensitive. For instance, take an input string of "$x $y" and a replacement dictionary of:
"$x" => "$y"
"$y" => "foo"
The results of the replacement are either "foo foo" or "$y foo" depending on which replacement is performed first.
You could control the ordering using a List<KeyValuePair<string, string>>
instead. The alternative is to walk through the string making sure you don't consume the replacements in further replace operations. That's likely to be a lot harder though.
If the data is tokenized (i.e. "Dear $name$, as of $date$ your balance is $amount$"), then a Regex
can be useful:
static readonly Regex re = new Regex(@"\$(\w+)\$", RegexOptions.Compiled);
static void Main() {
string input = @"Dear $name$, as of $date$ your balance is $amount$";
var args = new Dictionary<string, string>(
StringComparer.OrdinalIgnoreCase) {
{"name", "Mr Smith"},
{"date", "05 Aug 2009"},
{"amount", "GBP200"}
};
string output = re.Replace(input, match => args[match.Groups[1].Value]);
}
However, without something like this, I expect that your Replace
loop is probably about as much as you can do, without going to extreme lengths. If it isn't tokenized, perhaps profile it; is the Replace
actually a problem?
The correct tool for this particular task is Mustache, a simple standard logicless templating language.
There are many implementations. For this example, I'm going to use stubble:
var stubble = new StubbleBuilder().Build();
var dataHash = new Dictionary<string, Object>()
{
{"Foo","My Foo Example"},
{"Bar",5}
};
var output = stubble.Render(
"Hey, watch me replace this: {{Foo}} ... with example text. Also {{bar}} is 5"
, dataHash
);
Here's a lightly re-factored version of @Marc's great answer, to make the functionality available as an extension method to Regex:
static void Main()
{
string input = @"Dear $name$, as of $date$ your balance is $amount$";
var args = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
args.Add("name", "Mr Smith");
args.Add("date", "05 Aug 2009");
args.Add("amount", "GBP200");
Regex re = new Regex(@"\$(\w+)\$", RegexOptions.Compiled);
string output = re.replaceTokens(input, args);
// spot the LinqPad user // output.Dump();
}
public static class ReplaceTokensUsingDictionary
{
public static string replaceTokens(this Regex re, string input, IDictionary<string, string> args)
{
return re.Replace(input, match => args[match.Groups[1].Value]);
}
}