Solved

Need a regex to extract all parameters from string.Format text

Posted on 2011-03-03
7
417 Views
Last Modified: 2012-08-13
I want to create a SafeStringFormat which makes sure the parameter count matches the embedded parameters in the string text for a String.Format.

I'm guessing regex is the most efficient way to go; could somebody give me the code for extracting the parameters?

Something like this:

public string SafeStringFormat(string text, params object[] parms) {

    string[] embeddedParameters = <use regex to extract parameters from text>
    foreach (object o in parms) {
         if (<o doesn't corresponds at least one of the parameters in embeddedParameters>) {
             <throw exception saying that parm[#] is not implemented in <text>>
         }
     }
     foreach (string s in embeddedParameters) {
         if (<s doesn't correspond to one of the objects in parms>) {
             <throw exception saying s is an invalid parameter>
         }
     }
     return string.Format(text, parms);
}

It's the
string[] embeddedParameters = <use regex to extract parameters from text>
part of the code that I need help with...
0
Comment
Question by:FrancineTaylor
  • 3
  • 2
  • 2
7 Comments
 
LVL 7

Expert Comment

by:jdavistx
ID: 35031265
I would think a regex as simple as
(\{\d+\})

Open in new window

would give you your matches, but you'd have to also validate that your numbers are parameter numbers are sequential, and start at 0.

Not to be unhelpful, but I believe you'll get a compiler error if you try to use the normal String.Format() with invalid parameters.  You aren't really making it "safe" with what you've set out initially.

Also, I would think that you wouldn't want to throw exceptions in this way.  I've always considered Exceptions as things that are... exceptions, and not situations that can't be appropriately handled.  An exception would be some program-catastrophic event that no code would be able to accommodate.  In your sample, you could perform preemptive tests that would circumvent those "exceptions" from ever occurring.
0
 
LVL 7

Expert Comment

by:jdavistx
ID: 35031277
If only I could edit!

I would think a regex as simple as

1:

      

(\{\d+\})

Toggle HighlightingOpen in New WindowSelect All

would give you your matches, but you'd have to also validate that your parameter numbers are sequential, and start at 0.

Not to be unhelpful, but I believe you'll get a compiler error if you try to use the normal String.Format() with invalid parameters.  You aren't really making it "safe" with what you've set out initially.

Also, I would think that you wouldn't want to throw exceptions in this way.  I've always considered Exceptions as things that are... exceptions, and not situations that can be appropriately handled.  An exception would be some program-catastrophic event that no code would be able to accommodate.  In your sample, you could perform preemptive tests that would circumvent those "exceptions" from ever occurring.
0
 
LVL 33

Expert Comment

by:Todd Gerbert
ID: 35031357
I think "[^\{]\{(\d+)[^}]*}[^}]" will work better - as you need to account for two consecutive curly-braces being treated as an escape, meaning {{0} or {0}} should not be treated as a parameter position.

I also know String.Format will throw an exception if there aren't enough objects to fill all the {0} placeholders, but the reverse of that is not true, e.g. String.Format("{0}", "one", "two", "three") will not raise exception.

I think it is also appropriate to raise an exception here, since this method should handle just it's assigned task and not need to worry about how or if to correct the error.  The code calling this method should wrap it in a try/catch block and handle errors there if necessary.

The numbers in the {0} placeholders don't need to be sequential, although the parameter array must have at least N+1 elements, where N is the highest numbered placeholder.

Regex regex = new Regex(@"[^\{]\{(\d+)[^}]*}[^}]");

string test = "Hello {0}, {{ , {1:D}, }}, {{3}, {4}}";
MatchCollection matches = regex.Matches(test);

Open in new window

0
How our DevOps Teams Maximize Uptime

Our Dev teams are like yours. They’re continually cranking out code for new features/bugs fixes, testing, deploying, responding to production monitoring events and more. It’s complex. So, we thought you’d like to see what’s working for us. Read the use case whitepaper.

 
LVL 33

Expert Comment

by:Todd Gerbert
ID: 35031472
On exceptions...here the calling code handles the exception - you wouldn't want for SafeStringFormat to be showing message boxes and such.  SafeStringFormat might end up in a class library, in which case you have no idea how exceptions should be handled in future circumstances.

Alternatively, you could have the SafeStringFormat return a bool or int instead of raising an exception - though I think raising the exception would be more in line with the preferred .Net model.

Also maybe consider one method that raises exceptions, and another that returns a bool - a la Int32.Parse() and Int32.tryParse()

static void Main()
{
retry:
  try
  {
    SafeStringFormat("Hello {0}", "one", "two", "three");
  }
  catch (InvalidFormatException ex)
  {
    if (MessageBox.Show("Invalid format, try again?") == DialogResult.Yes)
      goto retry;
  }
}

static void SafeStringFormat(string format, params object[] args)
{
  if (args.Length > NumberOfPlaceHolders)
    throw new InvalidFormatException("Not enough placeholders.");
  else if ( args.Length < NumberOfPlaceHolders)
    throw new InvalidFormatException("Too many placeholders.");
}

Open in new window

0
 
LVL 1

Author Comment

by:FrancineTaylor
ID: 35039282
Okay, here is the code I used to test the regex:

            MatchCollection matches = regex.Matches(text);
            List<string> list = new List<string>();
            foreach (Match match in matches) {
                list.Add(match.Groups[1].Value);
            }
            string[] embeddedParameters = list.ToArray();

When I used it with jdavistx's regex ("(\{\d+\})"), I got the correct result from these strings:
    "This has four parms {0}{1}{2}{3}"
    "out of sequence types {42}{99}"
...but these were not extracted correctly:
    "{0:D}"
    "{0,10:C}"
    "Hello {0}, {{ , {1:D}, }}, {{3}, {4}}"

tgerbert's regex ("[^\{]\{(\d+)[^}]*}[^}]") didn't appear to extract any of them correctly.

Is that an accurate test, or am I using the MatchCollection incorrectly?
0
 
LVL 33

Accepted Solution

by:
Todd Gerbert earned 500 total points
ID: 35039537
Regex's are tricky.  This is closer, but still not quite right:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

class Program
{
	static void Main(string[] args)
	{
		string tests = @"This has four parms {0}{1}{2}{3}
out of sequence types {42}{99}
...but these were not extracted correctly:
{0:D}
{0,10:C}
Hello {0}, {{ , {1:D}, }}, {{3}, {4}}";

		foreach (string s in tests.Split(new string[] { "\r\n" }, StringSplitOptions.None))
			ShowParameterInfo(s);

		Console.Write("Press any key to exit...");
		Console.ReadKey();
	}

	static void ShowParameterInfo(string formatString)
	{
		Regex regex = new Regex(@"[.-[\{]]*\{(\d+)([^}]*)}[.-[}]]*");

		MatchCollection matches = regex.Matches(formatString);

		Console.WriteLine("Input string: {0}", formatString);
		Console.WriteLine("Parameter count: {0}", matches.Count);

		foreach (Match m in matches)
		{
			Console.WriteLine("\tParameter number: {0}, additional parameter details: {1}",
				m.Groups[1].Value, m.Groups[2].Value);
		}

		Console.WriteLine();
	}
}

Open in new window


Using String.Format("{{0}", "Hello") is invalid, but String.Format("{{{0}", "Hello") and would produce {Hello.  I'm not quite sure how to ensure that a { doesn't immediately precede {0}, unless {{ precede's the {0}, using regular expressions...
0
 
LVL 1

Author Closing Comment

by:FrancineTaylor
ID: 35200208
That works perfectly!  Thanks, tgerbert...
0

Featured Post

The New “Normal” in Modern Enterprise Operations

DevOps for the modern enterprise offers many benefits — increased agility, productivity, and more, but digital transformation isn’t easy, especially if you’re not addressing the right issues. Register for the webinar to dive into the “new normal” for enterprise modern ops.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Today I had a very interesting conundrum that had to get solved quickly. Needless to say, it wasn't resolved quickly because when we needed it we were very rushed, but as soon as the conference call was over and I took a step back I saw the correct …
Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
The Email Laundry PDF encryption service allows companies to send confidential encrypted  emails to anybody. The PDF document can also contain attachments that are embedded in the encrypted PDF. The password is randomly generated by The Email Laundr…
In an interesting question (https://www.experts-exchange.com/questions/29008360/) here at Experts Exchange, a member asked how to split a single image into multiple images. The primary usage for this is to place many photographs on a flatbed scanner…

828 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question