Exemplo n.º 1
0
        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.
            var column         = processor.Column;
            var line           = processor.Line;
            var sourcePosition = line.Start;
            var c            = line.CurrentChar;
            var matchingChar = c;

            int leadingCount = 0;

            while (c != '\0' && leadingCount <= 6)
            {
                if (c != matchingChar)
                {
                    break;
                }
                c = line.NextChar();
                leadingCount++;
            }

            // A space is required after leading #
            if (leadingCount > 0 && leadingCount <= 6 && (c.IsSpace() || c == '\0'))
            {
                // 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 + 1);

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

                // The optional closing sequence of #s must 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 - 1; i--)  // Go up to Start - 1 in order to match the space after the first ###
                {
                    c = processor.Line.Text[i];
                    if (endState == 0)
                    {
                        if (c.IsSpace()) // TODO: Not clear if it is a space or space+tab in the specs
                        {
                            continue;
                        }
                        endState = 1;
                    }
                    if (endState == 1)
                    {
                        if (c == matchingChar)
                        {
                            countClosingTags++;
                            continue;
                        }

                        if (countClosingTags > 0)
                        {
                            if (c.IsSpace())
                            {
                                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);
        }
Exemplo n.º 2
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var startPosition = processor.Start;
            var line          = processor.Line;

            // 4.1 Thematic breaks
            // A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching -, _, or * characters, each followed optionally by any number of spaces
            int  breakCharCount          = 0;
            var  breakChar               = line.CurrentChar;
            bool hasSpacesSinceLastMatch = false;
            bool hasInnerSpaces          = false;
            var  c = breakChar;

            while (c != '\0')
            {
                if (c == breakChar)
                {
                    if (hasSpacesSinceLastMatch)
                    {
                        hasInnerSpaces = true;
                    }

                    breakCharCount++;
                }
                else if (c.IsSpaceOrTab())
                {
                    hasSpacesSinceLastMatch = true;
                }
                else
                {
                    return(BlockState.None);
                }

                c = line.NextChar();
            }

            // If it as less than 3 chars or it is a setex heading and we are already in a paragraph, let the paragraph handle it
            var previousParagraph = processor.CurrentBlock as ParagraphBlock;

            var isSetexHeading = previousParagraph != null && breakChar == '-' && !hasInnerSpaces;

            if (isSetexHeading)
            {
                var parent = previousParagraph !.Parent !;
                if (previousParagraph.Column != processor.Column && (parent is QuoteBlock or ListItemBlock))
                {
                    isSetexHeading = false;
                }
            }

            if (breakCharCount < 3 || isSetexHeading)
            {
                return(BlockState.None);
            }

            // Push a new block
            processor.NewBlocks.Push(new ThematicBreakBlock(this)
            {
                Column            = processor.Column,
                Span              = new SourceSpan(startPosition, line.End),
                ThematicChar      = breakChar,
                ThematicCharCount = breakCharCount,
                // TODO: should we separate whitespace before/after?
                //BeforeWhitespace = beforeWhitespace,
                //AfterWhitespace = processor.PopBeforeWhitespace(processor.CurrentLineStartPosition),
                LinesBefore = processor.UseLinesBefore(),
                Content     = new StringSlice(line.Text, processor.TriviaStart, line.End, line.NewLine), //include whitespace for now
                NewLine     = processor.Line.NewLine,
            });
            return(BlockState.BreakDiscard);
        }
Exemplo n.º 3
0
 public override bool CanInterrupt(BlockProcessor processor, Block block)
 {
     return(!(block is ParagraphBlock));
 }
Exemplo n.º 4
0
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            var htmlBlock = (HtmlBlock)block;

            return(MatchEnd(processor, htmlBlock));
        }
Exemplo n.º 5
0
        private BlockState TryParseTagType16(BlockProcessor state, StringSlice line, int startColumn, int startPosition)
        {
            char c;

            c = line.CurrentChar;
            if (c == '!')
            {
                c = line.NextChar();
                if (c == '-' && line.PeekChar(1) == '-')
                {
                    return(CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn, startPosition)); // group 2
                }
                if (c.IsAlphaUpper())
                {
                    return(CreateHtmlBlock(state, HtmlBlockType.DocumentType, startColumn, startPosition)); // group 4
                }
                if (c == '[' && line.Match("CDATA[", 1))
                {
                    return(CreateHtmlBlock(state, HtmlBlockType.CData, startColumn, startPosition)); // group 5
                }

                return(BlockState.None);
            }

            if (c == '?')
            {
                return(CreateHtmlBlock(state, HtmlBlockType.ProcessingInstruction, startColumn, startPosition)); // group 3
            }

            var hasLeadingClose = c == '/';

            if (hasLeadingClose)
            {
                c = line.NextChar();
            }

            var tag   = new char[10];
            var count = 0;

            for (; count < tag.Length; count++)
            {
                if (!c.IsAlphaNumeric())
                {
                    break;
                }
                tag[count] = Char.ToLowerInvariant(c);
                c          = line.NextChar();
            }

            if (
                !(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar(1) == '>') || c.IsWhitespace() ||
                  c == '\0'))
            {
                return(BlockState.None);
            }

            if (count == 0)
            {
                return(BlockState.None);
            }

            var tagName  = new string(tag, 0, count);
            var tagIndex = Array.BinarySearch(HtmlTags, tagName, StringComparer.Ordinal);

            if (tagIndex < 0)
            {
                return(BlockState.None);
            }

            // Cannot start with </script </pre or </style
            if ((tagIndex == 45 || tagIndex == 46 || tagIndex == 49))
            {
                if (c == '/' || hasLeadingClose)
                {
                    return(BlockState.None);
                }
                return(CreateHtmlBlock(state, HtmlBlockType.ScriptPreOrStyle, startColumn, startPosition));
            }

            return(CreateHtmlBlock(state, HtmlBlockType.InterruptingBlock, startColumn, startPosition));
        }
