gstr_case.go 6.7 KB

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