diff --git a/utils/README.md b/utils/README.md index bf6ce2d1..543c3dd5 100644 --- a/utils/README.md +++ b/utils/README.md @@ -56,12 +56,26 @@ const uuid = Process("utils.str.UUID"); const pinyin = Process("utils.str.Pinyin", "你好"); // Returns: "ni hao" -// With options +// With tone marks const pinyinWithTone = Process("utils.str.Pinyin", "你好", { tone: true, separator: "-", }); +// Returns: "nǐ-hǎo" + +// With tone numbers +const pinyinWithToneNumbers = Process("utils.str.Pinyin", "你好", { + tone: "number", + separator: "-", +}); // Returns: "ni3-hao3" + +// With multiple pronunciations for characters (heteronym mode) +const pinyinWithHeteronym = Process("utils.str.Pinyin", "中国", { + heteronym: true, + tone: true, +}); +// Returns: "zhōng|zhòng guó" ``` #### Convert hex to string diff --git a/utils/str/str.go b/utils/str/str.go index 3e4351ff..971b8473 100644 --- a/utils/str/str.go +++ b/utils/str/str.go @@ -44,7 +44,8 @@ func ProcessUUID(process *process.Process) interface{} { // - arg[0]: string, the Chinese characters to convert // - arg[1]: map (optional) configuration options // { -// "tone": bool, // whether to include tone marks, default: false +// "tone": bool or string, // true or "mark" for tone marks, "number" for numeric tones, false or "none" for no tones +// "heteronym": bool, // whether to return multiple pronunciations for characters, default: false // "separator": string // separator between pinyin, default: " " // } func ProcessPinyin(process *process.Process) interface{} { @@ -55,17 +56,39 @@ func ProcessPinyin(process *process.Process) interface{} { a := pinyin.NewArgs() a.Style = pinyin.Normal // default style separator := " " // default separator + useToneNumber := false // flag to track if we need to convert to tone numbers // Apply custom settings if provided if process.NumOfArgs() > 1 { confMap := process.ArgsMap(1, maps.MapStrAny{}) - // Check if tone is enabled + // Check tone style toneVal, hasTone := confMap["tone"] - if hasTone { - if tone, ok := toneVal.(bool); ok && tone { - a.Style = pinyin.Tone + // Handle different types of tone parameter + switch v := toneVal.(type) { + case bool: + // Boolean: true = mark tones, false = no tones + if v { + a.Style = pinyin.Tone + } + case string: + // String: "mark" = mark tones, "number" = numeric tones, "none" = no tones + switch v { + case "mark": + a.Style = pinyin.Tone + case "number": + a.Style = pinyin.Tone2 + useToneNumber = true + } + } + } + + // Check if heteronym is enabled + heteronymVal, hasHeteronym := confMap["heteronym"] + if hasHeteronym { + if heteronym, ok := heteronymVal.(bool); ok && heteronym { + a.Heteronym = true } } @@ -78,14 +101,47 @@ func ProcessPinyin(process *process.Process) interface{} { // Convert to Pinyin result := pinyin.Pinyin(seed, a) - // Convert 2D array to 1D array + // Fix the tone number position if needed (to handle "ha3o" -> "hao3") + if useToneNumber { + for i, pys := range result { + for j, py := range pys { + // This is a hacky fix to move the tone number to the end + // since the library puts it after the vowel + result[i][j] = fixToneNumberPosition(py) + } + } + } + + // Process the pinyin results pinyinStr := make([]string, 0, len(result)) for _, py := range result { if len(py) > 0 { - pinyinStr = append(pinyinStr, py[0]) + if a.Heteronym && len(py) > 1 { + // For heteronyms, use a pipe separator between different pronunciations + charPinyins := strings.Join(py, "|") + pinyinStr = append(pinyinStr, charPinyins) + } else { + pinyinStr = append(pinyinStr, py[0]) + } } } final := strings.Join(pinyinStr, separator) return final } + +// fixToneNumberPosition moves the tone number from after the vowel to the end of the syllable +func fixToneNumberPosition(s string) string { + // Find the position of the first digit in the string + for i, c := range s { + if c >= '0' && c <= '9' { + // If the digit is at the end already, return as is + if i == len(s)-1 { + return s + } + // Move the digit to the end + return s[:i] + s[i+1:] + string(c) + } + } + return s // No digit found, return as is +} diff --git a/utils/str_test.go b/utils/str_test.go index 94079a81..efb20673 100644 --- a/utils/str_test.go +++ b/utils/str_test.go @@ -57,14 +57,70 @@ func TestProcessPinyin(t *testing.T) { res := process.New("utils.str.Pinyin", "你好世界").Run().(string) assert.Equal(t, "ni hao shi jie", res) - // Test with tone enabled and custom separator + // Test with tone enabled (boolean true) config := map[string]interface{}{ + "tone": true, + } + res = process.New("utils.str.Pinyin", "你好世界", config).Run().(string) + assert.Equal(t, "nǐ hǎo shì jiè", res) + + // Test with tone as string "mark" + config = map[string]interface{}{ + "tone": "mark", + } + res = process.New("utils.str.Pinyin", "你好世界", config).Run().(string) + assert.Equal(t, "nǐ hǎo shì jiè", res) + + // Test with tone as string "number" + config = map[string]interface{}{ + "tone": "number", + } + res = process.New("utils.str.Pinyin", "你好世界", config).Run().(string) + assert.Equal(t, "ni3 hao3 shi4 jie4", res) + + // Test with tone enabled (boolean true) and custom separator + config = map[string]interface{}{ "tone": true, "separator": "-", } res = process.New("utils.str.Pinyin", "你好世界", config).Run().(string) assert.Equal(t, "nǐ-hǎo-shì-jiè", res) + // Test with tone "number" and custom separator + config = map[string]interface{}{ + "tone": "number", + "separator": "-", + } + res = process.New("utils.str.Pinyin", "你好世界", config).Run().(string) + assert.Equal(t, "ni3-hao3-shi4-jie4", res) + + // Test with heteronym enabled + config = map[string]interface{}{ + "heteronym": true, + } + res = process.New("utils.str.Pinyin", "中国", config).Run().(string) + assert.Contains(t, res, "zhong") + assert.Contains(t, res, "guo") + + // Test with heteronym and tone enabled + config = map[string]interface{}{ + "heteronym": true, + "tone": true, + } + res = process.New("utils.str.Pinyin", "中国", config).Run().(string) + assert.Contains(t, res, "zhōng") + assert.Contains(t, res, "guó") + + // Test with heteronym and tone number + config = map[string]interface{}{ + "heteronym": true, + "tone": "number", + } + res = process.New("utils.str.Pinyin", "中国", config).Run().(string) + assert.Contains(t, res, "zhong1") + assert.NotContains(t, res, "zho1ng") + assert.Contains(t, res, "guo2") + // Test with only custom separator config = map[string]interface{}{ "separator": "_",