Exemplo n.º 6
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var startPosition = processor.Start;

            var line = processor.Line;

            // 4.1 Thematic breaks
            // A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching -, _, or * characters, each followed optionally by any number of spaces
            int  breakCharCount          = 0;
            var  breakChar               = line.CurrentChar;
            bool hasSpacesSinceLastMatch = false;
            bool hasInnerSpaces          = false;
            var  c = breakChar;

            while (c != '\0')
            {
                if (c == breakChar)
                {
                    if (hasSpacesSinceLastMatch)
                    {
                        hasInnerSpaces = true;
                    }

                    breakCharCount++;
                }
                else if (c.IsSpaceOrTab())
                {
                    hasSpacesSinceLastMatch = true;
                }
                else
                {
                    return(BlockState.None);
                }

                c = line.NextChar();
            }

            // If it as less than 3 chars or it is a setex heading and we are already in a paragraph, let the paragraph handle it
            var previousParagraph = processor.CurrentBlock as ParagraphBlock;

            var isSetexHeading = previousParagraph != null && breakChar == '-' && !hasInnerSpaces;

            if (isSetexHeading)
            {
                var parent = previousParagraph.Parent;
                if ((parent is QuoteBlock && processor.CurrentLineStartPosition > parent.Span.End) || (parent is ListItemBlock && previousParagraph.Column != processor.Column))
                {
                    isSetexHeading = false;
                }
            }

            if (breakCharCount < 3 || isSetexHeading)
            {
                return(BlockState.None);
            }

            // Push a new block
            processor.NewBlocks.Push(new ThematicBreakBlock(this)
            {
                Column            = processor.Column,
                Span              = new SourceSpan(startPosition, line.End),
                ThematicChar      = breakChar,
                ThematicCharCount = breakCharCount
            });
            return(BlockState.BreakDiscard);
        }
