Pop Context When Exiting Tag

I was not removing context when I was leaving a tag. This caused the
context to grow indefinitely, which is not a valid representation of
where the placeholder was located in the template.
This commit is contained in:
Travis Parks 2013-04-25 08:46:03 -04:00
parent 42463a888f
commit 11b73b696e
15 changed files with 105 additions and 57 deletions

View File

@ -362,10 +362,34 @@ Content";
Assert.IsNotNull(context, "The context was not set."); 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(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(String.Empty, context[0].TagName, "The top-most context had the wrong tag type.");
Assert.AreEqual("with", context[1].Tag.Name, "The inner context should have been a 'with' tag."); Assert.AreEqual("with", context[1].TagName, "The bottom context had the wrong tag type.");
Assert.AreEqual("Address", context[1].Argument, "The inner context argument was wrong.");
Assert.AreEqual(0, context[0].Parameters.Length, "The top-most context had the wrong number of parameters.");
Assert.AreEqual(1, context[1].Parameters.Length, "The bottom context had the wrong number of parameters.");
Assert.AreEqual("Address", context[1].Parameters[0].Argument, "The bottom context had the wrong argument.");
}
/// <summary>
/// I was leaving behind context even after reaching a closing tag. We need to make sure
/// that context is like a call stack and that it is cleaned up after leaving the context.
/// </summary>
[TestMethod]
public void TestCompile_ExitContext_RemoveContext()
{
FormatCompiler compiler = new FormatCompiler();
Context[] context = null;
compiler.PlaceholderFound += (o, e) =>
{
context = e.Context;
};
compiler.Compile(@"{{#with Address}}{{/with}}{{FirstName}}");
Assert.IsNotNull(context, "The context was not set.");
Assert.AreEqual(1, context.Length, "The context did not contain the right number of items.");
Assert.AreEqual(String.Empty, context[0].TagName, "The top-most context had the wrong tag type.");
} }
#endregion #endregion

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.7.0")] [assembly: AssemblyVersion("0.0.7.1")]
[assembly: AssemblyFileVersion("0.0.7.0")] [assembly: AssemblyFileVersion("0.0.7.1")]

View File

@ -26,20 +26,6 @@ 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

@ -90,12 +90,12 @@ namespace mustache
} }
/// <summary> /// <summary>
/// Gets the parameter that is used to create a new child context. /// Gets the parameters that are used to create a new child context.
/// </summary> /// </summary>
/// <returns>The parameter that is used to create a new child context.</returns> /// <returns>The parameters that are used to create a new child context.</returns>
public override TagParameter GetChildContextParameter() public override IEnumerable<TagParameter> GetChildContextParameters()
{ {
return null; return new TagParameter[0];
} }
} }
} }

View File

@ -10,22 +10,22 @@ namespace mustache
/// <summary> /// <summary>
/// Initializes a new instance of a Context. /// Initializes a new instance of a Context.
/// </summary> /// </summary>
/// <param name="definition">The definition of tag that created the context.</param> /// <param name="tagName">The name of the tag that created the context.</param>
/// <param name="argument">The argument used to create the context.</param> /// <param name="argument">The argument used to create the context.</param>
internal Context(TagDefinition definition, string argument) internal Context(string tagName, ContextParameter[] parameters)
{ {
Tag = definition; TagName = tagName;
Argument = argument; Parameters = parameters;
} }
/// <summary> /// <summary>
/// Gets the tag that created the context. /// Gets the tag that created the context.
/// </summary> /// </summary>
public TagDefinition Tag { get; private set; } public string TagName { get; private set; }
/// <summary> /// <summary>
/// Gets the argument used to create the context. /// Gets the argument used to create the context.
/// </summary> /// </summary>
public string Argument { get; private set; } public ContextParameter[] Parameters { get; private set; }
} }
} }

View File

@ -0,0 +1,31 @@
using System;
namespace mustache
{
/// <summary>
/// Holds information describing a parameter that creates a new context.
/// </summary>
public sealed class ContextParameter
{
/// <summary>
/// Initializes a new instance of a ContextParameter.
/// </summary>
/// <param name="parameter">The parameter that is used to create a new context.</param>
/// <param name="argument">The key whose corresponding value will be used to create the context.</param>
internal ContextParameter(string parameter, string argument)
{
Parameter = parameter;
Argument = argument;
}
/// <summary>
/// Gets the parameter that is used to create a new context.
/// </summary>
public string Parameter { get; private set; }
/// <summary>
/// Gets the key whose corresponding value will be used to create the context.
/// </summary>
public string Argument { get; private set; }
}
}

View File

@ -70,12 +70,12 @@ namespace mustache
} }
/// <summary> /// <summary>
/// Gets the parameter that is used to create a new child context. /// Gets the parameters that are used to create a new child context.
/// </summary> /// </summary>
/// <returns>The parameter that is used to create a new child context.</returns> /// <returns>The parameters that are used to create a new child context.</returns>
public override TagParameter GetChildContextParameter() public override IEnumerable<TagParameter> GetChildContextParameters()
{ {
return collection; return new TagParameter[] { collection };
} }
} }
} }

View File

