all repos — grayfriday @ 9c061de92b235695e9e2220384c93093922b4153

blackfriday fork with a few changes

Allow configurable header ID prefix/suffixes.

This is specifically driven by the Hugo usecase where multiple documents
are often rendered into the same ultimate HTML page.

When a header ID is written to the output HTML format (either through
`HTML_TOC`, `EXTENSION_HEADER_IDS`, or `EXTENSION_AUTO_HEADER_IDS`), it
is possible that multiple documents will hvae identical header IDs. To
permit validation to pass, it is useful to have a per-document prefix or
suffix (in our case, an MD5 of the content filename, and we will be
using it as a suffix).

That is, two documents (`A` and `B`) that have the same header ID (`#
Reason {#reason}`), will end up having an actual header ID of the form
`#reason-DOCID` (e.g., `#reason-A`, `#reason-B`) with these HTML
parameters.

This is built on top of #126 (more intelligent collision detection for
`EXTENSION_AUTO_HEADER_IDS`).
Austin Ziegler austin@zieglers.ca
Sun, 23 Nov 2014 20:37:27 -0500
commit

9c061de92b235695e9e2220384c93093922b4153

parent

40f28ee0224972fc3ac84999e877778a81f1e87d

2 files changed, 142 insertions(+), 3 deletions(-)

jump to
M block_test.goblock_test.go

