Cleaned up crappy public field naming/properties Added @alert support Added @fontawesome support Added teststags/0.9.0
| @@ -45,7 +45,8 @@ namespace Docnet | |||
| ExtraMode = true, | |||
| GitHubCodeBlocks = true, | |||
| AutoHeadingIDs = true, | |||
| NewWindowForExternalLinks = true | |||
| NewWindowForExternalLinks = true, | |||
| DocNetMode = true, | |||
| }; | |||
| #warning SET DocumentRoot and DocumentLocation for image features. | |||
| @@ -44,7 +44,7 @@ namespace MarkdownDeep | |||
| unsafe_html, // unsafe html that should be encoded | |||
| span, // an undecorated span of text (used for simple list items | |||
| // where content is not wrapped in paragraph tags | |||
| codeblock, // a code block (render only). If in github mode, `Data` contains the language name specified after the leading ```. | |||
| codeblock, // a code block (render only). If in githubcodeblocks mode, `Data` contains the language name specified after the leading ```. | |||
| li, // a list item (render only) | |||
| ol, // ordered list (render only) | |||
| ul, // unordered list (render only) | |||
| @@ -56,6 +56,9 @@ namespace MarkdownDeep | |||
| dl, // render only | |||
| footnote, // footnote definition eg: [^id] `data` holds the footnote id | |||
| p_footnote, // paragraph with footnote return link append. Return link string is in `data`. | |||
| // DocNet extensions | |||
| font_awesome, // Data is icon name | |||
| alert, // Data is the alert type specified. | |||
| } | |||
| class Block | |||
| @@ -67,18 +70,18 @@ namespace MarkdownDeep | |||
| internal Block(BlockType type) | |||
| { | |||
| blockType = type; | |||
| BlockType = type; | |||
| } | |||
| public string Content | |||
| { | |||
| get | |||
| { | |||
| switch (blockType) | |||
| switch (BlockType) | |||
| { | |||
| case BlockType.codeblock: | |||
| StringBuilder s = new StringBuilder(); | |||
| foreach (var line in children) | |||
| foreach (var line in Children) | |||
| { | |||
| s.Append(line.Content); | |||
| s.Append('\n'); | |||
| @@ -87,24 +90,27 @@ namespace MarkdownDeep | |||
| } | |||
| if (buf==null) | |||
| if (Buf==null) | |||
| return null; | |||
| else | |||
| return contentStart == -1 ? buf : buf.Substring(contentStart, contentLen); | |||
| return ContentStart == -1 ? Buf : Buf.Substring(ContentStart, ContentLen); | |||
| } | |||
| } | |||
| public int LineStart | |||
| /// <summary> | |||
| /// Gets the absolute line start: if line start is 0, it's returning the content start. | |||
| /// </summary> | |||
| public int LineStartAbsolute | |||
| { | |||
| get | |||
| { | |||
| return lineStart == 0 ? contentStart : lineStart; | |||
| return LineStart == 0 ? ContentStart : LineStart; | |||
| } | |||
| } | |||
| internal void RenderChildren(Markdown m, StringBuilder b) | |||
| { | |||
| foreach (var block in children) | |||
| foreach (var block in Children) | |||
| { | |||
| block.Render(m, b); | |||
| } | |||
| @@ -112,7 +118,7 @@ namespace MarkdownDeep | |||
| internal void RenderChildrenPlain(Markdown m, StringBuilder b) | |||
| { | |||
| foreach (var block in children) | |||
| foreach (var block in Children) | |||
| { | |||
| block.RenderPlain(m, b); | |||
| } | |||
| @@ -121,39 +127,39 @@ namespace MarkdownDeep | |||
| internal string ResolveHeaderID(Markdown m) | |||
| { | |||
| // Already resolved? | |||
| if (this.data!=null && this.data is string) | |||
| return (string)this.data; | |||
| if (this.Data!=null && this.Data is string) | |||
| return (string)this.Data; | |||
| // Approach 1 - PHP Markdown Extra style header id | |||
| int end=contentEnd; | |||
| string id = Utils.StripHtmlID(buf, contentStart, ref end); | |||
| int end=ContentEnd; | |||
| string id = Utils.StripHtmlID(Buf, ContentStart, ref end); | |||
| if (id != null) | |||
| { | |||
| contentEnd = end; | |||
| ContentEnd = end; | |||
| } | |||
| else | |||
| { | |||
| // Approach 2 - pandoc style header id | |||
| id = m.MakeUniqueHeaderID(buf, contentStart, contentLen); | |||
| id = m.MakeUniqueHeaderID(Buf, ContentStart, ContentLen); | |||
| } | |||
| this.data = id; | |||
| this.Data = id; | |||
| return id; | |||
| } | |||
| internal void Render(Markdown m, StringBuilder b) | |||
| { | |||
| switch (blockType) | |||
| switch (BlockType) | |||
| { | |||
| case BlockType.Blank: | |||
| return; | |||
| case BlockType.p: | |||
| m.SpanFormatter.FormatParagraph(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.FormatParagraph(b, Buf, ContentStart, ContentLen); | |||
| break; | |||
| case BlockType.span: | |||
| m.SpanFormatter.Format(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.Format(b, Buf, ContentStart, ContentLen); | |||
| b.Append("\n"); | |||
| break; | |||
| @@ -166,7 +172,7 @@ namespace MarkdownDeep | |||
| string id = string.Empty; | |||
| if (m.ExtraMode && !m.SafeMode) | |||
| { | |||
| b.Append("<" + blockType.ToString()); | |||
| b.Append("<" + BlockType.ToString()); | |||
| id = ResolveHeaderID(m); | |||
| if (!String.IsNullOrEmpty(id)) | |||
| { | |||
| @@ -181,53 +187,53 @@ namespace MarkdownDeep | |||
| } | |||
| else | |||
| { | |||
| b.Append("<" + blockType.ToString() + ">"); | |||
| b.Append("<" + BlockType.ToString() + ">"); | |||
| } | |||
| if(blockType == BlockType.h2 && !string.IsNullOrWhiteSpace(id)) | |||
| if(m.DocNetMode && BlockType == BlockType.h2 && !string.IsNullOrWhiteSpace(id)) | |||
| { | |||
| // collect h2 id + text in collector | |||
| var h2ContentSb = new StringBuilder(); | |||
| m.SpanFormatter.Format(h2ContentSb, buf, contentStart, contentLen); | |||
| m.SpanFormatter.Format(h2ContentSb, Buf, ContentStart, ContentLen); | |||
| var h2ContentAsString = h2ContentSb.ToString(); | |||
| b.Append(h2ContentAsString); | |||
| m.CreatedH2IdCollector.Add(new Tuple<string, string>(id, h2ContentAsString)); | |||
| } | |||
| else | |||
| { | |||
| m.SpanFormatter.Format(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.Format(b, Buf, ContentStart, ContentLen); | |||
| } | |||
| b.Append("</" + blockType.ToString() + ">\n"); | |||
| b.Append("</" + BlockType.ToString() + ">\n"); | |||
| break; | |||
| case BlockType.hr: | |||
| b.Append("<hr />\n"); | |||
| return; | |||
| case BlockType.user_break: | |||
| return; | |||
| case BlockType.ol_li: | |||
| case BlockType.ul_li: | |||
| b.Append("<li>"); | |||
| m.SpanFormatter.Format(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.Format(b, Buf, ContentStart, ContentLen); | |||
| b.Append("</li>\n"); | |||
| break; | |||
| case BlockType.dd: | |||
| b.Append("<dd>"); | |||
| if (children != null) | |||
| if (Children != null) | |||
| { | |||
| b.Append("\n"); | |||
| RenderChildren(m, b); | |||
| } | |||
| else | |||
| m.SpanFormatter.Format(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.Format(b, Buf, ContentStart, ContentLen); | |||
| b.Append("</dd>\n"); | |||
| break; | |||
| case BlockType.dt: | |||
| { | |||
| if (children == null) | |||
| if (Children == null) | |||
| { | |||
| foreach (var l in Content.Split('\n')) | |||
| { | |||
| @@ -252,38 +258,48 @@ namespace MarkdownDeep | |||
| return; | |||
| case BlockType.html: | |||
| b.Append(buf, contentStart, contentLen); | |||
| b.Append(Buf, ContentStart, ContentLen); | |||
| return; | |||
| case BlockType.unsafe_html: | |||
| m.HtmlEncode(b, buf, contentStart, contentLen); | |||
| m.HtmlEncode(b, Buf, ContentStart, ContentLen); | |||
| return; | |||
| case BlockType.codeblock: | |||
| if(m.FormatCodeBlockFunc == null) | |||
| { | |||
| var dataArgument = this.data as string ?? string.Empty; | |||
| var dataArgument = this.Data as string ?? string.Empty; | |||
| string tagSuffix = "</code></pre>\n\n"; | |||
| if(m.GitHubCodeBlocks && !string.IsNullOrWhiteSpace(dataArgument)) | |||
| { | |||
| b.AppendFormat("<pre><code class=\"{0}\">", dataArgument); | |||
| if(dataArgument == "nohighlight") | |||
| { | |||
| b.Append("<pre class=\"nocode\">"); | |||
| tagSuffix = "</pre>"; | |||
| } | |||
| else | |||
| { | |||
| b.AppendFormat("<pre><code class=\"{0}\">", dataArgument); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| b.Append("<pre><code>"); | |||
| } | |||
| foreach(var line in children) | |||
| foreach(var line in Children) | |||
| { | |||
| m.HtmlEncodeAndConvertTabsToSpaces(b, line.buf, line.contentStart, line.contentLen); | |||
| m.HtmlEncodeAndConvertTabsToSpaces(b, line.Buf, line.ContentStart, line.ContentLen); | |||
| b.Append("\n"); | |||
| } | |||
| b.Append("</code></pre>\n\n"); | |||
| b.Append(tagSuffix); | |||
| } | |||
| else | |||
| { | |||
| var sb = new StringBuilder(); | |||
| foreach(var line in children) | |||
| foreach(var line in Children) | |||
| { | |||
| m.HtmlEncodeAndConvertTabsToSpaces(sb, line.buf, line.contentStart, line.contentLen); | |||
| m.HtmlEncodeAndConvertTabsToSpaces(sb, line.Buf, line.ContentStart, line.ContentLen); | |||
| sb.Append("\n"); | |||
| } | |||
| b.Append(m.FormatCodeBlockFunc(m, sb.ToString())); | |||
| @@ -315,7 +331,7 @@ namespace MarkdownDeep | |||
| return; | |||
| case BlockType.HtmlTag: | |||
| var tag = (HtmlTag)data; | |||
| var tag = (HtmlTag)Data; | |||
| // Prepare special tags | |||
| var name=tag.name.ToLowerInvariant(); | |||
| @@ -341,38 +357,55 @@ namespace MarkdownDeep | |||
| return; | |||
| case BlockType.table_spec: | |||
| ((TableSpec)data).Render(m, b); | |||
| ((TableSpec)Data).Render(m, b); | |||
| break; | |||
| case BlockType.p_footnote: | |||
| b.Append("<p>"); | |||
| if (contentLen > 0) | |||
| if (ContentLen > 0) | |||
| { | |||
| m.SpanFormatter.Format(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.Format(b, Buf, ContentStart, ContentLen); | |||
| b.Append(" "); | |||
| } | |||
| b.Append((string)data); | |||
| b.Append((string)Data); | |||
| b.Append("</p>\n"); | |||
| break; | |||
| // DocNet Extensions | |||
| case BlockType.font_awesome: | |||
| if(m.DocNetMode) | |||
| { | |||
| b.Append("<i class=\"fa fa-"); | |||
| b.Append(this.Data as string ?? string.Empty); | |||
| b.Append("\"></i>"); | |||
| } | |||
| break; | |||
| case BlockType.alert: | |||
| if(m.DocNetMode) | |||
| { | |||
| RenderAlert(m, b); | |||
| } | |||
| break; | |||
| // End DocNet Extensions | |||
| default: | |||
| b.Append("<" + blockType.ToString() + ">"); | |||
| m.SpanFormatter.Format(b, buf, contentStart, contentLen); | |||
| b.Append("</" + blockType.ToString() + ">\n"); | |||
| b.Append("<" + BlockType.ToString() + ">"); | |||
| m.SpanFormatter.Format(b, Buf, ContentStart, ContentLen); | |||
| b.Append("</" + BlockType.ToString() + ">\n"); | |||
| break; | |||
| } | |||
| } | |||
| internal void RenderPlain(Markdown m, StringBuilder b) | |||
| { | |||
| switch (blockType) | |||
| switch (BlockType) | |||
| { | |||
| case BlockType.Blank: | |||
| return; | |||
| case BlockType.p: | |||
| case BlockType.span: | |||
| m.SpanFormatter.FormatPlain(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.FormatPlain(b, Buf, ContentStart, ContentLen); | |||
| b.Append(" "); | |||
| break; | |||
| @@ -382,7 +415,7 @@ namespace MarkdownDeep | |||
| case BlockType.h4: | |||
| case BlockType.h5: | |||
| case BlockType.h6: | |||
| m.SpanFormatter.FormatPlain(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.FormatPlain(b, Buf, ContentStart, ContentLen); | |||
| b.Append(" - "); | |||
| break; | |||
| @@ -390,23 +423,23 @@ namespace MarkdownDeep | |||
| case BlockType.ol_li: | |||
| case BlockType.ul_li: | |||
| b.Append("* "); | |||
| m.SpanFormatter.FormatPlain(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.FormatPlain(b, Buf, ContentStart, ContentLen); | |||
| b.Append(" "); | |||
| break; | |||
| case BlockType.dd: | |||
| if (children != null) | |||
| if (Children != null) | |||
| { | |||
| b.Append("\n"); | |||
| RenderChildrenPlain(m, b); | |||
| } | |||
| else | |||
| m.SpanFormatter.FormatPlain(b, buf, contentStart, contentLen); | |||
| m.SpanFormatter.FormatPlain(b, Buf, ContentStart, ContentLen); | |||
| break; | |||
| case BlockType.dt: | |||
| { | |||
| if (children == null) | |||
| if (Children == null) | |||
| { | |||
| foreach (var l in Content.Split('\n')) | |||
| { | |||
| @@ -426,9 +459,9 @@ namespace MarkdownDeep | |||
| return; | |||
| case BlockType.codeblock: | |||
| foreach (var line in children) | |||
| foreach (var line in Children) | |||
| { | |||
| b.Append(line.buf, line.contentStart, line.contentLen); | |||
| b.Append(line.Buf, line.ContentStart, line.ContentLen); | |||
| b.Append(" "); | |||
| } | |||
| return; | |||
| @@ -445,34 +478,72 @@ namespace MarkdownDeep | |||
| public void RevertToPlain() | |||
| { | |||
| blockType = BlockType.p; | |||
| contentStart = lineStart; | |||
| contentLen = lineLen; | |||
| BlockType = BlockType.p; | |||
| ContentStart = LineStart; | |||
| ContentLen = LineLen; | |||
| } | |||
| public int contentEnd | |||
| private void RenderAlert(Markdown m, StringBuilder b) | |||
| { | |||
| var alertType = this.Data as string; | |||
| if(string.IsNullOrWhiteSpace(alertType)) | |||
| { | |||
| alertType = "info"; | |||
| } | |||
| string title = string.Empty; | |||
| string faIconName = string.Empty; | |||
| switch(alertType) | |||
| { | |||
| case "danger": | |||
| title = "Danger!"; | |||
| faIconName = "times-circle"; | |||
| break; | |||
| case "warning": | |||
| title = "Warning!"; | |||
| faIconName = "warning"; | |||
| break; | |||
| case "neutral": | |||
| case "info": | |||
| title = "Info"; | |||
| faIconName = "info-circle"; | |||
| break; | |||
| } | |||
| b.Append("<div class=\"alert alert-"); | |||
| b.Append(alertType); | |||
| b.Append("\"><span class=\"alert-title\"><i class=\"fa fa-"); | |||
| b.Append(faIconName); | |||
| b.Append("\"></i>"); | |||
| b.Append(title); | |||
| b.Append("</span>"); | |||
| RenderChildren(m, b); | |||
| b.Append("</div>"); | |||
| } | |||
| #region Properties | |||
| public int ContentEnd | |||
| { | |||
| get | |||
| { | |||
| return contentStart + contentLen; | |||
| return ContentStart + ContentLen; | |||
| } | |||
| set | |||
| { | |||
| contentLen = value - contentStart; | |||
| ContentLen = value - ContentStart; | |||
| } | |||
| } | |||
| // Count the leading spaces on a block | |||
| // Used by list item evaluation to determine indent levels | |||
| // irrespective of indent line type. | |||
| public int leadingSpaces | |||
| public int LeadingSpaces | |||
| { | |||
| get | |||
| { | |||
| int count = 0; | |||
| for (int i = lineStart; i < lineStart + lineLen; i++) | |||
| for (int i = LineStart; i < LineStart + LineLen; i++) | |||
| { | |||
| if (buf[i] == ' ') | |||
| if (Buf[i] == ' ') | |||
| { | |||
| count++; | |||
| } | |||
| @@ -488,27 +559,30 @@ namespace MarkdownDeep | |||
| public override string ToString() | |||
| { | |||
| string c = Content; | |||
| return blockType.ToString() + " - " + (c==null ? "<null>" : c); | |||
| return BlockType.ToString() + " - " + (c==null ? "<null>" : c); | |||
| } | |||
| public Block CopyFrom(Block other) | |||
| { | |||
| blockType = other.blockType; | |||
| buf = other.buf; | |||
| contentStart = other.contentStart; | |||
| contentLen = other.contentLen; | |||
| lineStart = other.lineStart; | |||
| lineLen = other.lineLen; | |||
| BlockType = other.BlockType; | |||
| Buf = other.Buf; | |||
| ContentStart = other.ContentStart; | |||
| ContentLen = other.ContentLen; | |||
| LineStart = other.LineStart; | |||
| LineLen = other.LineLen; | |||
| return this; | |||
| } | |||
| internal BlockType blockType; | |||
| internal string buf; | |||
| internal int contentStart; | |||
| internal int contentLen; | |||
| internal int lineStart; | |||
| internal int lineLen; | |||
| internal object data; // content depends on block type | |||
| internal List<Block> children; | |||
| internal BlockType BlockType { get; set; } | |||
| internal string Buf { get; set; } | |||
| internal int ContentStart { get; set; } | |||
| internal int ContentLen { get; set; } | |||
| internal int LineStart { get; set; } | |||
| internal int LineLen { get; set; } | |||
| internal object Data { get; set; } // content depends on block type | |||
| internal List<Block> Children { get; set; } | |||
| #endregion | |||
| } | |||
| } | |||
| @@ -208,7 +208,7 @@ namespace MarkdownDeep | |||
| if (ret!=null) | |||
| { | |||
| pos = sp.position; | |||
| pos = sp.Position; | |||
| return ret; | |||
| } | |||
| @@ -218,7 +218,7 @@ namespace MarkdownDeep | |||
| public static HtmlTag Parse(StringScanner p) | |||
| { | |||
| // Save position | |||
| int savepos = p.position; | |||
| int savepos = p.Position; | |||
| // Parse it | |||
| var ret = ParseHelper(p); | |||
| @@ -226,14 +226,14 @@ namespace MarkdownDeep | |||
| return ret; | |||
| // Rewind if failed | |||
| p.position = savepos; | |||
| p.Position = savepos; | |||
| return null; | |||
| } | |||
| private static HtmlTag ParseHelper(StringScanner p) | |||
| { | |||
| // Does it look like a tag? | |||
| if (p.current != '<') | |||
| if (p.Current != '<') | |||
| return null; | |||
| // Skip '<' | |||
| @@ -270,7 +270,7 @@ namespace MarkdownDeep | |||
| // If it's a closing tag, no attributes | |||
| if (bClosing) | |||
| { | |||
| if (p.current != '>') | |||
| if (p.Current != '>') | |||
| return null; | |||
| p.SkipForward(1); | |||
| @@ -278,7 +278,7 @@ namespace MarkdownDeep | |||
| } | |||
| while (!p.eof) | |||
| while (!p.Eof) | |||
| { | |||
| // Skip whitespace | |||
| p.SkipWhitespace(); | |||
| @@ -328,10 +328,10 @@ namespace MarkdownDeep | |||
| { | |||
| // Scan the value | |||
| p.Mark(); | |||
| while (!p.eof && !char.IsWhiteSpace(p.current) && p.current != '>' && p.current != '/') | |||
| while (!p.Eof && !char.IsWhiteSpace(p.Current) && p.Current != '>' && p.Current != '/') | |||
| p.SkipForward(1); | |||
| if (!p.eof) | |||
| if (!p.Eof) | |||
| { | |||
| // Store the value | |||
| tag.m_attributes.Add(attributeName, p.Extract()); | |||
| @@ -146,10 +146,10 @@ namespace MarkdownDeep | |||
| // Parse a link definition | |||
| internal static LinkDefinition ParseLinkDefinition(StringScanner p, bool ExtraMode) | |||
| { | |||
| int savepos=p.position; | |||
| int savepos=p.Position; | |||
| var l = ParseLinkDefinitionInternal(p, ExtraMode); | |||
| if (l==null) | |||
| p.position = savepos; | |||
| p.Position = savepos; | |||
| return l; | |||
| } | |||
| @@ -180,7 +180,7 @@ namespace MarkdownDeep | |||
| p.SkipLinespace(); | |||
| // Trailing crap, not a valid link reference... | |||
| if (!p.eol) | |||
| if (!p.Eol) | |||
| return null; | |||
| return link; | |||
| @@ -195,7 +195,7 @@ namespace MarkdownDeep | |||
| p.SkipWhitespace(); | |||
| // End of string? | |||
| if (p.eol) | |||
| if (p.Eol) | |||
| return null; | |||
| // Create the link definition | |||
| @@ -208,9 +208,9 @@ namespace MarkdownDeep | |||
| p.Mark(); | |||
| // Find end of the url | |||
| while (p.current != '>') | |||
| while (p.Current != '>') | |||
| { | |||
| if (p.eof) | |||
| if (p.Eof) | |||
| return null; | |||
| p.SkipEscapableChar(ExtraMode); | |||
| } | |||
| @@ -230,9 +230,9 @@ namespace MarkdownDeep | |||
| // Find end of the url | |||
| p.Mark(); | |||
| int paren_depth = 1; | |||
| while (!p.eol) | |||
| while (!p.Eol) | |||
| { | |||
| char ch=p.current; | |||
| char ch=p.Current; | |||
| if (char.IsWhiteSpace(ch)) | |||
| break; | |||
| if (id == null) | |||
| @@ -259,9 +259,9 @@ namespace MarkdownDeep | |||
| if (p.DoesMatch(')')) | |||
| return r; | |||
| bool bOnNewLine = p.eol; | |||
| int posLineEnd = p.position; | |||
| if (p.eol) | |||
| bool bOnNewLine = p.Eol; | |||
| int posLineEnd = p.Position; | |||
| if (p.Eol) | |||
| { | |||
| p.SkipEol(); | |||
| p.SkipLinespace(); | |||
| @@ -269,11 +269,11 @@ namespace MarkdownDeep | |||
| // Work out what the title is delimited with | |||
| char delim; | |||
| switch (p.current) | |||
| switch (p.Current) | |||
| { | |||
| case '\'': | |||
| case '\"': | |||
| delim = p.current; | |||
| delim = p.Current; | |||
| break; | |||
| case '(': | |||
| @@ -283,7 +283,7 @@ namespace MarkdownDeep | |||
| default: | |||
| if (bOnNewLine) | |||
| { | |||
| p.position = posLineEnd; | |||
| p.Position = posLineEnd; | |||
| return r; | |||
| } | |||
| else | |||
| @@ -297,15 +297,15 @@ namespace MarkdownDeep | |||
| p.Mark(); | |||
| while (true) | |||
| { | |||
| if (p.eol) | |||
| if (p.Eol) | |||
| return null; | |||
| if (p.current == delim) | |||
| if (p.Current == delim) | |||
| { | |||
| if (delim != ')') | |||
| { | |||
| int savepos = p.position; | |||
| int savepos = p.Position; | |||
| // Check for embedded quotes in title | |||
| @@ -315,13 +315,13 @@ namespace MarkdownDeep | |||
| // 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)) | |||
| if ((id == null && p.Current != ')') || | |||
| (id != null && !p.Eol)) | |||
| { | |||
| continue; | |||
| } | |||
| p.position = savepos; | |||
| p.Position = savepos; | |||
| } | |||
| // End of title | |||
| @@ -178,28 +178,28 @@ namespace MarkdownDeep | |||
| var fn = m_UsedFootnotes[i]; | |||
| sb.Append("<li id=\"fn:"); | |||
| sb.Append((string)fn.data); // footnote id | |||
| sb.Append((string)fn.Data); // footnote id | |||
| sb.Append("\">\n"); | |||
| // We need to get the return link appended to the last paragraph | |||
| // in the footnote | |||
| string strReturnLink = string.Format("<a href=\"#fnref:{0}\" rev=\"footnote\">↩</a>", (string)fn.data); | |||
| string strReturnLink = string.Format("<a href=\"#fnref:{0}\" rev=\"footnote\">↩</a>", (string)fn.Data); | |||
| // Get the last child of the footnote | |||
| var child = fn.children[fn.children.Count - 1]; | |||
| if (child.blockType == BlockType.p) | |||
| var child = fn.Children[fn.Children.Count - 1]; | |||
| if (child.BlockType == BlockType.p) | |||
| { | |||
| child.blockType = BlockType.p_footnote; | |||
| child.data = strReturnLink; | |||
| child.BlockType = BlockType.p_footnote; | |||
| child.Data = strReturnLink; | |||
| } | |||
| else | |||
| { | |||
| child = CreateBlock(); | |||
| child.contentLen = 0; | |||
| child.blockType = BlockType.p_footnote; | |||
| child.data = strReturnLink; | |||
| fn.children.Add(child); | |||
| child.ContentLen = 0; | |||
| child.BlockType = BlockType.p_footnote; | |||
| child.Data = strReturnLink; | |||
| fn.Children.Add(child); | |||
| } | |||
| @@ -420,7 +420,7 @@ namespace MarkdownDeep | |||
| private bool IsSectionHeader(Block b) | |||
| { | |||
| return b.blockType >= BlockType.h1 && b.blockType <= BlockType.h3; | |||
| return b.BlockType >= BlockType.h1 && b.BlockType <= BlockType.h3; | |||
| } | |||
| @@ -441,10 +441,10 @@ namespace MarkdownDeep | |||
| for (int i = 0; i < blocks.Count; i++) | |||
| { | |||
| var b = blocks[i]; | |||
| if (b.blockType==BlockType.user_break) | |||
| if (b.BlockType==BlockType.user_break) | |||
| { | |||
| // Get the offset of the section | |||
| int iSectionOffset = b.lineStart; | |||
| int iSectionOffset = b.LineStart; | |||
| // Add section | |||
| Sections.Add(markdown.Substring(iPrevSectionOffset, iSectionOffset - iPrevSectionOffset).Trim()); | |||
| @@ -452,9 +452,9 @@ namespace MarkdownDeep | |||
| // Next section starts on next line | |||
| if (i + 1 < blocks.Count) | |||
| { | |||
| iPrevSectionOffset = blocks[i + 1].lineStart; | |||
| iPrevSectionOffset = blocks[i + 1].LineStart; | |||
| if (iPrevSectionOffset==0) | |||
| iPrevSectionOffset = blocks[i + 1].contentStart; | |||
| iPrevSectionOffset = blocks[i + 1].ContentStart; | |||
| } | |||
| else | |||
| iPrevSectionOffset = markdown.Length; | |||
| @@ -512,7 +512,7 @@ namespace MarkdownDeep | |||
| if (md.IsSectionHeader(b)) | |||
| { | |||
| // Get the offset of the section | |||
| int iSectionOffset = b.lineStart; | |||
| int iSectionOffset = b.LineStart; | |||
| // Add section | |||
| Sections.Add(markdown.Substring(iPrevSectionOffset, iSectionOffset - iPrevSectionOffset)); | |||
| @@ -560,7 +560,7 @@ namespace MarkdownDeep | |||
| internal void AddFootnote(Block footnote) | |||
| { | |||
| m_Footnotes[(string)footnote.data] = footnote; | |||
| m_Footnotes[(string)footnote.Data] = footnote; | |||
| } | |||
| // Look up a footnote, claim it and return it's index (or -1 if not found) | |||
| @@ -618,9 +618,9 @@ namespace MarkdownDeep | |||
| { | |||
| m_StringScanner.Reset(str, start, len); | |||
| var p = m_StringScanner; | |||
| while (!p.eof) | |||
| while (!p.Eof) | |||
| { | |||
| char ch = p.current; | |||
| char ch = p.Current; | |||
| switch (ch) | |||
| { | |||
| case '&': | |||
| @@ -654,9 +654,9 @@ namespace MarkdownDeep | |||
| m_StringScanner.Reset(str, start, len); | |||
| var p = m_StringScanner; | |||
| int pos = 0; | |||
| while (!p.eof) | |||
| while (!p.Eof) | |||
| { | |||
| char ch = p.current; | |||
| char ch = p.Current; | |||
| switch (ch) | |||
| { | |||
| case '\t': | |||
| @@ -826,6 +826,13 @@ namespace MarkdownDeep | |||
| set; | |||
| } | |||
| /// <summary> | |||
| /// Gets or sets a value indicating whether the parser is in DocNet mode. If set to true, it will handle '@' extensions added for DocNet as well as | |||
| /// collect H2 id elements. | |||
| /// </summary> | |||
| public bool DocNetMode { get; set; } | |||
| // When set, all html block level elements automatically support | |||
| // markdown syntax within them. | |||
| // (Similar to Pandoc's handling of markdown in html) | |||
| @@ -924,7 +931,7 @@ namespace MarkdownDeep | |||
| /// <summary> | |||
| /// Collector for the created id's for H2 headers. First element in Tuple is id name, second is name for ToC (the text for H2). Id's are generated | |||
| /// by the parser and use pandoc algorithm, as AutoHeadingId's is switched on. | |||
| /// by the parser and use pandoc algorithm, as AutoHeadingId's is switched on. Only in use if DocNetMode is set to true | |||
| /// </summary> | |||
| public List<Tuple<string, string>> CreatedH2IdCollector { get; private set; } | |||
| @@ -137,18 +137,18 @@ namespace MarkdownDeep | |||
| base.Reset(sb.ToString()); | |||
| // Skip everything up to the first letter | |||
| while (!eof) | |||
| while (!Eof) | |||
| { | |||
| if (Char.IsLetter(current)) | |||
| if (Char.IsLetter(Current)) | |||
| break; | |||
| SkipForward(1); | |||
| } | |||
| // Process all characters | |||
| sb.Length = 0; | |||
| while (!eof) | |||
| while (!Eof) | |||
| { | |||
| char ch = current; | |||
| char ch = Current; | |||
| if (char.IsLetterOrDigit(ch) || ch=='_' || ch=='-' || ch=='.') | |||
| sb.Append(Char.ToLower(ch)); | |||
| else if (ch == ' ') | |||
| @@ -262,6 +262,12 @@ namespace MarkdownDeep | |||
| 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); | |||
| @@ -337,18 +343,18 @@ namespace MarkdownDeep | |||
| bool ExtraMode = m_Markdown.ExtraMode; | |||
| // Scan string | |||
| int start_text_token = position; | |||
| while (!eof) | |||
| int start_text_token = Position; | |||
| while (!Eof) | |||
| { | |||
| int end_text_token=position; | |||
| int end_text_token=Position; | |||
| // Work out token | |||
| Token token = null; | |||
| switch (current) | |||
| int positionSave = Position; | |||
| switch (Current) | |||
| { | |||
| case '*': | |||
| case '_': | |||
| // Create emphasis mark | |||
| token = CreateEmphasisMark(); | |||
| @@ -376,64 +382,55 @@ namespace MarkdownDeep | |||
| case '[': | |||
| case '!': | |||
| { | |||
| // Process link reference | |||
| int linkpos = position; | |||
| int linkpos = Position; | |||
| token = ProcessLinkOrImageOrFootnote(); | |||
| // Rewind if invalid syntax | |||
| // (the '[' or '!' will be treated as a regular character and processed below) | |||
| if (token == null) | |||
| position = linkpos; | |||
| Position = linkpos; | |||
| break; | |||
| } | |||
| case '<': | |||
| { | |||
| // Is it a valid html tag? | |||
| int save = position; | |||
| HtmlTag tag = HtmlTag.Parse(this); | |||
| if (tag != null) | |||
| { | |||
| if (!m_Markdown.SafeMode || tag.IsSafe()) | |||
| { | |||
| // Yes, create a token for it | |||
| token = CreateToken(TokenType.HtmlTag, save, position - save); | |||
| token = CreateToken(TokenType.HtmlTag, positionSave, Position - positionSave); | |||
| } | |||
| else | |||
| { | |||
| // No, rewrite and encode it | |||
| position = save; | |||
| Position = positionSave; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // No, rewind and check if it's a valid autolink eg: <google.com> | |||
| position = save; | |||
| Position = positionSave; | |||
| token = ProcessAutoLink(); | |||
| if (token == null) | |||
| position = save; | |||
| Position = positionSave; | |||
| } | |||
| break; | |||
| } | |||
| case '&': | |||
| { | |||
| // Is it a valid html entity | |||
| int save=position; | |||
| string unused=null; | |||
| if (SkipHtmlEntity(ref unused)) | |||
| { | |||
| // Yes, create a token for it | |||
| token = CreateToken(TokenType.Html, save, position - save); | |||
| token = CreateToken(TokenType.Html, positionSave, Position - positionSave); | |||
| } | |||
| break; | |||
| } | |||
| case ' ': | |||
| { | |||
| // Check for double space at end of a line | |||
| if (CharAtOffset(1)==' ' && IsLineEnd(CharAtOffset(2))) | |||
| { | |||
| @@ -441,17 +438,15 @@ namespace MarkdownDeep | |||
| SkipForward(2); | |||
| // Don't put br's at the end of a paragraph | |||
| if (!eof) | |||
| if (!Eof) | |||
| { | |||
| SkipEol(); | |||
| token = CreateToken(TokenType.br, end_text_token, 0); | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| case '\\': | |||
| { | |||
| // Special handling for escaping <autolinks> | |||
| /* | |||
| if (CharAtOffset(1) == '<') | |||
| @@ -474,27 +469,44 @@ namespace MarkdownDeep | |||
| // Check followed by an escapable character | |||
| if (Utils.IsEscapableChar(CharAtOffset(1), ExtraMode)) | |||
| { | |||
| token = CreateToken(TokenType.Text, position + 1, 1); | |||
| token = CreateToken(TokenType.Text, Position + 1, 1); | |||
| SkipForward(2); | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| case '@': | |||
| if(m_Markdown.DocNetMode) | |||
| { | |||
| if(this.DoesMatch("@fa-")) | |||
| { | |||
| // expect FontAwesome. | |||
| string iconName = string.Empty; | |||
| int newPosition = 0; | |||
| if(Utils.SkipFontAwesome(this.Input, this.Position, out newPosition, out iconName)) | |||
| { | |||
| // token should be just the iconname, so adjust position specification to that. | |||
| token = CreateToken(TokenType.font_awesome, newPosition - iconName.Length, iconName.Length); | |||
| this.Position = newPosition; | |||
| } | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| // Look for abbreviations. | |||
| if (token == null && Abbreviations!=null && !Char.IsLetterOrDigit(CharAtOffset(-1))) | |||
| { | |||
| var savepos = position; | |||
| var savepos = Position; | |||
| foreach (var abbr in Abbreviations) | |||
| { | |||
| if (SkipString(abbr.Abbr) && !Char.IsLetterOrDigit(current)) | |||
| if (SkipString(abbr.Abbr) && !Char.IsLetterOrDigit(Current)) | |||
| { | |||
| token = CreateToken(TokenType.abbreviation, abbr); | |||
| break; | |||
| } | |||
| position = savepos; | |||
| Position = savepos; | |||
| } | |||
| } | |||
| @@ -512,7 +524,7 @@ namespace MarkdownDeep | |||
| m_Tokens.Add(token); | |||
| // Remember where the next text token starts | |||
| start_text_token=position; | |||
| start_text_token=Position; | |||
| } | |||
| else | |||
| { | |||
| @@ -522,9 +534,9 @@ namespace MarkdownDeep | |||
| } | |||
| // Append a token for any trailing text after the last token. | |||
| if (position > start_text_token) | |||
| if (Position > start_text_token) | |||
| { | |||
| m_Tokens.Add(CreateToken(TokenType.Text, start_text_token, position-start_text_token)); | |||
| m_Tokens.Add(CreateToken(TokenType.Text, start_text_token, Position-start_text_token)); | |||
| } | |||
| // Do we need to resolve and emphasis marks? | |||
| @@ -532,9 +544,6 @@ namespace MarkdownDeep | |||
| { | |||
| ResolveEmphasisMarks(m_Tokens, emphasis_marks); | |||
| } | |||
| // Done! | |||
| return; | |||
| } | |||
| static bool IsEmphasisChar(char ch) | |||
| @@ -559,61 +568,61 @@ namespace MarkdownDeep | |||
| public Token CreateEmphasisMark() | |||
| { | |||
| // Capture current state | |||
| char ch = current; | |||
| char ch = Current; | |||
| char altch = ch == '*' ? '_' : '*'; | |||
| int savepos = position; | |||
| int savepos = Position; | |||
| // Check for a consecutive sequence of just '_' and '*' | |||
| if (bof || char.IsWhiteSpace(CharAtOffset(-1))) | |||
| if (Bof || char.IsWhiteSpace(CharAtOffset(-1))) | |||
| { | |||
| while (IsEmphasisChar(current)) | |||
| while (IsEmphasisChar(Current)) | |||
| SkipForward(1); | |||
| if (eof || char.IsWhiteSpace(current)) | |||
| if (Eof || char.IsWhiteSpace(Current)) | |||
| { | |||
| return new Token(TokenType.Html, savepos, position - savepos); | |||
| return new Token(TokenType.Html, savepos, Position - savepos); | |||
| } | |||
| // Rewind | |||
| position = savepos; | |||
| Position = savepos; | |||
| } | |||
| // Scan backwards and see if we have space before | |||
| while (IsEmphasisChar(CharAtOffset(-1))) | |||
| SkipForward(-1); | |||
| bool bSpaceBefore = bof || char.IsWhiteSpace(CharAtOffset(-1)); | |||
| position = savepos; | |||
| bool bSpaceBefore = Bof || char.IsWhiteSpace(CharAtOffset(-1)); | |||
| Position = savepos; | |||
| // Count how many matching emphasis characters | |||
| while (current == ch) | |||
| while (Current == ch) | |||
| { | |||
| SkipForward(1); | |||
| } | |||
| int count=position-savepos; | |||
| int count=Position-savepos; | |||
| // Scan forwards and see if we have space after | |||
| while (IsEmphasisChar(CharAtOffset(1))) | |||
| SkipForward(1); | |||
| bool bSpaceAfter = eof || char.IsWhiteSpace(current); | |||
| position = savepos + count; | |||
| bool bSpaceAfter = Eof || char.IsWhiteSpace(Current); | |||
| Position = savepos + count; | |||
| // This should have been stopped by check above | |||
| System.Diagnostics.Debug.Assert(!bSpaceBefore || !bSpaceAfter); | |||
| if (bSpaceBefore) | |||
| { | |||
| return CreateToken(TokenType.opening_mark, savepos, position - savepos); | |||
| return CreateToken(TokenType.opening_mark, savepos, Position - savepos); | |||
| } | |||
| if (bSpaceAfter) | |||
| { | |||
| return CreateToken(TokenType.closing_mark, savepos, position - savepos); | |||
| return CreateToken(TokenType.closing_mark, savepos, Position - savepos); | |||
| } | |||
| if (m_Markdown.ExtraMode && ch == '_' && (Char.IsLetterOrDigit(current))) | |||
| if (m_Markdown.ExtraMode && ch == '_' && (Char.IsLetterOrDigit(Current))) | |||
| return null; | |||
| return CreateToken(TokenType.internal_mark, savepos, position - savepos); | |||
| return CreateToken(TokenType.internal_mark, savepos, Position - savepos); | |||
| } | |||
| // Split mark token | |||
| @@ -656,7 +665,7 @@ namespace MarkdownDeep | |||
| break; | |||
| // Ignore if different type (ie: `*` vs `_`) | |||
| if (input[opening_mark.startOffset] != input[closing_mark.startOffset]) | |||
| if (Input[opening_mark.startOffset] != Input[closing_mark.startOffset]) | |||
| continue; | |||
| // strong or em? | |||
| @@ -718,7 +727,7 @@ namespace MarkdownDeep | |||
| continue; | |||
| // Ignore if different type (ie: `*` vs `_`) | |||
| if (input[opening_mark.startOffset] != input[closing_mark.startOffset]) | |||
| if (Input[opening_mark.startOffset] != Input[closing_mark.startOffset]) | |||
| continue; | |||
| // Must be at least two | |||
| @@ -764,7 +773,7 @@ namespace MarkdownDeep | |||
| continue; | |||
| // Ignore if different type (ie: `*` vs `_`) | |||
| if (input[opening_mark.startOffset] != input[closing_mark.startOffset]) | |||
| if (Input[opening_mark.startOffset] != Input[closing_mark.startOffset]) | |||
| continue; | |||
| // Split the opening mark, keeping the LHS | |||
| @@ -884,9 +893,9 @@ namespace MarkdownDeep | |||
| bool ExtraMode = m_Markdown.ExtraMode; | |||
| // Allow anything up to the closing angle, watch for escapable characters | |||
| while (!eof) | |||
| while (!Eof) | |||
| { | |||
| char ch = current; | |||
| char ch = Current; | |||
| // No whitespace allowed | |||
| if (char.IsWhiteSpace(ch)) | |||
| @@ -945,7 +954,7 @@ namespace MarkdownDeep | |||
| return null; | |||
| // Is it a foonote? | |||
| var savepos=position; | |||
| var savepos=Position; | |||
| if (m_Markdown.ExtraMode && token_type==TokenType.link && SkipChar('^')) | |||
| { | |||
| SkipLinespace(); | |||
| @@ -964,7 +973,7 @@ namespace MarkdownDeep | |||
| } | |||
| // Rewind | |||
| position = savepos; | |||
| Position = savepos; | |||
| } | |||
| if (DisableLinks && token_type==TokenType.link) | |||
| @@ -976,9 +985,9 @@ namespace MarkdownDeep | |||
| // escapable characters | |||
| Mark(); | |||
| int depth = 1; | |||
| while (!eof) | |||
| while (!Eof) | |||
| { | |||
| char ch = current; | |||
| char ch = Current; | |||
| if (ch == '[') | |||
| { | |||
| depth++; | |||
| @@ -994,7 +1003,7 @@ namespace MarkdownDeep | |||
| } | |||
| // Quit if end | |||
| if (eof) | |||
| if (Eof) | |||
| return null; | |||
| // Get the link text and unescape it | |||
| @@ -1004,7 +1013,7 @@ namespace MarkdownDeep | |||
| SkipForward(1); | |||
| // Save position in case we need to rewind | |||
| savepos = position; | |||
| savepos = Position; | |||
| // Inline links must follow immediately | |||
| if (SkipChar('(')) | |||
| @@ -1029,7 +1038,7 @@ namespace MarkdownDeep | |||
| // If there's line end, we're allow it and as must line space as we want | |||
| // before the link id. | |||
| if (eol) | |||
| if (Eol) | |||
| { | |||
| SkipEol(); | |||
| SkipLinespace(); | |||
| @@ -1037,7 +1046,7 @@ namespace MarkdownDeep | |||
| // Reference link? | |||
| string link_id = null; | |||
| if (current == '[') | |||
| if (Current == '[') | |||
| { | |||
| // Skip the opening '[' | |||
| SkipForward(1); | |||
| @@ -1056,7 +1065,7 @@ namespace MarkdownDeep | |||
| else | |||
| { | |||
| // Rewind to just after the closing ']' | |||
| position = savepos; | |||
| Position = savepos; | |||
| } | |||
| // Link id not specified? | |||
| @@ -1087,7 +1096,7 @@ namespace MarkdownDeep | |||
| // Process a ``` code span ``` | |||
| Token ProcessCodeSpan() | |||
| { | |||
| int start = position; | |||
| int start = Position; | |||
| // Count leading ticks | |||
| int tickcount = 0; | |||
| @@ -1100,23 +1109,23 @@ namespace MarkdownDeep | |||
| SkipWhitespace(); | |||
| // End? | |||
| if (eof) | |||
| return CreateToken(TokenType.Text, start, position - start); | |||
| if (Eof) | |||
| return CreateToken(TokenType.Text, start, Position - start); | |||
| int startofcode = position; | |||
| int startofcode = Position; | |||
| // Find closing ticks | |||
| if (!Find(Substring(start, tickcount))) | |||
| return CreateToken(TokenType.Text, start, position - start); | |||
| return CreateToken(TokenType.Text, start, Position - start); | |||
| // Save end position before backing up over trailing whitespace | |||
| int endpos = position + tickcount; | |||
| int endpos = Position + tickcount; | |||
| while (char.IsWhiteSpace(CharAtOffset(-1))) | |||
| SkipForward(-1); | |||
| // Create the token, move back to the end and we're done | |||
| var ret = CreateToken(TokenType.code_span, startofcode, position - startofcode); | |||
| position = endpos; | |||
| var ret = CreateToken(TokenType.code_span, startofcode, Position - startofcode); | |||
| Position = endpos; | |||
| return ret; | |||
| } | |||
| @@ -84,7 +84,7 @@ namespace MarkdownDeep | |||
| } | |||
| // Get the entire input string | |||
| public string input | |||
| public string Input | |||
| { | |||
| get | |||
| { | |||
| @@ -93,7 +93,7 @@ namespace MarkdownDeep | |||
| } | |||
| // Get the character at the current position | |||
| public char current | |||
| public char Current | |||
| { | |||
| get | |||
| { | |||
| @@ -105,7 +105,7 @@ namespace MarkdownDeep | |||
| } | |||
| // Get/set the current position | |||
| public int position | |||
| public int Position | |||
| { | |||
| get | |||
| { | |||
| @@ -119,11 +119,11 @@ namespace MarkdownDeep | |||
| // Get the remainder of the input | |||
| // (use this in a watch window while debugging :) | |||
| public string remainder | |||
| public string Remainder | |||
| { | |||
| get | |||
| { | |||
| return Substring(position); | |||
| return Substring(Position); | |||
| } | |||
| } | |||
| @@ -201,7 +201,7 @@ namespace MarkdownDeep | |||
| // Skip a character if present | |||
| public bool SkipChar(char ch) | |||
| { | |||
| if (current == ch) | |||
| if (Current == ch) | |||
| { | |||
| SkipForward(1); | |||
| return true; | |||
| @@ -237,11 +237,11 @@ namespace MarkdownDeep | |||
| // Skip any whitespace | |||
| public bool SkipWhitespace() | |||
| { | |||
| if (!char.IsWhiteSpace(current)) | |||
| if (!char.IsWhiteSpace(Current)) | |||
| return false; | |||
| SkipForward(1); | |||
| while (char.IsWhiteSpace(current)) | |||
| while (char.IsWhiteSpace(Current)) | |||
| SkipForward(1); | |||
| return true; | |||
| @@ -256,11 +256,11 @@ namespace MarkdownDeep | |||
| // Skip spaces and tabs | |||
| public bool SkipLinespace() | |||
| { | |||
| if (!IsLineSpace(current)) | |||
| if (!IsLineSpace(Current)) | |||
| return false; | |||
| SkipForward(1); | |||
| while (IsLineSpace(current)) | |||
| while (IsLineSpace(Current)) | |||
| SkipForward(1); | |||
| return true; | |||
| @@ -269,7 +269,7 @@ namespace MarkdownDeep | |||
| // Does current character match something | |||
| public bool DoesMatch(char ch) | |||
| { | |||
| return current == ch; | |||
| return Current == ch; | |||
| } | |||
| // Does character at offset match a character | |||
| @@ -314,7 +314,7 @@ namespace MarkdownDeep | |||
| // Does current string position match a string | |||
| public bool DoesMatchI(string str) | |||
| { | |||
| return string.Compare(str, Substring(position, str.Length), true) == 0; | |||
| return string.Compare(str, Substring(Position, str.Length), true) == 0; | |||
| } | |||
| // Extract a substring | |||
| @@ -393,7 +393,7 @@ namespace MarkdownDeep | |||
| } | |||
| // Are we at eof? | |||
| public bool eof | |||
| public bool Eof | |||
| { | |||
| get | |||
| { | |||
| @@ -402,16 +402,16 @@ namespace MarkdownDeep | |||
| } | |||
| // Are we at eol? | |||
| public bool eol | |||
| public bool Eol | |||
| { | |||
| get | |||
| { | |||
| return IsLineEnd(current); | |||
| return IsLineEnd(Current); | |||
| } | |||
| } | |||
| // Are we at bof? | |||
| public bool bof | |||
| public bool Bof | |||
| { | |||
| get | |||
| { | |||
| @@ -437,7 +437,7 @@ namespace MarkdownDeep | |||
| // Skip an identifier | |||
| public bool SkipIdentifier(ref string identifier) | |||
| { | |||
| int savepos = position; | |||
| int savepos = Position; | |||
| if (!Utils.ParseIdentifier(this.str, ref pos, ref identifier)) | |||
| return false; | |||
| if (pos >= end) | |||
| @@ -450,7 +450,7 @@ namespace MarkdownDeep | |||
| public bool SkipFootnoteID(out string id) | |||
| { | |||
| int savepos = position; | |||
| int savepos = Position; | |||
| SkipLinespace(); | |||
| @@ -458,14 +458,14 @@ namespace MarkdownDeep | |||
| while (true) | |||
| { | |||
| char ch = current; | |||
| char ch = Current; | |||
| if (char.IsLetterOrDigit(ch) || ch == '-' || ch == '_' || ch == ':' || ch == '.' || ch == ' ') | |||
| SkipForward(1); | |||
| else | |||
| break; | |||
| } | |||
| if (position > mark) | |||
| if (Position > mark) | |||
| { | |||
| id = Extract().Trim(); | |||
| if (!String.IsNullOrEmpty(id)) | |||
| @@ -475,7 +475,7 @@ namespace MarkdownDeep | |||
| } | |||
| } | |||
| position = savepos; | |||
| Position = savepos; | |||
| id = null; | |||
| return false; | |||
| } | |||
| @@ -483,7 +483,7 @@ namespace MarkdownDeep | |||
| // Skip a Html entity (eg: &) | |||
| public bool SkipHtmlEntity(ref string entity) | |||
| { | |||
| int savepos = position; | |||
| int savepos = Position; | |||
| if (!Utils.SkipHtmlEntity(this.str, ref pos, ref entity)) | |||
| return false; | |||
| if (pos > end) | |||
| @@ -44,7 +44,7 @@ namespace MarkdownDeep | |||
| { | |||
| p.SkipLinespace(); | |||
| if (p.eol) | |||
| if (p.Eol) | |||
| return null; // Blank line ends the table | |||
| bool bAnyBars=LeadingBar; | |||
| @@ -58,11 +58,11 @@ namespace MarkdownDeep | |||
| // Parse all columns except the last | |||
| while (!p.eol) | |||
| while (!p.Eol) | |||
| { | |||
| // Find the next vertical bar | |||
| p.Mark(); | |||
| while (!p.eol && p.current != '|') | |||
| while (!p.Eol && p.Current != '|') | |||
| p.SkipEscapableChar(true); | |||
| row.Add(p.Extract().Trim()); | |||
| @@ -143,7 +143,7 @@ namespace MarkdownDeep | |||
| p.SkipLinespace(); | |||
| // Quick check for typical case | |||
| if (p.current != '|' && p.current != ':' && p.current != '-') | |||
| if (p.Current != '|' && p.Current != ':' && p.Current != '-') | |||
| return null; | |||
| // Don't create the spec until it at least looks like one | |||
| @@ -164,11 +164,11 @@ namespace MarkdownDeep | |||
| p.SkipLinespace(); | |||
| // Must have something in the spec | |||
| if (p.current == '|') | |||
| if (p.Current == '|') | |||
| return null; | |||
| bool AlignLeft = p.SkipChar(':'); | |||
| while (p.current == '-') | |||
| while (p.Current == '-') | |||
| p.SkipForward(1); | |||
| bool AlignRight = p.SkipChar(':'); | |||
| p.SkipLinespace(); | |||
| @@ -182,7 +182,7 @@ namespace MarkdownDeep | |||
| else if (AlignRight) | |||
| col = ColumnAlignment.Right; | |||
| if (p.eol) | |||
| if (p.Eol) | |||
| { | |||
| // Not a spec? | |||
| if (spec == null) | |||
| @@ -206,7 +206,7 @@ namespace MarkdownDeep | |||
| // Check for trailing vertical bar | |||
| p.SkipLinespace(); | |||
| if (p.eol) | |||
| if (p.Eol) | |||
| { | |||
| spec.TrailingBar = true; | |||
| return spec; | |||
| @@ -52,6 +52,9 @@ namespace MarkdownDeep | |||
| opening_mark, // opening '*' or '_' | |||
| closing_mark, // closing '*' or '_' | |||
| internal_mark, // internal '*' or '_' | |||
| // DocNet Extensions | |||
| font_awesome, // <i class="fa fa-name"></i> | |||
| } | |||
| // Token | |||
| @@ -87,7 +87,7 @@ namespace MarkdownDeep | |||
| identifer = str.Substring(startpos, pos - startpos); | |||
| return true; | |||
| } | |||
| // Skip over anything that looks like a valid html entity (eg: &, {, &#nnn) etc... | |||
| // Updates `pos` to character after the entity if matched | |||
| public static bool SkipHtmlEntity(string str, ref int pos, ref string entity) | |||
| @@ -322,7 +322,7 @@ namespace MarkdownDeep | |||
| // Extension method. Skip an escapable character, or one normal character | |||
| public static void SkipEscapableChar(this StringScanner p, bool ExtraMode) | |||
| { | |||
| if (p.current == '\\' && IsEscapableChar(p.CharAtOffset(1), ExtraMode)) | |||
| if (p.Current == '\\' && IsEscapableChar(p.CharAtOffset(1), ExtraMode)) | |||
| { | |||
| p.SkipForward(2); | |||
| } | |||
| @@ -367,16 +367,16 @@ namespace MarkdownDeep | |||
| StringBuilder sb = new StringBuilder(); | |||
| StringScanner sp = new StringScanner(str); | |||
| while (!sp.eof) | |||
| while (!sp.Eof) | |||
| { | |||
| if (sp.eol) | |||
| if (sp.Eol) | |||
| { | |||
| sb.Append('\n'); | |||
| sp.SkipEol(); | |||
| } | |||
| else | |||
| { | |||
| sb.Append(sp.current); | |||
| sb.Append(sp.Current); | |||
| sp.SkipForward(1); | |||
| } | |||
| } | |||
| @@ -491,5 +491,49 @@ namespace MarkdownDeep | |||
| return url.Contains("://") || url.StartsWith("mailto:"); | |||
| } | |||
| /// <summary> | |||
| /// Skips the font awesome specification at the current specified pos. Pos is inside str at the '@'. font awesome | |||
| /// directives are @fa-iconname. newPos is positioned to character after iconname if successful match. iconname scanned is specified in iconname. | |||
| /// </summary> | |||
| /// <param name="str">The string.</param> | |||
| /// <param name="currentPos">The current position.</param> | |||
| /// <param name="newPos">The new position.</param> | |||
| /// <param name="iconName">Name of the icon.</param> | |||
| /// <returns> | |||
| /// bool if match was found and properly skipped, otherwise false. | |||
| /// </returns> | |||
| public static bool SkipFontAwesome(string str, int currentPos, out int newPos, out string iconName) | |||
| { | |||
| newPos = currentPos; | |||
| iconName = string.Empty; | |||
| if(str[currentPos] != '@') | |||
| { | |||
| return false; | |||
| } | |||
| var scanner = new StringScanner(str, currentPos); | |||
| // skip '@' | |||
| scanner.SkipForward(1); | |||
| if(!scanner.DoesMatch("fa-")) | |||
| { | |||
| // not a font awesome specification | |||
| return false; | |||
| } | |||
| scanner.SkipForward(3); | |||
| iconName = string.Empty; | |||
| if(!scanner.SkipIdentifier(ref iconName)) | |||
| { | |||
| // no icon name specified | |||
| return false; | |||
| } | |||
| if(string.IsNullOrWhiteSpace(iconName)) | |||
| { | |||
| return false; | |||
| } | |||
| // matched a fontawesome specification | |||
| newPos = scanner.Position; | |||
| return true; | |||
| } | |||
| } | |||
| } | |||
| @@ -21,7 +21,7 @@ namespace MarkdownDeepTests | |||
| { | |||
| var b = p.Process("paragraph"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.p, b[0].blockType); | |||
| Assert.AreEqual(BlockType.p, b[0].BlockType); | |||
| Assert.AreEqual("paragraph", b[0].Content); | |||
| } | |||
| @@ -30,7 +30,7 @@ namespace MarkdownDeepTests | |||
| { | |||
| var b = p.Process("l1\nl2\n\n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.p, b[0].blockType); | |||
| Assert.AreEqual(BlockType.p, b[0].BlockType); | |||
| Assert.AreEqual("l1\nl2", b[0].Content); | |||
| } | |||
| @@ -39,7 +39,7 @@ namespace MarkdownDeepTests | |||
| { | |||
| var b = p.Process("heading\n===\n\n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.h1, b[0].blockType); | |||
| Assert.AreEqual(BlockType.h1, b[0].BlockType); | |||
| Assert.AreEqual("heading", b[0].Content); | |||
| } | |||
| @@ -48,7 +48,7 @@ namespace MarkdownDeepTests | |||
| { | |||
| var b = p.Process("heading\n---\n\n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.h2, b[0].blockType); | |||
| Assert.AreEqual(BlockType.h2, b[0].BlockType); | |||
| Assert.AreEqual("heading", b[0].Content); | |||
| } | |||
| @@ -58,13 +58,13 @@ namespace MarkdownDeepTests | |||
| var b = p.Process("p1\nheading\n---\np2\n"); | |||
| Assert.AreEqual(3, b.Count); | |||
| Assert.AreEqual(BlockType.p, b[0].blockType); | |||
| Assert.AreEqual(BlockType.p, b[0].BlockType); | |||
| Assert.AreEqual("p1", b[0].Content); | |||
| Assert.AreEqual(BlockType.h2, b[1].blockType); | |||
| Assert.AreEqual(BlockType.h2, b[1].BlockType); | |||
| Assert.AreEqual("heading", b[1].Content); | |||
| Assert.AreEqual(BlockType.p, b[2].blockType); | |||
| Assert.AreEqual(BlockType.p, b[2].BlockType); | |||
| Assert.AreEqual("p2", b[2].Content); | |||
| } | |||
| @@ -74,10 +74,10 @@ namespace MarkdownDeepTests | |||
| var b = p.Process("#heading#\nparagraph\n"); | |||
| Assert.AreEqual(2, b.Count); | |||
| Assert.AreEqual(BlockType.h1, b[0].blockType); | |||
| Assert.AreEqual(BlockType.h1, b[0].BlockType); | |||
| Assert.AreEqual("heading", b[0].Content); | |||
| Assert.AreEqual(BlockType.p, b[1].blockType); | |||
| Assert.AreEqual(BlockType.p, b[1].BlockType); | |||
| Assert.AreEqual("paragraph", b[1].Content); | |||
| } | |||
| @@ -88,13 +88,13 @@ namespace MarkdownDeepTests | |||
| Assert.AreEqual(3, b.Count); | |||
| Assert.AreEqual(BlockType.p, b[0].blockType); | |||
| Assert.AreEqual(BlockType.p, b[0].BlockType); | |||
| Assert.AreEqual("p1", b[0].Content); | |||
| Assert.AreEqual(BlockType.h2, b[1].blockType); | |||
| Assert.AreEqual(BlockType.h2, b[1].BlockType); | |||
| Assert.AreEqual("heading", b[1].Content); | |||
| Assert.AreEqual(BlockType.p, b[2].blockType); | |||
| Assert.AreEqual(BlockType.p, b[2].BlockType); | |||
| Assert.AreEqual("p2", b[2].Content); | |||
| } | |||
| @@ -107,7 +107,7 @@ namespace MarkdownDeepTests | |||
| Block cb = b[0] as Block; | |||
| Assert.AreEqual("code1\n\tcode2\ncode3\n", cb.Content); | |||
| Assert.AreEqual(BlockType.p, b[1].blockType); | |||
| Assert.AreEqual(BlockType.p, b[1].BlockType); | |||
| Assert.AreEqual("paragraph", b[1].Content); | |||
| } | |||
| @@ -116,7 +116,7 @@ namespace MarkdownDeepTests | |||
| { | |||
| var b = p.Process("<div>\n</div>\n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.html, b[0].blockType); | |||
| Assert.AreEqual(BlockType.html, b[0].BlockType); | |||
| Assert.AreEqual("<div>\n</div>\n", b[0].Content); | |||
| } | |||
| @@ -125,7 +125,7 @@ namespace MarkdownDeepTests | |||
| { | |||
| var b = p.Process("<!-- this is a\ncomments -->\n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.html, b[0].blockType); | |||
| Assert.AreEqual(BlockType.html, b[0].BlockType); | |||
| Assert.AreEqual("<!-- this is a\ncomments -->\n", b[0].Content); | |||
| } | |||
| @@ -134,27 +134,27 @@ namespace MarkdownDeepTests | |||
| { | |||
| var b = p.Process("---\n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.hr, b[0].blockType); | |||
| Assert.AreEqual(BlockType.hr, b[0].BlockType); | |||
| b = p.Process("___\n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.hr, b[0].blockType); | |||
| Assert.AreEqual(BlockType.hr, b[0].BlockType); | |||
| b = p.Process("***\n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.hr, b[0].blockType); | |||
| Assert.AreEqual(BlockType.hr, b[0].BlockType); | |||
| b = p.Process(" - - - \n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.hr, b[0].blockType); | |||
| Assert.AreEqual(BlockType.hr, b[0].BlockType); | |||
| b = p.Process(" _ _ _ \n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.hr, b[0].blockType); | |||
| Assert.AreEqual(BlockType.hr, b[0].BlockType); | |||
| b = p.Process(" * * * \n"); | |||
| Assert.AreEqual(1, b.Count); | |||
| Assert.AreEqual(BlockType.hr, b[0].blockType); | |||
| Assert.AreEqual(BlockType.hr, b[0].BlockType); | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using NUnit.Framework; | |||
| using MarkdownDeep; | |||
| using System.Reflection; | |||
| namespace MarkdownDeepTests | |||
| { | |||
| [TestFixture] | |||
| class DocNetModeTests | |||
| { | |||
| public static IEnumerable<TestCaseData> GetTests() | |||
| { | |||
| return Utils.GetTests("docnetmode"); | |||
| } | |||
| [Test, TestCaseSource("GetTests")] | |||
| public void Test(string resourceName) | |||
| { | |||
| Utils.RunResourceTest(resourceName); | |||
| } | |||
| } | |||
| } | |||
| @@ -70,6 +70,7 @@ | |||
| <Compile Include="CodeSpanTests.cs" /> | |||
| <Compile Include="AutoLinkTests.cs" /> | |||
| <Compile Include="AutoHeaderIDTests.cs" /> | |||
| <Compile Include="DocNetMode.cs" /> | |||
| <Compile Include="GithubMode.cs" /> | |||
| <Compile Include="TableSpecTests.cs" /> | |||
| <Compile Include="ExtraMode.cs" /> | |||
| @@ -454,6 +455,18 @@ | |||
| <Name>MarkdownDeep</Name> | |||
| </ProjectReference> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <EmbeddedResource Include="testfiles\docnetmode\FontAwesome%28DocNetMode%29.html" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <EmbeddedResource Include="testfiles\docnetmode\FontAwesome%28DocNetMode%29.txt" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <EmbeddedResource Include="testfiles\docnetmode\Alert%28DocNetMode%29.txt" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <EmbeddedResource Include="testfiles\docnetmode\Alert%28DocNetMode%29.html" /> | |||
| </ItemGroup> | |||
| <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
| <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||
| Other similar extension points exist, see Microsoft.Common.targets. | |||
| @@ -16,11 +16,11 @@ namespace MarkdownDeepTests | |||
| var p = new StringScanner(); | |||
| p.Reset("This is a string with something [bracketed]"); | |||
| Assert.IsTrue(p.bof); | |||
| Assert.IsFalse(p.eof); | |||
| Assert.IsTrue(p.Bof); | |||
| Assert.IsFalse(p.Eof); | |||
| Assert.IsTrue(p.SkipString("This")); | |||
| Assert.IsFalse(p.bof); | |||
| Assert.IsFalse(p.eof); | |||
| Assert.IsFalse(p.Bof); | |||
| Assert.IsFalse(p.Eof); | |||
| Assert.IsFalse(p.SkipString("huh?")); | |||
| Assert.IsTrue(p.SkipLinespace()); | |||
| Assert.IsTrue(p.SkipChar('i')); | |||
| @@ -37,7 +37,7 @@ namespace MarkdownDeepTests | |||
| Assert.IsTrue(p.Find(']')); | |||
| Assert.AreEqual("bracketed", p.Extract()); | |||
| Assert.IsTrue(p.SkipChar(']')); | |||
| Assert.IsTrue(p.eof); | |||
| Assert.IsTrue(p.Eof); | |||
| } | |||
| } | |||
| } | |||
| @@ -125,6 +125,7 @@ namespace MarkdownDeepTests | |||
| md.SafeMode = resourceName.IndexOf("(SafeMode)") >= 0; | |||
| md.ExtraMode = resourceName.IndexOf("(ExtraMode)") >= 0; | |||
| md.GitHubCodeBlocks = resourceName.IndexOf("(GitHubMode)") >= 0; | |||
| md.DocNetMode = resourceName.IndexOf("(DocNetMode") >= 0; | |||
| md.MarkdownInHtml = resourceName.IndexOf("(MarkdownInHtml)") >= 0; | |||
| md.AutoHeadingIDs = resourceName.IndexOf("(AutoHeadingIDs)") >= 0; | |||
| @@ -87,7 +87,7 @@ namespace MarkdownDeepTests | |||
| { | |||
| StringScanner p = new StringScanner(input); | |||
| while (!p.eof) | |||
| while (!p.Eof) | |||
| { | |||
| HtmlTag tag=HtmlTag.Parse(p); | |||
| if (tag!=null) | |||
| @@ -117,7 +117,7 @@ namespace MarkdownDeepTests | |||
| { | |||
| StringScanner p = new StringScanner(input); | |||
| while (!p.eof) | |||
| while (!p.Eof) | |||
| { | |||
| HtmlTag tag = HtmlTag.Parse(p); | |||
| if (tag != null) | |||
| @@ -0,0 +1,24 @@ | |||
| <p>pre</p> | |||
| <p>This is some text</p> | |||
| <div class="alert alert-danger"> | |||
| <span class="alert-title"><i class="fa fa-times-circle"></i>Danger!</span><p>This is a dangerous text, it will be displayed in a danger alert box!</p> | |||
| </div> | |||
| <div class="alert alert-warning"> | |||
| <span class="alert-title"><i class="fa fa-warning"></i>Warning!</span><p>This is a warning text, it will be displayed in a warning alert box!</p> | |||
| </div> | |||
| <div class="alert alert-info"> | |||
| <span class="alert-title"><i class="fa fa-info-circle"></i>Info</span><p>This is an info text, it will be displayed in an info alert box!</p> | |||
| </div> | |||
| <div class="alert alert-neutral"> | |||
| <span class="alert-title"><i class="fa fa-info-circle"></i>Info</span><p>This is an info text, it will be displayed in an info alert box!</p> | |||
| </div> | |||
| <p>@alert | |||
| This is an info text, it will be displayed in an info alert box! | |||
| @end</p> | |||
| <p>post</p> | |||
| @@ -0,0 +1,24 @@ | |||
| pre | |||
| This is some text | |||
| @alert danger | |||
| This is a dangerous text, it will be displayed in a danger alert box! | |||
| @end | |||
| @alert warning | |||
| This is a warning text, it will be displayed in a warning alert box! | |||
| @end | |||
| @alert info | |||
| This is an info text, it will be displayed in an info alert box! | |||
| @end | |||
| @alert neutral | |||
| This is an info text, it will be displayed in an info alert box! | |||
| @end | |||
| @alert | |||
| This is an info text, it will be displayed in an info alert box! | |||
| @end | |||
| post | |||
| @@ -0,0 +1,7 @@ | |||
| <p>pre</p> | |||
| <p>This is some text with a <i class="fa fa-github"></i> font awesome icon in it! <code>@fa-github</code>.</p> | |||
| <i class="fa fa-github"></i><i class="fa fa-github"></i><i class="fa fa-github"></i><p>a <i class="fa fa-github"></i></p> | |||
| <p>post</p> | |||
| @@ -0,0 +1,12 @@ | |||
| pre | |||
| This is some text with a @fa-github font awesome icon in it! `@fa-github`. | |||
| @fa-github | |||
| @fa-github | |||
| @fa-github | |||
| a @fa-github | |||
| post | |||
| @@ -24,10 +24,10 @@ line 3 | |||
| </code></pre> | |||
| <pre><code class="nohighlight">This is a fenced nohighlight code block | |||
| <pre class="nocode">This is a fenced nohighlight code block | |||
| line 2 | |||
| line 3 | |||
| </code></pre> | |||
| </pre> | |||
| <pre><code class="cs"> | |||
| This is a fenced c# code block with blank lines around it | |||