From 9c061de92b235695e9e2220384c93093922b4153 Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Sun, 23 Nov 2014 20:37:27 -0500 Subject: [PATCH] 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`). --- block_test.go | 128 +++++++++++++++++++++++++++++++++++++++++++++++++- html.go | 17 ++++++- 2 files changed, 142 insertions(+), 3 deletions(-) diff --git a/block_test.go b/block_test.go index 1c4f50a..2451cd2 100644 --- a/block_test.go +++ b/block_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 @@ func doTestsBlock(t *testing.T, tests []string, extensions int) { 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 @@ func TestPrefixHeaderIdExtension(t *testing.T) { doTestsBlock(t, tests, EXTENSION_HEADER_IDS) } +func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) { + var tests = []string{ + "# header 1 {#someid}\n", + "

header 1

\n", + + "## header 2 {#someid}\n", + "

header 2

\n", + + "### header 3 {#someid}\n", + "

header 3

\n", + + "#### header 4 {#someid}\n", + "

header 4

\n", + + "##### header 5 {#someid}\n", + "
header 5
\n", + + "###### header 6 {#someid}\n", + "
header 6
\n", + + "####### header 7 {#someid}\n", + "
# header 7
\n", + + "# header 1 # {#someid}\n", + "

header 1

\n", + + "## header 2 ## {#someid}\n", + "

header 2

\n", + + "* List\n# Header {#someid}\n* List\n", + "\n", + + "* List\n#Header {#someid}\n* List\n", + "\n", + + "* List\n * Nested list\n # Nested header {#someid}\n", + "\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", @@ -288,6 +355,63 @@ func TestPrefixAutoHeaderIdExtension(t *testing.T) { doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS) } +func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) { + var tests = []string{ + "# Header 1\n", + "

Header 1

\n", + + "# Header 1 \n", + "

Header 1

\n", + + "## Header 2\n", + "

Header 2

\n", + + "### Header 3\n", + "

Header 3

\n", + + "#### Header 4\n", + "

Header 4

\n", + + "##### Header 5\n", + "
Header 5
\n", + + "###### Header 6\n", + "
Header 6
\n", + + "####### Header 7\n", + "
# Header 7
\n", + + "Hello\n# Header 1\nGoodbye\n", + "

Hello

\n\n

Header 1

\n\n

Goodbye

\n", + + "* List\n# Header\n* List\n", + "\n", + + "* List\n#Header\n* List\n", + "\n", + + "* List\n * Nested list\n # Nested header\n", + "\n", + + "# Header\n\n# Header\n", + "

Header

\n\n

Header

\n", + + "# Header 1\n\n# Header 1", + "

Header 1

\n\n

Header 1

\n", + + "# Header\n\n# Header 1\n\n# Header\n\n# Header", + "

Header

\n\n

Header 1

\n\n

Header

\n\n

Header

\n", + } + + parameters := HtmlRendererParameters{ + HeaderIDPrefix: "PRE:", + HeaderIDSuffix: ":POST", + } + + doTestsBlockWithRunner(t, tests, EXTENSION_AUTO_HEADER_IDS, runnerWithRendererParameters(parameters)) +} + func TestPrefixMultipleHeaderExtensions(t *testing.T) { var tests = []string{ "# Header\n\n# Header {#header}\n\n# Header 1", diff --git a/html.go b/html.go index a552508..cab8383 100644 --- a/html.go +++ b/html.go @@ -62,6 +62,11 @@ type HtmlRendererParameters struct { // HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string // [return] 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 @@ func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id s } if id != "" { - out.WriteString(fmt.Sprintf("", 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("", level, id)) } else { out.WriteString(fmt.Sprintf("", level)) }