Search code examples
c#.netdotliquid

Is it possible to format each value using supplied format?


I have this program

private static void RunUsingILiquidizable()
{
    const string templateString = @"TopInt prop: '{{TopInt}}'; Child.Prop prop: '{{L1Prop.L1Int}}'; Child.Child.Prop prop: '{{L1Prop.L2Prop.L2Int}}'; Dict item: '{{ExtendedProps.Key1}}'";
    Template.NamingConvention = new CSharpNamingConvention();
    Template.RegisterValueTypeTransformer(typeof(DateTime), (v) => ((DateTime)v).ToString("MM=dd=yy"));
    var t = Template.Parse(templateString);

    var model = new TopModel()
    {
        TopInt = 23,
        L1Prop = new L1Model()
        {
            L1Int = 34,
            L2Prop = new L2Model() { L2Int = 98 }
        },
        ExtendedProps = new Dictionary<string, object>() { { "Key1", DateTime.Now } }
    };

    string output = t.Render(Hash.FromAnonymousObject(model));

    Console.WriteLine("RunUsingILiquidizable -->" + output);

}

Top Model definition:

public class TopModel : ILiquidizable
{
    public int TopInt { get; set; }
    public L1Model L1Prop { get; set; }
    public Dictionary<string, object> ExtendedProps { get; set; }
    public object ToLiquid()
    {
        return new { TopInt, L1Prop, ExtendedProps };
    }
}

Output:

RunUsingILiquidizable -->TopInt prop: '23'; Child.Prop prop: '34'; Child.Child.P rop prop: '98'; Dict item: '08=27=19'

My problem is - Template.RegisterValueTypeTransformer does global type format and template.Render(Hash.FromAnonymousObject(model), MyFormatProvider) formats all dates, numbers the same.

What I need is to format each specific token differently when needed by supplying format. Especially important for dictionary ExtendedProps.

I tried to do filters as well but is there a way to pass something like {{ExtendedProps.Key1 | SpecialFormat("dd--MM")}} ??


Solution

  • I encourage you to examine the source code for the StandardFilters class. Custom filters can do more than the wiki docs would have you believe, and the standard filters demonstrate much of it. If there's something you've seen the standard filters do, you can write a custom filter that does the same thing.

    Here's an example custom_date filter:

    public static class CustomFilter
    {
        public static string CustomDate(Context context, object input, string format = null, string culture = null)
        {
            if (input is DateTime dt)
            {
                IFormatProvider formatProvider = !string.IsNullOrEmpty(culture)
                    ? CultureInfo.GetCultureInfo(culture)
                    : context.FormatProvider;
    
                return dt.ToString(format, formatProvider);
            }
    
            return null;
        }
    }
    

    Points of interest:

    1. It accepts an initial Context parameter, which includes a FormatProvider property of type IFormatProvider (which CultureInfo implements). You only need to include this parameter if you need something from it.

    2. It accepts multiple parameters:

      a. A format, like certain ToString overloads, and just passes it through.

      b. A locale for which it retrieves the CultureInfo or, if null or "", defaults to context.FormatProvider, which is either the optional IFormatProvider passed to Render or the current culture.

    3. The parameters are defaulted. This is probably a good idea in general because there's no way to make them required in the template.

    Example usage

    Template.Register(typeof(CustomFilter));
    
    CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
    var template = Template.Parse("Today is {{ today | custom_date: 'd' }} (current culture), {{ today | custom_date: 'd', 'fr-CA' }} (fr-CA), and {{ today | custom_date: 'd', elsewhere }} ({{ elsewhere }}).");
    string output = template.Render(Hash.FromAnonymousObject(new { today = DateTime.Today, elsewhere = "de-DE" }));
    

    Output

    Today is 8/30/2019 (current culture), 2019-08-30 (fr-CA), and 30.08.2019 (de-DE).

    More points of interest:

    1. The parameters are specified after a colon and separated by a comma if there's more than one.

    2. Values in the "hash" that you pass to Render can be referenced in the parameters you pass (e.g. elsewhere).

    3. The "d" standard format is sensitive to the culture so you can see how the current culture (en-US), a hard-coded culture (fr-CA), and a parameterized culture (de-DE) can all be used.