Slug: Use urlencoding to support non-ASCII characters (#70691)

This commit is contained in:
Emil Tullstedt
2023-07-10 21:05:39 +02:00
committed by GitHub
parent d358bce562
commit 5c19272065
4 changed files with 58 additions and 35 deletions
+40 -17
View File
@@ -32,7 +32,7 @@ package slugify
import (
"bytes"
"encoding/base64"
"fmt"
"strings"
"unicode/utf8"
@@ -42,20 +42,18 @@ import (
var (
simpleSlugger = &slugger{
isValidCharacter: validCharacter,
replaceCharacter: '-',
replacementMap: getDefaultReplacements(),
omitMap: getDefaultOmitments(),
}
)
// Slugify creates a URL safe latin slug for a given value
// Slugify creates a URL safe version from a given string that is at most 50 bytes long.
func Slugify(value string) string {
s := simpleSlugger.Slugify(value)
if s == "" {
s = base64.RawURLEncoding.EncodeToString([]byte(value))
if len(s) > 50 || s == "" {
s = uuid.NewSHA1(uuid.NameSpaceOID, []byte(value)).String()
}
s := simpleSlugger.Slugify(strings.TrimSpace(value))
if len(s) > 50 || s == "" {
s = uuid.NewSHA1(uuid.NameSpaceOID, []byte(value)).String()
}
return s
}
@@ -66,21 +64,23 @@ func validCharacter(c rune) bool {
if c >= '0' && c <= '9' {
return true
}
if c == '_' || c == '-' {
return true
}
return false
}
// Slugifier based on settings
type slugger struct {
isValidCharacter func(c rune) bool
replaceCharacter rune
replacementMap map[rune]string
omitMap map[rune]struct{}
}
// Slugify creates a slug for a string
func (s slugger) Slugify(value string) string {
value = strings.ToLower(value)
var buffer bytes.Buffer
lastCharacterWasInvalid := false
for len(value) > 0 {
c, size := utf8.DecodeRuneInString(value)
@@ -88,24 +88,47 @@ func (s slugger) Slugify(value string) string {
if newCharacter, ok := s.replacementMap[c]; ok {
buffer.WriteString(newCharacter)
lastCharacterWasInvalid = false
continue
}
if s.isValidCharacter(c) {
buffer.WriteRune(c)
lastCharacterWasInvalid = false
} else if !lastCharacterWasInvalid {
buffer.WriteRune(s.replaceCharacter)
lastCharacterWasInvalid = true
continue
}
if _, ok := s.omitMap[c]; ok {
continue
}
p := make([]byte, 4)
size = utf8.EncodeRune(p, c)
for i := 0; i < size; i++ {
buffer.WriteString(fmt.Sprintf("%%%x", p[i]))
}
}
return strings.Trim(buffer.String(), string(s.replaceCharacter))
return buffer.String()
}
func getDefaultOmitments() map[rune]struct{} {
return map[rune]struct{}{
',': {},
'"': {},
'\'': {},
'\n': {},
'\r': {},
'\x00': {},
'?': {},
'.': {},
'(': {},
')': {},
}
}
func getDefaultReplacements() map[rune]string {
return map[rune]string{
' ': "-",
'&': "and",
'@': "at",
'©': "c",