So, you know everything about text, right?– part XIV

In the previous post, we’ve looked at the specificities associated with the usage of the IFormattable interface. As we’ve seen, its ToString method expects a format string and an IFormatProvider instance which allows any interested party to get a reference to an object that can be used for formatting and parsing (more on this in future posts). In this post, we’ll take a look at how we can create custom formatter objects. In this area, we’ve got a couple of options:

  • we can create a custom CultureInfo object. This is a useful approach when none of the cultures defined by the framework can quite be applied to an existing scenario.
  • we can create a custom ICustomFormatter instance. This is a good option when we’re only interested in customizing the way that a specific type is formatted.

Let’s start with the first strategy: creating a custom CultureInfo object. As I’ve said, the framework introduces several predefined cultures which define several important characteristics associated with text, dates and numbers formatting and parsing. Even though I never had to create a custom culture, the truth is that I have some friends which did it because none of the existing cultures covered all their needs. Fortunately for us, the framework introduced the CultureAndRegionInfoBuilder for helping us in the creation of a new culture. Currently, the process involves several steps:

  1. Create a new instance of CultureAndRegionInfoBuilder.
  2. If the new culture is based on an existing one, them use the LoadDataFromCultureInfo and LoadDataFromRegionInfo for initializing the data associated with an existing culture and region.
  3. Modify the properties you wish to customize.
  4. Register the new culture by invoking the Register method.

The registration process is interesting. It starts by creating a .npl file with the information defined by the CultureAndRegionInfoBuilder which is then stored in the %windir%Globalization folder. After that, it updates the framework’s configuration so that it searches for cultures in the %windir%Globalization folder instead of relying on the internal cache. As you’ve probably inferred, this process requires administrative privileges (though there is a workaround for non-admins). Here’s a small sample which creates a new culture that is based on the PT culture (it simply replaces the default decimal separator):

var cultureBuilder = new CultureAndRegionInfoBuilder("ptTest", CultureAndRegionModifiers.None);
cultureBuilder.LoadDataFromCultureInfo(new CultureInfo("pt-PT"));
cultureBuilder.LoadDataFromRegionInfo(new RegionInfo("PT"));
cultureBuilder.NumberFormat.CurrencyDecimalSeparator = ".";
cultureBuilder.Register();

After registering the new culture, you can create new custom CultureInfo objects as you normally do, ie, by passing its name to the constructor call.

var money = 10.0;
money.ToString("C", new CultureInfo("ptTest"));//10.0
money.ToString("C", new CultureInfo("pt-PT"));//10,0

As you can see, the ptTest culture uses the ‘.’ as a decimal separator. Before moving on, it’s important to understand that registered cultures can be used in other apps. They even survive eventual reboots of the machine (at least, until you remove the .npl file by calling the static Unregister method).

There are,however, times where you’re only interested in customizing the formatting applied to a specific type. In theses cases, you can implement the ICustomFormatter interface. Typically, the type that implements this interface will also implement the IFormatProvider interface. To illustrate this technique, let’s suppose that we want to return a value in the form [XXX] when XXX is an integer value. Here’s the code I’ve used for the formatter:

class Int32Formatter:IFormatProvider, ICustomFormatter {
    public object GetFormat(Type formatType) {
        //if Int32, return referenc to ICustomFormatter, ie, this
     
   
if( formatType == typeof(ICustomFormatter)) {
            return this;
        }
        //not int: return default formatter
        return Thread.CurrentThread.CurrentCulture.GetFormat(formatType);
    }

    public string Format(string format, object arg, IFormatProvider formatProvider) {
        var val = arg as IFormattable;
        if(val == null ) {
            //does not implement the IFormattable interface
            //call inherited ToString
            return arg.ToString();
        }

        var str = val.ToString(format, formatProvider);
        if(arg.GetType() == typeof(Int32)) {
            return "[" + str + "]";
        }
        return str;
    }
}

And now, we can test our code by using an overload of the String.Format method (or by calling the AppendFormat or …):

var someValue = 10;
var someDouble = 10.0;
var str = String.Format(new Int32Formatter(),
    "Here's an int value: {0} and a double: {1}",
    someValue,
    someDouble);
Console.WriteLine(str);

Now, how does that work? It’s not that complicated…Internally, the String.Format method relies on the StringBuilder’s AppendFormat method to do all the work. This method starts by checking if a formatter was passed and if it offers an ICustomFormatter  object (it does this by calling the IFormatProvider’s GetFormat method). If it does, then AppendFormat ends up calling the ICustomFormatter’s Format method.

Notice that the Format implementation will be called for all placeholder values defined in our string. And that’s why we need to check all the values we receive in the Format method. If the current value doesn’t support the IFormattable interface (required for allowing formatting of the values according to a specific format and culture), then I’ll simply return the result of the instance parameterless ToString method). On the other hand, if the object does implement the IFormattable interface, then I start by calling the IFormattable’s ToString method (and passing it the format string and the format provider). After getting the formatted string, I check for the type of the object. When it’s an integer, I wrap the format string with [ and  ] and return it. If it’s not an integer, I return the previously formatted value. And there you go: a custom provider which is only responsible for customizing the way an integer is formatted in a string.

And I guess this is it for now. On the next post, I’ll take a close look at the inverse process: parsing. Stay tuned for more.

Advertisements

~ by Luis Abreu on May 16, 2011.

One Response to “So, you know everything about text, right?– part XIV”

  1. The workaround through the problem of registering custom cultureInfo doesn’t work without Admin Privileges.

    Did you Test all this in a POC application, could you share it.

    Regards

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: