File Coverage

blib/lib/Lingua/JA/Moji.pm
Criterion Covered Total %
statement 560 633 88.4
branch 160 210 76.1
condition 14 21 66.6
subroutine 92 95 96.8
pod 54 86 62.7
total 880 1045 84.2


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