Beispiel #1
0
        private async ValueTask <bool> FoldClosingTagsAsync(List <Node> nodes)
        {
            for (var j = nodes.Count - 1; j >= 0; j--)
            {
                if (nodes[j] is Shortcode end && end.Style == ShortcodeStyle.Close)
                {
                    // Found an end tag
                    for (var i = 0; i < j; i++)
                    {
                        if (nodes[i] is Shortcode start && start.Style == ShortcodeStyle.Open && String.Equals(start.Identifier, end.Identifier, StringComparison.OrdinalIgnoreCase))
                        {
                            var text = "";

                            // Don't instantiate a builder if there is no inner node
                            if (i < j - 1)
                            {
                                using (var sb = StringBuilderPool.GetInstance())
                                {
                                    for (var k = i + 1; k < j; k++)
                                    {
                                        sb.Builder.Append(await RenderAsync(nodes[k]));
                                    }

                                    text = sb.ToString();
                                }
                            }

                            nodes.RemoveRange(i + 1, j - i);

                            start.Content = text;

                            return(true);
                        }
                    }
                }
            }

            return(false);
        }
        private async ValueTask <string> FoldClosingTagsAsync(string input, List <Node> nodes, int index, int length, Context context)
        {
            // This method should not be called when nodes has a single RawText element.
            // It's implementation assumes at least two nodes are provided.

            using var sb = StringBuilderPool.GetInstance();

            // The index of the next shortcode opening node
            var cursor = index;

            // Process the list
            while (cursor <= index + length - 1)
            {
                Shortcode start = null;
                var       head  = 0;
                var       tail  = 0;

                // Find the next opening tag
                while (cursor <= index + length - 1 && start == null)
                {
                    var node = nodes[cursor];

                    if (node is Shortcode shortCode)
                    {
                        if (shortCode.Style == ShortcodeStyle.Open)
                        {
                            head  = cursor;
                            start = shortCode;
                        }
                        else
                        {
                            // These closing tags need to be rendered
                            sb.Builder.Append(input, shortCode.SourceIndex, shortCode.SourceLength + 1);
                        }
                    }
                    else
                    {
                        var text = node as RawText;

                        sb.Builder.Append(text.Buffer, text.Offset, text.Count);
                    }

                    cursor += 1;
                }

                // if start is null, then there is nothing to fold
                if (start == null)
                {
                    return(sb.Builder.ToString());
                }

                Shortcode end = null;

                var depth = 1;

                // Find a matching closing tag
                while (cursor <= index + length - 1 && end == null)
                {
                    if (nodes[cursor] is Shortcode shortCode)
                    {
                        if (String.Equals(start.Identifier, shortCode.Identifier, StringComparison.OrdinalIgnoreCase))
                        {
                            if (shortCode.Style == ShortcodeStyle.Open)
                            {
                                // We need to count all opening shortcodes matching the start to account for:
                                // [a] [a] [/a] [/a]

                                depth += 1;
                            }
                            else
                            {
                                depth -= 1;

                                if (depth == 0)
                                {
                                    tail = cursor;
                                    end  = shortCode;
                                }
                            }
                        }
                    }

                    cursor += 1;
                }

                // Is it a single tag?
                if (end == null)
                {
                    cursor = head + 1;

                    // If there are more than one open/close brace we don't evaluate the shortcode
                    if (start.OpenBraces > 1 || start.CloseBraces > 1)
                    {
                        // We need to escape the braces if counts match
                        var bracesToSkip = start.OpenBraces == start.CloseBraces ? 1 : 0;

                        sb.Builder.Append('[', start.OpenBraces - bracesToSkip);
                        sb.Builder.Append(input, start.SourceIndex + start.OpenBraces, start.SourceLength - start.CloseBraces - start.OpenBraces + 1);
                        sb.Builder.Append(']', start.CloseBraces - bracesToSkip);
                    }
                    else
                    {
                        await AppendAsync(sb.Builder, input, start, null, context);
                    }
                }
                else
                {
                    // Standard braces are made of 1 brace on each edge
                    var standardBraces = start.OpenBraces == 1 && start.CloseBraces == 1 && end.OpenBraces == 1 && end.CloseBraces == 1;
                    var balancedBraces = start.OpenBraces == end.CloseBraces && start.CloseBraces == end.OpenBraces;

                    if (standardBraces)
                    {
                        // Are the tags adjacent?
                        if (tail - head == 1)
                        {
                            start.Content = "";
                            await AppendAsync(sb.Builder, input, start, end, context);
                        }
                        // Is there a single node between the tags?
                        else if (tail - head == 2)
                        {
                            // Render the inner node (raw or shortcode)
                            var content = nodes[head + 1];

                            // Set it to the start shortcode
                            using (var sbContent = StringBuilderPool.GetInstance())
                            {
                                await AppendAsync(sbContent.Builder, input, content, null, context);

                                start.Content = sbContent.ToString();
                            }

                            // Render the start shortcode
                            await AppendAsync(sb.Builder, input, start, end, context);
                        }
                        // Fold the inner nodes
                        else
                        {
                            start.Content = await FoldClosingTagsAsync(input, nodes, head + 1, tail - head - 1, context);
                            await AppendAsync(sb.Builder, input, start, end, context);
                        }
                    }
                    else
                    {
                        // Balanced braces represent an escape sequence, e.g. [[upper]foo[/upper]] -> [upper]foo[/upper]
                        if (balancedBraces)
                        {
                            var bracesToSkip = start.OpenBraces == end.CloseBraces ? 1 : 0;

                            sb.Builder.Append('[', start.OpenBraces - bracesToSkip);
                            sb.Builder.Append(input, start.SourceIndex + start.OpenBraces, end.SourceIndex + end.SourceLength - end.CloseBraces - start.SourceIndex - start.OpenBraces + 1);
                            sb.Builder.Append(']', end.CloseBraces - bracesToSkip);
                        }
                        // Unbalanced braces only evaluate inner content, e.g. [upper]foo[/upper]]
                        else
                        {
                            // Are the tags adjacent?
                            if (tail - head == 1)
                            {
                                AppendRawNode(sb.Builder, input, start);
                                AppendRawNode(sb.Builder, input, end);
                            }
                            // Is there a single node between the tags?
                            else if (tail - head == 2)
                            {
                                // Render the inner node (raw or shortcode)
                                var content = nodes[head + 1];

                                AppendRawNode(sb.Builder, input, start);
                                await AppendAsync(sb.Builder, input, content, null, context);

                                AppendRawNode(sb.Builder, input, end);
                            }
                            // Fold the inner nodes
                            else
                            {
                                var content = await FoldClosingTagsAsync(input, nodes, head + 1, tail - head - 1, context);

                                AppendRawNode(sb.Builder, input, start);
                                sb.Builder.Append(content);
                                AppendRawNode(sb.Builder, input, end);
                            }
                        }
                    }
                }
            }

            return(sb.Builder.ToString());
        }
