/// <summary>
        /// Verifies if the link is valid, before processing into a link, or plain text.
        /// </summary>
        /// <param name="element"> The parsed inline element to render. </param>
        /// <param name="context"> Persistent state. </param>
        protected void CheckRenderMarkdownLink(MarkdownLinkInline element, IRenderContext context)
        {
            // Avoid processing when link text is empty.
            if (element.Inlines.Count == 0)
            {
                return;
            }

            // Attempt to resolve references.
            element.ResolveReference(Document);
            if (element.Url == null)
            {
                // The element couldn't be resolved, just render it as text.
                RenderInlineChildren(element.Inlines, context);
                return;
            }

            foreach (MarkdownInline inline in element.Inlines)
            {
                if (inline is ImageInline imageInline)
                {
                    // this is an image, create Image.
                    if (!string.IsNullOrEmpty(imageInline.ReferenceId))
                    {
                        imageInline.ResolveReference(Document);
                    }

                    imageInline.Url = element.Url;
                    RenderImage(imageInline, context);
                    return;
                }
            }

            RenderMarkdownLink(element, context);
        }
Exemple #2
0
        static Common()
        {
            BoldItalicTextInline.AddTripChars(_triggerList);
            BoldTextInline.AddTripChars(_triggerList);
            ItalicTextInline.AddTripChars(_triggerList);
            MarkdownLinkInline.AddTripChars(_triggerList);
            HyperlinkInline.AddTripChars(_triggerList);
            CommentInline.AddTripChars(_triggerList);
            StrikethroughTextInline.AddTripChars(_triggerList);
            SuperscriptTextInline.AddTripChars(_triggerList);
            SubscriptTextInline.AddTripChars(_triggerList);
            CodeInline.AddTripChars(_triggerList);
            ImageInline.AddTripChars(_triggerList);
            EmojiInline.AddTripChars(_triggerList);
            LinkAnchorInline.AddTripChars(_triggerList);

            // Create an array of characters to search against using IndexOfAny.
            _tripCharacters = _triggerList.Select(trigger => trigger.FirstChar).Distinct().ToArray();
        }
        /// <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>
        /// Renders a link element
        /// </summary>
        /// <param name="element"> The parsed inline element to render. </param>
        /// <param name="context"> Persistent state. </param>
        protected override void RenderMarkdownLink(MarkdownLinkInline element, IRenderContext context)
        {
            var localContext = context as InlineRenderContext;

            if (localContext == null)
            {
                throw new RenderContextIncorrectException();
            }

            // HACK: Superscript is not allowed within a hyperlink.  But if we switch it around, so
            // that the superscript is outside the hyperlink, then it will render correctly.
            // This assumes that the entire hyperlink is to be rendered as superscript.
            if (AllTextIsSuperscript(element) == false)
            {
                // Regular ol' hyperlink.
                var link = new Hyperlink();

                // Register the link
                LinkRegister.RegisterNewHyperLink(link, element.Url);

                // Remove superscripts.
                RemoveSuperscriptRuns(element, insertCaret: true);

                // Render the children into the link inline.
                var childContext = new InlineRenderContext(link.Inlines, context)
                {
                    Parent          = link,
                    WithinHyperlink = true
                };

                if (localContext.OverrideForeground)
                {
                    link.Foreground = localContext.Foreground;
                }
                else if (LinkForeground != null)
                {
                    link.Foreground = LinkForeground;
                }

                RenderInlineChildren(element.Inlines, childContext);
                context.TrimLeadingWhitespace = childContext.TrimLeadingWhitespace;

                ToolTipService.SetToolTip(link, element.Tooltip ?? element.Url);

                // Add it to the current inlines
                localContext.InlineCollection.Add(link);
            }
            else
            {
                // THE HACK IS ON!

                // Create a fake superscript element.
                var fakeSuperscript = new SuperscriptTextInline
                {
                    Inlines = new List <MarkdownInline>
                    {
                        element
                    }
                };

                // Remove superscripts.
                RemoveSuperscriptRuns(element, insertCaret: false);

                // Now render it.
                RenderSuperscriptRun(fakeSuperscript, context);
            }
        }
