Beispiel #1
0
        /// <summary>
        /// Tries to continue matching a block already opened.
        /// </summary>
        /// <param name="processor">The parser processor.</param>
        /// <param name="block">The block already opened.</param>
        /// <returns>The result of the match. By default, don't expect any newline</returns>
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            // Determine if we have a closing fence.
            // It can start or end with either <c>---</c> or <c>...</c>
            var line = processor.Line;
            var c    = line.CurrentChar;

            if (processor.Column == 0 && (c == '-' || c == '.'))
            {
                int count = line.CountAndSkipChar(c);
                c = line.CurrentChar;

                // If we have a closing fence, close it and discard the current line
                // The line must contain only fence characters and optional following whitespace.
                if (count == 3 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
                {
                    block.UpdateSpanEnd(line.Start - 1);

                    // Don't keep the last line
                    return(BlockState.BreakDiscard);
                }
            }

            // Reset the indentation to the column before the indent
            processor.GoToColumn(processor.ColumnBeforeIndent);

            return(BlockState.Continue);
        }
Beispiel #2
0
        public override BlockState TryContinue(
            BlockProcessor processor,
            Block block)
        {
            var fence     = (IFencedBlock)block;
            var count     = fence.FencedCharCount;
            var matchChar = fence.FencedChar;
            var c         = processor.CurrentChar;

            // Match if we have a closing fence
            var line = processor.Line;

            while (c == matchChar)
            {
                c = line.NextChar();
                count--;
            }

            // If we have a closing fence, close it and discard the current line
            // The line must contain only fence opening character followed only by whitespaces.
            if (count <= 0 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
            {
                block.UpdateSpanEnd(line.Start - 1);

                // Don't keep the last line
                return(BlockState.BreakDiscard);
            }

            // Reset the indentation to the column before the indent
            processor.GoToColumn(processor.ColumnBeforeIndent);

            return(BlockState.Continue);
        }
Beispiel #3
0
        private BlockState TryOpen(BlockProcessor processor, bool isContinue)
        {
            // We expect footnote to appear only at document level and not indented more than a code indent block
            var currentContainer = processor.GetCurrentContainerOpened();

            if (processor.IsCodeIndent || (!isContinue && currentContainer.GetType() != typeof(MarkdownDocument)) || (isContinue && !(currentContainer is FootnoteGroup)))
            {
                return(BlockState.None);
            }

            var        saved = processor.Column;
            string     label;
            int        start = processor.Start;
            SourceSpan labelSpan;

            if (!LinkHelper.TryParseLabel(ref processor.Line, false, out label, out labelSpan) || !label.StartsWith("^") || processor.CurrentChar != ':')
            {
                processor.GoToColumn(saved);
                return(BlockState.None);
            }

            // Advance the column
            int deltaColumn = processor.Start - start;

            processor.Column = processor.Column + deltaColumn;

            processor.NextChar(); // Skip ':'

            var footnote = new Footnote(this)
            {
                Label     = label,
                LabelSpan = labelSpan,
            };

            // Maintain a list of all footnotes at document level
            var footnotes = processor.Document.GetData(DocumentKey) as FootnoteGroup;

            if (footnotes == null)
            {
                footnotes = new FootnoteGroup(this);
                processor.Document.Add(footnotes);
                processor.Document.SetData(DocumentKey, footnotes);
                processor.Document.ProcessInlinesEnd += Document_ProcessInlinesEnd;
            }
            footnotes.Add(footnote);

            var linkRef = new FootnoteLinkReferenceDefinition()
            {
                Footnote         = footnote,
                CreateLinkInline = CreateLinkToFootnote,
                Line             = processor.LineIndex,
                Span             = new SourceSpan(start, processor.Start - 2), // account for ]:
                LabelSpan        = labelSpan,
                Label            = label
            };

            processor.Document.SetLinkReferenceDefinition(footnote.Label, linkRef);
            processor.NewBlocks.Push(footnote);
            return(BlockState.Continue);
        }
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            var figure    = (Figure)block;
            var count     = figure.OpeningCharacterCount;
            var matchChar = figure.OpeningCharacter;
            var c         = processor.CurrentChar;

            var column = processor.Column;
            // Match if we have a closing fence
            var line          = processor.Line;
            var startPosition = line.Start;

            while (c == matchChar)
            {
                c = line.NextChar();
                count--;
            }

            // If we have a closing fence, close it and discard the current line
            // The line must contain only fence opening character followed only by whitespaces.
            if (count <= 0 && !processor.IsCodeIndent)
            {
                line.TrimStart();
                if (!line.IsEmpty)
                {
                    var caption = new FigureCaption(this)
                    {
                        Span   = new SourceSpan(line.Start, line.End),
                        Line   = processor.LineIndex,
                        Column = column + line.Start - startPosition,
                        IsOpen = false
                    };
                    caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition);
                    figure.Add(caption);
                }

                figure.Span.End = line.End;

                // Don't keep the last line
                return(BlockState.BreakDiscard);
            }

            // Reset the indentation to the column before the indent
            processor.GoToColumn(processor.ColumnBeforeIndent);

            figure.Span.End = line.End;

            return(BlockState.Continue);
        }
        /// <summary>
        /// 继续解析全部代码块。
        /// </summary>
        /// <param name="processor">代码块进程。</param>
        /// <param name="block">当前块实例对象。</param>
        /// <returns>返回读取的状态。</returns>
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var quote  = (QuoteSectionNoteBlock)block;
            var column = processor.Column;

            if (quote.QuoteType == QuoteSectionNoteType.DFMVideo)
            {
                return(BlockState.BreakDiscard);
            }

            var c = processor.CurrentChar;

            if (c != quote.QuoteChar)
            {
                return(processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None);
            }

            c = processor.NextChar(); // Skip opening char
            if (c.IsSpace())
            {
                processor.NextChar(); // Skip following space
            }

            // Check for New DFM block
            if (TryParseFromLine(processor, new QuoteSectionNoteBlock(this)))
            {
                // Meet note or section, close this block, new block will be open in the next steps
                processor.GoToColumn(column);
                return(BlockState.None);
            }
            else
            {
                block.UpdateSpanEnd(processor.Line.End);
                return(BlockState.Continue);
            }
        }