Exemplo n.º 7
0
        private BlockState MatchEnd(BlockProcessor state, HtmlBlock htmlBlock)
        {
            state.GoToColumn(state.ColumnBeforeIndent);

            // Early exit if it is not starting by an HTML tag
            var line   = state.Line;
            var result = BlockState.Continue;
            int index;

            switch (htmlBlock.Type)
            {
            case HtmlBlockType.Comment:
                index = line.IndexOf(EndOfComment);
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + EndOfComment.Length);
                    result = BlockState.Break;
                }
                break;

            case HtmlBlockType.CData:
                index = line.IndexOf(EndOfCDATA);
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + EndOfCDATA.Length);
                    result = BlockState.Break;
                }
                break;

            case HtmlBlockType.ProcessingInstruction:
                index = line.IndexOf(EndOfProcessingInstruction);
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + EndOfProcessingInstruction.Length);
                    result = BlockState.Break;
                }
                break;

            case HtmlBlockType.DocumentType:
                index = line.IndexOf('>');
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + 1);
                    result = BlockState.Break;
                }
                break;

            case HtmlBlockType.ScriptPreOrStyle:
                index = line.IndexOf("</script>", 0, true);
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + "</script>".Length);
                    result = BlockState.Break;
                }
                else
                {
                    index = line.IndexOf("</pre>", 0, true);
                    if (index >= 0)
                    {
                        htmlBlock.UpdateSpanEnd(index + "</pre>".Length);
                        result = BlockState.Break;
                    }
                    else
                    {
                        index = line.IndexOf("</style>", 0, true);
                        if (index >= 0)
                        {
                            htmlBlock.UpdateSpanEnd(index + "</style>".Length);
                            result = BlockState.Break;
                        }
                    }
                }
                break;

            case HtmlBlockType.InterruptingBlock:
                if (state.IsBlankLine)
                {
                    result = BlockState.BreakDiscard;
                }
                break;

            case HtmlBlockType.NonInterruptingBlock:
                if (state.IsBlankLine)
                {
                    result = BlockState.BreakDiscard;
                }
                break;
            }

            // Update only if we don't have a break discard
            if (result != BlockState.BreakDiscard)
            {
                htmlBlock.Span.End = line.End;
            }

            return(result);
        }
Exemplo n.º 8
0
        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(configurable) 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.
            var column         = processor.Column;
            var line           = processor.Line;
            var sourcePosition = line.Start;
            var c            = line.CurrentChar;
            var matchingChar = c;

            Debug.Assert(MaxLeadingCount > 0);
            int leadingCount = 0;

            while (c != '\0' && leadingCount <= MaxLeadingCount)
            {
                if (c != matchingChar)
                {
                    break;
                }
                c = processor.NextChar();
                leadingCount++;
            }

            // A space is required after leading #
            if (leadingCount > 0 && leadingCount <= MaxLeadingCount && (c.IsSpaceOrTab() || c == '\0'))
            {
                StringSlice trivia = StringSlice.Empty;
                if (processor.TrackTrivia && c.IsSpaceOrTab())
                {
                    trivia = new StringSlice(processor.Line.Text, processor.Start, processor.Start);
                    processor.NextChar();
                }
                // Move to the content
                var headingBlock = new HeadingBlock(this)
                {
                    HeaderChar = matchingChar,
                    Level      = leadingCount,
                    Column     = column,
                    Span       = { Start = sourcePosition },
                };

                if (processor.TrackTrivia)
                {
                    headingBlock.TriviaAfterAtxHeaderChar = trivia;
                    headingBlock.TriviaBefore             = processor.UseTrivia(sourcePosition - 1);
                    headingBlock.LinesBefore = processor.UseLinesBefore();
                    headingBlock.NewLine     = processor.Line.NewLine;
                }
                else
                {
                    processor.GoToColumn(column + leadingCount + 1);
                }

                processor.NewBlocks.Push(headingBlock);

                // Gives a chance to parse attributes
                TryParseAttributes?.Invoke(processor, ref processor.Line, headingBlock);

                // The optional closing sequence of #s must be preceded by a space and may be followed by spaces only.
                int endState         = 0;
                int countClosingTags = 0;
                int sourceEnd        = processor.Line.End;
                for (int i = processor.Line.End; i >= processor.Line.Start - 1; i--)  // Go up to Start - 1 in order to match the 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;

                if (processor.TrackTrivia)
                {
                    var wsa = new StringSlice(processor.Line.Text, processor.Line.End + 1, sourceEnd);
                    headingBlock.TriviaAfter = wsa;
                    if (wsa.Overlaps(headingBlock.TriviaAfterAtxHeaderChar))
                    {
                        // prevent double whitespace allocation in case of closing # i.e. "# #"
                        headingBlock.TriviaAfterAtxHeaderChar = StringSlice.Empty;
                    }
                }

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

            // Else we don't have an header
            processor.Line.Start = sourcePosition;
            processor.Column     = column;
            return(BlockState.None);
        }
