File Coverage

blib/lib/Lingua/KO/Hangul/Util.pm
Criterion Covered Total %
statement 169 169 100.0
branch 54 54 100.0
condition 37 38 97.3
subroutine 40 40 100.0
pod 13 13 100.0
total 313 314 99.6


line stmt bran cond sub pod time code
1             package Lingua::KO::Hangul::Util;
2              
3 6     6   3758 use 5.006001;
  6         55  
4 6     6   35 use strict;
  6         13  
  6         143  
5 6     6   33 use warnings;
  6         13  
  6         744  
6              
7             require Exporter;
8              
9             our $VERSION = '0.28';
10             our $PACKAGE = __PACKAGE__;
11              
12             our @EXPORT = qw(
13             decomposeHangul
14             composeHangul
15             getHangulName
16             parseHangulName
17             getHangulComposite
18             );
19             our @EXPORT_OK = qw(
20             decomposeSyllable
21             composeSyllable
22             decomposeJamo
23             composeJamo
24             decomposeFull
25             getSyllableType
26             isStandardForm
27             insertFiller
28             );
29             our %EXPORT_TAGS = (
30             'all' => [ @EXPORT, @EXPORT_OK ],
31             );
32              
33             ##### The above part is common to XS and PP #####
34              
35             our @ISA = qw(Exporter);
36 6     6   39 use Carp;
  6         15  
  6         1057  
37              
38             #####
39              
40             my @JamoL = ( # Initial (HANGUL CHOSEONG)
41             "G", "GG", "N", "D", "DD", "R", "M", "B", "BB",
42             "S", "SS", "", "J", "JJ", "C", "K", "T", "P", "H",
43             );
44              
45             my @JamoV = ( # Medial (HANGUL JUNGSEONG)
46             "A", "AE", "YA", "YAE", "EO", "E", "YEO", "YE", "O",
47             "WA", "WAE", "OE", "YO", "U", "WEO", "WE", "WI",
48             "YU", "EU", "YI", "I",
49             );
50              
51             my @JamoT = ( # Final (HANGUL JONGSEONG)
52             "", "G", "GG", "GS", "N", "NJ", "NH", "D", "L", "LG", "LM",
53             "LB", "LS", "LT", "LP", "LH", "M", "B", "BS",
54             "S", "SS", "NG", "J", "C", "K", "T", "P", "H",
55             );
56              
57             my $BlockName = "HANGUL SYLLABLE ";
58              
59             #####
60              
61 6     6   42 use constant SBase => 0xAC00;
  6         14  
  6         514  
62 6     6   37 use constant SFinal => 0xD7A3; # SBase -1 + SCount
  6         13  
  6         351  
63 6     6   52 use constant SCount => 11172; # LCount * NCount
  6         22  
  6         319  
64 6     6   35 use constant NCount => 588; # VCount * TCount
  6         12  
  6         324  
65 6     6   37 use constant LBase => 0x1100;
  6         14  
  6         301  
66 6     6   38 use constant LFinal => 0x1112;
  6         12  
  6         267  
67 6     6   38 use constant LCount => 19; # scalar @JamoL
  6         13  
  6         275  
68 6     6   37 use constant VBase => 0x1161;
  6         14  
  6         292  
69 6     6   32 use constant VFinal => 0x1175;
  6         13  
  6         243  
70 6     6   34 use constant VCount => 21; # scalar @JamoV
  6         10  
  6         329  
71 6     6   38 use constant TBase => 0x11A7;
  6         12  
  6         328  
72 6     6   37 use constant TFinal => 0x11C2;
  6         13  
  6         267  
73 6     6   33 use constant TCount => 28; # scalar @JamoT
  6         12  
  6         261  
74 6     6   33 use constant JBase => 0x1100;
  6         15  
  6         253  
75 6     6   53 use constant JFinal => 0x11FF;
  6         12  
  6         259  
76 6     6   32 use constant JCount => 256;
  6         11  
  6         285  
77              
78 6     6   38 use constant JamoLIni => 0x1100;
  6         13  
  6         312  
79 6     6   39 use constant JamoLFin => 0x1159;
  6         14  
  6         337  
80 6     6   36 use constant JamoLFill => 0x115F;
  6         12  
  6         265  
