├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── hiragana.go ├── init.go ├── kana.go ├── kana_test.go ├── katakana.go └── trie.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.12.x 4 | - tip 5 | 6 | before_script: 7 | - fmtRes="$(gofmt -s -l .)" bash -c '[ "$fmtRes" == "" ] || (echo -e "The following files did not pass gofmt -s:\n" "$fmtRes" && exit 1)' 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Herman Schaaf and Shawn Smith 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/gojp/kana.png?branch=master)](https://travis-ci.org/gojp/kana) [![go report card](https://goreportcard.com/badge/github.com/gojp/kana)](http://goreportcard.com/report/github.com/gojp/kana) [![GoDoc](https://godoc.org/github.com/gojp/kana?status.svg)](https://godoc.org/github.com/gojp/kana) 2 | 3 | # kana 4 | 5 | Golang library for convertiong hiragana to romaji, katakana to romaji, romaji to hiragana, and romaji to katakana. 6 | 7 | ## Installation 8 | 9 | Simply install with `go get`: 10 | 11 | go get github.com/gojp/kana 12 | 13 | ## Usage 14 | 15 | ### Convert hiragana or katakana to romaji: 16 | 17 | s := kana.KanaToRomaji("バナナ") // -> banana 18 | s = kana.KanaToRomaji("かんじ") // -> kanji 19 | 20 | ### Convert romaji to hiragana or katakana: 21 | 22 | s := kana.RomajiToHiragana("kanji") // -> かんじ 23 | s = kana.RomajiToKatakana("banana") // -> バナナ 24 | 25 | ### Tell whether strings are written with kana, kanji or latin characters: 26 | 27 | kana.IsLatin("banana") // -> true 28 | kana.IsLatin("バナナ") // -> false 29 | 30 | kana.IsKana("banana") // -> false 31 | kana.IsKana("バナナ") // -> true 32 | 33 | kana.IsKanji("banana") // -> false 34 | kana.IsKanji("減少") // -> true 35 | 36 | ### Normalize a romaji string to a standardized form (from the form given by Google Translate, for example): 37 | 38 | kana.NormalizeRomaji("Myūjikku") // -> myu-jikku 39 | kana.NormalizeRomaji("shitsuree") // -> shitsurei 40 | 41 | Please feel free to use, contribute, and enjoy! You can also see this in action at [nihongo.io](https://nihongo.io). 42 | 43 | ## 44 | 45 | - [Herman Schaaf](http://github.com/hermanschaaf) 46 | - [Shawn Smith](http://github.com/shawnps) 47 | -------------------------------------------------------------------------------- /hiragana.go: -------------------------------------------------------------------------------- 1 | package kana 2 | 3 | // HiraganaTable maps romaji to hiragana 4 | var HiraganaTable = ` a i u e o n 5 | あ い う え お ん 6 | x ぁ ぃ ぅ ぇ ぉ 7 | k か き く け こ 8 | ky きゃ きゅ きょ 9 | s さ す せ そ 10 | sh しゃ し しゅ しょ 11 | t た つ て と 12 | ts つ 13 | ch ちゃ ち ちゅ ちぇ ちょ 14 | n な に ぬ ね の 15 | ny にゃ にゅ にょ 16 | h は ひ ふ へ ほ 17 | hy ひゃ ひゅ ひょ 18 | f ふ 19 | m ま み む め も 20 | my みゃ みゅ みょ 21 | y や ゆ よ 22 | r ら り る れ ろ 23 | ry りゃ りぃ りゅ りぇ りょ 24 | w わ ゐ ゑ を 25 | g が ぎ ぐ げ ご 26 | gy ぎゃ ぎゅ ぎょ 27 | z ざ ず ぜ ぞ/ぢょ 28 | j じゃ/ぢゃ じ/ぢ じゅ/ぢゅ じょ 29 | d だ づ で ど 30 | b ば び ぶ べ ぼ 31 | by びゃ びゅ びょ 32 | p ぱ ぴ ぷ ぺ ぽ 33 | py ぴゃ ぴゅ ぴょ 34 | v ゔ` 35 | -------------------------------------------------------------------------------- /init.go: -------------------------------------------------------------------------------- 1 | package kana 2 | 3 | func init() { 4 | Initialize() 5 | } 6 | -------------------------------------------------------------------------------- /kana.go: -------------------------------------------------------------------------------- 1 | package kana 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | "unicode" 7 | "unicode/utf8" 8 | ) 9 | 10 | var ( 11 | consonants = []string{"b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "p", "r", "s", "t", "w", "z"} 12 | 13 | hiraganaRe = regexp.MustCompile(`ん([あいうえおなにぬねの])`) 14 | katakanaRe = regexp.MustCompile(`ン([アイウエオナニヌネノ])`) 15 | 16 | kanaToRomajiTrie *Trie 17 | romajiToHiraganaTrie *Trie 18 | romajiToKatakanaTrie *Trie 19 | ) 20 | 21 | // Initialize builds the Hiragana + Katakana trie. 22 | // Because there is no overlap between the hiragana and katakana sets, 23 | // they both use the same trie without conflict. Nice bonus! 24 | func Initialize() { 25 | kanaToRomajiTrie = newTrie() 26 | romajiToHiraganaTrie = newTrie() 27 | romajiToKatakanaTrie = newTrie() 28 | 29 | tables := []string{HiraganaTable, KatakanaTable} 30 | for t, table := range tables { 31 | rows := strings.Split(table, "\n") 32 | colNames := strings.Split(string(rows[0]), "\t")[1:] 33 | for _, row := range rows[1:] { 34 | cols := strings.Split(string(row), "\t") 35 | rowName := cols[0] 36 | for i, kana := range cols[1:] { 37 | value := rowName + colNames[i] 38 | kanas := strings.Split(kana, "/") 39 | for _, singleKana := range kanas { 40 | if singleKana != "" { 41 | // add to tries 42 | kanaToRomajiTrie.insert(singleKana, value) 43 | if t == 0 { 44 | romajiToHiraganaTrie.insert(value, singleKana) 45 | } else if t == 1 { 46 | romajiToKatakanaTrie.insert(value, singleKana) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | // KanaToRomaji converts a kana string to its romaji form 56 | func KanaToRomaji(kana string) (romaji string) { 57 | // unfortunate hack to deal with double n's 58 | romaji = hiraganaRe.ReplaceAllString(kana, "nn$1") 59 | romaji = katakanaRe.ReplaceAllString(romaji, "nn$1") 60 | 61 | romaji = kanaToRomajiTrie.convert(romaji) 62 | 63 | // do some post-processing for the tsu and stripe characters 64 | // maybe a bit of a hacky solution - how can we improve? 65 | // (they act more like punctuation) 66 | tsus := []string{"っ", "ッ"} 67 | for _, tsu := range tsus { 68 | if strings.Index(romaji, tsu) > -1 { 69 | for _, c := range romaji { 70 | ch := string(c) 71 | if ch == tsu { 72 | i := strings.Index(romaji, ch) 73 | runeSize := len(ch) 74 | followingLetter, _ := utf8.DecodeRuneInString(romaji[i+runeSize:]) 75 | followingLetterStr := string(followingLetter) 76 | if followingLetterStr != tsu { 77 | romaji = strings.Replace(romaji, tsu, followingLetterStr, 1) 78 | } else { 79 | romaji = strings.Replace(romaji, tsu, "", 1) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | line := "ー" 87 | for i := strings.Index(romaji, line); i > -1; i = strings.Index(romaji, line) { 88 | if i > 0 { 89 | romaji = strings.Replace(romaji, line, "-", 1) 90 | } else { 91 | romaji = strings.Replace(romaji, line, "", 1) 92 | } 93 | } 94 | return romaji 95 | } 96 | 97 | func replaceTsus(romaji string, tsu string) (result string) { 98 | result = romaji 99 | for _, consonant := range consonants { 100 | result = strings.Replace(result, consonant+consonant, tsu+consonant, -1) 101 | } 102 | return result 103 | } 104 | 105 | func replaceNs(romaji string, n string) (result string) { 106 | return strings.Replace(romaji, "nn", n, -1) 107 | } 108 | 109 | // RomajiToHiragana converts a romaji string to its hiragana form 110 | func RomajiToHiragana(romaji string) (hiragana string) { 111 | romaji = strings.Replace(romaji, "-", "ー", -1) 112 | romaji = replaceTsus(romaji, "っ") 113 | romaji = replaceNs(romaji, "ん") 114 | hiragana = romajiToHiraganaTrie.convert(romaji) 115 | return hiragana 116 | } 117 | 118 | // RomajiToKatakana converts a romaji string to its katakana form 119 | func RomajiToKatakana(romaji string) (katakana string) { 120 | romaji = strings.Replace(romaji, "-", "ー", -1) 121 | // convert double consonants to little tsus first 122 | romaji = replaceTsus(romaji, "ッ") 123 | romaji = replaceNs(romaji, "ン") 124 | katakana = romajiToKatakanaTrie.convert(romaji) 125 | return katakana 126 | } 127 | 128 | func isChar(s string, rangeTable []*unicode.RangeTable) bool { 129 | runeForm := []rune(s) 130 | for _, r := range runeForm { 131 | if !unicode.IsOneOf(rangeTable, r) { 132 | return false 133 | } 134 | } 135 | return true 136 | } 137 | 138 | // IsLatin returns true if the string contains only Latin characters 139 | func IsLatin(s string) bool { 140 | return isChar(s, []*unicode.RangeTable{unicode.Latin, unicode.ASCII_Hex_Digit, unicode.White_Space, unicode.Hyphen}) 141 | } 142 | 143 | // IsKana returns true if the string contains only kana 144 | func IsKana(s string) bool { 145 | return isChar(s, []*unicode.RangeTable{unicode.Hiragana, unicode.Katakana, unicode.Hyphen, unicode.Diacritic}) 146 | } 147 | 148 | // IsHiragana returns true if the string contains only hiragana 149 | func IsHiragana(s string) bool { 150 | return isChar(s, []*unicode.RangeTable{unicode.Hiragana, unicode.Hyphen, unicode.Diacritic}) 151 | } 152 | 153 | // IsKatakana returns true if the string contains only katakana 154 | func IsKatakana(s string) bool { 155 | return isChar(s, []*unicode.RangeTable{unicode.Katakana, unicode.Hyphen, unicode.Diacritic}) 156 | } 157 | 158 | // IsKanji return strue if the string contains only kanji 159 | func IsKanji(s string) bool { 160 | return isChar(s, []*unicode.RangeTable{unicode.Ideographic}) 161 | } 162 | 163 | func replaceAll(haystack string, needles []string, replacements []string) (replaced string) { 164 | replaced = haystack 165 | for i := range needles { 166 | replaced = strings.Replace(replaced, needles[i], replacements[i], -1) 167 | } 168 | return replaced 169 | } 170 | 171 | // NormalizeRomaji transforms romaji input to one specific standard form, 172 | // which should be as close as possible to hiragana so that 173 | // this library gives correct output when transforming to 174 | // hiragana/katakana 175 | func NormalizeRomaji(s string) (romaji string) { 176 | romaji = s 177 | romaji = strings.ToLower(romaji) 178 | romaji = replaceAll( 179 | romaji, 180 | []string{"ā", "ē", "ī", "ō", "ū", "ee", "uu"}, 181 | []string{"a-", "ei", "ii", "oo", "u-", "ei", "u-"}, 182 | ) 183 | 184 | return romaji 185 | } 186 | -------------------------------------------------------------------------------- /kana_test.go: -------------------------------------------------------------------------------- 1 | package kana 2 | 3 | import "testing" 4 | 5 | type kanaTest struct { 6 | orig, want string 7 | } 8 | 9 | var hiraganaToRomajiTests = []kanaTest{ 10 | {"ああいうえお", "aaiueo"}, 11 | {"かんじ", "kanji"}, 12 | {"ちゃう", "chau"}, 13 | {"きょうじゅ", "kyouju"}, 14 | {"な\nに ぬ ね の", "na\nni nu ne no"}, 15 | {"ばか dog", "baka dog"}, 16 | {"きった", "kitta"}, 17 | {"はんのう", "hannnou"}, 18 | {"ぜんいん", "zennin"}, 19 | {"んい", "nni"}, 20 | {"はんのう", "hannnou"}, 21 | {"はんおう", "hannou"}, 22 | {"あうでぃ", "audexi"}, 23 | {"だぢづを", "dajiduwo"}, 24 | {"ぢゃぢょぢゅ", "jazoju"}, 25 | } 26 | 27 | func TestHiraganaToRomaji(t *testing.T) { 28 | for _, tt := range hiraganaToRomajiTests { 29 | if got := KanaToRomaji(tt.orig); got != tt.want { 30 | t.Errorf("KanaToRomaji(%q) = %q, want %q", tt.orig, got, tt.want) 31 | } 32 | } 33 | } 34 | 35 | var katakanaToRomajiTests = []kanaTest{ 36 | {"バナナ", "banana"}, 37 | {"カンジ", "kanji"}, 38 | {"テレビ", "terebi"}, 39 | {"baking バナナ pancakes", "baking banana pancakes"}, 40 | {"ベッド", "beddo"}, 41 | {"モーター", "mo-ta-"}, 42 | {"CDプレーヤー", "CDpure-ya-"}, 43 | {"オーバーヘッドキック", "o-ba-heddokikku"}, 44 | {"ハンノウ", "hannnou"}, 45 | {"アウディ", "audexi"}, 46 | {"ダヂヅヲ", "dajiduwo"}, 47 | {"ヂャヂョヂュ", "jazoju"}, 48 | } 49 | 50 | func TestKatakanaToRomaji(t *testing.T) { 51 | for _, tt := range katakanaToRomajiTests { 52 | if got := KanaToRomaji(tt.orig); got != tt.want { 53 | t.Errorf("KanaToRomaji(%q) = %q, want %q", tt.orig, got, tt.want) 54 | } 55 | } 56 | } 57 | 58 | var romajiToKatakanaTests = []kanaTest{ 59 | {"banana", "バナナ"}, 60 | {"rajio", "ラジオ"}, 61 | {"terebi", "テレビ"}, 62 | {"furi-ta-", "フリーター"}, 63 | {"fa-suto", "ファースト"}, 64 | {"fesutibaru", "フェスティバル"}, 65 | {"ryukkusakku", "リュックサック"}, 66 | {"myu-jikku", "ミュージック"}, 67 | {"nyanda", "ニャンダ"}, 68 | {"hyakumeootokage", "ヒャクメオオトカゲ"}, 69 | {"CDプレーヤー", "CDプレーヤー"}, 70 | {"cheri-", "チェリー"}, 71 | {"otukare", "オツカレ"}, 72 | } 73 | 74 | func TestRomajiToKatakana(t *testing.T) { 75 | for _, tt := range romajiToKatakanaTests { 76 | if got := RomajiToKatakana(tt.orig); got != tt.want { 77 | t.Errorf("RomajiToKatakana(%q) = %q, want %q", tt.orig, got, tt.want) 78 | } 79 | } 80 | } 81 | 82 | var romajiToHiraganaTests = []kanaTest{ 83 | {"banana", "ばなな"}, 84 | {"hiragana", "ひらがな"}, 85 | {"suppai", "すっぱい"}, 86 | {"konnnichiha", "こんにちは"}, 87 | {"zouryou", "ぞうりょう"}, 88 | {"myaku", "みゃく"}, 89 | {"nyanko", "にゃんこ"}, 90 | {"hyaku", "ひゃく"}, 91 | {"motoduku", "もとづく"}, 92 | {"zenin", "ぜにん"}, 93 | {"zennin", "ぜんいん"}, 94 | {"hannnou", "はんのう"}, 95 | {"hannou", "はんおう"}, 96 | {"chuutohanpa", "ちゅうとはんぱ"}, 97 | {"CDプレーヤー", "CDプレーヤー"}, 98 | {"meccha", "めっちゃ"}, 99 | {"che", "ちぇ"}, 100 | {"otukare", "おつかれ"}, 101 | } 102 | 103 | func TestRomajiToHiragana(t *testing.T) { 104 | for _, tt := range romajiToHiraganaTests { 105 | if got := RomajiToHiragana(tt.orig); got != tt.want { 106 | t.Errorf("RomajiToHiragana(%q) = %q, want %q", tt.orig, got, tt.want) 107 | } 108 | } 109 | } 110 | 111 | type typeTest struct { 112 | text string 113 | valid bool 114 | } 115 | 116 | var isLatinTests = []typeTest{ 117 | {"banana", true}, 118 | {"a sd ds ds", true}, 119 | {"ばなな", false}, 120 | {"ファースト", false}, 121 | {"myu-jikku", true}, 122 | {"CDプレーヤー", false}, 123 | } 124 | 125 | func TestIsLatin(t *testing.T) { 126 | for _, tt := range isLatinTests { 127 | if got := IsLatin(tt.text); got != tt.valid { 128 | t.Errorf("IsLatin(%q) = %t, want %t", tt.text, got, tt.valid) 129 | } 130 | } 131 | } 132 | 133 | var isKanaTests = []typeTest{ 134 | {"ばなな", true}, 135 | {"ファースト", true}, 136 | {"test", false}, 137 | } 138 | 139 | func TestIsKana(t *testing.T) { 140 | for _, tt := range isKanaTests { 141 | if got := IsKana(tt.text); got != tt.valid { 142 | t.Errorf("IsKana(%q) = %t, want %t", tt.text, got, tt.valid) 143 | } 144 | } 145 | } 146 | 147 | var isHiraganaTests = []typeTest{ 148 | {"ばなな", true}, 149 | {"ファースト", false}, 150 | {"test", false}, 151 | } 152 | 153 | func TestIsHiragana(t *testing.T) { 154 | for _, tt := range isHiraganaTests { 155 | if got := IsHiragana(tt.text); got != tt.valid { 156 | t.Errorf("IsHiragana(%q) = %t, want %t", tt.text, got, tt.valid) 157 | } 158 | } 159 | } 160 | 161 | var isKatakanaTests = []typeTest{ 162 | {"ばなな", false}, 163 | {"ファースト", true}, 164 | {"test", false}, 165 | } 166 | 167 | func TestIsKatakana(t *testing.T) { 168 | for _, tt := range isKatakanaTests { 169 | if got := IsKatakana(tt.text); got != tt.valid { 170 | t.Errorf("IsKatakana(%q) = %t, want %t", tt.text, got, tt.valid) 171 | } 172 | } 173 | } 174 | 175 | var isKanjiTests = []typeTest{ 176 | {"ばなな", false}, 177 | {"ファースト", false}, 178 | {"test", false}, 179 | {"路加", true}, 180 | {"減少", true}, 181 | } 182 | 183 | func TestIsKanji(t *testing.T) { 184 | for _, tt := range isKanjiTests { 185 | if got := IsKanji(tt.text); got != tt.valid { 186 | t.Errorf("IsKanji(%q) = %t, want %t", tt.text, got, tt.valid) 187 | } 188 | } 189 | } 190 | 191 | var normalizeRomajiTests = []kanaTest{ 192 | {"myuujikku", "myu-jikku"}, 193 | {"Myūjikku", "myu-jikku"}, 194 | {"Banana", "banana"}, 195 | {"shitsuree", "shitsurei"}, 196 | {"減少", "減少"}, 197 | {"myuujikku Myūjikku Banana shitsuree", "myu-jikku myu-jikku banana shitsurei"}, 198 | } 199 | 200 | func TestNormalizeRomaji(t *testing.T) { 201 | for _, tt := range normalizeRomajiTests { 202 | if got := NormalizeRomaji(tt.orig); got != tt.want { 203 | t.Errorf("NormalizeRomaji(%q) = %q, want %q", tt.orig, got, tt.want) 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /katakana.go: -------------------------------------------------------------------------------- 1 | package kana 2 | 3 | // KatakanaTable maps romaji to katakana 4 | var KatakanaTable = ` a i u e o n 5 | ア イ ウ エ オ ン 6 | x ァ ィ ゥ ェ ォ 7 | k カ キ ク ケ コ 8 | ky キャ キュ キョ 9 | s サ ス セ ソ 10 | sh シャ シ シュ シェ ショ 11 | t タ ティ ツ テ ト 12 | ts ツ 13 | ch チャ チ チュ チェ チョ 14 | n ナ ニ ヌ ネ ノ 15 | ny ニャ ニュ ニョ 16 | h ハ ヒ フ ヘ ホ 17 | hy ヒャ ヒュ ヒョ 18 | f ファ フィ フ フェ フォ 19 | m マ ミ ム メ モ 20 | my ミャ ミュ ミョ 21 | y ヤ ユ ヨ 22 | r ラ リ ル レ ロ 23 | ry リャ リィ リュ リェ リョ 24 | w ワ ウィ ウェ ヲ 25 | g ガ ギ グ ゲ ゴ 26 | gy ギャ ギュ ギョ 27 | z ザ ズ ゼ ゾ/ヂョ 28 | j ジャ/ヂャ ジ/ヂ ジュ/ヂュ ジェ ジョ 29 | d ダ ヅ デ ド 30 | b バ ビ ブ ベ ボ 31 | by ビャ ビュ ビョ 32 | p パ ピ プ ペ ポ 33 | py ピャ ピュ ピョ 34 | v ゔ` 35 | -------------------------------------------------------------------------------- /trie.go: -------------------------------------------------------------------------------- 1 | package kana 2 | 3 | // Trie is a trie data structure 4 | type Trie struct { 5 | children map[string]*Trie 6 | letter string 7 | values []string 8 | } 9 | 10 | // Build a trie for efficient retrieval of entries 11 | func newTrie() *Trie { 12 | return &Trie{map[string]*Trie{}, "", []string{}} 13 | } 14 | 15 | // Insert a value into the trie 16 | func (t *Trie) insert(letters, value string) { 17 | lettersRune := []rune(letters) 18 | 19 | // loop through letters in argument word 20 | for l, letter := range lettersRune { 21 | 22 | letterStr := string(letter) 23 | 24 | // if letter in children 25 | if t.children[letterStr] != nil { 26 | t = t.children[letterStr] 27 | } else { 28 | // not found, so add letter to children 29 | t.children[letterStr] = &Trie{map[string]*Trie{}, "", []string{}} 30 | t = t.children[letterStr] 31 | } 32 | 33 | if l == len(lettersRune)-1 { 34 | // last letter, save value and exit 35 | t.values = append(t.values, value) 36 | break 37 | } 38 | } 39 | } 40 | 41 | // Convert a given string to the corresponding values 42 | // in the trie. This performed in a greedy fashion, 43 | // replacing the longest valid string it can find at any 44 | // given point. 45 | func (t *Trie) convert(origin string) (result string) { 46 | root := t 47 | originRune := []rune(origin) 48 | result = "" 49 | 50 | for l := 0; l < len(originRune); l++ { 51 | t = root 52 | foundVal := "" 53 | depth := 0 54 | for i := 0; i+l < len(originRune); i++ { 55 | letter := string(originRune[l+i]) 56 | if t.children[letter] == nil { 57 | // not found 58 | break 59 | } 60 | if len(t.children[letter].values) > 0 { 61 | foundVal = t.children[letter].values[0] 62 | depth = i 63 | } 64 | t = t.children[letter] 65 | } 66 | if foundVal != "" { 67 | result += foundVal 68 | l += depth 69 | } else { 70 | result += string(originRune[l : l+1]) 71 | } 72 | } 73 | return result 74 | } 75 | --------------------------------------------------------------------------------