File Coverage

blib/lib/Aozora2Epub.pm
Criterion Covered Total %
statement 113 169 66.8
branch 29 56 51.7
condition 5 17 29.4
subroutine 22 29 75.8
pod 4 7 57.1
total 173 278 62.2


line stmt bran cond sub pod time code
1             package Aozora2Epub;
2 4     4   815549 use utf8;
  4         375  
  4         30  
3 4     4   162 use strict;
  4         8  
  4         89  
4 4     4   19 use warnings;
  4         8  
  4         187  
5 4     4   1839 use Aozora2Epub::Gensym;
  4         11  
  4         334  
6 4     4   1791 use Aozora2Epub::CachedGet qw/http_get/;
  4         19  
  4         363  
7 4     4   2022 use Aozora2Epub::Epub;
  4         22  
  4         29  
8 4     4   2543 use Aozora2Epub::XHTML;
  4         35  
  4         33  
9 4     4   209 use Path::Tiny;
  4         8  
  4         294  
10 4     4   2781 use URI;
  4         24557  
  4         192  
11 4     4   1745 use HTML::Escape qw/escape_html/;
  4         3302  
  4         293  
12              
13 4     4   29 use base qw(Class::Accessor);
  4         7  
  4         1708  
14             __PACKAGE__->mk_accessors(qw/files title author epub bib_info notation_notes/);
15              
16             our $VERSION = '0.05';
17              
18             our $AOZORA_GAIJI_URL = 'https://www.aozora.gr.jp/gaiji/';
19             our $AOZORA_CARDS_URL = 'https://www.aozora.gr.jp/cards';
20              
21             sub _base_url {
22 0     0   0 my $base = shift;
23 0         0 $base =~ s{[^/]+\.html$}{}s;
24 0         0 return $base;
25             }
26              
27             sub _get_file {
28 0     0   0 my $url_or_path = "" . shift; # force to string.
29              
30 0 0       0 if ($url_or_path =~ m{^https?://}) {
31 0         0 return http_get($url_or_path);
32             }
33 0 0       0 if ($url_or_path =~ m{\.html$}) {
34 0         0 return path($url_or_path)->slurp_utf8;
35             }
36 0         0 return path($url_or_path)->slurp_raw;
37             }
38              
39             sub _get_content {
40 37     37   81 my $xhtml = shift;
41 37 50       165 if ($xhtml =~ m{/card\d+\.html$}) { # 図書カード
42 0 0       0 unless ($xhtml =~ m{^https?://}) { # $xhtml shuld be \d+/card\d+.html
43 0         0 $xhtml = "$AOZORA_CARDS_URL/$xhtml";
44             }
45 0         0 my $text = _get_file($xhtml);
46 0         0 my $tree = Aozora2Epub::XHTML::Tree->new($text);
47 0         0 my $xhtml_url;
48             $tree->process('//a[text()="いますぐXHTML版で読む"]' => sub {
49 0     0   0 $xhtml_url = shift->attr('href');
50 0         0 });
51 0         0 my $xhtml_uri = URI->new($xhtml_url)->abs(URI->new($xhtml));
52 0         0 return _get_content($xhtml_uri->as_string);
53             }
54 37 50       226 if ($xhtml =~ m{/files/\d+_\d+\.html$}) { # XHTML
55 0 0       0 unless ($xhtml =~ m{^https?://}) { # $xhtml shuld be \d+/files/xxx_xxx.html
56 0         0 $xhtml = "$AOZORA_CARDS_URL/$xhtml";
57             }
58 0         0 my $text = _get_file($xhtml);
59 0         0 return ($text, _base_url($xhtml));
60             }
61             # XHTML string
62 37         183 return (qq{
$xhtml
}, undef);
63             }
64              
65             sub new {
66 37     37 1 874 my ($class, $content, %options) = @_;
67 37         371 my $self = bless {
68             files => [],
69             epub => Aozora2Epub::Epub->new,
70             title => undef,
71             author => undef,
72             bib_info => '',
73             notation_notes => '',
74             }, $class;
75 37 50       286 $self->append($content, %options, title=>'') if $content;
76 37         1197 return $self;
77             }
78              
79             sub _cat_url {
80 0     0   0 my ($base, $path) = @_;
81 0 0       0 unless ($base =~ m{^https?://}) {
82 0         0 return path($base, $path);
83             }
84 0         0 return URI->new($path)->abs(URI->new($base));
85             }
86              
87             sub _build_elemlist_from_xhtml {
88 0     0   0 my $xhtml = shift;
89 0         0 my $tr = Aozora2Epub::XHTML->new_from_string(qq{
$xhtml
});;
90 0         0 return @{$tr->contents};
  0         0  
91             }
92              
93             sub append {
94 37     37 1 249 my ($self, $xhtml_like, %options) = @_;
95              
96 37         142 my ($xhtml, $base_url) = _get_content($xhtml_like);
97 37         390 my $doc = Aozora2Epub::XHTML->new_from_string($xhtml);
98              
99 37 100       163 unless ($options{no_fetch_assets}) {
100 4         5 for my $path (@{$doc->gaiji}) {
  4         8  
101 0         0 my $png = _get_file(_cat_url($AOZORA_GAIJI_URL, $path));
102 0         0 $self->epub->add_gaiji($png, $path);
103             }
104 4         67 for my $path (@{$doc->fig}) {
  4         7  
105 0         0 my $png = _get_file(_cat_url($base_url, $path));
106 0         0 $self->epub->add_image($png, $path);
107             }
108             }
109 37         250 my @files = $doc->split;
110 37         89 my $part_title;
111 37 50       167 if (defined $options{title_html}) {
112 0         0 $files[0]->insert_content(_build_elemlist_from_xhtml($options{title_html}));
113             } else {
114 37 50       210 unless (defined $options{title}) {
    50          
115 0 0       0 if ($options{use_subtitle}) {
116 0         0 $part_title = $doc->subtitle;
117             }
118 0   0     0 $part_title ||= $doc->title;
119 0         0 } elsif ($options{title} eq '') {
120 37         75 $part_title = undef;
121             } else {
122 0         0 $part_title = $options{title};
123             }
124 37 50 33     197 if ($files[0] && $part_title) {
125 0   0     0 my $title_level = $options{title_level} || 2;
126 0         0 my $tag = "h$title_level";
127 0         0 my $header_elem = HTML::Element->new_from_lol([ $tag, { id => gensym },
128             $part_title ]);
129 0         0 $files[0]->insert_content($header_elem);
130             }
131             }
132 37         68 push @{$self->files}, @files;
  37         169  
133 37 50       651 $self->title or $self->title($doc->title);
134 37 50       1146 $self->author or $self->author($doc->author);
135 37         1092 $self->add_bib_info($part_title, $doc->bib_info);
136 37         910 $self->add_notation_notes($part_title, $doc->notation_notes);
137             }
138              
139             sub add_bib_info {
140 37     37 0 510 my ($self, $part_title, $bib_info) = @_;
141              
142 37 50       168 $self->bib_info(join('',
143             $self->bib_info,
144             "
",
145             ($part_title
146             ? (q{
}, escape_html($part_title), "
")
147             : ()),
148             $bib_info));
149             }
150              
151             sub add_notation_notes {
152 37     37 0 457 my ($self, $part_title, $notes) = @_;
153              
154 37 50       124 $self->notation_notes(join('',
155             $self->notation_notes,
156             "
",
157             ($part_title
158             ? (q{
}, escape_html($part_title), "
")
159             : ()),
160             $notes));
161             }
162              
163             sub _make_content_iterator {
164 4     4   6 my $files = shift;
165              
166 4         9 my @files = @$files;
167 4         5 my $file = shift @files;
168 4         6 my @content = @{$file->content};
  4         18  
169 4         37 my $last;
170              
171             return (
172             sub { # get next element
173 34 100   34   433 if ($last) {
174 8         9 my $x = $last;
175 8         11 undef $last;
176 8         19 return $x;
177             }
178 26         30 my $elem = shift @content;
179 26 100       36 unless ($elem) {
180 11         16 $file = shift @files;
181 11 100       27 return unless $file;
182 3         5 @content = @{$file->content};
  3         8  
183 3         28 $elem = shift @content;
184             }
185 18         34 return { elem=>$elem, file=>$file->name };
186             },
187 8     8   10 sub { $last = shift; } # putback
188 4         29 );
189             }
190              
191             sub _toc {
192 10     10   19 my ($level, $next, $putback) = @_;
193              
194 10         11 my @cur;
195 10         14 while (my $c = $next->()) {
196 26         166 my $e = $c->{elem};
197 26 100       80 next unless $e->isa('HTML::Element');
198 19         34 my $tag = $e->tag;
199 19         138 my ($lev) = ($tag =~ m{h(\d)});
200 19 50       34 next unless $lev;
201 19 100       33 if ($lev > $level) {
202 6         13 $putback->($c);
203 6         13 my $children = _toc($lev, $next, $putback);
204 6 100 100     22 if ($cur[-1] && $cur[-1]->{level} < $lev) {
205 1         2 $cur[-1]->{children} = $children;
206             } else {
207 5         6 push @cur, @{$children};
  5         10  
208             }
209 6         19 next;
210             }
211 13 100       24 if ($lev < $level) {
212 2         7 $putback->($c);
213 2         7 return \@cur;
214             }
215             push @cur, {
216             name => gensym,
217             level => $lev,
218             id => $e->attr('id'),
219             title => $e->as_text,
220             file => $c->{file},
221 11         24 };
222             }
223 8         38 return \@cur;
224             }
225              
226             sub _make_toc {
227 4     4   7 my $self = shift;
228 4         10 my ($next, $putback) = _make_content_iterator($self->{files});
229 4         11 return _toc(1, $next, $putback);
230             }
231              
232             sub toc {
233 4     4 0 21 my ($self, $toc) = @_;
234 4 50       9 unless ($toc) {
235 4   33     19 $self->{toc} ||= $self->_make_toc;
236 4         12 return $self->{toc};
237             }
238 0           $self->{toc} = $toc;
239             }
240              
241             sub to_epub {
242 0     0 1   my ($self, %options) = @_;
243              
244 0           my $epub_filename = $options{output};
245 0   0       $epub_filename ||= $self->title . ".epub";
246              
247 0 0         if ($options{cover}) {
248 0           $self->epub->set_cover($options{cover});
249             }
250 0           $self->epub->build_from_doc($self);
251              
252 0           $self->epub->save($epub_filename);
253             }
254              
255             sub as_html {
256 0     0 1   my $self = shift;
257 0           return join('', map { $_->as_html } @{$self->files});
  0            
  0            
258             }
259             1;
260             __END__