Example #1
0
        public void Undo_ReplacesProxyTableBlockWithAParagraphBlock()
        {
            // Arrange
            const string dummyText            = "dummyText";
            var          dummyProxyTableBlock = new ProxyTableBlock(null);

            dummyProxyTableBlock.Lines = new StringLineGroup(dummyText);
            BlockParser    dummyBlockParser = _mockRepository.Create <BlockParser>().Object;
            ContainerBlock dummyParent      = _mockRepository.Create <ContainerBlock>(dummyBlockParser).Object; // Must specify block parser since we're calling ProcessLine later

            dummyParent.Add(dummyProxyTableBlock);                                                              // Assigns dummyParent to dummyProxyTableBlock.Parent
            BlockProcessor dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor();

            dummyBlockProcessor.Open(dummyParent);
            dummyBlockProcessor.Open(dummyProxyTableBlock);
            ExposedFlexiTableBlockParser testSubject = CreateExposedFlexiTableBlockParser();

            // Act
            testSubject.ExposedUndo(dummyBlockProcessor, dummyProxyTableBlock);

            // Assert
            Assert.Single(dummyParent);
            var resultParagraphBlock = dummyParent[0] as ParagraphBlock;

            Assert.NotNull(resultParagraphBlock);
            Assert.Equal(dummyText, resultParagraphBlock.Lines.ToString());
            // Verify that ParagraphBlock remains open
            dummyBlockProcessor.ProcessLine(new StringSlice(dummyText));
            Assert.Equal($"{dummyText}\n{dummyText}", resultParagraphBlock.Lines.ToString());
        }
Example #2
0
        /// <summary>
        /// Replaces a <see cref="ProxyTableBlock"/> with a <see cref="ParagraphBlock"/>.
        /// </summary>
        /// <param name="blockProcessor">The <see cref="BlockProcessor"/> processing the <see cref="ProxyTableBlock"/> to undo.</param>
        /// <param name="proxyTableBlock">The <see cref="ProxyTableBlock"/> to undo.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="blockProcessor"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="proxyTableBlock"/> is <c>null</c>.</exception>
        protected virtual void Undo(BlockProcessor blockProcessor, ProxyTableBlock proxyTableBlock)
        {
            if (blockProcessor == null)
            {
                throw new ArgumentNullException(nameof(blockProcessor));
            }

            if (proxyTableBlock == null)
            {
                throw new ArgumentNullException(nameof(proxyTableBlock));
            }

            // Discard proxyTableBlock
            ContainerBlock parent = proxyTableBlock.Parent;

            blockProcessor.Discard(proxyTableBlock);

            // Replace with paragraph block
            ParagraphBlockParser parser = blockProcessor.Parsers.FindExact <ParagraphBlockParser>();
            var paragraphBlock          = new ParagraphBlock(parser)
            {
                Lines = proxyTableBlock.Lines,
            };

            parent.Add(paragraphBlock);
            blockProcessor.Open(paragraphBlock);
        }
