555 lines
32 KiB
Go
555 lines
32 KiB
Go
|
|
package stringx
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bytes"
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
"reflect"
|
|||
|
|
"regexp"
|
|||
|
|
"regexp/syntax"
|
|||
|
|
"sort"
|
|||
|
|
"strconv"
|
|||
|
|
"strings"
|
|||
|
|
"unicode"
|
|||
|
|
"unsafe"
|
|||
|
|
|
|||
|
|
"git.cloudyne.io/go/hiscaler-gox/slicex"
|
|||
|
|
"golang.org/x/text/width"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
rxEmoji = regexp.MustCompile(`[\x{1F3F4}](?:\x{E0067}\x{E0062}\x{E0077}\x{E006C}\x{E0073}\x{E007F})|[\x{1F3F4}](?:\x{E0067}\x{E0062}\x{E0073}\x{E0063}\x{E0074}\x{E007F})|[\x{1F3F4}](?:\x{E0067}\x{E0062}\x{E0065}\x{E006E}\x{E0067}\x{E007F})|[\x{1F3F4}](?:\x{200D}\x{2620}\x{FE0F})|[\x{1F3F3}](?:\x{FE0F}\x{200D}\x{1F308})|[\x{0023}\x{002A}\x{0030}\x{0031}\x{0032}\x{0033}\x{0034}\x{0035}\x{0036}\x{0037}\x{0038}\x{0039}](?:\x{FE0F}\x{20E3})|[\x{1F441}](?:\x{FE0F}\x{200D}\x{1F5E8}\x{FE0F})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F467}\x{200D}\x{1F467})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F467}\x{200D}\x{1F466})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F467})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F466}\x{200D}\x{1F466})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F466})|[\x{1F468}](?:\x{200D}\x{1F468}\x{200D}\x{1F467}\x{200D}\x{1F467})|[\x{1F468}](?:\x{200D}\x{1F468}\x{200D}\x{1F466}\x{200D}\x{1F466})|[\x{1F468}](?:\x{200D}\x{1F468}\x{200D}\x{1F467}\x{200D}\x{1F466})|[\x{1F468}](?:\x{200D}\x{1F468}\x{200D}\x{1F467})|[\x{1F468}](?:\x{200D}\x{1F468}\x{200D}\x{1F466})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F467})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F466})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F469}\x{200D}\x{1F467})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F469}\x{200D}\x{1F466})|[\x{1F469}](?:\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F469})|[\x{1F469}\x{1F468}](?:\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F468})|[\x{1F469}](?:\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F48B}\x{200D}\x{1F469})|[\x{1F469}\x{1F468}](?:\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F48B}\x{200D}\x{1F468})|[\x{1F468}\x{1F469}](?:\x{1F3FF}\x{200D}\x{1F9B3})|[\x{1F468}\x{1F469}](?:\x{1F3FE}\x{200D}\x{1F9B3})|[\x{1F468}\x{1F469}](?:\x{1F3FD}\x{200D}\x{1F9B3})|[\x{1F468}\x{1F469}](?:\x{1F3FC}\x{200D}\x{1F9B3})|[\x{1F468}\x{1F469}](?:\x{1F3FB}\x{200D}\x{1F9B3})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F9B3})|[\x{1F468}\x{1F469}](?:\x{1F3FF}\x{200D}\x{1F9B2})|[\x{1F468}\x{1F469}](?:\x{1F3FE}\x{200D}\x{1F9B2})|[\x{1F468}\x{1F469}](?:\x{1F3FD}\x{200D}\x{1F9B2})|[\x{1F468}\x{1F469}](?:\x{1F3FC}\x{200D}\x{1F9B2})|[\x{1F468}\x{1F469}](?:\x{1F3FB}\x{200D}\x{1F9B2})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F9B2})|[\x{1F468}\x{1F469}](?:\x{1F3FF}\x{200D}\x{1F9B1})|[\x{1F468}\x{1F469}](?:\x{1F3FE}\x{200D}\x{1F9B1})|[\x{1F468}\x{1F469}](?:\x{1F3FD}\x{200D}\x{1F9B1})|[\x{1F468}\x{1F469}](?:\x{1F3FC}\x{200D}\x{1F9B1})|[\x{1F468}\x{1F469}](?:\x{1F3FB}\x{200D}\x{1F9B1})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F9B1})|[\x{1F468}\x{1F469}](?:\x{1F3FF}\x{200D}\x{1F9B0})|[\x{1F468}\x{1F469}](?:\x{1F3FE}\x{200D}\x{1F9B0})|[\x{1F468}\x{1F469}](?:\x{1F3FD}\x{200D}\x{1F9B0})|[\x{1F468}\x{1F469}](?:\x{1F3FC}\x{200D}\x{1F9B0})|[\x{1F468}\x{1F469}](?:\x{1F3FB}\x{200D}\x{1F9B0})|[\x{1F468}\x{1F469}](?:\x{200D}\x{1F9B0})|[\x{1F575}\x{1F3CC}\x{26F9}\x{1F3CB}](?:\x{FE0F}\x{200D}\x{2640}\x{FE0F})|[\x{1F575}\x{1F3CC}\x{26F9}\x{1F3CB}](?:\x{FE0F}\x{200D}\x{2642}\x{FE0F})|[\x{1F46E}\x{1F575}\x{1F482}\x{1F477}\x{1F473}\x{1F471}\x{1F9D9}\x{1F9DA}\x{1F9DB}\x{1F9DC}\x{1F9DD}\x{1F64D}\x{1F64E}\x{1F645}\x{1F646}\x{1F481}\x{1F64B}\x{1F647}\x{1F926}\x{1F937}\x{1F486}\x{1F487}\x{1F6B6}\x{1F3C3}\x{1F9D6}\x{1F9D7}\x{1F9D8}\x{1F3CC}\x{1F3C4}\x{1F6A3}\x{1F3CA}\x{26F9}\x{1F3CB}\x{1F6B4}\x{1F6B5}\x{1F938}\x{1F93D}\x{1F93E}\x{1F939}](?:\x{1F3FF}\x{200D}\x{2640}\x{FE0F})|[\x{1F46E}\x{1F575}\x{1F482}\x{1F477}\x{1F473}\x{1F471}\x{1F9D9}\x{1F9DA}\x{1F9DB}\x{1F9DC}\x{1F9DD}\x{1F64D}\x{1F64E}\x{1F645}\x{1F646}\x{1F481}\x{1F64B}\x{1F647}\x{1F926}\x{1F937}\x{1F486}\x{1F487}\x{1F6B6}\x{1F3C3}\x{1F9D6}\x{1F9D7}\x{1F9D8}\x{1F3CC}\x{1F3C4}\x{1F6A3}\x{1F3CA}\x{26F9}\x{1F3CB}\x{1F6B4}\x{1F6B5}\x{1F938}\x{1F93D}\x{1F93E}\x{1F939}](?:\x{1F3FE}\x{200D}\x{2640}\x{FE0F})|[\x{1F46E}\x{1F575}\x{1F482}\x{1F477}\x{1F473}\x{1F471}\x{1F9D9}\x{1F9DA}\x{1F9DB}\x{1F9DC}\x{1F9DD}\x{1F64D}\x{1F64E}\x{1F645}\x{1F646}\x{1F481}\x{1F64B}\x{1F647}\x{1F926}\x{1F937}\x{1F486}\x{1F487}\x{1F6B6}\x{1F3C3}\x{1F9D6}\x{1F9D7}\x{1F9D8}\x{1F3CC}\x{1F3C4}\x{1F6A3}\x{1F3CA}\x{26F9}\x{1F3CB
|
|||
|
|
rxExtraSpace = regexp.MustCompile("\\s{2,}")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// IsEmpty 判断字符串是否为空
|
|||
|
|
func IsEmpty(s string) bool {
|
|||
|
|
return len(s) == 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func IsBlank(s string) bool {
|
|||
|
|
return s == "" || strings.TrimSpace(s) == ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ToNumber 字符串转换为唯一数字
|
|||
|
|
// https://stackoverflow.com/questions/5459436/how-can-i-generate-a-unique-int-from-a-unique-string
|
|||
|
|
func ToNumber(s string) int {
|
|||
|
|
number := 0
|
|||
|
|
runes := []rune(s)
|
|||
|
|
for i, r := range runes {
|
|||
|
|
x := 0
|
|||
|
|
if i != 0 {
|
|||
|
|
x = int(runes[i-1])
|
|||
|
|
}
|
|||
|
|
number += ((x << 16) | (x >> 16)) ^ int(r)
|
|||
|
|
}
|
|||
|
|
return number
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ContainsChinese(str string) bool {
|
|||
|
|
if str == "" {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, v := range str {
|
|||
|
|
if unicode.Is(unicode.Han, v) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ToNarrow Full width string to half width
|
|||
|
|
func ToNarrow(str string) string {
|
|||
|
|
return width.Narrow.String(str)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ToWiden Half with string to full width
|
|||
|
|
func ToWiden(str string) string {
|
|||
|
|
return width.Widen.String(str)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Split split word by special seps, use empty string if seps is empty
|
|||
|
|
func Split(str string, separators ...string) []string {
|
|||
|
|
if len(separators) == 0 {
|
|||
|
|
return []string{str}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
texts := make([]string, 0)
|
|||
|
|
if str != "" {
|
|||
|
|
n := len(separators)
|
|||
|
|
parts := strings.Split(str, separators[0])
|
|||
|
|
if n == 1 {
|
|||
|
|
texts = parts
|
|||
|
|
} else {
|
|||
|
|
n -= 2
|
|||
|
|
for i, sep := range separators[1:] {
|
|||
|
|
m := len(parts)
|
|||
|
|
for _, part := range parts {
|
|||
|
|
for _, s := range strings.Split(part, sep) {
|
|||
|
|
s = strings.TrimSpace(s)
|
|||
|
|
if s == "" {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if n == i {
|
|||
|
|
texts = append(texts, s)
|
|||
|
|
} else {
|
|||
|
|
parts = append(parts, s)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
parts = append(parts[:0], parts[m:]...)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return texts
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func String(value interface{}) string {
|
|||
|
|
switch val := value.(type) {
|
|||
|
|
case []byte:
|
|||
|
|
return string(val)
|
|||
|
|
case string:
|
|||
|
|
return val
|
|||
|
|
}
|
|||
|
|
v := reflect.ValueOf(value)
|
|||
|
|
switch v.Kind() {
|
|||
|
|
case reflect.Invalid:
|
|||
|
|
return ""
|
|||
|
|
case reflect.Bool:
|
|||
|
|
return strconv.FormatBool(v.Bool())
|
|||
|
|
case reflect.String:
|
|||
|
|
return v.String()
|
|||
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|||
|
|
return strconv.FormatInt(v.Int(), 10)
|
|||
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|||
|
|
return strconv.FormatUint(v.Uint(), 10)
|
|||
|
|
case reflect.Float64:
|
|||
|
|
return strconv.FormatFloat(v.Float(), 'f', -1, 64)
|
|||
|
|
case reflect.Float32:
|
|||
|
|
return strconv.FormatFloat(v.Float(), 'f', -1, 32)
|
|||
|
|
case reflect.Ptr, reflect.Struct, reflect.Map, reflect.Slice, reflect.Array:
|
|||
|
|
if b, err := json.Marshal(v.Interface()); err == nil {
|
|||
|
|
return string(b)
|
|||
|
|
} else {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
default:
|
|||
|
|
return fmt.Sprintf("%v", value)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RemoveEmoji 清理 Emoji 表情
|
|||
|
|
func RemoveEmoji(str string, trim bool) string {
|
|||
|
|
if trim {
|
|||
|
|
str = strings.TrimSpace(str)
|
|||
|
|
}
|
|||
|
|
if str != "" {
|
|||
|
|
str = rxEmoji.ReplaceAllString(str, "")
|
|||
|
|
}
|
|||
|
|
return str
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TrimAny 移除头部和尾部指定的内容
|
|||
|
|
func TrimAny(s string, sets ...string) string {
|
|||
|
|
if s == "" || len(sets) == 0 {
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
trimSpace := false
|
|||
|
|
fixedSets := make([]string, 0)
|
|||
|
|
for _, set := range sets {
|
|||
|
|
n := 0
|
|||
|
|
for _, r := range set {
|
|||
|
|
if unicode.IsSpace(r) {
|
|||
|
|
n++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if n == len(set) {
|
|||
|
|
trimSpace = true
|
|||
|
|
} else {
|
|||
|
|
fixedSets = append(fixedSets, set) // 不包括空白字符
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if trimSpace {
|
|||
|
|
s = strings.TrimSpace(s)
|
|||
|
|
if s == "" {
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if len(fixedSets) == 0 {
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 降序
|
|||
|
|
sort.Slice(fixedSets, func(i, j int) bool {
|
|||
|
|
return len(fixedSets[i]) > len(fixedSets[j])
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
minL := len(fixedSets[0])
|
|||
|
|
for i := range fixedSets {
|
|||
|
|
n := len(fixedSets[i])
|
|||
|
|
if n < minL {
|
|||
|
|
minL = n
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
index := -1
|
|||
|
|
for i, set := range fixedSets {
|
|||
|
|
if len(set) == minL {
|
|||
|
|
index = i
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
values := [][]string{fixedSets[0:index], slicex.StringSliceReverse(fixedSets)}
|
|||
|
|
for _, value := range values {
|
|||
|
|
for {
|
|||
|
|
hitCounts := 0
|
|||
|
|
for _, set := range value {
|
|||
|
|
setLen := len(set)
|
|||
|
|
ss := []string{set}
|
|||
|
|
start := StartsWith(s, ss, false)
|
|||
|
|
if start {
|
|||
|
|
s = s[setLen:]
|
|||
|
|
if trimSpace {
|
|||
|
|
s = strings.TrimSpace(s)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
end := EndsWith(s, ss, false)
|
|||
|
|
if end {
|
|||
|
|
if len(s) > setLen {
|
|||
|
|
s = s[0 : len(s)-setLen]
|
|||
|
|
}
|
|||
|
|
if trimSpace {
|
|||
|
|
s = strings.TrimSpace(s)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if s == "" {
|
|||
|
|
hitCounts = 0
|
|||
|
|
break
|
|||
|
|
} else if start || end {
|
|||
|
|
hitCounts++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if hitCounts == 0 {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RemoveExtraSpace 移除多余的空格
|
|||
|
|
func RemoveExtraSpace(s string) string {
|
|||
|
|
if s != "" {
|
|||
|
|
s = strings.TrimSpace(s)
|
|||
|
|
}
|
|||
|
|
if s == "" {
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
return rxExtraSpace.ReplaceAllLiteralString(strings.Replace(s, " ", " ", -1), " ")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ToBytes(s string) []byte {
|
|||
|
|
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
|||
|
|
bh := reflect.SliceHeader{
|
|||
|
|
Data: sh.Data,
|
|||
|
|
Len: sh.Len,
|
|||
|
|
Cap: sh.Len,
|
|||
|
|
}
|
|||
|
|
return *(*[]byte)(unsafe.Pointer(&bh))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// WordMatched 判断 s 中是否包含指定的 words 单词
|
|||
|
|
// 对于英文必须是单词形式,比如 he 不能匹配 hello world,只能匹配 hello|world
|
|||
|
|
// 如果是中文的话,则会使用 Index 进行判断,不等于 -1 则判断为匹配
|
|||
|
|
func WordMatched(s string, words []string, caseSensitive bool) bool {
|
|||
|
|
if s == "" || len(words) == 0 {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var b strings.Builder
|
|||
|
|
if !caseSensitive {
|
|||
|
|
b.WriteString("(?i)")
|
|||
|
|
}
|
|||
|
|
b.WriteString(`(^|([\s\t\n]+))(`)
|
|||
|
|
replacer := strings.NewReplacer(
|
|||
|
|
"\\\\", "\\\\\\",
|
|||
|
|
"(", "\\(",
|
|||
|
|
"|", "\\|",
|
|||
|
|
")", "\\)",
|
|||
|
|
"^", "\\^",
|
|||
|
|
"$", "\\$",
|
|||
|
|
".", "\\.",
|
|||
|
|
"[", "\\[",
|
|||
|
|
"*", "\\*",
|
|||
|
|
"+", "\\+",
|
|||
|
|
"?", "\\?",
|
|||
|
|
"{", "\\{",
|
|||
|
|
"-", "\\-",
|
|||
|
|
",", "\\,",
|
|||
|
|
)
|
|||
|
|
for i, word := range words {
|
|||
|
|
n := 0
|
|||
|
|
word = strings.TrimSpace(word)
|
|||
|
|
for _, w := range word {
|
|||
|
|
if !syntax.IsWordChar(w) {
|
|||
|
|
n++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if n == len(word) {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if i != 0 {
|
|||
|
|
b.WriteString("|")
|
|||
|
|
}
|
|||
|
|
b.WriteString(replacer.Replace(word))
|
|||
|
|
}
|
|||
|
|
b.WriteString(`)($|([\s\t\n]+))`)
|
|||
|
|
if re, err := regexp.Compile(b.String()); err == nil {
|
|||
|
|
if re.MatchString(s) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 正则匹配失败后还需要判断是否为中文,为中文的话则直接使用 Index 函数进行判断是否存在
|
|||
|
|
if !caseSensitive {
|
|||
|
|
s = strings.ToLower(s)
|
|||
|
|
}
|
|||
|
|
for _, word := range words {
|
|||
|
|
if ContainsChinese(word) {
|
|||
|
|
if !caseSensitive {
|
|||
|
|
word = strings.ToLower(word)
|
|||
|
|
}
|
|||
|
|
if strings.Index(s, word) != -1 {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func StartsWith(s string, ss []string, caseSensitive bool) bool {
|
|||
|
|
n := len(s)
|
|||
|
|
if ss == nil || n == 0 {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
has := false
|
|||
|
|
for _, prefix := range ss {
|
|||
|
|
m := len(prefix)
|
|||
|
|
if m == 0 {
|
|||
|
|
has = true
|
|||
|
|
} else {
|
|||
|
|
if m <= n {
|
|||
|
|
if caseSensitive {
|
|||
|
|
has = strings.HasPrefix(s, prefix)
|
|||
|
|
} else {
|
|||
|
|
ns := s
|
|||
|
|
if n-m > 0 {
|
|||
|
|
ns = s[0:m]
|
|||
|
|
}
|
|||
|
|
has = strings.EqualFold(ns, prefix)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if has {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return has
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func EndsWith(s string, ss []string, caseSensitive bool) bool {
|
|||
|
|
n := len(s)
|
|||
|
|
if ss == nil || n == 0 {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
has := false
|
|||
|
|
for _, suffix := range ss {
|
|||
|
|
m := len(suffix)
|
|||
|
|
if m == 0 {
|
|||
|
|
has = true
|
|||
|
|
} else {
|
|||
|
|
if m <= n {
|
|||
|
|
if caseSensitive {
|
|||
|
|
has = strings.HasSuffix(s, suffix)
|
|||
|
|
} else {
|
|||
|
|
ns := s
|
|||
|
|
if n-m > 0 {
|
|||
|
|
ns = s[n-m:]
|
|||
|
|
}
|
|||
|
|
has = strings.EqualFold(ns, suffix)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if has {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return has
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func Contains(s string, ss []string, caseSensitive bool) bool {
|
|||
|
|
if len(ss) == 0 {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !caseSensitive {
|
|||
|
|
s = strings.ToLower(s)
|
|||
|
|
}
|
|||
|
|
for _, substr := range ss {
|
|||
|
|
if substr == "" {
|
|||
|
|
return true
|
|||
|
|
} else {
|
|||
|
|
if !caseSensitive {
|
|||
|
|
substr = strings.ToLower(substr)
|
|||
|
|
}
|
|||
|
|
if strings.Contains(s, substr) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func QuoteMeta(s string) string {
|
|||
|
|
if s == "" {
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var buf bytes.Buffer
|
|||
|
|
buf.Grow(len(s))
|
|||
|
|
for _, char := range s {
|
|||
|
|
switch char {
|
|||
|
|
case '.', '+', '\\', '(', ')', '[', ']', '$', '^', '*', '?':
|
|||
|
|
buf.WriteRune('\\')
|
|||
|
|
}
|
|||
|
|
buf.WriteRune(char)
|
|||
|
|
}
|
|||
|
|
return buf.String()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HexToByte 16进制字符串转 []byte
|
|||
|
|
// Like pack("H*", string) in PHP
|
|||
|
|
func HexToByte(hex string) []byte {
|
|||
|
|
length := len(hex) / 2
|
|||
|
|
bytes := make([]byte, length)
|
|||
|
|
rs := []rune(hex)
|
|||
|
|
|
|||
|
|
for i := 0; i < length; i++ {
|
|||
|
|
s := string(rs[i*2 : i*2+2])
|
|||
|
|
value, _ := strconv.ParseInt(s, 16, 10)
|
|||
|
|
bytes[i] = byte(value & 0xFF)
|
|||
|
|
}
|
|||
|
|
return bytes
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SequentialWordFields
|
|||
|
|
// 将 s 字符串根据指定的分隔符分隔为 1 - n 个连续单词组合的词组
|
|||
|
|
// 单词前后的非字母和数字将会移除掉(比如 help? 将会变成 help),单词中间的则不会处理(a?b 包含在单词中间的 ? 不会处理)
|
|||
|
|
//
|
|||
|
|
// stringx.SequentialWordFields("this is a string, are you sure?", 1, ",") => ["this", "is", "a", "string", "are", "you", "sure"]
|
|||
|
|
// stringx.SequentialWordFields("this is a string, are you sure?", 2, ",") => ["this", "is", "a", "string", "are", "you", "sure", "this is", "is a", "a string", "are you", "you sure"]
|
|||
|
|
func SequentialWordFields(s string, n int, separators ...string) []string {
|
|||
|
|
s = strings.TrimSpace(s)
|
|||
|
|
if s == "" {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fields := make(map[string]string, 0)
|
|||
|
|
sections := Split(s, separators...)
|
|||
|
|
fnCleanWord := func(word string) string {
|
|||
|
|
if word == "" {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
return strings.TrimFunc(word, func(r rune) bool {
|
|||
|
|
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
sb := strings.Builder{}
|
|||
|
|
for _, section := range sections {
|
|||
|
|
words := strings.Fields(section)
|
|||
|
|
maxN := len(words)
|
|||
|
|
validN := n
|
|||
|
|
if n > maxN {
|
|||
|
|
validN = maxN
|
|||
|
|
}
|
|||
|
|
for i, word := range words {
|
|||
|
|
word = fnCleanWord(word)
|
|||
|
|
if word == "" {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
fieldKey := strings.ToLower(word)
|
|||
|
|
if _, ok := fields[fieldKey]; !ok {
|
|||
|
|
fields[fieldKey] = word
|
|||
|
|
}
|
|||
|
|
if n > 1 {
|
|||
|
|
validN = i + n
|
|||
|
|
if validN > maxN {
|
|||
|
|
validN = maxN
|
|||
|
|
}
|
|||
|
|
for jj := validN; jj > i; jj-- {
|
|||
|
|
sb.Reset()
|
|||
|
|
for ii := i; ii < jj; ii++ {
|
|||
|
|
w := fnCleanWord(words[ii])
|
|||
|
|
if w != "" {
|
|||
|
|
sb.WriteString(w)
|
|||
|
|
if ii < jj-1 {
|
|||
|
|
sb.WriteRune(' ')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if sb.Len() > 0 {
|
|||
|
|
fieldKey = strings.ToLower(sb.String())
|
|||
|
|
if _, ok := fields[fieldKey]; !ok {
|
|||
|
|
fields[fieldKey] = sb.String()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if len(fields) == 0 {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
items := make([]string, len(fields))
|
|||
|
|
i := 0
|
|||
|
|
for _, v := range fields {
|
|||
|
|
items[i] = v
|
|||
|
|
i++
|
|||
|
|
}
|
|||
|
|
return items
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Len 获取字符串长度(一个中文算 1)
|
|||
|
|
func Len(s string) int {
|
|||
|
|
return len([]rune(s))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// UpperFirst 首字母大写
|
|||
|
|
func UpperFirst(s string) string {
|
|||
|
|
r := []rune(s)
|
|||
|
|
if len(s) > 0 && unicode.IsLetter(r[0]) && unicode.IsLower(r[0]) {
|
|||
|
|
r[0] -= 32
|
|||
|
|
return string(r)
|
|||
|
|
}
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// LowerFirst 首字母小写
|
|||
|
|
func LowerFirst(s string) string {
|
|||
|
|
r := []rune(s)
|
|||
|
|
if len(s) > 0 && unicode.IsLetter(r[0]) && unicode.IsUpper(r[0]) {
|
|||
|
|
r[0] += 32
|
|||
|
|
return string(r)
|
|||
|
|
}
|
|||
|
|
return s
|
|||
|
|
}
|