Upstream xoofx/markdig state of the touched files before 67a2e4948f
This commit is contained in:
1 parent
0e598ccf6f
commit
93406f115e
4 files changed
+745
No files matched your search
@@ -0,0 +1,24 @@
|
||||
using Markdig.Extensions.AutoLinks;
|
||||
|
||||
namespace Markdig.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class TestAutoLinks
|
||||
{
|
||||
[Test]
|
||||
[TestCase("https://localhost", "<p><a href=\"https://localhost\">https://localhost</a></p>")]
|
||||
[TestCase("http://localhost", "<p><a href=\"http://localhost\">http://localhost</a></p>")]
|
||||
[TestCase("https://l", "<p><a href=\"https://l\">https://l</a></p>")]
|
||||
[TestCase("www.l", "<p><a href=\"http://www.l\">www.l</a></p>")]
|
||||
[TestCase("https://localhost:5000", "<p><a href=\"https://localhost:5000\">https://localhost:5000</a></p>")]
|
||||
[TestCase("www.l:5000", "<p><a href=\"http://www.l:5000\">www.l:5000</a></p>")]
|
||||
public void TestLinksWithAllowDomainWithoutPeriod(string markdown, string expected)
|
||||
{
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseAutoLinks(new AutoLinkOptions { AllowDomainWithoutPeriod = true })
|
||||
.Build();
|
||||
var html = Markdown.ToHtml(markdown, pipeline);
|
||||
|
||||
Assert.That(html, Is.EqualTo(expected).IgnoreWhiteSpace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Markdig.Extensions.AutoIdentifiers;
|
||||
|
||||
/// <summary>
|
||||
/// A link reference definition to a <see cref="HeadingBlock"/> stored at the <see cref="MarkdownDocument"/> level.
|
||||
/// </summary>
|
||||
/// <seealso cref="LinkReferenceDefinition" />
|
||||
public class HeadingLinkReferenceDefinition : LinkReferenceDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the HeadingLinkReferenceDefinition class.
|
||||
/// </summary>
|
||||
public HeadingLinkReferenceDefinition(HeadingBlock headling)
|
||||
{
|
||||
Heading = headling;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the heading related to this link reference definition.
|
||||
/// </summary>
|
||||
public HeadingBlock Heading { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Parsers.Inlines;
|
||||
|
||||
/// <summary>
|
||||
/// An inline parser for <see cref="LinkInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="InlineParser" />
|
||||
public class LinkInlineParser : InlineParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinkInlineParser"/> class.
|
||||
/// </summary>
|
||||
public LinkInlineParser() : this(new LinkOptions())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinkInlineParser"/> class.
|
||||
/// </summary>
|
||||
public LinkInlineParser(LinkOptions options)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
OpeningCharacters = ['[', ']', '!'];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the options.
|
||||
/// </summary>
|
||||
public readonly LinkOptions Options;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to match the parser at the current position.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
var startPosition = processor.GetSourcePosition(slice.Start, out int line, out int column);
|
||||
|
||||
bool isImage = false;
|
||||
if (c == '!')
|
||||
{
|
||||
isImage = true;
|
||||
c = slice.NextChar();
|
||||
if (c != '[')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
string? label;
|
||||
SourceSpan labelWithTriviaSpan = SourceSpan.Empty;
|
||||
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;
|
||||
|
||||
SourceSpan labelSpan;
|
||||
// If the label is followed by either a ( or a [, this is not a shortcut
|
||||
if (processor.TrackTrivia)
|
||||
{
|
||||
if (LinkHelper.TryParseLabelTrivia(ref slice, out label, out labelSpan))
|
||||
{
|
||||
labelWithTriviaSpan.Start = labelSpan.Start; // skip opening [
|
||||
labelWithTriviaSpan.End = labelSpan.End; // skip closing ]
|
||||
if (!processor.Document.ContainsLinkReferenceDefinition(label))
|
||||
{
|
||||
label = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LinkHelper.TryParseLabel(ref slice, out label, out labelSpan))
|
||||
{
|
||||
if (!processor.Document.ContainsLinkReferenceDefinition(label))
|
||||
{
|
||||
label = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
slice = saved;
|
||||
|
||||
// Else we insert a LinkDelimiter
|
||||
slice.SkipChar();
|
||||
var linkDelimiter = 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
|
||||
};
|
||||
|
||||
if (processor.TrackTrivia)
|
||||
{
|
||||
linkDelimiter.LabelWithTrivia = new StringSlice(slice.Text, labelWithTriviaSpan.Start, labelWithTriviaSpan.End);
|
||||
}
|
||||
|
||||
processor.Inline = linkDelimiter;
|
||||
return true;
|
||||
|
||||
case ']':
|
||||
slice.SkipChar();
|
||||
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 ProcessLinkReference(
|
||||
InlineProcessor state,
|
||||
StringSlice text,
|
||||
string label,
|
||||
SourceSpan labelWithriviaSpan,
|
||||
bool isShortcut,
|
||||
SourceSpan labelSpan,
|
||||
LinkDelimiterInline parent,
|
||||
int endPosition,
|
||||
LocalLabel localLabel)
|
||||
{
|
||||
if (!state.Document.TryGetLinkReferenceDefinition(label, out LinkReferenceDefinition? linkRef))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Inline? link = null;
|
||||
// Try to use a callback directly defined on the LinkReferenceDefinition
|
||||
if (linkRef.CreateLinkInline != null)
|
||||
{
|
||||
link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild);
|
||||
link.Span = new SourceSpan(parent.Span.Start, endPosition);
|
||||
link.Line = parent.Line;
|
||||
link.Column = parent.Column;
|
||||
}
|
||||
|
||||
// Create a default link if the callback was not found
|
||||
if (link is null)
|
||||
{
|
||||
// Inline Link
|
||||
var linkInline = new LinkInline()
|
||||
{
|
||||
Url = HtmlHelper.Unescape(linkRef.Url, removeBackSlash: false),
|
||||
Title = HtmlHelper.Unescape(linkRef.Title, removeBackSlash: false),
|
||||
Label = label,
|
||||
LabelSpan = labelSpan,
|
||||
UrlSpan = linkRef.UrlSpan,
|
||||
IsImage = parent.IsImage,
|
||||
IsShortcut = isShortcut,
|
||||
Reference = linkRef,
|
||||
Span = new SourceSpan(parent.Span.Start, endPosition),
|
||||
Line = parent.Line,
|
||||
Column = parent.Column,
|
||||
};
|
||||
|
||||
if (state.TrackTrivia)
|
||||
{
|
||||
linkInline.LabelWithTrivia = new StringSlice(text.Text, labelWithriviaSpan.Start, labelWithriviaSpan.End);
|
||||
linkInline.LinkRefDefLabel = linkRef.Label;
|
||||
linkInline.LinkRefDefLabelWithTrivia = linkRef.LabelWithTrivia;
|
||||
linkInline.LocalLabel = localLabel;
|
||||
}
|
||||
|
||||
if (Options.OpenInNewWindow)
|
||||
{
|
||||
linkInline.GetAttributes().AddPropertyIfNotExist("target", "_blank");
|
||||
}
|
||||
|
||||
link = linkInline;
|
||||
}
|
||||
|
||||
if (link is ContainerInline containerLink)
|
||||
{
|
||||
var child = parent.FirstChild;
|
||||
if (child is null)
|
||||
{
|
||||
child = new LiteralInline()
|
||||
{
|
||||
Content = StringSlice.Empty,
|
||||
IsClosed = true,
|
||||
// Not exact but we leave it like this
|
||||
Span = parent.Span,
|
||||
Line = parent.Line,
|
||||
Column = parent.Column,
|
||||
};
|
||||
containerLink.AppendChild(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert all child into the link
|
||||
while (child != null)
|
||||
{
|
||||
var next = child.NextSibling;
|
||||
child.Remove();
|
||||
containerLink.AppendChild(child);
|
||||
child = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
link.IsClosed = true;
|
||||
|
||||
// Process emphasis delimiters
|
||||
state.PostProcessInlines(0, link, null, false);
|
||||
|
||||
state.Inline = link;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 == '(')
|
||||
{
|
||||
LinkInline? link = null;
|
||||
|
||||
if (inlineState.TrackTrivia)
|
||||
{
|
||||
link = TryParseInlineLinkTrivia(ref text, inlineState, openParent);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LinkHelper.TryParseInlineLink(ref text, out string? url, out string? title, out SourceSpan linkSpan, out SourceSpan titleSpan))
|
||||
{
|
||||
// Inline Link
|
||||
link = new LinkInline()
|
||||
{
|
||||
Url = HtmlHelper.Unescape(url, removeBackSlash: false),
|
||||
Title = title is null ? null : HtmlHelper.Unescape(title, removeBackSlash: false),
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (link is not null)
|
||||
{
|
||||
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;
|
||||
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))
|
||||
{
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
|
||||
// We have a nested [ ]
|
||||
// firstParent.Remove();
|
||||
// The opening [ will be transformed to a literal followed by all the children of the [
|
||||
|
||||
var literal = new LiteralInline()
|
||||
{
|
||||
Span = openParent.Span,
|
||||
Content = new StringSlice(openParent.IsImage ? "![" : "[")
|
||||
};
|
||||
|
||||
inlineState.Inline = openParent.ReplaceBy(literal);
|
||||
return false;
|
||||
|
||||
static LinkInline? TryParseInlineLinkTrivia(ref StringSlice text, InlineProcessor inlineState, LinkDelimiterInline openParent)
|
||||
{
|
||||
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);
|
||||
|
||||
return new LinkInline()
|
||||
{
|
||||
TriviaBeforeUrl = wsBeforeLink,
|
||||
Url = HtmlHelper.Unescape(url, removeBackSlash: false),
|
||||
UnescapedUrl = unescapedUrl,
|
||||
UrlHasPointyBrackets = urlHasPointyBrackets,
|
||||
TriviaAfterUrl = wsAfterLink,
|
||||
Title = HtmlHelper.Unescape(title, removeBackSlash: false),
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MarkParentAsInactive(Inline? inline)
|
||||
{
|
||||
while (inline != null)
|
||||
{
|
||||
if (inline is LinkDelimiterInline linkInline)
|
||||
{
|
||||
if (linkInline.IsImage)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
linkInline.IsActive = false;
|
||||
}
|
||||
|
||||
inline = inline.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Syntax;
|
||||
|
||||
/// <summary>
|
||||
/// A link reference definition (Section 4.7 CommonMark specs)
|
||||
/// </summary>
|
||||
/// <seealso cref="LeafBlock" />
|
||||
public class LinkReferenceDefinition : LeafBlock
|
||||
{
|
||||
private TriviaProperties? _trivia => TryGetDerivedTrivia<TriviaProperties>();
|
||||
private TriviaProperties Trivia => GetOrSetDerivedTrivia<TriviaProperties>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an inline link for the specified <see cref="LinkReferenceDefinition"/>.
|
||||
/// </summary>
|
||||
/// <param name="inlineState">State of the inline.</param>
|
||||
/// <param name="linkRef">The link reference.</param>
|
||||
/// <param name="child">The child.</param>
|
||||
/// <returns>An inline link or null to use the default implementation</returns>
|
||||
public delegate Inline CreateLinkInlineDelegate(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline? child = null);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinkReferenceDefinition"/> class.
|
||||
/// </summary>
|
||||
public LinkReferenceDefinition() : base(null)
|
||||
{
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinkReferenceDefinition"/> class.
|
||||
/// </summary>
|
||||
/// <param name="label">The label.</param>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="title">The title.</param>
|
||||
public LinkReferenceDefinition(string? label, string? url, string? title) : this()
|
||||
{
|
||||
Label = label;
|
||||
Url = url;
|
||||
Title = title;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the label. Text is normalized according to spec.
|
||||
/// </summary>
|
||||
/// https://spec.commonmark.org/0.29/#matches
|
||||
public string? Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The label span
|
||||
/// </summary>
|
||||
public SourceSpan LabelSpan;
|
||||
|
||||
/// <summary>
|
||||
/// Non-normalized Label (includes trivia)
|
||||
/// Trivia: only parsed when <see cref="MarkdownPipeline.TrackTrivia"/> is enabled, otherwise
|
||||
/// <see cref="StringSlice.Empty"/>.
|
||||
/// </summary>
|
||||
public StringSlice LabelWithTrivia { get => _trivia?.LabelWithTrivia ?? StringSlice.Empty; set => Trivia.LabelWithTrivia = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Whitespace before the <see cref="Url"/>.
|
||||
/// Trivia: only parsed when <see cref="MarkdownPipeline.TrackTrivia"/> is enabled, otherwise
|
||||
/// <see cref="StringSlice.Empty"/>.
|
||||
/// </summary>
|
||||
public StringSlice TriviaBeforeUrl { get => _trivia?.TriviaBeforeUrl ?? StringSlice.Empty; set => Trivia.TriviaBeforeUrl = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL.
|
||||
/// </summary>
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL span
|
||||
/// </summary>
|
||||
public SourceSpan UrlSpan;
|
||||
|
||||
/// <summary>
|
||||
/// Non-normalized <see cref="Url"/>.
|
||||
/// Trivia: only parsed when <see cref="MarkdownPipeline.TrackTrivia"/> is enabled, otherwise
|
||||
/// <see cref="StringSlice.Empty"/>.
|
||||
/// </summary>
|
||||
public StringSlice UnescapedUrl { get => _trivia?.UnescapedUrl ?? StringSlice.Empty; set => Trivia.UnescapedUrl = value; }
|
||||
|
||||
/// <summary>
|
||||
/// True when the <see cref="Url"/> is enclosed in point brackets in the source document.
|
||||
/// Trivia: only parsed when <see cref="MarkdownPipeline.TrackTrivia"/> is enabled, otherwise
|
||||
/// false.
|
||||
/// </summary>
|
||||
public bool UrlHasPointyBrackets { get => _trivia?.UrlHasPointyBrackets ?? false; set => Trivia.UrlHasPointyBrackets = value; }
|
||||
|
||||
/// <summary>
|
||||
/// gets or sets the whitespace before a <see cref="Title"/>.
|
||||
/// Trivia: only parsed when <see cref="MarkdownPipeline.TrackTrivia"/> is enabled, otherwise
|
||||
/// <see cref="StringSlice.Empty"/>.
|
||||
/// </summary>
|
||||
public StringSlice TriviaBeforeTitle { get => _trivia?.TriviaBeforeTitle ?? StringSlice.Empty; set => Trivia.TriviaBeforeTitle = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title.
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title span
|
||||
/// </summary>
|
||||
public SourceSpan TitleSpan;
|
||||
|
||||
/// <summary>
|
||||
/// Non-normalized <see cref="Title"/>.
|
||||
/// Trivia: only parsed when <see cref="MarkdownPipeline.TrackTrivia"/> is enabled, otherwise
|
||||
/// <see cref="StringSlice.Empty"/>.
|
||||
/// </summary>
|
||||
public StringSlice UnescapedTitle { get => _trivia?.UnescapedTitle ?? StringSlice.Empty; set => Trivia.UnescapedTitle = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the character the <see cref="Title"/> is enclosed in.
|
||||
/// Trivia: only parsed when <see cref="MarkdownPipeline.TrackTrivia"/> is enabled, otherwise \0.
|
||||
/// </summary>
|
||||
public char TitleEnclosingCharacter { get => _trivia?.TitleEnclosingCharacter ?? default; set => Trivia.TitleEnclosingCharacter = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the create link inline callback for this instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This callback is called when an inline link is matching this reference definition.
|
||||
/// </remarks>
|
||||
public CreateLinkInlineDelegate? CreateLinkInline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to the parse the specified text into a definition.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the text</typeparam>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="block">The block.</param>
|
||||
/// <returns><c>true</c> if parsing is successful; <c>false</c> otherwise</returns>
|
||||
public static bool TryParse<T>(ref T text, [NotNullWhen(true)] out LinkReferenceDefinition? block) where T : ICharIterator
|
||||
{
|
||||
block = null;
|
||||
|
||||
var startSpan = text.Start;
|
||||
|
||||
if (!LinkHelper.TryParseLinkReferenceDefinition(ref text, out string? label, out string? url, out string? title, out SourceSpan labelSpan, out SourceSpan urlSpan, out SourceSpan titleSpan))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
block = new LinkReferenceDefinition(label, url, title)
|
||||
{
|
||||
LabelSpan = labelSpan,
|
||||
UrlSpan = urlSpan,
|
||||
TitleSpan = titleSpan,
|
||||
Span = new SourceSpan(startSpan, titleSpan.End > 0 ? titleSpan.End: urlSpan.End)
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to the parse the specified text into a definition.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the text</typeparam>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="block">The block.</param>
|
||||
/// <param name="triviaBeforeLabel"></param>
|
||||
/// <param name="labelWithTrivia"></param>
|
||||
/// <param name="triviaBeforeUrl"></param>
|
||||
/// <param name="unescapedUrl"></param>
|
||||
/// <param name="triviaBeforeTitle"></param>
|
||||
/// <param name="unescapedTitle"></param>
|
||||
/// <param name="triviaAfterTitle"></param>
|
||||
/// <returns><c>true</c> if parsing is successful; <c>false</c> otherwise</returns>
|
||||
public static bool TryParseTrivia<T>(
|
||||
ref T text,
|
||||
[NotNullWhen(true)] out LinkReferenceDefinition? block,
|
||||
out SourceSpan triviaBeforeLabel,
|
||||
out SourceSpan labelWithTrivia,
|
||||
out SourceSpan triviaBeforeUrl,
|
||||
out SourceSpan unescapedUrl,
|
||||
out SourceSpan triviaBeforeTitle,
|
||||
out SourceSpan unescapedTitle,
|
||||
out SourceSpan triviaAfterTitle) where T : ICharIterator
|
||||
{
|
||||
block = null;
|
||||
|
||||
var startSpan = text.Start;
|
||||
|
||||
if (!LinkHelper.TryParseLinkReferenceDefinitionTrivia(
|
||||
ref text,
|
||||
out triviaBeforeLabel,
|
||||
out string? label,
|
||||
out labelWithTrivia,
|
||||
out triviaBeforeUrl,
|
||||
out string? url,
|
||||
out unescapedUrl,
|
||||
out bool urlHasPointyBrackets,
|
||||
out triviaBeforeTitle,
|
||||
out string? title,
|
||||
out unescapedTitle,
|
||||
out char titleEnclosingCharacter,
|
||||
out NewLine newLine,
|
||||
out triviaAfterTitle,
|
||||
out SourceSpan labelSpan,
|
||||
out SourceSpan urlSpan,
|
||||
out SourceSpan titleSpan))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
block = new LinkReferenceDefinition(label, url, title)
|
||||
{
|
||||
UrlHasPointyBrackets = urlHasPointyBrackets,
|
||||
TitleEnclosingCharacter = titleEnclosingCharacter,
|
||||
//LabelWithWhitespace = labelWithWhitespace,
|
||||
LabelSpan = labelSpan,
|
||||
UrlSpan = urlSpan,
|
||||
//UnescapedUrl = unescapedUrl,
|
||||
//UnescapedTitle = unescapedTitle,
|
||||
TitleSpan = titleSpan,
|
||||
Span = new SourceSpan(startSpan, titleSpan.End > 0 ? titleSpan.End : urlSpan.End),
|
||||
NewLine = newLine,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
private sealed class TriviaProperties
|
||||
{
|
||||
public StringSlice LabelWithTrivia;
|
||||
public StringSlice TriviaBeforeUrl;
|
||||
public StringSlice UnescapedUrl;
|
||||
public bool UrlHasPointyBrackets;
|
||||
public StringSlice TriviaBeforeTitle;
|
||||
public StringSlice UnescapedTitle;
|
||||
public char TitleEnclosingCharacter;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user