Provide context when placeholders found.

It could be useful to know the context when discovering a placeholder.
I also renamed MissingKeyEventArgs to KeyNotFoundEventArgs.
I created a new event KeyFoundEventArgs.
This commit is contained in:
Travis Parks 2013-04-24 21:21:00 -04:00
parent 20dcfc059d
commit 42463a888f
20 changed files with 911 additions and 692 deletions

View File

@ -332,7 +332,7 @@ Content";
/// registering with the PlaceholderFound event.
/// </summary>
[TestMethod]
public void TestCompile_FindsKeys_RecordsKeys()
public void TestCompile_FindsPlaceholders_RecordsPlaceholders()
{
FormatCompiler compiler = new FormatCompiler();
HashSet<string> keys = new HashSet<string>();
@ -346,6 +346,28 @@ Content";
CollectionAssert.AreEqual(expected, actual, "Not all placeholders were found.");
}
/// <summary>
/// We can determine the context in which a placeholder is found by looking at the provided context array.
/// </summary>
[TestMethod]
public void TestCompile_FindsPlaceholders_ProvidesContext()
{
FormatCompiler compiler = new FormatCompiler();
Context[] context = null;
compiler.PlaceholderFound += (o, e) =>
{
context = e.Context;
};
compiler.Compile(@"{{#with Address}}{{ZipCode}}{{/with}}");
Assert.IsNotNull(context, "The context was not set.");
Assert.AreEqual(2, context.Length, "The context did not contain the right number of items.");
Assert.AreEqual(String.Empty, context[0].Tag.Name, "The top-most context had the wrong tag type.");
Assert.AreEqual("this", context[0].Argument, "The top-level argument should always be 'this'.");
Assert.AreEqual("with", context[1].Tag.Name, "The inner context should have been a 'with' tag.");
Assert.AreEqual("Address", context[1].Argument, "The inner context argument was wrong.");
}
#endregion
#region Comment

View File

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("0.0.6.0")]
[assembly: AssemblyFileVersion("0.0.6.0")]
[assembly: AssemblyVersion("0.0.7.0")]
[assembly: AssemblyFileVersion("0.0.7.0")]

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace mustache
{
@ -29,6 +30,23 @@ namespace mustache
_argumentLookup.Add(parameter, key);
}
/// <summary>
/// Gets the key that will be used to find the substitute value.
/// </summary>
/// <param name="parameterName">The name of the parameter.</param>
public string GetKey(TagParameter parameter)
{
string key;
if (_argumentLookup.TryGetValue(parameter, out key))
{
return key;
}
else
{
return null;
}
}
/// <summary>
/// Substitutes the key placeholders with their respective values.
/// </summary>

View File

@ -26,6 +26,20 @@ namespace mustache
_primaryGenerators = new LinkedList<IGenerator>();
}
/// <summary>
/// Gets the argument that will act as the context for the content.
/// </summary>
/// <returns>The argument that will act as the context for the content.</returns>
public string GetContextArgument()
{
TagParameter parameter = _definition.GetChildContextParameter();
if (parameter == null)
{
return null;
}
return _arguments.GetKey(parameter);
}
/// <summary>
/// Adds the given generator.
/// </summary>

View File

@ -88,5 +88,14 @@ namespace mustache
return true;
}
}
/// <summary>
/// Gets the parameter that is used to create a new child context.
/// </summary>
/// <returns>The parameter that is used to create a new child context.</returns>
public override TagParameter GetChildContextParameter()
{
return null;
}
}
}

31
mustache-sharp/Context.cs Normal file
View File

@ -0,0 +1,31 @@
using System;
namespace mustache
{
/// <summary>
/// Represents a context within a template.
/// </summary>
public sealed class Context
{
/// <summary>
/// Initializes a new instance of a Context.
/// </summary>
/// <param name="definition">The definition of tag that created the context.</param>
/// <param name="argument">The argument used to create the context.</param>
internal Context(TagDefinition definition, string argument)
{
Tag = definition;
Argument = argument;
}
/// <summary>
/// Gets the tag that created the context.
/// </summary>
public TagDefinition Tag { get; private set; }
/// <summary>
/// Gets the argument used to create the context.
/// </summary>
public string Argument { get; private set; }
}
}

