Solved

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

Posted on 2011-03-03
7
419 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
Online Training Solution

Drastically shorten your training time with WalkMe's advanced online training solution that Guides your trainees to action. Forget about retraining and skyrocket knowledge retention rates.

 
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

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

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

Suggested Solutions

Many of us here at EE write code. Many of us write exceptional code; just as many of us write exception-prone code. As we all should know, exceptions are a mechanism for handling errors which are typically out of our control. From database errors, t…
This article aims to explain the working of CircularLogArchiver. This tool was designed to solve the buildup of log file in cases where systems do not support circular logging or where circular logging is not enabled
Exchange organizations may use the Journaling Agent of the Transport Service to archive messages going through Exchange. However, if the Transport Service is integrated with some email content management application (such as an antispam), the admini…

710 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