File Coverage

blib/lib/Lingua/JA/Moji.pm
Criterion Covered Total %
statement 564 637 88.5
branch 160 210 76.1
condition 14 21 66.6
subroutine 93 96 96.8
pod 55 87 63.2
total 886 1051 84.3


line stmt bran cond sub pod time code
1             package Lingua::JA::Moji;
2              
3 25     25   1864252 use warnings;
  25         293  
  25         899  
4 25     25   149 use strict;
  25         49  
  25         508  
5 25     25   16406 use utf8;
  25         406  
  25         134  
6              
7             require Exporter;
8             our @ISA = qw(Exporter);
9              
10             our $VERSION = '0.59';
11              
12 25     25   1875 use Carp 'croak';
  25         52  
  25         1378  
13 25     25   11951 use Convert::Moji qw/make_regex length_one unambiguous/;
  25         50784  
  25         1909  
14 25     25   12133 use JSON::Parse 'read_json';
  25         34127  
  25         45987  
15              
16             our @EXPORT_OK = qw/
17             InHankakuKatakana
18             InKana
19             InWideAscii
20             ascii2wide
21             bad_kanji
22             bracketed2kanji
23             braille2kana
24             circled2kana
25             circled2kanji
26             cleanup_kana
27             cyrillic2katakana
28             hangul2kana
29             hentai2kana
30             hentai2kanji
31             hira2kata
32             hw2katakana
33             is_hiragana
34             is_kana
35             is_romaji
36             is_romaji_semistrict
37             is_romaji_strict
38             is_voiced
39             join_sound_marks
40             kana2braille
41             kana2circled
42             kana2cyrillic
43             kana2hangul
44             kana2hentai
45             kana2hw
46             kana2katakana
47             kana2morse
48             kana2romaji
49             kana_consonant
50             kana_to_large
51             kanji2bracketed
52             kanji2circled
53             kanji2hentai
54             kata2hira
55             katakana2hw
56             katakana2square
57             katakana2syllable
58             morse2kana
59             new2old_kanji
60             nigori_first
61             normalize_romaji
62             old2new_kanji
63             romaji2hiragana
64             romaji2kana
65             romaji_styles
66             romaji_vowel_styles
67             smallize_kana
68             split_sound_marks
69             strip_sound_marks
70             square2katakana
71             wide2ascii
72             yurei_moji
73             /;
74              
75             our %EXPORT_TAGS = (
76             'all' => \@EXPORT_OK,
77             );
78              
79             # Load a specified convertor from the shared directory.
80              
81             sub load_convertor
82             {
83 21     21 0 165 my ($in, $out) = @_;
84 21         75 my $filename = $in."2".$out;
85 21         86 my $file = getdistfile ($filename);
86 21 100 66     847 if (! $file || ! -f $file) {
87 1         265 croak "Could not find distribution file '$filename'";
88             }
89 20         164 my $convertor = Convert::Moji::load_convertor ($file);
90 20         26493 return $convertor;
91             }
92              
93             sub add_boilerplate
94             {
95 16     16 0 48 my ($code, $name) = @_;
96 16         68 $code =<
97             sub convert_$name
98             {
99             my (\$conv,\$input,\$convert_type) = \@_;
100             $code
101             return \$input;
102             }
103             EOSUB
104 16         70 $code .= "\\\&".__PACKAGE__."::convert_$name;";
105 16         48 return $code;
106             }
107              
108             sub ambiguous_reverse
109             {
110 0     0 0 0 my ($table) = @_;
111 0         0 my %inverted;
112 0         0 for (keys %$table) {
113 0         0 my $val = $table->{$_};
114 0         0 push @{$inverted{$val}}, $_;
  0         0  
115             }
116 0         0 return \%inverted;
117             }
118              
119             # Callback
120              
121             sub split_match
122             {
123 0     0 0 0 my ($conv, $input, $convert_type) = @_;
124 0 0       0 if (!$convert_type) {
125 0         0 $convert_type = "all";
126             }
127 0         0 my @input = split '', $input;
128 0         0 my @output;
129 0         0 for (@input) {
130 0         0 my $in = $conv->{out2in}->{$_};
131             # No conversion defined.
132 0 0       0 if (! $in) {
133 0         0 push @output, $_;
134 0         0 next;
135             }
136             # Unambigous case
137 0 0       0 if (@{$in} == 1) {
  0         0  
138 0         0 push @output, $in->[0];
139 0         0 next;
140             }
141 0 0       0 if ($convert_type eq 'all') {
    0          
    0          
142 0         0 push @output, $in;
143             }
144             elsif ($convert_type eq 'first') {
145 0         0 push @output, $in->[0];
146             }
147             elsif ($convert_type eq 'random') {
148 0         0 my $pos = int rand @$in;
149 0         0 push @output, $in->[$pos];
150             }
151             }
152 0         0 return \@output;
153             }
154              
155             sub make_convertors
156             {
157 8     8 0 100 my ($in, $out, $table) = @_;
158 8         25 my $conv = {};
159 8 50       30 if (!$table) {
160 8         36 $table = load_convertor ($in, $out);
161             }
162 8         57 $conv->{in2out} = $table;
163 8         18 my @keys = keys %{$table};
  8         183  
164 8         31 my @values = values %{$table};
  8         145  
165 8         24 my $sub_in2out;
166             my $sub_out2in;
167 8 50       53 if (length_one (@keys)) {
168 8         1122 my $lhs = join '', @keys;
169              
170             # Improvement: one way tr/// for the ambiguous case lhs/rhs only.
171              
172 8 100 66     34 if (length_one (@values) && unambiguous ($table)) {
173             # can use tr///;
174 2         597 my $rhs = join '', @values;
175 2         12 $sub_in2out = "\$input =~ tr/$lhs/$rhs/;";
176 2         11 $sub_out2in = "\$input =~ tr/$rhs/$lhs/;";
177             }
178             else {
179 6         137 $sub_in2out = "\$input =~ s/([$lhs])/\$conv->{in2out}->{\$1}/eg;";
180 6         41 my $rhs = make_regex (@values);
181 6 50       3773 if (unambiguous($conv->{in2out})) {
182 6         2258 my %out2in_table = reverse %{$conv->{in2out}};
  6         205  
183 6         39 $conv->{out2in} = \%out2in_table;
184 6         36 $sub_out2in = "\$input =~ s/($rhs)/\$conv->{out2in}->{\$1}/eg;";
185             }
186             else {
187 0         0 $conv->{out2in} = ambiguous_reverse ($conv->{in2out});
188 0         0 $sub_out2in = "\$input = \$conv->split_match (\$input, \$convert_type);";
189             }
190             }
191             }
192             else {
193 0         0 my $lhs = make_regex (@keys);
194 0         0 $sub_in2out = "\$input =~ s/($lhs)/\$conv->{in2out}->{\$1}/eg;";
195 0         0 my $rhs = make_regex (@values);
196 0 0       0 if (unambiguous($conv->{in2out})) {
197 0         0 my %out2in_table = reverse %{$conv->{in2out}};
  0         0  
198 0         0 $conv->{out2in} = \%out2in_table;
199 0         0 $sub_out2in = " \$input =~ s/($rhs)/\$conv->{out2in}->{\$1}/eg;";
200             }
201             }
202 8         48 $sub_in2out = add_boilerplate ($sub_in2out, "${in}2$out");
203 8     1 0 1855 my $sub1 = eval $sub_in2out;
  1     1 0 4  
  1     1 0 28  
  1     1 0 6  
  1         4  
  1         23  
  1         5  
  1         3  
  1         17  
  3         13  
  1         5  
  1         4  
  1         5  
  7         24  
  1         5  
204 8         34 $conv->{in2out_sub} = $sub1;
205 8 50       44 if ($sub_out2in) {
206 8         41 $sub_out2in = add_boilerplate ($sub_out2in, "${out}2$in");
207 8     1 0 3122 my $sub2 = eval $sub_out2in;
  1     1 0 5  
  1     8 0 13  
  1     7 0 4  
  1         4  
  1         26  
  1         4  
  8         26  
  8         82  
  33         139  
  8         44  
  7         17  
  7         34  
  7         23  
  7         22  
208 8 50       54 if ($@) {
209 0         0 print "Errors are ",$@,"\n";
210 0         0 print "\$sub2 = ",$sub2,"\n";
211             }
212 8         40 $conv->{out2in_sub} = $sub2;
213             }
214 8         25 bless $conv;
215 8         98 return $conv;
216             }
217              
218             sub convert
219             {
220 4     4 0 13 my ($conv, $input) = @_;
221 4         9 return &{$conv->{in2out_sub}}($conv, $input);
  4         127  
222             }
223              
224             sub invert
225             {
226 17     17 0 47 my ($conv, $input, $convert_type) = @_;
227 17         40 return &{$conv->{out2in_sub}}($conv, $input, $convert_type);
  17         413  
228             }
229              
230              
231             # Kana ordered by consonant. Adds two bogus gyous, a "q" gyou for
232             # small vowels and an "xy" gyou for youon (ya, yu, yo), to the usual
233             # ones.
234              
235             my @gyou = (
236             a => [qw/ア イ ウ エ オ/],
237             # Not a real gyou.
238             q => [qw/ァ ィ ゥ ェ ォ/],
239             k => [qw/カ キ ク ケ コ/],
240             g => [qw/ガ ギ グ ゲ ゴ/],
241             s => [qw/サ シ ス セ ソ/],
242             z => [qw/ザ ジ ズ ゼ ゾ/],
243             t => [qw/タ チ ツ テ ト/],
244             d => [qw/ダ ヂ ヅ デ ド/],
245             n => [qw/ナ ニ ヌ ネ ノ/],
246             h => [qw/ハ ヒ フ ヘ ホ/],
247             b => [qw/バ ビ ブ ベ ボ/],
248             p => [qw/パ ピ プ ペ ポ/],
249             m => [qw/マ ミ ム メ モ/],
250             y => [qw/ヤ ユ ヨ/],
251             xy => [qw/ャ ュ ョ/],
252             r => [qw/ラ リ ル レ ロ/],
253             w => [qw/ワ ヰ ヱ ヲ/],
254             v => [qw/ヴ/],
255             );
256              
257             my %gyou = @gyou;
258              
259             # Kana => consonant mapping.
260              
261             my %siin;
262              
263             for my $consonant (keys %gyou) {
264             for my $kana (@{$gyou{$consonant}}) {
265             if ($consonant eq 'a') {
266             $siin{$kana} = '';
267             }
268             else {
269             $siin{$kana} = $consonant;
270             }
271             }
272             }
273              
274             # Vowel => kana mapping.
275              
276             my %dan = (a => [qw/ア カ ガ サ ザ タ ダ ナ ハ バ パ マ ヤ ラ ワ ャ ァ/],
277             i => [qw/イ キ ギ シ ジ チ ヂ ニ ヒ ビ ピ ミ リ ヰ ィ/],
278             u => [qw/ウ ク グ ス ズ ツ ヅ ヌ フ ブ プ ム ユ ル ュ ゥ ヴ/],
279             e => [qw/エ ケ ゲ セ ゼ テ デ ネ ヘ ベ ペ メ レ ヱ ェ/],
280             o => [qw/オ コ ゴ ソ ゾ ト ド ノ ホ ボ ポ モ ヨ ロ ヲ ョ ォ/]);
281              
282             # Kana => vowel mapping
283              
284             my %boin;
285              
286             # List of kana with a certain vowel.
287              
288             my %vowelclass;
289              
290             for my $vowel (keys %dan) {
291             my @kana_list = @{$dan{$vowel}};
292             for my $kana (@kana_list) {
293             $boin{$kana} = $vowel;
294             }
295             $vowelclass{$vowel} = join '', @kana_list;
296             }
297              
298             # Kana gyou which can be preceded by a sokuon (small tsu).
299              
300             # Added d to the list for ウッド
301             # Added z for "badge" etc.
302             # Added g for ドッグ etc.
303              
304             #my @takes_sokuon_gyou = qw/s t k p d z g/;
305             #my @takes_sokuon = (map {@{$gyou{$_}}} @takes_sokuon_gyou);
306             #my $takes_sokuon = join '', @takes_sokuon;
307             #die @takes_sokuon;
308             my $takes_sokuon = 'サシスセソタチツテトカキクケコパピプペポダヂヅデドザジズゼゾガギグゲゴ';
309              
310             # Any kana except ん
311              
312             #my@b4s;push@b4s,@{$gyou{$_}}for sort keys%gyou;@b4s=grep!/ん/,@b4s;die join'',@b4s;
313              
314             my $before_sokuon = 'ヤユヨナニヌネノャュョガギグゲゴダヂヅデドカキクケコヴラリルレロワヰヱヲバビブベボタチツテトアイウエオパピプペポサシスセソァィゥェォマミムメモハヒフヘホザジズゼゾ';
315              
316             # N
317              
318             # Kana gyou which need an apostrophe when preceded by an "n" kana.
319              
320             my $need_apostrophe = join '', (map {@{$gyou{$_}}} qw/a y/);
321              
322             # Gyou which turn an "n" into an "m" in some kinds of romanization
323              
324             my $need_m = join '', (map {@{$gyou{$_}}} qw/p b m/);
325              
326             # YOUON
327              
328             # Small ya, yu, yo.
329              
330             my $youon = join '', (@{$gyou{xy}});
331             my %youon = qw/a ャ u ュ o ョ ou ョ/;
332              
333             # HEPBURN
334              
335             # Hepburn irregular romanization
336              
337             my %hepburn = qw/シ sh ツ ts チ ch ジ j ヅ z ヂ j フ f/;
338              
339             # Hepburn map from vowel to list of kana with that vowel.
340              
341             my %hep_vowel = (i => 'シチジヂ', u => 'ヅツフ');
342             my $hep_list = join '', keys %hepburn;
343              
344             # Hepburn irregular romanization of ッチ as "tch".
345              
346             my %hepburn_sokuon = qw/チ t/;
347             my $hep_sok_list = join '', keys %hepburn_sokuon;
348              
349             # Hepburn variants for the youon case.
350              
351             my %hepburn_youon = qw/シ sh チ ch ジ j ヂ j/;
352             my $is_hepburn_youon = join '', keys %hepburn_youon;
353              
354             # Kunrei romanization
355              
356             my %kunrei = qw/ヅ z ヂ z/;
357              
358             my $kun_list = join '', keys %kunrei;
359              
360             my %kunrei_youon = qw/ヂ z/;
361             my $is_kunrei_youon = join '', keys %kunrei_youon;
362              
363             # LONG VOWELS
364              
365             # Long vowels, another bugbear of Japanese romanization.
366              
367             my @aiueo = qw/a i u e o ou/;
368              
369             # Various ways to display the long vowels.
370              
371             my %chouonhyouki;
372             @{$chouonhyouki{circumflex}}{@aiueo} = qw/â î û ê ô ô/;
373             @{$chouonhyouki{macron}}{@aiueo} = qw/ā ii ū ē ō ō/;
374             @{$chouonhyouki{wapuro}}{@aiueo} = qw/aa ii uu ee oo ou/;
375             @{$chouonhyouki{passport}}{@aiueo} = qw/a i u e oh oh/;
376             @{$chouonhyouki{none}}{@aiueo} = qw/a ii u e o o/;
377              
378 25     25   246 my $vowel_re = qr/[aeiouâêîôûāēōū]/i;
  25         64  
  25         344  