Example #3
0
        public void UpdateOpenFlexiSectionBlocks_ClosesFlexiSectionBlocksInCurrentBranchWithTheSameOrHigherLevels()
        {
            // Arrange
            BlockProcessor     dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor();
            Mock <BlockParser> mockBlockParser     = _mockRepository.Create <BlockParser>();

            mockBlockParser.Setup(b => b.TryContinue(dummyBlockProcessor, It.IsAny <Block>())).Returns(BlockState.Continue);
            FlexiSectionBlock dummyLevel1FlexiSectionBlock = CreateFlexiSectionBlock(level: 1, blockParser: mockBlockParser.Object);
            FlexiSectionBlock dummyLevel2FlexiSectionBlock = CreateFlexiSectionBlock(level: 2, blockParser: mockBlockParser.Object);
            FlexiSectionBlock dummyLevel3FlexiSectionBlock = CreateFlexiSectionBlock(level: 3, blockParser: mockBlockParser.Object);

            dummyBlockProcessor.Open(dummyLevel1FlexiSectionBlock);
            dummyBlockProcessor.Open(dummyLevel2FlexiSectionBlock);
            dummyBlockProcessor.Open(dummyLevel3FlexiSectionBlock);
            dummyBlockProcessor.ProcessLine(new StringSlice("")); // Sets BlockProcessor.CurrentContainer to a FlexiSectionBlock
            var dummyCurrentBranch = new Stack <FlexiSectionBlock>();

            dummyCurrentBranch.Push(dummyLevel1FlexiSectionBlock);
            dummyCurrentBranch.Push(dummyLevel2FlexiSectionBlock);
            dummyCurrentBranch.Push(dummyLevel3FlexiSectionBlock);
            var dummyOpenSectionBlocks = new Stack <Stack <FlexiSectionBlock> >();

            dummyOpenSectionBlocks.Push(dummyCurrentBranch);
            FlexiSectionBlock dummyNewFlexiSectionBlock     = CreateFlexiSectionBlock(level: 2);
            Mock <FlexiSectionBlockFactory> mockTestSubject = CreateMockFlexiSectionBlockFactory();

            mockTestSubject.CallBase = true;
            mockTestSubject.Setup(t => t.GetOrCreateOpenFlexiSectionBlocks(dummyBlockProcessor.Document)).Returns(dummyOpenSectionBlocks);

            // Act
            mockTestSubject.Object.UpdateOpenFlexiSectionBlocks(dummyBlockProcessor, dummyNewFlexiSectionBlock);

            // Assert
            _mockRepository.VerifyAll();
            Assert.Equal(2, dummyCurrentBranch.Count); // Level 2 and level 3 blocks get removed
            Assert.Same(dummyNewFlexiSectionBlock, dummyCurrentBranch.Pop());
            Assert.Same(dummyLevel1FlexiSectionBlock, dummyCurrentBranch.Peek());
            mockBlockParser.Verify(b => b.Close(dummyBlockProcessor, dummyLevel2FlexiSectionBlock)); // Gets closed
            mockBlockParser.Verify(b => b.Close(dummyBlockProcessor, dummyLevel3FlexiSectionBlock)); // Gets closed
        }
Example #4
0
        private static void Undo(BlockProcessor processor, GridTableState tableState, Table gridTable)
        {
            var parser = processor.Parsers.FindExact <ParagraphBlockParser>();
            // Discard the grid table
            var parent = gridTable.Parent;

            processor.Discard(gridTable);
            var paragraphBlock = new ParagraphBlock(parser)
            {
                Lines = tableState.Lines,
            };

            parent.Add(paragraphBlock);
            processor.Open(paragraphBlock);
        }
Example #5
0
        public void Create_CreatesFlexiCardBlock()
        {
            // Arrange
            const int                      dummyColumn                = 2;
            var                            dummyLine                  = new StringSlice("dummyText", 3, 8);
            const string                   dummyUrl                   = "dummyUrl";
            const string                   dummyBackgroundIcon        = "dummyBackgroundIcon";
            IFlexiCardBlockOptions         dummyDefaultCardOptions    = _mockRepository.Create <IFlexiCardBlockOptions>().Object;
            Mock <IFlexiCardsBlockOptions> mockFlexiCardsBlockOptions = _mockRepository.Create <IFlexiCardsBlockOptions>();

            mockFlexiCardsBlockOptions.Setup(f => f.DefaultCardOptions).Returns(dummyDefaultCardOptions);
            Mock <BlockParser>   dummyBlockParser          = _mockRepository.Create <BlockParser>();
            ProxyFlexiCardsBlock dummyProxyFlexiCardsBlock = CreateProxyFlexiCardsBlock(mockFlexiCardsBlockOptions.Object, blockParser: dummyBlockParser.Object);
            BlockProcessor       dummyBlockProcessor       = MarkdigTypesFactory.CreateBlockProcessor();

            // Following 3 lines set dummyBlockProcessor.CurrentContainer
            dummyBlockParser.Setup(b => b.TryContinue(dummyBlockProcessor, dummyProxyFlexiCardsBlock)).Returns(BlockState.ContinueDiscard);
            dummyBlockProcessor.Open(dummyProxyFlexiCardsBlock);
            dummyBlockProcessor.ProcessLine(new StringSlice(string.Empty));
            dummyBlockProcessor.Column = dummyColumn;
            dummyBlockProcessor.Line   = dummyLine;
            var dummyAttributes = new ReadOnlyDictionary <string, string>(new Dictionary <string, string>());
            Mock <IFlexiCardBlockOptions> mockFlexiCardBlockOptions = _mockRepository.Create <IFlexiCardBlockOptions>();

            mockFlexiCardBlockOptions.Setup(f => f.Url).Returns(dummyUrl);
            mockFlexiCardBlockOptions.Setup(f => f.BackgroundIcon).Returns(dummyBackgroundIcon);
            mockFlexiCardBlockOptions.Setup(f => f.Attributes).Returns(dummyAttributes);
            Mock <IBlockOptionsFactory <IFlexiCardBlockOptions> > mockFlexiCardBlockOptionsFactory = _mockRepository.
                                                                                                     Create <IBlockOptionsFactory <IFlexiCardBlockOptions> >();

            mockFlexiCardBlockOptionsFactory.Setup(f => f.Create(dummyDefaultCardOptions, dummyBlockProcessor)).Returns(mockFlexiCardBlockOptions.Object);
            FlexiCardBlockFactory testSubject = CreateFlexiCardBlockFactory(mockFlexiCardBlockOptionsFactory.Object);

            // Act
            FlexiCardBlock result = testSubject.Create(dummyBlockProcessor, dummyBlockParser.Object);

            // Assert
            _mockRepository.VerifyAll();
            Assert.Equal(dummyUrl, result.Url);
            Assert.Equal(dummyBackgroundIcon, result.BackgroundIcon);
            Assert.Same(dummyAttributes, result.Attributes);
            Assert.Same(dummyBlockParser.Object, result.Parser);
            Assert.Equal(dummyColumn, result.Column);
            Assert.Equal(dummyLine.Start, result.Span.Start);
            Assert.Equal(0, result.Span.End);
        }
