using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using mustache.Properties; namespace mustache { /// /// Allows for the generation of a string based on formatted template. /// public sealed class Formatter { private readonly CompoundBuilder builder; /// /// Initializes a new instance of a Formatter using the given format string. /// /// The string containing the placeholders to use as a template. /// The format string is null. /// The format string is invald. public Formatter(string format) { if (format == null) { throw new ArgumentNullException("format"); } builder = new CompoundBuilder(); List names = new List(); const string key = @"[_\w][_\w\d]*"; const string compoundKey = key + @"(\." + key + ")*"; const string openIfMatch = @"(?(#if\s+?" + compoundKey + @"\s*?))"; const string elifMatch = @"(?(#elif\s+?" + compoundKey + @"\s*?))"; const string elseMatch = @"(?(#else\s*?))"; const string closeIfMatch = @"(?(/if\s*?))"; const string openEachMatch = @"(?(#each\s+?" + compoundKey + @"\s*?))"; const string closeEachMatch = @"(?(/each\s*?))"; const string openWithMatch = @"(?(#with\s+?" + compoundKey + @"\s*?))"; const string closeWithMatch = @"(?(/with\s*?))"; const string commentMatch = @"(?#!.*?)"; const string keyMatch = @"((?" + compoundKey + @")(,(?(-)?[\d]+))?(:(?.*?))?)"; const string match = "{{(" + openIfMatch + "|" + elifMatch + "|" + elseMatch + "|" + closeIfMatch + "|" + openEachMatch + "|" + closeEachMatch + "|" + openWithMatch + "|" + closeWithMatch + "|" + commentMatch + "|" + keyMatch + ")}}"; Regex formatFinder = new Regex(match, RegexOptions.Compiled); List matches = formatFinder.Matches(format).Cast().ToList(); using (IEnumerator matchEnumerator = matches.GetEnumerator()) { Trimmer trimmer = new Trimmer(); int formatIndex = buildCompoundBuilder(builder, trimmer, format, 0, matchEnumerator); StaticBuilder trailingBuilder = new StaticBuilder(); string value = format.Substring(formatIndex); TagAttributes attributes = new TagAttributes() { Type = TagType.None, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); } } /// /// Substitutes the placeholders in the format string with the values found in the object. /// /// The string containing the placeholders to use as a template. /// The object to use to replace the placeholders. /// The format string with the placeholders substituted for by the object values. /// The format string is null. /// A property was not found in the value. public static string Format(string format, object value) { Formatter formatter = new Formatter(format); return formatter.Format(value); } /// /// Substitutes the placeholders in the format string with the values found in the object. /// /// The format provider to use -or- null to use the current culture. /// The string containing the placeholders to use as a template. /// The object to use to replace the placeholders. /// The format string with the placeholders substituted for by the object values. /// The format string is null. /// A property was not found in the value. public static string Format(IFormatProvider provider, string format, object value) { Formatter formatter = new Formatter(format); return formatter.Format(provider, value); } /// /// Substitutes the placeholders in the format string with the values found in the given object. /// /// The object to use to replace the placeholders. /// The format string with the placeholders substituted for by the lookup values. /// A property was not found in the object. /// A null value will be replaced with an empty string. public string Format(object value) { return format(CultureInfo.CurrentCulture, value); } /// /// Substitutes the placeholders in the format string with the values found in the given object. /// /// The format provider to use -or- null to use the current culture. /// The object to use to replace the placeholders. /// The format string with the placeholders substituted for by the lookup values. /// A property was not found in the object. /// A null value will be replaced with an empty string. public string Format(IFormatProvider provider, object value) { if (provider == null) { provider = CultureInfo.CurrentCulture; } return format(provider, value); } private static int buildCompoundBuilder(CompoundBuilder builder, Trimmer trimmer, string format, int formatIndex, IEnumerator matches) { while (matches.MoveNext()) { Match match = matches.Current; string value = format.Substring(formatIndex, match.Index - formatIndex); formatIndex = match.Index + match.Length; Group keyGroup = match.Groups["key"]; if (keyGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Singleton, IsOutput = true }; trimmer.AddStaticBuilder(builder, attributes, value); Group alignmentGroup = match.Groups["alignment"]; Group formatGroup = match.Groups["format"]; KeyBuilder keyBuilder = new KeyBuilder() { Key = keyGroup.Value, Alignment = alignmentGroup.Value, Format = formatGroup.Value, }; builder.AddBuilder(keyBuilder); continue; } Group openIfGroup = match.Groups["open_if"]; if (openIfGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Header, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); IfBuilder ifBuilder = new IfBuilder(); ifBuilder.Key = openIfGroup.Value.Substring(4).Trim(); formatIndex = buildIfBuilder(ifBuilder, true, trimmer, format, formatIndex, matches); builder.AddBuilder(ifBuilder); continue; } Group openEachGroup = match.Groups["open_each"]; if (openEachGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Header, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); EachBuilder eachBuilder = new EachBuilder(); eachBuilder.Key = openEachGroup.Value.Substring(6).Trim(); formatIndex = buildEachBuilder(eachBuilder, trimmer, format, formatIndex, matches); builder.AddBuilder(eachBuilder); continue; } Group openWithGroup = match.Groups["open_with"]; if (openWithGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Header, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); WithBuilder withBuilder = new WithBuilder(); withBuilder.Key = openWithGroup.Value.Substring(6).Trim(); formatIndex = buildWithBuilder(withBuilder, trimmer, format, formatIndex, matches); builder.AddBuilder(withBuilder); continue; } Group commentGroup = match.Groups["comment"]; if (commentGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Singleton, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); continue; } Group elifGroup = match.Groups["elif"]; if (elifGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Singleton, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); break; } Group elseGroup = match.Groups["else"]; if (elseGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Singleton, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); break; } Group closeIfGroup = match.Groups["close_if"]; if (closeIfGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Footer, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); break; } Group closeEachGroup = match.Groups["close_each"]; if (closeEachGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Footer, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); break; } Group closeWithGroup = match.Groups["close_with"]; if (closeWithGroup.Success) { TagAttributes attributes = new TagAttributes() { Type = TagType.Footer, IsOutput = false }; trimmer.AddStaticBuilder(builder, attributes, value); break; } } return formatIndex; } private static int buildIfBuilder(IfBuilder builder, bool expectClosingTag, Trimmer trimmer, string format, int formatIndex, IEnumerator matches) { formatIndex = buildCompoundBuilder(builder.TrueBuilder, trimmer, format, formatIndex, matches); Match match = matches.Current; if (match != null) { Group elifGroup = match.Groups["elif"]; if (elifGroup.Success) { IfBuilder elifBuilder = new IfBuilder(); elifBuilder.Key = elifGroup.Value.Substring(6).Trim(); formatIndex = buildIfBuilder(elifBuilder, false, trimmer, format, formatIndex, matches); builder.FalseBuilder.AddBuilder(elifBuilder); } else { Group elseGroup = match.Groups["else"]; if (elseGroup.Success) { formatIndex = buildCompoundBuilder(builder.FalseBuilder, trimmer, format, formatIndex, matches); } } } if (expectClosingTag) { Match closingMatch = matches.Current; checkClosingTag(closingMatch, "close_if", "if"); } return formatIndex; } private static int buildEachBuilder(EachBuilder builder, Trimmer trimmer, string format, int formatIndex, IEnumerator matches) { formatIndex = buildCompoundBuilder(builder.Builder, trimmer, format, formatIndex, matches); Match closingMatch = matches.Current; checkClosingTag(closingMatch, "close_each", "each"); return formatIndex; } private static int buildWithBuilder(WithBuilder builder, Trimmer trimmer, string format, int formatIndex, IEnumerator matches) { formatIndex = buildCompoundBuilder(builder.Builder, trimmer, format, formatIndex, matches); Match closingMatch = matches.Current; checkClosingTag(closingMatch, "close_with", "with"); return formatIndex; } private static void checkClosingTag(Match match, string expectedTag, string openingTag) { if (match == null || !match.Groups[expectedTag].Success) { string errorMessage = String.Format(CultureInfo.CurrentCulture, Resources.MissingClosingTag, openingTag); throw new FormatException(errorMessage); } } private string format(IFormatProvider provider, object topLevel) { Scope scope = new Scope(topLevel); StringBuilder output = new StringBuilder(); builder.Build(scope, output, provider); return output.ToString(); } } }