Add a flag to turn on header ID generation. - Fixes #51, #101, and #102. - Uses the [code][gfm] mentioned by @shurcooL from his Github Flavored Markdown parser extension in a [comment on #102][comment]. Since this was mentioned, I assumed that @shurcooL would be OK with this being included under the licence provided by blackfriday (there is no licence comment on his code). - I’ve added it behind another flag, EXTENSION_AUTO_HEADER_IDS, that would need to be turned on for it to work. It works with both prefix and underline headers. [gfm]: https://github.com/shurcooL/go/blob/3bec0366a85101d116bf88b477e92ea6cdd75e4e/github_flavored_markdown/main.go#L90-L102 [comment]: https://github.com/russross/blackfriday/issues/102#issuecomment-51272260
Austin Ziegler austin@zieglers.ca
Mon, 27 Oct 2014 16:54:23 -0400
3 files changed,
114 insertions(+),
2 deletions(-)
M
block.go
→
block.go
@@ -13,7 +13,10 @@ //
package blackfriday -import "bytes" +import ( + "bytes" + "unicode" +) // Parse block-level data. // Note: this function and many that it calls assume that@@ -223,6 +226,9 @@ for end > 0 && data[end-1] == ' ' {
end-- } if end > i { + if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { + id = createSanitizedAnchorName(string(data[i:end])) + } work := func() bool { p.inline(out, data[i:end]) return true@@ -1267,7 +1273,13 @@ pp.inline(o, d)
return true } }(out, p, data[prev:eol]) - p.r.Header(out, work, level, "") + + id := "" + if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { + id = createSanitizedAnchorName(string(data[prev:eol])) + } + + p.r.Header(out, work, level, id) // find the end of the underline for data[i] != '\n' {@@ -1313,3 +1325,16 @@
p.renderParagraph(out, data[:i]) return i } + +func createSanitizedAnchorName(text string) string { + var anchorName []rune + for _, r := range []rune(text) { + switch { + case r == ' ': + anchorName = append(anchorName, '-') + case unicode.IsLetter(r) || unicode.IsNumber(r): + anchorName = append(anchorName, unicode.ToLower(r)) + } + } + return string(anchorName) +}
M
block_test.go
→
block_test.go
@@ -237,6 +237,48 @@ }
doTestsBlock(t, tests, EXTENSION_HEADER_IDS) } +func TestPrefixAutoHeaderIdExtension(t *testing.T) { + var tests = []string{ + "# Header 1\n", + "<h1 id=\"header-1\">Header 1</h1>\n", + + "# Header 1 \n", + "<h1 id=\"header-1\">Header 1</h1>\n", + + "## Header 2\n", + "<h2 id=\"header-2\">Header 2</h2>\n", + + "### Header 3\n", + "<h3 id=\"header-3\">Header 3</h3>\n", + + "#### Header 4\n", + "<h4 id=\"header-4\">Header 4</h4>\n", + + "##### Header 5\n", + "<h5 id=\"header-5\">Header 5</h5>\n", + + "###### Header 6\n", + "<h6 id=\"header-6\">Header 6</h6>\n", + + "####### Header 7\n", + "<h6 id=\"-header-7\"># Header 7</h6>\n", + + "Hello\n# Header 1\nGoodbye\n", + "<p>Hello</p>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<p>Goodbye</p>\n", + + "* List\n# Header\n* List\n", + "<ul>\n<li><p>List</p>\n\n<h1 id=\"header\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n", + + "* List\n#Header\n* List\n", + "<ul>\n<li><p>List</p>\n\n<h1 id=\"header\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n", + + "* List\n * Nested list\n # Nested header\n", + "<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" + + "<h1 id=\"nested-header\">Nested header</h1></li>\n</ul></li>\n</ul>\n", + } + doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS) +} + func TestUnderlineHeaders(t *testing.T) { var tests = []string{ "Header 1\n========\n",@@ -285,6 +327,50 @@ "Double underline\n=====\n=====\n",
"<h1>Double underline</h1>\n\n<p>=====</p>\n", } doTestsBlock(t, tests, 0) +} + +func TestUnderlineHeadersAutoIDs(t *testing.T) { + var tests = []string{ + "Header 1\n========\n", + "<h1 id=\"header-1\">Header 1</h1>\n", + + "Header 2\n--------\n", + "<h2 id=\"header-2\">Header 2</h2>\n", + + "A\n=\n", + "<h1 id=\"a\">A</h1>\n", + + "B\n-\n", + "<h2 id=\"b\">B</h2>\n", + + "Paragraph\nHeader\n=\n", + "<p>Paragraph</p>\n\n<h1 id=\"header\">Header</h1>\n", + + "Header\n===\nParagraph\n", + "<h1 id=\"header\">Header</h1>\n\n<p>Paragraph</p>\n", + + "Header\n===\nAnother header\n---\n", + "<h1 id=\"header\">Header</h1>\n\n<h2 id=\"another-header\">Another header</h2>\n", + + " Header\n======\n", + "<h1 id=\"header\">Header</h1>\n", + + "Header with *inline*\n=====\n", + "<h1 id=\"header-with-inline\">Header with <em>inline</em></h1>\n", + + "Paragraph\n\n\n\n\nHeader\n===\n", + "<p>Paragraph</p>\n\n<h1 id=\"header\">Header</h1>\n", + + "Trailing space \n==== \n\n", + "<h1 id=\"trailing-space\">Trailing space</h1>\n", + + "Trailing spaces\n==== \n\n", + "<h1 id=\"trailing-spaces\">Trailing spaces</h1>\n", + + "Double underline\n=====\n=====\n", + "<h1 id=\"double-underline\">Double underline</h1>\n\n<p>=====</p>\n", + } + doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS) } func TestHorizontalRule(t *testing.T) {
M
markdown.go
→
markdown.go
@@ -41,6 +41,7 @@ EXTENSION_FOOTNOTES // Pandoc-style footnotes
EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, order list, unorder list)block EXTENSION_HEADER_IDS // specify header IDs with {#id} EXTENSION_TITLEBLOCK // Titleblock ala pandoc + EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text commonHtmlFlags = 0 | HTML_USE_XHTML |