File Coverage

blib/lib/AozoraBunko/Tools/Checkerkun.pm
Criterion Covered Total %
statement 120 120 100.0
branch 90 106 84.9
condition 31 42 73.8
subroutine 16 16 100.0
pod 2 2 100.0
total 259 286 90.5


line stmt bran cond sub pod time code
1             package AozoraBunko::Tools::Checkerkun;
2             our $VERSION = "0.03";
3              
4 4     4   5855 use 5.008001;
  4         15  
5 4     4   20 use strict;
  4         7  
  4         86  
6 4     4   27 use warnings;
  4         7  
  4         99  
7 4     4   900 use utf8;
  4         13  
  4         18  
8              
9 4     4   95 use Carp qw//;
  4         6  
  4         70  
10 4     4   15433 use File::ShareDir qw//;
  4         97099  
  4         118  
11 4     4   4720 use YAML::Tiny qw//;
  4         25036  
  4         108  
12 4     4   4247 use Encode qw//;
  4         50232  
  4         111  
13 4     4   3489 use Lingua::JA::Halfwidth::Katakana;
  4         970  
  4         5360  
14              
15             my $YAML_FILE = File::ShareDir::dist_file('AozoraBunko-Tools-Checkerkun', 'hiden_no_tare.yml');
16             my $YAML = YAML::Tiny->read($YAML_FILE)->[0];
17             my $ENC = Encode::find_encoding("Shift_JIS");
18              
19             my %VALID_OUTPUT_FORMAT;
20             @VALID_OUTPUT_FORMAT{qw/plaintext html/} = ();
21              
22             # [78hosetsu_tekiyo] 78互換包摂の対象となる不要な外字注記をチェックする
23             our $KUTENMEN_78HOSETSU_TEKIYO = $YAML->{'kutenmen_78hosetsu_tekiyo'};
24              
25             # [hosetsu_tekiyo] 包摂の対象となる不要な外字注記をチェックする
26             our $KUTENMEN_HOSETSU_TEKIYO = $YAML->{'kutenmen_hosetsu_tekiyo'};
27              
28             # 新JIS漢字で包摂基準の適用除外となる104字
29             our $JYOGAI = $YAML->{'jyogai'};
30              
31             # 78互換文字
32             our $J78 = $YAML->{'j78'};
33              
34             # 間違えやすい文字
35             # かとうかおりさんの「誤認識されやすい文字リスト」から
36             # http://plaza.users.to/katokao/digipr/digipr_charlist.html
37             our $GONIN1 = $YAML->{'gonin1'};
38              
39             # 誤認2
40             our $GONIN2 = $YAML->{'gonin2'};
41              
42             # 誤認3
43             # (砂場清隆さんの入力による)
44             our $GONIN3 = $YAML->{'gonin3'};
45              
46             sub _default_options
47             {
48             return {
49 64     64   527 'gaiji' => 1, # JIS外字をチェックする
50             'hansp' => 1, # 半角スペースをチェックする
51             'hanpar' => 1, # 半角カッコをチェックする
52             'zensp' => 0, # 全角スペースをチェックする
53             '78hosetsu_tekiyo' => 1, # 78互換包摂の対象となる不要な外字注記をチェックする
54             'hosetsu_tekiyo' => 1, # 包摂の対象となる不要な外字注記をチェックする
55             '78' => 0, # 78互換包摂29字をチェックする
56             'jyogai' => 0, # 新JIS漢字で包摂規準の適用除外となる104字をチェックする
57             'gonin1' => 0, # 誤認しやすい文字をチェックする(1)
58             'gonin2' => 0, # 誤認しやすい文字をチェックする(2)
59             'gonin3' => 0, # 誤認しやすい文字をチェックする(3)
60             'simplesp' => 0, # 半角スペースは「_」で、全角スペースは「□」で出力する
61             'output_format' => 'plaintext', # plaintext または html
62             };
63             }
64              
65             sub new
66             {
67 64     64 1 65396 my $class = shift;
68 64 100       244 my %args = (ref $_[0] eq 'HASH' ? %{$_[0]} : @_);
  58         479  
69              
70 64         252 my $options = $class->_default_options;
71              
72 64         265 for my $key (keys %args)
73             {
74 753 100       1596 if ( ! exists $options->{$key} ) { Carp::croak "Unknown option: '$key'"; }
  2         327  
75             else
76             {
77 751 100       1627 if ($key eq 'output_format')
78             {
79 58 100       302 Carp::croak "Output format option must be 'plaintext' or 'html'" unless exists $VALID_OUTPUT_FORMAT{ $args{$key} };
80             }
81              
82 750         1481 $options->{$key} = $args{$key};
83             }
84             }
85              
86 61         415 bless $options, $class;
87             }
88              
89             sub _tag_html
90             {
91 4034     4034   6481 my ($plaintext, $tag_name, $msg) = @_;
92              
93 4034 100       15079 return qq|$plaintext| unless defined $msg;
94 2020         6994 return qq|$plaintext|;
95             }
96              
97             # 例:
98             #
99             # ※[#「口+亞」、第3水準1-15-8、144-上-9]
100             # が
101             # ※[#「口+亞」、第3水準1-15-8、144-上-9] → [78hosetsu_tekiyo]【唖】
102             # に変換され、
103             #
104             # ※[#「にんべん+曾」、第3水準1-14-41、144-上-9]
105             # が
106             # ※[#「にんべん+曾」、第3水準1-14-41、144-上-9]→[hosetsu_tekiyo]【僧】
107             # に変換される。
108             #
109             sub _check_all_hosetsu_tekiyo
110             {
111 4008     4008   5914 my ($self, $chars_ref, $index) = @_;
112              
113 4008         4671 my ($replace, $usedlen);
114              
115 4008         5096 my $rear_index = $index + 80;
116 4008 100       4434 $rear_index = $#{$chars_ref} if $rear_index > $#{$chars_ref};
  14         20  
  4008         8996  
117              
118 4008 50       10354 if ( join("", @{$chars_ref}[$index .. $rear_index]) =~ /^(※[#.*?水準(\d+\-\d+\-\d+).*?])/ )
  4008         42335  
119             {
120 4008         9069 my ($match, $kutenmen) = ($1, $2);
121              
122 4008 100 66     20353 if ( $self->{'78hosetsu_tekiyo'} && exists $KUTENMEN_78HOSETSU_TEKIYO->{$kutenmen} )
    50 33        
123             {
124 2004 100       4967 if ($self->{'output_format'} eq 'plaintext')
    50          
125             {
126 1002         2488 $replace = $match . ' → [78hosetsu_tekiyo]【' . $KUTENMEN_78HOSETSU_TEKIYO->{$kutenmen} . '】 ';
127             }
128             elsif ($self->{'output_format'} eq 'html')
129             {
130 1002         2233 $replace = _tag_html($match, 'j78hosetsuTekiyo', $KUTENMEN_78HOSETSU_TEKIYO->{$kutenmen});
131             }
132              
133 2004         4568 $usedlen = length $match;
134             }
135             elsif ( $self->{'hosetsu_tekiyo'} && exists $KUTENMEN_HOSETSU_TEKIYO->{$kutenmen} )
136             {
137 2004 100       5193 if ($self->{'output_format'} eq 'plaintext')
    50          
138             {
139 1002         2698 $replace = $match . ' → [hosetsu_tekiyo]【' . $KUTENMEN_HOSETSU_TEKIYO->{$kutenmen} . '】 ';
140             }
141             elsif ($self->{'output_format'} eq 'html')
142             {
143 1002         2490 $replace = _tag_html($match, 'hosetsuTekiyo', $KUTENMEN_HOSETSU_TEKIYO->{$kutenmen});
144             }
145              
146 2004         4461 $usedlen = length $match;
147             }
148             }
149              
150 4008         16180 return ($replace, $usedlen);
151             }
152              
153             sub _is_gaiji
154             {
155 4016     4016   5361 my $char = shift; # コピーしないと、encode のタイミングで元の文字が消失してしまう。
156              
157             # UTF-8からSJISに変換できなければ外字と判定
158 4016         5273 eval { $ENC->encode($char, Encode::FB_CROAK) };
  4016         28373  
159 4016 100       19908 return 1 if $@;
160 12         43 return 0;
161             }
162              
163             sub check
164             {
165 57     57 1 299 my ($self, $text) = @_;
166              
167 57 100       161 return undef unless defined $text;
168              
169 56         143 my $output_format = $self->{'output_format'};
170              
171 56         18626 my @chars = split(//, $text);
172              
173 56         109 my $checked_text = '';
174              
175 56         220 for (my $i = 0; $i < @chars; $i++)
176             {
177 8636         12528 my $char = $chars[$i];
178              
179 8636 100       17847 if ($self->{simplesp})
180             {
181 64 100       173 $char = '_' if $char eq "\x{0020}";
182 64 100       162 $char = '□' if $char eq "\x{3000}";
183             }
184              
185 8636 100 100     87054 if ($char =~ /[\x{0000}-\x{0009}\x{000B}\x{000C}\x{000E}-\x{001F}\x{007F}-\x{009F}]/)
    100 100        
    100 100        
    100 66        
    100 66        
    100 66        
186             {
187             # 改行は含まない
188              
189 4 100       14 if ($output_format eq 'plaintext')
    50          
190             {
191 2         22 $checked_text .= "$char [ctrl]【" . sprintf("U+%04X", ord $char) . "】 ";
192             }
193             elsif ($output_format eq 'html')
194             {
195 2         13 $checked_text .= _tag_html($char, 'ctrl', sprintf("U+%04X", ord $char));
196             }
197             }
198 4     4   69 elsif ($char =~ /\p{InHalfwidthKatakana}/)
  4         8  
  4         65  
199             {
200 4 100       16 if ($output_format eq 'plaintext')
    50          
201             {
202 2         10 $checked_text .= "$char [hankata]【$char】 ";
203             }
204             elsif ($output_format eq 'html')
205             {
206 2         10 $checked_text .= _tag_html($char, 'hankata');
207             }
208             }
209             elsif ($self->{'hansp'} && $char =~ "\x{0020}")
210             {
211 4 100       15 if ($output_format eq 'plaintext')
    50          
212             {
213 2         8 $checked_text .= "$char [hansp]【$char】 ";
214             }
215             elsif ($output_format eq 'html')
216             {
217 2         5 $checked_text .= _tag_html($char, 'hansp');
218             }
219             }
220             elsif ($self->{'zensp'} && $char eq "\x{3000}")
221             {
222 4 100       13 if ($output_format eq 'plaintext')
    50          
223             {
224 2         9 $checked_text .= "$char [zensp]【$char】 ";
225             }
226             elsif ($output_format eq 'html')
227             {
228 2         5 $checked_text .= _tag_html($char, 'zensp');
229             }
230             }
231             elsif ( $self->{hanpar} && ($char eq '(' || $char eq ')') )
232             {
233 8 100       23 if ($output_format eq 'plaintext')
    50          
234             {
235 4         16 $checked_text .= "$char [hanpar]【$char】 ";
236             }
237             elsif ($output_format eq 'html')
238             {
239 4         9 $checked_text .= _tag_html($char, 'hanpar');
240             }
241             }
242             elsif ( $char eq '※' && ($self->{'78hosetsu_tekiyo'} || $self->{'hosetsu_tekiyo'}) )
243             {
244 4008         9253 my ($replace, $usedlen) = $self->_check_all_hosetsu_tekiyo(\@chars, $i);
245              
246 4008 50       8898 if ($replace)
247             {
248 4008         6726 $checked_text .= $replace;
249 4008         5049 $i += ($usedlen - 1);
250 4008         11273 next;
251             }
252             }
253             else
254             {
255 4604 100 66     47620 if ($self->{'78'} && $J78->{$char})
    100 66        
    100 66        
    100 66        
    100 66        
    100 100        
256             {
257 4 100       15 if ($output_format eq 'plaintext')
    50          
258             {
259 2         12 $checked_text .= "$char [78]【$char】(" . $J78->{$char} . ") ";
260             }
261             elsif ($output_format eq 'html')
262             {
263 2         6 $checked_text .= _tag_html($char, 'j78', $J78->{$char});
264             }
265             }
266             elsif ($self->{'jyogai'} && $JYOGAI->{$char})
267             {
268 4 100       22 if ($output_format eq 'plaintext')
    50          
269             {
270 2         9 $checked_text .= "$char [jyogai]【$char】 ";
271             }
272             elsif ($output_format eq 'html')
273             {
274 2         8 $checked_text .= _tag_html($char, 'jyogai');
275             }
276             }
277             elsif ($self->{'gonin1'} && $GONIN1->{$char})
278             {
279 8 100       43 if ($output_format eq 'plaintext')
    50          
280             {
281 4         34 $checked_text .= "$char [gonin1]【$char】(" . $GONIN1->{$char} . ") ";
282             }
283             elsif ($output_format eq 'html')
284             {
285 4         18 $checked_text .= _tag_html($char, 'gonin1', $GONIN1->{$char});
286             }
287             }
288             elsif ($self->{'gonin2'} && $GONIN2->{$char})
289             {
290 8 100       40 if ($output_format eq 'plaintext')
    50          
291             {
292 4         37 $checked_text .= "$char [gonin2]【$char】(" . $GONIN2->{$char} . ") ";
293             }
294             elsif ($output_format eq 'html')
295             {
296 4         16 $checked_text .= _tag_html($char, 'gonin2', $GONIN2->{$char});
297             }
298             }
299             elsif ($self->{'gonin3'} && $GONIN3->{$char})
300             {
301 8 100       24 if ($output_format eq 'plaintext')
    50          
302             {
303 4         20 $checked_text .= "$char [gonin3]【$char】(" . $GONIN3->{$char} . ") ";
304             }
305             elsif ($output_format eq 'html')
306             {
307 4         10 $checked_text .= _tag_html($char, 'gonin3', $GONIN3->{$char});
308             }
309             }
310             elsif ( $self->{'gaiji'} && _is_gaiji($char) )
311             {
312 4004 100       9164 if ($output_format eq 'plaintext')
    50          
313             {
314 2002         6962 $checked_text .= "$char [gaiji]【$char】 ";
315             }
316             elsif ($output_format eq 'html')
317             {
318 2002         3326 $checked_text .= _tag_html($char, 'gaiji');
319             }
320             }
321 568         1897 else { $checked_text .= $char; }
322             }
323             }
324              
325 56         8361 return $checked_text;
326             }
327              
328             1;
329              
330             __END__