using System; using System.Collections.Generic; using System.Linq; namespace Mustache { /// /// Removes unnecessary lines from the final output. /// internal sealed class Trimmer { private readonly LinkedList _lines; private LinkedListNode _currentLine; /// /// Initializes a new instance of a Trimmer. /// public Trimmer() { _lines = new LinkedList(); _currentLine = _lines.AddLast(new LineDetails()); } /// /// Updates the state of the trimmer, indicating that the given text was encountered before an inline tag. /// /// The text at the end of the format string. /// The generator created for the inline tag. /// A static generator containing the passed text. public IEnumerable RecordText(string value, bool isTag, bool isOutput) { int newLineIndex = value.IndexOf(Environment.NewLine); if (newLineIndex == -1) { StaticGenerator generator = new StaticGenerator() { Value = value }; _currentLine.Value.Generators.Add(generator); _currentLine.Value.HasTag |= isTag; _currentLine.Value.HasOutput |= !String.IsNullOrWhiteSpace(value); yield return generator; } else { string[] lines = value.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); // get the trailing generator string trailing = lines[0]; StaticGenerator trailingGenerator = new StaticGenerator() { Value = trailing }; _currentLine.Value.Generators.Add(trailingGenerator); _currentLine.Value.HasOutput |= !String.IsNullOrWhiteSpace(trailing); yield return trailingGenerator; // get the middle generators for (int lineIndex = 1; lineIndex < lines.Length - 1; ++lineIndex) { string middle = lines[lineIndex]; StaticGenerator middleGenerator = new StaticGenerator() { Value = middle }; LineDetails middleDetails = new LineDetails() { HasTag = false }; _currentLine = _lines.AddLast(middleDetails); _currentLine.Value.Generators.Add(middleGenerator); _currentLine.Value.HasOutput = true; yield return middleGenerator; } // get the leading generator string leading = lines[lines.Length - 1]; StaticGenerator leadingGenerator = new StaticGenerator() { Value = leading }; LineDetails details = new LineDetails() { HasTag = isTag }; _currentLine = _lines.AddLast(details); _currentLine.Value.Generators.Add(leadingGenerator); _currentLine.Value.HasOutput = !String.IsNullOrWhiteSpace(leading); yield return leadingGenerator; } if (isOutput) { _currentLine.Value.HasOutput = true; } } public void Trim() { removeBlankLines(); separateLines(); removeEmptyGenerators(); } private void removeBlankLines() { LinkedListNode current = _lines.First; while (current != null) { LineDetails details = current.Value; LinkedListNode temp = current; current = current.Next; if (details.HasTag && !details.HasOutput) { foreach (StaticGenerator generator in temp.Value.Generators) { generator.Prune(); } temp.List.Remove(temp); } } } private void separateLines() { LinkedListNode current = _lines.First; while (current != _lines.Last) { List generators = current.Value.Generators; StaticGenerator lastGenerator = generators[generators.Count - 1]; lastGenerator.Value += Environment.NewLine; current = current.Next; } } private void removeEmptyGenerators() { LinkedListNode current = _lines.First; while (current != null) { foreach (StaticGenerator generator in current.Value.Generators) { if (generator.Value.Length == 0) { generator.Prune(); } } current = current.Next; } } private sealed class LineDetails { public LineDetails() { Generators = new List(); } public bool HasTag { get; set; } public List Generators { get; set; } public bool HasOutput { get; set; } } } }