pinyin.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. package gpy
  2. import (
  3. // "fmt"
  4. "regexp"
  5. "strings"
  6. "unicode"
  7. )
  8. // Meta
  9. const (
  10. version = "0.10.0.34"
  11. // License = "MIT"
  12. )
  13. // GetVersion get version
  14. func GetVersion() string {
  15. return version
  16. }
  17. // 拼音风格(推荐)
  18. const (
  19. Normal = 0 // 普通风格,不带声调(默认风格)。如: zhong guo
  20. Tone = 1 // 声调风格1,拼音声调在韵母第一个字母上。如: zhōng guó
  21. Tone2 = 2 // 声调风格2,即拼音声调在各个韵母之后,用数字 [1-4] 进行表示。如: zho1ng guo2
  22. Tone3 = 8 // 声调风格3,即拼音声调在各个拼音之后,用数字 [1-4] 进行表示。如: zhong1 guo2
  23. Initials = 3 // 声母风格,只返回各个拼音的声母部分。如: zh g
  24. FirstLetter = 4 // 首字母风格,只返回拼音的首字母部分。如: z g
  25. Finals = 5 // 韵母风格,只返回各个拼音的韵母部分,不带声调。如: ong uo
  26. FinalsTone = 6 // 韵母风格1,带声调,声调在韵母第一个字母上。如: ōng uó
  27. FinalsTone2 = 7 // 韵母风格2,带声调,声调在各个韵母之后,用数字 [1-4] 进行表示。如: o1ng uo2
  28. FinalsTone3 = 9 // 韵母风格3,带声调,声调在各个拼音之后,用数字 [1-4] 进行表示。如: ong1 uo2
  29. )
  30. // 拼音风格(兼容之前的版本)
  31. // const (
  32. // NORMAL = Normal
  33. // TONE = Tone
  34. // TONE2 = Tone2
  35. // INITIALS = Initials
  36. // FIRST_LETTER = FirstLetter
  37. // FINALS = Finals
  38. // FINALS_TONE = FinalsTone
  39. // FINALS_TONE2 = FinalsTone2
  40. // )
  41. var (
  42. // 声母表
  43. initialArray = strings.Split(
  44. "b,p,m,f,d,t,n,l,g,k,h,j,q,x,r,zh,ch,sh,z,c,s",
  45. ",",
  46. )
  47. // 所有带声调的字符
  48. rePhoneticSymbolSource = func(m map[string]string) string {
  49. s := ""
  50. for k := range m {
  51. s = s + k
  52. }
  53. return s
  54. }(phoneticSymbol)
  55. )
  56. var (
  57. // 匹配带声调字符的正则表达式
  58. rePhoneticSymbol = regexp.MustCompile("[" + rePhoneticSymbolSource + "]")
  59. // 匹配使用数字标识声调的字符的正则表达式
  60. reTone2 = regexp.MustCompile("([aeoiuvnm])([1-4])$")
  61. // 匹配 Tone2 中标识韵母声调的正则表达式
  62. reTone3 = regexp.MustCompile("^([a-z]+)([1-4])([a-z]*)$")
  63. )
  64. // Args 配置信息
  65. type Args struct {
  66. Style int // 拼音风格(默认: Normal)
  67. Heteronym bool // 是否启用多音字模式(默认:禁用)
  68. Separator string // Slug 中使用的分隔符(默认:-)
  69. // 处理没有拼音的字符(默认忽略没有拼音的字符)
  70. // 函数返回的 slice 的长度为0 则表示忽略这个字符
  71. Fallback func(r rune, a Args) []string
  72. }
  73. var (
  74. // Style 默认配置:风格
  75. Style = Normal
  76. // Heteronym 默认配置:是否启用多音字模式
  77. Heteronym = false
  78. // Separator 默认配置: `Slug` 中 Join 所用的分隔符
  79. Separator = "-"
  80. // Fallback 默认配置: 如何处理没有拼音的字符(忽略这个字符)
  81. Fallback = func(r rune, a Args) []string {
  82. return []string{}
  83. }
  84. finalExceptionsMap = map[string]string{
  85. "ū": "ǖ",
  86. "ú": "ǘ",
  87. "ǔ": "ǚ",
  88. "ù": "ǜ",
  89. }
  90. reFinalExceptions = regexp.MustCompile("^(j|q|x)(ū|ú|ǔ|ù)$")
  91. reFinal2Exceptions = regexp.MustCompile("^(j|q|x)u(\\d?)$")
  92. )
  93. // NewArgs 返回包含默认配置的 `Args`
  94. func NewArgs() Args {
  95. return Args{Style, Heteronym, Separator, Fallback}
  96. }
  97. // 获取单个拼音中的声母
  98. func initial(p string) string {
  99. s := ""
  100. for _, v := range initialArray {
  101. if strings.HasPrefix(p, v) {
  102. s = v
  103. break
  104. }
  105. }
  106. return s
  107. }
  108. // 获取单个拼音中的韵母
  109. func final(p string) string {
  110. n := initial(p)
  111. if n == "" {
  112. return handleYW(p)
  113. }
  114. // 特例 j/q/x
  115. matches := reFinalExceptions.FindStringSubmatch(p)
  116. // jū -> jǖ
  117. if len(matches) == 3 && matches[1] != "" && matches[2] != "" {
  118. v, _ := finalExceptionsMap[matches[2]]
  119. return v
  120. }
  121. // ju -> jv, ju1 -> jv1
  122. p = reFinal2Exceptions.ReplaceAllString(p, "${1}v$2")
  123. return strings.Join(strings.SplitN(p, n, 2), "")
  124. }
  125. // 处理 y, w
  126. func handleYW(p string) string {
  127. // 特例 y/w
  128. if strings.HasPrefix(p, "yu") {
  129. p = "v" + p[2:] // yu -> v
  130. } else if strings.HasPrefix(p, "yi") {
  131. p = p[1:] // yi -> i
  132. } else if strings.HasPrefix(p, "y") {
  133. p = "i" + p[1:] // y -> i
  134. } else if strings.HasPrefix(p, "wu") {
  135. p = p[1:] // wu -> u
  136. } else if strings.HasPrefix(p, "w") {
  137. p = "u" + p[1:] // w -> u
  138. }
  139. return p
  140. }
  141. func toFixed(p string, a Args) string {
  142. if a.Style == Initials {
  143. return initial(p)
  144. }
  145. origP := p
  146. // 替换拼音中的带声调字符
  147. py := rePhoneticSymbol.ReplaceAllStringFunc(p, func(m string) string {
  148. symbol, _ := phoneticSymbol[m]
  149. switch a.Style {
  150. // 不包含声调
  151. case Normal, FirstLetter, Finals:
  152. // 去掉声调: a1 -> a
  153. m = reTone2.ReplaceAllString(symbol, "$1")
  154. case Tone2, FinalsTone2, Tone3, FinalsTone3:
  155. // 返回使用数字标识声调的字符
  156. m = symbol
  157. default:
  158. // 声调在头上
  159. }
  160. return m
  161. })
  162. switch a.Style {
  163. // 将声调移动到最后
  164. case Tone3, FinalsTone3:
  165. py = reTone3.ReplaceAllString(py, "$1$3$2")
  166. }
  167. switch a.Style {
  168. // 首字母
  169. case FirstLetter:
  170. py = py[:1]
  171. // 韵母
  172. case Finals, FinalsTone, FinalsTone2, FinalsTone3:
  173. // 转换为 []rune unicode 编码用于获取第一个拼音字符
  174. // 因为 string 是 utf-8 编码不方便获取第一个拼音字符
  175. rs := []rune(origP)
  176. switch string(rs[0]) {
  177. // 因为鼻音没有声母所以不需要去掉声母部分
  178. case "ḿ", "ń", "ň", "ǹ":
  179. default:
  180. py = final(py)
  181. }
  182. }
  183. return py
  184. }
  185. func applyStyle(p []string, a Args) []string {
  186. newP := []string{}
  187. for _, v := range p {
  188. newP = append(newP, toFixed(v, a))
  189. }
  190. return newP
  191. }
  192. // SinglePinyin 把单个 `rune` 类型的汉字转换为拼音.
  193. func SinglePinyin(r rune, a Args) []string {
  194. if a.Fallback == nil {
  195. a.Fallback = Fallback
  196. }
  197. value, ok := PinyinDict[int(r)]
  198. pys := []string{}
  199. if ok {
  200. pys = strings.Split(value, ",")
  201. } else {
  202. pys = a.Fallback(r, a)
  203. }
  204. if len(pys) > 0 {
  205. if !a.Heteronym {
  206. pys = pys[:1]
  207. }
  208. return applyStyle(pys, a)
  209. }
  210. return pys
  211. }
  212. // IsChineseChar to determine whether the Chinese string
  213. // 判断是否为中文字符串
  214. func IsChineseChar(str string) bool {
  215. for _, r := range str {
  216. if unicode.Is(unicode.Scripts["Han"], r) {
  217. return true
  218. }
  219. }
  220. return false
  221. }
  222. // HanPinyin 汉字转拼音,支持多音字模式.
  223. func HanPinyin(s string, a Args) [][]string {
  224. pys := [][]string{}
  225. for _, r := range s {
  226. py := SinglePinyin(r, a)
  227. if len(py) > 0 {
  228. pys = append(pys, py)
  229. }
  230. }
  231. return pys
  232. }
  233. // Pinyin 汉字转拼音,支持多音字模式和拼音与英文等字母混合.
  234. func Pinyin(s string, a Args) [][]string {
  235. pys := [][]string{}
  236. for _, r := range s {
  237. if unicode.Is(unicode.Scripts["Han"], r) {
  238. // if r {
  239. // }
  240. py := SinglePinyin(r, a)
  241. if len(py) > 0 {
  242. pys = append(pys, py)
  243. }
  244. }
  245. // else {
  246. // py := strings.Split(s, " ")
  247. // fmt.Println(py)
  248. // }
  249. }
  250. py := strings.Split(s, " ")
  251. for i := 0; i < len(py); i++ {
  252. var (
  253. pyarr []string
  254. cs int64
  255. )
  256. for _, r := range py[i] {
  257. if unicode.Is(unicode.Scripts["Han"], r) {
  258. // continue
  259. cs++
  260. }
  261. }
  262. if cs == 0 {
  263. pyarr = append(pyarr, py[i])
  264. pys = append(pys, pyarr)
  265. }
  266. }
  267. return pys
  268. }
  269. // LazyPinyin 汉字转拼音,与 `Pinyin` 的区别是:
  270. // 返回值类型不同,并且不支持多音字模式,每个汉字只取第一个音.
  271. func LazyPinyin(s string, a Args) []string {
  272. a.Heteronym = false
  273. pys := []string{}
  274. for _, v := range Pinyin(s, a) {
  275. pys = append(pys, v[0])
  276. }
  277. return pys
  278. }
  279. // Slug join `LazyPinyin` 的返回值.
  280. // 建议改用 https://github.com/mozillazg/go-slugify
  281. func Slug(s string, a Args) string {
  282. separator := a.Separator
  283. return strings.Join(LazyPinyin(s, a), separator)
  284. }
  285. // Convert 跟 Pinyin 的唯一区别就是 a 参数可以是 nil
  286. func Convert(s string, a *Args) [][]string {
  287. if a == nil {
  288. args := NewArgs()
  289. a = &args
  290. }
  291. return Pinyin(s, *a)
  292. }
  293. // LazyConvert 跟 LazyPinyin 的唯一区别就是 a 参数可以是 nil
  294. func LazyConvert(s string, a *Args) []string {
  295. if a == nil {
  296. args := NewArgs()
  297. a = &args
  298. }
  299. return LazyPinyin(s, *a)
  300. }