use StringSlice instead of string in InlineLink and LinkReferenceDefinition

This commit is contained in:
Ruud Poutsma
2020-11-07 11:47:12 +01:00
parent 453e8239d2
commit b3953dbe23
9 changed files with 91 additions and 62 deletions

View File

@@ -58,11 +58,21 @@ In order:
# Pull request discussion
- LinkHelper duplication
- keep current?
- if not, how to deduplicate?
- StringSlice vs String
- StringSlice is preferred
- amount of tests
- should we create even more permutations using `\v`, `\f`?
- newlines
- Newline struct itself
- handling newlines
- should newlines be supported?
- Example 207, 209, 291: Special-casing certain edgecases
- LineBreakInline now also parses /r and /r/n: this effectively removes an optimization
- Example 207, 209, 291: Special-casing certain edgecases
- TrackTrivia flag trickling down into 5-6 classes
- InlineProcessor
- BlockProcessor
- MarkdownParser
- Markdown static class
- LinkReferenceDefinitionHelper

View File

@@ -418,9 +418,9 @@ namespace Markdig.Helpers
public static bool TryParseInlineLinkWhitespace(
ref StringSlice text,
out string link,
out string unescapedLink,
out SourceSpan unescapedLink,
out string title,
out string unescapedTitle,
out SourceSpan unescapedTitle,
out char titleEnclosingCharacter,
out SourceSpan linkSpan,
out SourceSpan titleSpan,
@@ -438,9 +438,9 @@ namespace Markdig.Helpers
bool isValid = false;
var c = text.CurrentChar;
link = null;
unescapedLink = null;
unescapedLink = SourceSpan.Empty;
title = null;
unescapedTitle = null;
unescapedTitle = SourceSpan.Empty;
linkSpan = SourceSpan.Empty;
titleSpan = SourceSpan.Empty;
@@ -458,10 +458,12 @@ namespace Markdig.Helpers
text.TrimStart();
whitespaceBeforeLink = new SourceSpan(sourcePosition, text.Start - 1);
var pos = text.Start;
if (TryParseUrlWhitespace(ref text, out link, out unescapedLink, out urlHasPointyBrackets))
if (TryParseUrlWhitespace(ref text, out link, out urlHasPointyBrackets))
{
linkSpan.Start = pos;
linkSpan.End = text.Start - 1;
unescapedLink.Start = pos + (urlHasPointyBrackets ? 1 : 0);
unescapedLink.End = text.Start - 1 - (urlHasPointyBrackets ? 1 : 0);
if (linkSpan.End < linkSpan.Start)
{
linkSpan = SourceSpan.Empty;
@@ -486,10 +488,12 @@ namespace Markdig.Helpers
{
isValid = true;
}
else if (TryParseTitleWhitespace(ref text, out title, out unescapedTitle, out titleEnclosingCharacter))
else if (TryParseTitleWhitespace(ref text, out title, out titleEnclosingCharacter))
{
titleSpan.Start = pos;
titleSpan.End = text.Start - 1;
unescapedTitle.Start = pos + 1; // skip opening character
unescapedTitle.End = text.Start - 1 - 1; // skip closing character
if (titleSpan.End < titleSpan.Start)
{
titleSpan = SourceSpan.Empty;
@@ -616,11 +620,10 @@ namespace Markdig.Helpers
return isValid;
}
public static bool TryParseTitleWhitespace<T>(ref T text, out string title, out string unescapedTitle, out char enclosingCharacter) where T : ICharIterator
public static bool TryParseTitleWhitespace<T>(ref T text, out string title, out char enclosingCharacter) where T : ICharIterator
{
bool isValid = false;
var buffer = StringBuilderCache.Local();
var unescaped = new StringBuilder();
enclosingCharacter = '\0';
// a sequence of zero or more characters between straight double-quote characters ("), including a " character only if it is backslash-escaped, or
@@ -650,11 +653,9 @@ namespace Markdig.Helpers
hasOnlyWhiteSpacesSinceLastLine = -1;
}
buffer.Append(c);
unescaped.Append(c);
if (c == '\r' && text.PeekChar() == '\n')
{
buffer.Append('\n');
unescaped.Append(c);
}
continue;
}
@@ -669,7 +670,6 @@ namespace Markdig.Helpers
if (hasEscape)
{
buffer.Append(closingQuote);
unescaped.Append(closingQuote);
hasEscape = false;
continue;
}
@@ -688,7 +688,6 @@ namespace Markdig.Helpers
if (c == '\\')
{
hasEscape = true;
unescaped.Append('\\');
continue;
}
@@ -707,12 +706,10 @@ namespace Markdig.Helpers
}
buffer.Append(c);
unescaped.Append(c);
}
}
title = isValid ? buffer.ToString() : null;
unescapedTitle = isValid ? unescaped.ToString() : null;
buffer.Length = 0;
return isValid;
}
@@ -862,7 +859,7 @@ namespace Markdig.Helpers
return isValid;
}
public static bool TryParseUrlWhitespace<T>(ref T text, out string link, out string unescapedLink, out bool hasPointyBrackets, bool isAutoLink = false) where T : ICharIterator
public static bool TryParseUrlWhitespace<T>(ref T text, out string link, out bool hasPointyBrackets, bool isAutoLink = false) where T : ICharIterator
{
bool isValid = false;
hasPointyBrackets = false;
@@ -1004,7 +1001,6 @@ namespace Markdig.Helpers
}
link = isValid ? buffer.ToString() : null;
unescapedLink = isValid ? unescaped.ToString() : null;
buffer.Length = 0;
return isValid;
}
@@ -1177,14 +1173,14 @@ namespace Markdig.Helpers
ref T text,
out SourceSpan whitespaceBeforeLabel,
out string label,
out string labelWithWhitespace,
out SourceSpan labelWithWhitespace,
out SourceSpan whitespaceBeforeUrl, // can contain newline
out string url,
out string unescapedUrl,
out SourceSpan unescapedUrl,
out bool urlHasPointyBrackets,
out SourceSpan whitespaceBeforeTitle, // can contain newline
out string title, // can contain non-consecutive newlines
out string unescapedTitle,
out SourceSpan unescapedTitle,
out char titleEnclosingCharacter,
out Newline newline,
out SourceSpan whitespaceAfterTitle,
@@ -1192,12 +1188,13 @@ namespace Markdig.Helpers
out SourceSpan urlSpan,
out SourceSpan titleSpan) where T : ICharIterator
{
labelWithWhitespace = SourceSpan.Empty;
whitespaceBeforeUrl = SourceSpan.Empty;
url = null;
unescapedUrl = null;
unescapedUrl = SourceSpan.Empty;
whitespaceBeforeTitle = SourceSpan.Empty;
title = null;
unescapedTitle = null;
unescapedTitle = SourceSpan.Empty;
newline = Newline.None;
urlSpan = SourceSpan.Empty;
@@ -1209,10 +1206,12 @@ namespace Markdig.Helpers
urlHasPointyBrackets = false;
titleEnclosingCharacter = '\0';
if (!TryParseLabelWhitespace(ref text, out label, out labelWithWhitespace, out labelSpan))
labelWithWhitespace.Start = text.Start + 1; // skip opening [
if (!TryParseLabelWhitespace(ref text, out label, out labelSpan))
{
return false;
}
labelWithWhitespace.End = text.Start - 2; // skip closing ] and subsequent :
if (text.CurrentChar != ':')
{
@@ -1228,11 +1227,13 @@ namespace Markdig.Helpers
urlSpan.Start = text.Start;
bool isAngleBracketsUrl = text.CurrentChar == '<';
if (!TryParseUrlWhitespace(ref text, out url, out unescapedUrl, out urlHasPointyBrackets) || (!isAngleBracketsUrl && string.IsNullOrEmpty(url)))
unescapedUrl.Start = text.Start + (isAngleBracketsUrl ? 1 : 0);
if (!TryParseUrlWhitespace(ref text, out url, out urlHasPointyBrackets) || (!isAngleBracketsUrl && string.IsNullOrEmpty(url)))
{
return false;
}
urlSpan.End = text.Start - 1;
unescapedUrl.End = text.Start - 1 - (isAngleBracketsUrl ? 1 : 0);
int whitespaceBeforeTitleStart = text.Start;
var saved = text;
@@ -1243,9 +1244,11 @@ namespace Markdig.Helpers
if (c == '\'' || c == '"' || c == '(')
{
titleSpan.Start = text.Start;
if (TryParseTitleWhitespace(ref text, out title, out unescapedTitle, out titleEnclosingCharacter))
unescapedTitle.Start = text.Start + 1; // + 1; // skip opening enclosing character
if (TryParseTitleWhitespace(ref text, out title, out titleEnclosingCharacter))
{
titleSpan.End = text.Start - 1;
unescapedTitle.End = text.Start - 1 - 1; // skip closing enclosing character
// If we have a title, it requires a whitespace after the url
if (!hasWhiteSpaces)
{
@@ -1282,16 +1285,16 @@ namespace Markdig.Helpers
{
text = saved;
title = null;
unescapedTitle = null;
unescapedTitle = SourceSpan.Empty;
whitespaceAfterTitle = SourceSpan.Empty;
return true;
}
label = null;
url = null;
unescapedUrl = null;
unescapedUrl = SourceSpan.Empty;
title = null;
unescapedTitle = null;
unescapedTitle = SourceSpan.Empty;
return false;
}
whitespaceAfterTitle = new SourceSpan(whitespaceAfterTitleStart, text.Start - 1);
@@ -1334,9 +1337,9 @@ namespace Markdig.Helpers
return TryParseLabel(ref lines, false, out label, out labelSpan);
}
public static bool TryParseLabelWhitespace<T>(ref T lines, out string label, out string labelWithWhitespace, out SourceSpan labelSpan) where T : ICharIterator
public static bool TryParseLabelWhitespace<T>(ref T lines, out string label, out SourceSpan labelSpan) where T : ICharIterator
{
return TryParseLabelWhitespace(ref lines, false, out label, out labelWithWhitespace, out labelSpan);
return TryParseLabelWhitespace(ref lines, false, out label, out labelSpan);
}
public static bool TryParseLabel<T>(ref T lines, bool allowEmpty, out string label, out SourceSpan labelSpan) where T : ICharIterator
@@ -1454,10 +1457,9 @@ namespace Markdig.Helpers
return isValid;
}
public static bool TryParseLabelWhitespace<T>(ref T lines, bool allowEmpty, out string label, out string labelWithWhitespace, out SourceSpan labelSpan) where T : ICharIterator
public static bool TryParseLabelWhitespace<T>(ref T lines, bool allowEmpty, out string label, out SourceSpan labelSpan) where T : ICharIterator
{
label = null;
labelWithWhitespace = null;
char c = lines.CurrentChar;
labelSpan = SourceSpan.Empty;
if (c != '[')
@@ -1465,7 +1467,6 @@ namespace Markdig.Helpers
return false;
}
var buffer = StringBuilderCache.Local();
var bufferWhitespace = new StringBuilder();
var startLabel = -1;
var endLabel = -1;
@@ -1523,7 +1524,6 @@ namespace Markdig.Helpers
}
label = buffer.ToString();
labelWithWhitespace = bufferWhitespace.ToString();
isValid = true;
}
}
@@ -1541,7 +1541,6 @@ namespace Markdig.Helpers
startLabel = lines.Start;
}
hasEscape = true;
bufferWhitespace.Append(c);
}
else
{
@@ -1568,7 +1567,6 @@ namespace Markdig.Helpers
hasNonWhiteSpace = true;
}
}
bufferWhitespace.Append(c);
}
previousWhitespace = isWhitespace;
}

