gimchi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,265 @@
1
+ # encoding: UTF-8
2
+
3
+ module Gimchi
4
+ class Korean
5
+ DEFAULT_CONFIG_FILE_PATH =
6
+ File.dirname(__FILE__) + '/../../config/default.yml'
7
+
8
+ attr_reader :config
9
+ attr_accessor :pronouncer
10
+
11
+ # Initialize Gimchi::Korean.
12
+ # You can override many part of the implementation with customized config file.
13
+ def initialize config_file = DEFAULT_CONFIG_FILE_PATH
14
+ require 'yaml'
15
+ @config = YAML.load(File.read config_file)
16
+ @config.freeze
17
+
18
+ @pronouncer = Korean::Pronouncer.new(self)
19
+ end
20
+
21
+ # Array of chosung's
22
+ def chosungs
23
+ config['structure']['chosung']
24
+ end
25
+
26
+ # Array of jungsung's
27
+ def jungsungs
28
+ config['structure']['jungsung']
29
+ end
30
+
31
+ # Array of jongsung's
32
+ def jongsungs
33
+ config['structure']['jongsung']
34
+ end
35
+
36
+ # Checks if the given character is a korean character
37
+ def korean_char? ch
38
+ raise ArgumentError.new('Lengthy input') if ch.length > 1
39
+
40
+ complete_korean_char?(ch) ||
41
+ (chosungs + jungsungs + jongsungs).include?(ch)
42
+ end
43
+
44
+ # Checks if the given character is a "complete" korean character.
45
+ # "Complete" Korean character must have chosung and jungsung, with optional jongsung.
46
+ def complete_korean_char? ch
47
+ raise ArgumentError.new('Lengthy input') if ch.length > 1
48
+
49
+ # Range of Korean chracters in Unicode 2.0: AC00(가) ~ D7A3(힣)
50
+ ch.unpack('U').all? { | c | c >= 0xAC00 && c <= 0xD7A3 }
51
+ end
52
+
53
+ # Splits the given string into an array of Korean::Char's and strings.
54
+ def dissect str
55
+ str.each_char.map { |c|
56
+ korean_char?(c) ? Korean::Char.new(self, c) : c
57
+ }
58
+ end
59
+
60
+ # Reads a string with numbers in Korean way.
61
+ def read_number str
62
+ nconfig = config['number']
63
+
64
+ str.to_s.gsub(/([+-]\s*)?[0-9,]*,*[0-9]+(\.[0-9]+)?(\s*.)?/) {
65
+ read_number_sub($&, $3)
66
+ }
67
+ end
68
+
69
+ # Returns the pronunciation of the given string containing Korean characters.
70
+ # Takes optional options hash.
71
+ # - If :pronounce_each_char is true, each character of the string is pronounced respectively.
72
+ # - If :slur is true, characters separated by whitespaces are treated as if they were contiguous.
73
+ # - If :number is true, numberic parts of the string is also pronounced in Korean.
74
+ # - :except array allows you to skip certain transformations.
75
+ def pronounce str, options = {}
76
+ options = {
77
+ :pronounce_each_char => false,
78
+ :slur => false,
79
+ :number => true,
80
+ :except => [],
81
+ :debug => false
82
+ }.merge options
83
+
84
+ str = read_number(str) if options[:number]
85
+ chars = dissect str
86
+
87
+ transforms = []
88
+ idx = -1
89
+ while (idx += 1) < chars.length
90
+ c = chars[idx]
91
+
92
+ next if c.is_a?(Korean::Char) == false
93
+
94
+ next_c = chars[idx + 1]
95
+ next_kc = (options[:pronounce_each_char] == false &&
96
+ next_c.is_a?(Korean::Char) &&
97
+ next_c.complete?) ? next_c : nil
98
+
99
+ transforms += @pronouncer.transform(c, next_kc, :except => options[:except])
100
+
101
+ # Slur (TBD)
102
+ if options[:slur] && options[:pronounce_each_char] == false && next_c =~ /\s/
103
+ chars[(idx + 1)..-1].each_with_index do | nc, new_idx |
104
+ next if nc =~ /\s/
105
+
106
+ if nc.is_a?(Korean::Char) && nc.complete?
107
+ transforms += @pronouncer.transform(c, nc, :except => options[:except])
108
+ end
109
+
110
+ idx = idx + 1 + new_idx - 1
111
+ break
112
+ end
113
+ end
114
+ end
115
+
116
+ if options[:debug]
117
+ return chars.join, transforms
118
+ else
119
+ chars.join
120
+ end
121
+ end
122
+
123
+ # Returns the romanization (alphabetical notation) of the given Korean string.
124
+ # http://en.wikipedia.org/wiki/Korean_romanization
125
+ def romanize str, options = {}
126
+ options = {
127
+ :as_pronounced => true,
128
+ :number => true,
129
+ :slur => false
130
+ }.merge options
131
+
132
+ require 'yaml'
133
+ rdata = config['romanization']
134
+ post_subs = rdata["post substitution"]
135
+ rdata = [rdata["chosung"], rdata["jungsung"], rdata["jongsung"]]
136
+
137
+ str = pronounce str,
138
+ :pronounce_each_char => !options[:as_pronounced],
139
+ :number => options[:number],
140
+ :slur => options[:slur],
141
+ # 제1항 [붙임 1] ‘ㅢ’는 ‘ㅣ’로 소리 나더라도 ‘ui’로 적는다.
142
+ :except => %w[rule_5_3]
143
+ dash = rdata[0]["ㅇ"]
144
+ romanization = ""
145
+ (chars = str.each_char.to_a).each_with_index do | kc, cidx |
146
+ if korean_char? kc
147
+ Korean::Char.new(self, kc).to_a.each_with_index do | comp, idx |
148
+ next if comp.nil?
149
+ comp = rdata[idx][comp] || comp
150
+ comp = comp[1..-1] if comp[0] == dash &&
151
+ (romanization.empty? || romanization[-1] =~ /\s/ || comp[1] == 'w')
152
+ romanization += comp
153
+ end
154
+ else
155
+ romanization += kc
156
+ end
157
+ end
158
+
159
+ post_subs.keys.inject(romanization) { | output, pattern |
160
+ output.gsub(pattern, post_subs[pattern])
161
+ }.capitalize
162
+ end
163
+
164
+ private
165
+ def read_number_sub num, next_char = nil
166
+ nconfig = config['number']
167
+
168
+ # To number
169
+ if num.is_a? String
170
+ num = num.gsub(/[\s,]/, '')
171
+ raise ArgumentError.new("Invalid number format") unless num =~ /[-+]?[0-9,]*\.?[0-9]*/
172
+ num = num.to_f == num.to_i ? num.to_i : num.to_f
173
+ end
174
+
175
+ # Alternative notation for integers with proper suffix
176
+ alt = false
177
+ if num.is_a?(Float) == false && nconfig['alt notation']['when suffix'].keys.include?(next_char.to_s.strip)
178
+ max = nconfig['alt notation']['when suffix'][next_char.strip]['max']
179
+
180
+ if max.nil? || num <= max
181
+ alt = true
182
+ end
183
+ end
184
+
185
+ # Sign
186
+ if num < 0
187
+ num = -1 * num
188
+ negative = true
189
+ else
190
+ negative = false
191
+ end
192
+
193
+ if num.is_a? Float
194
+ below = nconfig['decimal point']
195
+ below = nconfig['digits'][0] + below if num < 1
196
+
197
+ s = num.to_s
198
+ if md = s.match(/(.*)e(.*)/)
199
+ s = md[1].tr '.', ''
200
+ exp = md[2].to_i
201
+ if exp > 0
202
+ s = s.ljust(exp + 1, '0')
203
+ else
204
+ s = '0.' + '0' * (-exp - 1) + s
205
+ end
206
+ end
207
+ s.sub(/.*\./, '').each_char do | char |
208
+ below += nconfig['digits'][char.to_i]
209
+ end
210
+ num = num.floor.to_i
211
+ else
212
+ below = ""
213
+ end
214
+
215
+ tokens = []
216
+ unit_idx = -1
217
+ while num > 0
218
+ v = num % 10000
219
+
220
+ if alt == false || unit_idx >= 0
221
+ str = ""
222
+ {1000 => '천',
223
+ 100 => '백',
224
+ 10 => '십'}.each do | u, sub_unit |
225
+ str += (nconfig['digits'][v/u] if v/u != 1).to_s + sub_unit + ' ' if v / u > 0
226
+ v %= u
227
+ end
228
+ str += nconfig['digits'][v] if v > 0
229
+
230
+ tokens << str.sub(/ $/, '') + nconfig['units'][unit_idx += 1]
231
+ else
232
+ str = ""
233
+ tenfolds = nconfig['alt notation']['tenfolds']
234
+ digits = nconfig['alt notation']['digits']
235
+ post_subs = nconfig['alt notation']['post substitution']
236
+
237
+ {1000 => '천',
238
+ 100 => '백',
239
+ }.each do | u, sub_unit |
240
+ str += (nconfig['digits'][v/u] if v/u != 1).to_s + sub_unit + ' ' if v / u > 0
241
+ v %= u
242
+ end
243
+
244
+ str += tenfolds[(v / 10) - 1] if v / 10 > 0
245
+ v %= 10
246
+ str += digits[v] if v > 0
247
+
248
+ suffix = next_char.strip
249
+ str = str + suffix
250
+ post_subs.each do | k, v |
251
+ str.gsub!(k, v)
252
+ end
253
+ str.sub!(/#{suffix}$/, '')
254
+ tokens << str.sub(/ $/, '') + nconfig['units'][unit_idx += 1]
255
+ end
256
+ num /= 10000
257
+ end
258
+
259
+ tokens << nconfig['negative'] if negative
260
+ tokens.reverse.join(' ') + next_char.to_s + below
261
+ end
262
+ end#Korean
263
+ end#Gimchi
264
+
265
+
@@ -0,0 +1,461 @@
1
+ # encoding: UTF-8
2
+
3
+ module Gimchi
4
+ class Korean
5
+ private
6
+ # Partial implementation of Korean pronouncement pronunciation rules specified in
7
+ # http://http://www.korean.go.kr/
8
+ class Pronouncer
9
+ attr_reader :applied
10
+
11
+ def initialize(korean)
12
+ @korean = korean
13
+ @pconfig = korean.config['pronouncer']
14
+ @applied = []
15
+ end
16
+
17
+ def transform kc, next_kc, options = {}
18
+ options = { :except => [] }.merge options
19
+ @applied.clear
20
+
21
+ # Cannot properly pronounce
22
+ return if kc.chosung.nil? && kc.jungsung.nil?
23
+
24
+ # Padding
25
+ kc.chosung = 'ㅇ' if kc.chosung.nil?
26
+ kc.jungsung = 'ㅡ' if kc.jungsung.nil?
27
+
28
+ if next_kc.nil?
29
+ rule_single kc, :except => options[:except]
30
+ else
31
+ not_todo = []
32
+ blocking_rule = @pconfig['transformation']['blocking rule']
33
+ @pconfig['transformation']['sequence'].each do | rule |
34
+ next if not_todo.include?(rule) || options[:except].include?(rule)
35
+
36
+ if self.send(rule, kc, next_kc)
37
+ @applied << rule
38
+ not_todo += blocking_rule[rule] if blocking_rule.has_key?(rule)
39
+ end
40
+ end
41
+ end
42
+ @applied
43
+ end
44
+
45
+ private
46
+ # shortcut
47
+ def fortis_map
48
+ @korean.config['structure']['fortis map']
49
+ end
50
+
51
+ # shortcut
52
+ def double_consonant_map
53
+ @korean.config['structure']['double consonant map']
54
+ end
55
+
56
+ def rule_single kc, options = {}
57
+ options = {:except => []}.merge options
58
+ rule_5_1 kc, nil unless options[:except].include? 'rule_5_1'
59
+ rule_5_3 kc, nil unless options[:except].include? 'rule_5_3'
60
+
61
+ if kc.jongsung
62
+ kc.jongsung = @pconfig['jongsung sound'][kc.jongsung]
63
+ end
64
+ end
65
+
66
+ # 제5항: ‘ㅑ ㅒ ㅕ ㅖ ㅘ ㅙ ㅛ ㅝ ㅞ ㅠ ㅢ’는 이중 모음으로 발음한다.
67
+ # 다만 1. 용언의 활용형에 나타나는 ‘져, 쪄, 쳐’는 [저, 쩌, 처]로 발음한다.
68
+ # 다만 3. 자음을 첫소리로 가지고 있는 음절의 ‘ㅢ’는 [ㅣ]로 발음한다.
69
+ def rule_5_1 kc, next_kc
70
+ if %w[져 쪄 쳐].include? kc.to_s
71
+ kc.jungsung = 'ㅓ'
72
+
73
+ true
74
+ end
75
+ end
76
+
77
+ def rule_5_3 kc, next_kc
78
+ if kc.jungsung == 'ㅢ' && kc.org.chosung.consonant?
79
+ kc.jungsung = 'ㅣ'
80
+
81
+ true
82
+ end
83
+ end
84
+
85
+ # 제9항: 받침 ‘ㄲ, ㅋ’, ‘ㅅ, ㅆ, ㅈ, ㅊ, ㅌ’, ‘ㅍ’은 어말 또는 자음 앞에서
86
+ # 각각 대표음 [ㄱ, ㄷ, ㅂ]으로 발음한다.
87
+ def rule_9 kc, next_kc
88
+ map = {
89
+ %w[ㄲ ㅋ] => 'ㄱ',
90
+ %w[ㅅ ㅆ ㅈ ㅊ ㅌ] => 'ㄷ',
91
+ %w[ㅍ] => 'ㅂ'
92
+ }
93
+ if map.keys.flatten.include?(kc.jongsung) && (next_kc.nil? || next_kc.chosung.consonant?)
94
+ kc.jongsung = map[ map.keys.find { |e| e.include? kc.jongsung } ]
95
+
96
+ true
97
+ end
98
+ end
99
+
100
+ # 제10항: 겹받침 ‘ㄳ’, ‘ㄵ’, ‘ㄼ, ㄽ, ㄾ’, ‘ㅄ’은 어말 또는 자음 앞에서
101
+ # 각각 [ㄱ, ㄴ, ㄹ, ㅂ]으로 발음한다.
102
+ def rule_10 kc, next_kc
103
+ map = {
104
+ %w[ㄳ] => 'ㄱ',
105
+ %w[ㄵ] => 'ㄴ',
106
+ %w[ㄼ ㄽ ㄾ] => 'ㄹ',
107
+ %w[ㅄ] => 'ㅂ'
108
+ }
109
+ if map.keys.flatten.include?(kc.jongsung) && (next_kc.nil? || next_kc.chosung.consonant?)
110
+ # Exceptions
111
+ if next_kc && (
112
+ (kc.to_s == '밟' && next_kc.chosung.consonant?) ||
113
+ (kc.to_s == '넓' && next_kc && %w[적 죽 둥].include?(next_kc.org.to_s))) # PATCH
114
+ kc.jongsung = 'ㅂ'
115
+ else
116
+ kc.jongsung = map[ map.keys.find { |e| e.include? kc.jongsung } ]
117
+ end
118
+
119
+ true
120
+ end
121
+ end
122
+
123
+ # 제11항: 겹받침 ‘ㄺ, ㄻ, ㄿ’은 어말 또는 자음 앞에서 각각 [ㄱ, ㅁ, ㅂ]으로 발음한다.
124
+ def rule_11 kc, next_kc
125
+ map = {
126
+ 'ㄺ' => 'ㄱ',
127
+ 'ㄻ' => 'ㅁ',
128
+ 'ㄿ' => 'ㅂ'
129
+ }
130
+ if map.keys.include?(kc.jongsung) && (next_kc.nil? || next_kc.chosung.consonant?)
131
+ # 다만, 용언의 어간 말음 ‘ㄺ’은 ‘ㄱ’ 앞에서 [ㄹ]로 발음한다.
132
+ # - 용언 여부 판단은?: 중성으로 판단 (PATCH)
133
+ if next_kc && kc.jongsung == 'ㄺ' &&
134
+ next_kc.org.chosung == 'ㄱ' &&
135
+ %w[맑 얽 섥 밝 늙 묽 넓].include?(kc.to_s) # PATCH
136
+ kc.jongsung = 'ㄹ'
137
+ else
138
+ kc.jongsung = map[kc.jongsung]
139
+ end
140
+
141
+ true
142
+ end
143
+ end
144
+
145
+ # 제12항: 받침 ‘ㅎ’의 발음은 다음과 같다.
146
+ # 1. ‘ㅎ(ㄶ, ㅀ)’ 뒤에 ‘ㄱ, ㄷ, ㅈ’이 결합되는 경우에는, 뒤 음절 첫소리와
147
+ # 합쳐서 [ㅋ, ㅌ, ㅊ]으로 발음한다.
148
+ # [붙임 1]받침 ‘ㄱ(ㄺ), ㄷ, ㅂ(ㄼ), ㅈ(ㄵ)’이 뒤 음절 첫소리 ‘ㅎ’과
149
+ # 결합되는 경우에도, 역시 두 음을 합쳐서 [ㅋ, ㅌ, ㅍ, ㅊ]으로 발음한다.
150
+ # [붙임 2]규정에 따라 ‘ㄷ’으로 발음되는 ‘ㅅ, ㅈ, ㅊ, ㅌ’의 경우에도 이에 준한다.
151
+ #
152
+ # 2. ‘ㅎ(ㄶ, ㅀ)’ 뒤에 ‘ㅅ’이 결합되는 경우에는, ‘ㅅ’을 [ㅆ]으로 발음한다.
153
+ #
154
+ # 3. ‘ㅎ’ 뒤에 ‘ㄴ’이 결합되는 경우에는, [ㄴ]으로 발음한다.
155
+ # [붙임]‘ㄶ, ㅀ’ 뒤에 ‘ㄴ’이 결합되는 경우에는, ‘ㅎ’을 발음하지 않는다.
156
+ #
157
+ # 4. ‘ㅎ(ㄶ, ㅀ)’ 뒤에 모음으로 시작된 어미나 접미사가 결합되는 경우에는, ‘ㅎ’을 발음하지 않는다.
158
+ def rule_12 kc, next_kc
159
+ return if next_kc.nil?
160
+
161
+ map_12_1 = {
162
+ 'ㄱ' => 'ㅋ',
163
+ 'ㄷ' => 'ㅌ',
164
+ 'ㅈ' => 'ㅊ' }
165
+ if %w[ㅎ ㄶ ㅀ].include?(kc.jongsung)
166
+ # 12-1
167
+ if map_12_1.keys.include?(next_kc.chosung)
168
+ next_kc.chosung = map_12_1[next_kc.chosung]
169
+ kc.jongsung = (dc = double_consonant_map[kc.jongsung]) && dc.first
170
+
171
+ # 12-2
172
+ elsif next_kc.chosung == 'ㅅ'
173
+ kc.jongsung = (dc = double_consonant_map[kc.jongsung]) && dc.first
174
+ next_kc.chosung = 'ㅆ'
175
+
176
+ # 12-3
177
+ elsif next_kc.chosung == 'ㄴ'
178
+ if dc = double_consonant_map[kc.jongsung]
179
+ kc.jongsung = dc.first
180
+ else
181
+ kc.jongsung = 'ㄴ'
182
+ end
183
+
184
+ # 12-4
185
+ elsif next_kc.chosung == 'ㅇ'
186
+ kc.jongsung = (dc = double_consonant_map[kc.jongsung]) && dc.first
187
+ end
188
+
189
+ true
190
+ end
191
+
192
+ # 12-1 붙임
193
+ if next_kc.chosung == 'ㅎ'
194
+ map_jongsung = {
195
+ # 붙임 1
196
+ 'ㄱ' => [nil, 'ㅋ'],
197
+ 'ㄺ' => ['ㄹ', 'ㅋ'],
198
+ 'ㄷ' => [nil, 'ㅌ'],
199
+ 'ㅂ' => [nil, 'ㅍ'],
200
+ 'ㄼ' => ['ㄹ', 'ㅍ'],
201
+ 'ㅈ' => [nil, 'ㅊ'],
202
+ 'ㄵ' => ['ㄴ', 'ㅊ'],
203
+
204
+ # 붙임 2
205
+ 'ㅅ' => [nil, 'ㅌ'],
206
+ #'ㅈ' => [nil, 'ㅌ'], # FIXME: 붙임2의 모순
207
+ 'ㅊ' => [nil, 'ㅌ'],
208
+ 'ㅌ' => [nil, 'ㅌ'],
209
+ }
210
+ if trans1 = map_jongsung[kc.jongsung]
211
+ kc.jongsung = trans1.first
212
+ next_kc.chosung = trans1.last
213
+
214
+ true
215
+ end
216
+ end
217
+ end
218
+
219
+ # 제13항: 홑받침이나 쌍받침이 모음으로 시작된 조사나 어미, 접미사와
220
+ # 결합되는 경우에는, 제 음가대로 뒤 음절 첫소리로 옮겨 발음한다.
221
+ def rule_13 kc, next_kc
222
+ return if kc.jongsung.nil? || kc.jongsung == 'ㅇ' || next_kc.nil? || next_kc.chosung != 'ㅇ'
223
+ next_kc.chosung = kc.jongsung
224
+ kc.jongsung = nil
225
+
226
+ true
227
+ end
228
+ # 제14항: 겹받침이 모음으로 시작된 조사나 어미, 접미사와 결합되는 경우에는,
229
+ # 뒤엣것만을 뒤 음절 첫소리로 옮겨 발음한다.(이 경우, ‘ㅅ’은 된소리로 발음함.)
230
+ #
231
+ def rule_14 kc, next_kc
232
+ return if kc.jongsung.nil? || kc.jongsung == 'ㅇ' || next_kc.nil? || next_kc.chosung != 'ㅇ'
233
+ if consonants = double_consonant_map[kc.jongsung]
234
+ consonants[1] = 'ㅆ' if consonants[1] == 'ㅅ'
235
+ kc.jongsung, next_kc.chosung = consonants
236
+
237
+ true
238
+ end
239
+ end
240
+ # 제15항: 받침 뒤에 모음 ‘ㅏ, ㅓ, ㅗ, ㅜ, ㅟ’들로 시작되는 __실질 형태소__가 연결되는
241
+ # 경우에는, 대표음으로 바꾸어서 뒤 음절 첫소리로 옮겨 발음한다.
242
+ def rule_15 kc, next_kc
243
+ return if kc.jongsung.nil? || kc.jongsung == 'ㅇ' || next_kc.nil? || next_kc.chosung != 'ㅇ'
244
+
245
+ if false && %w[ㅏ ㅓ ㅗ ㅜ ㅟ].include?(next_kc.jungsung) &&
246
+ %[ㅆ ㄲ ㅈ ㅊ ㄵ ㄻ ㄾ ㄿ ㄺ].include?(kc.jongsung) == false # PATCH
247
+ next_kc.chosung = @pconfig['jongsung sound'][ kc.jongsung ]
248
+ kc.jongsung = nil
249
+
250
+ true
251
+ end
252
+ end
253
+
254
+ # 제16항: 한글 자모의 이름은 그 받침소리를 연음하되, ‘ㄷ, ㅈ, ㅊ, ㅋ, ㅌ,
255
+ # ㅍ, ㅎ’의 경우에는 특별히 다음과 같이 발음한다.
256
+ def rule_16 kc, next_kc
257
+ return if next_kc.nil?
258
+
259
+ map = {'디귿' => '디긋',
260
+ '지읒' => '지읏',
261
+ '치읓' => '치읏',
262
+ '키읔' => '키윽',
263
+ '티읕' => '티읏',
264
+ '피읖' => '피읍',
265
+ '히읗' => '히읏'}
266
+
267
+ word = kc.to_s + next_kc.to_s
268
+ if map.keys.include? word
269
+ new_char = @korean.dissect(map[word][1])[0]
270
+ next_kc.chosung = new_char.chosung
271
+ next_kc.jongsung = new_char.jongsung
272
+
273
+ true
274
+ end
275
+ end
276
+
277
+ # 제17항: 받침 ‘ㄷ, ㅌ(ㄾ)’이 조사나 접미사의 모음 ‘ㅣ’와 결합되는 경우에는,
278
+ # [ㅈ, ㅊ]으로 바꾸어서 뒤 음절 첫소리로 옮겨 발음한다.
279
+ #
280
+ # [붙임] ‘ㄷ’ 뒤에 접미사 ‘히’가 결합되어 ‘티’를 이루는 것은 [치]로 발음한다.
281
+ def rule_17 kc, next_kc
282
+ return if next_kc.nil? || %w[ㄷ ㅌ ㄾ].include?(kc.jongsung) == false
283
+
284
+ if next_kc.to_s == '이'
285
+ next_kc.chosung = kc.jongsung == 'ㄷ' ? 'ㅈ' : 'ㅊ'
286
+ kc.jongsung = (dc = double_consonant_map[kc.jongsung]) && dc.first
287
+
288
+ true
289
+ elsif next_kc.to_s == '히'
290
+ next_kc.chosung = 'ㅊ'
291
+ kc.jongsung = (dc = double_consonant_map[kc.jongsung]) && dc.first
292
+
293
+ true
294
+ end
295
+ end
296
+
297
+ # 제18항: 받침 ‘ㄱ(ㄲ, ㅋ, ㄳ, ㄺ), ㄷ(ㅅ, ㅆ, ㅈ, ㅊ, ㅌ, ㅎ), ㅂ(ㅍ, ㄼ,
298
+ # ㄿ, ㅄ)’은 ‘ㄴ, ㅁ’ 앞에서 [ㅇ, ㄴ, ㅁ]으로 발음한다.
299
+ def rule_18 kc, next_kc
300
+ map = {
301
+ %w[ㄱ ㄲ ㅋ ㄳ ㄺ] => 'ㅇ',
302
+ %w[ㄷ ㅅ ㅆ ㅈ ㅊ ㅌ ㅎ] => 'ㄴ',
303
+ %w[ㅂ ㅍ ㄼ ㄿ ㅄ] => 'ㅁ'
304
+ }
305
+ if next_kc && map.keys.flatten.include?(kc.jongsung) && %w[ㄴ ㅁ].include?(next_kc.chosung)
306
+ kc.jongsung = map[ map.keys.find { |e| e.include? kc.jongsung } ]
307
+
308
+ true
309
+ end
310
+ end
311
+
312
+ # 제19항: 받침 ‘ㅁ, ㅇ’ 뒤에 연결되는 ‘ㄹ’은 [ㄴ]으로 발음한다.
313
+ # [붙임]받침 ‘ㄱ, ㅂ’ 뒤에 연결되는 ‘ㄹ’도 [ㄴ]으로 발음한다.
314
+ def rule_19 kc, next_kc
315
+ if next_kc && next_kc.chosung == 'ㄹ' && %w[ㅁ ㅇ ㄱ ㅂ].include?(kc.jongsung)
316
+ next_kc.chosung = 'ㄴ'
317
+
318
+ case kc.jongsung
319
+ when 'ㄱ' then kc.jongsung = 'ㅇ'
320
+ when 'ㅂ' then kc.jongsung = 'ㅁ'
321
+ end
322
+
323
+ true
324
+ end
325
+ end
326
+
327
+ # 제20항: ‘ㄴ’은 ‘ㄹ’의 앞이나 뒤에서 [ㄹ]로 발음한다.
328
+ def rule_20 kc, next_kc
329
+ return if next_kc.nil?
330
+
331
+ to = if %w[견란 진란 산량 단력 권력 원령 견례
332
+ 문로 단로 원론 원료 근류].include?(kc.org.to_s + next_kc.org.to_s)
333
+ 'ㄴ'
334
+ else
335
+ 'ㄹ'
336
+ end
337
+
338
+ if kc.jongsung == 'ㄹ' && next_kc.chosung == 'ㄴ'
339
+ kc.jongsung = next_kc.chosung = to
340
+
341
+ true
342
+ elsif kc.jongsung == 'ㄴ' && next_kc.chosung == 'ㄹ'
343
+ kc.jongsung = next_kc.chosung = to
344
+
345
+ true
346
+ end
347
+ end
348
+
349
+ # 제23항: 받침 ‘ㄱ(ㄲ, ㅋ, ㄳ, ㄺ), ㄷ(ㅅ, ㅆ, ㅈ, ㅊ, ㅌ), ㅂ(ㅍ, ㄼ, ㄿ,ㅄ)’
350
+ # 뒤에 연결되는 ‘ㄱ, ㄷ, ㅂ, ㅅ, ㅈ’은 된소리로 발음한다.
351
+ def rule_23 kc, next_kc
352
+ return if next_kc.nil?
353
+ if fortis_map.keys.include?(next_kc.chosung) &&
354
+ %w[ㄱ ㄲ ㅋ ㄳ ㄺ ㄷ ㅅ ㅆ ㅈ ㅊ ㅌ ㅂ ㅍ ㄼ ㄿ ㅄ].include?(kc.jongsung)
355
+ next_kc.chosung = fortis_map[next_kc.chosung]
356
+
357
+ true
358
+ end
359
+ end
360
+
361
+ # 제24항: 어간 받침 ‘ㄴ(ㄵ), ㅁ(ㄻ)’ 뒤에 결합되는 어미의 첫소리 ‘ㄱ, ㄷ, ㅅ, ㅈ’은 된소리로 발음한다.
362
+ # 다만, 피동, 사동의 접미사 ‘-기-’는 된소리로 발음하지 않는다.
363
+ # 용언 어간에만 적용.
364
+ def rule_24 kc, next_kc
365
+ return if next_kc.nil? ||
366
+ next_kc.to_s == '기' # FIXME 피동/사동 여부 판단 불가. e.g. 줄넘기
367
+
368
+ # FIXME 용언 여부를 판단. 정확한 판단 불가.
369
+ return unless case kc.jongsung
370
+ when 'ㄵ'
371
+ %w[앉 얹].include? kc.to_s
372
+ when 'ㄻ'
373
+ %w[젊 닮].include? kc.to_s
374
+ else
375
+ false # XXX 일반적인 경우 사전 없이 판단 불가
376
+ end
377
+
378
+ if %w[ㄱ ㄷ ㅅ ㅈ].include?(next_kc.chosung) &&
379
+ %w[ㄴ ㄵ ㅁ ㄻ ㄼ ㄾ].include?(kc.jongsung)
380
+ next_kc.chosung = fortis_map[next_kc.chosung]
381
+
382
+ true
383
+ end
384
+ end
385
+
386
+ # 제25항: 어간 받침 ‘ㄼ, ㄾ’ 뒤에 결합되는 어미의 첫소리 ‘ㄱ, ㄷ, ㅅ, ㅈ’은
387
+ # 된소리로 발음한다.
388
+ def rule_25 kc, next_kc
389
+ return if next_kc.nil?
390
+
391
+ if %w[ㄱ ㄷ ㅅ ㅈ].include?(next_kc.chosung) &&
392
+ %w[ㄼ ㄾ].include?(kc.jongsung)
393
+ next_kc.chosung = fortis_map[next_kc.chosung]
394
+
395
+ true
396
+ end
397
+ end
398
+
399
+ # 제26항: 한자어에서, ‘ㄹ’ 받침 뒤에 연결되는 ‘ㄷ, ㅅ, ㅈ’은 된소리로 발음한다.
400
+ def rule_26 kc, next_kc
401
+ # TODO
402
+ end
403
+
404
+ # 제27항: __관형사형__ ‘-(으)ㄹ’ 뒤에 연결되는 ‘ㄱ, ㄷ, ㅂ, ㅅ, ㅈ’은 된소리로 발음한다.
405
+ # - ‘-(으)ㄹ’로 시작되는 어미의 경우에도 이에 준한다.
406
+ def rule_27 kc, next_kc
407
+ # FIXME: NOT PROPERLY IMPLEMENTED
408
+ return if next_kc.nil?
409
+
410
+ # 비교적 확률이 높은 경우들에 대해서만 처리. "일" 은 제외.
411
+ if %w[할 갈 날 볼 을 앨 말 힐].include?(kc.to_s) && # kc.jongsung == 'ㄹ' &&
412
+ %w[ㄱ ㄷ ㅂ ㅅ ㅈ].include?(next_kc.chosung)
413
+ next_kc.chosung = fortis_map[next_kc.chosung]
414
+ true
415
+ end
416
+ end
417
+
418
+ # 제26항: 한자어에서, ‘ㄹ’ 받침 뒤에 연결되는 ‘ㄷ, ㅅ, ㅈ’은 된소리로 발음한다.
419
+ # 제28항: 표기상으로는 사이시옷이 없더라도, 관형격 기능을 지니는 사이시옷이
420
+ # 있어야 할(휴지가 성립되는) 합성어의 경우에는, 뒤 단어의 첫소리 ‘ㄱ, ㄷ,
421
+ # ㅂ, ㅅ, ㅈ’을 된소리로 발음한다.
422
+ def rule_26_28 kc, next_kc
423
+ # TODO
424
+ end
425
+
426
+ # 제29항: 합성어 및 파생어에서, 앞 단어나 접두사의 끝이 자음이고 뒤 단어나
427
+ # 접미사의 첫음절이 ‘이, 야, 여, 요, 유’인 경우에는, ‘ㄴ’ 음을 첨가하여
428
+ # [니, 냐, 녀, 뇨, 뉴]로 발음한다.
429
+ def rule_29 kc, next_kc
430
+ # TODO
431
+ end
432
+
433
+ # 제30항: 사이시옷이 붙은 단어는 다음과 같이 발음한다.
434
+ # 1. ‘ㄱ, ㄷ, ㅂ, ㅅ, ㅈ’으로 시작하는 단어 앞에 사이시옷이 올 때는 이들
435
+ # 자음만을 된소리로 발음하는 것을 원칙으로 하되, 사이시옷을 [ㄷ]으로
436
+ # 발음하는 것도 허용한다.
437
+ # 2. 사이시옷 뒤에 ‘ㄴ, ㅁ’이 결합되는 경우에는 [ㄴ]으로 발음한다.
438
+ # 3. 사이시옷 뒤에 ‘이’ 음이 결합되는 경우에는 [ㄴㄴ]으로 발음한다.
439
+ def rule_30 kc, next_kc
440
+ return if next_kc.nil? || kc.jongsung != 'ㅅ'
441
+
442
+ if %w[ㄱ ㄷ ㅂ ㅅ ㅈ].include? next_kc.chosung
443
+ kc.jongsung = 'ㄷ' # or nil
444
+ next_kc.chosung = fortis_map[next_kc.chosung]
445
+
446
+ true
447
+ elsif %w[ㄴ ㅁ].include? next_kc.chosung
448
+ kc.jongsung = 'ㄴ'
449
+
450
+ true
451
+ elsif next_kc.chosung == 'ㅇ' &&
452
+ %w[ㅣ ㅒ ㅖ ㅑ ㅕ ㅛ ㅠ].include?(next_kc.jungsung) &&
453
+ next_kc.jongsung # PATCH
454
+ kc.jongsung = next_kc.chosung = 'ㄴ'
455
+
456
+ true
457
+ end
458
+ end
459
+ end#Pronouncer
460
+ end#Korean
461
+ end#Gimchi