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  }