File Coverage

blib/lib/ChordPro/Output/HTML.pm
Criterion Covered Total %
statement 112 143 78.3
branch 33 60 55.0
condition 18 25 72.0
subroutine 13 13 100.0
pod 0 5 0.0
total 176 246 71.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package ChordPro::Output::HTML;
4              
5             # Produce nice viewable HMTL output.
6             #
7             # You should be able to print it using a decent browser (notexisting)
8             # or a formatting tool like weasyprint.
9              
10 2     2   966 use strict;
  2         4  
  2         90  
11 2     2   13 use warnings;
  2         10  
  2         62  
12 2     2   14 use ChordPro::Output::Common;
  2         4  
  2         145  
13 2     2   16 use ChordPro::Utils qw();
  2         7  
  2         3422  
14              
15             sub generate_songbook {
16 3     3 0 12 my ( $self, $sb ) = @_;
17              
18 3         7 my @book;
19 3   50     15 my $cfg = $::config->{html} // {};
20 3   50     13 $cfg->{styles}->{display} //= "chordpro.css";
21 3   50     12 $cfg->{styles}->{print} //= "chordpro_print.css";
22              
23 3         14 push( @book,
24             '',
25             '',
26             '' );
27 3         7 foreach ( sort keys %{ $cfg->{styles} } ) {
  3         24  
28             push( @book,
29 6 100       55 '
30             ( $_ =~ /^(display|default)$/ ? "" : qq{ media="$_"} ).
31             '>' );
32             }
33 3         18 push( @book, '',
34             '',
35             );
36              
37 3         6 foreach my $song ( @{$sb->{songs}} ) {
  3         8  
38 3         6 push( @book, @{ generate_song($song) } );
  3         14  
39             }
40              
41 3         16 push( @book, "", "" );
42 3         16 \@book;
43             }
44              
45             my $single_space = 0; # suppress chords line when empty
46             my $lyrics_only = 0; # suppress all chords lines
47             my $layout;
48              
49             sub generate_song {
50 3     3 0 9 my ( $s ) = @_;
51              
52 3         11 my $tidy = $::options->{tidy};
53 3         6 $single_space = $::options->{'single-space'};
54 3         9 $lyrics_only = $::config->{settings}->{'lyrics-only'};
55 3         17 $s->structurize;
56 3         42 $layout = Text::Layout::Text->new;
57              
58 3         8 my @s;
59              
60 3   50     15 for ( $s->{title} // "Untitled" ) {
61 3         19 push( @s,
62             '
',
63             '',
70             '
' . nhtml($_) . '
',
71             );
72              
73             }
74 3 50       12 if ( defined $s->{subtitle} ) {
75             push( @s,
76 3         8 map { '
' . nhtml($_) . '
' }
77 3         5 @{$s->{subtitle}} );
  3         10  
78             }
79              
80 3 50       11 push(@s, "") if $tidy;
81              
82 3         5 foreach my $elt ( @{$s->{body}} ) {
  3         8  
83              
84 33 50       74 if ( $elt->{type} eq "empty" ) {
85 0         0 push(@s, "***SHOULD NOT HAPPEN***");
86 0         0 next;
87             }
88              
89 33 100       68 if ( $elt->{type} eq "colb" ) {
90             # push(@s, "{column_break}");
91 3         7 next;
92             }
93              
94 30 100       73 if ( $elt->{type} eq "newpage" ) {
95             # push(@s, "{new_page}");
96 3         7 next;
97             }
98              
99 27 50       50 if ( $elt->{type} eq "songline" ) {
100 0         0 push(@s, songline( $s, $elt ));
101 0         0 next;
102             }
103              
104 27 50       57 if ( $elt->{type} eq "tab" ) {
105 0         0 my $p = '
';
106 0         0 foreach ( @{ $elt->{body} } ) {
  0         0  
107 0 0       0 next if $_->{type} eq "set";
108 0         0 push( @s, $p . html($_->{text}) );
109 0         0 $p = "";
110             }
111 0         0 push( @s, $p . '' );
112 0 0       0 push( @s, "") if $tidy;
113 0         0 next;
114             }
115              
116 27 100       55 if ( exists $elt->{body} ) {
117 12         38 push( @s, '
' );
118 12         20 foreach my $e ( @{$elt->{body}} ) {
  12         28  
119 51 50       139 if ( $e->{type} eq "empty" ) {
120 0         0 push( @s, "" );
121 0         0 next;
122             }
123 51 100       100 if ( $e->{type} eq "songline" ) {
124 48         85 push( @s, songline( $s, $e ) );
125 48         112 next;
126             }
127 3 50       13 if ( $e->{type} =~ /^comment(_\w+)?$/ ) {
128             push( @s,
129             '
' .
130 0         0 '' . nhtml($e->{text}) . '' );
131 0         0 next;
132             }
133 3 50 33     20 if ( $e->{type} eq "set" && $e->{name} eq "label" ) {
134             push( @s,
135 0         0 '
' . nhtml($e->{value}) . '
'
136             );
137 0         0 next;
138             }
139              
140             }
141 12         24 push( @s, '' );
142 12 50       31 push( @s, "" ) if $tidy;
143 12         25 next;
144             }
145              
146 15 100 100     76 if ( $elt->{type} eq "comment" || $elt->{type} eq "comment_italic" ) {
147             push( @s,
148             '
' .
149 12         43 '' . nhtml($elt->{text}) . '' );
150 12 50       44 push( @s, "" ) if $tidy;
151 12         24 next;
152             }
153              
154 3 50       17 if ( $elt->{type} eq "image" ) {
155 0         0 my @args;
156 0         0 while ( my($k,$v) = each( %{ $elt->{opts} } ) ) {
  0         0  
157 0         0 push( @args, "$k=\"$v\"" );
158             }
159             # First shot code. Fortunately (not surprisingly :))
160             # HTML understands most arguments.
161             push( @s,
162             '
' .
163 0         0 '
164             "@args" . "/>" .
165             '' );
166 0 0       0 push( @s, "" ) if $tidy;
167              
168             }
169              
170 3 50       10 if ( $elt->{type} eq "control" ) {
171 0 0       0 if ( $elt->{name} eq "lyrics-only" ) {
172             $lyrics_only = $elt->{value}
173 0 0       0 unless $lyrics_only > 1;
174             }
175             }
176             }
177              
178 3         23 push( @s, '' ); # song
179 3         70 \@s;
180             }
181              
182             sub songline {
183 48     48 0 83 my ( $song, $elt ) = @_;
184              
185 48         74 my $t_line = "";
186              
187 48   100     168 $elt->{chords} //= [ '' ];
188             my @c = map {
189 111 100       341 $_ eq "" ? "" : $song->{chordsinfo}->{$_->key }->name
190 48         67 } @{ $elt->{chords} };
  48         95  
191              
192 48 100 66     156 if ( $lyrics_only
      100        
      100        
193             or
194             $single_space && ! ( $elt->{chords} && join( "", @c ) =~ /\S/ )
195             ) {
196 24         117 $t_line = join( "", @{ $elt->{phrases} } );
  24         84  
197 24         123 $t_line =~ s/\s+$//;
198 24         57 return ( '', ', ', ',
199             '
200             ' ' . nhtml($t_line) . '
201             '
202             '
' );
203             }
204              
205 24 50       374 if ( $::config->{settings}->{'chords-under'} ) {
206             return ( '', ', ' } ', ', ' } ',
207             '
208             ' ' . join( '',
209 0 0       0 map { ( $_ =~ s/^\s+// ? '' : '' ) . nhtml($_) . '
210 0         0 ( @{ $elt->{phrases} } ) ),
211             '
212             '
213             ' ' . join( '',
214 0         0 map { '' . nhtml($_) . '
  0         0  
215             ( @c ) ),
216             '
217             '
' );
218             }
219             return ( '', ', ' } ', ', ' } ',
220             '
221             ' ' . join( '',
222 66         115 map { '' . nhtml($_) . '
223             ( @c ) ),
224             '
225             '
226             ' ' . join( '',
227 66 100       260 map { ( $_ =~ s/^\s+// ? '' : '' ) . nhtml($_) . '
228 24         167 ( @{ $elt->{phrases} } ) ),
  24         69  
229             '
230             '
' );
231             }
232              
233             sub nhtml {
234 174 50   174 0 352 return unless defined $_[0];
235 174         438 $layout->set_markup(shift);
236 174         6972 html($layout->render);
237             }
238              
239             sub html {
240 174     174 0 256 my $t = shift;
241 174         289 $t =~ s/&/&/g;
242 174         236 $t =~ s/
243 174         232 $t =~ s/>/>/g;
244 174         760 $t;
245             }
246              
247             # Temporary. Eventually we'll have a decent HTML backend for Text::Layout.
248              
249             package Text::Layout::Text;
250              
251 2     2   18 use parent 'Text::Layout';
  2         10  
  2         20  
252              
253             # Eliminate warning when HTML backend is loaded together with Text backend.
254 2     2   13221 no warnings 'redefine';
  2         13  
  2         397  
255              
256             sub new {
257 3     3   10 my ( $pkg, @data ) = @_;
258 3         23 my $self = $pkg->SUPER::new;
259 3         61 $self;
260             }
261              
262             sub render {
263 174     174   290 my ( $self ) = @_;
264 174         254 my $res = "";
265 174         236 foreach my $fragment ( @{ $self->{_content} } ) {
  174         325  
266 144 50       362 next unless length($fragment->{text});
267 144         329 $res .= ChordPro::Utils::fq($fragment->{text});
268             }
269 174         377 $res;
270             }
271              
272             1;