diff --git a/block.go b/block.go index b7d870c..23a693c 100644 --- a/block.go +++ b/block.go @@ -15,7 +15,7 @@ package blackfriday import ( "bytes" - "unicode" + "github.com/shurcooL/go/github_flavored_markdown/sanitized_anchor_name" ) // Parse block-level data. @@ -227,7 +227,7 @@ func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int { } if end > i { if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = createSanitizedAnchorName(string(data[i:end])) + id = sanitized_anchor_name.Create(string(data[i:end])) } work := func() bool { p.inline(out, data[i:end]) @@ -1276,7 +1276,7 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { id := "" if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = createSanitizedAnchorName(string(data[prev:eol])) + id = sanitized_anchor_name.Create(string(data[prev:eol])) } p.r.Header(out, work, level, id) @@ -1325,16 +1325,3 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { 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) -} diff --git a/block_test.go b/block_test.go index de77aa9..1c4f50a 100644 --- a/block_test.go +++ b/block_test.go @@ -275,10 +275,27 @@ func TestPrefixAutoHeaderIdExtension(t *testing.T) { "* 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", } doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS) } +func TestPrefixMultipleHeaderExtensions(t *testing.T) { + var tests = []string{ + "# Header\n\n# Header {#header}\n\n# Header 1", + "

Header

\n\n

Header

\n\n

Header 1

\n", + } + doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS|EXTENSION_HEADER_IDS) +} + func TestUnderlineHeaders(t *testing.T) { var tests = []string{ "Header 1\n========\n", @@ -369,6 +386,12 @@ func TestUnderlineHeadersAutoIDs(t *testing.T) { "Double underline\n=====\n=====\n", "

Double underline

\n\n

=====

\n", + + "Header\n======\n\nHeader\n======\n", + "

Header

\n\n

Header

\n", + + "Header 1\n========\n\nHeader 1\n========\n", + "

Header 1

\n\n

Header 1

\n", } doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS) } diff --git a/html.go b/html.go index a85f918..a552508 100644 --- a/html.go +++ b/html.go @@ -81,6 +81,9 @@ type Html struct { currentLevel int toc *bytes.Buffer + // Track header IDs to prevent ID collision in a single generation. + headerIDs map[string]int + smartypants *smartypantsRenderer } @@ -123,6 +126,8 @@ func HtmlRendererWithParameters(flags int, title string, currentLevel: 0, toc: new(bytes.Buffer), + headerIDs: make(map[string]int), + smartypants: smartypants(flags), } } @@ -190,11 +195,12 @@ func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id s marker := out.Len() doubleSpace(out) + if id == "" && options.flags&HTML_TOC != 0 { + id = fmt.Sprintf("toc_%d", options.headerCount) + } + if id != "" { - out.WriteString(fmt.Sprintf("", level, id)) - } else if options.flags&HTML_TOC != 0 { - // headerCount is incremented in htmlTocHeader - out.WriteString(fmt.Sprintf("", level, options.headerCount)) + out.WriteString(fmt.Sprintf("", level, options.ensureUniqueHeaderID(id))) } else { out.WriteString(fmt.Sprintf("", level)) } @@ -853,3 +859,22 @@ func isRelativeLink(link []byte) (yes bool) { } return } + +func (options *Html) ensureUniqueHeaderID(id string) string { + for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] { + tmp := fmt.Sprintf("%s-%d", id, count+1) + + if _, tmpFound := options.headerIDs[tmp]; !tmpFound { + options.headerIDs[id] = count + 1 + id = tmp + } else { + id = id + "-1" + } + } + + if _, found := options.headerIDs[id]; !found { + options.headerIDs[id] = 0 + } + + return id +}