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 }