Exemplo n.º 9
0
        private BlockState TryParseListItem(BlockProcessor state, Block block)
        {
            var currentListItem = block as ListItemBlock;
            var currentParent   = block as ListBlock ?? (ListBlock)currentListItem?.Parent;

            // We can early exit if we have a code indent and we are either (1) not in a ListItem, (2) preceded by a blank line, (3) in an unordered list
            if (state.IsCodeIndent && (currentListItem is null || currentListItem.LastChild is BlankLineBlock || !currentParent.IsOrdered))
            {
                return(BlockState.None);
            }

            var initColumnBeforeIndent = state.ColumnBeforeIndent;
            var initColumn             = state.Column;
            var sourcePosition         = state.Start;
            var sourceEndPosition      = state.Line.End;

            var c          = state.CurrentChar;
            var itemParser = mapItemParsers[c];

            if (itemParser == null)
            {
                return(BlockState.None);
            }

            // Try to parse the list item
            if (!itemParser.TryParse(state, currentParent?.BulletType ?? '\0', out ListInfo listInfo))
            {
                // Reset to an a start position
                state.GoToColumn(initColumn);
                return(BlockState.None);
            }

            bool isOrdered = itemParser is OrderedListItemParser;

            // Gets the current character after a successful parsing of the list information
            c = state.CurrentChar;

            // Item starting with a blank line
            int columnWidth;

            // Do we have a blank line right after the bullet?
            if (c == '\0')
            {
                // Use a negative number to store the number of expected chars
                columnWidth = -(state.Column - initColumnBeforeIndent + 1);
            }
            else
            {
                if (!c.IsSpaceOrTab())
                {
                    state.GoToColumn(initColumn);
                    return(BlockState.None);
                }

                // Parse the following indent
                state.RestartIndent();
                var columnBeforeIndent = state.Column;
                state.ParseIndent();

                // We expect at most 4 columns after
                // If we have more, we reset the position
                if (state.Indent > 4)
                {
                    state.GoToColumn(columnBeforeIndent + 1);
                }

                // Number of spaces required for the following content to be part of this list item
                // If the list item starts with a blank line, the number of spaces
                // following the list marker doesn't change the required indentation
                columnWidth = (state.IsBlankLine ? columnBeforeIndent : state.Column) - initColumnBeforeIndent;
            }

            // Starts/continue the list unless:
            // - an empty list item follows a paragraph
            // - an ordered list is not starting by '1'
            if ((block ?? state.LastBlock) is ParagraphBlock previousParagraph)
            {
                if (state.IsBlankLine ||
                    state.IsOpen(previousParagraph) && listInfo.BulletType == '1' && listInfo.OrderedStart != "1")
                {
                    state.GoToColumn(initColumn);
                    return(BlockState.None);
                }
            }

            int.TryParse(listInfo.OrderedStart, out int order);
            var newListItem = new ListItemBlock(this)
            {
                Column      = initColumn,
                ColumnWidth = columnWidth,
                Order       = order,
                Span        = new SourceSpan(sourcePosition, sourceEndPosition)
            };

            state.NewBlocks.Push(newListItem);

            if (currentParent != null)
            {
                // If we have a new list item, close the previous one
                if (currentListItem != null)
                {
                    state.Close(currentListItem);
                }

                // Reset the list if it is a new list or a new type of bullet
                if (currentParent.IsOrdered != isOrdered ||
                    currentParent.OrderedDelimiter != listInfo.OrderedDelimiter ||
                    currentParent.BulletType != listInfo.BulletType)
                {
                    state.Close(currentParent);
                    currentParent = null;
                }
            }

            if (currentParent == null)
            {
                var newList = new ListBlock(this)
                {
                    Column              = initColumn,
                    Span                = new SourceSpan(sourcePosition, sourceEndPosition),
                    IsOrdered           = isOrdered,
                    BulletType          = listInfo.BulletType,
                    OrderedDelimiter    = listInfo.OrderedDelimiter,
                    DefaultOrderedStart = listInfo.DefaultOrderedStart,
                    OrderedStart        = listInfo.OrderedStart,
                };
                state.NewBlocks.Push(newList);
            }

            return(BlockState.Continue);
        }
