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()); }
/// <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); }
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 }
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); }
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); }
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;
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); }
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); }
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(); }
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); }