Question

I'm attempting to learn to write a basic template engine implementation. For example I have a string:

string originalString = "The current Date is: {{Date}}, the time is: {{Time}}";

what is the best way of reading the contents of each {{}} and then replacing the whole token with the valid string?

EDIT: Thanks to BrunoLM for pointing me in the right direction, so far this is what I have and it parses just fine, is there any other things I can do to optimize this function?

private const string RegexIncludeBrackets = @"{{(.*?)}}";

public static string ParseString(string input)
{
    return Regex.Replace(input, RegexIncludeBrackets, match =>
    {
        string cleanedString = match.Value.Substring(2, match.Value.Length - 4).Replace(" ", String.Empty);
        switch (cleanedString)
        {
            case "Date":
                return DateTime.Now.ToString("yyyy/MM/d");
            case "Time":
                return DateTime.Now.ToString("HH:mm");
            case "DateTime":
                return DateTime.Now.ToString(CultureInfo.InvariantCulture);
            default:
                return match.Value;
        }
    });
}
Was it helpful?

Solution

Short answer

I think it would be best to use a Regex.

var result = Regex.Replace(str, @"{{(?<Name>[^}]+)}}", m =>
{
    return m.Groups["Name"].Value; // Date, Time
});

On you can use:

string result = $"Time: {DateTime.Now}";

String.Format & IFormattable

However, there is a method for that already. Documentation.

String.Format("The current Date is: {0}, the time is: {1}", date, time);

And also, you can use a class with IFormattable. I didn't do performance tests but this one might be fast:

public class YourClass : IFormattable
{
    public string ToString(string format, IFormatProvider formatProvider)
    {
        if (format == "Date")
            return DateTime.Now.ToString("yyyy/MM/d");
        if (format == "Time")
            return DateTime.Now.ToString("HH:mm");
        if (format == "DateTime")
            return DateTime.Now.ToString(CultureInfo.InvariantCulture);

        return format;

        // or throw new NotSupportedException();
    }
}

And use as

String.Format("The current Date is: {0:Date}, the time is: {0:Time}", yourClass);

Review of your code and detailed information

In your current code you are using

// match.Value = {{Date}}
match.Value.Substring(2, match.Value.Length - 4).Replace(" ", String.Empty);

Instead, if you look at my code above, I used the pattern

@"{{(?<Name>[^}]+)}}"

The syntax (?<SomeName>.*) means this is a named group, you can check the documentation here.

It allows you to access match.Groups["SomeName"].Value which will be equivalent to the pattern with this group. So it would match two times, returning "Date" and then "Time", so you don't need to use SubString.

Updating your code, it would be

private const string RegexIncludeBrackets = @"{{(?<Param>.*?)}}";

public static string ParseString(string input)
{
    return Regex.Replace(input, RegexIncludeBrackets, match =>
    {
        string cleanedString = match.Groups["Param"].Value.Replace(" ", String.Empty);
        switch (cleanedString)
        {
            case "Date":
                return DateTime.Now.ToString("yyyy/MM/d");
            case "Time":
                return DateTime.Now.ToString("HH:mm");
            case "DateTime":
                return DateTime.Now.ToString(CultureInfo.InvariantCulture);
            default:
                return match.Value;
        }
    });
}

To improve even more, you can have a static compiled Regex field:

private static Regex RegexTemplate = new Regex(@"{{(?<Param>.*?)}}", RegexOptions.Compiled);

And then use as

RegexTemplate.Replace(str, match => ...);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top