Exemplo n.º 10
0
        private BlockState TryContinueListItem(BlockProcessor state, ListItemBlock listItem)
        {
            var list = (ListBlock)listItem.Parent;

            // Allow all blanks lines if the last block is a fenced code block
            // Allow 1 blank line inside a list
            // If > 1 blank line, terminate this list
            var isBlankLine = state.IsBlankLine;

            var isCurrentBlockBreakable = state.CurrentBlock != null && state.CurrentBlock.IsBreakable;

            if (isBlankLine)
            {
                if (isCurrentBlockBreakable)
                {
                    if (!(state.NextContinue is ListBlock))
                    {
                        list.CountAllBlankLines++;
                        listItem.Add(new BlankLineBlock());
                    }
                    list.CountBlankLinesReset++;
                }

                if (list.CountBlankLinesReset == 1 && listItem.ColumnWidth < 0)
                {
                    state.Close(listItem);

                    // Leave the list open
                    list.IsOpen = true;
                    return(BlockState.Continue);
                }

                // Update list-item source end position
                listItem.UpdateSpanEnd(state.Line.End);

                return(BlockState.Continue);
            }

            list.CountBlankLinesReset = 0;

            int columnWidth = listItem.ColumnWidth;

            if (columnWidth < 0)
            {
                columnWidth = -columnWidth;
            }

            if (state.Indent >= columnWidth)
            {
                if (state.Indent > columnWidth && state.IsCodeIndent)
                {
                    state.GoToColumn(state.ColumnBeforeIndent + columnWidth);
                }

                // Update list-item source end position
                listItem.UpdateSpanEnd(state.Line.End);

                return(BlockState.Continue);
            }

            return(BlockState.None);
        }
Exemplo n.º 11
0
        public override bool Close(BlockProcessor processor, Block blockToClose)
        {
            var listBlock = blockToClose as ListBlock;

            // Process only if we have blank lines
            if (listBlock == null || listBlock.CountAllBlankLines <= 0)
            {
                return(true);
            }

            // TODO: This code is UGLY and WAY TOO LONG, simplify!
            bool isLastListItem = true;

            for (int listIndex = listBlock.Count - 1; listIndex >= 0; listIndex--)
            {
                var  block         = listBlock[listIndex];
                var  listItem      = (ListItemBlock)block;
                bool isLastElement = true;
                for (int i = listItem.Count - 1; i >= 0; i--)
                {
                    var item = listItem[i];
                    if (item is BlankLineBlock)
                    {
                        if ((isLastElement && listIndex < listBlock.Count - 1) || (listItem.Count > 2 && (i > 0 && i < (listItem.Count - 1))))
                        {
                            listBlock.IsLoose = true;
                        }

                        if (isLastElement && isLastListItem)
                        {
                            // Inform the outer list that we have a blank line
                            var parentListItemBlock = listBlock.Parent as ListItemBlock;
                            if (parentListItemBlock != null)
                            {
                                var parentList = (ListBlock)parentListItemBlock.Parent;

                                parentList.CountAllBlankLines++;
                                parentListItemBlock.Add(new BlankLineBlock());
                            }
                        }

                        listItem.RemoveAt(i);

                        // If we have remove all blank lines, we can exit
                        listBlock.CountAllBlankLines--;
                        if (listBlock.CountAllBlankLines == 0)
                        {
                            break;
                        }
                    }
                    isLastElement = false;
                }
                isLastListItem = false;
            }

            //// Update end-position for the list
            //if (listBlock.Count > 0)
            //{
            //    listBlock.Span.End = listBlock[listBlock.Count - 1].Span.End;
            //}

            return(true);
        }
Exemplo n.º 12
0
        private BlockState TryParseSetexHeading(BlockProcessor state, Block block)
        {
            var  paragraph      = (ParagraphBlock)block;
            var  headingChar    = (char)0;
            bool checkForSpaces = false;
            var  line           = state.Line;
            var  c = line.CurrentChar;

            while (c != '\0')
            {
                if (headingChar == 0)
                {
                    if (c == '=' || c == '-')
                    {
                        headingChar = c;
                        continue;
                    }
                    break;
                }

                if (checkForSpaces)
                {
                    if (!c.IsSpaceOrTab())
                    {
                        headingChar = (char)0;
                        break;
                    }
                }
                else if (c != headingChar)
                {
                    if (c.IsSpaceOrTab())
                    {
                        checkForSpaces = true;
                    }
                    else
                    {
                        headingChar = (char)0;
                        break;
                    }
                }
                c = line.NextChar();
            }

            if (headingChar != 0)
            {
                // If we matched a LinkReferenceDefinition before matching the heading, and the remaining
                // lines are empty, we can early exit and remove the paragraph
                if (!(TryMatchLinkReferenceDefinition(ref paragraph.Lines, state) && paragraph.Lines.Count == 0))
                {
                    // We dicard the paragraph that will be transformed to a heading
                    state.Discard(paragraph);

                    var level = headingChar == '=' ? 1 : 2;

                    var heading = new HeadingBlock(this)
                    {
                        Column = paragraph.Column,
                        Span   = new SourceSpan(paragraph.Span.Start, line.Start),
                        Level  = level,
                        Lines  = paragraph.Lines,
                    };
                    heading.Lines.Trim();

                    // Remove the paragraph as a pending block
                    state.NewBlocks.Push(heading);

                    return(BlockState.BreakDiscard);
                }
            }

            block.UpdateSpanEnd(state.Line.End);

            return(BlockState.Continue);
        }
Exemplo n.º 13
0
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var quote          = (QuoteBlock)block;
            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.
            var c = processor.CurrentChar;

            if (c != quote.QuoteChar)
            {
                if (processor.IsBlankLine)
                {
                    return(BlockState.BreakDiscard);
                }
                else
                {
                    if (processor.TrackTrivia)
                    {
                        quote.QuoteLines.Add(new QuoteBlockLine
                        {
                            QuoteChar = false,
                            NewLine   = processor.Line.NewLine,
                        });
                    }
                    return(BlockState.None);
                }
            }

            bool hasSpaceAfterQuoteChar = false;

            c = processor.NextChar(); // Skip quote marker char
            if (c == ' ')
            {
                processor.NextColumn();
                hasSpaceAfterQuoteChar         = true;
                processor.SkipFirstUnwindSpace = true;
            }
            else if (c == '\t')
            {
                processor.NextColumn();
            }

            if (processor.TrackTrivia)
            {
                var         triviaSpaceBefore = processor.UseTrivia(sourcePosition - 1);
                StringSlice triviaAfter       = StringSlice.Empty;
                bool        wasEmptyLine      = false;
                if (processor.Line.IsEmptyOrWhitespace())
                {
                    processor.TriviaStart = processor.Start;
                    triviaAfter           = processor.UseTrivia(processor.Line.End);
                    wasEmptyLine          = true;
                }
                quote.QuoteLines.Add(new QuoteBlockLine
                {
                    QuoteChar = true,
                    HasSpaceAfterQuoteChar = hasSpaceAfterQuoteChar,
                    TriviaBefore           = triviaSpaceBefore,
                    TriviaAfter            = triviaAfter,
                    NewLine = processor.Line.NewLine,
                });

                if (!wasEmptyLine)
                {
                    processor.TriviaStart = processor.Start;
                }
            }

            block.UpdateSpanEnd(processor.Line.End);
            return(BlockState.Continue);
        }
Exemplo n.º 14
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            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.
            var quoteChar = processor.CurrentChar;
            var column    = processor.Column;
            var c         = processor.NextChar();

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

            if (processor.TrackTrivia)
            {
                quoteBlock.LinesBefore = processor.UseLinesBefore();
            }

            bool hasSpaceAfterQuoteChar = false;

            if (c == ' ')
            {
                processor.NextColumn();
                hasSpaceAfterQuoteChar         = true;
                processor.SkipFirstUnwindSpace = true;
            }
            else if (c == '\t')
            {
                processor.NextColumn();
            }

            if (processor.TrackTrivia)
            {
                var         triviaBefore = processor.UseTrivia(sourcePosition - 1);
                StringSlice triviaAfter  = StringSlice.Empty;
                bool        wasEmptyLine = false;
                if (processor.Line.IsEmptyOrWhitespace())
                {
                    processor.TriviaStart = processor.Start;
                    triviaAfter           = processor.UseTrivia(processor.Line.End);
                    wasEmptyLine          = true;
                }

                if (!wasEmptyLine)
                {
                    processor.TriviaStart = processor.Start;
                }

                quoteBlock.QuoteLines.Add(new QuoteBlockLine
                {
                    TriviaBefore           = triviaBefore,
                    TriviaAfter            = triviaAfter,
                    QuoteChar              = true,
                    HasSpaceAfterQuoteChar = hasSpaceAfterQuoteChar,
                    NewLine = processor.Line.NewLine,
                });
            }

            processor.NewBlocks.Push(quoteBlock);
            return(BlockState.Continue);
        }
