Merge pull request #126 from halostatue/generate-unique-header-ids
Prevent header collisions on generation.
This commit is contained in:
commit
1f004e1c39
19
block.go
19
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)
|
||||
}
|
||||
|
|
|
@ -275,10 +275,27 @@ func TestPrefixAutoHeaderIdExtension(t *testing.T) {
|
|||
"* 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",
|
||||
|
||||
"# Header\n\n# Header\n",
|
||||
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n",
|
||||
|
||||
"# Header 1\n\n# Header 1",
|
||||
"<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
||||
|
||||
"# 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 TestPrefixMultipleHeaderExtensions(t *testing.T) {
|
||||
var tests = []string{
|
||||
"# Header\n\n# Header {#header}\n\n# Header 1",
|
||||
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\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",
|
||||
"<h1 id=\"double-underline\">Double underline</h1>\n\n<p>=====</p>\n",
|
||||
|
||||
"Header\n======\n\nHeader\n======\n",
|
||||
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n",
|
||||
|
||||
"Header 1\n========\n\nHeader 1\n========\n",
|
||||
"<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
||||
}
|
||||
doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS)
|
||||
}
|
||||
|
|
33
html.go
33
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("<h%d id=\"%s\">", level, id))
|
||||
} else if options.flags&HTML_TOC != 0 {
|
||||
// headerCount is incremented in htmlTocHeader
|
||||
out.WriteString(fmt.Sprintf("<h%d id=\"toc_%d\">", level, options.headerCount))
|
||||
out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, options.ensureUniqueHeaderID(id)))
|
||||
} else {
|
||||
out.WriteString(fmt.Sprintf("<h%d>", 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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue