package markup
import (
"strconv"
gast "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/util"
)
// slugIDs hands out heading anchors like goldmark's own generator — the
// same slug, then -1, -2 … for a repeat — but remembers where each
// repeat count stopped. Goldmark rescans from -1 on every repeat, so a
// document with n headings under the same name costs n² lookups: a
// megabyte of small "Notes" sections took minutes to render.
type slugIDs struct {
used map[string]bool
next map[string]int // slug -> the suffix to try next
}
func newSlugIDs() parser.IDs {
return &slugIDs{used: map[string]bool{}, next: map[string]int{}}
}
func (s *slugIDs) Put(value []byte) { s.used[string(value)] = true }
func (s *slugIDs) Generate(value []byte, kind gast.NodeKind) []byte {
value = util.TrimLeftSpace(value)
value = util.TrimRightSpace(value)
var slug []byte
for i := 0; i < len(value); {
v := value[i]
l := util.UTF8Len(v)
i += int(l)
if l != 1 {
continue
}
if util.IsAlphaNumeric(v) {
if 'A' <= v && v <= 'Z' {
v += 'a' - 'A'
}
slug = append(slug, v)
} else if util.IsSpace(v) || v == '-' || v == '_' {
slug = append(slug, '-')
}
}
if len(slug) == 0 {
if kind == gast.KindHeading {
slug = []byte("heading")
} else {
slug = []byte("id")
}
}
base := string(slug)
if !s.used[base] {
s.used[base] = true
return slug
}
for i := max(s.next[base], 1); ; i++ {
id := base + "-" + strconv.Itoa(i)
if !s.used[id] {
s.used[id] = true
s.next[base] = i + 1
return []byte(id)
}
}
}