Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

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

Posted on 2011-03-03
7
Medium Priority
?
428 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
What is SQL Server and how does it work?

The purpose of this paper is to provide you background on SQL Server. It’s your self-study guide for learning fundamentals. It includes both the history of SQL and its technical basics. Concepts and definitions will form the solid foundation of your future DBA expertise.

 
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 2000 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

Veeam Disaster Recovery in Microsoft Azure

Veeam PN for Microsoft Azure is a FREE solution designed to simplify and automate the setup of a DR site in Microsoft Azure using lightweight software-defined networking. It reduces the complexity of VPN deployments and is designed for businesses of ALL sizes.

Question has a verified solution.

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

For those of you who don't follow the news, or just happen to live under rocks, Microsoft Research released a beta SDK (http://www.microsoft.com/en-us/download/details.aspx?id=27876) for the Xbox 360 Kinect. If you don't know what a Kinect is (http:…
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 …
Integration Management Part 2
Is your data getting by on basic protection measures? In today’s climate of debilitating malware and ransomware—like WannaCry—that may not be enough. You need to establish more than basics, like a recovery plan that protects both data and endpoints.…
Suggested Courses

972 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