View File

@ -12,6 +12,7 @@ namespace mustache
internal sealed class EachTagDefinition : ContentTagDefinition
{
private const string collectionParameter = "collection";
private static readonly TagParameter collection = new TagParameter(collectionParameter) { IsRequired = true };
/// <summary>
/// Initializes a new instance of an EachTagDefinition.
@ -35,7 +36,7 @@ namespace mustache
/// <returns>The parameters.</returns>
protected override IEnumerable<TagParameter> GetParameters()
{
return new TagParameter[] { new TagParameter(collectionParameter) { IsRequired = true } };
return new TagParameter[] { collection };
}
/// <summary>
@ -67,5 +68,14 @@ namespace mustache
{
return new string[] { };
}
/// <summary>
/// Gets the parameter that is used to create a new child context.
/// </summary>
/// <returns>The parameter that is used to create a new child context.</returns>
public override TagParameter GetChildContextParameter()
{
return collection;
}
}
}

View File

@ -31,5 +31,14 @@ namespace mustache
{
return new string[] { "if" };
}
/// <summary>
/// Gets the parameter that is used to create a new child context.
/// </summary>
/// <returns>The parameter that is used to create a new child context.</returns>
public override TagParameter GetChildContextParameter()
{
return null;
}
}
}

View File

@ -74,7 +74,8 @@ namespace mustache
}
CompoundGenerator generator = new CompoundGenerator(_masterDefinition, new ArgumentCollection());
Trimmer trimmer = new Trimmer();
int formatIndex = buildCompoundGenerator(_masterDefinition, generator, trimmer, format, 0);
List<Context> context = new List<Context>() { new Context(_masterDefinition, "this") };
int formatIndex = buildCompoundGenerator(_masterDefinition, context, generator, trimmer, format, 0);
string trailing = format.Substring(formatIndex);
generator.AddStaticGenerators(trimmer.RecordText(trailing, false, false));
trimmer.Trim();
@ -166,6 +167,7 @@ namespace mustache
private int buildCompoundGenerator(
TagDefinition tagDefinition,
List<Context> context,
CompoundGenerator generator,
Trimmer trimmer,
string format, int formatIndex)
@ -193,7 +195,7 @@ namespace mustache
string key = match.Groups["key"].Value;
string alignment = match.Groups["alignment"].Value;
string formatting = match.Groups["format"].Value;
PlaceholderFoundEventArgs args = new PlaceholderFoundEventArgs(key, alignment, formatting);
PlaceholderFoundEventArgs args = new PlaceholderFoundEventArgs(key, alignment, formatting, context.ToArray());
if (PlaceholderFound != null)
{
PlaceholderFound(this, args);
@ -216,7 +218,12 @@ namespace mustache
generator.AddStaticGenerators(trimmer.RecordText(leading, true, false));
ArgumentCollection arguments = getArguments(nextDefinition, match);
CompoundGenerator compoundGenerator = new CompoundGenerator(nextDefinition, arguments);
formatIndex = buildCompoundGenerator(nextDefinition, compoundGenerator, trimmer, format, formatIndex);
string newContext = compoundGenerator.GetContextArgument();
if (newContext != null)
{
context.Add(new Context(nextDefinition, newContext));
}
formatIndex = buildCompoundGenerator(nextDefinition, context, compoundGenerator, trimmer, format, formatIndex);
generator.AddGenerator(nextDefinition, compoundGenerator);
}
else

View File