81 6     6   33 use constant JamoVIni => 0x1160;
  6         10  
  6         234  
82 6     6   32 use constant JamoVFin => 0x11A2;
  6         12  
  6         262  
83 6     6   37 use constant JamoTIni => 0x11A8;
  6         16  
  6         262  
84 6     6   36 use constant JamoTFin => 0x11F9;
  6         8  
  6         13922  
85              
86             my(%CodeL, %CodeV, %CodeT);
87             @CodeL{@JamoL} = 0 .. LCount-1;
88             @CodeV{@JamoV} = 0 .. VCount-1;
89             @CodeT{@JamoT} = 0 .. TCount-1;
90              
91             my $IsJ = sub { JBase <= $_[0] && $_[0] <= JFinal };
92             my $IsS = sub { SBase <= $_[0] && $_[0] <= SFinal };
93             my $IsL = sub { LBase <= $_[0] && $_[0] <= LFinal };
94             my $IsV = sub { VBase <= $_[0] && $_[0] <= VFinal };
95             my $IsT = sub { TBase < $_[0] && $_[0] <= TFinal };
96             # TBase <= $_[0] is false!
97             my $IsLV = sub {
98             SBase <= $_[0] && $_[0] <= SFinal && (($_[0] - SBase ) % TCount) == 0;
99             };
100              
101             #####
102              
103             # separator is a semicolon, ';'.
104             my %Map12; # ("integer;integer" => integer)
105             my %Map123; # ("integer;integer;integer" => integer)
106              
107             my %Decomp = (
108             0x1101 => [0x1100, 0x1100],
109             0x1104 => [0x1103, 0x1103],
110             0x1108 => [0x1107, 0x1107],
111             0x110A => [0x1109, 0x1109],
112             0x110D => [0x110C, 0x110C],
113             0x1113 => [0x1102, 0x1100],
114             0x1114 => [0x1102, 0x1102],
115             0x1115 => [0x1102, 0x1103],
116             0x1116 => [0x1102, 0x1107],
117             0x1117 => [0x1103, 0x1100],
118             0x1118 => [0x1105, 0x1102],
119             0x1119 => [0x1105, 0x1105],
120             0x111A => [0x1105, 0x1112],
121             0x111B => [0x1105, 0x110B],
122             0x111C => [0x1106, 0x1107],
123             0x111D => [0x1106, 0x110B],
124             0x111E => [0x1107, 0x1100],
125             0x111F => [0x1107, 0x1102],
126             0x1120 => [0x1107, 0x1103],
127             0x1121 => [0x1107, 0x1109],
128             0x1122 => [0x1107, 0x1109, 0x1100],
129             0x1123 => [0x1107, 0x1109, 0x1103],
130             0x1124 => [0x1107, 0x1109, 0x1107],
131             0x1125 => [0x1107, 0x1109, 0x1109],
132             0x1126 => [0x1107, 0x1109, 0x110C],
133             0x1127 => [0x1107, 0x110C],
134             0x1128 => [0x1107, 0x110E],
135             0x1129 => [0x1107, 0x1110],
136             0x112A => [0x1107, 0x1111],
137             0x112B => [0x1107, 0x110B],
138             0x112C => [0x1107, 0x1107, 0x110B],
139             0x112D => [0x1109, 0x1100],
140             0x112E => [0x1109, 0x1102],
141             0x112F => [0x1109, 0x1103],
142             0x1130 => [0x1109, 0x1105],
143             0x1131 => [0x1109, 0x1106],
144             0x1132 => [0x1109, 0x1107],
145             0x1133 => [0x1109, 0x1107, 0x1100],
146             0x1134 => [0x1109, 0x1109, 0x1109],
147             0x1135 => [0x1109, 0x110B],
148             0x1136 => [0x1109, 0x110C],
149             0x1137 => [0x1109, 0x110E],
150             0x1138 => [0x1109, 0x110F],
151             0x1139 => [0x1109, 0x1110],
152             0x113A => [0x1109, 0x1111],
153             0x113B => [0x1109, 0x1112],
154             0x113D => [0x113C, 0x113C],
155             0x113F => [0x113E, 0x113E],
156             0x1141 => [0x110B, 0x1100],
157             0x1142 => [0x110B, 0x1103],
158             0x1143 => [0x110B, 0x1106],
159             0x1144 => [0x110B, 0x1107],
160             0x1145 => [0x110B, 0x1109],
161             0x1146 => [0x110B, 0x1140],
162             0x1147 => [0x110B, 0x110B],
163             0x1148 => [0x110B, 0x110C],
164             0x1149 => [0x110B, 0x110E],
165             0x114A => [0x110B, 0x1110],
166             0x114B => [0x110B, 0x1111],
167             0x114D => [0x110C, 0x110B],
168             0x114F => [0x114E, 0x114E],
169             0x1151 => [0x1150, 0x1150],
170             0x1152 => [0x110E, 0x110F],
171             0x1153 => [0x110E, 0x1112],
172             0x1156 => [0x1111, 0x1107],
173             0x1157 => [0x1111, 0x110B],
174             0x1158 => [0x1112, 0x1112],
175             0x1162 => [0x1161, 0x1175],
176             0x1164 => [0x1163, 0x1175],
177             0x1166 => [0x1165, 0x1175],
178             0x1168 => [0x1167, 0x1175],
179             0x116A => [0x1169, 0x1161],
180             0x116B => [0x1169, 0x1161, 0x1175],
181             0x116C => [0x1169, 0x1175],
182             0x116F => [0x116E, 0x1165],
183             0x1170 => [0x116E, 0x1165, 0x1175],
184             0x1171 => [0x116E, 0x1175],
185             0x1174 => [0x1173, 0x1175],
186             0x1176 => [0x1161, 0x1169],
187             0x1177 => [0x1161, 0x116E],
188             0x1178 => [0x1163, 0x1169],
189             0x1179 => [0x1163, 0x116D],
190             0x117A => [0x1165, 0x1169],
191             0x117B => [0x1165, 0x116E],
192             0x117C => [0x1165, 0x1173],
193             0x117D => [0x1167, 0x1169],
194             0x117E => [0x1167, 0x116E],
195             0x117F => [0x1169, 0x1165],
196             0x1180 => [0x1169, 0x1165, 0x1175],
197             0x1181 => [0x1169, 0x1167, 0x1175],
198             0x1182 => [0x1169, 0x1169],
199             0x1183 => [0x1169, 0x116E],
200             0x1184 => [0x116D, 0x1163],
201             0x1185 => [0x116D, 0x1163, 0x1175],
202             0x1186 => [0x116D, 0x1167],
203             0x1187 => [0x116D, 0x1169],
204             0x1188 => [0x116D, 0x1175],
205             0x1189 => [0x116E, 0x1161],
206             0x118A => [0x116E, 0x1161, 0x1175],
207             0x118B => [0x116E, 0x1165, 0x1173],
208             0x118C => [0x116E, 0x1167, 0x1175],
209             0x118D => [0x116E, 0x116E],
210             0x118E => [0x1172, 0x1161],
211             0x118F => [0x1172, 0x1165],
212             0x1190 => [0x1172, 0x1165, 0x1175],
213             0x1191 => [0x1172, 0x1167],
214             0x1192 => [0x1172, 0x1167, 0x1175],
215             0x1193 => [0x1172, 0x116E],
216             0x1194 => [0x1172, 0x1175],
217             0x1195 => [0x1173, 0x116E],
218             0x1196 => [0x1173, 0x1173],
219             0x1197 => [0x1173, 0x1175, 0x116E],
220             0x1198 => [0x1175, 0x1161],
221             0x1199 => [0x1175, 0x1163],
222             0x119A => [0x1175, 0x1169],
223             0x119B => [0x1175, 0x116E],
224             0x119C => [0x1175, 0x1173],
225             0x119D => [0x1175, 0x119E],
226             0x119F => [0x119E, 0x1165],
227             0x11A0 => [0x119E, 0x116E],
228             0x11A1 => [0x119E, 0x1175],
229             0x11A2 => [0x119E, 0x119E],
230             0x11A9 => [0x11A8, 0x11A8],
231             0x11AA => [0x11A8, 0x11BA],
232             0x11AC => [0x11AB, 0x11BD],
233             0x11AD => [0x11AB, 0x11C2],
234             0x11B0 => [0x11AF, 0x11A8],
235             0x11B1 => [0x11AF, 0x11B7],
236             0x11B2 => [0x11AF, 0x11B8],
237             0x11B3 => [0x11AF, 0x11BA],
238             0x11B4 => [0x11AF, 0x11C0],
239             0x11B5 => [0x11AF, 0x11C1],
240             0x11B6 => [0x11AF, 0x11C2],
241             0x11B9 => [0x11B8, 0x11BA],
242             0x11BB => [0x11BA, 0x11BA],
243             0x11C3 => [0x11A8, 0x11AF],
244             0x11C4 => [0x11A8, 0x11BA, 0x11A8],
245             0x11C5 => [0x11AB, 0x11A8],
246             0x11C6 => [0x11AB, 0x11AE],
247             0x11C7 => [0x11AB, 0x11BA],
248             0x11C8 => [0x11AB, 0x11EB],
249             0x11C9 => [0x11AB, 0x11C0],
250             0x11CA => [0x11AE, 0x11A8],
251             0x11CB => [0x11AE, 0x11AF],
252             0x11CC => [0x11AF, 0x11A8, 0x11BA],
253             0x11CD => [0x11AF, 0x11AB],
254             0x11CE => [0x11AF, 0x11AE],
255             0x11CF => [0x11AF, 0x11AE, 0x11C2],
256             0x11D0 => [0x11AF, 0x11AF],
257             0x11D1 => [0x11AF, 0x11B7, 0x11A8],
258             0x11D2 => [0x11AF, 0x11B7, 0x11BA],
259             0x11D3 => [0x11AF, 0x11B8, 0x11BA],
260             0x11D4 => [0x11AF, 0x11B8, 0x11C2],
261             0x11D5 => [0x11AF, 0x11B8, 0x11BC],
262             0x11D6 => [0x11AF, 0x11BA, 0x11BA],
263             0x11D7 => [0x11AF, 0x11EB],
264             0x11D8 => [0x11AF, 0x11BF],
265             0x11D9 => [0x11AF, 0x11F9],
266             0x11DA => [0x11B7, 0x11A8],
267             0x11DB => [0x11B7, 0x11AF],
268             0x11DC => [0x11B7, 0x11B8],
269             0x11DD => [0x11B7, 0x11BA],
270             0x11DE => [0x11B7, 0x11BA, 0x11BA],
271             0x11DF => [0x11B7, 0x11EB],
272             0x11E0 => [0x11B7, 0x11BE],
273             0x11E1 => [0x11B7, 0x11C2],
274             0x11E2 => [0x11B7, 0x11BC],
275             0x11E3 => [0x11B8, 0x11AF],
276             0x11E4 => [0x11B8, 0x11C1],
277             0x11E5 => [0x11B8, 0x11C2],
278             0x11E6 => [0x11B8, 0x11BC],
279             0x11E7 => [0x11BA, 0x11A8],
280             0x11E8 => [0x11BA, 0x11AE],
281             0x11E9 => [0x11BA, 0x11AF],
282             0x11EA => [0x11BA, 0x11B8],
283             0x11EC => [0x11BC, 0x11A8],
284             0x11ED => [0x11BC, 0x11A8, 0x11A8],
285             0x11EE => [0x11BC, 0x11BC],
286             0x11EF => [0x11BC, 0x11BF],
287             0x11F1 => [0x11F0, 0x11BA],
288             0x11F2 => [0x11F0, 0x11EB],
289             0x11F3 => [0x11C1, 0x11B8],
290             0x11F4 => [0x11C1, 0x11BC],
291             0x11F5 => [0x11C2, 0x11AB],
292             0x11F6 => [0x11C2, 0x11AF],
293             0x11F7 => [0x11C2, 0x11B7],
294             0x11F8 => [0x11C2, 0x11B8],
295             );
296              
297             foreach my $char (sort {$a <=> $b} keys %Decomp) {
298             $char or croak("$PACKAGE : composition to NULL is not allowed");
299             my @dec = @{ $Decomp{$char} };
300             @dec == 2 || @dec == 3 or
301             croak(sprintf("$PACKAGE : weird decomposition [%04X]", $char));
302             if (@dec == 2) {
303             $Map12{"$dec[0];$dec[1]"} = $char;
304             } else {
305             $Map123{"$dec[0];$dec[1];$dec[2]"} = $char;
306             }
307             }
308              
309             #####
310              
311             sub getSyllableType($) {
312 251     251 1 350 my $u = shift;
313             return
314 251 100 100     1344 JamoLIni <= $u && $u <= JamoLFin || $u == JamoLFill ? "L" :
    100 100        
    100 100        
    100 100        
    100          
315             JamoVIni <= $u && $u <= JamoVFin ? "V" :
316             JamoTIni <= $u && $u <= JamoTFin ? "T" :
317             SBase <= $u && $u <= SFinal ?
318             ($u - SBase) % TCount ? "LVT" : "LV" : "NA";
319             }
320              
321             my %Fillers = (
322             "LT" => [ 0x1160, 0x115F, 0x1160 ],
323             "LNA" => [ 0x1160 ],
324             "TV" => [ 0x115F ],
325             "LVTV" => [ 0x115F ],
326             "NAV" => [ 0x115F ],
327             "NAT" => [ 0x115F, 0x1160 ],
328             );
329              
330             sub isStandardForm($) {
331 50     50 1 128 my $str = shift(@_).pack('U*');
332              
333 50         58 my $ptype = 'NA';
334 50         110 foreach my $ch (unpack('U*', $str)) {
335 109         145 my $ctype = getSyllableType($ch);
336 109 100       243 return "" if $Fillers{"$ptype$ctype"};
337 96         133 $ptype = $ctype;
338             }
339 37 100       121 return $ptype eq "L" ? "" : 1;
340             }
341              
342             sub insertFiller($) {
343 51     51 1 1150 my $str = shift(@_).pack('U*');
344 51         67 my $ptype = 'NA';
345 51         60 my(@ret);
346 51         108 foreach my $ch (unpack('U*', $str)) {
347 110         153 my $ctype = getSyllableType($ch);
348             $Fillers{"$ptype$ctype"} and
349 110 100       222 push(@ret, @{ $Fillers{"$ptype$ctype"} });
  13         23  
350 110         153 push @ret, $ch;
351 110         153 $ptype = $ctype;
352             }
353 51 100       138 $ptype eq "L" and push(@ret, @{ $Fillers{"LNA"} });
  4         9  
354 51         190 return pack('U*', @ret);
355             }
356              
357             sub getHangulName ($) {
358 11184     11184 1 32084 my $u = shift;
359 11184 100       15524 return undef unless &$IsS($u);
360 11177         16538 my $sindex = $u - SBase;
361 11177         16226 my $lindex = int( $sindex / NCount);
362 11177         15937 my $vindex = int(($sindex % NCount) / TCount);
363 11177         12938 my $tindex = $sindex % TCount;
364 11177         25716 return "$BlockName$JamoL[$lindex]$JamoV[$vindex]$JamoT[$tindex]";
365             }
366              
367             sub parseHangulName ($) {
368 11205     11205 1 14337 my $arg = shift;
369 11205 100       27298 return undef unless $arg =~ s/$BlockName//o;
370 11196 100       30849 return undef unless $arg =~ /^([^AEIOUWY]*)([AEIOUWY]+)([^AEIOUWY]*)$/;
371             return undef unless exists $CodeL{$1}
372 11193 100 100     42948 && exists $CodeV{$2} && exists $CodeT{$3};
      100        
373 11177         24659 return SBase + $CodeL{$1} * NCount + $CodeV{$2} * TCount + $CodeT{$3};
374             }
375              
376             sub getHangulComposite ($$) {
377 13 100 100 13 1 77 if (&$IsL($_[0]) && &$IsV($_[1])) {
378 2         3 my $lindex = $_[0] - LBase;
379 2         3 my $vindex = $_[1] - VBase;
380 2         7 return (SBase + ($lindex * VCount + $vindex) * TCount);
381             }
382 11 100 100     19 if (&$IsLV($_[0]) && &$IsT($_[1])) {
383 2         7 return($_[0] + $_[1] - TBase);
384             }
385 9         25 return undef;
386             }
387              
388             sub decomposeJamo ($) {
389 274     274 1 6806 my $str = shift(@_).pack('U*');
390 274         370 my(@ret);
391 274         603 foreach my $ch (unpack('U*', $str)) {
392 297 100       764 push @ret, $Decomp{$ch} ? @{ $Decomp{$ch} } : ($ch);
  197         413  
393             }
394 274         801 return pack('U*', @ret);
395             }
396              
397             sub decomposeSyllable ($) {
398 11190     11190 1 42998 my $str = shift(@_).pack('U*');
399 11190         12853 my(@ret);
400 11190         18964 foreach my $ch (unpack('U*', $str)) {
401 11202         15024 my @r = decomposeHangul($ch);
402 11202 100       22027 push @ret, @r ? @r : ($ch);
403             }
404 11190         26025 return pack('U*', @ret);
405             }
406              
407             sub decomposeHangul ($) {
408 22383     22383 1 47528 my $code = shift;
409 22383 100       31279 return unless &$IsS($code);
410 22364         31752 my $sindex = $code - SBase;
411 22364         35238 my $lindex = int( $sindex / NCount);
412 22364         33550 my $vindex = int(($sindex % NCount) / TCount);
413 22364         26130 my $tindex = $sindex % TCount;
414 22364 100       40556 my @ret = (
415             LBase + $lindex,
416             VBase + $vindex,
417             $tindex ? (TBase + $tindex) : (),
418             );
419 22364 100       48011 wantarray ? @ret : pack('U*', @ret);
420             }
421              
422             sub composeJamo ($) {
423 265     265 1 4811 my $str = shift(@_).pack('U*');
424 265         564 my @tmp = unpack('U*', $str);
425 265         598 for (my $i = 0; $i < @tmp; $i++) {
426 271 100       453 next unless &$IsJ($tmp[$i]);
427              
428 264 100 100     1229 if ($tmp[$i + 2] && $Map123{"$tmp[$i];$tmp[$i+1];$tmp[$i+2]"}) {
    100 100        
429 31         74 $tmp[$i] = $Map123{"$tmp[$i];$tmp[$i+1];$tmp[$i+2]"};
430 31         54 $tmp[$i+1] = $tmp[$i+2] = undef;
431 31         64 $i += 2;
432             }
433             elsif ($tmp[$i + 1] && $Map12{"$tmp[$i];$tmp[$i+1]"}) {
434 162         355 $tmp[$i] = $Map12{"$tmp[$i];$tmp[$i+1]"};
435 162         270 $tmp[$i+1] = undef;
436 162         325 $i ++;
437             }
438             }
439 265         995 return pack 'U*', grep defined, @tmp;
440             }
441              
442             sub composeSyllable ($) {
443 22370     22370 1 34007 my $str = shift(@_).pack('U*');
444 22370         26322 my(@ret);
445 22370         39478 foreach my $ch (unpack('U*', $str)) {
446 66310 100 50     117091 push(@ret, $ch) and next unless @ret;
447              
448             # 1. check to see if $ret[-1] is L and $ch is V.
449              
450 43943 100 100     59981 if (&$IsL($ret[-1]) && &$IsV($ch)) {
451 22355         27381 $ret[-1] -= LBase; # LIndex
452 22355         24102 $ch -= VBase; # VIndex
453 22355         29899 $ret[-1] = SBase + ($ret[-1] * VCount + $ch) * TCount;
454 22355         34720 next; # discard $ch
455             }
456              
457             # 2. check to see if $ret[-1] is LV and $ch is T.
458              
459 21588 100 100     31403 if (&$IsLV($ret[-1]) && &$IsT($ch)) {
460 21555         26636 $ret[-1] += $ch - TBase; # + TIndex
461 21555         32211 next; # discard $ch
462             }
463              
464             # 3. just append $ch
465 33         55 push(@ret, $ch);
466             }
467 22370         49082 return pack('U*', @ret);
468             }
469              
470             ##### The below part is common to XS and PP #####
471              
472 9     9 1 151 sub decomposeFull ($) { decomposeJamo(decomposeSyllable(shift)) }
473              
474             sub composeHangul ($) {
475 11189     11189 1 15822 my $ret = composeSyllable(shift);
476 11189 100       24854 wantarray ? unpack('U*', $ret) : $ret;
477             }
478              
479             1;
480             __END__