예제 #1
0
        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);
        }
예제 #2
0
        /// <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));
        }
예제 #3
0
        /// <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);
            }

            // 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 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));
        }
예제 #5
0
        /// <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));
        }
예제 #6
0
        /// <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
            {
                Inlines = Common.ParseInlineChildren(markdown, innerStart, innerEnd)
            };

            return(new InlineParseResult(result, start, innerEnd + 6));
        }
예제 #7
0
        /// <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 = $"mailto:{url}";
                }

                // We found a regular stand-alone link.
                var result = new MarkdownLinkInline
                {
                    Inlines = Common.ParseInlineChildren(markdown, linkTextOpen, linkTextClose, ignoreLinks: true),
                    Url     = url.Replace(" ", "%20", StringComparison.Ordinal),
                    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
                {
                    Inlines     = Common.ParseInlineChildren(markdown, linkTextOpen, linkTextClose, ignoreLinks: true),
                    ReferenceId = markdown.Substring(linkOpen + 1, linkClose - (linkOpen + 1))
                };
                if (string.IsNullOrEmpty(result.ReferenceId))
                {
                    result.ReferenceId = markdown.Substring(linkTextOpen, linkTextClose - linkTextOpen);
                }

                return(new InlineParseResult(result, start, linkClose + 1));
            }

            return(null);
        }
        /// <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
                {
                    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
                {
                    Inlines = Common.ParseInlineChildren(markdown, innerStart, innerEnd)
                };
                return(new InlineParseResult(result, start, end));
            }
        }
예제 #9
0
        /// <summary>
        /// Attempts to parse an image e.g. "![Toolkit logo](https://raw.githubusercontent.com/windows-toolkit/WindowsCommunityToolkit/master/Microsoft.Toolkit.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)
        {
            // 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] == '[')
            {
                int 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));
        }
예제 #10
0
            /// <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 == '|' && (pos == 0 || markdown[pos - 1] != '\\'))
                        {
                            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);
            }