// Handles closing of FlexiSectionBlocks. A FlexiSectionBlock is closed when another FlexiSectionBlock in the same tree with the same or // lower level opens. internal virtual void UpdateOpenFlexiSectionBlocks(BlockProcessor processor, FlexiSectionBlock flexiSectionBlock) { // Since sectioning content roots like blockquotes have their own discrete section trees, // we maintain a stack of stacks. Each stack represents the open branch of a tree. Stack <Stack <FlexiSectionBlock> > openFlexiSectionBlocks = GetOrCreateOpenFlexiSectionBlocks(processor.Document); // Discard stacks for closed branches while (openFlexiSectionBlocks.Count > 0) { // When a sectioning content root is closed, all of its children are closed, so the last section in its branch // will be closed. Under no circumstance will the section at the tip of a branch be closed without its ancestors // being closed as well. if (openFlexiSectionBlocks.Peek().Peek().IsOpen) { break; } openFlexiSectionBlocks.Pop(); } // Find parent container block - processor.CurrentContainer may be closed. processor.CurrentContainer is only updated when // BlockProcessor.ProcessNewBlocks calls BlockProcessor.CloseAll, so at this point, processor.CurrentContainer may not be the eventual // parent of our new FlexiSectionBlock. ContainerBlock parentContainerBlock = processor.CurrentContainer; while (!parentContainerBlock.IsOpen) // We will eventually reach the root MarkdownDocument (gauranteed to be open) if all other containers aren't open { parentContainerBlock = parentContainerBlock.Parent; } if (!(parentContainerBlock is FlexiSectionBlock)) // parentContainerBlock is a sectioning content root, for example, a blockquote. Create a new stack for a new tree { var newStack = new Stack <FlexiSectionBlock>(); newStack.Push(flexiSectionBlock); openFlexiSectionBlocks.Push(newStack); } else { Stack <FlexiSectionBlock> currentBranch = openFlexiSectionBlocks.Peek(); // If parentContainerBlock is a FlexiSectionBlock, at least 1 branch of open FlexiSectionBlocks exists // Close open FlexiSectionBlocks that have the same or higher levels FlexiSectionBlock flexiSectionBlockToClose = null; while (currentBranch.Count > 0) { if (currentBranch.Peek().Level < flexiSectionBlock.Level) { break; } flexiSectionBlockToClose = currentBranch.Pop(); } if (flexiSectionBlockToClose != null) { processor.Close(flexiSectionBlockToClose); } // Add new FlexiSectionBlock to current stack currentBranch.Push(flexiSectionBlock); } }
public override BlockState TryContinue(BlockProcessor processor, Block block) { var footnote = (Footnote)block; if (processor.CurrentBlock == null || processor.CurrentBlock.IsBreakable) { if (processor.IsBlankLine) { footnote.IsLastLineEmpty = true; return(BlockState.ContinueDiscard); } if (processor.Column == 0) { if (footnote.IsLastLineEmpty) { // Close the current footnote processor.Close(footnote); // Parse any opening footnote return(TryOpen(processor)); } // Make sure that consecutive footnotes without a blanklines are parsed correctly if (TryOpen(processor, true) == BlockState.Continue) { processor.Close(footnote); return(BlockState.Continue); } } } footnote.IsLastLineEmpty = false; if (processor.IsCodeIndent) { processor.GoToCodeIndent(); } return(BlockState.Continue); }
/// <summary> /// Continues a <typeparamref name="T"/> if not all its parts have been parsed. /// </summary> /// <param name="blockProcessor">The <see cref="BlockProcessor"/> for the <typeparamref name="T"/> to try continuing.</param> /// <param name="block">The <typeparamref name="T"/> to try continuing.</param> /// <returns> /// <see cref="BlockState.Continue"/> if the current line has code indent. /// <see cref="BlockState.Continue"/> if the <paramref name="block"/> is not the most recently opened multipart <see cref="Block"/>. /// <see cref="BlockState.Continue"/> if the current line is not a part divider line. /// <see cref="BlockState.BreakDiscard"/> if the <typeparamref name="T"/> is closed. /// <see cref="BlockState.ContinueDiscard"/> if a new part is opened. /// </returns> protected override BlockState TryContinueBlock(BlockProcessor blockProcessor, T block) { Stack <ContainerBlock> openMultipartBlocks = GetOrCreateOpenMultipartBlocks(blockProcessor); if (blockProcessor.IsCodeIndent || openMultipartBlocks.Peek() != block || // Should never be empty, at minimum, the block we're trying to continue should be in stack !IsPartDividerLine(blockProcessor)) { return(BlockState.Continue); } // If all parts have been parsed, close block int count = block.Count; if (count == _numParts) { // Remove from stack openMultipartBlocks.Pop(); // Update span end block.UpdateSpanEnd(blockProcessor.Line.End); return(BlockState.BreakDiscard); } // Close previous part so we can open next part as child of multipart block Block lastChild = block.LastChild; lastChild.UpdateSpanEnd(blockProcessor.Line.End); // Parts overlap, should not be a problem though. Important thing is that the spans allows us to locate parts. blockProcessor.Close(lastChild); // Create next part blockProcessor.NewBlocks.Push(_multipartBlockFactory.CreatePart(_partTypes[count], blockProcessor)); return(BlockState.ContinueDiscard); }
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(); }