/// <summary> /// Returns whether the specified <see cref="DeliminatorInfo"/> is followed by a punctuation character. /// </summary> /// <param name="text">The string which contains <paramref name="info"/>.</param> /// <param name="info">The <see cref="DeliminatorInfo"/> object.</param> /// <returns> /// <c>true</c> when the specified <see cref="DeliminatorInfo"/> is followed by a punctuation character, /// otherwise <c>false</c>. /// </returns> private static bool IsFollowedByPunctuation(string text, DeliminatorInfo info) { if (text.Length <= info.Index + info.DeliminatorLength) { return(false); } return(MarkdownElementBase.asciiPunctuationChars .Contains(text[info.Index + info.DeliminatorLength])); }
/// <summary> /// Returns whether the specified deliminator is right flanking. /// See https://spec.commonmark.org/0.28/#right-flanking-delimiter-run for more information. /// </summary> /// <param name="text">The string object which contains <paramref name="info"/>.</param> /// <param name="info"> /// The <see cref="DeliminatorInfo"/> to determine whether right flanking or not. /// </param> /// <returns>Whether the specified deliminator is right flanking.</returns> private static bool IsRightFlanking(string text, DeliminatorInfo info) { var prevChar = info.Index == 0 ? ' ' : text[info.Index - 1]; var nextChar = info.Index + info.DeliminatorLength >= text.Length ? ' ' : text[info.Index + info.DeliminatorLength]; if (char.IsWhiteSpace(prevChar)) { return(false); } if (!MarkdownElementBase.asciiPunctuationChars.Contains(prevChar)) { return(true); } return(char.IsWhiteSpace(nextChar) || MarkdownElementBase.asciiPunctuationChars.Contains(nextChar)); }
/// <summary> /// Returns whether the specified <see cref="DeliminatorInfo"/> is preceded by a punctuation character. /// </summary> /// <param name="text">The string which contains <paramref name="info"/>.</param> /// <param name="info">The <see cref="DeliminatorInfo"/> object.</param> /// <returns> /// <c>true</c> when the specified <see cref="DeliminatorInfo"/> is preceded by a punctuation character, /// otherwise <c>false</c>. /// </returns> private static bool IsPrecededByPunctuation(string text, DeliminatorInfo info) { return(info.Index != 0 && MarkdownElementBase.asciiPunctuationChars.Contains(text[info.Index - 1])); }
/// <summary> /// Parses the structure and add the <see cref="InlineSpan"/> to <paramref name="delimSpans"/>. /// </summary> /// <param name="deliminators">The list of <see cref="DeliminatorInfo"/>.</param> /// <param name="delimSpans">The list of <see cref="InlineSpan"/>.</param> /// <param name="stackBottom">The stack bottom.</param> private static void ParseEmphasis(LinkedList <DeliminatorInfo> deliminators, SortedList <int, InlineSpan> delimSpans, DeliminatorInfo stackBottom) { if (deliminators.Count == 0) { return; } LinkedListNode <DeliminatorInfo> firstClose = null; LinkedListNode <DeliminatorInfo> infoNode = stackBottom == null ? deliminators.First : deliminators.Find(stackBottom); while (infoNode != null) { if (infoNode.Value.CanClose) { firstClose = infoNode; break; } infoNode = infoNode.Next; } if (firstClose == null) { return; } LinkedListNode <DeliminatorInfo> startDelimNode = null; infoNode = firstClose; while ((infoNode = infoNode.Previous) != null && infoNode.Value != stackBottom) { if (infoNode.Value.CanOpen && infoNode.Value.Type == firstClose.Value.Type && ((infoNode.Value.DeliminatorLength + firstClose.Value.DeliminatorLength) % 3 != 0 || infoNode.Value.DeliminatorLength % 3 == 0 || (!firstClose.Value.CanOpen && !infoNode.Value.CanClose))) { startDelimNode = infoNode; break; } } if (startDelimNode == null) { if (!firstClose.Value.CanOpen) { deliminators.Remove(firstClose); } firstClose = firstClose.Next; if (firstClose == null) { return; } ParseEmphasis(deliminators, delimSpans, firstClose.Value); return; } DeliminatorInfo openInfo = startDelimNode.Value; DeliminatorInfo closeInfo = firstClose.Value; int delimLength = openInfo.DeliminatorLength > 1 && closeInfo.DeliminatorLength > 1 ? 2 : 1; var delimSpan = new InlineSpan { Begin = openInfo.Index + openInfo.DeliminatorLength - delimLength, End = closeInfo.Index + delimLength, SpanType = delimLength > 1 ? InlineSpanType.StrongEmphasis : InlineSpanType.Emphasis, }; delimSpan.ParseBegin = delimSpan.Begin + delimLength; delimSpan.ParseEnd = delimSpan.End - delimLength; delimSpans.Add(delimSpan.Begin, delimSpan); while ((infoNode = infoNode.Next) != null && infoNode != firstClose) { deliminators.Remove(infoNode); } openInfo.DeliminatorLength -= delimLength; if (openInfo.DeliminatorLength <= 0) { deliminators.Remove(openInfo); } closeInfo.DeliminatorLength -= delimLength; closeInfo.Index += delimLength; if (closeInfo.DeliminatorLength <= 0) { deliminators.Remove(closeInfo); } ParseEmphasis(deliminators, delimSpans, deliminators.Find(firstClose.Value) == null ? firstClose.Next?.Value : firstClose.Value); }
/// <summary> /// Parses inline elements to Links, Images Emphasis and Line breaks. /// </summary> /// <param name="text">The string object to @arse.</param> /// <param name="higherSpans">Delim Spans which represents higher priority.</param> /// <returns>The parse result.</returns> private IEnumerable <InlineElement> ParseLinkEmphasis(string text, List <InlineSpan> higherSpans) { var deliminators = new LinkedList <DeliminatorInfo>(); var inlineSpans = new SortedList <int, InlineSpan>(); for (int i = 0; i < text.Length; i++) { // If the character is escaped or contained in higherSpans if (IsEscaped(text, i) || higherSpans.Any(d => d.Begin <= i && d.End > i)) { continue; } if (text[i] == '*') { int length = CountSameChars(text, i); var delimInfo = DeliminatorInfo.Create(DeliminatorType.Star, i, length); delimInfo.CanOpen = IsLeftFlanking(text, delimInfo); delimInfo.CanClose = IsRightFlanking(text, delimInfo); deliminators.AddLast(delimInfo); i += length - 1; } else if (text[i] == '_') { int length = CountSameChars(text, i); var delimInfo = DeliminatorInfo.Create(DeliminatorType.UnderBar, i, length); bool leftFranking = IsLeftFlanking(text, delimInfo); bool rightFlanking = IsRightFlanking(text, delimInfo); delimInfo.CanOpen = leftFranking && (!rightFlanking || IsPrecededByPunctuation(text, delimInfo)); delimInfo.CanClose = rightFlanking && (!leftFranking || IsFollowedByPunctuation(text, delimInfo)); deliminators.AddLast(delimInfo); i += length - 1; } else if (text[i] == '[') { deliminators.AddLast(DeliminatorInfo.Create(DeliminatorType.OpenLink, i, 1)); } else if (BeginWith(text, i, "![")) { deliminators.AddLast(DeliminatorInfo.Create(DeliminatorType.OpenImage, i, 2)); i++; } else if (i > 0 && text[i] == ']') { DeliminatorInfo openInfo = deliminators .LastOrDefault(info => info.Type == DeliminatorType.OpenImage || info.Type == DeliminatorType.OpenLink); if (openInfo == null) { continue; } if (!openInfo.Active) { deliminators.Remove(openInfo); continue; } int linkLabel = GetStartIndexOfLinkLabel(text, 0, i + 1, higherSpans); int linkBody = GetLinkBodyEndIndex(text, i + 1, out var dest, out var title); int linkLabel2 = GetEndIndexOfLinkLabel(text, i + 1, higherSpans); // Inline Link/Image if (text[Math.Min(i + 1, text.Length - 1)] == '(' && linkLabel >= 0 && linkBody >= 0) { inlineSpans.Add(openInfo.Index, InlineSpan.FromDeliminatorInfo(openInfo, linkBody, i, dest ?? string.Empty, title)); i = linkBody - 1; } // Collapsed Reference Link else if (BeginWith(text, i + 1, "[]") && TryGetReference(text.Substring(openInfo.Index + openInfo.DeliminatorLength, i - openInfo.Index - openInfo.DeliminatorLength), out var definition)) { inlineSpans.Add(openInfo.Index, InlineSpan.FromDeliminatorInfo(openInfo, i + 3, i, definition)); i += 2; } // Full Reference Link else if (linkLabel2 >= 0 && TryGetReference(text.Substring(i + 2, linkLabel2 - i - 3), out definition)) { inlineSpans.Add(openInfo.Index, InlineSpan.FromDeliminatorInfo(openInfo, linkLabel2, i, definition)); i = linkLabel2 - 1; } // shortcut link else if (TryGetReference(text.Substring(openInfo.Index + openInfo.DeliminatorLength, i - openInfo.Index - openInfo.DeliminatorLength), out definition) && GetEndIndexOfLinkLabel(text, i + 1, higherSpans) < 0) { inlineSpans.Add(openInfo.Index, InlineSpan.FromDeliminatorInfo(openInfo, i + 1, i, definition)); } else { deliminators.Remove(openInfo); continue; } if (openInfo.Type == DeliminatorType.OpenLink) { foreach (var item in deliminators.TakeWhile(c => c != openInfo)) { if (item.Type == DeliminatorType.OpenLink) { item.Active = false; } } } ParseEmphasis(deliminators, inlineSpans, openInfo); deliminators.Remove(openInfo); } } ParseEmphasis(deliminators, inlineSpans, null); foreach (var item in higherSpans) { inlineSpans.Add(item.Begin, item); } var tree = GetInlineTree(inlineSpans, text.Length); return(ToInlines(text, tree)); }