Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a better way to count string format placeholders in a string in C#?

Tags:

string

c#

.net

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:

  • Regexes that count the number of placeholders doesn't account for literal braces ( {{0}} )
  • Counting placeholders doesn't account for repeated or skipped placeholders (e.g. "{0} has a {1} which also has a {1}")
like image 446
Damovisa Avatar asked Jun 04 '09 02:06

Damovisa


2 Answers

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.

like image 137
Joe Avatar answered Oct 03 '22 21:10

Joe


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

like image 37
MarekBaron Avatar answered Oct 03 '22 22:10

MarekBaron