Solved

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

Posted on 2011-03-03
7
420 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
Automating Terraform w Jenkins & AWS CodeCommit

How to configure Jenkins and CodeCommit to allow users to easily create and destroy infrastructure using Terraform code.

 
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

Creating Instructional Tutorials  

For Any Use & On Any Platform

Contextual Guidance at the moment of need helps your employees/users adopt software o& achieve even the most complex tasks instantly. Boost knowledge retention, software adoption & employee engagement with easy solution.

Question has a verified solution.

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

This article is for Object-Oriented Programming (OOP) beginners. An Interface contains declarations of events, indexers, methods and/or properties. Any class which implements the Interface should provide the concrete implementation for each Inter…
Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
If you’ve ever visited a web page and noticed a cool font that you really liked the look of, but couldn’t figure out which font it was so that you could use it for your own work, then this video is for you! In this Micro Tutorial, you'll learn yo…
Sometimes it takes a new vantage point, apart from our everyday security practices, to truly see our Active Directory (AD) vulnerabilities. We get used to implementing the same techniques and checking the same areas for a breach. This pattern can re…

623 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