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,56 +1,74 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace mustache
{ namespace mustache
/// <summary> {
/// Associates parameters to their argument values. /// <summary>
/// </summary> /// Associates parameters to their argument values.
internal sealed class ArgumentCollection /// </summary>
{ internal sealed class ArgumentCollection
private readonly Dictionary<TagParameter, string> _argumentLookup; {
private readonly Dictionary<TagParameter, string> _argumentLookup;
/// <summary>
/// Initializes a new instance of an ArgumentCollection. /// <summary>
/// </summary> /// Initializes a new instance of an ArgumentCollection.
public ArgumentCollection() /// </summary>
{ public ArgumentCollection()
_argumentLookup = new Dictionary<TagParameter, string>(); {
} _argumentLookup = new Dictionary<TagParameter, string>();
}
/// <summary>
/// Associates the given parameter to the key placeholder. /// <summary>
/// </summary> /// Associates the given parameter to the key placeholder.
/// <param name="parameter">The parameter to associate the key with.</param> /// </summary>
/// <param name="key">The key placeholder used as the argument.</param> /// <param name="parameter">The parameter to associate the key with.</param>
/// <remarks>If the key is null, the default value of the parameter will be used.</remarks> /// <param name="key">The key placeholder used as the argument.</param>
public void AddArgument(TagParameter parameter, string key) /// <remarks>If the key is null, the default value of the parameter will be used.</remarks>
{ public void AddArgument(TagParameter parameter, string key)
_argumentLookup.Add(parameter, key); {
} _argumentLookup.Add(parameter, key);
}
/// <summary>
/// Substitutes the key placeholders with their respective values. /// <summary>
/// </summary> /// Gets the key that will be used to find the substitute value.
/// <param name="scope">The current lexical scope.</param> /// </summary>
/// <returns>A dictionary associating the parameter name to the associated value.</returns> /// <param name="parameterName">The name of the parameter.</param>
public Dictionary<string, object> GetArguments(KeyScope scope) public string GetKey(TagParameter parameter)
{ {
Dictionary<string, object> arguments = new Dictionary<string,object>(); string key;
foreach (KeyValuePair<TagParameter, string> pair in _argumentLookup) if (_argumentLookup.TryGetValue(parameter, out key))
{ {
object value; return key;
if (pair.Value == null) }
{ else
value = pair.Key.DefaultValue; {
} return null;
else }
{ }
value = scope.Find(pair.Value);
} /// <summary>
arguments.Add(pair.Key.Name, value); /// Substitutes the key placeholders with their respective values.
} /// </summary>
return arguments; /// <param name="scope">The current lexical scope.</param>
} /// <returns>A dictionary associating the parameter name to the associated value.</returns>
} public Dictionary<string, object> GetArguments(KeyScope scope)
} {
Dictionary<string, object> arguments = new Dictionary<string,object>();
foreach (KeyValuePair<TagParameter, string> pair in _argumentLookup)
{
object value;
if (pair.Value == null)
{
value = pair.Key.DefaultValue;
}
else
{
value = scope.Find(pair.Value);
}
arguments.Add(pair.Key.Name, value);
}
return arguments;
}
}
}

View File