@ -11,7 +11,8 @@ namespace mustache
public sealed class Generator
{
private readonly IGenerator _generator;
private readonly List<EventHandler<MissingKeyEventArgs>> _handlers;
private readonly List<EventHandler<KeyFoundEventArgs>> _foundHandlers;
private readonly List<EventHandler<KeyNotFoundEventArgs>> _notFoundHandlers;
/// <summary>
/// Initializes a new instance of a Generator.
@ -20,16 +21,26 @@ namespace mustache
internal Generator(IGenerator generator)
{
_generator = generator;
_handlers = new List<EventHandler<MissingKeyEventArgs>>();
_foundHandlers = new List<EventHandler<KeyFoundEventArgs>>();
_notFoundHandlers = new List<EventHandler<KeyNotFoundEventArgs>>();
}
/// <summary>
/// Occurs when a key/property is found.
/// </summary>
public event EventHandler<KeyFoundEventArgs> KeyFound
{
add { _foundHandlers.Add(value); }
remove { _foundHandlers.Remove(value); }
}
/// <summary>
/// Occurs when a key/property is not found in the object graph.
/// </summary>
public event EventHandler<MissingKeyEventArgs> KeyNotFound
public event EventHandler<KeyNotFoundEventArgs> KeyNotFound
{
add { _handlers.Add(value); }
remove { _handlers.Remove(value); }
add { _notFoundHandlers.Add(value); }
remove { _notFoundHandlers.Remove(value); }
}
/// <summary>
@ -60,7 +71,11 @@ namespace mustache
private string render(IFormatProvider provider, object source)
{
KeyScope scope = new KeyScope(source);
foreach (EventHandler<MissingKeyEventArgs> handler in _handlers)
foreach (EventHandler<KeyFoundEventArgs> handler in _foundHandlers)
{
scope.KeyFound += handler;
}
foreach (EventHandler<KeyNotFoundEventArgs> handler in _notFoundHandlers)
{
scope.KeyNotFound += handler;
}

View File

@ -36,5 +36,14 @@ namespace mustache
{
return false;
}
/// <summary>
/// Gets the parameter that is used to create a child context.
/// </summary>
/// <returns>The parameter that is used to create a child context.</returns>
public override TagParameter GetChildContextParameter()
{
return null;
}
}
}

View File

@ -0,0 +1,29 @@
using System;
namespace mustache
{
/// <summary>
/// Holds the information about a key that was found.
/// </summary>
public class KeyFoundEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of a KeyFoundEventArgs.
/// </summary>
/// <param name="key">The fully-qualified key.</param>
internal KeyFoundEventArgs(string key, object value)
{
Key = key;
}
/// <summary>
/// Gets the fully-qualified key.
/// </summary>
public string Key { get; private set; }
/// <summary>
/// Gets or sets the object to use as the substitute.
/// </summary>
public object Substitute { get; set; }
}
}

View File

@ -5,14 +5,14 @@ namespace mustache
/// <summary>
/// Holds the information needed to handle a missing key.
/// </summary>
public class MissingKeyEventArgs : EventArgs
public class KeyNotFoundEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of a MissingKeyEventArgs.
/// Initializes a new instance of a KeyNotFoundEventArgs.
/// </summary>
/// <param name="key">The fully-qualified key.</param>
/// <param name="missingMember">The part of the key that could not be found.</param>
internal MissingKeyEventArgs(string key, string missingMember)
internal KeyNotFoundEventArgs(string key, string missingMember)
{
Key = key;
MissingMember = missingMember;

View File

@ -34,10 +34,15 @@ namespace mustache
_source = source;
}
/// <summary>
/// Occurs when a key/property is found in the object graph.
/// </summary>
public event EventHandler<KeyFoundEventArgs> KeyFound;
/// <summary>
/// Occurs when a key/property is not found in the object graph.
/// </summary>
public event EventHandler<MissingKeyEventArgs> KeyNotFound;
public event EventHandler<KeyNotFoundEventArgs> KeyNotFound;
/// <summary>
/// Creates a child scope that searches for keys in the given object.
@ -47,6 +52,7 @@ namespace mustache
public KeyScope CreateChildScope(object source)
{
KeyScope scope = new KeyScope(source, this);
scope.KeyFound = KeyFound;
scope.KeyNotFound = KeyNotFound;
return scope;
}
@ -75,6 +81,12 @@ namespace mustache
nextLevel = handleKeyNotFound(name, member);
}
}
if (KeyFound != null)
{
KeyFoundEventArgs args = new KeyFoundEventArgs(name, nextLevel);
KeyFound(this, args);
nextLevel = args.Substitute;
}
return nextLevel;
}
@ -94,7 +106,7 @@ namespace mustache
private object handleKeyNotFound(string fullName, string memberName)
{
MissingKeyEventArgs args = new MissingKeyEventArgs(fullName, memberName);
KeyNotFoundEventArgs args = new KeyNotFoundEventArgs(fullName, memberName);
if (KeyNotFound != null)
{
KeyNotFound(this, args);

View File

@ -32,5 +32,14 @@ namespace mustache
{
return new string[] { };
}
/// <summary>
/// Gets the parameter that is used to create a new child context.
/// </summary>
/// <returns>The parameter that is used to create a new child context.</returns>
public override TagParameter GetChildContextParameter()
{
return null;
}
}
}

