Skip to content

Instantly share code, notes, and snippets.

@jakejscott
Last active March 16, 2016 22:33
Show Gist options
  • Select an option

  • Save jakejscott/dfd22a747919c4d7042e to your computer and use it in GitHub Desktop.

Select an option

Save jakejscott/dfd22a747919c4d7042e to your computer and use it in GitHub Desktop.
Add a table of contents to a markdown document
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);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment