/// <summary> /// Tries to continue matching a block already opened. /// </summary> /// <param name="processor">The parser processor.</param> /// <param name="block">The block already opened.</param> /// <returns>The result of the match. By default, don't expect any newline</returns> public override BlockState TryContinue(BlockProcessor processor, Block block) { // Determine if we have a closing fence. // It can start or end with either <c>---</c> or <c>...</c> var line = processor.Line; var c = line.CurrentChar; if (processor.Column == 0 && (c == '-' || c == '.')) { int count = line.CountAndSkipChar(c); c = line.CurrentChar; // If we have a closing fence, close it and discard the current line // The line must contain only fence characters and optional following whitespace. if (count == 3 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd()) { block.UpdateSpanEnd(line.Start - 1); // Don't keep the last line return(BlockState.BreakDiscard); } } // Reset the indentation to the column before the indent processor.GoToColumn(processor.ColumnBeforeIndent); return(BlockState.Continue); }
public override BlockState TryContinue( BlockProcessor processor, Block block) { var fence = (IFencedBlock)block; var count = fence.FencedCharCount; var matchChar = fence.FencedChar; var c = processor.CurrentChar; // Match if we have a closing fence var line = processor.Line; while (c == matchChar) { c = line.NextChar(); count--; } // If we have a closing fence, close it and discard the current line // The line must contain only fence opening character followed only by whitespaces. if (count <= 0 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd()) { block.UpdateSpanEnd(line.Start - 1); // Don't keep the last line return(BlockState.BreakDiscard); } // Reset the indentation to the column before the indent processor.GoToColumn(processor.ColumnBeforeIndent); return(BlockState.Continue); }
private BlockState TryOpen(BlockProcessor processor, bool isContinue) { // We expect footnote to appear only at document level and not indented more than a code indent block var currentContainer = processor.GetCurrentContainerOpened(); if (processor.IsCodeIndent || (!isContinue && currentContainer.GetType() != typeof(MarkdownDocument)) || (isContinue && !(currentContainer is FootnoteGroup))) { return(BlockState.None); } var saved = processor.Column; string label; int start = processor.Start; SourceSpan labelSpan; if (!LinkHelper.TryParseLabel(ref processor.Line, false, out label, out labelSpan) || !label.StartsWith("^") || processor.CurrentChar != ':') { processor.GoToColumn(saved); return(BlockState.None); } // Advance the column int deltaColumn = processor.Start - start; processor.Column = processor.Column + deltaColumn; processor.NextChar(); // Skip ':' var footnote = new Footnote(this) { Label = label, LabelSpan = labelSpan, }; // Maintain a list of all footnotes at document level var footnotes = processor.Document.GetData(DocumentKey) as FootnoteGroup; if (footnotes == null) { footnotes = new FootnoteGroup(this); processor.Document.Add(footnotes); processor.Document.SetData(DocumentKey, footnotes); processor.Document.ProcessInlinesEnd += Document_ProcessInlinesEnd; } footnotes.Add(footnote); var linkRef = new FootnoteLinkReferenceDefinition() { Footnote = footnote, CreateLinkInline = CreateLinkToFootnote, Line = processor.LineIndex, Span = new SourceSpan(start, processor.Start - 2), // account for ]: LabelSpan = labelSpan, Label = label }; processor.Document.SetLinkReferenceDefinition(footnote.Label, linkRef); processor.NewBlocks.Push(footnote); return(BlockState.Continue); }
public override BlockState TryContinue(BlockProcessor processor, Block block) { var figure = (Figure)block; var count = figure.OpeningCharacterCount; var matchChar = figure.OpeningCharacter; var c = processor.CurrentChar; var column = processor.Column; // Match if we have a closing fence var line = processor.Line; var startPosition = line.Start; while (c == matchChar) { c = line.NextChar(); count--; } // If we have a closing fence, close it and discard the current line // The line must contain only fence opening character followed only by whitespaces. if (count <= 0 && !processor.IsCodeIndent) { line.TrimStart(); if (!line.IsEmpty) { var caption = new FigureCaption(this) { Span = new SourceSpan(line.Start, line.End), Line = processor.LineIndex, Column = column + line.Start - startPosition, IsOpen = false }; caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition); figure.Add(caption); } figure.Span.End = line.End; // Don't keep the last line return(BlockState.BreakDiscard); } // Reset the indentation to the column before the indent processor.GoToColumn(processor.ColumnBeforeIndent); figure.Span.End = line.End; return(BlockState.Continue); }
/// <summary> /// 继续解析全部代码块。 /// </summary> /// <param name="processor">代码块进程。</param> /// <param name="block">当前块实例对象。</param> /// <returns>返回读取的状态。</returns> public override BlockState TryContinue(BlockProcessor processor, Block block) { if (processor.IsCodeIndent) { return(BlockState.None); } var quote = (QuoteSectionNoteBlock)block; var column = processor.Column; if (quote.QuoteType == QuoteSectionNoteType.DFMVideo) { return(BlockState.BreakDiscard); } var c = processor.CurrentChar; if (c != quote.QuoteChar) { return(processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None); } c = processor.NextChar(); // Skip opening char if (c.IsSpace()) { processor.NextChar(); // Skip following space } // Check for New DFM block if (TryParseFromLine(processor, new QuoteSectionNoteBlock(this))) { // Meet note or section, close this block, new block will be open in the next steps processor.GoToColumn(column); return(BlockState.None); } else { block.UpdateSpanEnd(processor.Line.End); return(BlockState.Continue); } }
/// <summary> /// Overrides the TryOpen for the heading block parser to ignore the need for spaces /// </summary> /// <returns>The open.</returns> /// <param name="processor">Processor.</param> public override BlockState TryOpen(BlockProcessor processor) { // If we are in a CodeIndent, early exit if (processor.IsCodeIndent) { return(BlockState.None); } // 4.2 ATX headings // An ATX heading consists of a string of characters, parsed as inline content, // between an opening sequence of 1–6 unescaped # characters and an optional // closing sequence of any number of unescaped # characters. The opening sequence // of # characters must be followed by a space or by the end of line. The optional // closing sequence of #s must be preceded by a space and may be followed by spaces // only. The opening # character may be indented 0-3 spaces. The raw contents of // the heading are stripped of leading and trailing spaces before being parsed as // inline content. The heading level is equal to the number of # characters in the // opening sequence. // We are not doing this ^^ we don't have the spaces... so we need to handle that adjusted logic here var column = processor.Column; var line = processor.Line; var sourcePosition = line.Start; var c = line.CurrentChar; var matchingChar = c; int leadingCount = 0; // get how many of the headChar we have and limit to 6 (h6 is the last handled header) while (c == _headChar && leadingCount <= 6) { if (c != matchingChar) { break; } c = line.NextChar(); leadingCount++; } // A space is NOT required after leading # if (leadingCount > 0 && leadingCount <= 6) { // Move to the content var headingBlock = new HeadingBlock(this) { HeaderChar = matchingChar, Level = leadingCount, Column = column, Span = { Start = sourcePosition } }; processor.NewBlocks.Push(headingBlock); processor.GoToColumn(column + leadingCount); // no +1 - skip the space // Gives a chance to parse attributes if (TryParseAttributes != null) { TryParseAttributes(processor, ref processor.Line, headingBlock); } // The optional closing sequence of #s must not be preceded by a space and may be followed by spaces only. int endState = 0; int countClosingTags = 0; for (int i = processor.Line.End; i >= processor.Line.Start; i--) // Go up to Start in order to match the no space after the first ### { c = processor.Line.Text[i]; if (endState == 0) { if (c.IsSpaceOrTab()) { continue; } endState = 1; } if (endState == 1) { if (c == matchingChar) { countClosingTags++; continue; } if (countClosingTags > 0) { if (c.IsSpaceOrTab()) { processor.Line.End = i - 1; } break; } else { break; } } } // Setup the source end position of this element headingBlock.Span.End = processor.Line.End; // We expect a single line, so don't continue return(BlockState.Break); } // Else we don't have an header return(BlockState.None); }
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); }
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); }
public static void ResetLineIndent(BlockProcessor processor) { processor.GoToColumn(processor.ColumnBeforeIndent); }
private bool TryParseFromLine(BlockProcessor processor, QuoteSectionNoteBlock block) { int originalColumn = processor.Column; block.QuoteType = QuoteSectionNoteType.MarkdownQuote; if (processor.CurrentChar != '[') { return(false); } var stringBuilder = StringBuilderCache.Local(); var c = processor.CurrentChar; var hasEscape = false; while (c != '\0' && (c != ']' || hasEscape)) { if (c == '\\' && !hasEscape) { hasEscape = true; } else { stringBuilder.Append(c); hasEscape = false; } c = processor.NextChar(); } stringBuilder.Append(c); var infoString = stringBuilder.ToString().Trim(); if (c == '\0') { processor.GoToColumn(originalColumn); return(false); } if (c == ']') { // "> [!NOTE] content" is invalid, go to end to see it. processor.NextChar(); while (processor.CurrentChar.IsSpaceOrTab()) { processor.NextChar(); } var isNoteVideoDiv = infoString.StartsWith("[!div", StringComparison.OrdinalIgnoreCase) || infoString.StartsWith("[!Video", StringComparison.OrdinalIgnoreCase) || SectionNoteType.IsNoteType(infoString); if (processor.CurrentChar != '\0' && isNoteVideoDiv) { processor.GoToColumn(originalColumn); return(false); } } if (SectionNoteType.IsNoteType(infoString)) { block.QuoteType = QuoteSectionNoteType.DFMNote; block.NoteTypeString = infoString.Substring(2, infoString.Length - 3).ToLowerInvariant(); return(true); } if (infoString.StartsWith("[!div", StringComparison.OrdinalIgnoreCase)) { block.QuoteType = QuoteSectionNoteType.DFMSection; string attribute = infoString.Substring(5, infoString.Length - 6).Trim(); if (attribute.Length >= 2 && attribute.First() == '`' && attribute.Last() == '`') { block.SectionAttributeString = attribute.Substring(1, attribute.Length - 2).Trim(); } if (attribute.Length >= 1 && attribute.First() != '`' && attribute.Last() != '`') { block.SectionAttributeString = attribute; } return(true); } if (infoString.StartsWith("[!Video", StringComparison.OrdinalIgnoreCase)) { string link = infoString.Substring(7, infoString.Length - 8); if (link.StartsWith(" http://") || link.StartsWith(" https://")) { block.QuoteType = QuoteSectionNoteType.DFMVideo; block.VideoLink = link.Trim(); return(true); } } processor.GoToColumn(originalColumn); return(false); }
// Subtracts opening fence indent from current line indent internal virtual void UpdateLineStart(BlockProcessor blockProcessor, TProxy block) { blockProcessor.GoToColumn(blockProcessor.ColumnBeforeIndent + Math.Min(block.OpeningFenceIndent, blockProcessor.Indent)); }