| blib/lib/Novel/Robot/Parser/txt.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 91 | 91 | 100.0 |
| branch | 8 | 12 | 66.6 |
| condition | 4 | 10 | 40.0 |
| subroutine | 14 | 14 | 100.0 |
| pod | 1 | 5 | 20.0 |
| total | 118 | 132 | 89.3 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | # ABSTRACT: txt parser | ||||||
| 2 | =pod | ||||||
| 3 | |||||||
| 4 | =encoding utf8 | ||||||
| 5 | |||||||
| 6 | =head1 FUNCTION | ||||||
| 7 | |||||||
| 8 | =head2 parse_novel | ||||||
| 9 | |||||||
| 10 | 解析txt | ||||||
| 11 | |||||||
| 12 | my $txt_content_ref = $self->parse_novel( | ||||||
| 13 | [ '/somedir/', '/someotherdir/somefile.txt' ], | ||||||
| 14 | writer => 'some_writer', | ||||||
| 15 | book => 'some_book', | ||||||
| 16 | chapter_regex => qr/(第\d+章)/, | ||||||
| 17 | ); | ||||||
| 18 | |||||||
| 19 | |||||||
| 20 | =cut | ||||||
| 21 | package Novel::Robot::Parser::txt; | ||||||
| 22 | 1 | 1 | 6 | use strict; | |||
| 1 | 1 | ||||||
| 1 | 27 | ||||||
| 23 | 1 | 1 | 4 | use warnings; | |||
| 1 | 2 | ||||||
| 1 | 22 | ||||||
| 24 | 1 | 1 | 5 | use base 'Novel::Robot::Parser'; | |||
| 1 | 1 | ||||||
| 1 | 85 | ||||||
| 25 | |||||||
| 26 | 1 | 1 | 449 | use File::Find::Rule; | |||
| 1 | 7277 | ||||||
| 1 | 6 | ||||||
| 27 | 1 | 1 | 49 | use Encode; | |||
| 1 | 2 | ||||||
| 1 | 80 | ||||||
| 28 | 1 | 1 | 5 | use Encode::Locale; | |||
| 1 | 2 | ||||||
| 1 | 51 | ||||||
| 29 | 1 | 1 | 7 | use Encode::Detect::CJK qw/detect/; | |||
| 1 | 2 | ||||||
| 1 | 43 | ||||||
| 30 | 1 | 1 | 5 | use utf8; | |||
| 1 | 2 | ||||||
| 1 | 4 | ||||||
| 31 | |||||||
| 32 | |||||||
| 33 | sub parse_novel { | ||||||
| 34 | 2 | 2 | 1 | 4 | my ($self, $path, %opt) = @_; | ||
| 35 | 2 | 33 | 13 | $opt{chapter_regex} ||= get_default_chapter_regex(); | |||
| 36 | |||||||
| 37 | 2 | 2 | my %data; | ||||
| 38 | 2 | 50 | 10 | $data{writer} = $opt{writer} || 'unknown'; | |||
| 39 | 2 | 50 | 8 | $data{book} = $opt{book} || 'unknown'; | |||
| 40 | |||||||
| 41 | 2 | 50 | 7 | my $p_ref = ref($path) eq 'ARRAY' ? $path : [ $path ]; | |||
| 42 | 2 | 4 | for my $p (@$p_ref){ | ||||
| 43 | 2 | 53 | my @txts = sort File::Find::Rule->file()->in($p); | ||||
| 44 | 2 | 1131 | for my $txt (@txts){ | ||||
| 45 | 2 | 8 | my $txt_data_ref = $self->read_single_txt($txt, %opt); | ||||
| 46 | 2 | 16 | my $txt_file = decode(locale => $txt); | ||||
| 47 | 2 | 126 | for my $t (@$txt_data_ref){ | ||||
| 48 | #$t->{url} = $txt_file; | ||||||
| 49 | 6 | 8 | push @{$data{item_list}}, $t; | ||||
| 6 | 14 | ||||||
| 50 | } | ||||||
| 51 | } | ||||||
| 52 | } | ||||||
| 53 | |||||||
| 54 | 2 | 14 | $self->update_item_list($data{item_list}); | ||||
| 55 | |||||||
| 56 | #$data{url} = ''; | ||||||
| 57 | |||||||
| 58 | 2 | 10 | return \%data; | ||||
| 59 | } | ||||||
| 60 | |||||||
| 61 | sub get_default_chapter_regex { | ||||||
| 62 | #指定分割章节的正则表达式 | ||||||
| 63 | |||||||
| 64 | #序号 | ||||||
| 65 | 2 | 2 | 0 | 8 | my $r_num = | ||
| 66 | qr/[0123456789零○〇一二三四五六七八九十百千\d]+/; | ||||||
| 67 | 2 | 6 | my $r_split = qr/[上中下]/; | ||||
| 68 | 2 | 5 | my $r_not_chap_head = qr/引子|楔子|尾声|内容简介|正文|番外|终章|序言|后记|文案/; | ||||
| 69 | |||||||
| 70 | #第x章,卷x,第x章(大结局),尾声x | ||||||
| 71 | 2 | 57 | my $r_head = qr/(卷|第|$r_not_chap_head)?/; | ||||
| 72 | 2 | 5 | my $r_tail = qr/(章|卷|回|部|折)?/; | ||||
| 73 | 2 | 4 | my $r_post = qr/([.\s\-\(\/(]+.{0,35})?/; | ||||
| 74 | 2 | 99 | my $regex_a = qr/(【?$r_head\s*$r_num\s*$r_tail$r_post】?)/; | ||||
| 75 | |||||||
| 76 | #(1),(1)xxx | ||||||
| 77 | #xxx(1),xxx(1)yyy | ||||||
| 78 | #(1-上|中|下) | ||||||
| 79 | 2 | 27 | my $regex_b_index = qr/[((]$r_num[))]/; | ||||
| 80 | 2 | 24 | my $regex_b_tail = qr/$regex_b_index\s*\S+/; | ||||
| 81 | 2 | 23 | my $regex_b_head = qr/\S+\s*$regex_b_index.{0,10}/; | ||||
| 82 | 2 | 40 | my $regex_b_split = qr/[((]$r_num[--]$r_split[))]/; | ||||
| 83 | 2 | 88 | my $regex_b = qr/$regex_b_head|$regex_b_tail|$regex_b_index|$regex_b_split/; | ||||
| 84 | |||||||
| 85 | #1、xxx,一、xxx | ||||||
| 86 | 2 | 38 | my $regex_c = qr/$r_num[、.. ].{0,10}/; | ||||
| 87 | |||||||
| 88 | #第x卷 xxx 第x章 xxx | ||||||
| 89 | #第x卷/第x章 xxx | ||||||
| 90 | 2 | 101 | my $regex_d = qr/($regex_a(\s+.{0,10})?){2}/; | ||||
| 91 | |||||||
| 92 | #后记 xxx | ||||||
| 93 | 2 | 68 | my $regex_e = qr/(【?$r_not_chap_head\s*$r_post】?)/; | ||||
| 94 | |||||||
| 95 | #总体 | ||||||
| 96 | 2 | 352 | my $chap_r = qr/^\s*($regex_a|$regex_b|$regex_c|$regex_d|$regex_e)\s*$/m; | ||||
| 97 | |||||||
| 98 | 2 | 24 | return $chap_r; | ||||
| 99 | } | ||||||
| 100 | |||||||
| 101 | |||||||
| 102 | |||||||
| 103 | sub read_single_txt { | ||||||
| 104 | |||||||
| 105 | #读入单个txt文件 | ||||||
| 106 | 2 | 2 | 0 | 6 | my ($self, $txt, %opt) = @_; | ||
| 107 | |||||||
| 108 | 2 | 5 | my $charset = $self->detect_file_charset($txt); | ||||
| 109 | 1 | 1 | 7 | open my $sh, "<:encoding($charset)", $txt; | |||
| 1 | 2 | ||||||
| 1 | 8 | ||||||
| 2 | 2878 | ||||||
| 110 | |||||||
| 111 | 2 | 5902 | my @data; | ||||
| 112 | 2 | 8 | my ( $single_toc, $single_content ) = ( '', '' ); | ||||
| 113 | |||||||
| 114 | #第一章 | ||||||
| 115 | 2 | 65 | while (<$sh>) { | ||||
| 116 | 2 | 50 | 24 | next unless /\S/; | |||
| 117 | 2 | 50 | 27 | $single_toc = /$opt{chapter_regex}/ ? $1 : $_; | |||
| 118 | 2 | 4 | last; | ||||
| 119 | } ## end while (<$sh>) | ||||||
| 120 | |||||||
| 121 | #后续章节 | ||||||
| 122 | 2 | 8 | while (<$sh>) { | ||||
| 123 | 26 | 100 | 65 | next unless /\S/; | |||
| 124 | 16 | 100 | 125 | if ( my ($new_single_toc) = /$opt{chapter_regex}/ ) { | |||
| 125 | 4 | 50 | 33 | 19 | if ( $single_toc =~ /\S/ and $single_content =~ /\S/s ) { | ||
| 126 | 4 | 14 | push @data, { title => $single_toc, content => $single_content }; | ||||
| 127 | 4 | 7 | $single_toc = ''; | ||||
| 128 | } ## end if ( $single_toc =~ /\S/...) | ||||||
| 129 | 4 | 9 | $single_toc .= $new_single_toc . "\n"; | ||||
| 130 | 4 | 13 | $single_content = ''; | ||||
| 131 | } | ||||||
| 132 | else { | ||||||
| 133 | 12 | 53 | $single_content .= $_; | ||||
| 134 | } ## end else [ if ( my ($new_single_toc...))] | ||||||
| 135 | } ## end while (<$sh>) | ||||||
| 136 | |||||||
| 137 | 2 | 11 | push @data, { title => $single_toc, content => $single_content }; | ||||
| 138 | 2 | 9 | $self->format_chapter_content($_) for @data; | ||||
| 139 | |||||||
| 140 | 2 | 35 | return \@data; | ||||
| 141 | } ## end sub read_single_txt | ||||||
| 142 | |||||||
| 143 | sub format_chapter_content { | ||||||
| 144 | 6 | 6 | 0 | 12 | my ($self, $r) = @_; | ||
| 145 | 6 | 9 | for ($r->{content}) { | ||||
| 146 | 6 | 12 | s# #\n#gi; |
||||
| 147 | 6 | 41 | s#\s*(.*\S)\s*# $1 \n#gm; |
||||
| 148 | 6 | 25 | s# \s* ##g; |
||||
| 149 | } ## end for ($chap_c) | ||||||
| 150 | |||||||
| 151 | 6 | 12 | return $self; | ||||
| 152 | } | ||||||
| 153 | |||||||
| 154 | sub detect_file_charset { | ||||||
| 155 | 2 | 2 | 0 | 4 | my ($self, $file) = @_; | ||
| 156 | 2 | 69 | open my $fh, '<', $file; | ||||
| 157 | 2 | 59 | read $fh, my $text, 360; | ||||
| 158 | 2 | 11 | return detect($text); | ||||
| 159 | } ## end sub detect_file_charset | ||||||
| 160 | |||||||
| 161 | 1; |