github.com/gogf/gf@v1.16.9/text/gstr/gstr.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 // Package gstr provides functions for string handling. 8 package gstr 9 10 import ( 11 "bytes" 12 "fmt" 13 "math" 14 "strconv" 15 "strings" 16 "unicode" 17 "unicode/utf8" 18 19 "github.com/gogf/gf/internal/utils" 20 21 "github.com/gogf/gf/util/gconv" 22 23 "github.com/gogf/gf/util/grand" 24 ) 25 26 const ( 27 // NotFoundIndex is the position index for string not found in searching functions. 28 NotFoundIndex = -1 29 ) 30 31 const ( 32 defaultSuffixForStrLimit = "..." 33 ) 34 35 // Count counts the number of `substr` appears in `s`. 36 // It returns 0 if no `substr` found in `s`. 37 func Count(s, substr string) int { 38 return strings.Count(s, substr) 39 } 40 41 // CountI counts the number of `substr` appears in `s`, case-insensitively. 42 // It returns 0 if no `substr` found in `s`. 43 func CountI(s, substr string) int { 44 return strings.Count(ToLower(s), ToLower(substr)) 45 } 46 47 // ToLower returns a copy of the string s with all Unicode letters mapped to their lower case. 48 func ToLower(s string) string { 49 return strings.ToLower(s) 50 } 51 52 // ToUpper returns a copy of the string s with all Unicode letters mapped to their upper case. 53 func ToUpper(s string) string { 54 return strings.ToUpper(s) 55 } 56 57 // UcFirst returns a copy of the string s with the first letter mapped to its upper case. 58 func UcFirst(s string) string { 59 return utils.UcFirst(s) 60 } 61 62 // LcFirst returns a copy of the string s with the first letter mapped to its lower case. 63 func LcFirst(s string) string { 64 if len(s) == 0 { 65 return s 66 } 67 if IsLetterUpper(s[0]) { 68 return string(s[0]+32) + s[1:] 69 } 70 return s 71 } 72 73 // UcWords uppercase the first character of each word in a string. 74 func UcWords(str string) string { 75 return strings.Title(str) 76 } 77 78 // IsLetterLower tests whether the given byte b is in lower case. 79 func IsLetterLower(b byte) bool { 80 return utils.IsLetterLower(b) 81 } 82 83 // IsLetterUpper tests whether the given byte b is in upper case. 84 func IsLetterUpper(b byte) bool { 85 return utils.IsLetterUpper(b) 86 } 87 88 // IsNumeric tests whether the given string s is numeric. 89 func IsNumeric(s string) bool { 90 return utils.IsNumeric(s) 91 } 92 93 // Reverse returns a string which is the reverse of `str`. 94 func Reverse(str string) string { 95 runes := []rune(str) 96 for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { 97 runes[i], runes[j] = runes[j], runes[i] 98 } 99 return string(runes) 100 } 101 102 // NumberFormat formats a number with grouped thousands. 103 // `decimals`: Sets the number of decimal points. 104 // `decPoint`: Sets the separator for the decimal point. 105 // `thousandsSep`: Sets the thousands' separator. 106 // See http://php.net/manual/en/function.number-format.php. 107 func NumberFormat(number float64, decimals int, decPoint, thousandsSep string) string { 108 neg := false 109 if number < 0 { 110 number = -number 111 neg = true 112 } 113 // Will round off 114 str := fmt.Sprintf("%."+strconv.Itoa(decimals)+"F", number) 115 prefix, suffix := "", "" 116 if decimals > 0 { 117 prefix = str[:len(str)-(decimals+1)] 118 suffix = str[len(str)-decimals:] 119 } else { 120 prefix = str 121 } 122 sep := []byte(thousandsSep) 123 n, l1, l2 := 0, len(prefix), len(sep) 124 // thousands sep num 125 c := (l1 - 1) / 3 126 tmp := make([]byte, l2*c+l1) 127 pos := len(tmp) - 1 128 for i := l1 - 1; i >= 0; i, n, pos = i-1, n+1, pos-1 { 129 if l2 > 0 && n > 0 && n%3 == 0 { 130 for j := range sep { 131 tmp[pos] = sep[l2-j-1] 132 pos-- 133 } 134 } 135 tmp[pos] = prefix[i] 136 } 137 s := string(tmp) 138 if decimals > 0 { 139 s += decPoint + suffix 140 } 141 if neg { 142 s = "-" + s 143 } 144 145 return s 146 } 147 148 // ChunkSplit splits a string into smaller chunks. 149 // Can be used to split a string into smaller chunks which is useful for 150 // e.g. converting BASE64 string output to match RFC 2045 semantics. 151 // It inserts end every chunkLen characters. 152 // It considers parameter `body` and `end` as unicode string. 153 func ChunkSplit(body string, chunkLen int, end string) string { 154 if end == "" { 155 end = "\r\n" 156 } 157 runes, endRunes := []rune(body), []rune(end) 158 l := len(runes) 159 if l <= 1 || l < chunkLen { 160 return body + end 161 } 162 ns := make([]rune, 0, len(runes)+len(endRunes)) 163 for i := 0; i < l; i += chunkLen { 164 if i+chunkLen > l { 165 ns = append(ns, runes[i:]...) 166 } else { 167 ns = append(ns, runes[i:i+chunkLen]...) 168 } 169 ns = append(ns, endRunes...) 170 } 171 return string(ns) 172 } 173 174 // Compare returns an integer comparing two strings lexicographically. 175 // The result will be 0 if a==b, -1 if a < b, and +1 if a > b. 176 func Compare(a, b string) int { 177 return strings.Compare(a, b) 178 } 179 180 // Equal reports whether `a` and `b`, interpreted as UTF-8 strings, 181 // are equal under Unicode case-folding, case-insensitively. 182 func Equal(a, b string) bool { 183 return strings.EqualFold(a, b) 184 } 185 186 // Fields returns the words used in a string as slice. 187 func Fields(str string) []string { 188 return strings.Fields(str) 189 } 190 191 // HasPrefix tests whether the string s begins with prefix. 192 func HasPrefix(s, prefix string) bool { 193 return strings.HasPrefix(s, prefix) 194 } 195 196 // HasSuffix tests whether the string s ends with suffix. 197 func HasSuffix(s, suffix string) bool { 198 return strings.HasSuffix(s, suffix) 199 } 200 201 // CountWords returns information about words' count used in a string. 202 // It considers parameter `str` as unicode string. 203 func CountWords(str string) map[string]int { 204 m := make(map[string]int) 205 buffer := bytes.NewBuffer(nil) 206 for _, r := range []rune(str) { 207 if unicode.IsSpace(r) { 208 if buffer.Len() > 0 { 209 m[buffer.String()]++ 210 buffer.Reset() 211 } 212 } else { 213 buffer.WriteRune(r) 214 } 215 } 216 if buffer.Len() > 0 { 217 m[buffer.String()]++ 218 } 219 return m 220 } 221 222 // CountChars returns information about chars' count used in a string. 223 // It considers parameter `str` as unicode string. 224 func CountChars(str string, noSpace ...bool) map[string]int { 225 m := make(map[string]int) 226 countSpace := true 227 if len(noSpace) > 0 && noSpace[0] { 228 countSpace = false 229 } 230 for _, r := range []rune(str) { 231 if !countSpace && unicode.IsSpace(r) { 232 continue 233 } 234 m[string(r)]++ 235 } 236 return m 237 } 238 239 // WordWrap wraps a string to a given number of characters. 240 // TODO: Enable cut parameter, see http://php.net/manual/en/function.wordwrap.php. 241 func WordWrap(str string, width int, br string) string { 242 if br == "" { 243 br = "\n" 244 } 245 var ( 246 current int 247 wordBuf, spaceBuf bytes.Buffer 248 init = make([]byte, 0, len(str)) 249 buf = bytes.NewBuffer(init) 250 ) 251 for _, char := range []rune(str) { 252 if char == '\n' { 253 if wordBuf.Len() == 0 { 254 if current+spaceBuf.Len() > width { 255 current = 0 256 } else { 257 current += spaceBuf.Len() 258 spaceBuf.WriteTo(buf) 259 } 260 spaceBuf.Reset() 261 } else { 262 current += spaceBuf.Len() + wordBuf.Len() 263 spaceBuf.WriteTo(buf) 264 spaceBuf.Reset() 265 wordBuf.WriteTo(buf) 266 wordBuf.Reset() 267 } 268 buf.WriteRune(char) 269 current = 0 270 } else if unicode.IsSpace(char) { 271 if spaceBuf.Len() == 0 || wordBuf.Len() > 0 { 272 current += spaceBuf.Len() + wordBuf.Len() 273 spaceBuf.WriteTo(buf) 274 spaceBuf.Reset() 275 wordBuf.WriteTo(buf) 276 wordBuf.Reset() 277 } 278 spaceBuf.WriteRune(char) 279 } else { 280 wordBuf.WriteRune(char) 281 if current+spaceBuf.Len()+wordBuf.Len() > width && wordBuf.Len() < width { 282 buf.WriteString(br) 283 current = 0 284 spaceBuf.Reset() 285 } 286 } 287 } 288 289 if wordBuf.Len() == 0 { 290 if current+spaceBuf.Len() <= width { 291 spaceBuf.WriteTo(buf) 292 } 293 } else { 294 spaceBuf.WriteTo(buf) 295 wordBuf.WriteTo(buf) 296 } 297 return buf.String() 298 } 299 300 // RuneLen returns string length of unicode. 301 // Deprecated, use LenRune instead. 302 func RuneLen(str string) int { 303 return LenRune(str) 304 } 305 306 // LenRune returns string length of unicode. 307 func LenRune(str string) int { 308 return utf8.RuneCountInString(str) 309 } 310 311 // Repeat returns a new string consisting of multiplier copies of the string input. 312 func Repeat(input string, multiplier int) string { 313 return strings.Repeat(input, multiplier) 314 } 315 316 // Shuffle randomly shuffles a string. 317 // It considers parameter `str` as unicode string. 318 func Shuffle(str string) string { 319 runes := []rune(str) 320 s := make([]rune, len(runes)) 321 for i, v := range grand.Perm(len(runes)) { 322 s[i] = runes[v] 323 } 324 return string(s) 325 } 326 327 // Split splits string `str` by a string `delimiter`, to an array. 328 func Split(str, delimiter string) []string { 329 return strings.Split(str, delimiter) 330 } 331 332 // SplitAndTrim splits string `str` by a string `delimiter` to an array, 333 // and calls Trim to every element of this array. It ignores the elements 334 // which are empty after Trim. 335 func SplitAndTrim(str, delimiter string, characterMask ...string) []string { 336 array := make([]string, 0) 337 for _, v := range strings.Split(str, delimiter) { 338 v = Trim(v, characterMask...) 339 if v != "" { 340 array = append(array, v) 341 } 342 } 343 return array 344 } 345 346 // SplitAndTrimSpace splits string `str` by a string `delimiter` to an array, 347 // and calls TrimSpace to every element of this array. 348 // Deprecated, use SplitAndTrim instead. 349 func SplitAndTrimSpace(str, delimiter string) []string { 350 array := make([]string, 0) 351 for _, v := range strings.Split(str, delimiter) { 352 v = strings.TrimSpace(v) 353 if v != "" { 354 array = append(array, v) 355 } 356 } 357 return array 358 } 359 360 // Join concatenates the elements of `array` to create a single string. The separator string 361 // `sep` is placed between elements in the resulting string. 362 func Join(array []string, sep string) string { 363 return strings.Join(array, sep) 364 } 365 366 // JoinAny concatenates the elements of `array` to create a single string. The separator string 367 // `sep` is placed between elements in the resulting string. 368 // 369 // The parameter `array` can be any type of slice, which be converted to string array. 370 func JoinAny(array interface{}, sep string) string { 371 return strings.Join(gconv.Strings(array), sep) 372 } 373 374 // Explode splits string `str` by a string `delimiter`, to an array. 375 // See http://php.net/manual/en/function.explode.php. 376 func Explode(delimiter, str string) []string { 377 return Split(str, delimiter) 378 } 379 380 // Implode joins array elements `pieces` with a string `glue`. 381 // http://php.net/manual/en/function.implode.php 382 func Implode(glue string, pieces []string) string { 383 return strings.Join(pieces, glue) 384 } 385 386 // Chr return the ascii string of a number(0-255). 387 func Chr(ascii int) string { 388 return string([]byte{byte(ascii % 256)}) 389 } 390 391 // Ord converts the first byte of a string to a value between 0 and 255. 392 func Ord(char string) int { 393 return int(char[0]) 394 } 395 396 // HideStr replaces part of the string `str` to `hide` by `percentage` from the `middle`. 397 // It considers parameter `str` as unicode string. 398 func HideStr(str string, percent int, hide string) string { 399 array := strings.Split(str, "@") 400 if len(array) > 1 { 401 str = array[0] 402 } 403 var ( 404 rs = []rune(str) 405 length = len(rs) 406 mid = math.Floor(float64(length / 2)) 407 hideLen = int(math.Floor(float64(length) * (float64(percent) / 100))) 408 start = int(mid - math.Floor(float64(hideLen)/2)) 409 hideStr = []rune("") 410 hideRune = []rune(hide) 411 ) 412 for i := 0; i < hideLen; i++ { 413 hideStr = append(hideStr, hideRune...) 414 } 415 buffer := bytes.NewBuffer(nil) 416 buffer.WriteString(string(rs[0:start])) 417 buffer.WriteString(string(hideStr)) 418 buffer.WriteString(string(rs[start+hideLen:])) 419 if len(array) > 1 { 420 buffer.WriteString("@" + array[1]) 421 } 422 return buffer.String() 423 } 424 425 // Nl2Br inserts HTML line breaks(<br>|<br />) before all newlines in a string: 426 // \n\r, \r\n, \r, \n. 427 // It considers parameter `str` as unicode string. 428 func Nl2Br(str string, isXhtml ...bool) string { 429 r, n, runes := '\r', '\n', []rune(str) 430 var br []byte 431 if len(isXhtml) > 0 && isXhtml[0] { 432 br = []byte("<br />") 433 } else { 434 br = []byte("<br>") 435 } 436 skip := false 437 length := len(runes) 438 var buf bytes.Buffer 439 for i, v := range runes { 440 if skip { 441 skip = false 442 continue 443 } 444 switch v { 445 case n, r: 446 if (i+1 < length) && (v == r && runes[i+1] == n) || (v == n && runes[i+1] == r) { 447 buf.Write(br) 448 skip = true 449 continue 450 } 451 buf.Write(br) 452 default: 453 buf.WriteRune(v) 454 } 455 } 456 return buf.String() 457 } 458 459 // AddSlashes quotes chars('"\) with slashes. 460 func AddSlashes(str string) string { 461 var buf bytes.Buffer 462 for _, char := range str { 463 switch char { 464 case '\'', '"', '\\': 465 buf.WriteRune('\\') 466 } 467 buf.WriteRune(char) 468 } 469 return buf.String() 470 } 471 472 // StripSlashes un-quotes a quoted string by AddSlashes. 473 func StripSlashes(str string) string { 474 var buf bytes.Buffer 475 l, skip := len(str), false 476 for i, char := range str { 477 if skip { 478 skip = false 479 } else if char == '\\' { 480 if i+1 < l && str[i+1] == '\\' { 481 skip = true 482 } 483 continue 484 } 485 buf.WriteRune(char) 486 } 487 return buf.String() 488 } 489 490 // QuoteMeta returns a version of str with a backslash character (\) 491 // before every character that is among: .\+*?[^]($) 492 func QuoteMeta(str string, chars ...string) string { 493 var buf bytes.Buffer 494 for _, char := range str { 495 if len(chars) > 0 { 496 for _, c := range chars[0] { 497 if c == char { 498 buf.WriteRune('\\') 499 break 500 } 501 } 502 } else { 503 switch char { 504 case '.', '+', '\\', '(', '$', ')', '[', '^', ']', '*', '?': 505 buf.WriteRune('\\') 506 } 507 } 508 buf.WriteRune(char) 509 } 510 return buf.String() 511 } 512 513 // SearchArray searches string `s` in string slice `a` case-sensitively, 514 // returns its index in `a`. 515 // If `s` is not found in `a`, it returns -1. 516 func SearchArray(a []string, s string) int { 517 for i, v := range a { 518 if s == v { 519 return i 520 } 521 } 522 return NotFoundIndex 523 } 524 525 // InArray checks whether string `s` in slice `a`. 526 func InArray(a []string, s string) bool { 527 return SearchArray(a, s) != NotFoundIndex 528 }