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   928 use strict;
  2         5  
  2         74  
11 2     2   11 use warnings;
  2         4  
  2         74  
12 2     2   12 use ChordPro::Output::Common;
  2         4  
  2         116  
13 2     2   13 use ChordPro::Utils qw();
  2         4  
  2         3404  
14              
15             sub generate_songbook {
16 3     3 0 10 my ( $self, $sb ) = @_;
17              
18 3         6 my @book;
19 3   50     18 my $cfg = $::config->{html} // {};
20 3   50     18 $cfg->{styles}->{display} //= "chordpro.css";
21 3   50     11 $cfg->{styles}->{print} //= "chordpro_print.css";
22              
23 3         12 push( @book,
24             '',
25             '',
26             '' );
27 3         8 foreach ( sort keys %{ $cfg->{styles} } ) {
  3         26  
28             push( @book,
29 6 100       53 '
30             ( $_ =~ /^(display|default)$/ ? "" : qq{ media="$_"} ).
31             '>' );
32             }
33 3         11 push( @book, '',
34             '',
35             );
36              
37 3         7 foreach my $song ( @{$sb->{songs}} ) {
  3         14  
38 3         6 push( @book, @{ generate_song($song) } );
  3         12  
39             }
40              
41 3         16 push( @book, "", "" );
42 3         17 \@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 6 my ( $s ) = @_;
51              
52 3         12 my $tidy = $::options->{tidy};
53 3         7 $single_space = $::options->{'single-space'};
54 3         8 $lyrics_only = $::config->{settings}->{'lyrics-only'};
55 3         25 $s->structurize;
56 3         38 $layout = Text::Layout::Text->new;
57              
58 3         7 my @s;
59              
60 3   50     14 for ( $s->{title} // "Untitled" ) {
61 3         21 push( @s,
62             '
',
63             '',
70             '
' . nhtml($_) . '
',
71             );
72              
73             }
74 3 50       15 if ( defined $s->{subtitle} ) {
75             push( @s,
76 3         7 map { '
' . nhtml($_) . '
' }
77 3         9 @{$s->{subtitle}} );
  3         9  
78             }
79              
80 3 50       11 push(@s, "") if $tidy;
81              
82 3         6 foreach my $elt ( @{$s->{body}} ) {
  3         10  
83              
84 33 50       81 if ( $elt->{type} eq "empty" ) {
85 0         0 push(@s, "***SHOULD NOT HAPPEN***");
86 0         0 next;
87             }
88              
89 33 100       72 if ( $elt->{type} eq "colb" ) {
90             # push(@s, "{column_break}");
91 3         10 next;
92             }
93              
94 30 100       62 if ( $elt->{type} eq "newpage" ) {
95             # push(@s, "{new_page}");
96 3         7 next;
97             }
98              
99 27 50       54 if ( $elt->{type} eq "songline" ) {
100 0         0 push(@s, songline( $s, $elt ));
101 0         0 next;
102             }
103              
104 27 50       55 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       61 if ( exists $elt->{body} ) {
117 12         35 push( @s, '
' );
118 12         21 foreach my $e ( @{$elt->{body}} ) {
  12         26  
119 51 50       117 if ( $e->{type} eq "empty" ) {
120 0         0 push( @s, "" );
121 0         0 next;
122             }
123 51 100       103 if ( $e->{type} eq "songline" ) {
124 48         99 push( @s, songline( $s, $e ) );
125 48         125 next;
126             }
127 3 50       14 if ( $e->{type} =~ /^comment(_\w+)?$/ ) {
128             push( @s,
129             '
' .
130 0         0 '' . nhtml($e->{text}) . '' );
131 0         0 next;
132             }
133 3 50 33     22 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         25 push( @s, '' );
142 12 50       27 push( @s, "" ) if $tidy;
143 12         23 next;
144             }
145              
146 15 100 100     65 if ( $elt->{type} eq "comment" || $elt->{type} eq "comment_italic" ) {
147             push( @s,
148             '
' .
149 12         42 '' . nhtml($elt->{text}) . '' );
150 12 50       30 push( @s, "" ) if $tidy;
151 12         24 next;
152             }
153              
154 3 50       14 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       14 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         9 push( @s, '' ); # song
179 3         101 \@s;
180             }
181              
182             sub songline {
183 48     48 0 83 my ( $song, $elt ) = @_;
184              
185 48         78 my $t_line = "";
186              
187 48   100     215 $elt->{chords} //= [ '' ];
188             my @c = map {
189 111 100       349 $_ eq "" ? "" : $song->{chordsinfo}->{$_->key }->name
190 48         73 } @{ $elt->{chords} };
  48         101  
191              
192 48 100 66     139 if ( $lyrics_only
      100        
      100        
193             or
194             $single_space && ! ( $elt->{chords} && join( "", @c ) =~ /\S/ )
195             ) {
196 24         114 $t_line = join( "", @{ $elt->{phrases} } );
  24         106  
197 24         123 $t_line =~ s/\s+$//;
198 24         53 return ( '', ', ', ',
199             '
200             ' ' . nhtml($t_line) . '
201             '
202             '
' );
203             }
204              
205 24 50       303 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         113 map { '' . nhtml($_) . '
223             ( @c ) ),
224             '
225             '
226             ' ' . join( '',
227 66 100       267 map { ( $_ =~ s/^\s+// ? '' : '' ) . nhtml($_) . '
228 24         171 ( @{ $elt->{phrases} } ) ),
  24         58  
229             '
230             '
' );
231             }
232              
233             sub nhtml {
234 174 50   174 0 338 return unless defined $_[0];
235 174         470 $layout->set_markup(shift);
236 174         6798 html($layout->render);
237             }
238              
239             sub html {
240 174     174 0 266 my $t = shift;
241 174         278 $t =~ s/&/&/g;
242 174         255 $t =~ s/
243 174         245 $t =~ s/>/>/g;
244 174         806 $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   17 use parent 'Text::Layout';
  2         5  
  2         17  
252              
253             # Eliminate warning when HTML backend is loaded together with Text backend.
254 2     2   13511 no warnings 'redefine';
  2         5  
  2         410  
255              
256             sub new {
257 3     3   12 my ( $pkg, @data ) = @_;
258 3         30 my $self = $pkg->SUPER::new;
259 3         64 $self;
260             }
261              
262             sub render {
263 174     174   280 my ( $self ) = @_;
264 174         273 my $res = "";
265 174         228 foreach my $fragment ( @{ $self->{_content} } ) {
  174         343  
266 144 50       370 next unless length($fragment->{text});
267 144         314 $res .= ChordPro::Utils::fq($fragment->{text});
268             }
269 174         389 $res;
270             }
271              
272             1;