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