View File

@ -14,11 +14,13 @@ namespace mustache
/// <param name="key">The key that was found.</param>
/// <param name="alignment">The alignment that will be applied to the substitute value.</param>
/// <param name="formatting">The formatting that will be applied to the substitute value.</param>
internal PlaceholderFoundEventArgs(string key, string alignment, string formatting)
/// <param name="context">The context where the placeholder was found.</param>
internal PlaceholderFoundEventArgs(string key, string alignment, string formatting, Context[] context)
{
Key = key;
Alignment = alignment;
Formatting = formatting;
Context = context;
}
/// <summary>
@ -35,5 +37,10 @@ namespace mustache
/// Gets or sets the formatting that will be applied to the substitute value.
/// </summary>
public string Formatting { get; set; }
/// <summary>
/// Gets the context where the placeholder was found.
/// </summary>
public Context[] Context { get; private set; }
}
}

View File

@ -34,6 +34,6 @@ using System.Runtime.CompilerServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.0.6.0")]
[assembly: AssemblyFileVersion("0.0.6.0")]
[assembly: AssemblyVersion("0.0.7.0")]
[assembly: AssemblyFileVersion("0.0.7.0")]
[assembly: InternalsVisibleTo("mustache-sharp.test")]

View File

@ -129,6 +129,12 @@ namespace mustache
return new string[] { };
}
/// <summary>
/// Gets the parameter that will be used to create a new child scope.
/// </summary>
/// <returns>The parameter that will be used to create a new child scope -or- null if no new scope is created.</returns>
public abstract TagParameter GetChildContextParameter();
/// <summary>
/// Gets the context to use when building the inner text of the tag.
/// </summary>

View File

@ -10,6 +10,7 @@ namespace mustache
internal sealed class WithTagDefinition : ContentTagDefinition
{
private const string contextParameter = "context";
private static readonly TagParameter context = new TagParameter(contextParameter) { IsRequired = true };
/// <summary>
/// Initializes a new instance of a WithTagDefinition.
@ -33,7 +34,16 @@ namespace mustache
/// <returns>The parameters.</returns>
protected override IEnumerable<TagParameter> GetParameters()
{
return new TagParameter[] { new TagParameter(contextParameter) { IsRequired = true } };
return new TagParameter[] { context };
}
/// <summary>
/// Gets the parameter that is used to create a new child context.
/// </summary>
/// <returns>The parameter that is used to create a new child context.</returns>
public override TagParameter GetChildContextParameter()
{
return context;
}
/// <summary>

View File

@ -38,6 +38,8 @@
<Compile Include="CompoundGenerator.cs" />
<Compile Include="ConditionTagDefinition.cs" />
<Compile Include="ContentTagDefinition.cs" />
<Compile Include="Context.cs" />
<Compile Include="KeyFoundEventArgs.cs" />
<Compile Include="InlineTagDefinition.cs" />
<Compile Include="EachTagDefinition.cs" />
<Compile Include="ElifTagDefinition.cs" />
@ -50,7 +52,7 @@
<Compile Include="PlaceholderFoundEventArgs.cs" />
<Compile Include="KeyGenerator.cs" />
<Compile Include="MasterTagDefinition.cs" />
<Compile Include="MissingKeyEventArgs.cs" />
<Compile Include="KeyNotFoundEventArgs.cs" />
<Compile Include="NestedContext.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">