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.
- 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
|