github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/markup/goldmark/autoid.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package goldmark
    15  
    16  import (
    17  	"bytes"
    18  	"strconv"
    19  	"unicode"
    20  	"unicode/utf8"
    21  
    22  	"github.com/gohugoio/hugo/markup/blackfriday"
    23  
    24  	"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
    25  
    26  	"github.com/gohugoio/hugo/common/text"
    27  
    28  	"github.com/yuin/goldmark/ast"
    29  	"github.com/yuin/goldmark/parser"
    30  	"github.com/yuin/goldmark/util"
    31  
    32  	bp "github.com/gohugoio/hugo/bufferpool"
    33  )
    34  
    35  func sanitizeAnchorNameString(s string, idType string) string {
    36  	return string(sanitizeAnchorName([]byte(s), idType))
    37  }
    38  
    39  func sanitizeAnchorName(b []byte, idType string) []byte {
    40  	return sanitizeAnchorNameWithHook(b, idType, nil)
    41  }
    42  
    43  func sanitizeAnchorNameWithHook(b []byte, idType string, hook func(buf *bytes.Buffer)) []byte {
    44  	buf := bp.GetBuffer()
    45  
    46  	if idType == goldmark_config.AutoHeadingIDTypeBlackfriday {
    47  		// TODO(bep) make it more efficient.
    48  		buf.WriteString(blackfriday.SanitizedAnchorName(string(b)))
    49  	} else {
    50  		asciiOnly := idType == goldmark_config.AutoHeadingIDTypeGitHubAscii
    51  
    52  		if asciiOnly {
    53  			// Normalize it to preserve accents if possible.
    54  			b = text.RemoveAccents(b)
    55  		}
    56  
    57  		b = bytes.TrimSpace(b)
    58  
    59  		for len(b) > 0 {
    60  			r, size := utf8.DecodeRune(b)
    61  			switch {
    62  			case asciiOnly && size != 1:
    63  			case r == '-' || r == ' ':
    64  				buf.WriteRune('-')
    65  			case isAlphaNumeric(r):
    66  				buf.WriteRune(unicode.ToLower(r))
    67  			default:
    68  			}
    69  
    70  			b = b[size:]
    71  		}
    72  	}
    73  
    74  	if hook != nil {
    75  		hook(buf)
    76  	}
    77  
    78  	result := make([]byte, buf.Len())
    79  	copy(result, buf.Bytes())
    80  
    81  	bp.PutBuffer(buf)
    82  
    83  	return result
    84  }
    85  
    86  func isAlphaNumeric(r rune) bool {
    87  	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
    88  }
    89  
    90  var _ parser.IDs = (*idFactory)(nil)
    91  
    92  type idFactory struct {
    93  	idType string
    94  	vals   map[string]struct{}
    95  }
    96  
    97  func newIDFactory(idType string) *idFactory {
    98  	return &idFactory{
    99  		vals:   make(map[string]struct{}),
   100  		idType: idType,
   101  	}
   102  }
   103  
   104  func (ids *idFactory) Generate(value []byte, kind ast.NodeKind) []byte {
   105  	return sanitizeAnchorNameWithHook(value, ids.idType, func(buf *bytes.Buffer) {
   106  		if buf.Len() == 0 {
   107  			if kind == ast.KindHeading {
   108  				buf.WriteString("heading")
   109  			} else {
   110  				buf.WriteString("id")
   111  			}
   112  		}
   113  
   114  		if _, found := ids.vals[util.BytesToReadOnlyString(buf.Bytes())]; found {
   115  			// Append a hypen and a number, starting with 1.
   116  			buf.WriteRune('-')
   117  			pos := buf.Len()
   118  			for i := 1; ; i++ {
   119  				buf.WriteString(strconv.Itoa(i))
   120  				if _, found := ids.vals[util.BytesToReadOnlyString(buf.Bytes())]; !found {
   121  					break
   122  				}
   123  				buf.Truncate(pos)
   124  			}
   125  		}
   126  
   127  		ids.vals[buf.String()] = struct{}{}
   128  	})
   129  }
   130  
   131  func (ids *idFactory) Put(value []byte) {
   132  	ids.vals[util.BytesToReadOnlyString(value)] = struct{}{}
   133  }