diff --git a/mustache-sharp.test/FormatCompilerTester.cs b/mustache-sharp.test/FormatCompilerTester.cs
index 92d1085..71aaf22 100644
--- a/mustache-sharp.test/FormatCompilerTester.cs
+++ b/mustache-sharp.test/FormatCompilerTester.cs
@@ -1,8 +1,8 @@
using System;
-using System.Globalization;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace mustache.test
{
@@ -327,6 +327,25 @@ Content";
Assert.AreEqual(expected, result, "The wrong text was generated.");
}
+ ///
+ /// We can track all of the keys that appear in a template by
+ /// registering with the PlaceholderFound event.
+ ///
+ [TestMethod]
+ public void TestCompile_FindsKeys_RecordsKeys()
+ {
+ FormatCompiler compiler = new FormatCompiler();
+ HashSet keys = new HashSet();
+ compiler.PlaceholderFound += (o, e) =>
+ {
+ keys.Add(e.Key);
+ };
+ compiler.Compile(@"{{FirstName}} {{LastName}}");
+ string[] expected = new string[] { "FirstName", "LastName" };
+ string[] actual = keys.OrderBy(s => s).ToArray();
+ CollectionAssert.AreEqual(expected, actual, "Not all placeholders were found.");
+ }
+
#endregion
#region Comment
diff --git a/mustache-sharp.test/Properties/AssemblyInfo.cs b/mustache-sharp.test/Properties/AssemblyInfo.cs
index a321787..b5286ae 100644
--- a/mustache-sharp.test/Properties/AssemblyInfo.cs
+++ b/mustache-sharp.test/Properties/AssemblyInfo.cs
@@ -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.5.0")]
-[assembly: AssemblyFileVersion("0.0.5.0")]
+[assembly: AssemblyVersion("0.0.6.0")]
+[assembly: AssemblyFileVersion("0.0.6.0")]
diff --git a/mustache-sharp/FormatCompiler.cs b/mustache-sharp/FormatCompiler.cs
index 9f22b44..2f483e1 100644
--- a/mustache-sharp/FormatCompiler.cs
+++ b/mustache-sharp/FormatCompiler.cs
@@ -1,279 +1,289 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-using mustache.Properties;
-
-namespace mustache
-{
- ///
- /// Parses a format string and returns a text generator.
- ///
- public sealed class FormatCompiler
- {
- private readonly Dictionary _tagLookup;
- private readonly Dictionary _regexLookup;
- private readonly MasterTagDefinition _masterDefinition;
-
- ///
- /// Initializes a new instance of a FormatCompiler.
- ///
- public FormatCompiler()
- {
- _tagLookup = new Dictionary();
- _regexLookup = new Dictionary();
- _masterDefinition = new MasterTagDefinition();
-
- IfTagDefinition ifDefinition = new IfTagDefinition();
- _tagLookup.Add(ifDefinition.Name, ifDefinition);
- ElifTagDefinition elifDefinition = new ElifTagDefinition();
- _tagLookup.Add(elifDefinition.Name, elifDefinition);
- ElseTagDefinition elseDefinition = new ElseTagDefinition();
- _tagLookup.Add(elseDefinition.Name, elseDefinition);
- EachTagDefinition eachDefinition = new EachTagDefinition();
- _tagLookup.Add(eachDefinition.Name, eachDefinition);
- WithTagDefinition withDefinition = new WithTagDefinition();
- _tagLookup.Add(withDefinition.Name, withDefinition);
- }
-
- ///
- /// Registers the given tag definition with the parser.
- ///
- /// The tag definition to register.
- /// Specifies whether the tag is immediately in scope.
- public void RegisterTag(TagDefinition definition, bool isTopLevel)
- {
- if (definition == null)
- {
- throw new ArgumentNullException("definition");
- }
- if (_tagLookup.ContainsKey(definition.Name))
- {
- string message = String.Format(Resources.DuplicateTagDefinition, definition.Name);
- throw new ArgumentException(message, "definition");
- }
- _tagLookup.Add(definition.Name, definition);
- }
-
- ///
- /// Builds a text generator based on the given format.
- ///
- /// The format to parse.
- /// The text generator.
- public Generator Compile(string format)
- {
- if (format == null)
- {
- throw new ArgumentNullException("format");
- }
- CompoundGenerator generator = new CompoundGenerator(_masterDefinition, new ArgumentCollection());
- Trimmer trimmer = new Trimmer();
- int formatIndex = buildCompoundGenerator(_masterDefinition, generator, trimmer, format, 0);
- string trailing = format.Substring(formatIndex);
- generator.AddStaticGenerators(trimmer.RecordText(trailing, false, false));
- trimmer.Trim();
- return new Generator(generator);
- }
-
- private Match findNextTag(TagDefinition definition, string format, int formatIndex)
- {
- Regex regex = prepareRegex(definition);
- return regex.Match(format, formatIndex);
- }
-
- private Regex prepareRegex(TagDefinition definition)
- {
- Regex regex;
- if (!_regexLookup.TryGetValue(definition.Name, out regex))
- {
- List matches = new List();
- matches.Add(getKeyRegex());
- matches.Add(getCommentTagRegex());
- foreach (string closingTag in definition.ClosingTags)
- {
- matches.Add(getClosingTagRegex(closingTag));
- }
- foreach (TagDefinition globalDefinition in _tagLookup.Values)
- {
- if (!globalDefinition.IsContextSensitive)
- {
- matches.Add(getTagRegex(globalDefinition));
- }
- }
- foreach (string childTag in definition.ChildTags)
- {
- TagDefinition childDefinition = _tagLookup[childTag];
- matches.Add(getTagRegex(childDefinition));
- }
- matches.Add(getUnknownTagRegex());
- string match = "{{(" + String.Join("|", matches) + ")}}";
- regex = new Regex(match, RegexOptions.Compiled);
- _regexLookup.Add(definition.Name, regex);
- }
- return regex;
- }
-
- private static string getClosingTagRegex(string tagName)
- {
- StringBuilder regexBuilder = new StringBuilder();
- regexBuilder.Append(@"(?(/(?");
- regexBuilder.Append(tagName);
- regexBuilder.Append(@")\s*?))");
- return regexBuilder.ToString();
- }
-
- private static string getCommentTagRegex()
- {
- return @"(?#!.*?)";
- }
-
- private static string getKeyRegex()
- {
- return @"((?" + RegexHelper.CompoundKey + @")(,(?(\+|-)?[\d]+))?(:(?.*?))?)";
- }
-
- private static string getTagRegex(TagDefinition definition)
- {
- StringBuilder regexBuilder = new StringBuilder();
- regexBuilder.Append(@"(?(#(?");
- regexBuilder.Append(definition.Name);
- regexBuilder.Append(@")");
- foreach (TagParameter parameter in definition.Parameters)
- {
- regexBuilder.Append(@"(\s+?");
- regexBuilder.Append(@"(?");
- regexBuilder.Append(RegexHelper.CompoundKey);
- regexBuilder.Append(@"))");
- if (!parameter.IsRequired)
- {
- regexBuilder.Append("?");
- }
- }
- regexBuilder.Append(@"\s*?))");
- return regexBuilder.ToString();
- }
-
- private string getUnknownTagRegex()
- {
- return @"(?(#.*?))";
- }
-
- private int buildCompoundGenerator(
- TagDefinition tagDefinition,
- CompoundGenerator generator,
- Trimmer trimmer,
- string format, int formatIndex)
- {
- while (true)
- {
- Match match = findNextTag(tagDefinition, format, formatIndex);
-
- if (!match.Success)
- {
- if (tagDefinition.ClosingTags.Any())
- {
- string message = String.Format(Resources.MissingClosingTag, tagDefinition.Name);
- throw new FormatException(message);
- }
- break;
- }
-
- string leading = format.Substring(formatIndex, match.Index - formatIndex);
-
- if (match.Groups["key"].Success)
- {
- generator.AddStaticGenerators(trimmer.RecordText(leading, true, true));
- formatIndex = match.Index + match.Length;
- string key = match.Groups["key"].Value;
- string alignment = match.Groups["alignment"].Value;
- string formatting = match.Groups["format"].Value;
- KeyGenerator keyGenerator = new KeyGenerator(key, alignment, formatting);
- generator.AddGenerator(keyGenerator);
- }
- else if (match.Groups["open"].Success)
- {
- formatIndex = match.Index + match.Length;
- string tagName = match.Groups["name"].Value;
- TagDefinition nextDefinition = _tagLookup[tagName];
- if (nextDefinition == null)
- {
- string message = String.Format(Resources.UnknownTag, tagName);
- throw new FormatException(message);
- }
- if (nextDefinition.HasContent)
- {
- 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);
- generator.AddGenerator(nextDefinition, compoundGenerator);
- }
- else
- {
- generator.AddStaticGenerators(trimmer.RecordText(leading, true, true));
- Match nextMatch = findNextTag(nextDefinition, format, formatIndex);
- ArgumentCollection arguments = getArguments(nextDefinition, nextMatch);
- InlineGenerator inlineGenerator = new InlineGenerator(nextDefinition, arguments);
- generator.AddGenerator(inlineGenerator);
- }
- }
- else if (match.Groups["close"].Success)
- {
- generator.AddStaticGenerators(trimmer.RecordText(leading, true, false));
- string tagName = match.Groups["name"].Value;
- TagDefinition nextDefinition = _tagLookup[tagName];
- formatIndex = match.Index;
- if (tagName == tagDefinition.Name)
- {
- formatIndex += match.Length;
- }
- break;
- }
- else if (match.Groups["comment"].Success)
- {
- generator.AddStaticGenerators(trimmer.RecordText(leading, true, false));
- formatIndex = match.Index + match.Length;
- }
- else if (match.Groups["unknown"].Success)
- {
- throw new FormatException(Resources.UnknownTag);
- }
- }
- return formatIndex;
- }
-
- private static ArgumentCollection getArguments(TagDefinition definition, Match match)
- {
- ArgumentCollection collection = new ArgumentCollection();
- List captures = match.Groups["argument"].Captures.Cast().ToList();
- List parameters = definition.Parameters.ToList();
- if (captures.Count > parameters.Count)
- {
- string message = String.Format(Resources.WrongNumberOfArguments, definition.Name);
- throw new FormatException(message);
- }
- if (captures.Count < parameters.Count)
- {
- captures.AddRange(Enumerable.Repeat((Capture)null, parameters.Count - captures.Count));
- }
- foreach (var pair in parameters.Zip(captures, (p, c) => new { Capture = c, Parameter = p }))
- {
- if (pair.Capture == null)
- {
- if (pair.Parameter.IsRequired)
- {
- string message = String.Format(Resources.WrongNumberOfArguments, definition.Name);
- throw new FormatException(message);
- }
- collection.AddArgument(pair.Parameter, null);
- }
- else
- {
- collection.AddArgument(pair.Parameter, pair.Capture.Value);
- }
- }
- return collection;
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using mustache.Properties;
+
+namespace mustache
+{
+ ///
+ /// Parses a format string and returns a text generator.
+ ///
+ public sealed class FormatCompiler
+ {
+ private readonly Dictionary _tagLookup;
+ private readonly Dictionary _regexLookup;
+ private readonly MasterTagDefinition _masterDefinition;
+
+ ///
+ /// Initializes a new instance of a FormatCompiler.
+ ///
+ public FormatCompiler()
+ {
+ _tagLookup = new Dictionary();
+ _regexLookup = new Dictionary();
+ _masterDefinition = new MasterTagDefinition();
+
+ IfTagDefinition ifDefinition = new IfTagDefinition();
+ _tagLookup.Add(ifDefinition.Name, ifDefinition);
+ ElifTagDefinition elifDefinition = new ElifTagDefinition();
+ _tagLookup.Add(elifDefinition.Name, elifDefinition);
+ ElseTagDefinition elseDefinition = new ElseTagDefinition();
+ _tagLookup.Add(elseDefinition.Name, elseDefinition);
+ EachTagDefinition eachDefinition = new EachTagDefinition();
+ _tagLookup.Add(eachDefinition.Name, eachDefinition);
+ WithTagDefinition withDefinition = new WithTagDefinition();
+ _tagLookup.Add(withDefinition.Name, withDefinition);
+ }
+
+ ///
+ /// Occurs when a placeholder is found in the template.
+ ///
+ public event EventHandler PlaceholderFound;
+
+ ///
+ /// Registers the given tag definition with the parser.
+ ///
+ /// The tag definition to register.
+ /// Specifies whether the tag is immediately in scope.
+ public void RegisterTag(TagDefinition definition, bool isTopLevel)
+ {
+ if (definition == null)
+ {
+ throw new ArgumentNullException("definition");
+ }
+ if (_tagLookup.ContainsKey(definition.Name))
+ {
+ string message = String.Format(Resources.DuplicateTagDefinition, definition.Name);
+ throw new ArgumentException(message, "definition");
+ }
+ _tagLookup.Add(definition.Name, definition);
+ }
+
+ ///
+ /// Builds a text generator based on the given format.
+ ///
+ /// The format to parse.
+ /// The text generator.
+ public Generator Compile(string format)
+ {
+ if (format == null)
+ {
+ throw new ArgumentNullException("format");
+ }
+ CompoundGenerator generator = new CompoundGenerator(_masterDefinition, new ArgumentCollection());
+ Trimmer trimmer = new Trimmer();
+ int formatIndex = buildCompoundGenerator(_masterDefinition, generator, trimmer, format, 0);
+ string trailing = format.Substring(formatIndex);
+ generator.AddStaticGenerators(trimmer.RecordText(trailing, false, false));
+ trimmer.Trim();
+ return new Generator(generator);
+ }
+
+ private Match findNextTag(TagDefinition definition, string format, int formatIndex)
+ {
+ Regex regex = prepareRegex(definition);
+ return regex.Match(format, formatIndex);
+ }
+
+ private Regex prepareRegex(TagDefinition definition)
+ {
+ Regex regex;
+ if (!_regexLookup.TryGetValue(definition.Name, out regex))
+ {
+ List matches = new List();
+ matches.Add(getKeyRegex());
+ matches.Add(getCommentTagRegex());
+ foreach (string closingTag in definition.ClosingTags)
+ {
+ matches.Add(getClosingTagRegex(closingTag));
+ }
+ foreach (TagDefinition globalDefinition in _tagLookup.Values)
+ {
+ if (!globalDefinition.IsContextSensitive)
+ {
+ matches.Add(getTagRegex(globalDefinition));
+ }
+ }
+ foreach (string childTag in definition.ChildTags)
+ {
+ TagDefinition childDefinition = _tagLookup[childTag];
+ matches.Add(getTagRegex(childDefinition));
+ }
+ matches.Add(getUnknownTagRegex());
+ string match = "{{(" + String.Join("|", matches) + ")}}";
+ regex = new Regex(match, RegexOptions.Compiled);
+ _regexLookup.Add(definition.Name, regex);
+ }
+ return regex;
+ }
+
+ private static string getClosingTagRegex(string tagName)
+ {
+ StringBuilder regexBuilder = new StringBuilder();
+ regexBuilder.Append(@"(?(/(?");
+ regexBuilder.Append(tagName);
+ regexBuilder.Append(@")\s*?))");
+ return regexBuilder.ToString();
+ }
+
+ private static string getCommentTagRegex()
+ {
+ return @"(?#!.*?)";
+ }
+
+ private static string getKeyRegex()
+ {
+ return @"((?" + RegexHelper.CompoundKey + @")(,(?(\+|-)?[\d]+))?(:(?.*?))?)";
+ }
+
+ private static string getTagRegex(TagDefinition definition)
+ {
+ StringBuilder regexBuilder = new StringBuilder();
+ regexBuilder.Append(@"(?(#(?");
+ regexBuilder.Append(definition.Name);
+ regexBuilder.Append(@")");
+ foreach (TagParameter parameter in definition.Parameters)
+ {
+ regexBuilder.Append(@"(\s+?");
+ regexBuilder.Append(@"(?");
+ regexBuilder.Append(RegexHelper.CompoundKey);
+ regexBuilder.Append(@"))");
+ if (!parameter.IsRequired)
+ {
+ regexBuilder.Append("?");
+ }
+ }
+ regexBuilder.Append(@"\s*?))");
+ return regexBuilder.ToString();
+ }
+
+ private string getUnknownTagRegex()
+ {
+ return @"(?(#.*?))";
+ }
+
+ private int buildCompoundGenerator(
+ TagDefinition tagDefinition,
+ CompoundGenerator generator,
+ Trimmer trimmer,
+ string format, int formatIndex)
+ {
+ while (true)
+ {
+ Match match = findNextTag(tagDefinition, format, formatIndex);
+
+ if (!match.Success)
+ {
+ if (tagDefinition.ClosingTags.Any())
+ {
+ string message = String.Format(Resources.MissingClosingTag, tagDefinition.Name);
+ throw new FormatException(message);
+ }
+ break;
+ }
+
+ string leading = format.Substring(formatIndex, match.Index - formatIndex);
+
+ if (match.Groups["key"].Success)
+ {
+ generator.AddStaticGenerators(trimmer.RecordText(leading, true, true));
+ formatIndex = match.Index + match.Length;
+ 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);
+ if (PlaceholderFound != null)
+ {
+ PlaceholderFound(this, args);
+ }
+ KeyGenerator keyGenerator = new KeyGenerator(args.Key, args.Alignment, args.Formatting);
+ generator.AddGenerator(keyGenerator);
+ }
+ else if (match.Groups["open"].Success)
+ {
+ formatIndex = match.Index + match.Length;
+ string tagName = match.Groups["name"].Value;
+ TagDefinition nextDefinition = _tagLookup[tagName];
+ if (nextDefinition == null)
+ {
+ string message = String.Format(Resources.UnknownTag, tagName);
+ throw new FormatException(message);
+ }
+ if (nextDefinition.HasContent)
+ {
+ 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);
+ generator.AddGenerator(nextDefinition, compoundGenerator);
+ }
+ else
+ {
+ generator.AddStaticGenerators(trimmer.RecordText(leading, true, true));
+ Match nextMatch = findNextTag(nextDefinition, format, formatIndex);
+ ArgumentCollection arguments = getArguments(nextDefinition, nextMatch);
+ InlineGenerator inlineGenerator = new InlineGenerator(nextDefinition, arguments);
+ generator.AddGenerator(inlineGenerator);
+ }
+ }
+ else if (match.Groups["close"].Success)
+ {
+ generator.AddStaticGenerators(trimmer.RecordText(leading, true, false));
+ string tagName = match.Groups["name"].Value;
+ TagDefinition nextDefinition = _tagLookup[tagName];
+ formatIndex = match.Index;
+ if (tagName == tagDefinition.Name)
+ {
+ formatIndex += match.Length;
+ }
+ break;
+ }
+ else if (match.Groups["comment"].Success)
+ {
+ generator.AddStaticGenerators(trimmer.RecordText(leading, true, false));
+ formatIndex = match.Index + match.Length;
+ }
+ else if (match.Groups["unknown"].Success)
+ {
+ throw new FormatException(Resources.UnknownTag);
+ }
+ }
+ return formatIndex;
+ }
+
+ private static ArgumentCollection getArguments(TagDefinition definition, Match match)
+ {
+ ArgumentCollection collection = new ArgumentCollection();
+ List captures = match.Groups["argument"].Captures.Cast().ToList();
+ List parameters = definition.Parameters.ToList();
+ if (captures.Count > parameters.Count)
+ {
+ string message = String.Format(Resources.WrongNumberOfArguments, definition.Name);
+ throw new FormatException(message);
+ }
+ if (captures.Count < parameters.Count)
+ {
+ captures.AddRange(Enumerable.Repeat((Capture)null, parameters.Count - captures.Count));
+ }
+ foreach (var pair in parameters.Zip(captures, (p, c) => new { Capture = c, Parameter = p }))
+ {
+ if (pair.Capture == null)
+ {
+ if (pair.Parameter.IsRequired)
+ {
+ string message = String.Format(Resources.WrongNumberOfArguments, definition.Name);
+ throw new FormatException(message);
+ }
+ collection.AddArgument(pair.Parameter, null);
+ }
+ else
+ {
+ collection.AddArgument(pair.Parameter, pair.Capture.Value);
+ }
+ }
+ return collection;
+ }
+ }
+}
diff --git a/mustache-sharp/PlaceholderFoundEventArgs.cs b/mustache-sharp/PlaceholderFoundEventArgs.cs
new file mode 100644
index 0000000..8849994
--- /dev/null
+++ b/mustache-sharp/PlaceholderFoundEventArgs.cs
@@ -0,0 +1,39 @@
+using System;
+using mustache.Properties;
+
+namespace mustache
+{
+ ///
+ /// Holds the information descibing a key that is found in a template.
+ ///
+ public class PlaceholderFoundEventArgs : EventArgs
+ {
+ ///
+ /// Initializes a new instance of a PlaceholderFoundEventArgs.
+ ///
+ /// The key that was found.
+ /// The alignment that will be applied to the substitute value.
+ /// The formatting that will be applied to the substitute value.
+ internal PlaceholderFoundEventArgs(string key, string alignment, string formatting)
+ {
+ Key = key;
+ Alignment = alignment;
+ Formatting = formatting;
+ }
+
+ ///
+ /// Gets or sets the key that was found.
+ ///
+ public string Key { get; set; }
+
+ ///
+ /// Gets or sets the alignment that will be applied to the substitute value.
+ ///
+ public string Alignment { get; set; }
+
+ ///
+ /// Gets or sets the formatting that will be applied to the substitute value.
+ ///
+ public string Formatting { get; set; }
+ }
+}
diff --git a/mustache-sharp/Properties/AssemblyInfo.cs b/mustache-sharp/Properties/AssemblyInfo.cs
index f1d3dae..d72b0f3 100644
--- a/mustache-sharp/Properties/AssemblyInfo.cs
+++ b/mustache-sharp/Properties/AssemblyInfo.cs
@@ -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.5.0")]
-[assembly: AssemblyFileVersion("0.0.4.0")]
+[assembly: AssemblyVersion("0.0.6.0")]
+[assembly: AssemblyFileVersion("0.0.6.0")]
[assembly: InternalsVisibleTo("mustache-sharp.test")]
\ No newline at end of file
diff --git a/mustache-sharp/RegexHelper.cs b/mustache-sharp/RegexHelper.cs
index 38548c6..ad2b715 100644
--- a/mustache-sharp/RegexHelper.cs
+++ b/mustache-sharp/RegexHelper.cs
@@ -1,25 +1,29 @@
-using System;
-using System.Text.RegularExpressions;
-
-namespace mustache
-{
- ///
- /// Provides utility methods that require regular expressions.
- ///
- public static class RegexHelper
- {
- private const string Key = @"[_\w][_\w\d]*";
- internal const string CompoundKey = Key + @"(\." + Key + ")*";
-
- ///
- /// Determines whether the given name is a legal identifier.
- ///
- /// The name to check.
- /// True if the name is a legal identifier; otherwise, false.
- public static bool IsValidIdentifier(string name)
- {
- Regex regex = new Regex("^" + Key + "$");
- return regex.IsMatch(name);
- }
- }
-}
+using System;
+using System.Text.RegularExpressions;
+
+namespace mustache
+{
+ ///
+ /// Provides utility methods that require regular expressions.
+ ///
+ public static class RegexHelper
+ {
+ private const string Key = @"[_\w][_\w\d]*";
+ internal const string CompoundKey = Key + @"(\." + Key + ")*";
+
+ ///
+ /// Determines whether the given name is a legal identifier.
+ ///
+ /// The name to check.
+ /// True if the name is a legal identifier; otherwise, false.
+ public static bool IsValidIdentifier(string name)
+ {
+ if (name == null)
+ {
+ return false;
+ }
+ Regex regex = new Regex("^" + Key + "$");
+ return regex.IsMatch(name);
+ }
+ }
+}
diff --git a/mustache-sharp/mustache-sharp.csproj b/mustache-sharp/mustache-sharp.csproj
index d50cf59..97b2235 100644
--- a/mustache-sharp/mustache-sharp.csproj
+++ b/mustache-sharp/mustache-sharp.csproj
@@ -47,6 +47,7 @@
+