Example #1
0
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var quote = (FooterBlock)block;

            // A footer
            // A Footer marker consists of 0-3 spaces of initial indent, plus (a) the characters ^^ together with a following space, or (b) a double character ^^ not followed by a space.
            var c      = processor.CurrentChar;
            var result = BlockState.Continue;

            if (c != quote.OpeningCharacter || processor.PeekChar(1) != c)
            {
                result = processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None;
            }
            else
            {
                processor.NextChar();     // Skip ^^ char (1st)
                c = processor.NextChar(); // Skip ^^ char (2nd)
                if (c.IsSpace())
                {
                    processor.NextChar(); // Skip following space
                }
                block.UpdateSpanEnd(processor.Line.End);
            }
            return(result);
        }
        /// <summary>
        /// Parses a line.
        /// </summary>
        internal virtual BlockState ParseLine(BlockProcessor blockProcessor, FlexiAlertBlock flexiAlertBlock)
        {
            if (blockProcessor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            if (blockProcessor.CurrentChar != _openingChar)
            {
                return(blockProcessor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None);
            }

            if (blockProcessor.PeekChar(1) == '[') // Avoid conflicting with images
            {
                return(BlockState.None);
            }

            if (flexiAlertBlock == null)
            {
                blockProcessor.NewBlocks.Push(_flexiAlertBlockFactory.Create(blockProcessor, this));
            }
            else
            {
                flexiAlertBlock.UpdateSpanEnd(blockProcessor.Line.End);
            }

            // Skip opening char and first whitespace char following it
            if (blockProcessor.NextChar().IsSpaceOrTab())
            {
                blockProcessor.NextChar();
            }

            return(BlockState.Continue);
        }
Example #3
0
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var quote = (QuoteBlock)block;

            // 5.1 Block quotes
            // A block quote marker consists of 0-3 spaces of initial indent, plus
            // (a) the character > together with a following space, or
            // (b) a single character > not followed by a space.
            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
            }


            block.UpdateSpanEnd(processor.Line.End);
            return(BlockState.Continue);
        }
Example #4
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var column        = processor.Column;
            var startPosition = processor.Start;

            // A footer
            // A Footer marker consists of 0-3 spaces of initial indent, plus (a) the characters ^^ together with a following space, or (b) a double character ^^ not followed by a space.
            var openingChar = processor.CurrentChar;

            if (processor.PeekChar(1) != openingChar)
            {
                return(BlockState.None);
            }
            processor.NextChar();         // Grab 2nd^
            var c = processor.NextChar(); // grab space

            if (c.IsSpaceOrTab())
            {
                processor.NextColumn();
            }
            processor.NewBlocks.Push(new FooterBlock(this)
            {
                Span             = new SourceSpan(startPosition, processor.Line.End),
                OpeningCharacter = openingChar,
                Column           = column,
                Line             = processor.LineIndex,
            });
            return(BlockState.Continue);
        }
Example #5
0
        private bool IsRestLineEmpty(BlockProcessor processor, int movedCharCount)
        {
            int column = processor.Column;

            while (movedCharCount-- > 0)
            {
                processor.NextChar();
            }
            while (processor.CurrentChar.IsSpaceOrTab())
            {
                processor.NextChar();
            }
            return(processor.CurrentChar == '\0');
        }
Example #6
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var line = processor.Line;

            if (!line.Match(MARKER))
            {
                return(BlockState.None);
            }

            var block = new TableOfContentsBlock(this);

            block.Span.Start = line.Start;
            block.Span.End   = line.End;
            processor.NewBlocks.Push(block);

            for (int i = 0; i < MARKER.Length; i++)
            {
                processor.NextChar();
            }

            return(BlockState.Break);
        }
Example #7
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);
        }