Beispiel #3
0
        private async ValueTask <string> FoldClosingTagsAsync(string input, List <Node> nodes, int index, int length, Context context)
        {
            // This method should not be called when nodes has a single RawText element.
            // It's implementation assumes at least two nodes are provided.

            using var sb = StringBuilderPool.GetInstance();

            // The index of the next shortcode opening node
            var cursor = index;

            // Process the list
            while (cursor <= index + length - 1)
            {
                Shortcode start = null;
                var       head  = 0;
                var       tail  = 0;

                // Find the next opening tag
                while (cursor < nodes.Count && start == null)
                {
                    var node = nodes[cursor];

                    if (node is Shortcode shortCode)
                    {
                        if (shortCode.Style == ShortcodeStyle.Open)
                        {
                            head  = cursor;
                            start = shortCode;
                        }
                    }
                    else
                    {
                        var text = node as RawText;

                        sb.Builder.Append(text.Text);
                    }

                    cursor += 1;
                }

                // if start is null, then there is nothing to fold
                if (start == null)
                {
                    return(sb.Builder.ToString());
                }

                Shortcode end = null;

                var depth = 1;

                // Find a matching closing tag
                while (cursor <= index + length - 1 && end == null)
                {
                    if (nodes[cursor] is Shortcode shortCode)
                    {
                        if (String.Equals(start.Identifier, shortCode.Identifier, StringComparison.OrdinalIgnoreCase))
                        {
                            if (shortCode.Style == ShortcodeStyle.Open)
                            {
                                // We need to count all opening shortcodes matching the start to account for:
                                // [a] [a] [/a] [/a]

                                depth += 1;
                            }
                            else
                            {
                                depth -= 1;

                                if (depth == 0)
                                {
                                    tail = cursor;
                                    end  = shortCode;
                                }
                            }
                        }
                    }

                    cursor += 1;
                }

                // Is is a single tag?
                if (end == null)
                {
                    cursor = head + 1;

                    // If there are more than one open/close brace we don't evaluate the shortcode
                    if (start.OpenBraces > 1 || start.CloseBraces > 1)
                    {
                        // We need to escape the braces if counts match
                        var bracesToSkip = start.OpenBraces == start.CloseBraces ? 1 : 0;

                        sb.Builder.Append('[', start.OpenBraces - bracesToSkip);
                        sb.Builder.Append(input.Substring(start.SourceIndex + start.OpenBraces, start.SourceLength - start.CloseBraces - start.OpenBraces + 1));
                        sb.Builder.Append(']', start.CloseBraces - bracesToSkip);
                    }
                    else
                    {
                        sb.Builder.Append(await RenderAsync(start, context));
                    }
                }
                else
                {
                    // If the braces are unbalanced we can't render the shortcode
                    var canRenderShortcode = start.OpenBraces == 1 && start.CloseBraces == 1 && end.OpenBraces == 1 && end.CloseBraces == 1;

                    if (canRenderShortcode)
                    {
                        // Are the tags adjacent?
                        if (tail - head == 1)
                        {
                            start.Content = "";
                            sb.Builder.Append(await RenderAsync(start, context));
                        }
                        // Is there a single Raw text between the tags?
                        else if (tail - head == 2)
                        {
                            var content = nodes[head + 1] as RawText;
                            start.Content = content.Text;
                            sb.Builder.Append(await RenderAsync(start, context));
                        }
                        // Fold the inner nodes
                        else
                        {
                            var content = await FoldClosingTagsAsync(input, nodes, head + 1, tail - head - 1, context);

                            start.Content = content;
                            sb.Builder.Append(await RenderAsync(start, context));
                        }
                    }
                    else
                    {
                        var bracesToSkip = start.OpenBraces == end.CloseBraces ? 1 : 0;

                        sb.Builder.Append('[', start.OpenBraces - bracesToSkip);
                        sb.Builder.Append(input.Substring(start.SourceIndex + start.OpenBraces, end.SourceIndex + end.SourceLength - end.CloseBraces - start.SourceIndex - start.OpenBraces + 1));
                        sb.Builder.Append(']', end.CloseBraces - bracesToSkip);
                    }
                }
            }

            return(sb.Builder.ToString());
        }