Beispiel #6
0
        /// <summary>
        /// Overrides the TryOpen for the heading block parser to ignore the need for spaces
        /// </summary>
        /// <returns>The open.</returns>
        /// <param name="processor">Processor.</param>
        public override BlockState TryOpen(BlockProcessor processor)
        {
            // If we are in a CodeIndent, early exit
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            // 4.2 ATX headings
            // An ATX heading consists of a string of characters, parsed as inline content,
            // between an opening sequence of 1–6 unescaped # characters and an optional
            // closing sequence of any number of unescaped # characters. The opening sequence
            // of # characters must be followed by a space or by the end of line. The optional
            // closing sequence of #s must be preceded by a space and may be followed by spaces
            // only. The opening # character may be indented 0-3 spaces. The raw contents of
            // the heading are stripped of leading and trailing spaces before being parsed as
            // inline content. The heading level is equal to the number of # characters in the
            // opening sequence.

            // We are not doing this ^^ we don't have the spaces... so we need to handle that adjusted logic here
            var column         = processor.Column;
            var line           = processor.Line;
            var sourcePosition = line.Start;
            var c            = line.CurrentChar;
            var matchingChar = c;

            int leadingCount = 0;

            // get how many of the headChar we have and limit to 6 (h6 is the last handled header)
            while (c == _headChar && leadingCount <= 6)
            {
                if (c != matchingChar)
                {
                    break;
                }
                c = line.NextChar();
                leadingCount++;
            }

            // A space is NOT required after leading #
            if (leadingCount > 0 && leadingCount <= 6)
            {
                // Move to the content
                var headingBlock = new HeadingBlock(this)
                {
                    HeaderChar = matchingChar,
                    Level      = leadingCount,
                    Column     = column,
                    Span       = { Start = sourcePosition }
                };
                processor.NewBlocks.Push(headingBlock);
                processor.GoToColumn(column + leadingCount); // no +1 - skip the space

                // Gives a chance to parse attributes
                if (TryParseAttributes != null)
                {
                    TryParseAttributes(processor, ref processor.Line, headingBlock);
                }

                // The optional closing sequence of #s must not be preceded by a space and may be followed by spaces only.
                int endState         = 0;
                int countClosingTags = 0;
                for (int i = processor.Line.End; i >= processor.Line.Start; i--)  // Go up to Start in order to match the no space after the first ###
                {
                    c = processor.Line.Text[i];
                    if (endState == 0)
                    {
                        if (c.IsSpaceOrTab())
                        {
                            continue;
                        }
                        endState = 1;
                    }
                    if (endState == 1)
                    {
                        if (c == matchingChar)
                        {
                            countClosingTags++;
                            continue;
                        }

                        if (countClosingTags > 0)
                        {
                            if (c.IsSpaceOrTab())
                            {
                                processor.Line.End = i - 1;
                            }
                            break;
                        }
                        else
                        {
                            break;
                        }
                    }
                }

                // Setup the source end position of this element
                headingBlock.Span.End = processor.Line.End;

                // We expect a single line, so don't continue
                return(BlockState.Break);
            }

            // Else we don't have an header
            return(BlockState.None);
        }
