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); }
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); }
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); }
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'); }
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); }
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 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); } }
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); }
public override Boolean TryParse(BlockProcessor state, Char pendingBulletType, out ListInfo result) { result = new ListInfo(); if (state.PeekChar(1) != ' ') { return(false); } state.NextChar(); return(true); }
/// <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); }
/// <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; }
/// <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)); }
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); }
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); }
public override bool TryParse(BlockProcessor state, char pendingBulletType, out ListInfo result) { result = new ListInfo(state.CurrentChar); state.NextChar(); return(true); }
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); }