Exemplo n.º 15
0
        private BlockState TryParseListItem(BlockProcessor state, Block block)
        {
            // If we have a code indent and we are not in a ListItem, early exit
            if (!(block is ListItemBlock) && state.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var currentListItem = block as ListItemBlock;
            var currentParent   = block as ListBlock ?? (ListBlock)currentListItem?.Parent;

            var initColumnBeforeIndent = state.ColumnBeforeIndent;
            var initColumn             = state.Column;
            var sourcePosition         = state.Start;
            var sourceEndPosition      = state.Line.End;

            var  c          = state.CurrentChar;
            var  itemParser = mapItemParsers[c];
            bool isOrdered  = itemParser is OrderedListItemParser;

            if (itemParser == null)
            {
                return(BlockState.None);
            }

            // Try to parse the list item
            ListInfo listInfo;

            if (!itemParser.TryParse(state, currentParent?.BulletType ?? '\0', out listInfo))
            {
                // Reset to an a start position
                state.GoToColumn(initColumn);
                return(BlockState.None);
            }

            // Gets the current character after a succesfull parsing of the list information
            c = state.CurrentChar;

            // Item starting with a blank line
            int columnWidth;

            // Do we have a blank line right after the bullet?
            if (c == '\0')
            {
                // Use a negative number to store the number of expected chars
                columnWidth = -(state.Column - initColumnBeforeIndent + 1);
            }
            else
            {
                if (!c.IsSpaceOrTab())
                {
                    state.GoToColumn(initColumn);
                    return(BlockState.None);
                }

                // We require at least one char
                state.NextColumn();

                // Parse the following indent
                state.RestartIndent();
                var columnBeforeIndent = state.Column;
                state.ParseIndent();

                if (state.IsCodeIndent)
                {
                    state.GoToColumn(columnBeforeIndent);
                }

                // Number of spaces required for the following content to be part of this list item
                // If the list item starts with a blank line, the number of spaces
                // following the list marker doesn't change the required indentation
                columnWidth = (state.IsBlankLine ? columnBeforeIndent : state.Column) - initColumnBeforeIndent;
            }

            var newListItem = new ListItemBlock(this)
            {
                Column      = initColumn,
                ColumnWidth = columnWidth,
                Span        = new SourceSpan(sourcePosition, sourceEndPosition)
            };

            state.NewBlocks.Push(newListItem);

            if (currentParent != null)
            {
                // If we have a new list item, close the previous one
                if (currentListItem != null)
                {
                    state.Close(currentListItem);
                }

                // Reset the list if it is a new list or a new type of bullet
                if (currentParent.IsOrdered != isOrdered ||
                    currentParent.OrderedDelimiter != listInfo.OrderedDelimiter ||
                    currentParent.BulletType != listInfo.BulletType)
                {
                    state.Close(currentParent);
                    currentParent = null;
                }
            }

            if (currentParent == null)
            {
                var newList = new ListBlock(this)
                {
                    Column              = initColumn,
                    Span                = new SourceSpan(sourcePosition, sourceEndPosition),
                    IsOrdered           = isOrdered,
                    BulletType          = listInfo.BulletType,
                    OrderedDelimiter    = listInfo.OrderedDelimiter,
                    DefaultOrderedStart = listInfo.DefaultOrderedStart,
                    OrderedStart        = listInfo.OrderedStart,
                };
                state.NewBlocks.Push(newList);
            }

            return(BlockState.Continue);
        }
Exemplo n.º 16
0
        private BlockState MatchEnd(BlockProcessor state, HtmlBlock htmlBlock)
        {
            state.GoToColumn(state.ColumnBeforeIndent);

            // Early exit if it is not starting by an HTML tag
            var line   = state.Line;
            var c      = line.CurrentChar;
            var result = BlockState.Continue;
            int endof;

            switch (htmlBlock.Type)
            {
            case HtmlBlockType.Comment:
                if (line.Search("-->", out endof))
                {
                    htmlBlock.Span.End = endof - 1;
                    result             = BlockState.Break;
                }
                break;

            case HtmlBlockType.CData:
                if (line.Search("]]>", out endof))
                {
                    htmlBlock.Span.End = endof - 1;
                    result             = BlockState.Break;
                }
                break;

            case HtmlBlockType.ProcessingInstruction:
                if (line.Search("?>", out endof))
                {
                    htmlBlock.Span.End = endof - 1;
                    result             = BlockState.Break;
                }
                break;

            case HtmlBlockType.DocumentType:
                if (line.Search(">", out endof))
                {
                    htmlBlock.Span.End = endof - 1;
                    result             = BlockState.Break;
                }
                break;

            case HtmlBlockType.ScriptPreOrStyle:
                if (line.SearchLowercase("</script>", out endof) || line.SearchLowercase("</pre>", out endof) || line.SearchLowercase("</style>", out endof))
                {
                    htmlBlock.Span.End = endof - 1;
                    result             = BlockState.Break;
                }
                break;

            case HtmlBlockType.InterruptingBlock:
                if (state.IsBlankLine)
                {
                    result = BlockState.BreakDiscard;
                }
                break;

            case HtmlBlockType.NonInterruptingBlock:
                if (state.IsBlankLine)
                {
                    result = BlockState.BreakDiscard;
                }
                break;
            }

            // Update only if we don't have a break discard
            if (result != BlockState.BreakDiscard)
            {
                htmlBlock.Span.End = line.End;
            }

            return(result);
        }