MustacheSharp/mustache-sharp/FormatCompiler.cs

233 lines
9.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using mustache.Properties;
namespace mustache
{
/// <summary>
/// Parses a format string and returns a text generator.
/// </summary>
public sealed class FormatCompiler
{
private const string key = @"[_\w][_\w\d]*";
private const string compoundKey = key + @"(\." + key + ")*";
private readonly MasterTagDefinition _master;
private readonly TagScope _tagScope;
/// <summary>
/// Initializes a new instance of a FormatCompiler.
/// </summary>
public FormatCompiler()
{
_master = new MasterTagDefinition();
_tagScope = new TagScope();
registerTags(_master, _tagScope);
}
/// <summary>
/// Registers the given tag definition with the parser.
/// </summary>
/// <param name="definition">The tag definition to register.</param>
public void RegisterTag(TagDefinition definition)
{
if (definition == null)
{
throw new ArgumentNullException("definition");
}
_tagScope.AddTag(definition);
}
/// <summary>
/// Builds a text generator based on the given format.
/// </summary>
/// <param name="format">The format to parse.</param>
/// <returns>The text generator.</returns>
public Generator Compile(string format)
{
CompoundGenerator generator = new CompoundGenerator(_master, new ArgumentCollection());
int formatIndex = buildCompoundGenerator(_master, _tagScope, generator, format, 0);
string trailing = format.Substring(formatIndex);
StaticGenerator staticGenerator = new StaticGenerator(trailing);
generator.AddGenerator(staticGenerator);
return new Generator(generator);
}
private static void registerTags(TagDefinition definition, TagScope scope)
{
foreach (TagDefinition childTag in definition.ChildTags)
{
scope.AddTag(childTag);
}
}
private static Match findNextTag(TagDefinition definition, string format, int formatIndex)
{
List<string> matches = new List<string>();
matches.Add(getKeyRegex());
matches.Add(getCommentTagRegex());
foreach (TagDefinition closingTag in definition.ClosingTags)
{
matches.Add(getClosingTagRegex(closingTag));
}
foreach (TagDefinition childTag in definition.ChildTags)
{
matches.Add(getTagRegex(childTag));
}
string match = "{{(" + String.Join("|", matches) + ")}}";
Regex regex = new Regex(match);
return regex.Match(format, formatIndex);
}
private static string getClosingTagRegex(TagDefinition definition)
{
StringBuilder regexBuilder = new StringBuilder();
regexBuilder.Append(@"(?<close>(/(?<name>");
regexBuilder.Append(definition.Name);
regexBuilder.Append(@")\s*?))");
return regexBuilder.ToString();
}
private static string getCommentTagRegex()
{
return @"(?<comment>#!.*?)";
}
private static string getKeyRegex()
{
return @"((?<key>" + compoundKey + @")(,(?<alignment>(-)?[\d]+))?(:(?<format>.*?))?)";
}
private static string getTagRegex(TagDefinition definition)
{
StringBuilder regexBuilder = new StringBuilder();
regexBuilder.Append(@"(?<open>(#(?<name>");
regexBuilder.Append(definition.Name);
regexBuilder.Append(@")");
foreach (TagParameter parameter in definition.Parameters)
{
regexBuilder.Append(@"\s+?");
regexBuilder.Append(@"(?<argument>");
regexBuilder.Append(compoundKey);
regexBuilder.Append(@")");
}
regexBuilder.Append(@"\s*?))");
return regexBuilder.ToString();
}
private static int buildCompoundGenerator(
TagDefinition tagDefinition,
TagScope scope,
CompoundGenerator generator,
string format, int formatIndex)
{
while (true)
{
Match match = findNextTag(tagDefinition, format, formatIndex);
if (!match.Success)
{
if (tagDefinition.ClosingTags.Any())
{
string message = String.Format(Resources.MissingClosingTag, tagDefinition.Name);
throw new FormatException(message);
}
break;
}
string leading = format.Substring(formatIndex, match.Index - formatIndex);
StaticGenerator staticGenerator = new StaticGenerator(leading);
generator.AddGenerator(staticGenerator);
if (match.Groups["key"].Success)
{
formatIndex = match.Index + match.Length;
string key = match.Groups["key"].Value;
string alignment = match.Groups["alignment"].Value;
string formatting = match.Groups["format"].Value;
KeyGenerator keyGenerator = new KeyGenerator(key, alignment, formatting);
generator.AddGenerator(keyGenerator);
}
else if (match.Groups["open"].Success)
{
formatIndex = match.Index + match.Length;
string tagName = match.Groups["name"].Value;
TagDefinition nextDefinition = scope.Find(tagName);
if (nextDefinition == null)
{
string message = String.Format(Resources.UnknownTag, tagName);
throw new FormatException(message);
}
if (nextDefinition.HasBody)
{
ArgumentCollection arguments = getArguments(nextDefinition, match);
CompoundGenerator compoundGenerator = new CompoundGenerator(nextDefinition, arguments);
TagScope nextScope = new TagScope(scope);
registerTags(nextDefinition, nextScope);
formatIndex = buildCompoundGenerator(nextDefinition, nextScope, compoundGenerator, format, formatIndex);
generator.AddGenerator(nextDefinition, compoundGenerator);
}
else
{
Match nextMatch = findNextTag(nextDefinition, format, formatIndex);
ArgumentCollection arguments = getArguments(nextDefinition, nextMatch);
InlineGenerator inlineGenerator = new InlineGenerator(nextDefinition, arguments);
generator.AddGenerator(inlineGenerator);
}
}
else if (match.Groups["close"].Success)
{
string tagName = match.Groups["name"].Value;
formatIndex = match.Index;
if (tagName == tagDefinition.Name)
{
formatIndex += match.Length;
}
break;
}
else if (match.Groups["comment"].Success)
{
formatIndex = match.Index + match.Length;
}
}
return formatIndex;
}
private static ArgumentCollection getArguments(TagDefinition definition, Match match)
{
ArgumentCollection collection = new ArgumentCollection();
List<Capture> captures = match.Groups["argument"].Captures.Cast<Capture>().ToList();
List<TagParameter> parameters = definition.Parameters.ToList();
if (captures.Count > parameters.Count)
{
string message = String.Format(Resources.WrongNumberOfArguments, definition.Name);
throw new FormatException(message);
}
if (captures.Count < parameters.Count)
{
captures.AddRange(Enumerable.Repeat((Capture)null, parameters.Count - captures.Count));
}
foreach (var pair in parameters.Zip(captures, (p, c) => new { Capture = c, Parameter = p }))
{
if (pair.Capture == null)
{
if (pair.Parameter.IsRequired)
{
string message = String.Format(Resources.WrongNumberOfArguments, definition.Name);
throw new FormatException(message);
}
collection.AddArgument(pair.Parameter, null);
}
else
{
collection.AddArgument(pair.Parameter, pair.Capture.Value);
}
}
return collection;
}
}
}