Example #6
0
        public void UpdateOpenFlexiSectionBlocks_IgnoresAncestorContainerBlocksThatAreClosed()
        {
            // Arrange
            BlockProcessor dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor();
            // Create abitrary closed blocks (ListItemBlock and ListBlock) and set BlockProcessor.CurrentContainer to dummyListItemBlock.
            // Closed blocks should be ignored, so UpdateOpenFlexiSectionBlocks should not create a new stack.
            Mock <BlockParser> mockBlockParser = _mockRepository.Create <BlockParser>();

            mockBlockParser.Setup(b => b.TryContinue(dummyBlockProcessor, It.IsAny <Block>())).Returns(BlockState.Continue);
            var dummyListItemBlock = new ListItemBlock(mockBlockParser.Object);
            var dummyListBlock     = new ListBlock(null)
            {
                dummyListItemBlock
            };
            FlexiSectionBlock dummyFlexiSectionBlock = CreateFlexiSectionBlock();

            dummyFlexiSectionBlock.Add(dummyListBlock);
            dummyBlockProcessor.Open(dummyListItemBlock);
            dummyBlockProcessor.ProcessLine(new StringSlice("")); // This line sets BlockProcessor.CurrentContainer to dummyListItemBlock
            dummyListItemBlock.IsOpen = false;
            dummyListBlock.IsOpen     = false;
            FlexiSectionBlock dummyNewFlexiSectionBlock = CreateFlexiSectionBlock();
            var dummyCurrentBranch = new Stack <FlexiSectionBlock>();

            dummyCurrentBranch.Push(dummyFlexiSectionBlock);
            var dummyOpenSectionBlocks = new Stack <Stack <FlexiSectionBlock> >();

            dummyOpenSectionBlocks.Push(dummyCurrentBranch);
            Mock <FlexiSectionBlockFactory> mockTestSubject = CreateMockFlexiSectionBlockFactory();

            mockTestSubject.CallBase = true;
            mockTestSubject.Setup(t => t.GetOrCreateOpenFlexiSectionBlocks(dummyBlockProcessor.Document)).Returns(dummyOpenSectionBlocks);

            // Act
            mockTestSubject.Object.UpdateOpenFlexiSectionBlocks(dummyBlockProcessor, dummyNewFlexiSectionBlock);

            // Assert
            _mockRepository.VerifyAll();
            Assert.Single(dummyCurrentBranch);
            Assert.Equal(dummyNewFlexiSectionBlock, dummyCurrentBranch.Peek());
        }
        internal virtual FlexiTableCellBlock CreateFlexiTableCellBlock(FlexiTableType type,
                                                                       BlockProcessor childBlockProcessor,
                                                                       List <ColumnDefinition> columnDefinitions,
                                                                       Cell cell)
        {
            // Colspan and rowspan
            int startColumnIndex = cell.StartColumnIndex;
            int colspan          = cell.EndColumnIndex - startColumnIndex + 1;
            int rowspan          = cell.EndRowIndex - cell.StartRowIndex + 1;

            if (type != FlexiTableType.Unresponsive && (rowspan > 1 || colspan > 1))
            {
                throw new OptionsException(nameof(IFlexiTableBlockOptions.Type), Strings.OptionsException_FlexiTableBlockFactory_TypeInvalidForTablesWithCellsThatHaveRowspanOrColspan);
            }

            // Create
            var flexiTableCellBlock = new FlexiTableCellBlock(colspan,
                                                              rowspan,
                                                              columnDefinitions?[startColumnIndex].ContentAlignment ?? ContentAlignment.None);

            // Process cell block contents
            childBlockProcessor.LineIndex = cell.LineIndex;
            childBlockProcessor.Open(flexiTableCellBlock);
            ref StringLineGroup stringLineGroup = ref cell.Lines;
Example #8
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            var paragraphBlock = processor.LastBlock as ParagraphBlock;

            if (processor.IsCodeIndent || paragraphBlock == null || paragraphBlock.LastLine - processor.LineIndex > 1)
            {
                return(BlockState.None);
            }

            var startPosition = processor.Start;

            var column = processor.ColumnBeforeIndent;

            processor.NextChar();
            processor.ParseIndent();
            var delta = processor.Column - column;

            // We expect to have a least
            if (delta < 4)
            {
                // Return back to original position
                processor.GoToColumn(column);
                return(BlockState.None);
            }

            if (delta > 4)
            {
                processor.GoToColumn(column + 4);
            }

            var previousParent        = paragraphBlock.Parent;
            var currentDefinitionList = GetCurrentDefinitionList(paragraphBlock, previousParent);

            processor.Discard(paragraphBlock);

            // If the paragraph block was not part of the opened blocks, we need to remove it manually from its parent container
            if (paragraphBlock.Parent != null)
            {
                paragraphBlock.Parent.Remove(paragraphBlock);
            }

            if (currentDefinitionList == null)
            {
                currentDefinitionList = new DefinitionList(this)
                {
                    Span   = new SourceSpan(paragraphBlock.Span.Start, processor.Line.End),
                    Column = paragraphBlock.Column,
                    Line   = paragraphBlock.Line,
                };
                previousParent.Add(currentDefinitionList);
            }

            var definitionItem = new DefinitionItem(this)
            {
                Line             = processor.LineIndex,
                Column           = column,
                Span             = new SourceSpan(startPosition, processor.Line.End),
                OpeningCharacter = processor.CurrentChar,
            };

            for (int i = 0; i < paragraphBlock.Lines.Count; i++)
            {
                var line = paragraphBlock.Lines.Lines[i];
                var term = new DefinitionTerm(this)
                {
                    Column = paragraphBlock.Column,
                    Line   = line.Line,
                    Span   = new SourceSpan(paragraphBlock.Span.Start, paragraphBlock.Span.End),
                    IsOpen = false
                };
                term.AppendLine(ref line.Slice, line.Column, line.Line, line.Position);
                definitionItem.Add(term);
            }
            currentDefinitionList.Add(definitionItem);
            processor.Open(definitionItem);

            // Update the end position
            currentDefinitionList.UpdateSpanEnd(processor.Line.End);

            return(BlockState.Continue);
        }
