C# .NET Replace several different chars with strings
Can use this for escaping XML values, etc.
The String.Replace()
function can be used to replace all instance one character with another character ( String.Replace(char, char)
), or all instance of one substring with another string ( String.Replace(char, char)
). But, it is sometimes necessary to replace all instance of several different characters with a different string for each of those characters. The article describes a fairly efficient way to do that in C#.
The first issue is how to specify the characters that need to be replaced, and the strings that should replace them. Should two arrays of the same length (or lists, or collections, or whatever) be passed in, where one is a char[]
and one is a string[]
? Or, perhaps a Dictionary<char, string>
should be passed in, or maybe a list or array of KeyValuePair<char, string>
.
I decided to use a params array of Tuple<char, string>
objects passed into a string extension method with the name XReplace
. If you have not yet read my article entitled "C# .NET Create KeyValuePairs and Tuples the Easy Way 'a'.Tpl("ABC"): More with Extension Methods and Implied Generic Arguments", now would be a good time to do so!
Using the Tpl
extension method I created for that article, the new XReplace
method can be used to do things like escape XML:
var xmlEscapedValue = value.XReplace(
'<'.Tpl("<"),
'>'.Tpl(">"),
'\"'.Tpl("""),
'\''.Tpl("'"),
'&'.Tpl("&"));
If everything is working as expected, that code above should yield the same result as the code below.
var xmlEscapedValue = SecurityElement.Escape(value);
Of course, for XML I would probably just go ahead and use SecurityElement.Escape(value)
. But, the fact that they should both do the same thing provides a nice opportunity to check both functionality and performance. I have found them to be functional the same, and of similar performance. Additionally, I have found that the XReplace
extension method I am about to show only takes about 1/4 the time of the following code:
var xmlEscapedValue = value.Replace("&", "&").Replace("'", "'").Replace("\"", """).Replace(">", ">").Replace("<", "<");
And now, without further ado, here is the extension method you have been waiting for.
public static class StringExtensions
{
public static string XReplace(
this string value,
params Tuple<char, string>[] replaceSpecs)
{
// Short circuit if the value and/or array of replaceSpecs is null or empty.
if (string.IsNullOrEmpty(value)
|| (null == replaceSpecs)
|| (replaceSpecs.Length < 1) )
{
return value;
}
// Trivial case if there is exactly one replaceSpec. In this case, can just call
// into the standard String.Replace() function from the framework.
if (replaceSpecs.Length == 1)
{
var onlyReplaceSpec = replaceSpecs[0];
return value.Replace(
onlyReplaceSpec.Item1.ToString(),
onlyReplaceSpec.Item2);
}
// Looks like this needs to be done the fun way!
//
// Create a char[] containing all of the chars that need to be replaced.
var charsToReplaceAsArray = new char[replaceSpecs.Length];
for (int index = replaceSpecs.Length - 1; index >= 0; index--)
{
charsToReplaceAsArray[index] = replaceSpecs[index].Item1;
}
// Now create a string from that char[].
var charsToReplaceAsString = new string(charsToReplaceAsArray);
// Initialize some loop variables
var sb = new StringBuilder();
bool hasReplacedChars = false;
int startOfSearchIndex = 0;
int nextHitIndex;
// Loop while the value string has more chars from the char[] that still need
// to be processed.
while ( (nextHitIndex = value.IndexOfAny(charsToReplaceAsArray, startOfSearchIndex)) >= 0)
{
// Set a flag to indicate one or more characters has been replaced.
hasReplacedChars = true;
// If necessary, copy all of the characters between the previous hit (or start
// of value string) and current hit.
if (nextHitIndex > startOfSearchIndex)
{
sb.Append(value.Substring(
startOfSearchIndex,
nextHitIndex - startOfSearchIndex));
}
// Replace the char at the current "hit" position with the corresponding string.
// Note that the IndexOf() on the string representation of characters to be
// replaced provides a fast, convenient way to find the index of the tuple that
// specs the char to be replaced and string to replace that char since the
// tuple, char[] and that string all have the chars from the replaceSpecs in the
// same order.
sb.Append(replaceSpecs[charsToReplaceAsString.IndexOf(
value[nextHitIndex])].Item2
);
// On the next iteration of the loop, start at the character after the current
// "hit"position.
startOfSearchIndex = 1 + nextHitIndex;
}
// If no chars were replaced, then there is really nothing to do so just return
// the original value.
if (!hasReplacedChars)
{
return value;
}
// Looks like at least one char had to be replaced. There may still be some chars
// between the last "hit" position and the end of the string that need to be copied.
if (startOfSearchIndex < value.Length)
{
sb.Append(value.Substring(
startOfSearchIndex,
value.Length - startOfSearchIndex
));
}
// Now combine it all together and return the new string.
return sb.ToString();
}
}
Programmer, Engineer