Question

I'm writing a reporting system that needs to use HTML files as templates. I'm looking at using Nustache to process this, as it seems ideal and very lightweight. The only problem I have with this is that I need to be able to specify a specific number of decimal places in the numeric databound items.

I know I could have an accessor property in the datasource which internally does a String.Format and then returns a formatted string representing the number. The problem with this is that the template itself can't specify the format (eg. number of decimal places).

Is there a way I can specify the decimal places from the template itself? As I really like Nustache, I'm concidering writing something that processes the template file before and after to handle formatting. But wanted to check that I'm not reinventing the wheel if this already is supported, and I've just missed some existing functionality.

What I was thinking is if the pre-process replaced {{MyNumber|3}} with <$$${{MyNumber}}|3$$$>, before allowing Nustache to do it's thing. Where 3 is the number of decimal places. So it would then look like <$$$123|3$$$>. Then after Nustache had processed, I'd have a post-process that used a regular expression to extract both the number that Nustache inserted and the 3, and then did a string format and replace. Note that the <$$$ and $$$> are just made up strings to indicate the start and end of a block that the post-process needs to handle.

In my case, I'm only going to need this for specifying decimal places, so I'm safe to presume that anything with {{x|y}} in it is a decimal value where y indicates the number of decimal places.

Am I reinventing a wheel doing this? Any other ideas? Or problems with this solution?

Was it helpful?

Solution

I've actually implemented my suggestion as it didn't take long at all to do. Seems to work very nicely. It's limited as it stands, as it presumes that {{x|y}} is with x as a decimal type and y as a number of decimal characters. I also used <### and ###> instead because $ is used in the regex replacement string (eg. $1 is the first matched group, etc). So using # meant I didn't have to escape them.

Just incase anyone's interested, here's the code:

using System.Text.RegularExpressions;

public static class NustacheUtil
{
    public static string PreProcess(string templateString)
    {
        return Regex.Replace(templateString, @"{{(.*?)\|(\d+)}}", @"<###{{$1}}|$2###>");
    }

    public static string PostProcess(string templateString)
    {
        var result = templateString;

        for(;;)
        {
            var match = Regex.Match(result, @"<###([0-9.]+?)\|(\d+)###>");

            if(!match.Success || match.Groups.Count != 3)
            {
                break;
            }

            decimal value;

            if(decimal.TryParse(match.Groups[1].Value, out value))
            {
                var numDecimalPlaces = match.Groups[2].Value;
                result = result.Remove(match.Index, match.Length);
                result = result.Insert(match.Index, value.ToString("N" + numDecimalPlaces));
            }
        }

        return result;
    }
}

And a few tests:

using NUnit.Framework;

[TestFixture]
class NustacheUtilTests
{
    [Test]
    public void PreProcessTest_Single()
    {
        // Arrange
        const string templateString = "Test number: {{MyNumber|3}}";

        // Act
        var result = NustacheUtil.PreProcess(templateString);

        // Assert
        Assert.That(result, Is.EqualTo("Test number: <###{{MyNumber}}|3###>"));
    }

    [Test]
    public void PostProcessTest_Single()
    {
        // Arrange
        const string templateString = "Test number: <###123.456789|3###>";

        // Act
        var result = NustacheUtil.PostProcess(templateString);

        // Assert
        Assert.That(result, Is.EqualTo("Test number: 123.457"));
    }

    [Test]
    public void PreProcessTest_Multiple()
    {
        // Arrange
        const string templateString = "Test number: {{MyNumber|3}}, and another: {{OtherNumber|2}}";

        // Act
        var result = NustacheUtil.PreProcess(templateString);

        // Assert
        Assert.That(result, Is.EqualTo("Test number: <###{{MyNumber}}|3###>, and another: <###{{OtherNumber}}|2###>"));
    }

    [Test]
    public void PostProcessTest_Multiple()
    {
        // Arrange
        const string templateString = "Test number: <###123.457|3###>, and another: <###947.74933|2###>";

        // Act
        var result = NustacheUtil.PostProcess(templateString);

        // Assert
        Assert.That(result, Is.EqualTo("Test number: 123.457, and another: 947.75"));
    }
}

And usage with Nustache:

var templateString = NustacheUtil.PreProcess(templateString);
templateString = Render.StringToString(templateString, dataSource);
templateString = NustacheUtil.PostProcess(templateString);

It obviously hasn't yet been used in anger. I'll update the code here when I make changes, as I'm sure I will make improvements as I use it properly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top