2022-11-30 10:12:56 -06:00
|
|
|
|
/*
|
|
|
|
|
* This file evolved from the MIT licensed: https://github.com/machiel/slugify
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
The MIT License (MIT)
|
|
|
|
|
|
|
|
|
|
Copyright (c) 2015 Machiel Molenaar
|
|
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
|
|
|
copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
SOFTWARE.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package slugify
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2023-07-10 14:05:39 -05:00
|
|
|
|
"fmt"
|
2022-11-30 10:12:56 -06:00
|
|
|
|
"strings"
|
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
2023-03-21 05:31:29 -05:00
|
|
|
|
"github.com/google/uuid"
|
2022-11-30 10:12:56 -06:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
simpleSlugger = &slugger{
|
|
|
|
|
isValidCharacter: validCharacter,
|
2023-08-10 15:12:50 -05:00
|
|
|
|
replaceCharacter: '-',
|
2022-11-30 10:12:56 -06:00
|
|
|
|
replacementMap: getDefaultReplacements(),
|
2023-07-10 14:05:39 -05:00
|
|
|
|
omitMap: getDefaultOmitments(),
|
2022-11-30 10:12:56 -06:00
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2023-07-10 14:05:39 -05:00
|
|
|
|
// Slugify creates a URL safe version from a given string that is at most 50 bytes long.
|
2022-11-30 10:12:56 -06:00
|
|
|
|
func Slugify(value string) string {
|
2023-07-10 14:05:39 -05:00
|
|
|
|
s := simpleSlugger.Slugify(strings.TrimSpace(value))
|
|
|
|
|
if len(s) > 50 || s == "" {
|
|
|
|
|
s = uuid.NewSHA1(uuid.NameSpaceOID, []byte(value)).String()
|
2022-11-30 10:12:56 -06:00
|
|
|
|
}
|
2023-07-10 14:05:39 -05:00
|
|
|
|
|
2022-11-30 10:12:56 -06:00
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func validCharacter(c rune) bool {
|
|
|
|
|
if c >= 'a' && c <= 'z' {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if c >= '0' && c <= '9' {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Slugifier based on settings
|
|
|
|
|
type slugger struct {
|
2023-08-10 15:12:50 -05:00
|
|
|
|
replaceCharacter rune
|
2022-11-30 10:12:56 -06:00
|
|
|
|
isValidCharacter func(c rune) bool
|
|
|
|
|
replacementMap map[rune]string
|
2023-07-10 14:05:39 -05:00
|
|
|
|
omitMap map[rune]struct{}
|
2022-11-30 10:12:56 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Slugify creates a slug for a string
|
|
|
|
|
func (s slugger) Slugify(value string) string {
|
|
|
|
|
value = strings.ToLower(value)
|
|
|
|
|
var buffer bytes.Buffer
|
2023-08-10 15:12:50 -05:00
|
|
|
|
lastCharacterWasInvalid := false
|
2022-11-30 10:12:56 -06:00
|
|
|
|
|
|
|
|
|
for len(value) > 0 {
|
|
|
|
|
c, size := utf8.DecodeRuneInString(value)
|
|
|
|
|
value = value[size:]
|
|
|
|
|
|
|
|
|
|
if newCharacter, ok := s.replacementMap[c]; ok {
|
2023-08-10 15:12:50 -05:00
|
|
|
|
if lastCharacterWasInvalid {
|
|
|
|
|
buffer.WriteRune(s.replaceCharacter)
|
|
|
|
|
}
|
2022-11-30 10:12:56 -06:00
|
|
|
|
buffer.WriteString(newCharacter)
|
2023-08-10 15:12:50 -05:00
|
|
|
|
lastCharacterWasInvalid = false
|
2022-11-30 10:12:56 -06:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if s.isValidCharacter(c) {
|
2023-08-10 15:12:50 -05:00
|
|
|
|
if lastCharacterWasInvalid {
|
|
|
|
|
buffer.WriteRune(s.replaceCharacter)
|
|
|
|
|
}
|
2022-11-30 10:12:56 -06:00
|
|
|
|
buffer.WriteRune(c)
|
2023-08-10 15:12:50 -05:00
|
|
|
|
lastCharacterWasInvalid = false
|
2023-07-10 14:05:39 -05:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, ok := s.omitMap[c]; ok {
|
2023-08-10 15:12:50 -05:00
|
|
|
|
lastCharacterWasInvalid = true
|
2023-07-10 14:05:39 -05:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p := make([]byte, 4)
|
|
|
|
|
size = utf8.EncodeRune(p, c)
|
2023-08-10 15:12:50 -05:00
|
|
|
|
if lastCharacterWasInvalid {
|
|
|
|
|
buffer.WriteRune(s.replaceCharacter)
|
|
|
|
|
}
|
2023-07-10 14:05:39 -05:00
|
|
|
|
for i := 0; i < size; i++ {
|
2023-08-10 15:12:50 -05:00
|
|
|
|
buffer.WriteString(fmt.Sprintf("%x", p[i]))
|
2022-11-30 10:12:56 -06:00
|
|
|
|
}
|
2023-08-10 15:12:50 -05:00
|
|
|
|
lastCharacterWasInvalid = true
|
2022-11-30 10:12:56 -06:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-10 15:12:50 -05:00
|
|
|
|
return strings.Trim(buffer.String(), string(s.replaceCharacter))
|
2023-07-10 14:05:39 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getDefaultOmitments() map[rune]struct{} {
|
|
|
|
|
return map[rune]struct{}{
|
2023-08-10 15:12:50 -05:00
|
|
|
|
' ': {},
|
2023-07-10 14:05:39 -05:00
|
|
|
|
',': {},
|
|
|
|
|
'"': {},
|
|
|
|
|
'\'': {},
|
|
|
|
|
'\n': {},
|
|
|
|
|
'\r': {},
|
|
|
|
|
'\x00': {},
|
|
|
|
|
'?': {},
|
|
|
|
|
'.': {},
|
|
|
|
|
'(': {},
|
|
|
|
|
')': {},
|
2023-08-10 15:12:50 -05:00
|
|
|
|
'-': {},
|
|
|
|
|
'_': {},
|
|
|
|
|
'[': {},
|
|
|
|
|
']': {},
|
|
|
|
|
'/': {},
|
|
|
|
|
'\\': {},
|
|
|
|
|
'!': {},
|
|
|
|
|
'{': {},
|
|
|
|
|
'}': {},
|
|
|
|
|
'%': {},
|
2023-07-10 14:05:39 -05:00
|
|
|
|
}
|
2022-11-30 10:12:56 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getDefaultReplacements() map[rune]string {
|
|
|
|
|
return map[rune]string{
|
|
|
|
|
'&': "and",
|
|
|
|
|
'@': "at",
|
|
|
|
|
'©': "c",
|
|
|
|
|
'®': "r",
|
|
|
|
|
'Æ': "ae",
|
|
|
|
|
'ß': "ss",
|
|
|
|
|
'à': "a",
|
|
|
|
|
'á': "a",
|
|
|
|
|
'â': "a",
|
|
|
|
|
'ä': "a", // or "ae"
|
|
|
|
|
'å': "a",
|
|
|
|
|
'æ': "ae",
|
|
|
|
|
'ç': "c",
|
|
|
|
|
'è': "e",
|
|
|
|
|
'é': "e",
|
|
|
|
|
'ê': "e",
|
|
|
|
|
'ë': "e",
|
|
|
|
|
'ì': "i",
|
|
|
|
|
'í': "i",
|
|
|
|
|
'î': "i",
|
|
|
|
|
'ï': "i",
|
|
|
|
|
'ò': "o",
|
|
|
|
|
'ó': "o",
|
|
|
|
|
'ô': "o",
|
|
|
|
|
'õ': "o",
|
|
|
|
|
'ö': "o", // or "oe"?
|
|
|
|
|
'ø': "o",
|
|
|
|
|
'ù': "u",
|
|
|
|
|
'ú': "u",
|
|
|
|
|
'û': "u",
|
|
|
|
|
'ü': "ue",
|
|
|
|
|
'ý': "y",
|
|
|
|
|
'þ': "p",
|
|
|
|
|
'ÿ': "y",
|
|
|
|
|
'ā': "a",
|
|
|
|
|
'ă': "a",
|
|
|
|
|
'Ą': "a",
|
|
|
|
|
'ą': "a",
|
|
|
|
|
'ć': "c",
|
|
|
|
|
'ĉ': "c",
|
|
|
|
|
'ċ': "c",
|
|
|
|
|
'č': "c",
|
|
|
|
|
'ď': "d",
|
|
|
|
|
'đ': "d",
|
|
|
|
|
'ē': "e",
|
|
|
|
|
'ĕ': "e",
|
|
|
|
|
'ė': "e",
|
|
|
|
|
'ę': "e",
|
|
|
|
|
'ě': "e",
|
|
|
|
|
'ĝ': "g",
|
|
|
|
|
'ğ': "g",
|
|
|
|
|
'ġ': "g",
|
|
|
|
|
'ģ': "g",
|
|
|
|
|
'ĥ': "h",
|
|
|
|
|
'ħ': "h",
|
|
|
|
|
'ĩ': "i",
|
|
|
|
|
'ī': "i",
|
|
|
|
|
'ĭ': "i",
|
|
|
|
|
'į': "i",
|
|
|
|
|
'ı': "i",
|
|
|
|
|
'ij': "ij",
|
|
|
|
|
'ĵ': "j",
|
|
|
|
|
'ķ': "k",
|
|
|
|
|
'ĸ': "k",
|
|
|
|
|
'Ĺ': "l",
|
|
|
|
|
'ĺ': "l",
|
|
|
|
|
'ļ': "l",
|
|
|
|
|
'ľ': "l",
|
|
|
|
|
'ŀ': "l",
|
|
|
|
|
'ł': "l",
|
|
|
|
|
'ń': "n",
|
|
|
|
|
'ņ': "n",
|
|
|
|
|
'ň': "n",
|
|
|
|
|
'ʼn': "n",
|
|
|
|
|
'ŋ': "n",
|
|
|
|
|
'ō': "o",
|
|
|
|
|
'ŏ': "o",
|
|
|
|
|
'ő': "o",
|
|
|
|
|
'Œ': "oe",
|
|
|
|
|
'œ': "oe",
|
|
|
|
|
'ŕ': "r",
|
|
|
|
|
'ŗ': "r",
|
|
|
|
|
'ř': "r",
|
|
|
|
|
'ś': "s",
|
|
|
|
|
'ŝ': "s",
|
|
|
|
|
'ş': "s",
|
|
|
|
|
'š': "s",
|
|
|
|
|
'ţ': "t",
|
|
|
|
|
'ť': "t",
|
|
|
|
|
'ŧ': "t",
|
|
|
|
|
'ũ': "u",
|
|
|
|
|
'ū': "u",
|
|
|
|
|
'ŭ': "u",
|
|
|
|
|
'ů': "u",
|
|
|
|
|
'ű': "u",
|
|
|
|
|
'ų': "u",
|
|
|
|
|
'ŵ': "w",
|
|
|
|
|
'ŷ': "y",
|
|
|
|
|
'ź': "z",
|
|
|
|
|
'ż': "z",
|
|
|
|
|
'ž': "z",
|
|
|
|
|
'ſ': "z",
|
|
|
|
|
'Ə': "e",
|
|
|
|
|
'ƒ': "f",
|
|
|
|
|
'Ơ': "o",
|
|
|
|
|
'ơ': "o",
|
|
|
|
|
'Ư': "u",
|
|
|
|
|
'ư': "u",
|
|
|
|
|
'ǎ': "a",
|
|
|
|
|
'ǐ': "i",
|
|
|
|
|
'ǒ': "o",
|
|
|
|
|
'ǔ': "u",
|
|
|
|
|
'ǖ': "u",
|
|
|
|
|
'ǘ': "u",
|
|
|
|
|
'ǚ': "u",
|
|
|
|
|
'ǜ': "u",
|
|
|
|
|
'ǻ': "a",
|
|
|
|
|
'Ǽ': "ae",
|
|
|
|
|
'ǽ': "ae",
|
|
|
|
|
'Ǿ': "o",
|
|
|
|
|
'ǿ': "o",
|
|
|
|
|
'ə': "e",
|
|
|
|
|
'Є': "e",
|
|
|
|
|
'Б': "b",
|
|
|
|
|
'Г': "g",
|
|
|
|
|
'Д': "d",
|
|
|
|
|
'Ж': "zh",
|
|
|
|
|
'З': "z",
|
|
|
|
|
'У': "u",
|
|
|
|
|
'Ф': "f",
|
|
|
|
|
'Х': "h",
|
|
|
|
|
'Ц': "c",
|
|
|
|
|
'Ч': "ch",
|
|
|
|
|
'Ш': "sh",
|
|
|
|
|
'Щ': "sch",
|
|
|
|
|
'Ъ': "-",
|
|
|
|
|
'Ы': "y",
|
|
|
|
|
'Ь': "-",
|
|
|
|
|
'Э': "je",
|
|
|
|
|
'Ю': "ju",
|
|
|
|
|
'Я': "ja",
|
|
|
|
|
'а': "a",
|
|
|
|
|
'б': "b",
|
|
|
|
|
'в': "v",
|
|
|
|
|
'г': "g",
|
|
|
|
|
'д': "d",
|
|
|
|
|
'е': "e",
|
|
|
|
|
'ж': "zh",
|
|
|
|
|
'з': "z",
|
|
|
|
|
'и': "i",
|
|
|
|
|
'й': "j",
|
|
|
|
|
'к': "k",
|
|
|
|
|
'л': "l",
|
|
|
|
|
'м': "m",
|
|
|
|
|
'н': "n",
|
|
|
|
|
'о': "o",
|
|
|
|
|
'п': "p",
|
|
|
|
|
'р': "r",
|
|
|
|
|
'с': "s",
|
|
|
|
|
'т': "t",
|
|
|
|
|
'у': "u",
|
|
|
|
|
'ф': "f",
|
|
|
|
|
'х': "h",
|
|
|
|
|
'ц': "c",
|
|
|
|
|
'ч': "ch",
|
|
|
|
|
'ш': "sh",
|
|
|
|
|
'щ': "sch",
|
|
|
|
|
'ъ': "-",
|
|
|
|
|
'ы': "y",
|
|
|
|
|
'ь': "-",
|
|
|
|
|
'э': "je",
|
|
|
|
|
'ю': "ju",
|
|
|
|
|
'я': "ja",
|
|
|
|
|
'ё': "jo",
|
|
|
|
|
'є': "e",
|
|
|
|
|
'і': "i",
|
|
|
|
|
'ї': "i",
|
|
|
|
|
'Ґ': "g",
|
|
|
|
|
'ґ': "g",
|
|
|
|
|
'א': "a",
|
|
|
|
|
'ב': "b",
|
|
|
|
|
'ג': "g",
|
|
|
|
|
'ד': "d",
|
|
|
|
|
'ה': "h",
|
|
|
|
|
'ו': "v",
|
|
|
|
|
'ז': "z",
|
|
|
|
|
'ח': "h",
|
|
|
|
|
'ט': "t",
|
|
|
|
|
'י': "i",
|
|
|
|
|
'ך': "k",
|
|
|
|
|
'כ': "k",
|
|
|
|
|
'ל': "l",
|
|
|
|
|
'ם': "m",
|
|
|
|
|
'מ': "m",
|
|
|
|
|
'ן': "n",
|
|
|
|
|
'נ': "n",
|
|
|
|
|
'ס': "s",
|
|
|
|
|
'ע': "e",
|
|
|
|
|
'ף': "p",
|
|
|
|
|
'פ': "p",
|
|
|
|
|
'ץ': "C",
|
|
|
|
|
'צ': "c",
|
|
|
|
|
'ק': "q",
|
|
|
|
|
'ר': "r",
|
|
|
|
|
'ש': "w",
|
|
|
|
|
'ת': "t",
|
|
|
|
|
'™': "tm",
|
|
|
|
|
'ả': "a",
|
|
|
|
|
'ã': "a",
|
|
|
|
|
'ạ': "a",
|
|
|
|
|
|
|
|
|
|
'ắ': "a",
|
|
|
|
|
'ằ': "a",
|
|
|
|
|
'ẳ': "a",
|
|
|
|
|
'ẵ': "a",
|
|
|
|
|
'ặ': "a",
|
|
|
|
|
|
|
|
|
|
'ấ': "a",
|
|
|
|
|
'ầ': "a",
|
|
|
|
|
'ẩ': "a",
|
|
|
|
|
'ẫ': "a",
|
|
|
|
|
'ậ': "a",
|
|
|
|
|
|
|
|
|
|
'ẻ': "e",
|
|
|
|
|
'ẽ': "e",
|
|
|
|
|
'ẹ': "e",
|
|
|
|
|
'ế': "e",
|
|
|
|
|
'ề': "e",
|
|
|
|
|
'ể': "e",
|
|
|
|
|
'ễ': "e",
|
|
|
|
|
'ệ': "e",
|
|
|
|
|
|
|
|
|
|
'ỉ': "i",
|
|
|
|
|
'ị': "i",
|
|
|
|
|
|
|
|
|
|
'ỏ': "o",
|
|
|
|
|
'ọ': "o",
|
|
|
|
|
'ố': "o",
|
|
|
|
|
'ồ': "o",
|
|
|
|
|
'ổ': "o",
|
|
|
|
|
'ỗ': "o",
|
|
|
|
|
'ộ': "o",
|
|
|
|
|
'ớ': "o",
|
|
|
|
|
'ờ': "o",
|
|
|
|
|
'ở': "o",
|
|
|
|
|
'ỡ': "o",
|
|
|
|
|
'ợ': "o",
|
|
|
|
|
|
|
|
|
|
'ủ': "u",
|
|
|
|
|
'ụ': "u",
|
|
|
|
|
'ứ': "u",
|
|
|
|
|
'ừ': "u",
|
|
|
|
|
'ử': "u",
|
|
|
|
|
'ữ': "u",
|
|
|
|
|
'ự': "u",
|
|
|
|
|
|
|
|
|
|
'ỳ': "y",
|
|
|
|
|
'ỷ': "y",
|
|
|
|
|
'ỹ': "y",
|
|
|
|
|
'ỵ': "y",
|
|
|
|
|
}
|
|
|
|
|
}
|