diff --git a/mustache-sharp/CompoundGenerator.cs b/mustache-sharp/CompoundGenerator.cs index d5ba34c..7614ceb 100644 --- a/mustache-sharp/CompoundGenerator.cs +++ b/mustache-sharp/CompoundGenerator.cs @@ -71,7 +71,11 @@ namespace mustache } else { - generators = new List() { _subGenerator }; + generators = new List(); + if (_subGenerator != null) + { + generators.Add(_subGenerator); + } } foreach (KeyScope childScope in scopes) { diff --git a/mustache-sharp/FormatCompiler.cs b/mustache-sharp/FormatCompiler.cs index 842e39f..73154ec 100644 --- a/mustache-sharp/FormatCompiler.cs +++ b/mustache-sharp/FormatCompiler.cs @@ -49,7 +49,8 @@ namespace mustache public Generator Compile(string format) { CompoundGenerator generator = new CompoundGenerator(_master, new ArgumentCollection()); - int formatIndex = buildCompoundGenerator(_master, _tagScope, generator, format, 0); + Trimmer trimmer = new Trimmer(); + int formatIndex = buildCompoundGenerator(_master, _tagScope, generator, trimmer, format, 0); string trailing = format.Substring(formatIndex); StaticGenerator staticGenerator = new StaticGenerator(trailing); generator.AddGenerator(staticGenerator); @@ -60,7 +61,7 @@ namespace mustache { foreach (TagDefinition childTag in definition.ChildTags) { - scope.AddTag(childTag); + scope.TryAddTag(childTag); } } @@ -121,7 +122,8 @@ namespace mustache private static int buildCompoundGenerator( TagDefinition tagDefinition, TagScope scope, - CompoundGenerator generator, + CompoundGenerator generator, + Trimmer trimmer, string format, int formatIndex) { while (true) @@ -139,11 +141,10 @@ namespace mustache } string leading = format.Substring(formatIndex, match.Index - formatIndex); - StaticGenerator staticGenerator = new StaticGenerator(leading); - generator.AddGenerator(staticGenerator); if (match.Groups["key"].Success) { + trimmer.AddStaticGenerator(generator, true, leading); formatIndex = match.Index + match.Length; string key = match.Groups["key"].Value; string alignment = match.Groups["alignment"].Value; @@ -161,13 +162,14 @@ namespace mustache string message = String.Format(Resources.UnknownTag, tagName); throw new FormatException(message); } + trimmer.AddStaticGeneratorBeforeTag(generator, true, leading); if (nextDefinition.HasBody) { ArgumentCollection arguments = getArguments(nextDefinition, match); CompoundGenerator compoundGenerator = new CompoundGenerator(nextDefinition, arguments); TagScope nextScope = new TagScope(scope); registerTags(nextDefinition, nextScope); - formatIndex = buildCompoundGenerator(nextDefinition, nextScope, compoundGenerator, format, formatIndex); + formatIndex = buildCompoundGenerator(nextDefinition, nextScope, compoundGenerator, trimmer, format, formatIndex); generator.AddGenerator(nextDefinition, compoundGenerator); } else @@ -181,6 +183,8 @@ namespace mustache else if (match.Groups["close"].Success) { string tagName = match.Groups["name"].Value; + TagDefinition nextDefinition = scope.Find(tagName); + trimmer.AddStaticGeneratorBeforeTag(generator, false, leading); formatIndex = match.Index; if (tagName == tagDefinition.Name) { @@ -190,6 +194,7 @@ namespace mustache } else if (match.Groups["comment"].Success) { + trimmer.AddStaticGenerator(generator, false, leading); formatIndex = match.Index + match.Length; } } diff --git a/mustache-sharp/TagScope.cs b/mustache-sharp/TagScope.cs index 0a0ddd2..40684b9 100644 --- a/mustache-sharp/TagScope.cs +++ b/mustache-sharp/TagScope.cs @@ -45,6 +45,18 @@ namespace mustache _tagLookup.Add(definition.Name, definition); } + /// + /// Trys to register the tag in the current scope. + /// + /// The tag to add to the current scope. + public void TryAddTag(TagDefinition definition) + { + if (Find(definition.Name) == null) + { + _tagLookup.Add(definition.Name, definition); + } + } + /// /// Finds the tag definition with the given name. /// diff --git a/mustache-sharp/Trimmer.cs b/mustache-sharp/Trimmer.cs new file mode 100644 index 0000000..0f078ec --- /dev/null +++ b/mustache-sharp/Trimmer.cs @@ -0,0 +1,90 @@ +using System; + +namespace mustache +{ + /// + /// Removes unnecessary whitespace from static text. + /// + internal sealed class Trimmer + { + private bool hasHeader; + private bool hasFooter; + private bool hasTag; + private bool canTrim; + + /// + /// Initializes a new instance of a Trimmer. + /// + public Trimmer() + { + hasTag = false; + canTrim = true; + } + + /// + /// Processes the given text, creating a StaticGenerator and adding it to the current compound generator. + /// + /// The compound generator to add the static generator to. + /// Gets whether we're encountered the header tag. + /// The static text to trim. + public void AddStaticGeneratorBeforeTag(CompoundGenerator generator, bool isHeader, string value) + { + string trimmed = processLines(value); + hasHeader |= isHeader; + hasFooter |= hasHeader && !isHeader; + addStaticGenerator(generator, trimmed); + } + + /// + /// Processes the given text, creating a StaticGenerator and adding it to the current compound generator. + /// + /// The compound generator to add the static generator to. + /// Specifies whether the tag results in output. + /// The static text to trim. + public void AddStaticGenerator(CompoundGenerator generator, bool isOutput, string value) + { + string trimmed = processLines(value); + canTrim &= !isOutput; + addStaticGenerator(generator, trimmed); + } + + private string processLines(string value) + { + string trimmed = value; + int newline = value.IndexOf(Environment.NewLine); + if (newline == -1) + { + canTrim &= String.IsNullOrWhiteSpace(value); + } + else + { + // finish processing the previous line + if (canTrim && hasTag && (!hasHeader || !hasFooter)) + { + string lineEnd = trimmed.Substring(0, newline); + if (String.IsNullOrWhiteSpace(lineEnd)) + { + trimmed = trimmed.Substring(newline + Environment.NewLine.Length); + } + } + // start processing the next line + hasTag = false; + hasHeader = false; + hasFooter = false; + int lastNewline = value.LastIndexOf(Environment.NewLine); + string lineStart = value.Substring(lastNewline + Environment.NewLine.Length); + canTrim = String.IsNullOrWhiteSpace(lineStart); + } + return trimmed; + } + + private static void addStaticGenerator(CompoundGenerator generator, string trimmed) + { + if (trimmed.Length > 0) + { + StaticGenerator leading = new StaticGenerator(trimmed); + generator.AddGenerator(leading); + } + } + } +} \ No newline at end of file diff --git a/mustache-sharp/mustache-sharp.csproj b/mustache-sharp/mustache-sharp.csproj index 5dea571..bc30af7 100644 --- a/mustache-sharp/mustache-sharp.csproj +++ b/mustache-sharp/mustache-sharp.csproj @@ -60,6 +60,7 @@ +