Beispiel #1
0
        // 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())));
        }