Example #8
0
        public override bool TryParse(BlockProcessor state, char pendingBulletType, out ListInfo result)
        {
            result = new ListInfo();

            var c = state.CurrentChar;

            var isRomanLow = CharHelper.IsRomanLetterLowerPartial(c);
            var isRomanUp  = !isRomanLow && CharHelper.IsRomanLetterUpperPartial(c);

            // We allow to parse roman only if we start on a new list or the pending list is already a roman list)
            if ((isRomanLow || isRomanUp) && (pendingBulletType == '\0' || pendingBulletType == 'i' || pendingBulletType == 'I'))
            {
                int startChar = state.Start;
                int endChar   = 0;
                // With a roman, we can have multiple characters
                // Note that we don't validate roman numbers
                while (isRomanLow ? CharHelper.IsRomanLetterLowerPartial(c) : CharHelper.IsRomanLetterUpperPartial(c))
                {
                    endChar = state.Start;
                    c       = state.NextChar();
                }

                result.OrderedStart        = CharHelper.RomanToArabic(state.Line.Text.Substring(startChar, endChar - startChar + 1)).ToString();
                result.BulletType          = isRomanLow ? 'i' : 'I';
                result.DefaultOrderedStart = isRomanLow ? "i" : "I";
            }
            else
            {
                // otherwise we expect a regular alpha lettered list with a single character.
                var isUpper = c.IsAlphaUpper();
                result.OrderedStart        = (Char.ToUpper(c) - 64).ToString();
                result.BulletType          = isUpper ? 'A' : 'a';
                result.DefaultOrderedStart = isUpper ? "A" : "a";
                state.NextChar();
            }

            // Finally we expect to always have a delimiter '.' or ')'
            char orderedDelimiter;

            if (!TryParseDelimiter(state, out orderedDelimiter))
            {
                return(false);
            }

            result.OrderedDelimiter = orderedDelimiter;
            return(true);
        }
        /// <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);
            }
        }
Example #10
0
        public static bool MatchStart(BlockProcessor processor, string startString, bool isCaseSensitive = true)
        {
            var c     = processor.CurrentChar;
            var index = 0;

            while (c != '\0' && index < startString.Length && CharEqual(c, startString[index], isCaseSensitive))
            {
                c = processor.NextChar();
                index++;
            }

            return(index == startString.Length);
        }
Example #11
0
        public override Boolean TryParse(BlockProcessor state, Char pendingBulletType, out ListInfo result)
        {
            result = new ListInfo();

            if (state.PeekChar(1) != ' ')
            {
                return(false);
            }

            state.NextChar();

            return(true);
        }
Example #12
0
 /// <summary>
 /// Utility method that tries to parse the delimiter coming after an ordered list start (e.g: the `)` after `1)`).
 /// </summary>
 /// <param name="state">The state.</param>
 /// <param name="orderedDelimiter">The ordered delimiter found if this method is successful.</param>
 /// <returns><c>true</c> if parsing was successful; <c>false</c> otherwise.</returns>
 protected bool TryParseDelimiter(BlockProcessor state, out char orderedDelimiter)
 {
     // Check if we have an ordered delimiter
     orderedDelimiter = state.CurrentChar;
     foreach (char delimiter in OrderedDelimiters)
     {
         if (delimiter == orderedDelimiter)
         {
             state.NextChar();
             return(true);
         }
     }
     return(false);
 }
Example #13
0
 /// <summary>
 /// Utility method that tries to parse the delimiter coming after an ordered list start (e.g: the `)` after `1)`).
 /// </summary>
 /// <param name="state">The state.</param>
 /// <param name="orderedDelimiter">The ordered delimiter found if this method is successful.</param>
 /// <returns><c>true</c> if parsing was successful; <c>false</c> otherwise.</returns>
 protected bool TryParseDelimiter(BlockProcessor state, out char orderedDelimiter)
 {
     // Check if we have an ordered delimiter
     orderedDelimiter = state.CurrentChar;
     for (int i = 0; i < OrderedDelimiters.Length; i++)
     {
         if (OrderedDelimiters[i] == orderedDelimiter)
         {
             state.NextChar();
             return true;
         }
     }
     return false;
 }
