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); }
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); }
public override bool CanInterrupt(BlockProcessor processor, Block block) { return(!(block is ParagraphBlock)); }
public override BlockState TryContinue(BlockProcessor processor, Block block) { var htmlBlock = (HtmlBlock)block; return(MatchEnd(processor, htmlBlock)); }
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)); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }