File Coverage

blib/lib/Lingua/JA/Moji.pm
Criterion Covered Total %
statement 563 644 87.4
branch 160 214 74.7
condition 14 21 66.6
subroutine 92 97 94.8
pod 57 89 64.0
total 886 1065 83.1


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