Example #9
0
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            var definitionItem = (DefinitionItem)block;

            if (processor.IsCodeIndent)
            {
                processor.GoToCodeIndent();
                return(BlockState.Continue);
            }

            var list          = (DefinitionList)definitionItem.Parent;
            var lastBlankLine = definitionItem.LastChild as BlankLineBlock;

            // Check if we have another definition list
            if (Array.IndexOf(OpeningCharacters, processor.CurrentChar) >= 0)
            {
                var startPosition = processor.Start;
                var column        = processor.ColumnBeforeIndent;
                processor.NextChar();
                processor.ParseIndent();
                var delta = processor.Column - column;

                // We expect to have a least
                if (delta < 4)
                {
                    // Remove the blankline before breaking this definition item
                    if (lastBlankLine != null)
                    {
                        definitionItem.RemoveAt(definitionItem.Count - 1);
                    }

                    list.Span.End = list.LastChild.Span.End;
                    return(BlockState.None);
                }

                if (delta > 4)
                {
                    processor.GoToColumn(column + 4);
                }

                processor.Close(definitionItem);
                var nextDefinitionItem = new DefinitionItem(this)
                {
                    Span             = new SourceSpan(startPosition, processor.Line.End),
                    Line             = processor.LineIndex,
                    Column           = processor.Column,
                    OpeningCharacter = processor.CurrentChar,
                };
                list.Add(nextDefinitionItem);
                processor.Open(nextDefinitionItem);

                return(BlockState.Continue);
            }

            var isBreakable = definitionItem.LastChild?.IsBreakable ?? true;

            if (processor.IsBlankLine)
            {
                if (lastBlankLine == null && isBreakable)
                {
                    definitionItem.Add(new BlankLineBlock());
                }
                return(isBreakable ? BlockState.ContinueDiscard : BlockState.Continue);
            }

            var paragraphBlock = definitionItem.LastChild as ParagraphBlock;

            if (lastBlankLine == null && paragraphBlock != null)
            {
                return(BlockState.Continue);
            }

            // Remove the blankline before breaking this definition item
            if (lastBlankLine != null)
            {
                definitionItem.RemoveAt(definitionItem.Count - 1);
            }

            list.Span.End = list.LastChild.Span.End;
            return(BlockState.Break);
        }