@ -33,12 +33,12 @@ namespace mustache
} }
/// <summary> /// <summary>
/// Gets the parameter that is used to create a new child context. /// Gets the parameters that are used to create a new child context.
/// </summary> /// </summary>
/// <returns>The parameter that is used to create a new child context.</returns> /// <returns>The parameters that are used to create a new child context.</returns>
public override TagParameter GetChildContextParameter() public override IEnumerable<TagParameter> GetChildContextParameters()
{ {
return null; return new TagParameter[0];
} }
} }
} }

View File

@ -74,7 +74,7 @@ namespace mustache
} }
CompoundGenerator generator = new CompoundGenerator(_masterDefinition, new ArgumentCollection()); CompoundGenerator generator = new CompoundGenerator(_masterDefinition, new ArgumentCollection());
Trimmer trimmer = new Trimmer(); Trimmer trimmer = new Trimmer();
List<Context> context = new List<Context>() { new Context(_masterDefinition, "this") }; List<Context> context = new List<Context>() { new Context(_masterDefinition.Name, new ContextParameter[0]) };
int formatIndex = buildCompoundGenerator(_masterDefinition, context, generator, trimmer, format, 0); 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));
@ -218,13 +218,19 @@ 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);
string newContext = compoundGenerator.GetContextArgument(); IEnumerable<TagParameter> contextParameters = nextDefinition.GetChildContextParameters();
if (newContext != null) bool hasContext = contextParameters.Any();
if (hasContext)
{ {
context.Add(new Context(nextDefinition, newContext)); ContextParameter[] parameters = contextParameters.Select(p => new ContextParameter(p.Name, arguments.GetKey(p))).ToArray();
context.Add(new Context(nextDefinition.Name, parameters));
} }
formatIndex = buildCompoundGenerator(nextDefinition, context, compoundGenerator, trimmer, format, formatIndex); formatIndex = buildCompoundGenerator(nextDefinition, context, compoundGenerator, trimmer, format, formatIndex);
generator.AddGenerator(nextDefinition, compoundGenerator); generator.AddGenerator(nextDefinition, compoundGenerator);
if (hasContext)
{
context.RemoveAt(context.Count - 1);
}
} }
else else
{ {

View File

@ -38,12 +38,12 @@ namespace mustache
} }
/// <summary> /// <summary>
/// Gets the parameter that is used to create a child context. /// Gets the parameters that are used to create a child context.
/// </summary> /// </summary>
/// <returns>The parameter that is used to create a child context.</returns> /// <returns>The parameters that are used to create a child context.</returns>
public override TagParameter GetChildContextParameter() public override IEnumerable<TagParameter> GetChildContextParameters()
{ {
return null; return new TagParameter[0];
} }
} }
} }

View File

@ -34,12 +34,12 @@ namespace mustache
} }
/// <summary> /// <summary>
/// Gets the parameter that is used to create a new child context. /// Gets the parameters that are used to create a new child context.
/// </summary> /// </summary>
/// <returns>The parameter that is used to create a new child context.</returns> /// <returns>The parameters that are used to create a new child context.</returns>
public override TagParameter GetChildContextParameter() public override IEnumerable<TagParameter> GetChildContextParameters()
{ {
return null; return new TagParameter[0];
} }
} }
} }

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.7.0")] [assembly: AssemblyVersion("0.0.7.1")]
[assembly: AssemblyFileVersion("0.0.7.0")] [assembly: AssemblyFileVersion("0.0.7.1")]
[assembly: InternalsVisibleTo("mustache-sharp.test")] [assembly: InternalsVisibleTo("mustache-sharp.test")]

View File

@ -133,7 +133,7 @@ namespace mustache
/// Gets the parameter that will be used to create a new child scope. /// Gets the parameter that will be used to create a new child scope.
/// </summary> /// </summary>
/// <returns>The parameter that will be used to create a new child scope -or- null if no new scope is created.</returns> /// <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(); public abstract IEnumerable<TagParameter> GetChildContextParameters();
/// <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.

View File

@ -38,12 +38,12 @@ namespace mustache
} }
/// <summary> /// <summary>
/// Gets the parameter that is used to create a new child context. /// Gets the parameters that are used to create a new child context.
/// </summary> /// </summary>
/// <returns>The parameter that is used to create a new child context.</returns> /// <returns>The parameters that are used to create a new child context.</returns>
public override TagParameter GetChildContextParameter() public override IEnumerable<TagParameter> GetChildContextParameters()
{ {
return context; return new TagParameter[] { context };
} }
/// <summary> /// <summary>

View File

@ -39,6 +39,7 @@
<Compile Include="ConditionTagDefinition.cs" /> <Compile Include="ConditionTagDefinition.cs" />
<Compile Include="ContentTagDefinition.cs" /> <Compile Include="ContentTagDefinition.cs" />
<Compile Include="Context.cs" /> <Compile Include="Context.cs" />
<Compile Include="ContextParameter.cs" />
<Compile Include="KeyFoundEventArgs.cs" /> <Compile Include="KeyFoundEventArgs.cs" />
<Compile Include="InlineTagDefinition.cs" /> <Compile Include="InlineTagDefinition.cs" />
<Compile Include="EachTagDefinition.cs" /> <Compile Include="EachTagDefinition.cs" />