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