Beispiel #7
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);
        }
Beispiel #8
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);
        }
Beispiel #9
0
 public static void ResetLineIndent(BlockProcessor processor)
 {
     processor.GoToColumn(processor.ColumnBeforeIndent);
 }
        private bool TryParseFromLine(BlockProcessor processor, QuoteSectionNoteBlock block)
        {
            int originalColumn = processor.Column;

            block.QuoteType = QuoteSectionNoteType.MarkdownQuote;

            if (processor.CurrentChar != '[')
            {
                return(false);
            }

            var stringBuilder = StringBuilderCache.Local();
            var c             = processor.CurrentChar;

            var hasEscape = false;

            while (c != '\0' && (c != ']' || hasEscape))
            {
                if (c == '\\' && !hasEscape)
                {
                    hasEscape = true;
                }
                else
                {
                    stringBuilder.Append(c);
                    hasEscape = false;
                }
                c = processor.NextChar();
            }

            stringBuilder.Append(c);
            var infoString = stringBuilder.ToString().Trim();

            if (c == '\0')
            {
                processor.GoToColumn(originalColumn);
                return(false);
            }

            if (c == ']')
            {
                // "> [!NOTE] content" is invalid, go to end to see it.
                processor.NextChar();
                while (processor.CurrentChar.IsSpaceOrTab())
                {
                    processor.NextChar();
                }
                var isNoteVideoDiv = infoString.StartsWith("[!div", StringComparison.OrdinalIgnoreCase) ||
                                     infoString.StartsWith("[!Video", StringComparison.OrdinalIgnoreCase) ||
                                     SectionNoteType.IsNoteType(infoString);
                if (processor.CurrentChar != '\0' && isNoteVideoDiv)
                {
                    processor.GoToColumn(originalColumn);
                    return(false);
                }
            }

            if (SectionNoteType.IsNoteType(infoString))
            {
                block.QuoteType      = QuoteSectionNoteType.DFMNote;
                block.NoteTypeString = infoString.Substring(2, infoString.Length - 3).ToLowerInvariant();
                return(true);
            }

            if (infoString.StartsWith("[!div", StringComparison.OrdinalIgnoreCase))
            {
                block.QuoteType = QuoteSectionNoteType.DFMSection;
                string attribute = infoString.Substring(5, infoString.Length - 6).Trim();
                if (attribute.Length >= 2 && attribute.First() == '`' && attribute.Last() == '`')
                {
                    block.SectionAttributeString = attribute.Substring(1, attribute.Length - 2).Trim();
                }
                if (attribute.Length >= 1 && attribute.First() != '`' && attribute.Last() != '`')
                {
                    block.SectionAttributeString = attribute;
                }
                return(true);
            }

            if (infoString.StartsWith("[!Video", StringComparison.OrdinalIgnoreCase))
            {
                string link = infoString.Substring(7, infoString.Length - 8);
                if (link.StartsWith(" http://") || link.StartsWith(" https://"))
                {
                    block.QuoteType = QuoteSectionNoteType.DFMVideo;
                    block.VideoLink = link.Trim();
                    return(true);
                }
            }

            processor.GoToColumn(originalColumn);
            return(false);
        }
 // Subtracts opening fence indent from current line indent
 internal virtual void UpdateLineStart(BlockProcessor blockProcessor, TProxy block)
 {
     blockProcessor.GoToColumn(blockProcessor.ColumnBeforeIndent + Math.Min(block.OpeningFenceIndent, blockProcessor.Indent));
 }