russross/blackfriday — pull request #126 by halostatue (2014-11-01 23:45:15+00:00)
Title: Prevent header collisions on generation.

The automatic header ID generation code that I submitted (#125) has a
subtle bug where it will use the same ID for two headers that are
identical. In the case below, both headers will be rendered as `

Header

`.

```markdown
# Header

# Header

# Header
```

This change is a relatively naive approach that uses an incrementing
counter for cases like this to prevent header collision (resulting in
`header`, `header-1`, and `header-2`). This will not prevent a collision
like this:

```markdown
# Header

# Header 1

# Header
```

This could be prevented by a somewhat smarter approach that appends a
suffix (like `-1`) to each collision (resulting in `header`, `header-1`,
`header-1-1`), but that feels a bit wrong and the implementation is
heavyweight in two ways:

1. The `parser.headers` would be changed from `map[string]int` to
`map[string]bool`, and would grow larger for *each* header that
collides, because each of the forms `header`, `header-1`, and
`header-1-1` would be put into `parser.headers`.
2. `parser.createSanitizedAnchorName` would need to be modified to be
something like what follows. (The code here is untested.)

```go
func (p *parser) 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 ensureUniqueAnchor(string(anchorName))
}

func (p *parser) ensureUniqueAnchor(anchor string) string {
for _, found := p.headers[anchor]; found; found = p.headers[anchor] {
anchor = anchor + "-1";
}

p.headers[anchor] = true

return anchor
}
```