gimchi 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +20 -0
- data/README.ko.rdoc +116 -0
- data/README.rdoc +118 -0
- data/config/default.yml +156 -0
- data/lib/gimchi.rb +8 -0
- data/lib/gimchi/char.rb +104 -0
- data/lib/gimchi/korean.rb +265 -0
- data/lib/gimchi/pronouncer.rb +461 -0
- data/test/helper.rb +17 -0
- data/test/pronunciation.yml +490 -0
- data/test/romanization.yml +97 -0
- data/test/test_gimchi.rb +135 -0
- metadata +111 -0
@@ -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
|