Example #14
0
        /// <summary>
        /// 截取非空字符串,直到遇到<paramref name="end"/>字符,并且忽略空白字符。
        /// </summary>
        /// <param name="processor">代码块分析器。</param>
        /// <param name="end">截止字符。</param>
        /// <returns>返回非空字符串。</returns>
        public static string Substring(this BlockProcessor processor, char end)
        {
            var start   = processor.Start;
            var current = processor.CurrentChar;
            var builder = new StringBuilder();

            while (current != '\0')
            {
                if (current == end)
                {
                    return(builder.ToString());
                }
                if (!current.IsWhiteSpaceOrZero())
                {
                    builder.Append(current);
                }
                current = processor.NextChar();
            }
            processor.Line.Start = start;
            return(null);
        }
        /// <summary>
        /// 尝试解析当前字符串。
        /// </summary>
        /// <param name="processor">代码块进程。</param>
        /// <returns>返回代码块状态。</returns>
        public override BlockState TryOpen(BlockProcessor processor)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var column         = processor.Column;
            var sourcePosition = processor.Start;

            var quoteChar = processor.CurrentChar;
            var c         = processor.NextChar();

            if (c.IsSpaceOrTab())
            {
                processor.NextColumn();
            }

            var rawNewBlock = new QuoteSectionNoteBlock(this)
            {
                Line      = processor.LineIndex,
                QuoteChar = quoteChar,
                Column    = column,
                Span      = new SourceSpan(sourcePosition, processor.Line.End),
            };

            TryParseFromLine(processor, rawNewBlock);
            processor.NewBlocks.Push(rawNewBlock);

            if (rawNewBlock.QuoteType == QuoteSectionNoteType.DFMVideo)
            {
                return(BlockState.BreakDiscard);
            }
            else
            {
                return(BlockState.Continue);
            }
        }
        /// <summary>
        /// Opens a <typeparamref name="TProxy"/> if a line begins with at least 3 fence characters.
        /// </summary>
        /// <param name="blockProcessor">The <see cref="BlockProcessor"/> for the document that contains a line with a fence character as its first character.</param>
        /// <returns>
        /// <see cref="BlockState.None"/> if the current line has code indent.
        /// <see cref="BlockState.None"/> if the current line does not contain an opening fence.
        /// <see cref="BlockState.ContinueDiscard"/> if the current line contains an opening fence and a <typeparamref name="TProxy"/> is opened.
        ///</returns>
        protected override BlockState TryOpenBlock(BlockProcessor blockProcessor)
        {
            if (blockProcessor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            // First line of a JSONBlock must begin with <opening char>{
            if (blockProcessor.PeekChar(1) != '{')
            {
                return(BlockState.None);
            }

            // Create block
            TProxy proxyJsonBlock = _jsonBlockFactory.CreateProxyJsonBlock(blockProcessor, this);

            blockProcessor.NewBlocks.Push(proxyJsonBlock);

            // Dispose of first char (JSON starts at the curly bracket)
            blockProcessor.NextChar();

            return(ParseLine(blockProcessor.Line, proxyJsonBlock));
        }
Example #17
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 #18
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);
        }
        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);
        }
Example #20
0
 public override bool TryParse(BlockProcessor state, char pendingBulletType, out ListInfo result)
 {
     result = new ListInfo(state.CurrentChar);
     state.NextChar();
     return(true);
 }
Example #21
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var column         = processor.Column;
            var sourcePosition = processor.Start;

            // 5.1 Block quotes
            // A block quote marker consists of 0-3 spaces of initial indent, plus
            // (a) the character > together with a following space, or
            // (b) a single character > not followed by a space.
            // (c) a single character > followed by a space and explanation mark
            // denoting the css class to apply to the block quote
            // i.e....
            // > !danger This is a dangerous quote
            // > !success This is a success quote

            var currentChar = processor.CurrentChar;
            var c           = processor.NextChar();

            if (c.IsSpaceOrTab())
            {
                c = processor.NextChar();
            }

            var cssClass = "";

            if (c == '!')
            {
                // Parse until we reach white space
                while (true)
                {
                    if (c.IsWhitespace())
                    {
                        break;
                    }
                    c = processor.NextChar();
                    if (!c.IsWhitespace())
                    {
                        cssClass += c;
                    }
                }
            }

            if (c.IsSpaceOrTab())
            {
                processor.NextColumn();
            }

            // Build quote
            var quoteBlock = new QuoteBlock(this)
            {
                QuoteChar = currentChar,
                Column    = column,
                Span      = new SourceSpan(sourcePosition, processor.Line.End),
            };

            // If we found a css class ensure it's allowed
            if (!string.IsNullOrEmpty(cssClass))
            {
                var validCss = cssClass.ToLower();
                if (_validCssClasses.Contains(validCss))
                {
                    quoteBlock.GetAttributes().AddClass(validCss);
                }
            }

            processor.NewBlocks.Push(quoteBlock);

            return(BlockState.Continue);
        }