/// <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 Common.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 && Common.IsWhiteSpace(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 && Common.IsWhiteSpace(markdown[linkOpen])) linkOpen++; // Find the ')' character. pos = linkOpen; int linkClose = -1; while (pos < maxEnd) { linkClose = Common.IndexOf(markdown, ')', pos, maxEnd); if (linkClose == -1) return null; if (markdown[linkClose - 1] != '\\') break; pos = linkClose + 1; } if (pos >= maxEnd) return null; int end = linkClose + 1; // Skip whitespace backwards. while (linkClose > linkOpen && Common.IsWhiteSpace(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 (!IsUrlValid(url)) return null; // 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 Common.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 Common.InlineParseResult(result, start, linkClose + 1); } return null; }
/// <summary> /// Renders a link element /// </summary> /// <param name="inlineCollection"> The list to add to. </param> /// <param name="element"> The parsed inline element to render. </param> /// <param name="parent"> The container element. </param> /// <param name="context"> Persistent state. </param> private void RenderMarkdownLink(InlineCollection inlineCollection, MarkdownLinkInline element, TextElement parent, RenderContext context) { // Avoid crash when link text is empty. if (element.Inlines.Count == 0) return; // Attempt to resolve references. element.ResolveReference(this.document); if (element.Url == null) { // The element couldn't be resolved, just render it as text. RenderInlineChildren(inlineCollection, element.Inlines, parent, context); return; } // 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 this.linkRegister.RegisterNewHyperLink(link, element.Url); // Remove superscripts. RemoveSuperscriptRuns(element, insertCaret: true); // Render the children into the link inline. var childContext = context.Clone(); childContext.WithinHyperlink = true; RenderInlineChildren(link.Inlines, element.Inlines, link, childContext); context.TrimLeadingWhitespace = childContext.TrimLeadingWhitespace; // Add it to the current inlines inlineCollection.Add(link); } else { // THE HACK IS ON! // Create a fake superscript element. var fakeSuperscript = new SuperscriptTextInline(); fakeSuperscript.Inlines = new List<MarkdownInline> { element }; // Remove superscripts. RemoveSuperscriptRuns(element, insertCaret: false); // Now render it. RenderSuperscriptRun(inlineCollection, fakeSuperscript, parent, context); } }
/// <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 Common.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 && Common.IsWhiteSpace(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 && Common.IsWhiteSpace(markdown[linkOpen])) { linkOpen++; } // Find the ')' character. pos = linkOpen; int linkClose = -1; while (pos < maxEnd) { linkClose = Common.IndexOf(markdown, ')', pos, maxEnd); if (linkClose == -1) { return(null); } if (markdown[linkClose - 1] != '\\') { break; } pos = linkClose + 1; } if (pos >= maxEnd) { return(null); } int end = linkClose + 1; // Skip whitespace backwards. while (linkClose > linkOpen && Common.IsWhiteSpace(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 (!IsUrlValid(url)) { return(null); } // 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 Common.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 Common.InlineParseResult(result, start, linkClose + 1)); } return(null); }
/// <summary> /// Renders a link element /// </summary> /// <param name="element"></param> /// <param name="currentInlines"></param> /// <param name="trimTextStart">If true this element should trin the start of the text and set to fales.</param> private void RenderMarkdownLink(MarkdownLinkInline element, InlineCollection currentInlines, ref bool trimTextStart) { // Create the text run Hyperlink link = new Hyperlink(); // Register the link m_linkRegister.RegisterNewHyperLink(link, element.Url); // Render the children into the link inline. RenderInlineChildren(element, link.Inlines, ref trimTextStart); // Add it to the current inlines currentInlines.Add(link); }