/// <summary> /// Attempts to parse a subscript text span. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location to start parsing. </param> /// <param name="maxEnd"> The location to stop parsing. </param> /// <returns> A parsed subscript text span, or <c>null</c> if this is not a subscript text span. </returns> internal static InlineParseResult Parse(string markdown, int start, int maxEnd) { // Check the first character. // e.g. "<sub>……</sub>" if (maxEnd - start < 5) { return(null); } if (markdown.Substring(start, 5) != "<sub>") { return(null); } int innerStart = start + 5; int innerEnd = Common.IndexOf(markdown, "</sub>", innerStart, maxEnd); // if don't have the end character or no character between start and end if (innerEnd == -1 || innerEnd == innerStart) { return(null); } // No match if the character after the caret is a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerStart]) || ParseHelpers.IsMarkdownWhiteSpace(markdown[innerEnd - 1])) { return(null); } // We found something! var result = new SubscriptTextInline(); result.Inlines = Common.ParseInlineChildren(markdown, innerStart, innerEnd); return(new InlineParseResult(result, start, innerEnd + 6)); }
internal static InlineParseResult Parse(string markdown, int start, int maxEnd) { if (start >= maxEnd - 1) { return(null); } // Check the start sequence. string startSequence = markdown.Substring(start, 1); if (startSequence != ":") { return(null); } // Find the end of the span. var innerStart = start + 1; int innerEnd = Common.IndexOf(markdown, startSequence, innerStart, maxEnd); if (innerEnd == -1) { return(null); } // The span must contain at least one character. if (innerStart == innerEnd) { return(null); } // The first character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerStart])) { return(null); } // The last character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerEnd - 1])) { return(null); } var emojiName = markdown.Substring(innerStart, innerEnd - innerStart); if (_emojiCodesDictionary.TryGetValue(emojiName, out var emojiCode)) { var result = new EmojiInline { Text = char.ConvertFromUtf32(emojiCode), Type = MarkdownInlineType.Emoji }; return(new InlineParseResult(result, start, innerEnd + 1)); } return(null); }
/// <summary> /// Attempts to parse a italic text span. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location to start parsing. </param> /// <param name="maxEnd"> The location to stop parsing. </param> /// <returns> A parsed italic text span, or <c>null</c> if this is not a italic text span. </returns> internal static InlineParseResult Parse(string markdown, int start, int maxEnd) { // Check the first char. char startChar = markdown[start]; if (start == maxEnd || (startChar != '*' && startChar != '_')) { return(null); } // Find the end of the span. The end character (either '*' or '_') must be the same as // the start character. var innerStart = start + 1; int innerEnd = Common.IndexOf(markdown, startChar, start + 1, maxEnd); if (innerEnd == -1) { return(null); } // The span must contain at least one character. if (innerStart == innerEnd) { return(null); } // The first character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerStart])) { return(null); } // The last character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerEnd - 1])) { return(null); } // Discord avoids italicising snake_case_for_some_reason if (startChar == '_' && innerEnd + 1 < maxEnd && !ParseHelpers.IsMarkdownWhiteSpace(markdown[innerEnd + 1])) { return(null); } // We found something! var result = new ItalicTextInline { Inlines = Common.ParseInlineChildren(markdown, innerStart, innerEnd) }; return(new InlineParseResult(result, start, innerEnd + 1)); }
/// <summary> /// Attempts to parse a bold text span. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location to start parsing. </param> /// <param name="maxEnd"> The location to stop parsing. </param> /// <returns> A parsed bold text span, or <c>null</c> if this is not a bold text span. </returns> internal static InlineParseResult Parse(string markdown, int start, int maxEnd) { if (start >= maxEnd - 1) { return(null); } // Check the start sequence. string startSequence = markdown.Substring(start, 2); if (startSequence != "**" && startSequence != "__") { return(null); } // Find the end of the span. The end sequence (either '**' or '__') must be the same // as the start sequence. var innerStart = start + 2; int innerEnd = Common.IndexOf(markdown, startSequence, innerStart, maxEnd); if (innerEnd == -1) { return(null); } // The span must contain at least one character. if (innerStart == innerEnd) { return(null); } // The first character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerStart])) { return(null); } // The last character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerEnd - 1])) { return(null); } // We found something! var result = new BoldTextInline { Inlines = Common.ParseInlineChildren(markdown, innerStart, innerEnd) }; return(new InlineParseResult(result, start, innerEnd + 2)); }
/// <summary> /// Attempts to parse a strikethrough text span. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location to start parsing. </param> /// <param name="maxEnd"> The location to stop parsing. </param> /// <returns> A parsed strikethrough text span, or <c>null</c> if this is not a strikethrough text span. </returns> internal static InlineParseResult Parse(string markdown, int start, int maxEnd) { // Check the start sequence. if (start >= maxEnd - 1 || markdown.Substring(start, 2) != "~~") { return(null); } // Find the end of the span. var innerStart = start + 2; int innerEnd = Common.IndexOf(markdown, "~~", innerStart, maxEnd); if (innerEnd == -1) { return(null); } // The span must contain at least one character. if (innerStart == innerEnd) { return(null); } // The first character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerStart])) { return(null); } // The last character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerEnd - 1])) { return(null); } // We found something! var result = new StrikethroughTextInline { Inlines = Common.ParseInlineChildren(markdown, innerStart, innerEnd) }; return(new InlineParseResult(result, start, innerEnd + 2)); }
/// <summary> /// Parsing helper method. /// </summary> private static void AppendTextToListItem(ListItemBlock listItem, string markdown, int start, int end, bool newLine = false) { ListItemBuilder listItemBuilder = null; if (listItem.Blocks.Count > 0) { listItemBuilder = listItem.Blocks[listItem.Blocks.Count - 1] as ListItemBuilder; } if (listItemBuilder == null) { // Add a new block. listItemBuilder = new ListItemBuilder(); listItem.Blocks.Add(listItemBuilder); } var builder = listItemBuilder.Builder; if (builder.Length >= 2 && ParseHelpers.IsMarkdownWhiteSpace(builder[builder.Length - 2]) && ParseHelpers.IsMarkdownWhiteSpace(builder[builder.Length - 1])) { builder.Length -= 2; builder.AppendLine(); } else if (builder.Length > 0) { builder.Append(' '); } if (newLine) { builder.Append(Environment.NewLine); } builder.Append(markdown.Substring(start, end - start)); }
/// <summary> /// Parses a horizontal rule. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location of the start of the line. </param> /// <param name="end"> The location of the end of the line. </param> /// <returns> A parsed horizontal rule block, or <c>null</c> if this is not a horizontal rule. </returns> internal static HorizontalRuleBlock Parse(string markdown, int start, int end) { // A horizontal rule is a line with at least 3 stars, optionally separated by spaces // OR a line with at least 3 dashes, optionally separated by spaces // OR a line with at least 3 underscores, optionally separated by spaces. char hrChar = '\0'; int hrCharCount = 0; int pos = start; while (pos < end) { char c = markdown[pos++]; if (c == '*' || c == '-' || c == '_') { // All of the non-whitespace characters on the line must match. if (hrCharCount > 0 && c != hrChar) { return(null); } hrChar = c; hrCharCount++; } else if (c == '\n') { break; } else if (!ParseHelpers.IsMarkdownWhiteSpace(c)) { return(null); } } // Hopefully there were at least 3 stars/dashes/underscores. return(hrCharCount >= 3 ? new HorizontalRuleBlock() : null); }
/// <summary> /// Attempts to parse an image e.g. "![Toolkit logo](https://raw.githubusercontent.com/Microsoft/WindowsCommunityToolkit/master/WamWooWam.Uwp.SampleApp/Assets/ToolkitLogo.png)". /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location to start parsing. </param> /// <param name="end"> The location to stop parsing. </param> /// <returns> A parsed markdown image, or <c>null</c> if this is not a markdown image. </returns> internal static InlineParseResult Parse(string markdown, int start, int end) { int refstart = 0; // Expect a '!' character. if (start >= end || markdown[start] != '!') { return(null); } int pos = start + 1; // Then a '[' character if (pos >= end || markdown[pos] != '[') { return(null); } pos++; // Find the ']' character while (pos < end) { if (markdown[pos] == ']') { break; } pos++; } if (pos == end) { return(null); } // Extract the alt. string tooltip = markdown.Substring(start + 2, pos - (start + 2)); // Expect the '(' character. pos++; string reference = string.Empty; string url = string.Empty; int imageWidth = 0; int imageHeight = 0; if (pos < end && markdown[pos] == '[') { refstart = pos; // Find the reference ']' character while (pos < end) { if (markdown[pos] == ']') { break; } pos++; } reference = markdown.Substring(refstart + 1, pos - refstart - 1); } else if (pos < end && markdown[pos] == '(') { while (pos < end && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos])) { pos++; } // Extract the URL. int urlStart = pos; while (pos < end && markdown[pos] != ')') { pos++; } var imageDimensionsPos = markdown.IndexOf(" =", urlStart, pos - urlStart, StringComparison.Ordinal); url = imageDimensionsPos > 0 ? TextRunInline.ResolveEscapeSequences(markdown, urlStart + 1, imageDimensionsPos) : TextRunInline.ResolveEscapeSequences(markdown, urlStart + 1, pos); if (imageDimensionsPos > 0) { // trying to find 'x' which separates image width and height var dimensionsSepatorPos = markdown.IndexOf("x", imageDimensionsPos + 2, pos - imageDimensionsPos - 1, StringComparison.Ordinal); // didn't find separator, trying to parse value as imageWidth if (dimensionsSepatorPos == -1) { var imageWidthStr = markdown.Substring(imageDimensionsPos + 2, pos - imageDimensionsPos - 2); int.TryParse(imageWidthStr, out imageWidth); } else { var dimensions = markdown.Substring(imageDimensionsPos + 2, pos - imageDimensionsPos - 2).Split('x'); // got width and height if (dimensions.Length == 2) { int.TryParse(dimensions[0], out imageWidth); int.TryParse(dimensions[1], out imageHeight); } } } } if (pos == end) { return(null); } // We found something! var result = new ImageInline { Tooltip = tooltip, RenderUrl = url, ReferenceId = reference, Url = url, Text = markdown.Substring(start, pos + 1 - start), ImageWidth = imageWidth, ImageHeight = imageHeight }; return(new InlineParseResult(result, start, pos + 1)); }
/// <summary> /// Attempts to parse a reference e.g. "[example]: http://www.reddit.com 'title'". /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location to start parsing. </param> /// <param name="end"> The location to stop parsing. </param> /// <returns> A parsed markdown link, or <c>null</c> if this is not a markdown link. </returns> internal static LinkReferenceBlock Parse(string markdown, int start, int end) { // Expect a '[' character. if (start >= end || markdown[start] != '[') { return(null); } // Find the ']' character int pos = start + 1; while (pos < end) { if (markdown[pos] == ']') { break; } pos++; } if (pos == end) { return(null); } // Extract the ID. string id = markdown.Substring(start + 1, pos - (start + 1)); // Expect the ':' character. pos++; if (pos == end || markdown[pos] != ':') { return(null); } // Skip whitespace pos++; while (pos < end && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos])) { pos++; } if (pos == end) { return(null); } // Extract the URL. int urlStart = pos; while (pos < end && !ParseHelpers.IsMarkdownWhiteSpace(markdown[pos])) { pos++; } string url = TextRunInline.ResolveEscapeSequences(markdown, urlStart, pos); // Ignore leading '<' and trailing '>'. url = url.TrimStart('<').TrimEnd('>'); // Skip whitespace. pos++; while (pos < end && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos])) { pos++; } string tooltip = null; if (pos < end) { // Extract the tooltip. char tooltipEndChar; switch (markdown[pos]) { case '(': tooltipEndChar = ')'; break; case '"': case '\'': tooltipEndChar = markdown[pos]; break; default: return(null); } pos++; int tooltipStart = pos; while (pos < end && markdown[pos] != tooltipEndChar) { pos++; } if (pos == end) { return(null); // No end character. } tooltip = markdown.Substring(tooltipStart, pos - tooltipStart); // Check there isn't any trailing text. pos++; while (pos < end && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos])) { pos++; } if (pos < end) { return(null); } } // We found something! var result = new LinkReferenceBlock(); result.Id = id; result.Url = url; result.Tooltip = tooltip; return(result); }
/// <summary> /// Attempts to parse a markdown link e.g. "[](http://www.reddit.com)". /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location to start parsing. </param> /// <param name="maxEnd"> The location to stop parsing. </param> /// <returns> A parsed markdown link, or <c>null</c> if this is not a markdown link. </returns> internal static InlineParseResult Parse(string markdown, int start, int maxEnd) { // Expect a '[' character. if (start == maxEnd || markdown[start] != '[') { return(null); } // Find the ']' character, keeping in mind that [test [0-9]](http://www.test.com) is allowed. int linkTextOpen = start + 1; int pos = linkTextOpen; int linkTextClose; int openSquareBracketCount = 0; while (true) { linkTextClose = markdown.IndexOfAny(new char[] { '[', ']' }, pos, maxEnd - pos); if (linkTextClose == -1) { return(null); } if (markdown[linkTextClose] == '[') { openSquareBracketCount++; } else if (openSquareBracketCount > 0) { openSquareBracketCount--; } else { break; } pos = linkTextClose + 1; } // Skip whitespace. pos = linkTextClose + 1; while (pos < maxEnd && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos])) { pos++; } if (pos == maxEnd) { return(null); } // Expect the '(' character or the '[' character. int linkOpen = pos; if (markdown[pos] == '(') { // Skip whitespace. linkOpen++; while (linkOpen < maxEnd && ParseHelpers.IsMarkdownWhiteSpace(markdown[linkOpen])) { linkOpen++; } // Find the ')' character. pos = linkOpen; int linkClose = -1; var openParenthesis = 0; while (pos < maxEnd) { if (markdown[pos] == ')') { if (openParenthesis == 0) { linkClose = pos; break; } else { openParenthesis--; } } if (markdown[pos] == '(') { openParenthesis++; } pos++; } if (pos >= maxEnd) { return(null); } int end = linkClose + 1; // Skip whitespace backwards. while (linkClose > linkOpen && ParseHelpers.IsMarkdownWhiteSpace(markdown[linkClose - 1])) { linkClose--; } // If there is no text whatsoever, then this is not a valid link. if (linkOpen == linkClose) { return(null); } // Check if there is tooltip text. string url; string tooltip = null; bool lastUrlCharIsDoubleQuote = markdown[linkClose - 1] == '"'; int tooltipStart = Common.IndexOf(markdown, " \"", linkOpen, linkClose - 1); if (tooltipStart == linkOpen) { return(null); } if (lastUrlCharIsDoubleQuote && tooltipStart != -1) { // Extract the URL (resolving any escape sequences). url = TextRunInline.ResolveEscapeSequences(markdown, linkOpen, tooltipStart).TrimEnd(' ', '\t', '\r', '\n'); tooltip = markdown.Substring(tooltipStart + 2, (linkClose - 1) - (tooltipStart + 2)); } else { // Extract the URL (resolving any escape sequences). url = TextRunInline.ResolveEscapeSequences(markdown, linkOpen, linkClose); } // Check the URL is okay. if (!url.IsEmail()) { if (!Common.IsUrlValid(url)) { return(null); } } else { tooltip = url = string.Format("mailto:{0}", url); } // We found a regular stand-alone link. var result = new MarkdownLinkInline(); result.Inlines = Common.ParseInlineChildren(markdown, linkTextOpen, linkTextClose, ignoreLinks: true); result.Url = url.Replace(" ", "%20"); result.Tooltip = tooltip; return(new InlineParseResult(result, start, end)); } else if (markdown[pos] == '[') { // Find the ']' character. int linkClose = Common.IndexOf(markdown, ']', pos + 1, maxEnd); if (linkClose == -1) { return(null); } // We found a reference-style link. var result = new MarkdownLinkInline(); result.Inlines = Common.ParseInlineChildren(markdown, linkTextOpen, linkTextClose, ignoreLinks: true); result.ReferenceId = markdown.Substring(linkOpen + 1, linkClose - (linkOpen + 1)); if (result.ReferenceId == string.Empty) { result.ReferenceId = markdown.Substring(linkTextOpen, linkTextClose - linkTextOpen); } return(new InlineParseResult(result, start, linkClose + 1)); } return(null); }
/// <summary> /// Parses the contents of the row, ignoring whitespace at the beginning and end of each cell. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="startingPos"> The position of the start of the row. </param> /// <param name="maxEndingPos"> The maximum position of the end of the row </param> /// <param name="quoteDepth"> The current nesting level for block quoting. </param> /// <param name="requireVerticalBar"> Indicates whether the line must contain a vertical bar. </param> /// <param name="contentParser"> Called for each cell. </param> /// <returns> The position of the start of the next line. </returns> internal static int ParseContents(string markdown, int startingPos, int maxEndingPos, int quoteDepth, bool requireVerticalBar, Action <int, int> contentParser) { // Skip quote characters. int pos = Common.SkipQuoteCharacters(markdown, startingPos, maxEndingPos, quoteDepth); // If the line starts with a '|' character, skip it. bool lineHasVerticalBar = false; if (pos < maxEndingPos && markdown[pos] == '|') { lineHasVerticalBar = true; pos++; } while (pos < maxEndingPos) { // Ignore any whitespace at the start of the cell (except for a newline character). while (pos < maxEndingPos && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos]) && markdown[pos] != '\n' && markdown[pos] != '\r') { pos++; } int startOfCellContent = pos; // Find the end of the cell. bool endOfLineFound = true; while (pos < maxEndingPos) { char c = markdown[pos]; if (c == '|') { lineHasVerticalBar = true; endOfLineFound = false; break; } if (c == '\n') { break; } if (c == '\r') { if (pos < maxEndingPos && markdown[pos + 1] == '\n') { pos++; // Swallow the complete linefeed. } break; } pos++; } int endOfCell = pos; // If a vertical bar is required, and none was found, then exit early. if (endOfLineFound && !lineHasVerticalBar && requireVerticalBar) { return(startingPos); } // Ignore any whitespace at the end of the cell. if (endOfCell > startOfCellContent) { while (ParseHelpers.IsMarkdownWhiteSpace(markdown[pos - 1])) { pos--; } } int endOfCellContent = pos; if (endOfLineFound == false || endOfCellContent > startOfCellContent) { // Parse the contents of the cell. contentParser(startOfCellContent, endOfCellContent); } // End of input? if (pos == maxEndingPos) { break; } // Move to the next cell, or the next line. pos = endOfCell + 1; // End of the line? if (endOfLineFound) { break; } } return(pos); }
/// <summary> /// Attempts to parse a superscript text span. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location to start parsing. </param> /// <param name="maxEnd"> The location to stop parsing. </param> /// <returns> A parsed superscript text span, or <c>null</c> if this is not a superscript text span. </returns> internal static InlineParseResult Parse(string markdown, int start, int maxEnd) { // Check the first character. bool isHTMLSequence = false; if (start == maxEnd || (markdown[start] != '^' && markdown[start] != '<')) { return(null); } if (markdown[start] != '^') { if (maxEnd - start < 5) { return(null); } else if (markdown.Substring(start, 5) != "<sup>") { return(null); } else { isHTMLSequence = true; } } if (isHTMLSequence) { int innerStart = start + 5; int innerEnd, end; innerEnd = Common.IndexOf(markdown, "</sup>", innerStart, maxEnd); if (innerEnd == -1) { return(null); } if (innerEnd == innerStart) { return(null); } if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerStart]) || ParseHelpers.IsMarkdownWhiteSpace(markdown[innerEnd - 1])) { return(null); } // We found something! end = innerEnd + 6; var result = new SuperscriptTextInline(); result.Inlines = Common.ParseInlineChildren(markdown, innerStart, innerEnd); return(new InlineParseResult(result, start, end)); } else { // The content might be enclosed in parentheses. int innerStart = start + 1; int innerEnd, end; if (innerStart < maxEnd && markdown[innerStart] == '(') { // Find the end parenthesis. innerStart++; innerEnd = Common.IndexOf(markdown, ')', innerStart, maxEnd); if (innerEnd == -1) { return(null); } end = innerEnd + 1; } else { // Search for the next whitespace character. innerEnd = Common.FindNextWhiteSpace(markdown, innerStart, maxEnd, ifNotFoundReturnLength: true); if (innerEnd == innerStart) { // No match if the character after the caret is a space. return(null); } end = innerEnd; } // We found something! var result = new SuperscriptTextInline(); result.Inlines = Common.ParseInlineChildren(markdown, innerStart, innerEnd); return(new InlineParseResult(result, start, end)); } }
internal static InlineParseResult Parse(string markdown, int start, int maxEnd) { if (start >= maxEnd - 1) { return(null); } // Check the start sequence. string startSequence = markdown.Substring(start, 1); if (startSequence == ":") { // Find the end of the span. var innerStart = start + 1; int innerEnd = Common.IndexOf(markdown, startSequence, innerStart, maxEnd); if (innerEnd == -1) { return(null); } // The span must contain at least one character. if (innerStart == innerEnd) { return(null); } // The first character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerStart])) { return(null); } // The last character inside the span must NOT be a space. if (ParseHelpers.IsMarkdownWhiteSpace(markdown[innerEnd - 1])) { return(null); } var emojiName = markdown.Substring(innerStart, innerEnd - innerStart); if (_emojiCodesDictionary.TryGetValue(emojiName, out var emojiCode)) { var result = new EmojiInline { Name = char.ConvertFromUtf32(emojiCode), Type = MarkdownInlineType.Emoji }; return(new InlineParseResult(result, start, innerEnd + 1)); } } else if (startSequence == "<" && maxEnd - start >= 3) { int innerStart = start + 1; int pos = -1; bool animated = false; if (string.Equals(markdown.Substring(innerStart, 1), ":", StringComparison.OrdinalIgnoreCase)) { // Emoji scheme found. pos = innerStart + 1; } else if (string.Equals(markdown.Substring(innerStart, 2), "a:", StringComparison.OrdinalIgnoreCase)) { // Animated emoji scheme found. pos = innerStart + 1; animated = true; } if (pos == -1) { return(null); } // Angle bracket links should not have any whitespace. int innerEnd = markdown.IndexOfAny(new char[] { ' ', '\t', '\r', '\n', '>' }, pos, maxEnd - pos); if (innerEnd == -1 || markdown[innerEnd] != '>') { return(null); } // There should be at least one character after the http://. if (innerEnd == pos) { return(null); } var substr = markdown.Substring(innerStart, innerEnd - innerStart); //Emoji markdown must have at least two colons, as it is <:name:id> int dotcnt = 0; foreach (char c in substr) { if (c == ':') { dotcnt++; } } if (dotcnt < 2) { return(null); } string name = ""; if (animated) { substr = substr.Remove(0, 1); } name = substr.Substring(0, substr.IndexOf(":", 1) + 1); var id = substr.Substring(name.Length, substr.Length - name.Length); var result = new EmojiInline { Name = name, Id = id, IsAnimated = animated, Type = MarkdownInlineType.Emoji }; return(new InlineParseResult(result, start, innerEnd + 1)); } return(null); }