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  }