internal virtual FlexiTableBlock CreateFlexiTableBlock(string blockName,
                                                               FlexiTableType type,
                                                               ReadOnlyDictionary <string, string> attributes,
                                                               ProxyTableBlock proxyTableBlock,
                                                               BlockProcessor blockProcessor)
        {
            // Create table block
            var flexiTableBlock = new FlexiTableBlock(blockName, type, attributes, proxyTableBlock.Parser)
            {
                Column = proxyTableBlock.Column,
                Line   = proxyTableBlock.Line,
                Span   = proxyTableBlock.Span
            };

            // Create row blocks
            bool                    headerRowFound      = false;
            BlockProcessor          childBlockProcessor = blockProcessor.CreateChild();
            List <Row>              rows = proxyTableBlock.Rows;
            List <ColumnDefinition> columnDefinitions = proxyTableBlock.ColumnDefinitions;
            int  numColumns         = proxyTableBlock.NumColumns;
            int  numRows            = rows.Count;
            bool typeIsUnresponsive = type == FlexiTableType.Unresponsive;

            for (int rowIndex = 0; rowIndex < numRows; rowIndex++)
            {
                Row  row         = rows[rowIndex];
                bool isHeaderRow = row.IsHeaderRow;
                if (!typeIsUnresponsive && isHeaderRow)
                {
                    if (headerRowFound)
                    {
                        throw new OptionsException(nameof(IFlexiTableBlockOptions.Type), Strings.OptionsException_FlexiTableBlockFactory_TypeInvalidForTablesWithMultipleHeaderRows);
                    }
                    headerRowFound = true;
                }

                // Create and add row
                flexiTableBlock.Add(CreateFlexiTableRowBlock(type,
                                                             childBlockProcessor,
                                                             columnDefinitions,
                                                             numColumns,
                                                             rowIndex,
                                                             row,
                                                             isHeaderRow));
            }

            // Release for reuse
            childBlockProcessor.ReleaseChild();

            return(flexiTableBlock);
        }
Example #2
0
        internal virtual void ProcessContent(BlockProcessor blockProcessor,
                                             FlexiIncludeBlock flexiIncludeBlock,
                                             ContainerBlock parentOfNewBlocks,
                                             ReadOnlyCollection <string> content)
        {
            // The method used here is also used for GridTables. Using a child processor avoids conflicts with existing
            // open blocks in the root processor.
            BlockProcessor childProcessor = blockProcessor.CreateChild();

            childProcessor.Open(parentOfNewBlocks);

            // If content is code, start with ```
            bool isCode = flexiIncludeBlock.Type == FlexiIncludeType.Code;

            if (isCode)
            {
                childProcessor.ProcessLine(_codeBlockFence);
            }

            // TODO this has the potential to be really slow if content has lots of lines and we've got lots of clippings using start/end strings
            // Clippings - need not be sequential, they can also overlap
            int contentNumLines = content.Count;

            foreach (Clipping clipping in flexiIncludeBlock.Clippings ?? _defaultClippings)
            {
                string before = clipping.Before;
                if (isCode && before != null)
                {
                    childProcessor.ProcessLine(new StringSlice(before)); // No issue even if Before is multiline since we're in a code block
                }

                int startLineNumber = -1;
                (string startString, string endString) = clipping.GetNormalizedStartAndEndStrings();
                bool startStringSpecified = startString != null;
                bool endStringSpecified   = endString != null;
                (int normalizedStartLine, int normalizedEndLine)normalizedStartAndEndLines = default;
                if (!startStringSpecified || !endStringSpecified)
                {
                    normalizedStartAndEndLines = clipping.GetNormalizedStartAndEndLines(contentNumLines);
                }
                if (startStringSpecified)
                {
                    int lastIndex = contentNumLines - 2; // Since demarcation lines are not included in the clipping, the last line cannot be a start demarcation line.
                    for (int i = 0; i <= lastIndex; i++)
                    {
                        if (content[i].Contains(startString))
                        {
                            startLineNumber = i + 2;
                            break;
                        }
                    }

                    if (startLineNumber == -1)
                    {
                        throw new OptionsException(nameof(Clipping.StartString),
                                                   string.Format(Strings.OptionsException_FlexiIncludeBlockFactory_NoLineContainsStartString, startString));
                    }
                }
                else
                {
                    startLineNumber = normalizedStartAndEndLines.normalizedStartLine;
                }

                // If we encounter an invalid block in the included content, this ensures the BlockException thrown has the right line number in the included content's source.
                childProcessor.LineIndex = startLineNumber - 1;

                for (int lineNumber = startLineNumber; lineNumber <= contentNumLines; lineNumber++)
                {
                    var stringSlice = new StringSlice(content[lineNumber - 1]);

                    if (!stringSlice.IsEmpty)
                    {
                        if (clipping.Indent > 0)
                        {
                            stringSlice = _leadingWhitespaceEditorService.Indent(stringSlice, clipping.Indent);
                        }

                        _leadingWhitespaceEditorService.Dedent(ref stringSlice, clipping.Dedent);
                        _leadingWhitespaceEditorService.Collapse(ref stringSlice, clipping.Collapse);
                    }

                    childProcessor.ProcessLine(stringSlice);

                    // Check whether we've reached the end of the clipping
                    if (endStringSpecified)
                    {
                        if (lineNumber == contentNumLines)
                        {
                            throw new OptionsException(nameof(Clipping.EndString),
                                                       string.Format(Strings.OptionsException_FlexiIncludeBlockFactory_NoLineContainsEndString, endString));
                        }

                        // Check if next line contains the end line substring
                        if (content[lineNumber].Contains(endString))
                        {
                            break;
                        }
                    }
                    else if (lineNumber == normalizedStartAndEndLines.normalizedEndLine)
                    {
                        break;
                    }
                }

                string after = clipping.After;
                if (isCode && after != null)
                {
                    childProcessor.ProcessLine(new StringSlice(after)); // No issue even if Before is multiline since we're in a code block
                }
            }

            if (isCode) // If content is code, end with ```
            {
                childProcessor.ProcessLine(_codeBlockFence);
            }

            // Ensure that the last replacement block has been closed. While the block never makes it to the OpenedBlocks collection in the root processor,
            // calling Close for it ensures that it and its children's Close methods and events get called.
            childProcessor.Close(parentOfNewBlocks.LastChild);

            // BlockProcessors are pooled. Once we're done with innerProcessor, we must release it. This also removes all references to
            // tempContainerBlock, which should allow it to be collected quickly.
            childProcessor.ReleaseChild();
        }