Exemple #5
0
        /// <summary>
        /// Finds the next inline element by matching trip chars and verifying the match.
        /// </summary>
        /// <param name="markdown"> The markdown text to parse. </param>
        /// <param name="start"> The position to start parsing. </param>
        /// <param name="end"> The position to stop parsing. </param>
        /// <param name="ignoreLinks"> Indicates whether to parse links. </param>
        /// <returns>Returns the next element</returns>
        private static InlineParseResult FindNextInlineElement(string markdown, int start, int end, bool ignoreLinks)
        {
            // Search for the next inline sequence.
            for (int pos = start; pos < end; pos++)
            {
                // IndexOfAny should be the fastest way to skip characters we don't care about.
                pos = markdown.IndexOfAny(_tripCharacters, pos, end - pos);
                if (pos < 0)
                {
                    break;
                }

                // Find the trigger(s) that matched.
                char currentChar = markdown[pos];
                foreach (InlineTripCharHelper currentTripChar in _triggerList)
                {
                    // Check if our current char matches the suffix char.
                    if (currentChar == currentTripChar.FirstChar)
                    {
                        // Don't match if the previous character was a backslash.
                        if (pos > start && markdown[pos - 1] == '\\')
                        {
                            continue;
                        }

                        // If we are here we have a possible match. Call into the inline class to verify.
                        InlineParseResult parseResult = null;
                        switch (currentTripChar.Method)
                        {
                        case InlineParseMethod.BoldItalic:
                            parseResult = BoldItalicTextInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.Comment:
                            parseResult = CommentInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.LinkReference:
                            parseResult = LinkAnchorInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.Bold:
                            parseResult = BoldTextInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.Italic:
                            parseResult = ItalicTextInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.MarkdownLink:
                            if (!ignoreLinks)
                            {
                                parseResult = MarkdownLinkInline.Parse(markdown, pos, end);
                            }

                            break;

                        case InlineParseMethod.AngleBracketLink:
                            if (!ignoreLinks)
                            {
                                parseResult = HyperlinkInline.ParseAngleBracketLink(markdown, pos, end);
                            }

                            break;

                        case InlineParseMethod.Url:
                            if (!ignoreLinks)
                            {
                                parseResult = HyperlinkInline.ParseUrl(markdown, pos, end);
                            }

                            break;

                        case InlineParseMethod.RedditLink:
                            if (!ignoreLinks)
                            {
                                parseResult = HyperlinkInline.ParseRedditLink(markdown, pos, end);
                            }

                            break;

                        case InlineParseMethod.PartialLink:
                            if (!ignoreLinks)
                            {
                                parseResult = HyperlinkInline.ParsePartialLink(markdown, pos, end);
                            }

                            break;

                        case InlineParseMethod.Email:
                            if (!ignoreLinks)
                            {
                                parseResult = HyperlinkInline.ParseEmailAddress(markdown, start, pos, end);
                            }

                            break;

                        case InlineParseMethod.Strikethrough:
                            parseResult = StrikethroughTextInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.Superscript:
                            parseResult = SuperscriptTextInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.Subscript:
                            parseResult = SubscriptTextInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.Code:
                            parseResult = CodeInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.Image:
                            parseResult = ImageInline.Parse(markdown, pos, end);
                            break;

                        case InlineParseMethod.Emoji:
                            parseResult = EmojiInline.Parse(markdown, pos, end);
                            break;
                        }

                        if (parseResult != null)
                        {
                            return(parseResult);
                        }
                    }
                }
            }

            // If we didn't find any elements we have a normal text block.
            // Let us consume the entire range.
            return(new InlineParseResult(TextRunInline.Parse(markdown, start, end), start, end));
        }