Пример #1
0
        // 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);
            }
        }
Пример #2
0
        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);
        }
Пример #3
0
        /// <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);
        }
Пример #4
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);
        }
Пример #5
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();
        }