// The inputStream will be disposed if this returns a result document but will not otherwise private async Task <IDocument> ProcessShortcodesAsync(IDocument input, IExecutionContext context) { // Parse the input stream looking for shortcodes ShortcodeParser parser = new ShortcodeParser(_startDelimiter, _endDelimiter, context.Shortcodes); List <ShortcodeLocation> locations; using (Stream inputStream = input.GetContentStream()) { locations = parser.Parse(inputStream); } // Return the original document if we didn't find any if (locations.Count == 0) { return(null); } // Otherwise, create a shortcode instance for each named shortcode ImmutableDictionary <string, IShortcode> shortcodes = locations .Select(x => x.Name) .Distinct(StringComparer.OrdinalIgnoreCase) .ToImmutableDictionary(x => x, x => context.Shortcodes.CreateInstance(x), StringComparer.OrdinalIgnoreCase); // Execute each of the shortcodes in order List <InsertingStreamLocation> insertingLocations = new List <InsertingStreamLocation>(); foreach (ShortcodeLocation location in locations) { InsertingStreamLocation insertingLocation = await ExecuteShortcodesAsync(input, context, location, shortcodes); insertingLocations.Add(insertingLocation); // Accumulate metadata for the following shortcodes if (insertingLocation.Document != null) { input = input.Clone(insertingLocation.Document); } } // Dispose any shortcodes that implement IDisposable foreach (IDisposable disposableShortcode in shortcodes.Values.Select(x => x as IDisposable).Where(x => x != null)) { disposableShortcode.Dispose(); } // Construct a new stream with the shortcode results inserted // We have to use all TextWriter/TextReaders over the streams to ensure consistent encoding Stream resultStream = context.MemoryStreamFactory.GetStream(); char[] buffer = new char[4096]; using (TextWriter writer = new StreamWriter(resultStream, Encoding.UTF8, 4096, true)) { // The input stream will get disposed when the reader is using (TextReader reader = new StreamReader(input.GetContentStream())) { int position = 0; int length = 0; foreach (InsertingStreamLocation insertingLocation in insertingLocations) { // Copy up to the start of this shortcode length = insertingLocation.FirstIndex - position; Read(reader, writer, length, ref buffer); position += length; // Copy the shortcode content to the result stream if (insertingLocation.Document != null) { // This will dispose the shortcode content stream when done using (TextReader insertingReader = new StreamReader(insertingLocation.Document.GetContentStream())) { Read(insertingReader, writer, null, ref buffer); } // Merge shortcode metadata with the current document input = input.Clone(insertingLocation.Document); } // Skip the shortcode text length = insertingLocation.LastIndex - insertingLocation.FirstIndex + 1; Read(reader, null, length, ref buffer); position += length; } // Copy remaining Read(reader, writer, null, ref buffer); } } return(input.Clone(context.GetContentProvider(resultStream))); }
/// <inheritdoc /> public override async Task <IDocument> ExecuteAsync(KeyValuePair <string, string>[] args, string content, IDocument document, IExecutionContext context) { IMetadataDictionary dictionary = args.ToDictionary( "Class", "HeaderRows", "FooterRows", "HeaderCols", "HeaderClass", "BodyClass", "FooterClass"); string[] lines = content .Trim() .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .ToArray(); // Table XElement table = new XElement( "table", dictionary.XAttribute("class")); int line = 0; // Header int headerRows = dictionary.Get("HeaderRows", 0); XElement header = null; for (int c = 0; c < headerRows && line < lines.Length; c++, line++) { // Create the section if (c == 0) { header = new XElement( "thead", dictionary.XAttribute("class", "HeaderClass")); table.Add(header); } // Create the current row XElement row = new XElement("tr"); header.Add(row); // Add the columns foreach (string col in ShortcodeParser.SplitArguments(lines[line], 0).ToValueArray()) { row.Add(new XElement("th", col)); } } // Body int bodyRows = lines.Length - line - dictionary.Get("FooterRows", 0); XElement body = null; for (int c = 0; c < bodyRows && line < lines.Length; c++, line++) { // Create the section if (c == 0) { body = new XElement( "tbody", dictionary.XAttribute("class", "BodyClass")); table.Add(body); } // Create the current row XElement row = new XElement("tr"); body.Add(row); // Add the columns int th = dictionary.Get("HeaderCols", 0); foreach (string col in ShortcodeParser.SplitArguments(lines[line], 0).ToValueArray()) { row.Add(new XElement(th-- > 0 ? "th" : "td", col)); } } // Footer XElement footer = null; for (int c = 0; line < lines.Length; c++, line++) { // Create the section if (c == 0) { footer = new XElement( "tfoot", dictionary.XAttribute("class", "FooterClass")); table.Add(footer); } // Create the current row XElement row = new XElement("tr"); footer.Add(row); // Add the columns int th = dictionary.Get("HeaderCols", 0); foreach (string col in ShortcodeParser.SplitArguments(lines[line], 0).ToValueArray()) { row.Add(new XElement(th-- > 0 ? "th" : "td", col)); } } return(context.CreateDocument(await context.GetContentProviderAsync(table.ToString()))); }