Last active
March 16, 2016 22:33
-
-
Save jakejscott/dfd22a747919c4d7042e to your computer and use it in GitHub Desktop.
Add a table of contents to a markdown document
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using CommonMark; | |
using CommonMark.Formatters; | |
using CommonMark.Syntax; | |
namespace ConsoleApp | |
{ | |
public class Program | |
{ | |
// blocks are elemets like paragraphs and lists, inlines are | |
// elements like emphasis, links, images. | |
public static void Main(string[] args) | |
{ | |
CommonMarkSettings.Default.OutputDelegate = (document, textWriter, settings) => | |
{ | |
var formatter = new CustomHtmlFormatter(document, textWriter, settings); | |
formatter.WriteDocument(document); | |
}; | |
using (var reader = new StreamReader("./README.md")) | |
{ | |
var document = CommonMarkConverter.Parse(reader); | |
using (var writer = new StringWriter()) | |
{ | |
// write the HTML output | |
CommonMarkConverter.ProcessStage3(document, writer); | |
Console.WriteLine(writer.ToString()); | |
} | |
} | |
Console.ReadLine(); | |
} | |
private class CustomHtmlFormatter : HtmlFormatter | |
{ | |
private readonly List<EnumeratorEntry> headings; | |
public CustomHtmlFormatter(Block block, TextWriter target, CommonMarkSettings settings) | |
: base(target, settings) | |
{ | |
headings = block.AsEnumerable().Where( | |
x => x.IsOpening && | |
x.Block != null && | |
x.Block.Tag == BlockTag.AtxHeading && | |
x.Block.InlineContent != null | |
).ToList(); | |
} | |
protected override void WriteBlock(Block block, bool isOpening, bool isClosing, out bool ignoreChildNodes) | |
{ | |
if (block.Tag == BlockTag.AtxHeading && block.InlineContent != null) | |
{ | |
// | |
// Add anchorable id to each heading | |
// | |
ignoreChildNodes = false; | |
if (isOpening) | |
{ | |
var slug = Slug.Create(true, block.InlineContent.LiteralContent); | |
Write(string.Format("<h{0} id=\"{1}\">", block.Heading.Level, slug)); | |
} | |
if (isClosing) | |
{ | |
Write(string.Format("</h{0}>", block.Heading.Level)); | |
} | |
} | |
else if (block.Tag == BlockTag.Paragraph && block.InlineContent != null && block.InlineContent.LiteralContent == "{:toc}" && headings.Any()) | |
{ | |
// | |
// Injects a table of contents. | |
// | |
ignoreChildNodes = true; | |
if (isOpening) | |
{ | |
WriteLine(); | |
Write("<ul>"); | |
foreach (var heading in headings) | |
{ | |
var text = heading.Block.InlineContent.LiteralContent; | |
var slug = Slug.Create(true, text); | |
WriteLine(); | |
Write(string.Format("<li href=\"#{0}\">", slug)); | |
Write(text); | |
Write("</li>"); | |
} | |
} | |
if (isClosing) | |
{ | |
WriteLine(); | |
Write("</ul>"); | |
WriteLine(); | |
} | |
} | |
else | |
{ | |
base.WriteBlock(block, isOpening, isClosing, out ignoreChildNodes); | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Text; | |
namespace ConsoleApp | |
{ | |
public static class Slug | |
{ | |
public static string Create(bool toLower, params string[] values) | |
{ | |
return Create(toLower, String.Join("-", values)); | |
} | |
public static string Create(bool toLower, string value) | |
{ | |
if (value == null) return ""; | |
var normalised = value.Normalize(NormalizationForm.FormKD); | |
const int maxlen = 80; | |
int len = normalised.Length; | |
bool prevDash = false; | |
var sb = new StringBuilder(len); | |
char c; | |
for (int i = 0; i < len; i++) | |
{ | |
c = normalised[i]; | |
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) | |
{ | |
if (prevDash) | |
{ | |
sb.Append('-'); | |
prevDash = false; | |
} | |
sb.Append(c); | |
} | |
else if (c >= 'A' && c <= 'Z') | |
{ | |
if (prevDash) | |
{ | |
sb.Append('-'); | |
prevDash = false; | |
} | |
// tricky way to convert to lowercase | |
if (toLower) | |
sb.Append((char)(c | 32)); | |
else | |
sb.Append(c); | |
} | |
else if (c == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-' || c == '_' || c == '=') | |
{ | |
if (!prevDash && sb.Length > 0) | |
{ | |
prevDash = true; | |
} | |
} | |
else | |
{ | |
string swap = ConvertEdgeCases(c, toLower); | |
if (swap != null) | |
{ | |
if (prevDash) | |
{ | |
sb.Append('-'); | |
prevDash = false; | |
} | |
sb.Append(swap); | |
} | |
} | |
if (sb.Length == maxlen) break; | |
} | |
return sb.ToString(); | |
} | |
static string ConvertEdgeCases(char c, bool toLower) | |
{ | |
string swap = null; | |
switch (c) | |
{ | |
case 'ı': | |
swap = "i"; | |
break; | |
case 'ł': | |
swap = "l"; | |
break; | |
case 'Ł': | |
swap = toLower ? "l" : "L"; | |
break; | |
case 'đ': | |
swap = "d"; | |
break; | |
case 'ß': | |
swap = "ss"; | |
break; | |
case 'ø': | |
swap = "o"; | |
break; | |
case 'Þ': | |
swap = "th"; | |
break; | |
} | |
return swap; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment