public override Boolean Match(InlineProcessor processor, ref StringSlice slice) { var startPosition = processor.GetSourcePosition(slice.Start, out int line, out int column); var last = slice.PeekCharExtra(-1); var current = slice.CurrentChar; if (current != '[' || last == '\\') { return(false); } // Link reference if (LinkHelper.TryParseLabel(ref slice, out String label, out SourceSpan labelSpan)) { if (processor.Document.ContainsLinkReferenceDefinition(label)) { return(false); } } // Explicit link if (slice.CurrentChar.IsOneOf('[', '(', ':')) { return(false); } // Shorthand processor.Inline = new LinkInline(label, "") { LabelSpan = processor.GetSourcePositionFromLocalSpan(labelSpan), IsImage = false, Span = new SourceSpan( startPosition, processor.GetSourcePosition(slice.Start - 1)), Line = line, Column = column, IsClosed = true, }.AppendChild(new LiteralInline(label)); return(true); }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { // The following methods are inspired by the "An algorithm for parsing nested emphasis and links" // at the end of the CommonMark specs. var c = slice.CurrentChar; int line; int column; var startPosition = processor.GetSourcePosition(slice.Start, out line, out column); bool isImage = false; if (c == '!') { isImage = true; c = slice.NextChar(); if (c != '[') { return(false); } } switch (c) { case '[': // If this is not an image, we may have a reference link shortcut // so we try to resolve it here var saved = slice; string label; SourceSpan labelSpan; // If the label is followed by either a ( or a [, this is not a shortcut if (LinkHelper.TryParseLabel(ref slice, out label, out labelSpan)) { if (!processor.Document.ContainsLinkReferenceDefinition(label)) { label = null; } } slice = saved; // Else we insert a LinkDelimiter slice.NextChar(); processor.Inline = new LinkDelimiterInline(this) { Type = DelimiterType.Open, Label = label, LabelSpan = processor.GetSourcePositionFromLocalSpan(labelSpan), IsImage = isImage, Span = new SourceSpan(startPosition, processor.GetSourcePosition(slice.Start - 1)), Line = line, Column = column }; return(true); case ']': slice.NextChar(); if (processor.Inline != null) { if (TryProcessLinkOrImage(processor, ref slice)) { return(true); } } // If we don’t find one, we return a literal slice node ]. // (Done after by the LiteralInline parser) return(false); } // We don't have an emphasis return(false); }
private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice text) { LinkDelimiterInline openParent = null; foreach (var parent in inlineState.Inline.FindParentOfType <LinkDelimiterInline>()) { openParent = parent; break; } if (openParent != null) { // If we do find one, but it’s not active, // we remove the inactive delimiter from the stack, // and return a literal text node ]. if (!openParent.IsActive) { inlineState.Inline = new LiteralInline() { Content = new StringSlice("["), Span = openParent.Span, Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(inlineState.Inline); return(false); } // If we find one and it’s active, // then we parse ahead to see if we have // an inline link/image, reference link/image, // compact reference link/image, // or shortcut reference link/image var parentDelimiter = openParent.Parent; switch (text.CurrentChar) { case '(': string url; string title; SourceSpan linkSpan; SourceSpan titleSpan; if (LinkHelper.TryParseInlineLink(ref text, out url, out title, out linkSpan, out titleSpan)) { // Inline Link var link = new LinkInline() { Url = HtmlHelper.Unescape(url), Title = HtmlHelper.Unescape(title), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan), Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)), Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(link); // Notifies processor as we are creating an inline locally inlineState.Inline = link; // Process emphasis delimiters inlineState.PostProcessInlines(0, link, null, false); // If we have a link (and not an image), // we also set all [ delimiters before the opening delimiter to inactive. // (This will prevent us from getting links within links.) if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } link.IsClosed = true; return(true); } break; default: var labelSpan = SourceSpan.Empty; string label = null; bool isLabelSpanLocal = true; // Handle Collapsed links if (text.CurrentChar == '[') { if (text.PeekChar(1) == ']') { label = openParent.Label; labelSpan = openParent.LabelSpan; isLabelSpanLocal = false; text.NextChar(); // Skip [ text.NextChar(); // Skip ] } } else { label = openParent.Label; } if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan)) { if (isLabelSpanLocal) { labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan); } if (ProcessLinkReference(inlineState, label, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1))) { // Remove the open parent openParent.Remove(); if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } } else { return(false); } return(true); } break; } // We have a nested [ ] // firstParent.Remove(); // The opening [ will be transformed to a literal followed by all the childrens of the [ var literal = new LiteralInline() { Span = openParent.Span, Content = new StringSlice(openParent.IsImage ? "![" : "[") }; inlineState.Inline = openParent.ReplaceBy(literal); return(false); } return(false); }
private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice text) { LinkDelimiterInline?openParent = inlineState.Inline !.FirstParentOfType <LinkDelimiterInline>(); if (openParent is null) { return(false); } // If we do find one, but it’s not active, // we remove the inactive delimiter from the stack, // and return a literal text node ]. if (!openParent.IsActive) { inlineState.Inline = new LiteralInline() { Content = new StringSlice("["), Span = openParent.Span, Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(inlineState.Inline); return(false); } // If we find one and it’s active, // then we parse ahead to see if we have // an inline link/image, reference link/image, // compact reference link/image, // or shortcut reference link/image var parentDelimiter = openParent.Parent; var savedText = text; if (text.CurrentChar == '(') { if (inlineState.TrackTrivia) { if (LinkHelper.TryParseInlineLinkTrivia( ref text, out string?url, out SourceSpan unescapedUrlSpan, out string?title, out SourceSpan unescapedTitleSpan, out char titleEnclosingCharacter, out SourceSpan linkSpan, out SourceSpan titleSpan, out SourceSpan triviaBeforeLink, out SourceSpan triviaAfterLink, out SourceSpan triviaAfterTitle, out bool urlHasPointyBrackets)) { var wsBeforeLink = new StringSlice(text.Text, triviaBeforeLink.Start, triviaBeforeLink.End); var wsAfterLink = new StringSlice(text.Text, triviaAfterLink.Start, triviaAfterLink.End); var wsAfterTitle = new StringSlice(text.Text, triviaAfterTitle.Start, triviaAfterTitle.End); var unescapedUrl = new StringSlice(text.Text, unescapedUrlSpan.Start, unescapedUrlSpan.End); var unescapedTitle = new StringSlice(text.Text, unescapedTitleSpan.Start, unescapedTitleSpan.End); // Inline Link var link = new LinkInline() { TriviaBeforeUrl = wsBeforeLink, Url = HtmlHelper.Unescape(url), UnescapedUrl = unescapedUrl, UrlHasPointyBrackets = urlHasPointyBrackets, TriviaAfterUrl = wsAfterLink, Title = HtmlHelper.Unescape(title), UnescapedTitle = unescapedTitle, TitleEnclosingCharacter = titleEnclosingCharacter, TriviaAfterTitle = wsAfterTitle, IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan), Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)), Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(link); // Notifies processor as we are creating an inline locally inlineState.Inline = link; // Process emphasis delimiters inlineState.PostProcessInlines(0, link, null, false); // If we have a link (and not an image), // we also set all [ delimiters before the opening delimiter to inactive. // (This will prevent us from getting links within links.) if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } link.IsClosed = true; return(true); } } else { if (LinkHelper.TryParseInlineLink(ref text, out string?url, out string?title, out SourceSpan linkSpan, out SourceSpan titleSpan)) { // Inline Link var link = new LinkInline() { Url = HtmlHelper.Unescape(url), Title = HtmlHelper.Unescape(title), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan), Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)), Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(link); // Notifies processor as we are creating an inline locally inlineState.Inline = link; // Process emphasis delimiters inlineState.PostProcessInlines(0, link, null, false); // If we have a link (and not an image), // we also set all [ delimiters before the opening delimiter to inactive. // (This will prevent us from getting links within links.) if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } link.IsClosed = true; return(true); } } text = savedText; } var labelSpan = SourceSpan.Empty; string? label = null; SourceSpan labelWithTrivia = SourceSpan.Empty; bool isLabelSpanLocal = true; bool isShortcut = false; LocalLabel localLabel = LocalLabel.Local; // Handle Collapsed links if (text.CurrentChar == '[') { if (text.PeekChar() == ']') { label = openParent.Label; labelSpan = openParent.LabelSpan; isLabelSpanLocal = false; localLabel = LocalLabel.Empty; text.SkipChar(); // Skip [ text.SkipChar(); // Skip ] } } else { localLabel = LocalLabel.None; label = openParent.Label; isShortcut = true; } if (label != null || LinkHelper.TryParseLabelTrivia(ref text, true, out label, out labelSpan)) { labelWithTrivia = new SourceSpan(labelSpan.Start, labelSpan.End); if (isLabelSpanLocal) { labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan); } if (ProcessLinkReference(inlineState, text, label !, labelWithTrivia, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1), localLabel)) { // Remove the open parent openParent.Remove(); if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } return(true); } else if (text.CurrentChar != ']' && text.CurrentChar != '[') { return(false); } }