379             my $no_u_vowel_re = qr/[aeioâêîôāēō]/i;
380             my $u_re = qr/[uūû]/i;
381              
382             sub kana2romaji
383             {
384 185     185 1 7842 my ($input, $options) = @_;
385 185         443 $input = kana2katakana ($input);
386 185 100       427 if (! $options) {
387 9         18 $options = {};
388             }
389             # Parse the options
390 185         561 my $kunrei;
391             my $hepburn;
392 185         0 my $passport;
393 185         0 my $common;
394 185 100       439 if ($options->{style}) {
395 26         48 my $style = $options->{style};
396 26 100       64 if ($style eq 'kunrei') {
397 1         3 $kunrei = 1;
398             }
399 26 50       51 if ($style eq 'passport') {
400 0         0 $passport = 1;
401             }
402 26 100       56 if ($style eq 'hepburn') {
403 18         29 $hepburn = 1;
404             }
405 26 100       51 if ($style eq 'common') {
406 6         9 $hepburn = 1;
407 6         13 $common = 1;
408             }
409 26 50 66     185 if (!$kunrei && !$passport && !$hepburn && $style ne "nihon" &&
      66        
      33        
410             $style ne 'nippon') {
411 0         0 croak "Unknown romanization style '$options->{style}'";
412             }
413             }
414 185         235 my $wapuro;
415 185 100       368 if ($options->{wapuro}) {
416 158         258 $wapuro = 1;
417             }
418 185         281 my $use_m = 0;
419 185 100 66     663 if ($hepburn || $passport) {
420 24         32 $use_m = 1;
421             }
422 185 100       385 if (defined $options->{use_m}) {
423             $use_m = $options->{use_m}
424 2         5 }
425 185         277 my $ve_type = 'circumflex'; # type of vowel extension to use.
426 185 100       337 if ($hepburn) {
427 24         54 $ve_type = 'macron';
428             }
429 185 100       334 if ($wapuro) {
430 158         229 $ve_type = 'wapuro';
431             }
432 185 50       341 if ($passport) {
433 0         0 $hepburn = 1;
434 0         0 $ve_type = 'passport';
435 0         0 $use_m = 1;
436             }
437 185 100       335 if ($options->{ve_type}) {
438 10         149 $ve_type = $options->{ve_type};
439             }
440 185 50       417 if (! $chouonhyouki{$ve_type}) {
441 0         0 print STDERR "Warning: unrecognized long vowel type '$ve_type'\n";
442 0         0 $ve_type = 'circumflex';
443             }
444 185         243 my $wo;
445 185 100       365 if ($options->{wo}) {
446 1         2 $wo = 1;
447             }
448             # Start of conversion
449              
450             # 撥音 (ん)
451 185         915 $input =~ s/ン(?=[$need_apostrophe])/n\'/g;
452 185 100       430 if ($use_m) {
453 22         135 $input =~ s/ン(?=[$need_m])/m/g;
454             }
455 185         372 $input =~ s/ン/n/g;
456             # 促音 (っ)
457 185 100       343 if ($hepburn) {
458 24         101 $input =~ s/ッ([$hep_sok_list])/$hepburn_sokuon{$1}$1/g;
459             }
460 185         753 $input =~ s/ッ([$takes_sokuon])/$siin{$1}$1/g;
461 185 100       454 if ($ve_type eq 'wapuro') {
462 167         286 $input =~ s/ー/-/g;
463             }
464 185 100       364 if ($ve_type eq 'none') {
465 1         5 $input =~ s/ー//g;
466             }
467             # 長音 (ー)
468 185         354 for my $vowel (@aiueo) {
469 1110         2657 my $ve = $chouonhyouki{$ve_type}->{$vowel};
470 1110         1547 my $vowelclass;
471             my $vowel_kana;
472 1110 100       1987 if ($vowel eq 'ou') {
473 185         322 $vowelclass = $vowelclass{o};
474 185         254 $vowel_kana = 'ウ';
475             }
476             else {
477 925         1316 $vowelclass = $vowelclass{$vowel};
478 925         1489 $vowel_kana = $dan{$vowel}->[0];
479             }
480             # 長音 (ー) + 拗音 (きょ)
481 1110         1631 my $y = $youon{$vowel};
482 1110 100       1943 if ($y) {
483 740 100       1169 if ($hepburn) {
484 96         1427 $input =~ s/([$is_hepburn_youon])${y}[ー$vowel_kana]/$hepburn_youon{$1}$ve/g;
485             }
486 740         12736 $input =~ s/([$vowelclass{i}])${y}[ー$vowel_kana]/$siin{$1}y$ve/g;
487             }
488 1110 100 100     2811 if ($hepburn && $hep_vowel{$vowel}) {
489 48         587 $input =~ s/([$hep_vowel{$vowel}])[ー$vowel_kana]/$hepburn{$1}$ve/g;
490             }
491 1110         11600 $input =~ s/${vowel_kana}[ー$vowel_kana]/$ve/g;
492 1110         16913 $input =~ s/([$vowelclass])[ー$vowel_kana]/$siin{$1}$ve/g;
493             }
494             # 拗音 (きょ)
495 185 100       647 if ($hepburn) {
    100          
496 24         130 $input =~ s/([$is_hepburn_youon])([$youon])/$hepburn_youon{$1}$boin{$2}/g;
497             }
498             elsif ($kunrei) {
499 1         17 $input =~ s/([$is_kunrei_youon])([$youon])/$kunrei_youon{$1}y$boin{$2}/g;
500             }
501 185         965 $input =~ s/([$vowelclass{i}])([$youon])/$siin{$1}y$boin{$2}/g;
502             # その他
503 185 100       430 if ($wo) {
504 1         7 $input =~ s/ヲ/wo/g;
505 1         6 $input =~ s/([アイウエオ])/$boin{$1}/g;
506             }
507             else {
508 184         627 $input =~ s/([アイウエオヲ])/$boin{$1}/g;
509             }
510 185         860 $input =~ s/([ァィゥェォ])/q$boin{$1}/g;
511 185         415 $input =~ s/ヮ/xwa/g;
512 185 100       413 if ($hepburn) {
    100          
513 24         173 $input =~ s/([$hep_list])/$hepburn{$1}$boin{$1}/g;
514             }
515             elsif ($kunrei) {
516 1         15 $input =~ s/([$kun_list])/$kunrei{$1}$boin{$1}/g;
517             }
518 185         1262 $input =~ s/([カ-ヂツ-ヱヴ])/$siin{$1}$boin{$1}/g;
519 185         1372 $input =~ s/q($vowel_re)/x$1/g;
520 185 100       499 if ($common) {
521             # Convert kana + small vowel into thingumibob, if there is a
522             # consonant before.
523 6         255 $input =~ s/([^\Waiueo])$vowel_re[x]($vowel_re)/$1$2/;
524             # Convert u + small kana into w + vowel
525 6         151 $input =~ s/($vowel_re|\b)ux([iue])/$1w$2/i;
526             }
527 185         1059 return $input;
528             }
529              
530             sub romaji2hiragana
531             {
532 53     53 1 1533 my ($input, $options) = @_;
533 53 100       152 if (! $options) {
534 50         90 $options = {};
535             }
536 53         206 my $katakana = romaji2kana ($input, {wapuro => 1, %$options});
537 53         158 return kata2hira ($katakana);
538             }
539              
540             sub romaji_styles
541             {
542 1     1 1 5 my ($check) = @_;
543 1         13 my @styles = (
544             {
545             abbrev => "hepburn",
546             full_name => "Hepburn",
547             }, {
548             abbrev => 'nihon',
549             full_name => 'Nihon-shiki',
550             }, {
551             abbrev => 'kunrei',
552             full_name => 'Kunrei-shiki',
553             }, {
554             abbrev => 'common',
555             full_name => 'common',
556             });
557 1 50       4 if (! defined ($check)) {
558 0         0 return (@styles);
559             }
560             else {
561 1         4 for my $style (@styles) {
562 2 100       7 if ($check eq $style->{abbrev}) {
563 1         8 return 1;
564             }
565             }
566 0         0 return;
567             }
568             }
569              
570             my %styles = (
571             macron => 1,
572             circumflex => 1,
573             wapuro => 1,
574             passport => 1,
575             none => 1,
576             );
577              
578             # Check whether this vowel style is allowed.
579              
580             sub romaji_vowel_styles
581             {
582 1     1 1 4 my ($check) = @_;
583 1 50       5 if (! defined ($check)) {
584 1         9 return [keys %styles];
585             }
586             else {
587 0         0 return $styles{$check};
588             }
589             }
590              
591             my $romaji2katakana;
592             my $romaji_regex;
593              
594             my %longvowels;
595             @longvowels{qw/â î û ê ô/} = qw/aー iー uー eー oー/;
596             @longvowels{qw/ā ī ū ē ō/} = qw/aー iー uー eー oー/;
597             my $longvowels = join '|', sort {length($a)<=>length($b)} keys %longvowels;
598              
599             sub romaji2kana
600             {
601 363     363 1 2433 my ($input, $options) = @_;
602 363 100       819 if (! defined $romaji2katakana) {
603 8         40 $romaji2katakana = load_convertor ('romaji', 'katakana');
604 8         442 $romaji_regex = make_regex (keys %$romaji2katakana);
605             }
606             # Set to true if we want long o to be オウ rather than オー
607 363         13946 my $wapuro;
608             # Set to true if we want gumma to be ぐっま and onnna to be おんな.
609             my $ime;
610 363 100       771 if ($options) {
611 269         448 $wapuro = $options->{wapuro};
612 269         426 $ime = $options->{ime};
613             }
614              
615 363 50       685 if (! defined $input) {
616 0         0 return;
617             }
618 363         748 $input = lc $input;
619             # Deal with long vowels
620 363 100       12868 if ($wapuro) {
621 269         597 $input =~ s/[âā]/aa/g;
622 269         572 $input =~ s/[îī]/ii/g;
623 269         376 $input =~ s/[êē]/ee/g;
624 269         383 $input =~ s/[ûū]/uu/g;
625 269         394 $input =~ s/[ôō]/ou/g;
626             }
627             else {
628 94         718 $input =~ s/($longvowels)/$longvowels{$1}/g;
629             # Doubled vowels to chouon
630 94         303 $input =~ s/([aiueo])\1/$1ー/g;
631             }
632             # Deal with double consonants
633             # danna -> だんな
634 363 100       600 if ($ime) {
635             # IME romaji rules:
636             # Allow double n for ん:
637             # gunnma -> グンマ, dannna -> ダンナ
638 3         16 $input =~ s/n{1,2}(?=[nm][aiueo])/ン/g;
639             # Substitute sokuon for mm + vowel:
640             # gumma -> グッマ
641 3         14 $input =~ s/m(?=[nm][aiueo])/ッ/g;
642             }
643             else {
644             # Usual romaji rules: Don't allow double n for ん, change
645             # gumma to グンマ.
646 360         646 $input =~ s/[nm](?=[nm][aiueo])/ン/g;
647             }
648             # shimbun -> しんぶん
649 363         724 $input =~ s/m(?=[pb]y?[aiueo])/ン/g;
650             # tcha -> っちゃ
651 363         709 $input =~ s/t(?=ch[aiueo])/ッ/g;
652             # ccha -> っちゃ
653 363         574 $input =~ s/c(?=ch[aiueo])/ッ/g;
654             # kkya -> っきゃ etc.
655 363         1328 $input =~ s/([kstfhmrgzdbpjqvwy])(?=\1y?[aiueo])/ッ/g;
656             # kkya -> っきゃ etc.
657 363         689 $input =~ s/ttsu/ッツ/g;
658             # xtsu -> っ
659 363         536 $input =~ s/xtsu/ッ/g;
660             # ssha -> っしゃ
661 363         601 $input =~ s/([s])(?=\1h[aiueo])/ッ/g;
662             # Passport romaji,
663             # oh{consonant} -> oo
664 363 100       723 if (! $ime) {
665             # IMEs do not recognize passport romaji.
666 360 100       612 if ($wapuro) {
667 266         436 $input =~ s/oh(?=[ksthmrgzdbp])/オウ/g;
668             }
669             else {
670 94         169 $input =~ s/oh(?=[ksthmrgzdbp])/オー/g;
671             }
672             }
673             # All the special cases have been dealt with, now substitute all
674             # the kana.
675 363         8882 $input =~ s/($romaji_regex)/$romaji2katakana->{$1}/g;
676 363         1618 return $input;
677             }
678              
679             sub is_voiced
680             {
681 2     2 1 6 my ($sound) = @_;
682 2 50       8 if (is_kana ($sound)) {
    0          
683 2         9 $sound = kana2romaji ($sound);
684             }
685             elsif (my $romaji = is_romaji ($sound)) {
686             # Normalize to nihon shiki so that we don't have to worry
687             # about ch, j, ts, etc. at the start of the sound.
688 0         0 $sound = $romaji;
689             }
690 2 100       9 if ($sound =~ /^[aiueogzbpmnry]/) {
691 1         6 return 1;
692             }
693             else {
694 1         7 return undef;
695             }
696             }
697              
698             sub is_romaji
699             {
700 218     218 1 4426 my ($romaji) = @_;
701 218 50       628 if (length ($romaji) == 0) {
702 0         0 return undef;
703             }
704             # Test that $romaji contains only characters which may be
705             # romanized Japanese.
706 218 100       1001 if ($romaji =~ /[^\sa-zāīūēōâîûêô'-]|^-/i) {
707 4         23 return undef;
708             }
709 214         689 my $kana = romaji2kana ($romaji, {wapuro => 1});
710 214 100       1204 if ($kana =~ /^[ア-ンッー\s]+$/) {
711 143         482 return kana2romaji ($kana, {wapuro => 1});
712             }
713 71         239 return undef;
714             }
715              
716              
717             sub is_romaji_semistrict
718             {
719 94     94 1 196 my ($romaji) = @_;
720 94 100       213 if (! is_romaji ($romaji)) {
721 34         146 return undef;
722             }
723 60 100       867 if ($romaji =~ /
724             # Don't allow small vowels, small tsu, or fya,
725             # fye etc.
726             (fy|l|x|v)y?($vowel_re|ts?u|wa|ka|ke)
727             |
728             # Don't allow hyi, hye, yi, ye.
729             [zh]?y[ieêîē]
730             |
731             # Don't allow tye
732             ty[eêē]
733             |
734             # Don't allow wh-, kw-, gw-, dh-, etc.
735             (wh|kw|gw|dh|thy)$vowel_re
736             |
737             # Don't allow "t'i"
738             [dt]'(i|y?$u_re)
739             |
740             # Don't allow dwu, twu
741             [dt](w$u_re)
742             |
743             hwy$u_re
744             |
745             # Don't allow "wi" or "we".
746             w(i|e)
747             |
748             # Don't allow some non-Japanese double consonants.
749             (?:rr|yy)
750             |
751             # Don't allow 'thi'
752             thi
753             /ix) {
754 51         293 return undef;
755             }
756 9         64 return 1;
757             }
758              
759             sub is_romaji_strict
760             {
761 107     107 1 29576 my ($romaji) = @_;
762 107         239 my $canonical = is_romaji ($romaji);
763 107 100       230 if (! $canonical) {
764 34         158 return undef;
765             }
766 73         135 my $kana = romaji2kana ($romaji);
767 73 100       303 if ($kana =~ m!
768             # Don't allow tanggono
769             ンッ
770             |
771             # Don't allow "nmichi".
772             ^ン
773             |
774             # Don't allow ffun etc.
775             ^ッ
776             !x) {
777 6         38 return undef;
778             }
779 67 100       1289 if ($romaji =~ m!
780             (fy|l|x|v)y?($vowel_re|ts?u|wa|ka|ke)
781             |
782             # Don't allow hyi, hye, yi, ye.
783             [zh]?y[ieêîē]
784             |
785             # Don't allow tye
786             ty[eêē]
787             |
788             # Don't allow wh-, kw-, gw-, dh-, etc.
789             (wh|kw|gw|dh|thy)$vowel_re
790             |
791             # Don't allow tsa, tsi, tse, tso, fa, fe, fi, fo.
792             (ts|f)$no_u_vowel_re
793             |
794             # Don't allow "t'i"
795             [dt]'(i|y?$u_re)
796             |
797             # Don't allow dwu, twu
798             [dt](w$u_re)
799             |
800             hwy$u_re
801             |
802             # Don't allow "wi" or "we".
803             w(i|e)
804             |
805             # Don't allow 'je', 'che', 'she'
806             (?:[cs]h|j)e
807             |
808             # Don't allow some non-Japanese double consonants.
809             (?:rr|yy)
810             |
811             # Don't allow 'thi'/'thu'
812             th[iu]
813             |
814             # Don't allow 'johann'
815             nn$
816             |
817             # Don't allow 'ridzuan' etc.
818             dz
819             |
820             # Qs are out.
821             q
822             |
823             # Double ws, hs, etc. are out
824             ww|hh|bb
825             |
826             # This is allowed by IMEs as "ちゃ" etc.
827             cy
828             !ix) {
829 64         421 return undef;
830             }
831 3         43 return $canonical;
832             }
833              
834             sub hira2kata
835             {
836 203     203 1 930 my (@input) = @_;
837 203 50       522 if (!@input) {
838 0         0 return;
839             }
840 203         424 for (@input) {
841 203 50       419 if ($_) {
842 203         1035 tr/ぁ-んゔ/ァ-ンヴ/;
843             }
844             }
845 203 50       847 return wantarray ? @input : "@input";
846             }
847              
848             sub kata2hira
849             {
850 57     57 1 163 my (@input) = @_;
851 57         120 for (@input) {tr/ァ-ンヴ/ぁ-んゔ/}
  57         314  
852 57 50       457 return wantarray ? @input : "@input";
853             }
854              
855             # Make the list of dakuon stuff.
856              
857             sub make_dak_list
858             {
859 0     0 0 0 my @dak_list;
860 0         0 for (@_) {
861 0         0 push @dak_list, @{$gyou{$_}};
  0         0  
862 0         0 push @dak_list, hira2kata (@{$gyou{$_}});
  0         0  
863             }
864 0         0 return @dak_list;
865             }
866              
867             sub load_kana2hw2
868             {
869 3     3 0 20 my $conv = Convert::Moji->new (["oneway", "tr", "あ-ん", "ア-ン"],
870             ["file",
871             getdistfile ("katakana2hw_katakana")]);
872 3         7881 return $conv;
873             }
874              
875             my $kata2hw;
876              
877             sub make_kata2hw
878             {
879 1 50   1 0 5 if (!$kata2hw) {
880 0         0 $kata2hw = make_convertors ('katakana','hw_katakana');
881             }
882             }
883              
884             my $kana2hw;
885              
886             sub kana2hw
887             {
888 3     3 1 448 my ($input) = @_;
889 3 50       14 if (! $kana2hw) {
890 3         10 $kana2hw = load_kana2hw2 ();
891             }
892 3         17 return $kana2hw->convert ($input);
893             }
894              
895             sub katakana2hw
896             {
897 1     1 1 311 my ($input) = @_;
898 1         4 make_kata2hw ();
899 1         3 return $kata2hw->convert ($input);
900             }
901              
902             sub hw2katakana
903             {
904 8     8 1 2952 my ($input) = @_;
905 8 100       32 if (!$kata2hw) {
906 5         24 $kata2hw = make_convertors ('katakana','hw_katakana');
907             }
908 8         36 return $kata2hw->invert ($input);
909             }
910              
911             sub InHankakuKatakana
912             {
913 13     13 1 17822 return <<'END';
914             +utf8::Katakana
915             &utf8::InHalfwidthAndFullwidthForms
916             END
917             }
918              
919             # The two lists in wide2ascii and ascii2wide have exactly the same
920             # length.
921             #
922             # The warnings produced by Perl versions later than 22 are bugs in
923             # Perl:
924             #
925             # https://rt.perl.org/Public/Bug/Display.html?id=125493
926             #
927             # To save problems for users, switch off warnings in these routines.
928             #
929             # I have no idea what command to use to switch off just the
930             # "Replacement list is longer than search list" warning and leave the
931             # others intact.
932              
933 25     25   810312 no warnings 'misc';
  25         63  
  25         2646  
934              
935             sub wide2ascii
936             {
937 4     4 1 1264 my ($input) = @_;
938 4         221 $input =~ tr/\x{3000}\x{FF01}-\x{FF5E}/ -~/;
939 4         15 return $input;
940             }
941              
942             sub ascii2wide
943             {
944 2     2 1 1089 my ($input) = @_;
945 2         62 $input =~ tr/ -~/\x{3000}\x{FF01}-\x{FF5E}/;
946 2         7 return $input;
947             }
948              
949 25     25   73878 use warnings;
  25         61  
  25         3297  
950              
951             sub InWideAscii
952             {
953 4     4 1 17810 return <<'END';
954             FF01 FF5E
955             3000
956             END
957             }
958              
959             my $kana2morse;
960              
961             sub load_kana2morse
962             {
963 2 100   2 0 10 if (!$kana2morse) {
964 1         4 $kana2morse = make_convertors ('katakana', 'morse');
965             }
966             }
967              
968             sub kana2morse
969             {
970 1     1 1 600 my ($input) = @_;
971 1         5 load_kana2morse;
972 1         5 $input = hira2kata ($input);
973 1         11 $input =~ tr/ァィゥェォャュョッ/アイウエオヤユヨツ/;
974 1         6 $input = split_sound_marks ($input);
975 1         7 $input = join ' ', (split '', $input);
976 1         6 $input = $kana2morse->convert ($input);
977 1         4 return $input;
978             }
979              
980              
981             sub getdistfile
982             {
983 31     31 0 88 my ($filename) = @_;
984 31         76 my $dir = __FILE__;
985 31         221 $dir =~ s!\.pm$!/!;
986 31         113 my $file = "$dir$filename.txt";
987 31         155 return $file;
988             }
989              
990             sub morse2kana
991             {
992 1     1 1 482 my ($input) = @_;
993 1         4 load_kana2morse;
994 1         8 my @input = split ' ',$input;
995 1         3 for (@input) {
996 7         17 $_ = $kana2morse->invert ($_);
997             }
998 1         4 $input = join '', @input;
999 1         6 $input = join_sound_marks ($input);
1000 1         6 return $input;
1001             }
1002              
1003             my $kana2braille;
1004              
1005             sub load_kana2braille
1006             {
1007 2 100   2 0 10 if (!$kana2braille) {
1008 1         4 $kana2braille = make_convertors ('katakana', 'braille');
1009             }
1010             }
1011              
1012             my %nippon2kana;
1013              
1014             for my $k (keys %gyou) {
1015             for my $ar (@{$gyou{$k}}) {
1016             my $vowel = $boin{$ar};
1017             my $nippon = $k.$vowel;
1018             $nippon2kana{$nippon} = $ar;
1019             }
1020             }
1021              
1022             sub is_kana
1023             {
1024 7     7 1 377 my ($may_be_kana) = @_;
1025 7 100       68 if ($may_be_kana =~ /^[あ-んア-ン]+$/) {
1026 5         24 return 1;
1027             }
1028 2         10 return;
1029             }
1030              
1031             sub is_hiragana
1032             {
1033 2     2 1 5 my ($may_be_kana) = @_;
1034 2 100       20 if ($may_be_kana =~ /^[あ-ん]+$/) {
1035 1         7 return 1;
1036             }
1037 1         5 return;
1038             }
1039              
1040             my %daku2not = (qw/
1041             が か
1042             ぎ き
1043             ぐ く
1044             げ け
1045             ご こ
1046             だ た
1047             ぢ ち
1048             づ つ
1049             で て
1050             ど と
1051             ざ さ
1052             じ し
1053             ず す
1054             ぜ せ
1055             ぞ そ
1056             ば は
1057             び ひ
1058             ぶ ふ
1059             べ へ
1060             ぼ ほ
1061             ガ カ
1062             ギ キ
1063             グ ク
1064             ゲ ケ
1065             ゴ コ
1066             ダ タ
1067             ヂ チ
1068             ヅ ツ
1069             デ テ
1070             ド ト
1071             ザ サ
1072             ジ シ
1073             ズ ス
1074             ゼ セ
1075             ゾ ソ
1076             バ ハ
1077             ビ ヒ
1078             ブ フ
1079             ベ ヘ
1080             ボ ホ
1081             /);
1082              
1083             my %not2daku = reverse %daku2not;
1084              
1085             my $daku = qr![がぎぐげごだぢづでどざじずぜぞばびぶべぼガギグゲゴダヂヅデドザジズゼゾバビブベボ]!;
1086              
1087             my $nodaku = qr![かきくけこたしつてとさしすせそはひふへほカキクケコタシツテトサシスセソハヒフヘホ]!;
1088              
1089             my %handaku2not = (qw!
1090             ぱ は
1091             ぴ ひ
1092             ぷ ふ
1093             ぺ へ
1094             ぽ ほ
1095             パ ハ
1096             ピ ヒ
1097             プ フ
1098             ペ ヘ
1099             ポ ホ
1100             !);
1101              
1102             my %not2handaku = reverse %handaku2not;
1103              
1104             my $handaku = qr![ぱぴぷぺぽパピプペポ]!;
1105              
1106             my $nohandaku = qr![はひふへほハヒフヘホ]!;
1107              
1108             sub join_sound_marks
1109             {
1110 205     205 1 493 my ($input) = @_;
1111 205         1659 $input =~ s!($nohandaku)(゚|゜)!$not2handaku{$1}!g;
1112 205         1322 $input =~ s!($nodaku)(゙|゛)!$not2daku{$1}!g;
1113             # Remove strays.
1114 205         534 $input =~ s![゙゛゚゜]!!g;
1115 205         523 return $input;
1116             }
1117              
1118             sub split_sound_marks
1119             {
1120 7     7 1 46 my ($input) = @_;
1121 7         123 $input =~ s!($handaku)!$handaku2not{$1}゜!g;
1122 7         114 $input =~ s!($daku)!$daku2not{$1}゛!g;
1123 7         31 return $input;
1124             }
1125              
1126             sub strip_sound_marks
1127             {
1128 2     2 1 5 my ($input) = @_;
1129 2         40 $input =~ s!($handaku)!$handaku2not{$1}!g;
1130 2         31 $input =~ s!($daku)!$daku2not{$1}!g;
1131 2         12 return $input;
1132             }
1133              
1134             sub kana2katakana
1135             {
1136 200     200 1 652 my ($input) = @_;
1137 200         437 $input = join_sound_marks ($input);
1138 200         566 $input = hira2kata($input);
1139 200 100       918 if ($input =~ /\p{InHankakuKatakana}/) {
1140 2         10 $input = hw2katakana($input);
1141             }
1142 200         9911 return $input;
1143             }
1144              
1145             sub kana2braille
1146             {
1147 1     1 1 317 my ($input) = @_;
1148 1         6 load_kana2braille;
1149 1         5 $input = kana2katakana ($input);
1150 1         5 $input = split_sound_marks ($input);
1151 1         7 $input =~ s/([キシチヒ])゛([ャュョ])/'⠘'.$nippon2kana{$siin{$1}.$boin{$2}}/eg;
  0         0  
1152 1         4 $input =~ s/(ヒ)゜([ャュョ])/'⠨'.$nippon2kana{$siin{$1}.$boin{$2}}/eg;
  0         0  
1153 1         7 $input =~ s/([キシチニヒミリ])([ャュョ])/'⠈'.$nippon2kana{$siin{$1}.$boin{$2}}/eg;
  1         11  
1154 1         43 $input =~ s/([$vowelclass{o}])ウ/$1ー/g;
1155 1         7 $input = $kana2braille->convert ($input);
1156 1         11 $input =~ s/(.)([⠐⠠])/$2$1/g;
1157 1         6 return $input;
1158             }
1159              
1160             sub braille2kana
1161             {
1162 1     1 1 514 my ($input) = @_;
1163 1         6 load_kana2braille;
1164 1         14 $input =~ s/([⠐⠠])(.)/$2$1/g;
1165 1         5 $input = $kana2braille->invert ($input);
1166 1         4 $input =~ s/⠘(.)/$nippon2kana{$siin{$1}.'i'}.'゛'.$youon{$boin{$1}}/eg;
  0         0  
1167 1         4 $input =~ s/⠨(.)/$nippon2kana{$siin{$1}.'i'}.'゜'.$youon{$boin{$1}}/eg;
  0         0  
1168 1         7 $input =~ s/⠈(.)/$nippon2kana{$siin{$1}.'i'}.$youon{$boin{$1}}/eg;
  1         8  
1169 1         7 $input = join_sound_marks ($input);
1170 1         4 return $input;
1171             }
1172              
1173             my $circled_conv;
1174              
1175             sub load_circled_conv
1176             {
1177 2 100   2 0 11 if (!$circled_conv) {
1178 1         7 $circled_conv = make_convertors ("katakana", "circled");
1179             }
1180             }
1181              
1182             sub kana2circled
1183             {
1184 1     1 1 3 my ($input) = @_;
1185 1         5 $input = kana2katakana($input);
1186 1         10 $input = split_sound_marks ($input);
1187 1         5 load_circled_conv;
1188 1         7 $input = $circled_conv->convert ($input);
1189 1         6 return $input;
1190             }
1191              
1192             sub circled2kana
1193             {
1194 1     1 1 612 my ($input) = @_;
1195 1         5 load_circled_conv;
1196 1         5 $input = $circled_conv->invert ($input);
1197 1         5 $input = join_sound_marks ($input);
1198 1         6 return $input;
1199             }
1200              
1201             sub normalize_romaji
1202             {
1203 2     2 1 533 my ($romaji) = @_;
1204 2         15 my $kana = romaji2kana ($romaji, {wapuro => 1});
1205 2         13 $kana =~ s/[っッ]/xtu/g;
1206 2         15 my $romaji_out = kana2romaji ($kana, {ve_type => 'wapuro'});
1207             }
1208              
1209             my $new2old_kanji;
1210              
1211             sub load_new2old_kanji
1212             {
1213 1     1 0 4 $new2old_kanji = Convert::Moji->new (
1214             ['file', getdistfile ('new_kanji2old_kanji')],
1215             );
1216             }
1217              
1218             sub new2old_kanji
1219             {
1220 1     1 1 520 my ($new_kanji) = @_;
1221 1 50       5 if (! $new2old_kanji) {
1222 0         0 load_new2old_kanji ();
1223             }
1224 1         5 my $old_kanji = $new2old_kanji->convert ($new_kanji);
1225 1         119 return $old_kanji;
1226             }
1227              
1228             sub old2new_kanji
1229             {
1230 1     1 1 88 my ($old_kanji) = @_;
1231 1 50       4 if (! $new2old_kanji) {
1232 1         3 load_new2old_kanji ();
1233             }
1234 1         5070 my $new_kanji = $new2old_kanji->invert ($old_kanji);
1235 1         151 return $new_kanji;
1236             }
1237              
1238             my $katakana2cyrillic;
1239              
1240             sub load_katakana2cyrillic
1241             {
1242 1     1 0 6 $katakana2cyrillic = Convert::Moji->new (['file', getdistfile ('katakana2cyrillic')]);
1243             }
1244              
1245             sub kana2cyrillic
1246             {
1247 4     4 1 1137 my ($kana) = @_;
1248 4         18 my $katakana = kana2katakana ($kana);
1249 4         31 $katakana =~ s/ン([アイウエオヤユヨ])/ンъ$1/g;
1250 4 100       15 if (! $katakana2cyrillic) {
1251 1         4 load_katakana2cyrillic ();
1252             }
1253 4         3486 my $cyrillic = $katakana2cyrillic->convert ($katakana);
1254 4         584 $cyrillic =~ s/н([пбм])/м$1/g;
1255 4         15 return $cyrillic;
1256             }
1257              
1258             sub cyrillic2katakana
1259             {
1260 6     6 1 1947 my ($cyrillic) = @_;
1261             # Convert the Cyrillic letters to lower case versions of the
1262             # letters. This table of conversions was made from the one in
1263             # Wikipedia at
1264             # using Emacs, the revision being
1265             # .
1266             # I do not know if it covers the alphabets perfectly.
1267 6         55 $cyrillic =~ tr/АБВГДЕЖЗИЙIКЛМНОПРСТУФХЦЧШЩЬЮЯ/абвгдежзийiклмнопрстуфхцчшщьюя/;
1268 6 50       21 if (! $katakana2cyrillic) {
1269 0         0 load_katakana2cyrillic ();
1270             }
1271 6         19 my $katakana = $katakana2cyrillic->invert ($cyrillic);
1272 6         625 $katakana =~ s/м/ン/g;
1273 6         28 $katakana =~ s/ンъ([アイウエオヤユヨ])/ン$1/g;
1274 6         18 return $katakana;
1275             }
1276              
1277             my $first2hangul;
1278             my $rest2hangul;
1279              
1280             my $first2hangul_re;
1281             my $rest2hangul_re;
1282              
1283             sub load_kana2hangul
1284             {
1285 2     2 0 7 $first2hangul = load_convertor ('first', 'hangul');
1286 2         6 $rest2hangul = load_convertor ('rest', 'hangul');
1287 2         37 $first2hangul_re = '\b' . make_regex (keys %$first2hangul);
1288 2         1218 $rest2hangul_re = make_regex (keys %$rest2hangul);
1289             }
1290              
1291             sub kana2hangul
1292             {
1293 2     2 1 1515 my ($kana) = @_;
1294 2         6 my $katakana = kana2katakana ($kana);
1295 2 100       7 if (! $first2hangul) {
1296 1         4 load_kana2hangul ();
1297             }
1298 2         841 $katakana =~ s/($first2hangul_re)/$first2hangul->{$1}/g;
1299 2         387 $katakana =~ s/($rest2hangul_re)/$rest2hangul->{$1}/g;
1300 2         17 return $katakana;
1301             }
1302              
1303             my $firsth2k_re;
1304             my $resth2k_re;
1305             my $firsth2k;
1306             my $resth2k;
1307              
1308             sub load_hangul2kana
1309             {
1310 1     1 0 5 load_kana2hangul ();
1311 1         614 $firsth2k = { reverse %$first2hangul };
1312 1         37 $resth2k = { reverse %$rest2hangul };
1313 1         28 $firsth2k_re = '\b' . make_regex (keys %$firsth2k);
1314 1         261 $resth2k_re = make_regex (keys %$resth2k);
1315             }
1316              
1317             sub hangul2kana
1318             {
1319 1     1 0 3 my ($hangul) = @_;
1320 1 50       7 if (! $firsth2k) {
1321 1         4 load_hangul2kana ();
1322             }
1323 1         371 $hangul =~ s/($firsth2k_re)/$firsth2k->{$1}/;
1324 1         38 $hangul =~ s/($resth2k_re)/$resth2k->{$1}/;
1325 1         7 return $hangul;
1326             }
1327              
1328             my %small2large = qw!
1329             ゃ や
1330             ゅ ゆ
1331             ょ よ
1332             ぁ あ
1333             ぃ い
1334             ぅ う
1335             ぇ え
1336             ぉ お
1337             っ つ
1338             ゎ わ
1339             !;
1340              
1341             sub kana_to_large
1342             {
1343 2     2 1 1371 my ($kana) = @_;
1344 2         28 $kana =~ tr/ゃゅょぁぃぅぇぉっゎ/やゆよあいうえおつわ/;
1345 2         16 $kana =~ tr/ャュョァィゥェォッヮ/ヤユヨアイウエオツワ/;
1346             # Katakana phonetic extensions.
1347 2         17 $kana =~ tr/ㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ/クシストヌハヒフヘホムラリルレロ/;
1348 2         6 return $kana;
1349             }
1350              
1351             my $circled2kanji;
1352              
1353             sub load_circled2kanji
1354             {
1355 4 100   4 0 17 if (! $circled2kanji) {
1356 1         8 $circled2kanji =
1357             Convert::Moji->new (["file",
1358             getdistfile ('circled2kanji')]);
1359             }
1360 4 50       1383 if (! $circled2kanji) {
1361 0         0 die "ERROR";
1362             }
1363             }
1364              
1365             sub circled2kanji
1366             {
1367 2     2 1 3193 my ($input) = @_;
1368 2         10 load_circled2kanji ();
1369 2         12 return $circled2kanji->convert ($input);
1370             }
1371              
1372             sub kanji2circled
1373             {
1374 2     2 1 1659 my ($input) = @_;
1375 2         8 load_circled2kanji ();
1376 2         10 return $circled2kanji->invert ($input);
1377             }
1378              
1379             my $bracketed2kanji;
1380              
1381             sub load_bracketed2kanji
1382             {
1383 2 100   2 0 9 if (! $bracketed2kanji) {
1384 1         3 $bracketed2kanji =
1385             Convert::Moji->new (["file",
1386             getdistfile ('bracketed2kanji')]);
1387             }
1388             }
1389              
1390             sub bracketed2kanji
1391             {
1392 1     1 1 604 my ($input) = @_;
1393 1         6 load_bracketed2kanji ();
1394 1         966 return $bracketed2kanji->convert ($input);
1395             }
1396              
1397             sub kanji2bracketed
1398             {
1399 1     1 1 650 my ($input) = @_;
1400 1         4 load_bracketed2kanji ();
1401 1         4 return $bracketed2kanji->invert ($input);
1402             }
1403              
1404             sub InKana
1405             {
1406 4     4 1 18091 return <<"END";
1407             +utf8::Katakana
1408             +utf8::InKatakana
1409             +utf8::InHiragana
1410             FF9E\tFF9F
1411             FF70
1412             -utf8::IsCn
1413             -30FB
1414             END
1415             # Explanation of the above gibberish: The funny hex is for dakuten
1416             # and handakuten half width. The "Katakana" catches halfwidth
1417             # katakana, and the "InKatakana" catches the chouon mark. "IsCn"
1418             # means "other, not assigned", so we remove this to prevent
1419             # matching non-kana characters floating around near to real
1420             # ones. 30FB is "Katakana middle dot", which is not kana as far as
1421             # I know, so that's also removed.
1422             }
1423              
1424             # お
1425              
1426             # Match zero or one sokuons, one full-sized kana character, then zero
1427             # or one each of small kana, chouon, and syllabic n, in that order.
1428              
1429             my $kana2syllable_re = qr/ッ?[アイウエオ-モヤユヨ-ヴ](?:[ャュョァィゥェォ])?ー?ン?/;
1430              
1431             sub katakana2syllable
1432             {
1433 4     4 1 2611 my ($kana) = @_;
1434 4         7 my @pieces;
1435 4         81 while ($kana =~ /($kana2syllable_re)/g) {
1436 22         129 push @pieces, $1;
1437             }
1438 4         14 return \@pieces;
1439             }
1440              
1441             my $square2katakana;
1442              
1443             sub load_square2katakana
1444             {
1445 2 100   2 0 8 if (! $square2katakana) {
1446 1         5 $square2katakana =
1447             Convert::Moji->new (["file",
1448             getdistfile ('square-katakana')]);
1449             }
1450             }
1451              
1452             sub square2katakana
1453             {
1454 1     1 1 86 load_square2katakana ();
1455 1         2274 return $square2katakana->convert (@_);
1456             }
1457              
1458             sub katakana2square
1459             {
1460 1     1 1 894 load_square2katakana ();
1461 1         7 return $square2katakana->invert (@_);
1462             }
1463              
1464             sub nigori_first
1465             {
1466 1     1 1 669 my ($list) = @_;
1467 1         2 my @nigori;
1468 1         4 for my $kana (@$list) {
1469 4         11 my ($first, $remaining) = split //, $kana, 2;
1470 4         12 my $nf = $not2daku{$first};
1471 4 100       9 if ($nf) {
1472 3         8 push @nigori, $nf.$remaining;
1473             }
1474 4         8 my $hf = $not2handaku{$first};
1475 4 100       10 if ($hf) {
1476 1         3 push @nigori, $hf.$remaining;
1477             }
1478             }
1479 1 50       5 if (@nigori) {
1480 1         5 push @$list, @nigori;
1481             }
1482             }
1483              
1484             # Hentaigana (Unicode 10.0) related
1485              
1486             my $hentai_file = __FILE__;
1487             $hentai_file =~ s!\.pm$!/!;
1488             $hentai_file .= "hentaigana.json";
1489             # Hentai to hiragana (one to one)
1490             my %hen2hi;
1491             # Hiragana to hentai (one to many)
1492             my %hi2hen;
1493             # Hentaigana to kanji
1494             my %hen2k;
1495             # Kanji to hentaigana
1496             my %k2hen;
1497             my $k2hen_re;
1498             # Hentai to hiragana/kanji regex (recycled for the kanji case).
1499             my $hen_re;
1500             # Hiragana to hentai regex
1501             my $hi2hen_re;
1502             # Hentai data
1503             my $hendat;
1504              
1505             sub load_hentai
1506             {
1507 1     1 0 7 $hendat = read_json ($hentai_file);
1508 1         942 for my $h (@$hendat) {
1509 285         409 my $hi = $h->{hi};
1510 285         452 my $hen = chr ($h->{u});
1511 285         540 $hen2hi{$hen} = $hi;
1512 285         402 for my $hiragana (@$hi) {
1513 298         961 push @{$hi2hen{$hiragana}}, $hen;
  298         672  
1514             }
1515 285         531 $hen2k{$hen} = $h->{ka};
1516 285         325 push @{$k2hen{$h->{ka}}}, $hen;
  285         968  
1517             }
1518 1         51 $hen_re = make_regex (keys %hen2hi);
1519 1         907 $hi2hen_re = make_regex (keys %hi2hen);
1520 1         176 $k2hen_re = make_regex (keys %k2hen);
1521             }
1522              
1523             sub hentai2kana
1524             {
1525 1     1 1 896 my ($text) = @_;
1526 1 50       6 if (! $hendat) {
1527 1         3 load_hentai ();
1528             }
1529 1         737 $text =~ s/$hen_re/join ('・', @{$hen2hi{$1}})/ge;
  4         7  
  4         19  
1530 1         7 return $text;
1531             }
1532              
1533             sub kana2hentai
1534             {
1535 2     2 1 6 my ($text) = @_;
1536 2 50       8 if (! $hendat) {
1537 0         0 load_hentai ();
1538             }
1539             # Make it all-hiragana.
1540 2         6 $text = split_sound_marks ($text);
1541 2         7 $text = kata2hira ($text);
1542 2         30 $text =~ s/$hi2hen_re/join ('・', @{$hi2hen{$1}})/ge;
  8         15  
  8         35  
1543 2         11 return $text;
1544             # what to do?
1545             }
1546              
1547             sub hentai2kanji
1548             {
1549 1     1 1 3 my ($text) = @_;
1550 1 50       4 if (! $hendat) {
1551 0         0 load_hentai ();
1552             }
1553             # This uses the same regex as the kanji case.
1554 1         91 $text =~ s/$hen_re/$hen2k{$1}/g;
1555 1         7 return $text;
1556             }
1557              
1558             sub kanji2hentai
1559             {
1560 1     1 1 18 my ($text) = @_;
1561 1 50       5 if (! $hendat) {
1562 0         0 load_hentai ();
1563             }
1564 1         101 $text =~ s/$k2hen_re/join ('・', @{$k2hen{$1}})/ge;
  4         7  
  4         19  
1565 1         7 return $text;
1566             }
1567              
1568             my %yayuyo = (qw/
1569             ヤ ャ
1570             ユ ュ
1571             ヨ ョ
1572             /);
1573              
1574             my %l2s = qw!ア ァ イ ィ ウ ゥ エ ェ オ ォ!;
1575              
1576             sub smallize_kana
1577             {
1578 10     10 1 109 my ($kana) = @_;
1579 10         15 my $orig = $kana;
1580 10         87 $kana =~ s/([キギシジチヂニヒビピミリ])([ヤユヨ])/$1$yayuyo{$2}/g;
1581             # Don't make "ツル" into "ッル".
1582 10         135 $kana =~ s/([$before_sokuon])ツ([$takes_sokuon])/$1ッ$2/g;
1583 10         46 $kana =~ s/フ([アイエオ])/フ$l2s{$1}/g;
1584 10 100       29 if ($kana ne $orig) {
1585 7         45 return $kana;
1586             }
1587 3         16 return undef;
1588             }
1589              
1590             sub cleanup_kana
1591             {
1592 3     3 1 792 my ($kana) = @_;
1593 3 50       12 if (! $kana) {
1594 0         0 return $kana;
1595             }
1596 3 100       31 if ($kana =~ /[\x{ff01}-\x{ff5e}]/) {
    50          
1597 1         6 $kana = wide2ascii ($kana);
1598 1         7 $kana = romaji2kana ($kana);
1599             }
1600             elsif ($kana =~ /[a-zâîûêôôāūēō]/i) {
1601 0         0 $kana = romaji2kana ($kana);
1602             }
1603             # This calls join_sound_marks, so that call is not necessary.
1604 3         12 $kana = kana2katakana ($kana);
1605             # Translate kanjis into katakana where a "naive user" has inserted
1606             # kanji not kana. Because the following expression is visually
1607             # confusing, note that the LHS are all kanji, and the RHS are all
1608             # kana/chouon
1609 3         33 $kana =~ tr/囗口八力二一/ロロハカニー/;
1610             # Turn silly small youon kana into big ones
1611 3         22 $kana =~ s/([^きぎしじちぢにひびぴみり]|^)([ゃゅょ])/$1$small2large{$2}/g;
1612 3         21 return $kana;
1613             }
1614              
1615             sub load_kanji
1616             {
1617 2     2 0 7 my ($file) = @_;
1618 2         10 my $bkfile = getdistfile ($file);
1619 2 50       163 open my $in, "<:encoding(utf8)", $bkfile
1620             or die "Error opening '$bkfile': $!";
1621 2         163 my @bk;
1622 2         56 while (<$in>) {
1623 20         93 while (/(\p{InCJKUnifiedIdeographs})/g) {
1624 268         882 push @bk, $1;
1625             }
1626             }
1627 2 50       40 close $in or die $!;
1628 2         82 return @bk;
1629             }
1630              
1631             sub yurei_moji
1632             {
1633 1     1 1 1287 return load_kanji ('yurei-moji')
1634             }
1635              
1636             sub bad_kanji
1637             {
1638 1     1 1 6 return load_kanji ('bad-kanji');
1639             }
1640              
1641             sub kana_consonant
1642             {
1643 5     5 1 1204 my ($kana) = @_;
1644 5 100       22 if (length ($kana) < 1) {
1645 1         211 croak "Empty input to kana_consonant";
1646             }
1647 4         16 my $first = substr ($kana, 0, 1);
1648 4 100       36 if ($first !~ /\p{InKana}/) {
1649 1         2029 croak "First character '$first' of '$kana' is not kana";
1650             }
1651 3         10 $first = kana2katakana ($first);
1652 3         9 my $not = $daku2not{$first};
1653 3 100       8 if (defined $not) {
1654 1         3 $first = $not;
1655             }
1656 3         37 my $con = $siin{$first};
1657             }
1658              
1659             1;