@@ -17,16 +17,35 @@ import (

"testing" ) +func runMarkdownBlockWithRenderer(input string, extensions int, renderer Renderer) string { + return string(Markdown([]byte(input), renderer, extensions)) +} + func runMarkdownBlock(input string, extensions int) string { htmlFlags := 0 htmlFlags |= HTML_USE_XHTML renderer := HtmlRenderer(htmlFlags, "", "") - return string(Markdown([]byte(input), renderer, extensions)) + return runMarkdownBlockWithRenderer(input, extensions, renderer) +} + +func runnerWithRendererParameters(parameters HtmlRendererParameters) func(string, int) string { + return func(input string, extensions int) string { + htmlFlags := 0 + htmlFlags |= HTML_USE_XHTML + + renderer := HtmlRendererWithParameters(htmlFlags, "", "", parameters) + + return runMarkdownBlockWithRenderer(input, extensions, renderer) + } } func doTestsBlock(t *testing.T, tests []string, extensions int) { + doTestsBlockWithRunner(t, tests, extensions, runMarkdownBlock) +} + +func doTestsBlockWithRunner(t *testing.T, tests []string, extensions int, runner func(string, int) string) { // catch and report panics var candidate string defer func() {

@@ -39,7 +58,7 @@ for i := 0; i+1 < len(tests); i += 2 {

input := tests[i] candidate = input expected := tests[i+1] - actual := runMarkdownBlock(candidate, extensions) + actual := runner(candidate, extensions) if actual != expected { t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]", candidate, expected, actual)

@@ -237,6 +256,54 @@ }

doTestsBlock(t, tests, EXTENSION_HEADER_IDS) } +func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) { + var tests = []string{ + "# header 1 {#someid}\n", + "<h1 id=\"PRE:someid:POST\">header 1</h1>\n", + + "## header 2 {#someid}\n", + "<h2 id=\"PRE:someid:POST\">header 2</h2>\n", + + "### header 3 {#someid}\n", + "<h3 id=\"PRE:someid:POST\">header 3</h3>\n", + + "#### header 4 {#someid}\n", + "<h4 id=\"PRE:someid:POST\">header 4</h4>\n", + + "##### header 5 {#someid}\n", + "<h5 id=\"PRE:someid:POST\">header 5</h5>\n", + + "###### header 6 {#someid}\n", + "<h6 id=\"PRE:someid:POST\">header 6</h6>\n", + + "####### header 7 {#someid}\n", + "<h6 id=\"PRE:someid:POST\"># header 7</h6>\n", + + "# header 1 # {#someid}\n", + "<h1 id=\"PRE:someid:POST\">header 1</h1>\n", + + "## header 2 ## {#someid}\n", + "<h2 id=\"PRE:someid:POST\">header 2</h2>\n", + + "* List\n# Header {#someid}\n* List\n", + "<ul>\n<li><p>List</p>\n\n<h1 id=\"PRE:someid:POST\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n", + + "* List\n#Header {#someid}\n* List\n", + "<ul>\n<li><p>List</p>\n\n<h1 id=\"PRE:someid:POST\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n", + + "* List\n * Nested list\n # Nested header {#someid}\n", + "<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" + + "<h1 id=\"PRE:someid:POST\">Nested header</h1></li>\n</ul></li>\n</ul>\n", + } + + parameters := HtmlRendererParameters{ + HeaderIDPrefix: "PRE:", + HeaderIDSuffix: ":POST", + } + + doTestsBlockWithRunner(t, tests, EXTENSION_HEADER_IDS, runnerWithRendererParameters(parameters)) +} + func TestPrefixAutoHeaderIdExtension(t *testing.T) { var tests = []string{ "# Header 1\n",

@@ -286,6 +353,63 @@ "# Header\n\n# Header 1\n\n# Header\n\n# Header",

"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header</h1>\n\n<h1 id=\"header-1-2\">Header</h1>\n", } doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS) +} + +func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) { + var tests = []string{ + "# Header 1\n", + "<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n", + + "# Header 1 \n", + "<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n", + + "## Header 2\n", + "<h2 id=\"PRE:header-2:POST\">Header 2</h2>\n", + + "### Header 3\n", + "<h3 id=\"PRE:header-3:POST\">Header 3</h3>\n", + + "#### Header 4\n", + "<h4 id=\"PRE:header-4:POST\">Header 4</h4>\n", + + "##### Header 5\n", + "<h5 id=\"PRE:header-5:POST\">Header 5</h5>\n", + + "###### Header 6\n", + "<h6 id=\"PRE:header-6:POST\">Header 6</h6>\n", + + "####### Header 7\n", + "<h6 id=\"PRE:-header-7:POST\"># Header 7</h6>\n", + + "Hello\n# Header 1\nGoodbye\n", + "<p>Hello</p>\n\n<h1 id=\"PRE:header-1:POST\">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=\"PRE:header:POST\">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=\"PRE:header:POST\">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=\"PRE:nested-header:POST\">Nested header</h1></li>\n</ul></li>\n</ul>\n", + + "# Header\n\n# Header\n", + "<h1 id=\"PRE:header:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1:POST\">Header</h1>\n", + + "# Header 1\n\n# Header 1", + "<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n\n<h1 id=\"PRE:header-1-1:POST\">Header 1</h1>\n", + + "# Header\n\n# Header 1\n\n# Header\n\n# Header", + "<h1 id=\"PRE:header:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n\n<h1 id=\"PRE:header-1-1:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1-2:POST\">Header</h1>\n", + } + + parameters := HtmlRendererParameters{ + HeaderIDPrefix: "PRE:", + HeaderIDSuffix: ":POST", + } + + doTestsBlockWithRunner(t, tests, EXTENSION_AUTO_HEADER_IDS, runnerWithRendererParameters(parameters)) } func TestPrefixMultipleHeaderExtensions(t *testing.T) {
M html.gohtml.go

@@ -62,6 +62,11 @@ // Show this text inside the <a> tag for a footnote return link, if the

// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string // <sup>[return]</sup> is used. FootnoteReturnLinkContents string + // If set, add this text to the front of each Header ID, to ensure + // uniqueness. + HeaderIDPrefix string + // If set, add this text to the back of each Header ID, to ensure uniqueness. + HeaderIDSuffix string } // Html is a type that implements the Renderer interface for HTML output.

@@ -200,7 +205,17 @@ id = fmt.Sprintf("toc_%d", options.headerCount)

} if id != "" { - out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, options.ensureUniqueHeaderID(id))) + id = options.ensureUniqueHeaderID(id) + + if options.parameters.HeaderIDPrefix != "" { + id = options.parameters.HeaderIDPrefix + id + } + + if options.parameters.HeaderIDSuffix != "" { + id = id + options.parameters.HeaderIDSuffix + } + + out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id)) } else { out.WriteString(fmt.Sprintf("<h%d>", level)) }