View File

@@ -42,9 +42,7 @@ namespace Markdig.Parsers.Inlines
}
}
string label;
string labelWithWhitespace = null;
SourceSpan labelSpan = SourceSpan.Empty;
SourceSpan labelWithWhitespaceSpan = SourceSpan.Empty;
switch (c)
{
case '[':
@@ -52,11 +50,14 @@ namespace Markdig.Parsers.Inlines
// 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.TryParseLabelWhitespace(ref slice, out label, out labelWithWhitespace, out labelSpan))
if (LinkHelper.TryParseLabelWhitespace(ref slice, out label, out labelSpan))
{
labelWithWhitespaceSpan.Start = labelSpan.Start; // skip opening [
labelWithWhitespaceSpan.End = labelSpan.End; // skip closing ]
if (!processor.Document.ContainsLinkReferenceDefinition(label))
{
label = null;
@@ -77,6 +78,8 @@ namespace Markdig.Parsers.Inlines
// Else we insert a LinkDelimiter
slice.NextChar();
//labelWithWhitespaceSpan = processor.GetSourcePositionFromLocalSpan(labelWithWhitespaceSpan);
var labelWithWhitespace = new StringSlice(slice.Text, labelWithWhitespaceSpan.Start, labelWithWhitespaceSpan.End);
processor.Inline = new LinkDelimiterInline(this)
{
Type = DelimiterType.Open,
@@ -111,8 +114,9 @@ namespace Markdig.Parsers.Inlines
private bool ProcessLinkReference(
InlineProcessor state,
StringSlice text,
string label,
string labelWithWhitespace,
SourceSpan labelWithWhitespaceSpan,
bool isShortcut,
SourceSpan labelSpan,
LinkDelimiterInline parent,
@@ -134,6 +138,7 @@ namespace Markdig.Parsers.Inlines
// Create a default link if the callback was not found
if (link == null)
{
var labelWithWhitespace = new StringSlice(text.Text, labelWithWhitespaceSpan.Start, labelWithWhitespaceSpan.End);
// Inline Link
link = new LinkInline()
{
@@ -234,9 +239,9 @@ namespace Markdig.Parsers.Inlines
if (LinkHelper.TryParseInlineLinkWhitespace(
ref text,
out string url,
out string unescapedUrl,
out SourceSpan unescapedUrlSpan,
out string title,
out string unescapedTitle,
out SourceSpan unescapedTitleSpan,
out char titleEnclosingCharacter,
out SourceSpan linkSpan,
out SourceSpan titleSpan,
@@ -248,6 +253,8 @@ namespace Markdig.Parsers.Inlines
var wsBeforeLink = new StringSlice(text.Text, whitespaceBeforeLink.Start, whitespaceBeforeLink.End);
var wsAfterLink = new StringSlice(text.Text, whitespaceAfterLink.Start, whitespaceAfterLink.End);
var wsAfterTitle = new StringSlice(text.Text, whitespaceAfterTitle.Start, whitespaceAfterTitle.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()
{
@@ -333,7 +340,7 @@ namespace Markdig.Parsers.Inlines
var labelSpan = SourceSpan.Empty;
string label = null;
string labelWithWhitespace = null;
SourceSpan labelWithWhitespace = SourceSpan.Empty;
bool isLabelSpanLocal = true;
bool isShortcut = false;
@@ -357,14 +364,15 @@ namespace Markdig.Parsers.Inlines
label = openParent.Label;
isShortcut = true;
}
if (label != null || LinkHelper.TryParseLabelWhitespace(ref text, true, out label, out labelWithWhitespace, out labelSpan))
if (label != null || LinkHelper.TryParseLabelWhitespace(ref text, true, out label, out labelSpan))
{
labelWithWhitespace = new SourceSpan(labelSpan.Start, labelSpan.End);
if (isLabelSpanLocal)
{
labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan);
}
if (ProcessLinkReference(inlineState, label, labelWithWhitespace, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1), localLabel))
if (ProcessLinkReference(inlineState, text, label, labelWithWhitespace, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1), localLabel))
{
// Remove the open parent
openParent.Remove();

View File

@@ -223,8 +223,11 @@ namespace Markdig.Parsers
ref iterator,
out LinkReferenceDefinition lrd,
out SourceSpan whitespaceBeforeLabel,
out SourceSpan labelWithWhitespace,
out SourceSpan whitespaceBeforeUrl,
out SourceSpan unescapedUrl,
out SourceSpan whitespaceBeforeTitle,
out SourceSpan unescapedTitle,
out SourceSpan whitespaceAfterTitle))
{
state.Document.SetLinkReferenceDefinition(lrd.Label, lrd, false);
@@ -237,16 +240,22 @@ namespace Markdig.Parsers
int startPosition = lines.Lines[0].Slice.Start;
whitespaceBeforeLabel = whitespaceBeforeLabel.MoveForward(startPosition);
labelWithWhitespace = labelWithWhitespace.MoveForward(startPosition);
whitespaceBeforeUrl = whitespaceBeforeUrl.MoveForward(startPosition);
unescapedUrl = unescapedUrl.MoveForward(startPosition);
whitespaceBeforeTitle = whitespaceBeforeTitle.MoveForward(startPosition);
unescapedTitle = unescapedTitle.MoveForward(startPosition);
whitespaceAfterTitle = whitespaceAfterTitle.MoveForward(startPosition);
lrd.Span = lrd.Span.MoveForward(startPosition);
lrd.BeforeWhitespace = new StringSlice(text, whitespaceBeforeLabel.Start, whitespaceBeforeLabel.End);
lrd.LabelSpan = lrd.LabelSpan.MoveForward(startPosition);
lrd.LabelWithWhitespace = new StringSlice(text, labelWithWhitespace.Start, labelWithWhitespace.End);
lrd.WhitespaceBeforeUrl = new StringSlice(text, whitespaceBeforeUrl.Start, whitespaceBeforeUrl.End);
lrd.UrlSpan = lrd.UrlSpan.MoveForward(startPosition);
lrd.UnescapedUrl = new StringSlice(text, unescapedUrl.Start, unescapedUrl.End);
lrd.WhitespaceBeforeTitle = new StringSlice(text, whitespaceBeforeTitle.Start, whitespaceBeforeTitle.End);
lrd.TitleSpan = lrd.TitleSpan.MoveForward(startPosition);
lrd.UnescapedTitle = new StringSlice(text, unescapedTitle.Start, unescapedTitle.End);
lrd.AfterWhitespace = new StringSlice(text, whitespaceAfterTitle.Start, whitespaceAfterTitle.End);
lrd.LinesBefore = paragraph.LinesBefore;

View File

@@ -52,7 +52,7 @@ namespace Markdig.Renderers.Roundtrip.Inlines
}
renderer.Write(link.WhitespaceAfterUrl);
if (!string.IsNullOrEmpty(link.UnescapedTitle))
if (!string.IsNullOrEmpty(link.Title))
{
var open = link.TitleEnclosingCharacter;
var close = link.TitleEnclosingCharacter;

View File

@@ -29,7 +29,7 @@ namespace Markdig.Renderers.Roundtrip
}
renderer.Write(linkDef.WhitespaceBeforeTitle);
if (linkDef.UnescapedTitle != null)
if (linkDef.Title != null)
{
var open = linkDef.TitleEnclosingCharacter;
var close = linkDef.TitleEnclosingCharacter;

View File

@@ -2,6 +2,7 @@
// 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.Parsers;
namespace Markdig.Syntax.Inlines
@@ -26,7 +27,7 @@ namespace Markdig.Syntax.Inlines
/// </summary>
public string Label { get; set; }
public string LabelWithWhitespace { get; set; }
public StringSlice LabelWithWhitespace { get; set; }
/// <summary>
/// The label span

View File

@@ -51,7 +51,7 @@ namespace Markdig.Syntax.Inlines
/// Gets or sets the URL.
/// </summary>
public string Url { get; set; }
public string UnescapedUrl { get; internal set; }
public StringSlice UnescapedUrl { get; internal set; }
public bool UrlHasPointyBrackets { get; set; }
@@ -67,7 +67,7 @@ namespace Markdig.Syntax.Inlines
/// Gets or sets the title.
/// </summary>
public string Title { get; set; }
public string UnescapedTitle { get; internal set; }
public StringSlice UnescapedTitle { get; internal set; }
public char TitleEnclosingCharacter { get; set; }
@@ -80,7 +80,7 @@ namespace Markdig.Syntax.Inlines
public string LinkRefDefLabel { get; set; }
public string LabelWithWhitespace { get; set; }
public StringSlice LabelWithWhitespace { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is an image link.
@@ -101,7 +101,7 @@ namespace Markdig.Syntax.Inlines
/// Gets or sets the reference this link is attached to. May be null.
/// </summary>
public LinkReferenceDefinition Reference { get; set; }
public string LinkRefDefLabelWithWhitespace { get; internal set; }
public StringSlice LinkRefDefLabelWithWhitespace { get; internal set; }
public LocalLabel LocalLabel { get; internal set; }
/// <summary>

View File

@@ -53,7 +53,7 @@ namespace Markdig.Syntax
/// <summary>
/// Non-normalized Label (includes whitespace)
/// </summary>
public string LabelWithWhitespace { get; set; }
public StringSlice LabelWithWhitespace { get; set; }
public StringSlice WhitespaceBeforeUrl { get; set; }
@@ -61,7 +61,7 @@ namespace Markdig.Syntax
/// Gets or sets the URL.
/// </summary>
public string Url { get; set; }
public string UnescapedUrl { get; set; }
public StringSlice UnescapedUrl { get; set; }
public bool UrlHasPointyBrackets { get; set; }
@@ -71,7 +71,7 @@ namespace Markdig.Syntax
/// Gets or sets the title.
/// </summary>
public string Title { get; set; }
public string UnescapedTitle { get; set; }
public StringSlice UnescapedTitle { get; set; }
public char TitleEnclosingCharacter { get; set; }
@@ -138,8 +138,11 @@ namespace Markdig.Syntax
ref T text,
out LinkReferenceDefinition block,
out SourceSpan whitespaceBeforeLabel,
out SourceSpan labelWithWhitespace,
out SourceSpan whitespaceBeforeUrl,
out SourceSpan unescapedUrl,
out SourceSpan whitespaceBeforeTitle,
out SourceSpan unescapedTitle,
out SourceSpan whitespaceAfterTitle) where T : ICharIterator
{
block = null;
@@ -150,14 +153,14 @@ namespace Markdig.Syntax
ref text,
out whitespaceBeforeLabel,
out string label,
out string labelWithWhitespace,
out labelWithWhitespace,
out whitespaceBeforeUrl,
out string url,
out string unescapedUrl,
out unescapedUrl,
out bool urlHasPointyBrackets,
out whitespaceBeforeTitle,
out string title,
out string unescapedTitle,
out unescapedTitle,
out char titleEnclosingCharacter,
out Newline newline,
out whitespaceAfterTitle,
@@ -172,11 +175,11 @@ namespace Markdig.Syntax
{
UrlHasPointyBrackets = urlHasPointyBrackets,
TitleEnclosingCharacter = titleEnclosingCharacter,
LabelWithWhitespace = labelWithWhitespace,
//LabelWithWhitespace = labelWithWhitespace,
LabelSpan = labelSpan,
UrlSpan = urlSpan,
UnescapedUrl = unescapedUrl,
UnescapedTitle = unescapedTitle,
//UnescapedUrl = unescapedUrl,
//UnescapedTitle = unescapedTitle,
TitleSpan = titleSpan,
Span = new SourceSpan(startSpan, titleSpan.End > 0 ? titleSpan.End : urlSpan.End),
Newline = newline,