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()); }
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()); }