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();
}
}
}