// // MarkdownDeep - http://www.toptensoftware.com/markdowndeep // Copyright (C) 2010-2011 Topten Software // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in // compliance with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software distributed under the License is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and limitations under the License. // using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MarkdownDeep { public class LinkDefinition { public LinkDefinition(string id) { this.id= id; } public LinkDefinition(string id, string url) { this.id = id; this.url = url; } public LinkDefinition(string id, string url, string title) { this.id = id; this.url = url; this.title = title; } public string id { get; set; } public string url { get; set; } public string title { get; set; } internal void RenderLink(Markdown m, StringBuilder b, string link_text) { if (url.StartsWith("mailto:")) { b.Append("'); Utils.HtmlRandomize(b, link_text); b.Append(""); } else { HtmlTag tag = new HtmlTag("a"); // encode url StringBuilder sb = m.GetStringBuilder(); Utils.SmartHtmlEncodeAmpsAndAngles(sb, url); tag.attributes["href"] = sb.ToString(); // encode title if (!String.IsNullOrEmpty(title )) { sb.Length = 0; Utils.SmartHtmlEncodeAmpsAndAngles(sb, title); tag.attributes["title"] = sb.ToString(); } // Do user processing m.OnPrepareLink(tag); // Render the opening tag tag.RenderOpening(b); b.Append(link_text); // Link text already escaped by SpanFormatter b.Append(""); } } internal void RenderImg(Markdown m, StringBuilder b, string alt_text) { HtmlTag tag = new HtmlTag("img"); // encode url StringBuilder sb = m.GetStringBuilder(); Utils.SmartHtmlEncodeAmpsAndAngles(sb, url); tag.attributes["src"] = sb.ToString(); // encode alt text if (!String.IsNullOrEmpty(alt_text)) { sb.Length = 0; Utils.SmartHtmlEncodeAmpsAndAngles(sb, alt_text); tag.attributes["alt"] = sb.ToString(); } // encode title if (!String.IsNullOrEmpty(title)) { sb.Length = 0; Utils.SmartHtmlEncodeAmpsAndAngles(sb, title); tag.attributes["title"] = sb.ToString(); } tag.closed = true; m.OnPrepareImage(tag, m.RenderingTitledImage); tag.RenderOpening(b); } // Parse a link definition from a string (used by test cases) internal static LinkDefinition ParseLinkDefinition(string str, bool ExtraMode) { StringScanner p = new StringScanner(str); return ParseLinkDefinitionInternal(p, ExtraMode); } // Parse a link definition internal static LinkDefinition ParseLinkDefinition(StringScanner p, bool ExtraMode) { int savepos=p.Position; var l = ParseLinkDefinitionInternal(p, ExtraMode); if (l==null) p.Position = savepos; return l; } internal static LinkDefinition ParseLinkDefinitionInternal(StringScanner p, bool ExtraMode) { // Skip leading white space p.SkipWhitespace(); // Must start with an opening square bracket if (!p.SkipChar('[')) return null; // Extract the id p.Mark(); if (!p.Find(']')) return null; string id = p.Extract(); if (id.Length == 0) return null; if (!p.SkipString("]:")) return null; // Parse the url and title var link=ParseLinkTarget(p, id, ExtraMode); // and trailing whitespace p.SkipLinespace(); // Trailing crap, not a valid link reference... if (!p.Eol) return null; return link; } // Parse just the link target // For reference link definition, this is the bit after "[id]: thisbit" // For inline link, this is the bit in the parens: [link text](thisbit) internal static LinkDefinition ParseLinkTarget(StringScanner p, string id, bool ExtraMode) { // Skip whitespace p.SkipWhitespace(); // End of string? if (p.Eol) return null; // Create the link definition var r = new LinkDefinition(id); // Is the url enclosed in angle brackets if (p.SkipChar('<')) { // Extract the url p.Mark(); // Find end of the url while (p.Current != '>') { if (p.Eof) return null; p.SkipEscapableChar(ExtraMode); } string url = p.Extract(); if (!p.SkipChar('>')) return null; // Unescape it r.url = Utils.UnescapeString(url.Trim(), ExtraMode); // Skip whitespace p.SkipWhitespace(); } else { // Find end of the url p.Mark(); int paren_depth = 1; while (!p.Eol) { char ch=p.Current; if (char.IsWhiteSpace(ch)) break; if (id == null) { if (ch == '(') paren_depth++; else if (ch == ')') { paren_depth--; if (paren_depth==0) break; } } p.SkipEscapableChar(ExtraMode); } r.url = Utils.UnescapeString(p.Extract().Trim(), ExtraMode); } p.SkipLinespace(); // End of inline target if (p.DoesMatch(')')) return r; bool bOnNewLine = p.Eol; int posLineEnd = p.Position; if (p.Eol) { p.SkipEol(); p.SkipLinespace(); } // Work out what the title is delimited with char delim; switch (p.Current) { case '\'': case '\"': delim = p.Current; break; case '(': delim = ')'; break; default: if (bOnNewLine) { p.Position = posLineEnd; return r; } else return null; } // Skip the opening title delimiter p.SkipForward(1); // Find the end of the title p.Mark(); while (true) { if (p.Eol) return null; if (p.Current == delim) { if (delim != ')') { int savepos = p.Position; // Check for embedded quotes in title // Skip the quote and any trailing whitespace p.SkipForward(1); p.SkipLinespace(); // Next we expect either the end of the line for a link definition // or the close bracket for an inline link if ((id == null && p.Current != ')') || (id != null && !p.Eol)) { continue; } p.Position = savepos; } // End of title break; } p.SkipEscapableChar(ExtraMode); } // Store the title r.title = Utils.UnescapeString(p.Extract(), ExtraMode); // Skip closing quote p.SkipForward(1); // Done! return r; } } }