private async Task <InsertingStreamLocation> ExecuteShortcodesAsync( IDocument input, IExecutionContext context, ShortcodeLocation location, ImmutableDictionary <string, IShortcode> shortcodes) { // Execute the shortcode IDocument shortcodeResult = await shortcodes[location.Name].ExecuteAsync(location.Arguments, location.Content, input, context); IDocument mergedResult = null; if (shortcodeResult != null) { // Merge output metadata with the current input document // Creating a new document is the easiest way to ensure all the metadata from shortcodes gets accumulated correctly mergedResult = input.Clone(shortcodeResult, shortcodeResult.ContentProvider); // Don't process nested shortcodes if it's the raw shortcode if (!location.Name.Equals(RawShortcode.RawShortcodeName, StringComparison.OrdinalIgnoreCase)) { // Recursively parse shortcodes IDocument nestedResult = await ProcessShortcodesAsync(mergedResult, context); if (nestedResult != null) { mergedResult = nestedResult; } } return(new InsertingStreamLocation(location.FirstIndex, location.LastIndex, mergedResult)); } return(new InsertingStreamLocation(location.FirstIndex, location.LastIndex, mergedResult)); }
private async Task <InsertingStreamLocation> ExecuteShortcodesAsync( IDocument input, IExecutionContext context, ShortcodeLocation location, ImmutableDictionary <string, IShortcode> shortcodes) { // Execute the shortcode IEnumerable <ShortcodeResult> results = await shortcodes[location.Name].ExecuteAsync(location.Arguments, location.Content, input, context); // Process the results if (results != null) { // Iterate the result content streams List <IContentProvider> resultContentProviders = new List <IContentProvider>(); foreach (ShortcodeResult result in results) { if (result != null && result.ContentProvider != null) { // Don't process nested shortcodes if it's the raw shortcode IContentProvider resultContentProvider = result.ContentProvider; if (!location.Name.Equals(RawShortcode.RawShortcodeName, StringComparison.OrdinalIgnoreCase)) { // Clone the input document with nested metadata if we have any IDocument nestedInput = result.NestedMetadata?.Count > 0 ? input.Clone(result.NestedMetadata) : input; // Recursively parse shortcodes IContentProvider nestedContentProvider = await ProcessShortcodesAsync(nestedInput, result.ContentProvider, context); if (nestedContentProvider != null) { resultContentProvider = nestedContentProvider; } } resultContentProviders.Add(resultContentProvider); } } if (resultContentProviders.Count > 0) { return(new InsertingStreamLocation(location.FirstIndex, location.LastIndex, resultContentProviders)); } } return(new InsertingStreamLocation(location.FirstIndex, location.LastIndex, null)); }
/// <summary> /// Identifies shortcode locations in a stream. /// </summary> /// <param name="stream">The stream to parse. This method will not dispose the passed-in stream.</param> /// <returns>All of the shortcode locations in the stream.</returns> public List <ShortcodeLocation> Parse(Stream stream) { List <ShortcodeLocation> locations = new List <ShortcodeLocation>(); CurrentTag currentTag = null; ShortcodeLocation shortcode = null; StringBuilder content = null; using (TextReader reader = new StreamReader(stream, Encoding.UTF8, true, 1024, true)) { int r; int i = 0; while ((r = reader.Read()) != -1) { char c = (char)r; // Look for delimiters and tags if (currentTag == null && shortcode == null) { // Searching for open tag start delimiter if (_startDelimiter.Locate(c, false)) { currentTag = new CurrentTag(i - (_startDelimiter.Text.Length - 1)); } } else if (currentTag != null && shortcode == null) { // Searching for open tag end delimiter currentTag.Content.Append(c); if (_endDelimiter.Locate(c, false)) { // Is this self-closing? if (currentTag.Content[currentTag.Content.Length - _endDelimiter.Text.Length - 1] == '/') { // Self-closing shortcode = GetShortcodeLocation( currentTag.FirstIndex, currentTag.Content.ToString(0, currentTag.Content.Length - _endDelimiter.Text.Length - 1)); shortcode.Finish(i); locations.Add(shortcode); shortcode = null; } else { // Look for a closing tag shortcode = GetShortcodeLocation( currentTag.FirstIndex, currentTag.Content.ToString(0, currentTag.Content.Length - _endDelimiter.Text.Length)); content = new StringBuilder(); } currentTag = null; } } else if (currentTag == null && shortcode != null) { content.Append(c); // Searching for close tag start delimiter if (_startDelimiter.Locate(c, true)) { currentTag = new CurrentTag(i); } } else { currentTag.Content.Append(c); // Searching for close tag end delimiter if (_endDelimiter.Locate(c, false)) { // Get the name of this shortcode close tag string name = currentTag.Content.ToString( 0, currentTag.Content.Length - _endDelimiter.Text.Length) .Trim(); if (name.Any(x => char.IsWhiteSpace(x))) { throw new ShortcodeParserException("Closing shortcode tags should only consist of the shortcode name"); } // Make sure it's the same name if (name.Equals(shortcode.Name, StringComparison.OrdinalIgnoreCase)) { // If the content is contained within a processing instruction, trim that string shortcodeContent = content.ToString(0, content.Length - _startDelimiter.Text.Length - 1); string trimmedContent = shortcodeContent.Trim(); if (trimmedContent.StartsWith(TrimStartDelimiter) && trimmedContent.EndsWith(TrimEndDelimiter)) { shortcodeContent = trimmedContent.Substring( TrimStartDelimiter.Length, trimmedContent.Length - (TrimStartDelimiter.Length + TrimEndDelimiter.Length)); } shortcode.Content = shortcodeContent; shortcode.Finish(i); locations.Add(shortcode); shortcode = null; content = null; } else { // It wasn't the same name, so add the tag content to the running content content.Append(currentTag.Content.ToString()); } currentTag = null; } } i++; } if (shortcode != null) { throw new ShortcodeParserException($"The shortcode {shortcode.Name} was not terminated"); } } return(locations); }