Exemplo n.º 1
0
        /**
        * Breaks up the given `template` string into a tree of tokens. If the `tags`
        * argument is given here it must be an array with two string values: the
        * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of
        * course, the default is to use mustaches (i.e. mustache.tags).
        *
        * A token is an array with at least 4 elements. The first element is the
        * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag
        * did not contain a symbol (i.e. {{myValue}}) this element is "Name". For
        * all text that appears outside a symbol this element is "text".
        *
        * The second element of a token is its "Value". For mustache tags this is
        * whatever else was inside the tag besides the opening symbol. For text tokens
        * this is the text itself.
        *
        * The third and fourth elements of the token are the Start and End indices,
        * respectively, of the token in the original template.
        *
        * Tokens that are the root node of a subtree contain two more elements: 1) an
        * array of tokens in the subtree and 2) the index in the original template at
        * which the closing tag for that section begins.
        */
        private static List<Token> ParseTemplate(string template, Tags tags = null)
        {
            if (!template.Any())
                return new List<Token>();

            var sections = new Stack<Token>(); // Stack to hold section tokens
            var tokens = new List<Token>(); // Buffer to hold the tokens
            var spaces = new Stack<int>(); // Indices of whitespace tokens on the current line
            var hasTag = false; // Is there a {{tag}} on the current line?
            var nonSpace = false; // Is there a non-space char on the current line?

            // Strips all whitespace tokens array for the current line
            // if there was a {{#tag}} on it and otherwise only space.
            Action stripSpace = () =>
            {
                if (hasTag && !nonSpace)
                {
                    while (spaces.Any())
                        tokens.RemoveAt(spaces.Pop());
                }
                else
                {
                    spaces.Clear();
                }

                hasTag = false;
                nonSpace = false;
            };

            // TODO: this `= null` is to avoid "Use of unassigned local variable" C# compiler error.
            Regex openingTagRe = null;
            Regex closingTagRe = null;
            Regex closingCurlyRe = null;
            Action<Tags> compileTags = delegate(Tags tagsToCompile)
            {
                openingTagRe = new Regex(Regex.Escape(tagsToCompile.Opener) + "\\s*");
                closingTagRe = new Regex("\\s*" + Regex.Escape(tagsToCompile.Closer));
                closingCurlyRe = new Regex("\\s*" + Regex.Escape('}' + tagsToCompile.Closer));
            };

            if (tags == null)
                compileTags(MustacheTags);
            else
                compileTags(tags);

            //var Start, Type, Value, chr, token, openSection;
            var scanner = new Scanner(template);
            Token openSection = null;
            while (!scanner.Eos())
            {
                var start = scanner._pos;
                var value = scanner.ScanUntil(openingTagRe);
                var valueLength = value.Length;
                if (valueLength > 0)
                {
                    for (var i = 0; i < valueLength; ++i)
                    {
                        string chr = "" + value[i];

                        if (IsWhitespace(chr))
                        {
                            spaces.Push(tokens.Count);
                        }
                        else
                        {
                            nonSpace = true;
                        }

                        tokens.Add(new Token {Type = "text", Value = chr, Start = start, End = start + 1});
                        start += 1;

                        // Check for whitespace on the current line.
                        if (chr == "\n")
                            stripSpace();
                    }
                }

                // Match the opening tag.
                if (!scanner.Scan(openingTagRe).Any())
                    break;

                hasTag = true;

                // Get the tag Type.
                var scanTag = scanner.Scan(_tagRe);
                string type;
                if (!scanTag.Any())
                    type = "Name";
                else
                    type = scanTag;

                scanner.Scan(_whiteRe);

                // Get the tag Value.
                switch (type)
                {
                    case "=":
                        value = scanner.ScanUntil(_equalsRe);
                        scanner.Scan(_equalsRe);
                        scanner.ScanUntil(closingTagRe);
                        break;
                    case "{":
                        value = scanner.ScanUntil(closingCurlyRe);
                        scanner.Scan(_curlyRe);
                        scanner.ScanUntil(closingTagRe);
                        type = "&";
                        break;
                    default:
                        value = scanner.ScanUntil(closingTagRe);
                        break;
                }

                // Match the closing tag.
                if (!scanner.Scan(closingTagRe).Any())
                    throw new Exception("Unclosed tag at " + scanner._pos);
                var arr = value.Split('|');
                string format = null;
                if (arr.Length == 2)
                {
                    value = arr[0];
                    format = arr[1];
                }

                var token = new Token {Type = type, Value = value, Format = format, Start = start, End = scanner._pos};
                tokens.Add(token);
                switch (type)
                {
                    case "#":
                    case "^":
                        sections.Push(token);
                        break;
                    case "/":
                        // Check section nesting.
                        openSection = sections.Pop();

                        if (openSection == null)
                            throw new Exception("Unopened section \"" + value + "\" at " + start);

                        if (openSection.Value != value)
                            throw new Exception("Unclosed section \"" + openSection.Value + "\" at " + start);
                        break;
                    case "Name":
                    case "{":
                    case "&":
                        nonSpace = true;
                        break;
                    case "=":
                        // Set the tags for the next time around.
                        var newTags = _spaceRe.Split(value, 2);
                        compileTags(new Tags {Opener = newTags[0], Closer = newTags[1]});
                        break;
                }
            }

            // Make sure there are no open sections when we're done.
            if (sections.Any())
            {
                openSection = sections.Pop();
                throw new Exception("Unclosed section \"" + openSection.Value + "\" at " + scanner._pos);
            }

            return NestTokens(SquashTokens(tokens));
        }