gimchi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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