I have a template string and an array of parameters that come from different sources but need to be matched up to create a new "filled-in" string:
string templateString = GetTemplate(); // e.g. "Mr {0} has a {1}"
string[] dataItems = GetDataItems(); // e.g. ["Jones", "ceiling cat"}
string resultingString = String.Format(templateString, dataItems);
// e.g. "Mr Jones has a ceiling cat"
With this code, I'm assuming that the number of string format placeholders in the template will equal the number of data items. It's generally a fair assumption in my case, but I want to be able to produce a resultingString
without failing even if the assumption is wrong. I don't mind if there are empty spaces for missing data.
If there are too many items in dataItems
, the String.Format
method handles it fine. If there aren't enough, I get an Exception.
To overcome this, I'm counting the number of placeholders and adding new items to the dataItems array if there aren't enough.
To count the placeholders, the code I'm working with at the moment is:
private static int CountOccurrences(string haystack)
{
// Loop through all instances of the string "}".
int count = 0;
int i = 0;
while ((i = text.IndexOf("}", i)) != -1)
{
i++;
count++;
}
return count;
}
Obviously this makes the assumption that there aren't any closing curly braces that aren't being used for format placeholders. It also just feels wrong. :)
Is there a better way to count the string format placeholders in a string?
A number of people have correctly pointed out that the answer I marked as correct won't work in many circumstances. The main reasons are:
{{0}}
)"{0} has a {1} which also has a {1}"
)Counting the placeholders doesn't help - consider the following cases:
"{0} ... {1} ... {0}" - needs 2 values
"{1} {3}" - needs 4 values of which two are ignored
The second example isn't farfetched.
For example, you may have something like this in US English:
String.Format("{0} {1} {2} has a {3}", firstName, middleName, lastName, animal);
In some cultures, the middle name may not be used and you may have:
String.Format("{0} {2} ... {3}", firstName, middleName, lastName, animal);
If you want to do this, you need to look for the format specifiers {index[,length][:formatString]} with the maximum index, ignoring repeated braces (e.g. {{n}}). Repeated braces are used to insert braces as literals in the output string. I'll leave the coding as an exercise :) - but I don't think it can or should be done with Regex in the most general case (i.e. with length and/or formatString).
And even if you aren't using length or formatString today, a future developer may think it's an innocuous change to add one - it would be a shame for this to break your code.
I would try to mimic the code in StringBuilder.AppendFormat (which is called by String.Format) even though it's a bit ugly - use Lutz Reflector to get this code. Basically iterate through the string looking for format specifiers, and get the value of the index for each specifier.
Merging Damovisa's and Joe's answers. I've updated answer afer Aydsman's nad activa's comments.
int count = Regex.Matches(templateString, @"(?<!\{)\{([0-9]+).*?\}(?!})") //select all placeholders - placeholder ID as separate group
.Cast<Match>() // cast MatchCollection to IEnumerable<Match>, so we can use Linq
.Max(m => int.Parse(m.Groups[1].Value)) + 1; // select maximum value of first group (it's a placegolder ID) converted to int
This approach will work for templates like:
"{0} aa {2} bb {1}" => count = 3
"{4} aa {0} bb {0}, {0}" => count = 5
"{0} {3} , {{7}}" => count = 4
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With