/// <summary> /// This function can be called by any element parsing. Given a start and stopping point this will /// parse all found elements out of the range. /// </summary> /// <param name="markdown"></param> /// <param name="startingPos"></param> /// <param name="maxEndingPos"></param> protected void ParseInlineChildren(ref string markdown, int startingPos, int maxEndingPos) { int currentParsePosition = startingPos; while (currentParsePosition < maxEndingPos) { int nextElemntStart = 0; int nextElementEnd = 0; // Find the next element MarkdownInline element = Common.FindNextInlineElement(ref markdown, currentParsePosition, maxEndingPos, ref nextElemntStart, ref nextElementEnd); // If the element we found doesn't start at the position we are looking for there is text between the element and // the start. We need to wrap it into a Text Run if (nextElemntStart != currentParsePosition) { TextRunInline textRun = new TextRunInline(); textRun.Parse(ref markdown, currentParsePosition, nextElemntStart); Children.Add(textRun); } // Ask it to parse, it will return us the ending pos of itself. currentParsePosition = element.Parse(ref markdown, nextElemntStart, nextElementEnd); // Add it the the children Children.Add(element); } }
/// <summary> /// Called when the object should parse it's goods out of the markdown. The markdown, start, and stop are given. /// The start and stop are what is returned from the FindNext function below. The object should do it's parsing and /// return up to the last pos it used. This can be shorter than what is given to the function in endingPos. /// </summary> /// <param name="markdown">The markdown</param> /// <param name="startingPos">Where the parse should start</param> /// <param name="endingPos">Where the parse should end</param> /// <returns></returns> internal override int Parse(ref string markdown, int startingPos, int endingPos) { // Find all of the link parts int linkTextOpen = Common.IndexOf(ref markdown, '[', startingPos, endingPos); int linkTextClose = Common.IndexOf(ref markdown, ']', linkTextOpen, endingPos); int linkOpen = Common.IndexOf(ref markdown, '(', linkTextClose, endingPos); int linkClose = Common.IndexOf(ref markdown, ')', linkOpen, endingPos); // These should always be = if (linkTextOpen != startingPos) { DebuggingReporter.ReportCriticalError("link parse didn't find [ in at the starting pos"); } if (linkClose + 1 != endingPos) { DebuggingReporter.ReportCriticalError("link parse didn't find ] in at the end pos"); } // Make sure there is something to parse, and not just dead space linkTextOpen++; if (linkTextClose > linkTextOpen) { // Parse any children of this link element ParseInlineChildren(ref markdown, linkTextOpen, linkTextClose); } // We can't render links in links. So if anything in the children of this is a link // we have to remove it for(int count = 0; count < Children.Count; count++) { // Look through the children for a link, if found grab the text MarkdownInlineType type = ((MarkdownInline)Children[count]).Type; string replaceText = null; if (type == MarkdownInlineType.MarkdownLink) { // If it is a link just grab the URL. Ideally we would grab the text // but that is too hard and this will never happen. replaceText = ((MarkdownLinkInline)Children[count]).Url; } else if (type == MarkdownInlineType.RawHyperlink) { replaceText = ((RawHyperlinkInline)Children[count]).Url; } else if (type == MarkdownInlineType.RawSubreddit) { replaceText = ((RawSubredditInline)Children[count]).Text; } // If we found text to replace add a new text element as the text. if(replaceText != null) { TextRunInline textRun = new TextRunInline(); textRun.Text = replaceText; Children[count] = textRun; } } // Grab the link linkOpen++; Url = markdown.Substring(linkOpen, linkClose - linkOpen); // Return the point after the ) return linkClose + 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 && Common.IsWhiteSpace(markdown[pos])) { pos++; } if (pos == end) { return(null); } // Extract the URL. int urlStart = pos; while (pos < end && !Common.IsWhiteSpace(markdown[pos])) { pos++; } string url = TextRunInline.ResolveEscapeSequences(markdown, urlStart, pos); // Ignore leading '<' and trailing '>'. url = url.TrimStart('<').TrimEnd('>'); // Skip whitespace. pos++; while (pos < end && Common.IsWhiteSpace(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 && Common.IsWhiteSpace(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 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> /// Called when the object should parse it's goods out of the markdown. The markdown, start, and stop are given. /// The start and stop are what is returned from the FindNext function below. The object should do it's parsing and /// return up to the last pos it used. This can be shorter than what is given to the function in endingPos. /// </summary> /// <param name="markdown">The markdown</param> /// <param name="startingPos">Where the parse should start</param> /// <param name="endingPos">Where the parse should end</param> /// <returns></returns> internal override int Parse(ref string markdown, int startingPos, int endingPos) { // Find all of the link parts int linkTextOpen = Common.IndexOf(ref markdown, '[', startingPos, endingPos); int linkTextClose = Common.IndexOf(ref markdown, ']', linkTextOpen, endingPos); int linkOpen = Common.IndexOf(ref markdown, '(', linkTextClose, endingPos); int linkClose = Common.IndexOf(ref markdown, ')', linkOpen, endingPos); // These should always be = if (linkTextOpen != startingPos) { DebuggingReporter.ReportCriticalError("link parse didn't find [ in at the starting pos"); } if (linkClose + 1 != endingPos) { DebuggingReporter.ReportCriticalError("link parse didn't find ] in at the end pos"); } // Make sure there is something to parse, and not just dead space linkTextOpen++; if (linkTextClose > linkTextOpen) { // Parse any children of this link element ParseInlineChildren(ref markdown, linkTextOpen, linkTextClose); } // We can't render links in links. So if anything in the children of this is a link // we have to remove it for (int count = 0; count < Children.Count; count++) { // Look through the children for a link, if found grab the text MarkdownInlineType type = ((MarkdownInline)Children[count]).Type; string replaceText = null; if (type == MarkdownInlineType.MarkdownLink) { // If it is a link just grab the URL. Ideally we would grab the text // but that is too hard and this will never happen. replaceText = ((MarkdownLinkInline)Children[count]).Url; } else if (type == MarkdownInlineType.RawHyperlink) { replaceText = ((RawHyperlinkInline)Children[count]).Url; } else if (type == MarkdownInlineType.RawSubreddit) { replaceText = ((RawSubredditInline)Children[count]).Text; } // If we found text to replace add a new text element as the text. if (replaceText != null) { TextRunInline textRun = new TextRunInline(); textRun.Text = replaceText; Children[count] = textRun; } } // Grab the link linkOpen++; Url = markdown.Substring(linkOpen, linkClose - linkOpen); // Return the point after the ) return(linkClose + 1); }
/// <summary> /// Renders a text run 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 RenderTextRun(InlineCollection inlineCollection, TextRunInline element, TextElement parent, RenderContext context) { // Create the text run Run textRun = new Run(); textRun.Text = CollapseWhitespace(context, element.Text); // Add it inlineCollection.Add(textRun); }
/// <summary> /// Renders a text run 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 RenderTextRun(TextRunInline element, InlineCollection currentInlines, ref bool trimTextStart) { // Create the text run Run textRun = new Run(); textRun.Text = element.Text; // Check if we should trim the starting text. This allows us to trim the text starting a block // but nothing else. If we do a trim set it to false so no one else does. if(trimTextStart) { trimTextStart = false; textRun.Text = textRun.Text.TrimStart(); } // Add it currentInlines.Add(textRun); }