@ -1,105 +1,119 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace mustache namespace mustache
{ {
/// <summary> /// <summary>
/// Builds text by combining the output of other generators. /// Builds text by combining the output of other generators.
/// </summary> /// </summary>
internal sealed class CompoundGenerator : IGenerator internal sealed class CompoundGenerator : IGenerator
{ {
private readonly TagDefinition _definition; private readonly TagDefinition _definition;
private readonly ArgumentCollection _arguments; private readonly ArgumentCollection _arguments;
private readonly LinkedList<IGenerator> _primaryGenerators; private readonly LinkedList<IGenerator> _primaryGenerators;
private IGenerator _subGenerator; private IGenerator _subGenerator;
/// <summary> /// <summary>
/// Initializes a new instance of a CompoundGenerator. /// Initializes a new instance of a CompoundGenerator.
/// </summary> /// </summary>
/// <param name="definition">The tag that the text is being generated for.</param> /// <param name="definition">The tag that the text is being generated for.</param>
/// <param name="arguments">The arguments that were passed to the tag.</param> /// <param name="arguments">The arguments that were passed to the tag.</param>
public CompoundGenerator(TagDefinition definition, ArgumentCollection arguments) public CompoundGenerator(TagDefinition definition, ArgumentCollection arguments)
{ {
_definition = definition; _definition = definition;
_arguments = arguments; _arguments = arguments;
_primaryGenerators = new LinkedList<IGenerator>(); _primaryGenerators = new LinkedList<IGenerator>();
} }
/// <summary> /// <summary>
/// Adds the given generator. /// Gets the argument that will act as the context for the content.
/// </summary> /// </summary>
/// <param name="generator">The generator to add.</param> /// <returns>The argument that will act as the context for the content.</returns>
public void AddGenerator(IGenerator generator) public string GetContextArgument()
{ {
addGenerator(generator, false); TagParameter parameter = _definition.GetChildContextParameter();
} if (parameter == null)
{
/// <summary> return null;
/// Adds the given generator, determining whether the generator should }
/// be part of the primary generators or added as an secondary generator. return _arguments.GetKey(parameter);
/// </summary> }
/// <param name="definition">The tag that the generator is generating text for.</param>
/// <param name="generator">The generator to add.</param> /// <summary>
public void AddGenerator(TagDefinition definition, IGenerator generator) /// Adds the given generator.
{ /// </summary>
bool isSubGenerator = _definition.ShouldCreateSecondaryGroup(definition); /// <param name="generator">The generator to add.</param>
addGenerator(generator, isSubGenerator); public void AddGenerator(IGenerator generator)
} {
addGenerator(generator, false);
/// <summary> }
/// Creates a StaticGenerator from the given value and adds it.
/// </summary> /// <summary>
/// <param name="generators">The static generators to add.</param> /// Adds the given generator, determining whether the generator should
public void AddStaticGenerators(IEnumerable<StaticGenerator> generators) /// be part of the primary generators or added as an secondary generator.
{ /// </summary>
foreach (StaticGenerator generator in generators) /// <param name="definition">The tag that the generator is generating text for.</param>
{ /// <param name="generator">The generator to add.</param>
LinkedListNode<IGenerator> node = _primaryGenerators.AddLast(generator); public void AddGenerator(TagDefinition definition, IGenerator generator)
generator.Node = node; {
} bool isSubGenerator = _definition.ShouldCreateSecondaryGroup(definition);
} addGenerator(generator, isSubGenerator);
}
private void addGenerator(IGenerator generator, bool isSubGenerator)
{ /// <summary>
if (isSubGenerator) /// Creates a StaticGenerator from the given value and adds it.
{ /// </summary>
_subGenerator = generator; /// <param name="generators">The static generators to add.</param>
} public void AddStaticGenerators(IEnumerable<StaticGenerator> generators)
else {
{ foreach (StaticGenerator generator in generators)
_primaryGenerators.AddLast(generator); {
} LinkedListNode<IGenerator> node = _primaryGenerators.AddLast(generator);
} generator.Node = node;
}
void IGenerator.GetText(KeyScope scope, TextWriter writer) }
{
Dictionary<string, object> arguments = _arguments.GetArguments(scope); private void addGenerator(IGenerator generator, bool isSubGenerator)
IEnumerable<NestedContext> contexts = _definition.GetChildContext(writer, scope, arguments); {
LinkedList<IGenerator> generators; if (isSubGenerator)
if (_definition.ShouldGeneratePrimaryGroup(arguments)) {
{ _subGenerator = generator;
generators = _primaryGenerators; }
} else
else {
{ _primaryGenerators.AddLast(generator);
generators = new LinkedList<IGenerator>(); }
if (_subGenerator != null) }
{
generators.AddLast(_subGenerator); void IGenerator.GetText(KeyScope scope, TextWriter writer)
} {
} Dictionary<string, object> arguments = _arguments.GetArguments(scope);
foreach (NestedContext context in contexts) IEnumerable<NestedContext> contexts = _definition.GetChildContext(writer, scope, arguments);
{ LinkedList<IGenerator> generators;
foreach (IGenerator generator in generators) if (_definition.ShouldGeneratePrimaryGroup(arguments))
{ {
generator.GetText(context.KeyScope ?? scope, context.Writer ?? writer); generators = _primaryGenerators;
if (context.WriterNeedsConsidated) }
{ else
writer.Write(_definition.ConsolidateWriter(context.Writer ?? writer, arguments)); {
} generators = new LinkedList<IGenerator>();
} if (_subGenerator != null)
} {
} generators.AddLast(_subGenerator);
} }
}
foreach (NestedContext context in contexts)
{
foreach (IGenerator generator in generators)
{
generator.GetText(context.KeyScope ?? scope, context.Writer ?? writer);
if (context.WriterNeedsConsidated)
{
writer.Write(_definition.ConsolidateWriter(context.Writer ?? writer, arguments));
}
}
}
}
}
} }

View File

@ -1,92 +1,101 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace mustache namespace mustache
{ {
/// <summary> /// <summary>
/// Defines a tag that conditionally prints its content. /// Defines a tag that conditionally prints its content.
/// </summary> /// </summary>
internal abstract class ConditionTagDefinition : ContentTagDefinition internal abstract class ConditionTagDefinition : ContentTagDefinition
{ {
private const string conditionParameter = "condition"; private const string conditionParameter = "condition";
/// <summary> /// <summary>
/// Initializes a new instance of a ConditionTagDefinition. /// Initializes a new instance of a ConditionTagDefinition.
/// </summary> /// </summary>
/// <param name="tagName">The name of the tag.</param> /// <param name="tagName">The name of the tag.</param>
protected ConditionTagDefinition(string tagName) protected ConditionTagDefinition(string tagName)
: base(tagName, true) : base(tagName, true)
{ {
} }
/// <summary> /// <summary>
/// Gets the parameters that can be passed to the tag. /// Gets the parameters that can be passed to the tag.
/// </summary> /// </summary>
/// <returns>The parameters.</returns> /// <returns>The parameters.</returns>
protected override IEnumerable<TagParameter> GetParameters() protected override IEnumerable<TagParameter> GetParameters()
{ {
return new TagParameter[] { new TagParameter(conditionParameter) { IsRequired = true } }; return new TagParameter[] { new TagParameter(conditionParameter) { IsRequired = true } };
} }
/// <summary> /// <summary>
/// Gets the tags that come into scope within the context of the current tag. /// Gets the tags that come into scope within the context of the current tag.
/// </summary> /// </summary>
/// <returns>The child tag definitions.</returns> /// <returns>The child tag definitions.</returns>
protected override IEnumerable<string> GetChildTags() protected override IEnumerable<string> GetChildTags()
{ {
return new string[] { "elif", "else" }; return new string[] { "elif", "else" };
} }
/// <summary> /// <summary>
/// Gets whether the given tag's generator should be used for a secondary (or substitute) text block. /// Gets whether the given tag's generator should be used for a secondary (or substitute) text block.
/// </summary> /// </summary>
/// <param name="definition">The tag to inspect.</param> /// <param name="definition">The tag to inspect.</param>
/// <returns>True if the tag's generator should be used as a secondary generator.</returns> /// <returns>True if the tag's generator should be used as a secondary generator.</returns>
public override bool ShouldCreateSecondaryGroup(TagDefinition definition) public override bool ShouldCreateSecondaryGroup(TagDefinition definition)
{ {
return new string[] { "elif", "else" }.Contains(definition.Name); return new string[] { "elif", "else" }.Contains(definition.Name);
} }
/// <summary> /// <summary>
/// Gets whether the primary generator group should be used to render the tag. /// Gets whether the primary generator group should be used to render the tag.
/// </summary> /// </summary>
/// <param name="arguments">The arguments passed to the tag.</param> /// <param name="arguments">The arguments passed to the tag.</param>
/// <returns> /// <returns>
/// True if the primary generator group should be used to render the tag; /// True if the primary generator group should be used to render the tag;
/// otherwise, false to use the secondary group. /// otherwise, false to use the secondary group.
/// </returns> /// </returns>
public override bool ShouldGeneratePrimaryGroup(Dictionary<string, object> arguments) public override bool ShouldGeneratePrimaryGroup(Dictionary<string, object> arguments)
{ {
object condition = arguments[conditionParameter]; object condition = arguments[conditionParameter];
return isConditionSatisfied(condition); return isConditionSatisfied(condition);
} }
private bool isConditionSatisfied(object condition) private bool isConditionSatisfied(object condition)
{ {
if (condition == null) if (condition == null)
{ {
return false; return false;
} }
IEnumerable enumerable = condition as IEnumerable; IEnumerable enumerable = condition as IEnumerable;
if (enumerable != null) if (enumerable != null)
{ {
return enumerable.Cast<object>().Any(); return enumerable.Cast<object>().Any();
} }
if (condition is Char) if (condition is Char)
{ {
return (Char)condition != '\0'; return (Char)condition != '\0';
} }
try try
{ {
decimal number = (decimal)Convert.ChangeType(condition, typeof(decimal)); decimal number = (decimal)Convert.ChangeType(condition, typeof(decimal));
return number != 0.0m; return number != 0.0m;
} }
catch catch
{ {
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

@ -1,71 +1,81 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace mustache namespace mustache
{ {
/// <summary> /// <summary>
/// Defines a tag that can iterate over a collection of items and render /// Defines a tag that can iterate over a collection of items and render
/// the content using each item as the context. /// the content using each item as the context.
/// </summary> /// </summary>
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>
/// Initializes a new instance of an EachTagDefinition. /// <summary>
/// </summary> /// Initializes a new instance of an EachTagDefinition.
public EachTagDefinition() /// </summary>
: base("each", true) public EachTagDefinition()
{ : base("each", true)
} {
}
/// <summary>
/// Gets whether the tag only exists within the scope of its parent. /// <summary>
/// </summary> /// Gets whether the tag only exists within the scope of its parent.
protected override bool GetIsContextSensitive() /// </summary>
{ protected override bool GetIsContextSensitive()
return false; {
} return false;
}
/// <summary>
/// Gets the parameters that can be passed to the tag. /// <summary>
/// </summary> /// Gets the parameters that can be passed to the tag.
/// <returns>The parameters.</returns> /// </summary>
protected override IEnumerable<TagParameter> GetParameters() /// <returns>The parameters.</returns>
{ protected override IEnumerable<TagParameter> GetParameters()
return new TagParameter[] { new TagParameter(collectionParameter) { IsRequired = true } }; {
} return new TagParameter[] { collection };
}
/// <summary>
/// Gets the context to use when building the inner text of the tag. /// <summary>
/// </summary> /// Gets the context to use when building the inner text of the tag.
/// <param name="writer">The text writer passed</param> /// </summary>
/// <param name="scope">The current scope.</param> /// <param name="writer">The text writer passed</param>
/// <param name="arguments">The arguments passed to the tag.</param> /// <param name="scope">The current scope.</param>
/// <returns>The scope to use when building the inner text of the tag.</returns> /// <param name="arguments">The arguments passed to the tag.</param>
public override IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments) /// <returns>The scope to use when building the inner text of the tag.</returns>
{ public override IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments)
object value = arguments[collectionParameter]; {
IEnumerable enumerable = value as IEnumerable; object value = arguments[collectionParameter];
if (enumerable == null) IEnumerable enumerable = value as IEnumerable;
{ if (enumerable == null)
yield break; {
} yield break;
foreach (object item in enumerable) }
{ foreach (object item in enumerable)
yield return new NestedContext() { KeyScope = scope.CreateChildScope(item), Writer = writer }; {
} yield return new NestedContext() { KeyScope = scope.CreateChildScope(item), Writer = writer };
} }
}
/// <summary>
/// Gets the tags that are in scope under this tag. /// <summary>
/// </summary> /// Gets the tags that are in scope under this tag.
/// <returns>The name of the tags that are in scope.</returns> /// </summary>
protected override IEnumerable<string> GetChildTags() /// <returns>The name of the tags that are in scope.</returns>
{ protected override IEnumerable<string> GetChildTags()
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

@ -1,35 +1,44 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace mustache namespace mustache
{ {
/// <summary> /// <summary>
/// Defines a tag that renders its content if all preceding if and elif tags. /// Defines a tag that renders its content if all preceding if and elif tags.
/// </summary> /// </summary>
internal sealed class ElseTagDefinition : ContentTagDefinition internal sealed class ElseTagDefinition : ContentTagDefinition
{ {
/// <summary> /// <summary>
/// Initializes a new instance of a ElseTagDefinition. /// Initializes a new instance of a ElseTagDefinition.
/// </summary> /// </summary>
public ElseTagDefinition() public ElseTagDefinition()
: base("else", true) : base("else", true)
{ {
} }
/// <summary> /// <summary>
/// Gets whether the tag only exists within the scope of its parent. /// Gets whether the tag only exists within the scope of its parent.
/// </summary> /// </summary>
protected override bool GetIsContextSensitive() protected override bool GetIsContextSensitive()
{ {
return true; return true;
} }
/// <summary> /// <summary>
/// Gets the tags that indicate the end of the current tag's content. /// Gets the tags that indicate the end of the current tag's content.
/// </summary> /// </summary>
protected override IEnumerable<string> GetClosingTags() protected override IEnumerable<string> GetClosingTags()
{ {
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();
@ -165,7 +166,8 @@ 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

@ -1,40 +1,49 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace mustache namespace mustache
{ {
/// <summary> /// <summary>
/// Defines a tag that cannot contain inner text. /// Defines a tag that cannot contain inner text.
/// </summary> /// </summary>
public abstract class InlineTagDefinition : TagDefinition public abstract class InlineTagDefinition : TagDefinition
{ {
/// <summary> /// <summary>
/// Initializes a new instance of an InlineTagDefinition. /// Initializes a new instance of an InlineTagDefinition.
/// </summary> /// </summary>
/// <param name="tagName">The name of the tag being defined.</param> /// <param name="tagName">The name of the tag being defined.</param>
protected InlineTagDefinition(string tagName) protected InlineTagDefinition(string tagName)
: base(tagName) : base(tagName)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of an InlineTagDefinition. /// Initializes a new instance of an InlineTagDefinition.
/// </summary> /// </summary>
/// <param name="tagName">The name of the tag being defined.</param> /// <param name="tagName">The name of the tag being defined.</param>
/// <param name="isBuiltin">Specifies whether the tag is a built-in tag.</param> /// <param name="isBuiltin">Specifies whether the tag is a built-in tag.</param>
internal InlineTagDefinition(string tagName, bool isBuiltin) internal InlineTagDefinition(string tagName, bool isBuiltin)
: base(tagName, isBuiltin) : base(tagName, isBuiltin)
{ {
} }
/// <summary> /// <summary>
/// Gets or sets whether the tag can have content. /// Gets or sets whether the tag can have content.
/// </summary> /// </summary>
/// <returns>True if the tag can have a body; otherwise, false.</returns> /// <returns>True if the tag can have a body; otherwise, false.</returns>
protected override bool GetHasContent() protected override bool GetHasContent()
{ {
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

@ -1,36 +1,45 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace mustache namespace mustache
{ {
/// <summary> /// <summary>
/// Defines a pseudo tag that wraps the entire content of a format string. /// Defines a pseudo tag that wraps the entire content of a format string.
/// </summary> /// </summary>
internal sealed class MasterTagDefinition : ContentTagDefinition internal sealed class MasterTagDefinition : ContentTagDefinition
{ {
/// <summary> /// <summary>
/// Initializes a new instance of a MasterTagDefinition. /// Initializes a new instance of a MasterTagDefinition.
/// </summary> /// </summary>
public MasterTagDefinition() public MasterTagDefinition()
: base(String.Empty, true) : base(String.Empty, true)
{ {
} }
/// <summary> /// <summary>
/// Gets whether the tag only exists within the scope of its parent. /// Gets whether the tag only exists within the scope of its parent.
/// </summary> /// </summary>
protected override bool GetIsContextSensitive() protected override bool GetIsContextSensitive()
{ {
return true; return true;
} }
/// <summary> /// <summary>
/// Gets the name of the tags that indicate that the tag's context is closed. /// Gets the name of the tags that indicate that the tag's context is closed.
/// </summary> /// </summary>
/// <returns>The tag names.</returns> /// <returns>The tag names.</returns>
protected override IEnumerable<string> GetClosingTags() protected override IEnumerable<string> GetClosingTags()
{ {
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

@ -1,184 +1,190 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using mustache.Properties; using mustache.Properties;
using System.IO; using System.IO;
namespace mustache namespace mustache
{ {
/// <summary> /// <summary>
/// Defines the attributes of a custom tag. /// Defines the attributes of a custom tag.
/// </summary> /// </summary>
public abstract class TagDefinition public abstract class TagDefinition
{ {
private readonly string _tagName; private readonly string _tagName;
/// <summary> /// <summary>
/// Initializes a new instance of a TagDefinition. /// Initializes a new instance of a TagDefinition.
/// </summary> /// </summary>
/// <param name="tagName">The name of the tag.</param> /// <param name="tagName">The name of the tag.</param>
/// <exception cref="System.ArgumentException">The name of the tag is null or blank.</exception> /// <exception cref="System.ArgumentException">The name of the tag is null or blank.</exception>
protected TagDefinition(string tagName) protected TagDefinition(string tagName)
: this(tagName, false) : this(tagName, false)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of a TagDefinition. /// Initializes a new instance of a TagDefinition.
/// </summary> /// </summary>
/// <param name="tagName">The name of the tag.</param> /// <param name="tagName">The name of the tag.</param>
/// <param name="isBuiltIn">Specifies whether the tag is built-in or not. Checks are not performed on the names of built-in tags.</param> /// <param name="isBuiltIn">Specifies whether the tag is built-in or not. Checks are not performed on the names of built-in tags.</param>
internal TagDefinition(string tagName, bool isBuiltIn) internal TagDefinition(string tagName, bool isBuiltIn)
{ {
if (!isBuiltIn && !RegexHelper.IsValidIdentifier(tagName)) if (!isBuiltIn && !RegexHelper.IsValidIdentifier(tagName))
{ {
throw new ArgumentException(Resources.BlankTagName, "tagName"); throw new ArgumentException(Resources.BlankTagName, "tagName");
} }
_tagName = tagName; _tagName = tagName;
} }
/// <summary> /// <summary>
/// Gets the name of the tag. /// Gets the name of the tag.
/// </summary> /// </summary>
public string Name public string Name
{ {
get { return _tagName; } get { return _tagName; }
} }
/// <summary> /// <summary>
/// Gets whether the tag is limited to the parent tag's context. /// Gets whether the tag is limited to the parent tag's context.
/// </summary> /// </summary>
internal bool IsContextSensitive internal bool IsContextSensitive
{ {
get { return GetIsContextSensitive(); } get { return GetIsContextSensitive(); }
} }
/// <summary> /// <summary>
/// Gets whether a tag is limited to the parent tag's context. /// Gets whether a tag is limited to the parent tag's context.
/// </summary> /// </summary>
protected virtual bool GetIsContextSensitive() protected virtual bool GetIsContextSensitive()
{ {
return false; return false;
} }
/// <summary> /// <summary>
/// Gets the parameters that are defined for the tag. /// Gets the parameters that are defined for the tag.
/// </summary> /// </summary>
internal IEnumerable<TagParameter> Parameters internal IEnumerable<TagParameter> Parameters
{ {
get { return GetParameters(); } get { return GetParameters(); }
} }
/// <summary> /// <summary>
/// Specifies which parameters are passed to the tag. /// Specifies which parameters are passed to the tag.
/// </summary> /// </summary>
/// <returns>The tag parameters.</returns> /// <returns>The tag parameters.</returns>
protected virtual IEnumerable<TagParameter> GetParameters() protected virtual IEnumerable<TagParameter> GetParameters()
{ {
return new TagParameter[] { }; return new TagParameter[] { };
} }
/// <summary> /// <summary>
/// Gets whether the tag contains content. /// Gets whether the tag contains content.
/// </summary> /// </summary>
internal bool HasContent internal bool HasContent
{ {
get { return GetHasContent(); } get { return GetHasContent(); }
} }
/// <summary> /// <summary>
/// Gets whether tag has content. /// Gets whether tag has content.
/// </summary> /// </summary>
/// <returns>True if the tag has content; otherwise, false.</returns> /// <returns>True if the tag has content; otherwise, false.</returns>
protected abstract bool GetHasContent(); protected abstract bool GetHasContent();
/// <summary> /// <summary>
/// Gets the tags that can indicate that the tag has closed. /// Gets the tags that can indicate that the tag has closed.
/// This field is only used if no closing tag is expected. /// This field is only used if no closing tag is expected.
/// </summary> /// </summary>
internal IEnumerable<string> ClosingTags internal IEnumerable<string> ClosingTags
{ {
get { return GetClosingTags(); } get { return GetClosingTags(); }
} }
protected virtual IEnumerable<string> GetClosingTags() protected virtual IEnumerable<string> GetClosingTags()
{ {
if (HasContent) if (HasContent)
{ {
return new string[] { Name }; return new string[] { Name };
} }
else else
{ {
return new string[] { }; return new string[] { };
} }
} }
/// <summary> /// <summary>
/// Gets the tags that are in scope within the current tag. /// Gets the tags that are in scope within the current tag.
/// </summary> /// </summary>
internal IEnumerable<string> ChildTags internal IEnumerable<string> ChildTags
{ {
get { return GetChildTags(); } get { return GetChildTags(); }
} }
/// <summary> /// <summary>
/// Specifies which tags are scoped under the current tag. /// Specifies which tags are scoped under the current tag.
/// </summary> /// </summary>
/// <returns>The child tag definitions.</returns> /// <returns>The child tag definitions.</returns>
protected virtual IEnumerable<string> GetChildTags() protected virtual IEnumerable<string> GetChildTags()
{ {
return new string[] { }; return new string[] { };
} }
/// <summary> /// <summary>
/// Gets the context to use when building the inner text of the tag. /// Gets the parameter that will be used to create a new child scope.
/// </summary> /// </summary>
/// <param name="writer">The text writer passed</param> /// <returns>The parameter that will be used to create a new child scope -or- null if no new scope is created.</returns>
/// <param name="scope">The current scope.</param> public abstract TagParameter GetChildContextParameter();
/// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The scope to use when building the inner text of the tag.</returns> /// <summary>
public virtual IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments) /// Gets the context to use when building the inner text of the tag.
{ /// </summary>
yield return new NestedContext() { KeyScope = scope, Writer = writer }; /// <param name="writer">The text writer passed</param>
} /// <param name="scope">The current scope.</param>
/// <param name="arguments">The arguments passed to the tag.</param>
/// <summary> /// <returns>The scope to use when building the inner text of the tag.</returns>
/// Applies additional formatting to the inner text of the tag. public virtual IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments)
/// </summary> {
/// <param name="writer">The text writer to write to.</param> yield return new NestedContext() { KeyScope = scope, Writer = writer };
/// <param name="arguments">The arguments passed to the tag.</param> }
public virtual void GetText(TextWriter writer, Dictionary<string, object> arguments)
{ /// <summary>
} /// Applies additional formatting to the inner text of the tag.
/// </summary>
/// <summary> /// <param name="writer">The text writer to write to.</param>
/// Consolidates the text in the given writer to a string, using the given arguments as necessary. /// <param name="arguments">The arguments passed to the tag.</param>
/// </summary> public virtual void GetText(TextWriter writer, Dictionary<string, object> arguments)
/// <param name="writer">The writer containing the text to consolidate.</param> {
/// <param name="arguments">The arguments passed to the tag.</param> }
/// <returns>The consolidated string.</returns>
public virtual string ConsolidateWriter(TextWriter writer, Dictionary<string, object> arguments) /// <summary>
{ /// Consolidates the text in the given writer to a string, using the given arguments as necessary.
return writer.ToString(); /// </summary>
} /// <param name="writer">The writer containing the text to consolidate.</param>
/// <param name="arguments">The arguments passed to the tag.</param>
/// <summary> /// <returns>The consolidated string.</returns>
/// Requests which generator group to associate the given tag type. public virtual string ConsolidateWriter(TextWriter writer, Dictionary<string, object> arguments)
/// </summary> {
/// <param name="definition">The child tag definition being grouped.</param> return writer.ToString();
/// <returns>The name of the group to associate the given tag with.</returns> }
public virtual bool ShouldCreateSecondaryGroup(TagDefinition definition)
{ /// <summary>
return false; /// Requests which generator group to associate the given tag type.
} /// </summary>
/// <param name="definition">The child tag definition being grouped.</param>
/// <summary> /// <returns>The name of the group to associate the given tag with.</returns>
/// Gets whether the group with the given name should have text generated for them. public virtual bool ShouldCreateSecondaryGroup(TagDefinition definition)
/// </summary> {
/// <param name="arguments">The arguments passed to the tag.</param> return false;
/// <returns>True if text should be generated for the group; otherwise, false.</returns> }
public virtual bool ShouldGeneratePrimaryGroup(Dictionary<string, object> arguments)
{ /// <summary>
return true; /// Gets whether the group with the given name should have text generated for them.
} /// </summary>
} /// <param name="arguments">The arguments passed to the tag.</param>
} /// <returns>True if text should be generated for the group; otherwise, false.</returns>
public virtual bool ShouldGeneratePrimaryGroup(Dictionary<string, object> arguments)
{
return true;
}
}
}

View File

@ -1,52 +1,62 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace mustache namespace mustache
{ {
/// <summary> /// <summary>
/// Defines a tag that changes the scope to the object passed as an argument. /// Defines a tag that changes the scope to the object passed as an argument.
/// </summary> /// </summary>
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>
/// Initializes a new instance of a WithTagDefinition. /// <summary>
/// </summary> /// Initializes a new instance of a WithTagDefinition.
public WithTagDefinition() /// </summary>
: base("with", true) public WithTagDefinition()
{ : base("with", true)
} {
}
/// <summary>
/// Gets whether the tag only exists within the scope of its parent. /// <summary>
/// </summary> /// Gets whether the tag only exists within the scope of its parent.
protected override bool GetIsContextSensitive() /// </summary>
{ protected override bool GetIsContextSensitive()
return false; {
} return false;
}
/// <summary>
/// Gets the parameters that can be passed to the tag. /// <summary>
/// </summary> /// Gets the parameters that can be passed to the tag.
/// <returns>The parameters.</returns> /// </summary>
protected override IEnumerable<TagParameter> GetParameters() /// <returns>The parameters.</returns>
{ protected override IEnumerable<TagParameter> GetParameters()
return new TagParameter[] { new TagParameter(contextParameter) { IsRequired = true } }; {
} return new TagParameter[] { context };
}
/// <summary>
/// Gets the context to use when building the inner text of the tag. /// <summary>
/// </summary> /// Gets the parameter that is used to create a new child context.
/// <param name="writer">The text writer passed</param> /// </summary>
/// <param name="scope">The current scope.</param> /// <returns>The parameter that is used to create a new child context.</returns>
/// <param name="arguments">The arguments passed to the tag.</param> public override TagParameter GetChildContextParameter()
/// <returns>The scope to use when building the inner text of the tag.</returns> {
public override IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments) return context;
{ }
object context = arguments[contextParameter];
yield return new NestedContext() { KeyScope = scope.CreateChildScope(context), Writer = writer }; /// <summary>
} /// Gets the context to use when building the inner text of the tag.
} /// </summary>
} /// <param name="writer">The text writer passed</param>
/// <param name="scope">The current scope.</param>
/// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The scope to use when building the inner text of the tag.</returns>
public override IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments)
{
object context = arguments[contextParameter];
yield return new NestedContext() { KeyScope = scope.CreateChildScope(context), Writer = writer };
}
}
}

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">