github.com/gogf/gf@v1.16.9/text/gstr/gstr_case.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  //   | Function                          | Result             |
     8  //   |-----------------------------------|--------------------|
     9  //   | CaseSnake(s)                      | any_kind_of_string |
    10  //   | CaseSnakeScreaming(s)             | ANY_KIND_OF_STRING |
    11  //   | CaseSnakeFirstUpper("RGBCodeMd5") | rgb_code_md5       |
    12  //   | CaseKebab(s)                      | any-kind-of-string |
    13  //   | CaseKebabScreaming(s)             | ANY-KIND-OF-STRING |
    14  //   | CaseDelimited(s, '.')             | any.kind.of.string |
    15  //   | CaseDelimitedScreaming(s, '.')    | ANY.KIND.OF.STRING |
    16  //   | CaseCamel(s)                      | AnyKindOfString    |
    17  //   | CaseCamelLower(s)                 | anyKindOfString    |
    18  
    19  package gstr
    20  
    21  import (
    22  	"regexp"
    23  	"strings"
    24  )
    25  
    26  var (
    27  	numberSequence      = regexp.MustCompile(`([a-zA-Z]{0,1})(\d+)([a-zA-Z]{0,1})`)
    28  	firstCamelCaseStart = regexp.MustCompile(`([A-Z]+)([A-Z]?[_a-z\d]+)|$`)
    29  	firstCamelCaseEnd   = regexp.MustCompile(`([\w\W]*?)([_]?[A-Z]+)$`)
    30  )
    31  
    32  // CamelCase converts a string to CamelCase.
    33  // Deprecated, use CaseCamel instead.
    34  func CamelCase(s string) string {
    35  	return CaseCamel(s)
    36  }
    37  
    38  // CaseCamel converts a string to CamelCase.
    39  func CaseCamel(s string) string {
    40  	return toCamelInitCase(s, true)
    41  }
    42  
    43  // CamelLowerCase converts a string to lowerCamelCase.
    44  // Deprecated, use CaseCamelLower instead.
    45  func CamelLowerCase(s string) string {
    46  	return CaseCamelLower(s)
    47  }
    48  
    49  // CaseCamelLower converts a string to lowerCamelCase.
    50  func CaseCamelLower(s string) string {
    51  	if s == "" {
    52  		return s
    53  	}
    54  	if r := rune(s[0]); r >= 'A' && r <= 'Z' {
    55  		s = strings.ToLower(string(r)) + s[1:]
    56  	}
    57  	return toCamelInitCase(s, false)
    58  }
    59  
    60  // SnakeCase converts a string to snake_case.
    61  // Deprecated, use CaseSnake instead.
    62  func SnakeCase(s string) string {
    63  	return CaseSnake(s)
    64  }
    65  
    66  // CaseSnake converts a string to snake_case.
    67  func CaseSnake(s string) string {
    68  	return DelimitedCase(s, '_')
    69  }
    70  
    71  // SnakeScreamingCase converts a string to SNAKE_CASE_SCREAMING.
    72  // Deprecated, use CaseSnakeScreaming instead.
    73  func SnakeScreamingCase(s string) string {
    74  	return CaseSnakeScreaming(s)
    75  }
    76  
    77  // CaseSnakeScreaming converts a string to SNAKE_CASE_SCREAMING.
    78  func CaseSnakeScreaming(s string) string {
    79  	return CaseDelimitedScreaming(s, '_', true)
    80  }
    81  
    82  // SnakeFirstUpperCase converts a string from RGBCodeMd5 to rgb_code_md5.
    83  // The length of word should not be too long
    84  // Deprecated, use CaseSnakeFirstUpper instead.
    85  func SnakeFirstUpperCase(word string, underscore ...string) string {
    86  	return CaseSnakeFirstUpper(word, underscore...)
    87  }
    88  
    89  // CaseSnakeFirstUpper converts a string like "RGBCodeMd5" to "rgb_code_md5".
    90  // TODO for efficiency should change regexp to traversing string in future.
    91  func CaseSnakeFirstUpper(word string, underscore ...string) string {
    92  	replace := "_"
    93  	if len(underscore) > 0 {
    94  		replace = underscore[0]
    95  	}
    96  
    97  	m := firstCamelCaseEnd.FindAllStringSubmatch(word, 1)
    98  	if len(m) > 0 {
    99  		word = m[0][1] + replace + TrimLeft(ToLower(m[0][2]), replace)
   100  	}
   101  
   102  	for {
   103  		m := firstCamelCaseStart.FindAllStringSubmatch(word, 1)
   104  		if len(m) > 0 && m[0][1] != "" {
   105  			w := strings.ToLower(m[0][1])
   106  			w = w[:len(w)-1] + replace + string(w[len(w)-1])
   107  
   108  			word = strings.Replace(word, m[0][1], w, 1)
   109  		} else {
   110  			break
   111  		}
   112  	}
   113  
   114  	return TrimLeft(word, replace)
   115  }
   116  
   117  // KebabCase converts a string to kebab-case.
   118  // Deprecated, use CaseKebab instead.
   119  func KebabCase(s string) string {
   120  	return CaseKebab(s)
   121  }
   122  
   123  // CaseKebab converts a string to kebab-case
   124  func CaseKebab(s string) string {
   125  	return CaseDelimited(s, '-')
   126  }
   127  
   128  // KebabScreamingCase converts a string to KEBAB-CASE-SCREAMING.
   129  // Deprecated, use CaseKebabScreaming instead.
   130  func KebabScreamingCase(s string) string {
   131  	return CaseKebabScreaming(s)
   132  }
   133  
   134  // CaseKebabScreaming converts a string to KEBAB-CASE-SCREAMING.
   135  func CaseKebabScreaming(s string) string {
   136  	return CaseDelimitedScreaming(s, '-', true)
   137  }
   138  
   139  // DelimitedCase converts a string to snake.case.delimited.
   140  // Deprecated, use CaseDelimited instead.
   141  func DelimitedCase(s string, del uint8) string {
   142  	return CaseDelimited(s, del)
   143  }
   144  
   145  // CaseDelimited converts a string to snake.case.delimited.
   146  func CaseDelimited(s string, del uint8) string {
   147  	return CaseDelimitedScreaming(s, del, false)
   148  }
   149  
   150  // DelimitedScreamingCase converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case.
   151  // Deprecated, use CaseDelimitedScreaming instead.
   152  func DelimitedScreamingCase(s string, del uint8, screaming bool) string {
   153  	return CaseDelimitedScreaming(s, del, screaming)
   154  }
   155  
   156  // CaseDelimitedScreaming converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case.
   157  func CaseDelimitedScreaming(s string, del uint8, screaming bool) string {
   158  	s = addWordBoundariesToNumbers(s)
   159  	s = strings.Trim(s, " ")
   160  	n := ""
   161  	for i, v := range s {
   162  		// treat acronyms as words, eg for JSONData -> JSON is a whole word
   163  		nextCaseIsChanged := false
   164  		if i+1 < len(s) {
   165  			next := s[i+1]
   166  			if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') {
   167  				nextCaseIsChanged = true
   168  			}
   169  		}
   170  
   171  		if i > 0 && n[len(n)-1] != del && nextCaseIsChanged {
   172  			// add underscore if next letter case type is changed
   173  			if v >= 'A' && v <= 'Z' {
   174  				n += string(del) + string(v)
   175  			} else if v >= 'a' && v <= 'z' {
   176  				n += string(v) + string(del)
   177  			}
   178  		} else if v == ' ' || v == '_' || v == '-' || v == '.' {
   179  			// replace spaces/underscores with delimiters
   180  			n += string(del)
   181  		} else {
   182  			n = n + string(v)
   183  		}
   184  	}
   185  
   186  	if screaming {
   187  		n = strings.ToUpper(n)
   188  	} else {
   189  		n = strings.ToLower(n)
   190  	}
   191  	return n
   192  }
   193  
   194  func addWordBoundariesToNumbers(s string) string {
   195  	r := numberSequence.ReplaceAllFunc([]byte(s), func(bytes []byte) []byte {
   196  		var result []byte
   197  		match := numberSequence.FindSubmatch(bytes)
   198  		if len(match[1]) > 0 {
   199  			result = append(result, match[1]...)
   200  			result = append(result, []byte(" ")...)
   201  		}
   202  		result = append(result, match[2]...)
   203  		if len(match[3]) > 0 {
   204  			result = append(result, []byte(" ")...)
   205  			result = append(result, match[3]...)
   206  		}
   207  		return result
   208  	})
   209  	return string(r)
   210  }
   211  
   212  // Converts a string to CamelCase
   213  func toCamelInitCase(s string, initCase bool) string {
   214  	s = addWordBoundariesToNumbers(s)
   215  	s = strings.Trim(s, " ")
   216  	n := ""
   217  	capNext := initCase
   218  	for _, v := range s {
   219  		if v >= 'A' && v <= 'Z' {
   220  			n += string(v)
   221  		}
   222  		if v >= '0' && v <= '9' {
   223  			n += string(v)
   224  		}
   225  		if v >= 'a' && v <= 'z' {
   226  			if capNext {
   227  				n += strings.ToUpper(string(v))
   228  			} else {
   229  				n += string(v)
   230  			}
   231  		}
   232  		if v == '_' || v == ' ' || v == '-' || v == '.' {
   233  			capNext = true
   234  		} else {
   235  			capNext = false
   236  		}
   237  	}
   238  	return n
   239  }