using System; using System.Collections.Generic; using System.Globalization; namespace Vanara { // Leveraged code from t3chb0t and Bogdan Yarema at https://codereview.stackexchange.com/questions/138747/custom-string-formatters /// Binds multiple formatters together. /// internal sealed class CompositeFormatter : Formatter { private readonly List _formatters; /// Initializes a new instance of the class. /// The culture. /// The formatters. public CompositeFormatter(CultureInfo culture = null, params Formatter[] formatters) : base(culture) { _formatters = new List(formatters); } /// Adds the specified formatter. /// The formatter. public void Add(Formatter formatter) => _formatters.Add(formatter); /// /// Converts the value of a specified object to an equivalent string representation using specified format and culture-specific /// formatting information. /// /// A format string containing formatting specifications. /// An object to format. /// An object that supplies format information about the current instance. /// /// The string representation of the value of , formatted as specified by and /// . /// public override string Format(string format, object arg, IFormatProvider formatProvider) { foreach (var formatter in _formatters) { var result = formatter.Format(format, arg, formatProvider); if (result != null) return result; } return null; } } /// Extension method to combine formatter instances. public static class FormatterComposer { /// Adds a chain of formatters with specific cultures to make a composite. /// A derived type. /// The formatter instance to start the chain. /// The culture. /// A composite formatter. /// /// /// // Build composite formatter from custom formatters derived from Formatter /// var formatter = Formatter.Default().Add<CustomFormatter1>().Add<CustomFormatter2>(); /// // Use custom format extensions defined in the custom formatters to format the string /// var output = string.Format(formatter, "{0:cf1} = {0:cf2}", 512); /// /// public static Formatter Add(this Formatter formatter, CultureInfo culture = null) where T : Formatter, new() { var newFormatter = new T(); if (!(formatter is CompositeFormatter compositeFormatter)) return new CompositeFormatter(culture, formatter, newFormatter); compositeFormatter.Add(newFormatter); return compositeFormatter; } } /// Base class for expandable formatters. public abstract class Formatter : IFormatProvider, ICustomFormatter { /// Initializes a new instance of the class. /// The culture. protected Formatter(CultureInfo culture = null) { Culture = culture ?? CultureInfo.InvariantCulture; } /// Gets a default instance of a composite formatter. /// The culture. /// A composite formatter. public static Formatter Default(CultureInfo culture = null) => new CompositeFormatter(culture); /// Gets the culture. /// The culture. public CultureInfo Culture { get; } /// Returns an object that provides formatting services for the specified type. /// An object that specifies the type of format object to return. /// /// An instance of the object specified by , if the IFormatProvider implementation can supply that type /// of object; otherwise, . /// public virtual object GetFormat(Type formatType) => formatType == typeof(ICustomFormatter) ? this : null; /// /// Converts the value of a specified object to an equivalent string representation using specified format and culture-specific /// formatting information. /// /// A format string containing formatting specifications. /// An object to format. /// An object that supplies format information about the current instance. /// /// The string representation of the value of , formatted as specified by and /// . /// public abstract string Format(string format, object arg, IFormatProvider formatProvider); /// Helper method that can be used inside the Format method to handle unrecognized formats. /// A format string containing formatting specifications. /// An object to format. /// /// The string representation of the value of , formatted as specified by . /// protected string HandleOtherFormats(string format, object arg) => (arg as IFormattable)?.ToString(format, Culture) ?? (arg?.ToString() ?? string.Empty); } }