File Coverage

blib/lib/Text/Md2Inao/Builder/InDesign.pm
Criterion Covered Total %
statement 89 89 100.0
branch 19 26 73.0
condition 2 3 66.6
subroutine 18 18 100.0
pod 0 7 0.0
total 128 143 89.5


line stmt bran cond sub pod time code
1             package Text::Md2Inao::Builder::InDesign;
2 23     23   5064 use utf8;
  23         50  
  23         484  
3 23     23   1297 use strict;
  23         53  
  23         1130  
4 23     23   142 use warnings;
  23         49  
  23         864  
5              
6 23     23   1140 use parent qw/Text::Md2Inao::Builder/;
  23         56  
  23         203  
7 23     23   2209 use Text::Md2Inao::Builder::DSL;
  23         50  
  23         1756  
8              
9 23     23   138 use Text::Md2Inao::Logger;
  23         45  
  23         1552  
10 23     23   136 use Text::Md2Inao::Util;
  23         45  
  23         230  
11 23     23   28594 use File::ShareDir qw(dist_dir);
  23         185498  
  23         2311  
12 23     23   335 use Path::Tiny;
  23         314  
  23         1174  
13 23     23   136 use List::Util qw/max/;
  23         48  
  23         110413  
14              
15             tie my %meta2label, "Tie::IxHash",
16                 author => '著者',
17                 'author(romaji)' => '著者(ローマ字)',
18                 supervisor => '監修',
19                 'supervisor(romaji)' => '監修(ローマ字)',
20                 affiliation => '所属',
21                 url => 'URL',
22                 mail => 'mail',
23                 github => 'Github',
24                 twitter => 'Twitter',
25             ;
26              
27             sub _new {
28 23     23   64     my $class = shift;
29 23         286     my $self = $class->SUPER::_new(@_);
30              
31             # check the repo's config/ first, and then try to get dist_dir()
32 23         376     for my $dir('config', eval { dist_dir('Text-Md2Inao') }) {
  23         152  
33 23 50       5621         if (-d $dir) {
34 23         160             $self->load_filter_config(path($dir, 'id_filter.json'));
35 23         380             last;
36                     }
37                 }
38 23         181     return $self;
39             }
40              
41             sub prepend_metadata {
42 107     107 0 272     my ($self, $c, $text) = @_;
43 107 100       374     if ($c->metadata) {
44 1         5         my @lines;
45 1 50       3         if (my $chapter = $c->metadata->{chapter}) {
46 1         10             push @lines, sprintf "<ParaStyle:章番号・連載回数>章番号:第%d章", $chapter;
47                     }
48              
49 1 50       3         if (my $serial = $c->metadata->{serial}) {
50 1         10             push @lines, sprintf "<ParaStyle:章番号・連載回数>連載回数:第%d回", $serial;
51                     }
52              
53 1 50       3        if (my $title = $c->metadata->{title}) {
54 1         9             push @lines, sprintf "<ParaStyle:タイトル>タイトル:%s", $title;
55                     }
56              
57 1 50       3         if (my $subtitle = $c->metadata->{subtitle}) {
58 1         8             push @lines, sprintf "<ParaStyle:キャッチ>キャッチ:%s", $subtitle;
59                     }
60              
61 1         8         for (keys %meta2label) {
62 9 50       117             if (my $value = $c->metadata->{$_}) {
63 9         61                 push @lines, sprintf "<ParaStyle:本文>%s:%s", $meta2label{$_}, $value;
64                         }
65                     }
66 1         16         $text = join "\n", @lines, $text;
67                 }
68 107         708     return $text;
69             }
70              
71             sub after_filter {
72 107     107 0 1840     my ($self, $c, $out) = @_;
73 107         408     $out = $self->prepend_metadata($c, $out);
74 107         803     $out = $self->SUPER::after_filter($c, $out);
75 107         366     chomp $out;
76 107         813     return <<EOF;
77             <SJIS-MAC>
78             $out
79             EOF
80             }
81              
82             sub list_marker {
83 45     45 0 78     my ($style, $i) = @_;
84              
85 45 100       94     if ($style eq 'disc') {
86 11         14         my $code = 0x2776 - 1;
87 11         83         return sprintf "<CharStyle:丸文字><%x><CharStyle:>", $code + $i;
88                 }
89              
90 34 100       107     if ($style eq 'circle') {
91 13         14         my $code = 0x2460 - 1;
92 13         78         return sprintf "<CharStyle:丸文字><%x><CharStyle:>", $code + $i;
93                 }
94              
95 21 100       44     if ($style eq 'square') {
96 12         72         return sprintf "<cTypeface:B><cFont:A-OTF ゴシックMB101 Pro><cotfcalt:0><cotfl:nalt,7>%d<cTypeface:><cFont:><cotfcalt:><cotfl:>", $i;
97                 }
98              
99 9 50       182     if ($style eq 'alpha') {
100 9         73         return sprintf "<CharStyle:丸文字><cLigatures:0><cOTFContAlt:0><cOTFeatureList:nalt,3>%s<cLigatures:><cOTFContAlt:><cOTFeatureList:><CharStyle:>", chr($i + 96);
101                 }
102             }
103              
104             sub replace_list_maker {
105 193     193 0 320     my $text = shift;
106              
107             # convert
108 193         406     $text =~ s/\(d(\d+)\)/list_marker('disc', $1)/eg;
  4         13  
109 193         351     $text =~ s/\(c(\d+)\)/list_marker('circle', $1)/eg;
  8         15  
110 193         373     $text =~ s/\(s(\d+)\)/list_marker('square', $1)/eg;
  5         10  
111 193         313     $text =~ s/\(a(\d+)\)/list_marker('alpha', $1)/eg;
  4         9  
112              
113             # escape
114 193         354     $text =~ s/\(\\([dcsa]?\d+)\)/($1)/g;
115              
116 193         1143     return $text;
117             }
118              
119             sub escape_html {
120 194     194 0 535     my $html = shift;
121 194         781     $html =~ s/([<>])/<005C>$1/g;
122 194         461     return $html;
123             }
124              
125             sub fallback_to_escaped_html {
126 1     1 0 6     return escape_html(fallback_to_html(shift));
127             }
128              
129             sub blank_line {
130 31     31 0 54     my $c = shift;
131 31 100 66     112     if ($c->blank_style and $c->blank_style eq 'full') {
132 3         50         return '<ParaStyle:本文>';
133                 } else {
134 28 100       235         $c->in_column ? return '<ParaStyle:コラム半行アキ>' : return '<ParaStyle:半行アキ>'
135                 }
136             }
137              
138             case default => sub {
139                 my ($c, $h) = @_;
140                 $h->as_HTML('', '', {});
141             };
142              
143             case text => sub {
144                 my ($c, $text) = @_;
145                 $text = escape_html($text);
146              
147                 if ($text =~ m!\(注:! or $c->in_footnote) {
148                     $text = replace_note_parenthesis($c, $text, '注');
149                     $text =~ s!◆注/◆!<cstyle:上付き><fnStart:><pstyle:注釈>!g;
150                     $text =~ s!◆/注◆!<fnEnd:><cstyle:>!g;
151                 }
152              
153             # 各行の余計な先頭空白を取り除く (全角は字下げ指定なので残す)
154                 $text =~ s/^[ ]+//mg;
155              
156             # 改行を取り除く
157                 $text =~ s/(\n|\r)//g;
158             # キャプション
159                 if ($text =~ s!^●(.+?)::(.+)!●$1\t$2!) {
160                     $text =~ s!\[(.+)\]$!\n$1!;
161                 }
162              
163                 return replace_list_maker $text;
164             };
165              
166             case "h1" => sub {
167                 my ($c, $h) = @_;
168                 return sprintf "<ParaStyle:大見出し>%s\n", $c->parse_element($h);
169             };
170              
171             case "h2" => sub {
172                 my ($c, $h) = @_;
173                 return sprintf "<ParaStyle:中見出し>%s\n", $c->parse_element($h);
174             };
175              
176             case "h3" => sub {
177                 my ($c, $h) = @_;
178                 return sprintf "<ParaStyle:小見出し>%s\n", $c->parse_element($h);
179             };
180              
181             case "h4" => sub {
182                 my ($c, $h) = @_;
183                 return sprintf "<ParaStyle:コラムタイトル>%s\n", $c->parse_element($h);
184             };
185              
186             case "h5" => sub {
187                 my ($c, $h) = @_;
188                 return sprintf "<ParaStyle:コラム小見出し>%s\n", $c->parse_element($h);
189             };
190              
191             case strong => sub {
192                 my ($c, $h) = @_;
193                 return sprintf "<CharStyle:太字>%s<CharStyle:>", $c->parse_element($h);
194             };
195              
196             case em => sub {
197                 my ($c, $h) = @_;
198                 my $ret;
199                 $ret .= $c->use_special_italic ? '<CharStyle:イタリック(変形斜体)>' : '<CharStyle:イタリック(変形斜体)>';
200                 $ret .= $c->parse_element($h);
201                 $ret .= '<CharStyle:>';
202                 return $ret;
203             };
204              
205             case code => sub {
206                 my ($c, $h) = @_;
207                 return sprintf "<CharStyle:コマンド>%s<CharStyle:>", $c->parse_element($h);
208             };
209              
210             case p => sub {
211                 my ($c, $h) = @_;
212                 my $text = $c->parse_element($h);
213                 if ($text !~ /^[\s ]+$/) {
214                     if ($text =~ /^<ParaStyle:キャプション>/) { ## Dirty Hack...
215                         return $text;
216                     }
217              
218                     my $label;
219                     if ($c->in_column) {
220                         $label = 'コラム本文';
221                     } elsif ($c->in_quote_block) {
222                         $label = '引用';
223                     } else {
224                         $label = '本文';
225                     }
226                     return sprintf "<ParaStyle:%s>%s\n", $label, $text;
227                 }
228             };
229              
230             case kbd => sub {
231                 my ($c, $h) = @_;
232                 sprintf "<cFont:KeyMother>%s<cFont:>" , $c->parse_element($h);
233             };
234              
235             case span => sub {
236                 my ($c, $h) = @_;
237                 if ($h->attr('class') eq 'red') {
238                     return sprintf "<CharStyle:赤字>%s<CharStyle:>", $c->parse_element($h);
239                 }
240                 elsif ($h->attr('class') eq 'ruby') {
241                     my $ruby = $h->as_trimmed_text;
242                     $ruby =~ s!(.+)\((.+)\)!<cr:1><crstr:$2><cmojir:0>$1<cr:><crstr:><cmojir:>!;
243                     return $ruby;
244                 }
245              
246             ## ここでは inao に変換して、後で自由置換で変換
247                 elsif ($h->attr('class') eq 'symbol') {
248                     return sprintf "◆%s◆", $c->parse_element($h);
249                 }
250              
251                 else {
252                     return fallback_to_escaped_html($h);
253                 }
254             };
255              
256             case blockquote => sub {
257                 my ($c, $h) = @_;
258                 $c->in_quote_block(1);
259                 my $blockquote = $c->parse_element($_);
260                 $c->in_quote_block(0);
261                 return $blockquote;
262             };
263              
264             case div => sub {
265                 my ($c, $h) = @_;
266              
267                 if ($h->attr('class') eq 'column') {
268                     $c->in_column(1);
269              
270             # HTMLとして取得してcolumn自信のdivタグを削除
271                     my $md = $h->as_HTML('');
272                     $md =~ s/^<div.+?>//;
273                     $md =~ s/<\/div>$//;
274              
275                     my $column = $c->parse_markdown($md);
276                     $c->in_column(0);
277                     return $column;
278                 } else {
279                     return fallback_to_escaped_html($h);
280                 }
281             };
282              
283             case ul => sub {
284                 my ($c, $h) = @_;
285                 my $label = $c->in_column ? 'コラム箇条書き' : '箇条書き';
286              
287                 if ($c->in_list) {
288                     my $ret = "\n";
289                     for ($h->content_list) {
290                         if ($_->tag eq 'li') {
291                             $ret .= sprintf "<ParaStyle:%s2階層目>・%s\n", $label, $c->parse_element($_);
292                         }
293                     }
294                     chomp $ret;
295                     return $ret;
296                 } else {
297                     my $ret;
298                     for my $list ($h->content_list) {
299                         $c->in_list(1);
300                         $ret .= sprintf("<ParaStyle:%s>・%s\n", $label, $c->parse_element($list));
301                         $c->in_list(0);
302                     }
303                     chomp $ret;
304                     my $blank = blank_line($c);
305                     return <<EOF;
306             $blank
307             $ret
308             EOF
309                 }
310             };
311              
312             case ol => sub {
313                 my ($c, $h) = @_;
314                 my $out = '';
315                 my $label = $c->in_column ? 'コラム箇条書き' : '箇条書き';
316                 my $style = $h->attr('class') || $c->default_list;
317                 my $i = 0;
318                 for my $list ($h->find('li')) {
319                     $out .= sprintf(
320                         "<ParaStyle:%s>%s%s\n",
321                         $label,
322                         list_marker($style, ++$i),
323                         $c->parse_element($list)
324                     );
325                 }
326                 chomp $out;
327                 my $blank = blank_line($c);
328                 return <<EOF;
329             $blank
330             $out
331             EOF
332             };
333              
334             case pre => sub {
335                 my ($c, $h) = @_;
336                 $c->in_code_block(1);
337              
338                 my $code = $h->find('code');
339                 my $text = $code ? $code->as_text : '';
340                 $text = escape_html($text);
341              
342                 my $list_label = 'リスト';
343                 my $comment_label = 'リストコメント';
344              
345             # 「!!! cmd」で始まるコードブロックはコマンドライン(黒背景)
346                 if ($text =~ /!!!(\s+)?cmd/) {
347                     $text =~ s/.+?\n//;
348                     $list_label .= '白文字';
349                     $comment_label .= '白地黒文字';
350                 }
351              
352             ## リストスタイル
353                 $text = replace_list_maker($text);
354              
355             # 文字数カウント
356                 my $max = max(map { visual_length($_) } split /\r?\n/, $text);
357                 if ($text =~ /^●/) {
358                     if ($max > $c->max_list_length) {
359                         log warn => "リストは" . $c->max_list_length . "文字まで!(現在${max}使用):\n$text\n\n";
360                     }
361                 }
362                 else {
363                     if ($max > $c->max_inline_list_length) {
364                         log warn => "本文埋め込みリストは" . $c->max_inline_list_length . "文字まで!(現在${max}使用):\n$text\n\n";
365                     }
366                 }
367              
368             # コード内コメント
369                 if ($text =~ m!\(注:! or $c->in_footnote) {
370                     $text = replace_note_parenthesis($c, $text, '注');
371                     $text =~ s!◆注/◆!<CharStyle:$comment_label> !g;
372                     $text =~ s!◆/注◆! <CharStyle:>!g;
373                 }
374              
375             # コード内強調
376                 $text =~ s!\*\*(.+?)\*\*!<CharStyle:コマンド太字>$1<CharStyle:>!g;
377              
378             # コード内イタリック
379                 $text =~ s!\___(.+?)\___!<CharStyle:イタリック(変形斜体)>$1<CharStyle:>!g;
380              
381                 chomp $text;
382              
383                 $c->in_code_block(0);
384              
385                 my $has_caption;
386                 my @lines = map {
387                     if (m/^●(.+?)::(.+)/) {
388                         $has_caption = 1;
389                         sprintf "<ParaStyle:キャプション>%s\t%s", $1, $2;
390                     }
391                     else {
392                         sprintf "<ParaStyle:%s>%s", $list_label, $_;
393                     }
394                 } split /\n/, $text;
395              
396                 my $lines = join "\n", @lines;
397                 if ($has_caption) {
398                     return $lines . "\n";
399                 } else {
400                     my $blank = blank_line($c);
401                     return <<EOF;
402             $blank
403             $lines
404             EOF
405                 }
406             };
407              
408             case a => sub {
409                 my ($c, $h) = @_;
410                 my $url = $h->attr('href');
411                 my $title = $c->parse_element($h);
412                 if ($url and $title) {
413                     return sprintf "%s<cstyle:上付き><fnStart:><pstyle:注釈>%s<fnEnd:><cstyle:>", $title, $url;
414                 } else {
415                     return fallback_to_escaped_html($h);
416                 }
417             };
418              
419             case img => sub {
420                 my ($c, $h) = @_;
421                 $c->{img_number} += 1;
422              
423                 my $template = <<EOF;
424             <ParaStyle:キャプション>●図%d\t%s
425             <ParaStyle:赤字段落>%s
426             EOF
427              
428                 return sprintf (
429                     $template,
430                     $c->{img_number},
431                     $h->attr('alt') || $h->attr('title'),
432                     $h->attr('src')
433                 );
434             };
435              
436             case dl => sub {
437                 my ($c, $h) = @_;
438                 my $out = '';
439                 my $label = $c->in_column ? 'コラム箇条書き' : '箇条書き';
440                 for ($h->descendants) {
441                     if ($_->tag eq 'dt') {
442                         $out .= sprintf "<ParaStyle:%s>・%s\n", $label, $c->parse_element($_);
443                     } elsif ($_->tag eq 'dd') {
444                         $out .= sprintf "<ParaStyle:%s説明>%s\n", $label, $c->parse_element($_);
445                     }
446                 }
447                 chomp $out;
448                 my $blank = blank_line($c);
449                 return <<EOF;
450             $blank
451             $out
452             EOF
453             };
454              
455             case table => sub {
456                 my ($c, $h) = @_;
457                 my $out = '';
458              
459                 my $summary = $h->attr('summary') || '';
460                 $summary =~ m!(.+?)::(.+)!;
461                 $out .= sprintf "<ParaStyle:キャプション>%s\t%s\n", $1, $2;
462              
463                 for my $table ($h->find('tr')) {
464                     if (my $header = join "\t", map { $c->parse_element($_) } $table->find('th')) {
465                         $out .= sprintf "<ParaStyle:表見出し行>%s\n", $header;
466                     }
467              
468                     if (my $data = join "\t", map { $c->parse_element($_) } $table->find('td')) {
469                         $out .= sprintf "<ParaStyle:表>%s\n", $data;
470                     }
471                 }
472              
473                 return $out;
474             };
475              
476             case hr => sub {
477                 my ($c, $h) = @_;
478                 return "<ParaStyle:区切り線>\n"
479             };
480              
481             1;
482