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. /// registering with the PlaceholderFound event.
/// </summary> /// </summary>
[TestMethod] [TestMethod]
public void TestCompile_FindsKeys_RecordsKeys() public void TestCompile_FindsPlaceholders_RecordsPlaceholders()
{ {
FormatCompiler compiler = new FormatCompiler(); FormatCompiler compiler = new FormatCompiler();
HashSet<string> keys = new HashSet<string>(); HashSet<string> keys = new HashSet<string>();
@ -346,6 +346,28 @@ Content";
CollectionAssert.AreEqual(expected, actual, "Not all placeholders were found."); 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 #endregion
#region Comment #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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("0.0.6.0")] [assembly: AssemblyVersion("0.0.7.0")]
[assembly: AssemblyFileVersion("0.0.6.0")] [assembly: AssemblyFileVersion("0.0.7.0")]

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace mustache namespace mustache
{ {
@ -29,6 +30,23 @@ namespace mustache
_argumentLookup.Add(parameter, key); _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> /// <summary>
/// Substitutes the key placeholders with their respective values. /// Substitutes the key placeholders with their respective values.
/// </summary> /// </summary>

View File

@ -26,6 +26,20 @@ namespace mustache
_primaryGenerators = new LinkedList<IGenerator>(); _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> /// <summary>
/// Adds the given generator. /// Adds the given generator.
/// </summary> /// </summary>

View File

@ -88,5 +88,14 @@ namespace mustache
return true; 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 internal sealed class EachTagDefinition : ContentTagDefinition
{ {
private const string collectionParameter = "collection"; private const string collectionParameter = "collection";
private static readonly TagParameter collection = new TagParameter(collectionParameter) { IsRequired = true };
/// <summary> /// <summary>
/// Initializes a new instance of an EachTagDefinition. /// Initializes a new instance of an EachTagDefinition.
@ -35,7 +36,7 @@ namespace mustache
/// <returns>The parameters.</returns> /// <returns>The parameters.</returns>
protected override IEnumerable<TagParameter> GetParameters() protected override IEnumerable<TagParameter> GetParameters()
{ {
return new TagParameter[] { new TagParameter(collectionParameter) { IsRequired = true } }; return new TagParameter[] { collection };
} }
/// <summary> /// <summary>
@ -67,5 +68,14 @@ namespace mustache
{ {
return new string[] { }; 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" }; 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()); CompoundGenerator generator = new CompoundGenerator(_masterDefinition, new ArgumentCollection());
Trimmer trimmer = new Trimmer(); 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); string trailing = format.Substring(formatIndex);
generator.AddStaticGenerators(trimmer.RecordText(trailing, false, false)); generator.AddStaticGenerators(trimmer.RecordText(trailing, false, false));
trimmer.Trim(); trimmer.Trim();
@ -166,6 +167,7 @@ namespace mustache
private int buildCompoundGenerator( private int buildCompoundGenerator(
TagDefinition tagDefinition, TagDefinition tagDefinition,
List<Context> context,
CompoundGenerator generator, CompoundGenerator generator,
Trimmer trimmer, Trimmer trimmer,
string format, int formatIndex) string format, int formatIndex)
@ -193,7 +195,7 @@ namespace mustache
string key = match.Groups["key"].Value; string key = match.Groups["key"].Value;
string alignment = match.Groups["alignment"].Value; string alignment = match.Groups["alignment"].Value;
string formatting = match.Groups["format"].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) if (PlaceholderFound != null)
{ {
PlaceholderFound(this, args); PlaceholderFound(this, args);
@ -216,7 +218,12 @@ namespace mustache
generator.AddStaticGenerators(trimmer.RecordText(leading, true, false)); generator.AddStaticGenerators(trimmer.RecordText(leading, true, false));
ArgumentCollection arguments = getArguments(nextDefinition, match); ArgumentCollection arguments = getArguments(nextDefinition, match);
CompoundGenerator compoundGenerator = new CompoundGenerator(nextDefinition, arguments); 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); generator.AddGenerator(nextDefinition, compoundGenerator);
} }
else else

View File

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

View File

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

View File

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

View File

@ -32,5 +32,14 @@ namespace mustache
{ {
return new string[] { }; 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="key">The key that was found.</param>
/// <param name="alignment">The alignment that will be applied to the substitute value.</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> /// <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; Key = key;
Alignment = alignment; Alignment = alignment;
Formatting = formatting; Formatting = formatting;
Context = context;
} }
/// <summary> /// <summary>
@ -35,5 +37,10 @@ namespace mustache
/// Gets or sets the formatting that will be applied to the substitute value. /// Gets or sets the formatting that will be applied to the substitute value.
/// </summary> /// </summary>
public string Formatting { get; set; } 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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.0.6.0")] [assembly: AssemblyVersion("0.0.7.0")]
[assembly: AssemblyFileVersion("0.0.6.0")] [assembly: AssemblyFileVersion("0.0.7.0")]
[assembly: InternalsVisibleTo("mustache-sharp.test")] [assembly: InternalsVisibleTo("mustache-sharp.test")]

View File

@ -129,6 +129,12 @@ namespace mustache
return new string[] { }; 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> /// <summary>
/// Gets the context to use when building the inner text of the tag. /// Gets the context to use when building the inner text of the tag.
/// </summary> /// </summary>

View File

@ -10,6 +10,7 @@ namespace mustache
internal sealed class WithTagDefinition : ContentTagDefinition internal sealed class WithTagDefinition : ContentTagDefinition
{ {
private const string contextParameter = "context"; private const string contextParameter = "context";
private static readonly TagParameter context = new TagParameter(contextParameter) { IsRequired = true };
/// <summary> /// <summary>
/// Initializes a new instance of a WithTagDefinition. /// Initializes a new instance of a WithTagDefinition.
@ -33,7 +34,16 @@ namespace mustache
/// <returns>The parameters.</returns> /// <returns>The parameters.</returns>
protected override IEnumerable<TagParameter> GetParameters() 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> /// <summary>

View File

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