// 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); }
// 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; }
internal string MakeID(string str, int start, int len) { // Parse the string into a list of tokens Tokenize(str, start, len); StringBuilder sb = new StringBuilder(); foreach (var t in m_Tokens) { switch (t.type) { case TokenType.Text: sb.Append(str, t.startOffset, t.length); break; case TokenType.link: LinkInfo li = (LinkInfo)t.data; sb.Append(li.link_text); break; } FreeToken(t); } // Now clean it using the same rules as pandoc base.Reset(sb.ToString()); // Skip everything up to the first letter while (!eof) { if (Char.IsLetter(current)) { break; } SkipForward(1); } // Process all characters sb.Length = 0; while (!eof) { char ch = current; if (char.IsLetterOrDigit(ch) || ch == '_' || ch == '-' || ch == '.') { sb.Append(Char.ToLower(ch)); } else if (ch == ' ') { sb.Append("-"); } else if (IsLineEnd(ch)) { sb.Append("-"); SkipEol(); continue; } SkipForward(1); } return(sb.ToString()); }
// Render a list of tokens to a destinatino string builder. private void Render(StringBuilder sb, string str) { foreach (Token t in m_Tokens) { switch (t.type) { case TokenType.Text: // Append encoded text m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); break; case TokenType.HtmlTag: // Append html as is Utils.SmartHtmlEncodeAmps(sb, str, t.startOffset, t.length); break; case TokenType.Html: case TokenType.opening_mark: case TokenType.closing_mark: case TokenType.internal_mark: // Append html as is sb.Append(str, t.startOffset, t.length); break; case TokenType.br: sb.Append("<br />\n"); break; case TokenType.open_em: sb.Append("<em>"); break; case TokenType.close_em: sb.Append("</em>"); break; case TokenType.open_strong: sb.Append("<strong>"); break; case TokenType.close_strong: sb.Append("</strong>"); break; case TokenType.code_span: if (m_Markdown.FormatCodeSpan != null) { var encoded = new StringBuilder(); m_Markdown.HtmlEncode(encoded, str, t.startOffset, t.length); sb.Append(m_Markdown.FormatCodeSpan(m_Markdown, encoded.ToString())); } else { sb.Append("<code>"); m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); sb.Append("</code>"); } break; case TokenType.link: { LinkInfo li = (LinkInfo)t.data; var sf = new SpanFormatter(m_Markdown); sf.DisableLinks = true; li.def.RenderLink(m_Markdown, sb, sf.Format(li.link_text)); break; } case TokenType.img: { LinkInfo li = (LinkInfo)t.data; li.def.RenderImg(m_Markdown, sb, li.link_text); break; } case TokenType.footnote: { FootnoteReference r = (FootnoteReference)t.data; sb.Append("<sup id=\"fnref:"); sb.Append(r.id); sb.Append("\"><a href=\"#fn:"); sb.Append(r.id); sb.Append("\" rel=\"footnote\">"); sb.Append(r.index + 1); sb.Append("</a></sup>"); break; } case TokenType.abbreviation: { Abbreviation a = (Abbreviation)t.data; sb.Append("<abbr"); if (!String.IsNullOrEmpty(a.Title)) { sb.Append(" title=\""); m_Markdown.HtmlEncode(sb, a.Title, 0, a.Title.Length); sb.Append("\""); } sb.Append(">"); m_Markdown.HtmlEncode(sb, a.Abbr, 0, a.Abbr.Length); sb.Append("</abbr>"); break; } } FreeToken(t); } }
// 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 var li = new LinkInfo(link_def, link_text); m_Markdown.AddLinkInfo(li); return(CreateToken(token_type, li)); } // 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 var newLink = new LinkInfo(def, link_text); m_Markdown.AddLinkInfo(newLink); return(CreateToken(token_type, newLink)); }
// Render a list of tokens to a destinatino string builder. private void Render(StringBuilder sb, string str) { var reg = new Regex("<([^ >]+)"); var tags = new Stack <String>(); foreach (Token t in m_Tokens) { switch (t.type) { case TokenType.Text: // Append encoded text m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); break; case TokenType.HtmlTag: // Append html as is var tag = str.Substring(t.startOffset, t.length); var mtag = reg.Match(tag).Groups[1].Value; var mtag_l = mtag.ToLower(); if (mtag.StartsWith("/")) { while (tags.Count() > 0) { var ptag = tags.Pop(); if (ptag.ToLower() == mtag_l) { Utils.SmartHtmlEncodeAmps(sb, str, t.startOffset, t.length); break; } else { sb.Append("<" + ptag + ">"); } } } else { tags.Push("/" + mtag); Utils.SmartHtmlEncodeAmps(sb, str, t.startOffset, t.length); } break; case TokenType.Html: case TokenType.opening_mark: case TokenType.closing_mark: case TokenType.internal_mark: // Append html as is sb.Append(str, t.startOffset, t.length); break; case TokenType.br: sb.Append("<br />\n"); break; case TokenType.open_em: sb.Append("<em>"); break; case TokenType.close_em: sb.Append("</em>"); break; case TokenType.open_strong: sb.Append("<strong>"); break; case TokenType.close_strong: sb.Append("</strong>"); break; case TokenType.code_span: sb.Append("<code>"); m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); sb.Append("</code>"); break; case TokenType.link: { LinkInfo li = (LinkInfo)t.data; var sf = new SpanFormatter(m_Markdown); sf.DisableLinks = true; li.def.RenderLink(m_Markdown, sb, sf.Format(li.link_text)); break; } case TokenType.img: { LinkInfo li = (LinkInfo)t.data; li.def.RenderImg(m_Markdown, sb, li.link_text); break; } case TokenType.footnote: { FootnoteReference r = (FootnoteReference)t.data; sb.Append("<sup id=\"fnref:"); sb.Append(r.id); sb.Append("\"><a href=\"#fn:"); sb.Append(r.id); sb.Append("\" rel=\"footnote\">"); sb.Append(r.index + 1); sb.Append("</a></sup>"); break; } case TokenType.abbreviation: { Abbreviation a = (Abbreviation)t.data; sb.Append("<abbr"); if (!String.IsNullOrEmpty(a.Title)) { sb.Append(" title=\""); m_Markdown.HtmlEncode(sb, a.Title, 0, a.Title.Length); sb.Append("\""); } sb.Append(">"); m_Markdown.HtmlEncode(sb, a.Abbr, 0, a.Abbr.Length); sb.Append("</abbr>"); break; } } FreeToken(t); } while (tags.Count() > 0) { sb.Append("<" + tags.Pop() + ">"); } }
// Render a list of tokens to a destinatino string builder. private void Render(StringBuilder sb, string str) { foreach (Token t in m_Tokens) { switch (t.type) { case TokenType.Text: // Append encoded text m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); break; case TokenType.HtmlTag: // Append html as is Utils.SmartHtmlEncodeAmps(sb, str, t.startOffset, t.length); break; case TokenType.Html: case TokenType.opening_mark: case TokenType.closing_mark: case TokenType.internal_mark: // Append html as is sb.Append(str, t.startOffset, t.length); break; case TokenType.br: sb.Append("<br />\n"); break; case TokenType.open_em: sb.Append("<em>"); break; case TokenType.close_em: sb.Append("</em>"); break; case TokenType.open_strong: sb.Append("<strong>"); break; case TokenType.close_strong: sb.Append("</strong>"); break; case TokenType.code_span: sb.Append("<code>"); m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); sb.Append("</code>"); break; case TokenType.link: { LinkInfo li = (LinkInfo)t.data; li.RenderLink(m_Markdown, sb); break; } case TokenType.img: { LinkInfo li = (LinkInfo)t.data; li.RenderImage(m_Markdown, sb); break; } case TokenType.footnote: { FootnoteReference r = (FootnoteReference)t.data; sb.Append("<sup id=\"fnref:"); sb.Append(r.id); sb.Append("\"><a href=\"#fn:"); sb.Append(r.id); sb.Append("\" rel=\"footnote\">"); sb.Append(r.index + 1); sb.Append("</a></sup>"); break; } case TokenType.abbreviation: { Abbreviation a = (Abbreviation)t.data; sb.Append("<abbr"); if (!String.IsNullOrEmpty(a.Title)) { sb.Append(" title=\""); m_Markdown.HtmlEncode(sb, a.Title, 0, a.Title.Length); sb.Append("\""); } sb.Append(">"); m_Markdown.HtmlEncode(sb, a.Abbr, 0, a.Abbr.Length); sb.Append("</abbr>"); break; } // DocNet Extensions case TokenType.font_awesome: sb.Append("<i class=\"fas fa-"); m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); sb.Append("\"></i>"); break; // HnD Extensions case TokenType.strikethrough_span: sb.Append("<s>"); m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); sb.Append("</s>"); break; case TokenType.emoji: var emojiFilename = string.Empty; var emojiName = t.data as string ?? string.Empty; if (m_Markdown.EmojiFilePerName != null) { m_Markdown.EmojiFilePerName.TryGetValue(emojiName, out emojiFilename); } if (string.IsNullOrWhiteSpace(emojiFilename)) { sb.Append(":"); m_Markdown.HtmlEncode(sb, emojiName, 0, emojiName.Length); sb.Append(":"); } else { sb.Append("<img src=\""); sb.Append(emojiFilename); sb.Append("\" class=\"emoji\" alt=\""); sb.Append(emojiName); sb.Append("\"/>"); } break; } FreeToken(t); } }
internal void AddLinkInfo(LinkInfo link) { m_FoundLinks.Add(link); }
// 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 var li = new LinkInfo(link_def, link_text); m_Markdown.AddLinkInfo(li); return CreateToken(token_type, li); } // 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 var newLink = new LinkInfo(def, link_text); m_Markdown.AddLinkInfo(newLink); return CreateToken(token_type, newLink); }
// Render a list of tokens to a destinatino string builder. private void Render(StringBuilder sb, string str) { foreach (Token t in m_Tokens) { switch (t.type) { case TokenType.Text: // Append encoded text m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); break; case TokenType.HtmlTag: // Append html as is Utils.SmartHtmlEncodeAmps(sb, str, t.startOffset, t.length); break; case TokenType.Html: case TokenType.opening_mark: case TokenType.closing_mark: case TokenType.internal_mark: // Append html as is sb.Append(str, t.startOffset, t.length); break; case TokenType.br: sb.Append("<br />\n"); break; case TokenType.open_em: sb.Append("<em>"); break; case TokenType.close_em: sb.Append("</em>"); break; case TokenType.open_strong: sb.Append("<strong>"); break; case TokenType.close_strong: sb.Append("</strong>"); break; case TokenType.code_span: sb.Append("<code>"); m_Markdown.HtmlEncode(sb, str, t.startOffset, t.length); sb.Append("</code>"); break; case TokenType.link: { LinkInfo li = (LinkInfo)t.data; li.RenderLink(m_Markdown, sb); break; } case TokenType.img: { LinkInfo li = (LinkInfo)t.data; li.RenderImage(m_Markdown, sb); break; } case TokenType.footnote: { FootnoteReference r = (FootnoteReference)t.data; sb.Append("<sup id=\"fnref:"); sb.Append(r.id); sb.Append("\"><a href=\"#fn:"); sb.Append(r.id); sb.Append("\" rel=\"footnote\">"); sb.Append(r.index + 1); sb.Append("</a></sup>"); break; } case TokenType.abbreviation: { Abbreviation a = (Abbreviation)t.data; sb.Append("<abbr"); if (!String.IsNullOrEmpty(a.Title)) { sb.Append(" title=\""); m_Markdown.HtmlEncode(sb, a.Title, 0, a.Title.Length); sb.Append("\""); } sb.Append(">"); m_Markdown.HtmlEncode(sb, a.Abbr, 0, a.Abbr.Length); sb.Append("</abbr>"); break; } case TokenType.font_awesome: sb.Append("<i class=\"fa fa-"); sb.Append(str, t.startOffset, t.length); sb.Append("\"></i>"); break; } FreeToken(t); } }