Example #10
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();
        }
Example #11
0
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            var gridTable  = (Table)block;
            var tableState = (GridTableState)block.GetData(typeof(GridTableState));

            // We expect to start at the same
            //if (processor.Start == tableState.Start)
            {
                var columns = tableState.ColumnSlices;

                foreach (var columnSlice in columns)
                {
                    columnSlice.PreviousColumnSpan = columnSlice.CurrentColumnSpan;
                    columnSlice.CurrentColumnSpan  = 0;
                }

                if (processor.CurrentChar == '+')
                {
                    var result = ParseRowSeparator(processor, tableState, gridTable);
                    if (result != BlockState.None)
                    {
                        return(result);
                    }
                }
                else if (processor.CurrentChar == '|')
                {
                    var line = processor.Line;

                    // | ------------- | ------------ | ---------------------------------------- |
                    // Calculate the colspan for the new row
                    int columnIndex = -1;
                    foreach (var columnSlice in columns)
                    {
                        if (line.PeekCharExtra(columnSlice.Start) == '|')
                        {
                            columnIndex++;
                        }
                        if (columnIndex >= 0)
                        {
                            columns[columnIndex].CurrentColumnSpan++;
                        }
                    }

                    // Check if the colspan of the current row is the same than the previous row
                    bool continueRow = true;
                    foreach (var columnSlice in columns)
                    {
                        if (columnSlice.PreviousColumnSpan != columnSlice.CurrentColumnSpan)
                        {
                            continueRow = false;
                            break;
                        }
                    }

                    // If the current row doesn't continue the previous row (col span are different)
                    // Close the previous row
                    if (!continueRow)
                    {
                        TerminateLastRow(processor, tableState, gridTable, false);
                    }

                    for (int i = 0; i < columns.Count;)
                    {
                        var column          = columns[i];
                        var nextColumnIndex = i + column.CurrentColumnSpan;
                        // If the span is 0, we exit
                        if (nextColumnIndex == i)
                        {
                            break;
                        }
                        var nextColumn = nextColumnIndex < columns.Count ? columns[nextColumnIndex] : null;

                        var sliceForCell = line;
                        sliceForCell.Start = line.Start + column.Start + 1;
                        if (nextColumn != null)
                        {
                            sliceForCell.End = line.Start + nextColumn.Start - 1;
                        }
                        else
                        {
                            var columnEnd = columns[columns.Count - 1].End;
                            // If there is a `|` exactly at the expected end of the table row, we cut the line
                            // otherwise we allow to have the last cell of a row to be open for longer cell content
                            if (line.PeekCharExtra(columnEnd + 1) == '|')
                            {
                                sliceForCell.End = line.Start + columnEnd;
                            }
                        }
                        sliceForCell.TrimEnd();

                        // Process the content of the cell
                        column.BlockProcessor.LineIndex = processor.LineIndex;
                        column.BlockProcessor.ProcessLine(sliceForCell);

                        // Go to next column
                        i = nextColumnIndex;
                    }

                    return(BlockState.ContinueDiscard);
                }
            }

            TerminateLastRow(processor, tableState, gridTable, true);

            // If we don't have a row, it means that only the header was valid
            // So we need to remove the grid table, and create a ParagraphBlock
            // with the 2 slices
            if (gridTable.Count == 0)
            {
                var parser = processor.Parsers.FindExact <ParagraphBlockParser>();
                // Discard the grid table
                var parent = gridTable.Parent;
                processor.Discard(gridTable);
                var paragraphBlock = new ParagraphBlock(parser)
                {
                    Lines = tableState.Lines,
                };
                parent.Add(paragraphBlock);
                processor.Open(paragraphBlock);
            }

            return(BlockState.Break);
        }