// Process '*', '**' or '_', '__' // This is horrible and probably much better done through regex, but I'm stubborn. // For normal cases this routine works as expected. For unusual cases (eg: overlapped // strong and emphasis blocks), the behaviour is probably not the same as the original // markdown scanner. /* * public Token ProcessEmphasisOld(ref Token prev_single, ref Token prev_double) * { * // Check whitespace before/after * bool bSpaceBefore = !bof && IsLineSpace(CharAtOffset(-1)); * bool bSpaceAfter = IsLineSpace(CharAtOffset(1)); * * // Ignore if surrounded by whitespace * if (bSpaceBefore && bSpaceAfter) * { * return null; * } * * // Save the current character and skip it * char ch = current; * Skip(1); * * // Do we have a previous matching single star? * if (!bSpaceBefore && prev_single != null) * { * // Yes, match them... * prev_single.type = TokenType.open_em; * prev_single = null; * return CreateToken(TokenType.close_em, position - 1, 1); * } * * // Is this a double star/under * if (current == ch) * { * // Skip second character * Skip(1); * * // Space after? * bSpaceAfter = IsLineSpace(current); * * // Space both sides? * if (bSpaceBefore && bSpaceAfter) * { * // Ignore it * return CreateToken(TokenType.Text, position - 2, 2); * } * * // Do we have a previous matching double * if (!bSpaceBefore && prev_double != null) * { * // Yes, match them * prev_double.type = TokenType.open_strong; * prev_double = null; * return CreateToken(TokenType.close_strong, position - 2, 2); * } * * if (!bSpaceAfter) * { * // Opening double star * prev_double = CreateToken(TokenType.Text, position - 2, 2); * return prev_double; * } * * // Ignore it * return CreateToken(TokenType.Text, position - 2, 2); * } * * // If there's a space before, we can open em * if (!bSpaceAfter) * { * // Opening single star * prev_single = CreateToken(TokenType.Text, position - 1, 1); * return prev_single; * } * * // Ignore * Skip(-1); * return null; * } */ // Process auto links eg: <google.com> Token ProcessAutoLink() { if (DisableLinks) { return(null); } // Skip the angle bracket and remember the start SkipForward(1); Mark(); bool ExtraMode = m_Markdown.ExtraMode; // Allow anything up to the closing angle, watch for escapable characters while (!eof) { char ch = current; // No whitespace allowed if (char.IsWhiteSpace(ch)) { break; } // End found? if (ch == '>') { string url = Utils.UnescapeString(Extract(), ExtraMode); LinkInfo li = null; if (Utils.IsEmailAddress(url)) { string link_text; if (url.StartsWith("mailto:")) { link_text = url.Substring(7); } else { link_text = url; url = "mailto:" + url; } li = new LinkInfo(new LinkDefinition("auto", url, null), link_text); } else if (Utils.IsWebAddress(url)) { li = new LinkInfo(new LinkDefinition("auto", url, null), url); } if (li != null) { SkipForward(1); return(CreateToken(TokenType.link, li)); } return(null); } this.SkipEscapableChar(ExtraMode); } // Didn't work return(null); }
// 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); }
// Process [link] and ![image] directives Token ProcessLinkOrImageOrFootnote() { // Link or image? TokenType token_type = SkipChar('!') ? TokenType.img : TokenType.link; // Opening '[' if (!SkipChar('[')) { return(null); } // Is it a foonote? var savepos = position; if (m_Markdown.ExtraMode && token_type == TokenType.link && SkipChar('^')) { SkipLinespace(); // Parse it string id; if (SkipFootnoteID(out id) && SkipChar(']')) { // Look it up and create footnote reference token int footnote_index = m_Markdown.ClaimFootnote(id); if (footnote_index >= 0) { // Yes it's a footnote return(CreateToken(TokenType.footnote, new FootnoteReference(footnote_index, id))); } } // Rewind position = savepos; } if (DisableLinks && token_type == TokenType.link) { return(null); } bool ExtraMode = m_Markdown.ExtraMode; // Find the closing square bracket, allowing for nesting, watching for // escapable characters Mark(); int depth = 1; while (!eof) { char ch = current; if (ch == '[') { depth++; } else if (ch == ']') { depth--; if (depth == 0) { break; } } this.SkipEscapableChar(ExtraMode); } // Quit if end if (eof) { return(null); } // Get the link text and unescape it string link_text = Utils.UnescapeString(Extract(), ExtraMode); // The closing ']' SkipForward(1); // Save position in case we need to rewind savepos = position; // Inline links must follow immediately if (SkipChar('(')) { // Extract the url and title var link_def = LinkDefinition.ParseLinkTarget(this, null, m_Markdown.ExtraMode); if (link_def == null) { return(null); } // Closing ')' SkipWhitespace(); if (!SkipChar(')')) { return(null); } // Create the token return(CreateToken(token_type, new LinkInfo(link_def, link_text))); } // Optional space or tab if (!SkipChar(' ')) { SkipChar('\t'); } // If there's line end, we're allow it and as must line space as we want // before the link id. if (eol) { SkipEol(); SkipLinespace(); } // Reference link? string link_id = null; if (current == '[') { // Skip the opening '[' SkipForward(1); // Find the start/end of the id Mark(); if (!Find(']')) { return(null); } // Extract the id link_id = Extract(); // Skip closing ']' SkipForward(1); } else { // Rewind to just after the closing ']' position = savepos; } // Link id not specified? if (string.IsNullOrEmpty(link_id)) { // Use the link text (implicit reference link) link_id = Utils.NormalizeLineEnds(link_text); // If the link text has carriage returns, normalize // to spaces if (!object.ReferenceEquals(link_id, link_text)) { while (link_id.Contains(" \n")) { link_id = link_id.Replace(" \n", "\n"); } link_id = link_id.Replace("\n", " "); } } // Find the link definition abort if not defined var def = m_Markdown.GetLinkDefinition(link_id); if (def == null) { return(null); } // Create a token return(CreateToken(token_type, new LinkInfo(def, link_text))); }