File Coverage

lib/ChordPro/Song.pm
Criterion Covered Total %
statement 1006 1396 72.0
branch 546 972 56.1
condition 266 514 51.7
subroutine 64 66 96.9
pod 0 40 0.0
total 1882 2988 62.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3 90     90   696 use utf8;
  90         232  
  90         853  
4              
5             package main;
6              
7             our $options;
8             our $config;
9              
10             package ChordPro::Song;
11              
12 90     90   8804 use strict;
  90         225  
  90         2851  
13 90     90   549 use warnings;
  90         187  
  90         4789  
14              
15 90     90   543 use ChordPro;
  90         207  
  90         2411  
16 90     90   465 use ChordPro::Files;
  90         183  
  90         15346  
17 90     90   649 use ChordPro::Paths;
  90         195  
  90         4902  
18 90     90   582 use ChordPro::Chords;
  90         200  
  90         2539  
19 90     90   44297 use ChordPro::Chords::Appearance;
  90         293  
  90         5212  
20 90     90   735 use ChordPro::Chords::Parser;
  90         204  
  90         3065  
21 90     90   528 use ChordPro::Output::Common;
  90         182  
  90         5968  
22 90     90   588 use ChordPro::Utils;
  90         178  
  90         14780  
23 90     90   647 use ChordPro::Symbols qw( is_strum );
  90         175  
  90         942  
24              
25 90     90   536 use Carp;
  90         176  
  90         5635  
26 90     90   594 use List::Util qw(any);
  90         191  
  90         6207  
27 90     90   553 use Storable qw(dclone);
  90         195  
  90         4603  
28 90     90   533 use feature 'state';
  90         181  
  90         10284  
29 90     90   580 use Text::ParseWords qw(quotewords);
  90         188  
  90         6246  
30 90     90   599 use Ref::Util qw( is_arrayref );
  90         192  
  90         2910553  
31              
32             # Parser context.
33             my $def_context = "";
34             my $in_context = $def_context;
35             my $skip_context = 0;
36             my $grid_arg; # also used for grilles?
37             my $grid_cells; # also used for grilles?
38             my $grid_type = 0; # 0 = chords, 1,2 = strums
39             my @grille;
40              
41             # Local transposition.
42             my $xpose = 0;
43             my $xpose_dir;
44             my $capo;
45              
46             # Used chords, in order of appearance.
47             my @used_chords;
48              
49             # Chorus lines, if any.
50             my @chorus;
51             my $chorus_xpose = 0;
52             my $chorus_xpose_dir = 0;
53              
54             # Memorized chords.
55             my $cctag; # current cc name
56             my %memchords; # all sections
57             my $memchords; # current section
58             my $memcrdinx; # chords tally
59             my $memorizing; # if memorizing (a.o.t. recalling)
60              
61             # Keep track of unknown chords, to avoid dup warnings.
62             my %warned_chords;
63              
64             our $re_chords; # for chords
65             my $propitems_re = propitems_re();
66             my $intervals; # number of note intervals
67             my @labels; # labels used
68              
69             # Normally, transposition and subtitutions are handled by the parser.
70             my $decapo;
71             my $no_transpose; # NYI
72             my $xcmov; # transcode to movable system
73             my $no_substitute;
74              
75             # Stack for properties like textsize.
76             my %propstack;
77              
78             my $diag; # for diagnostics
79             my @diag; # keep track of includes
80             my $lineinfo; # keep lineinfo
81             my $assetid = "001"; # for assets
82              
83             # Constructor.
84              
85             sub new {
86 216     216 0 225076 my ( $pkg, $opts ) = @_;
87              
88 216   66     1572 my $filesource = $opts->{filesource} || $opts->{_filesource};
89              
90 216         547 $xpose = 0;
91 216         999 $grid_arg = [ 4, 4, 1, 1, "" ]; # 1+4x4+1
92 216         627 $in_context = $def_context;
93 216         689 @used_chords = ();
94 216         617 %warned_chords = ();
95 216         655 %memchords = ();
96 216         549 %propstack = ();
97 216         1483 ChordPro::Chords::reset_song_chords();
98 216         488 @labels = ();
99 216         2145 @chorus = ();
100 216         634 $capo = undef;
101 216         461 $xcmov = undef;
102 216         1001 upd_config();
103              
104 216   33     2052 $diag->{format} = $opts->{diagformat} // $config->{diagnostics}->{format};
105 216         733 $diag->{file} = $filesource;
106 216         622 $diag->{line} = 0;
107 216         759 $diag->{orig} = "(at start of song)";
108              
109             bless { chordsinfo => {},
110             meta => {},
111             generate => $opts->{generate},
112 216         2387 structure => "linear",
113             } => $pkg;
114             }
115              
116             sub upd_config {
117 441     441 0 1809 $decapo = $config->{settings}->{decapo};
118 441         1265 $lineinfo = $config->{settings}->{lineinfo};
119 441         906 $intervals = @{ $config->{notes}->{sharp} };
  441         1725  
120             }
121              
122       214     sub ::break() {}
123              
124             sub is_gridstrum($) {
125 235 50   235 0 1388 $_[0] == 1 || $_[0] == 2;
126             }
127              
128             sub parse_song {
129 214     214 0 934 my ( $self, $lines, $linecnt, $meta, $defs ) = @_;
130 214 50       1090 die("OOPS! Wrong meta") unless ref($meta) eq 'HASH';
131 214         373839 local $config = dclone($config);
132              
133 214 50       2031 warn("Processing song ", $diag->{file}, "...\n") if $options->{verbose};
134 214         1059 ::break();
135 214         531 my @configs;
136             #
137 214 50       1392 if ( $lines->[0] =~ /^##config:\s*json/ ) {
138 0         0 my $cf = "";
139 0         0 shift(@$lines);
140 0         0 $$linecnt++;
141 0         0 while ( @$lines ) {
142 0 0       0 if ( $lines->[0] =~ /^# (.*)/ ) {
143 0         0 $cf .= $1 . "\n";
144 0         0 shift(@$lines);
145 0         0 $$linecnt++;
146             }
147             else {
148 0         0 last;
149             }
150             }
151 0 0       0 if ( $cf ) {
152 0         0 my $prename = "__PRECFG__";
153 0         0 my $precfg = ChordPro::Config->new( json_load( $cf, $prename ) );
154 0         0 $precfg->precheck($prename);
155 0         0 push( @configs, $precfg->prep_configs($prename) );
156             }
157             }
158             # Load song-specific config, if any.
159 214 50 66     1548 if ( !$options->{nosongconfig} && $diag->{file} ) {
160 107 50       488 if ( $options->{verbose} ) {
161 0         0 my $this = ChordPro::Chords::get_parser();
162 0 0       0 $this = defined($this) ? $this->{system} : "";
163 0         0 print STDERR ("Parsers at start of ", $diag->{file}, ":");
164             print STDERR ( $this eq $_ ? " *" : " ", "$_")
165 0 0       0 for keys %{ ChordPro::Chords::Parser->parsers };
  0         0  
166 0         0 print STDERR ("\n");
167             }
168 107 50 33     843 if ( $meta && $meta->{__config} ) {
169 0         0 my $cf = delete($meta->{__config})->[0];
170 0 0       0 warn("Config[song]: $cf\n") if $options->{verbose};
171 0         0 my $have = ChordPro::Config::get_config( CP->findcfg($cf) );
172 0 0       0 die("Missing config: $cf\n") unless $have;
173 0         0 push( @configs, $have->prep_configs($cf) );
174             }
175             else {
176 107         407 for ( "prp", "json" ) {
177 214         1280 ( my $cf = $diag->{file} ) =~ s/\.\w+$/.$_/;
178 214 100       860 $cf .= ".$_" if $cf eq $diag->{file};
179 214 50       1679 next unless fs_test( s => $cf );
180 0 0       0 warn("Config[song]: $cf\n") if $options->{verbose};
181 0         0 my $have = ChordPro::Config::get_config($cf);
182 0         0 push( @configs, $have->prep_configs($cf) );
183 0         0 last;
184             }
185             }
186             }
187 214         1183 my $tuncheck = join("|",@{$config->{tuning}});
  214         1204  
188 214         812 foreach my $have ( @configs ) {
189 0 0       0 warn("Config[song*]: ", $have->{_src}, "\n") if $options->{verbose};
190 0         0 my $chords = $have->{chords};
191 0         0 $config->augment($have);
192 0 0       0 if ( $tuncheck ne join("|",@{$config->{tuning}}) ) {
  0         0  
193 0         0 my $res =
194             ChordPro::Chords::set_tuning($config);
195 0 0       0 warn( "Invalid tuning in config: ", $res, "\n" ) if $res;
196             }
197 0         0 ChordPro::Chords::reset_parser();
198 0         0 ChordPro::Chords::Parser->reset_parsers;
199 0 0       0 if ( $chords ) {
200 0         0 my $c = $chords;
201 0 0 0     0 if ( @$c && $c->[0] eq "append" ) {
202 0         0 shift(@$c);
203             }
204 0         0 foreach ( @$c ) {
205 0         0 my $res =
206             ChordPro::Chords::add_config_chord($_);
207             warn( "Invalid chord in config: ",
208 0 0       0 $_->{name}, ": ", $res, "\n" ) if $res;
209             }
210             }
211 0 0       0 if ( $options->{verbose} > 1 ) {
212 0 0       0 warn( "Processed ", scalar(@$chords), " chord entries\n")
213             if $chords;
214 0         0 warn( "Totals: ",
215             ChordPro::Chords::chord_stats(), "\n" );
216             }
217 0         0 if ( 0 && $options->{verbose} ) {
218             my $this = ChordPro::Chords::get_parser()->{system};
219             print STDERR ("Parsers after local config:");
220             print STDERR ( $this eq $_ ? " *" : " ", "$_")
221             for keys %{ ChordPro::Chords::Parser->parsers };
222             print STDERR ("\n");
223             }
224             }
225              
226 214         1740 $config->unlock;
227 214 50       529431 if ( my $a = $config->{parser}->{altbrackets} ) {
228 0 0       0 die("Config error: parser.altbrackets must be a 2-character string\n")
229             unless length($a) == 2;
230             }
231              
232 214 50       959 if ( %$defs ) {
233 0         0 prpadd2cfg( $config, %$defs );
234             }
235              
236 214         790 for ( qw( transpose transcode decapo lyrics-only ) ) {
237 856 100       2884 next unless defined $options->{$_};
238 18         148 $config->{settings}->{$_} = $options->{$_};
239             }
240             # Catch common error.
241 214 50       1433 unless ( UNIVERSAL::isa( $config->{instrument}, 'HASH' ) ) {
242 0   0     0 $config->{instrument} //= "guitar";
243             $config->{instrument} =
244             { type => $config->{instrument},
245 0         0 description => ucfirst $config->{instrument} };
246             do_warn( "Missing or invalid instrument - set to ",
247 0         0 $config->{instrument}->{type}, "\n" );
248             }
249              
250             # Remove inactive delegates.
251 214         550 while ( my ($k,$v) = each %{ $config->{delegates} } ) {
  1070         4039  
252             delete( $config->{delegates}->{$k} )
253 856 50 33     4261 if !$v || $v->{type} eq 'none';
254             }
255              
256             # And lock the config.
257 214         1689 $config->lock;
258              
259 214         579148 for ( keys %{ $config->{meta} } ) {
  214         1360  
260 214   50     1110 $meta->{$_} //= [];
261 214         972 my $v = $config->{meta}->{$_};
262 214 50       1360 $v = [ $v ] unless is_arrayref($v);
263 214 50       1038 if ( is_arrayref($meta->{$_}) ) {
264 0         0 push( @{ $meta->{$_} }, @$v );
  0         0  
265             }
266             else {
267 214         811 $meta->{$_} = $v;
268             }
269             }
270              
271 214         845 $no_transpose = $options->{'no-transpose'};
272 214         661 $no_substitute = $options->{'no-substitute'};
273 214         613 my $fragment = $options->{fragment};
274 214         932 my $target = $config->{settings}->{transcode};
275 214 100       840 if ( $target ) {
276 2 50       23 unless ( ChordPro::Chords::Parser->have_parser($target) ) {
277 2 50       15 if ( my $file = CP->findres("config/notes/$target.json") ) {
278 2         15 for ( ChordPro::Config::get_config($file) ) {
279 2         20 my $new = $config->hmerge($_);
280 2         8 local $config = $new;
281 2         35 ChordPro::Chords::Parser->new($new);
282             }
283             }
284             }
285 2 50       46 unless ( ChordPro::Chords::Parser->have_parser($target) ) {
286 0         0 die("No transcoder for ", $target, "\n");
287             }
288 2 50       10 warn("Got transcoder for $target\n") if $::options->{verbose};
289 2         17 ChordPro::Chords::set_parser($target);
290 2         9 my $p = ChordPro::Chords::get_parser;
291 2         11 $xcmov = $p->movable;
292 2 50       10 if ( $target ne $p->{system} ) {
293 0         0 ::dump(ChordPro::Chords::Parser->parsers);
294             warn("OOPS parser mixup, $target <> ",
295             ChordPro::Chords::get_parser->{system})
296 0         0 }
297 2         26 ChordPro::Chords::set_parser($self->{system});
298             }
299             else {
300 212         1091 $target = $self->{system};
301             }
302              
303 214         967 upd_config();
304 214         1796 $self->{source} = { file => $diag->{file}, line => 1 + $$linecnt };
305 214         1061 $self->{system} = $config->{notes}->{system};
306 214         799 $self->{config} = $config;
307 214 50       1304 $self->{meta} = $meta if $meta;
308 214         711 $self->{chordsinfo} = {};
309 214   66     1534 $target //= $self->{system};
310              
311             # Preprocessor.
312 214         1679 my $prep = make_preprocessor( $config->{parser}->{preprocess} );
313              
314             # Pre-fill meta data, if any. TODO? ALREADY DONE?
315 214 50       1245 if ( $options->{meta} ) {
316 0         0 while ( my ($k, $v ) = each( %{ $options->{meta} } ) ) {
  0         0  
317 0         0 $self->{meta}->{$k} = [ $v ];
318             }
319             }
320             $self->{meta}->{"chordpro.songsource"} = $diag->{file}
321 214 100       824 unless $::running_under_test;
322              
323             # Build regexp to split out chords.
324 214 100       1987 if ( $config->{settings}->{memorize} ) {
325 3         19 $re_chords = qr/(\[.*?\]|\^)/;
326             }
327             else {
328 211         1836 $re_chords = qr/(\[.*?\])/;
329             }
330              
331 214         654 my $skipcnt = 0;
332 214         946 while ( @$lines ) {
333 2201 50       5450 if ( $skipcnt ) {
334 0         0 $skipcnt--;
335             }
336             else {
337 2201         5788 $diag->{line} = ++$$linecnt;
338             }
339              
340 2201         5597 $_ = shift(@$lines);
341 2201   33     9089 while ( /\\\Z/ && @$lines ) {
342 0         0 chop;
343 0         0 my $cont = shift(@$lines);
344 0         0 $$linecnt++;
345 0         0 $cont =~ s/^\s+//;
346 0         0 $_ .= $cont;
347             }
348              
349             # Uncomment this to allow \uDXXX\uDYYY (surrogate) escapes.
350             s/ \\u(d[89ab][[:xdigit:]]{2})\\u(d[cdef][[:xdigit:]]{2})
351 0         0 / pack('U*', 0x10000 + (hex($1) - 0xD800) * 0x400 + (hex($2) - 0xDC00) )
352 2201         4800 /igex;
353              
354             # Uncomment this to allow \uXXXX escapes.
355 2201         4356 s/\\u([0-9a-f]{4})/chr(hex("0x$1"))/ige;
  0         0  
356             # Uncomment this to allow \u{XX...} escapes.
357 2201         5059 s/\\u\{([0-9a-f]+)\}/chr(hex("0x$1"))/ige;
  0         0  
358              
359 2201         5012 $diag->{orig} = $_;
360             # Get rid of TABs.
361 2201         4193 s/\t/ /g;
362              
363 2201 50       7784 if ( $config->{debug}->{echo} ) {
364 0         0 warn(sprintf("==[%3d]=> %s\n", $diag->{line}, $diag->{orig} ) );
365             }
366              
367 2201         5976 for my $pp ( "all", "env-$in_context" ) {
368             next if $pp eq "env-$in_context"
369             && /^\s*\{(\w+)\}\s*$/
370 4402 100 100     24634 && $self->parse_directive($1)->{name} eq "end_of_$in_context";
      100        
371 4305 50       13090 if ( $prep->{$pp} ) {
372 0 0       0 $config->{debug}->{pp} && warn("PRE: ", $_, "\n");
373 0         0 $prep->{$pp}->($_);
374 0 0       0 $config->{debug}->{pp} && warn("POST: ", $_, "\n");
375 0 0       0 if ( /\n/ ) {
376 0         0 my @a = split( /\n/, $_ );
377 0         0 $_ = shift(@a);
378 0         0 unshift( @$lines, @a );
379 0         0 $skipcnt += @a;
380             }
381             }
382             }
383              
384 2201 100       5336 if ( $skip_context ) {
385 4 100       45 if ( /^\s*\{(\w+)\}\s*$/ ) {
386 2         9 my $dir = $self->parse_directive($1);
387 2 50       12 if ( $dir->{name} eq "end_of_$in_context" ) {
388 2         5 $in_context = $def_context;
389 2         8 $skip_context = 0;
390             }
391             }
392 4         12 next;
393             }
394              
395 2197 100       6759 if ( /^\s*\{((?:new_song|ns)\b.*)\}\s*$/ ) {
396 63 100       262 if ( $self->{body} ) {
397 29         97 unshift( @$lines, $_ );
398 29         65 $$linecnt--;
399 29         85 last;
400             }
401 34         152 my $dir = $self->parse_directive($1);
402 34 50 50     289 next unless my $kv = parse_kv($dir->{arg}//"");
403 34 50       145 if ( defined $kv->{toc} ) {
404 0         0 $self->{meta}->{_TOC} = [ $kv->{toc} ];
405             }
406 34 50       143 if ( $kv->{forceifempty} ) {
407 0         0 push( @{ $self->{body} },
408             { type => "set",
409             name => "forceifempty",
410 0         0 value => $kv->{forceifempty} } );
411             }
412 34         196 next;
413             }
414              
415 2134 100       5587 if ( /^#/ ) {
416              
417             # Handle assets.
418 57         160 my $kw = "";
419 57         124 my $kv = {};
420 57 100 66     287 if ( /^##(image|asset|include)(?:-(.+))?:\s+(.*)/i
421             && $self->selected($2) ) {
422 3         13 $kw = lc($1);
423 3         20 $kv = parse_kv($3);
424             }
425              
426 57 100       184 if ( $kw eq "image" ) {
427 2         8 my $id = $kv->{id};
428 2 50       8 unless ( $id ) {
429 0         0 do_warn("Missing id for image asset\n");
430 0         0 next;
431             }
432              
433             # In-line image asset.
434 2         20 require MIME::Base64;
435 2         1646 require Image::Info;
436              
437             # Read the image.
438 2         7080 my $data = '';
439 2   100     28 while ( @$lines && $lines->[0] =~ /^# (.+)/ ) {
440 9         45 $data .= MIME::Base64::decode($1);
441 9         69 shift(@$lines);
442             }
443              
444             # Get info.
445 2         10 my $info = Image::Info::image_info(\$data);
446 2 50       14179 if ( $info->{error} ) {
447 0         0 do_warn($info->{error});
448 0         0 next;
449             }
450              
451             # Store in assets.
452 2   100     15 $self->{assets} //= {};
453             $self->{assets}->{$id} =
454             { type => "image",
455             data => $data,
456             subtype => $info->{file_ext},
457             width => $info->{width},
458             height => $info->{height},
459 2         87 opts => $kv,
460             };
461              
462 2 50       17 if ( $config->{debug}->{images} ) {
463             warn( "asset[$id] type=image/$info->{file_ext} ",
464             length($data), " bytes, ",
465             "width=$info->{width}, height=$info->{height}",
466 0 0       0 $kv->{persist} ? ", persist" : "",
467             "\n");
468             }
469 2         31 next;
470             }
471              
472 55 100       150 if ( $kw eq "asset" ) {
473 1         3 my $id = $kv->{id};
474 1         5 my $type = $kv->{type};
475 1 50       7 unless ( $id ) {
476 0         0 do_warn("Missing id for asset\n");
477 0         0 next;
478             }
479 1 50       3 unless ( $type ) {
480 0         0 do_warn("Missing type for asset\n");
481 0         0 next;
482             }
483 1 50       8 unless ( exists $config->{delegates}->{$type} ) {
484 0         0 do_warn("Unhandled type for asset: $type\n");
485 0         0 next;
486             }
487              
488             # Read the data.
489 1         3 my @data;
490 1   66     12 while ( @$lines && $lines->[0] =~ /^# (.+)/ ) {
491 3         54 push( @data, $1 );
492 3         18 shift(@$lines);
493             }
494              
495             # Store in assets.
496 1   50     4 $self->{assets} //= {};
497             $self->{assets}->{$id} =
498             { data => \@data,
499             type => "image",
500             subtype => $type,
501             module => $config->{delegates}->{$type}->{module},
502             handler => $config->{delegates}->{$type}->{handler},
503 1         15 opts => $kv,
504             };
505 1 50       5 if ( $config->{debug}->{images} ) {
506             warn("asset[$id] type=image/$type ",
507             scalar(@data), " lines",
508 0 0       0 $kv->{persist} ? ", persist" : "",
509             "\n");
510             }
511 1         5 next;
512             }
513              
514 54 50       131 if ( $kw eq "include" ) {
515 0 0       0 if ( $kv->{end} ) {
516 0         0 $diag = pop( @diag );
517 0         0 $$linecnt = $diag->{line};
518             }
519             else {
520 0         0 my $uri = $kv->{src};
521 0 0 0     0 if ( $uri && CP->is_here($uri) ) {
522 0         0 my $found = CP->siblingres( $diag->{file}, $uri, class => "include" );
523 0 0       0 if ( $found ) {
524 0         0 $uri = $found;
525             }
526             else {
527 0         0 do_warn("Missing include for \"$uri\"");
528 0         0 $uri = undef;
529             }
530             }
531 0 0       0 if ( $uri ) {
532 0         0 unshift( @$lines, @{fs_load($uri)}, "##include: end=1" );
  0         0  
533 0         0 push( @diag, { %$diag } );
534 0         0 $diag->{file} = $uri;
535 0         0 $diag->{line} = $$linecnt = 0;
536 0         0 $diag->{orig} = "(including $uri)";
537             }
538             }
539 0         0 next;
540             }
541              
542             # Currently the ChordPro backend is the only one that
543             # cares about comment lines.
544             # Collect pre-title stuff separately.
545             next unless exists $config->{lc $self->{generate}}
546             && exists $config->{lc $self->{generate}}->{comments}
547 54 100 100     582 && $config->{lc $self->{generate}}->{comments} eq "retain";
      66        
548              
549 39 50 33     130 if ( exists $self->{title} || $fragment ) {
550 39         147 $self->add( type => "ignore", text => $_ );
551             }
552             else {
553 0         0 push( @{ $self->{preamble} }, $_ );
  0         0  
554             }
555 39         158 next;
556             }
557              
558             # Tab content goes literally.
559 2077 100       5131 if ( $in_context eq "tab" ) {
560 64 100       203 unless ( /^\s*\{(?:end_of_tab|eot)\}\s*$/ ) {
561 49         135 $self->add( type => "tabline", text => $_ );
562 49         99 next;
563             }
564             }
565              
566 2028 100       6564 if ( exists $config->{delegates}->{$in_context} ) {
567             # 'open' indicates open.
568 11 100       131 if ( /^\s*\{(?:end_of_\Q$in_context\E)\}\s*$/ ) {
569 2         8 delete $self->{body}->[-1]->{open};
570 2         5 $grid_type = 0;
571             # A subsequent {start_of_XXX} will open a new item
572              
573 2         7 my $d = $config->{delegates}->{$in_context};
574 2 50       9 if ( $d->{type} eq "image" ) {
575 2         3 local $_;
576 2         4 my $a = pop( @{ $self->{body} } );
  2         7  
577 2         5 my $id = $a->{id};
578 2         4 my $opts = {};
579 2 50       10 unless ( $id ) {
580 2         6 my $pkg = 'ChordPro::Delegate::' . $a->{delegate};
581 2 50       265 eval "require $pkg" || warn($@);
582 2 50       60 if ( my $c = $pkg->can("options") ) {
583 2         10 $opts = $c->($a->{data});
584 2         7 $id = $opts->{id};
585             }
586             }
587 2         7 $opts = $a->{opts} = { %$opts, %{$a->{opts}} };
  2         14  
588 2 50       14 unless ( is_true($opts->{omit}) ) {
589 2 0 33     8 if ( $opts->{align} && $opts->{x} && $opts->{x} =~ /\%$/ ) {
      0        
590 0         0 do_warn( "Useless combination of x percentage with align (align ignored)" );
591 0         0 delete $opts->{align};
592             }
593              
594 2         6 my $def = !!$id;
595 2   66     13 $id //= "_Image".$assetid++;
596              
597 2 50       6 if ( defined $opts->{spread} ) {
598 0         0 $def++;
599 0 0       0 if ( exists $self->{spreadimage} ) {
600 0         0 do_warn("Skipping superfluous spread image");
601             }
602             else {
603             $self->{spreadimage} =
604 0         0 { id => $id, space => $opts->{spread} };
605             warn("Got spread image $id with space=$opts->{spread}\n")
606 0 0       0 if $config->{debug}->{images};
607             }
608             }
609              
610             # Move to assets.
611 2         13 $self->{assets}->{$id} = $a;
612 2 100       8 if ( $def ) {
613 1         3 my $label = delete $a->{label};
614 1 50       5 do_warn("Label \"$label\" ignored on non-displaying $in_context section\n")
615             if $label;
616             }
617             else {
618 1         3 my $label = delete $opts->{label};
619 1 50 33     6 $self->add( type => "set",
620             name => "label",
621             value => $label )
622             if $label && $label ne "";
623 1         6 $self->add( type => "image",
624             opts => $opts,
625             id => $id );
626 1 50       6 if ( $opts->{label} ) {
627             push( @labels, $opts->{label} )
628             unless $in_context eq "chorus"
629 0 0 0     0 && !$config->{settings}->{choruslabels};
630             }
631             }
632             }
633             }
634             }
635             else {
636             # Add to an open item.
637 9 50 33     32 if ( $self->{body} && @{ $self->{body} }
  9   33     83  
      33        
638             && $self->{body}->[-1]->{context} eq $in_context
639             && $self->{body}->[-1]->{open} ) {
640 9         15 push( @{$self->{body}->[-1]->{data}},
  9         48  
641             fmt_subst( $self, $_ ) );
642             }
643              
644             # Else start new item.
645             else {
646 0         0 croak("Reopening delegate");
647             }
648 9         1048 next;
649             }
650             }
651              
652             # For now, directives should go on their own lines.
653 2019 100       9456 if ( /^\s*\{(.*)\}\s*$/ ) {
654 1204         4011 my $dir = $1;
655 1204 50       3401 if ( $prep->{directive} ) {
656 0 0       0 $config->{debug}->{pp} && warn("PRE: ", $_, "\n");
657 0         0 $prep->{directive}->($dir);
658 0 0       0 $config->{debug}->{pp} && warn("POST: {", $dir, "}\n");
659             }
660 1204 100       4763 $self->add( type => "ignore",
661             text => $_ )
662             unless $self->directive($dir);
663 1204         9730 next;
664             }
665              
666 815 50 66     6135 if ( /\S/ && !$fragment && !exists $self->{title} ) {
      66        
667 0         0 do_warn("Missing {title} -- prepare for surprising results");
668 0         0 unshift( @$lines, "{title:$_}");
669 0         0 $skipcnt++;
670 0         0 next;
671             }
672              
673 815 50       2126 if ( $in_context eq "tab" ) {
674 0         0 $self->add( type => "tabline", text => $_ );
675 0         0 warn("OOPS");
676 0         0 next;
677             }
678              
679 815 100       1889 if ( $in_context eq "grid" ) {
680 39         186 $self->add( type => "gridline", $self->decompose_grid($_) );
681 39         159 next;
682             }
683 776 50 33     2277 if ( $in_context eq "grille" && @grille ) {
684             push( @grille, { line => $diag->{line},
685 0         0 $self->decompose_grid($_) } );
686 0         0 next;
687             }
688              
689 776 100 33     3143 if ( /\S/ ) {
    50          
690 474 50       1291 if ( $prep->{songline} ) {
691 0 0       0 $config->{debug}->{pp} && warn("PRE: ", $_, "\n");
692 0         0 $prep->{songline}->($_);
693 0 0       0 $config->{debug}->{pp} && warn("POST: ", $_, "\n");
694             }
695 474 50 33     2256 if ( $config->{settings}->{flowtext}
696 0   0     0 && @{ $self->{body}//[] } ) {
697 0         0 my $prev = $self->{body}->[-1];
698 0         0 my $this = { $self->decompose($_) };
699 0 0 0     0 if ( $prev->{type} eq "songline"
      0        
700             && !$prev->{chords}
701             && !$this->{chords} ) {
702 0         0 $prev->{phrases}->[0] .= " " . $this->{phrases}->[0];
703             }
704             else {
705 0         0 $self->add( type => "songline", %$this );
706             }
707             }
708             else {
709 474         1903 $self->add( type => "songline", $self->decompose($_) );
710             }
711             }
712             elsif ( exists $self->{title} || $fragment ) {
713 302         1243 $self->add( type => "empty" );
714             }
715             else {
716             # Collect pre-title stuff separately.
717 0         0 push( @{ $self->{preamble} }, $_ );
  0         0  
718             }
719             }
720 214 50       777 do_warn("Unterminated context in song: $in_context")
721             if $in_context;
722              
723             # These don't make sense after processing. Or do they?
724             # delete $self->{meta}->{$_} for qw( key_actual key_from );
725              
726 214 50       1061 warn("Processed song...\n") if $options->{verbose};
727 214         842 $diag->{format} = "\"%f\": %m";
728              
729             ::dump($self->{assets}, as => "Assets, Pass 1")
730 214 50       1169 if $config->{debug}->{assets} & 1;
731 214 50       974 $self->dump(0) if $config->{debug}->{song} > 1;
732              
733 214 100       755 if ( @labels ) {
734 1         7 $self->{labels} = [ @labels ];
735             }
736              
737             # Suppress chords that the user considers 'easy'.
738 214         432 my %suppress;
739 214         770 my $xc = $config->{settings}->{transcode};
740 214         581 for ( @{ $config->{diagrams}->{suppress} } ) {
  214         1219  
741 0         0 my $info = ChordPro::Chords::known_chord($_);
742 0 0       0 warn("Unknown chord \"$_\" in suppress list\n"), next
743             unless $info;
744             # Note we do transcode, but we do not transpose.
745 0 0       0 if ( $xc ) {
746 0         0 $info = $info->transcode($xc);
747             }
748 0         0 $suppress{$info->name} = $info->{origin} ne "song";
749             }
750             # Suppress chords that the user don't want.
751 214         499 while ( my ($k,$v) = each %{ $self->{chordsinfo} } ) {
  668         2903  
752 454 100 100     2705 $suppress{$k} = 1 if !is_true($v->{diagram}//1);
753             }
754 214 100       718 @used_chords = map { $suppress{$_} ? () : $_ } @used_chords;
  947         2858  
755              
756 214         482 my $diagrams;
757 214 100       1046 if ( exists($self->{settings}->{diagrams} ) ) {
758 6         21 $diagrams = $self->{settings}->{diagrams};
759 6   100     40 $diagrams &&= $config->{diagrams}->{show} || "all";
      66        
760             }
761             else {
762 208         755 $diagrams = $config->{diagrams}->{show};
763             }
764              
765 214 50 66     2639 if ( $diagrams =~ /^(user|all)$/
766             && !ChordPro::Chords::Parser->get_parser($target,1)->has_diagrams ) {
767             do_warn( "Chord diagrams suppressed for " .
768 0 0       0 ucfirst($target) . " chords" ) unless $options->{silent};
769 0         0 $diagrams = "none";
770             }
771              
772 214 50       831 if ( $diagrams eq "user" ) {
773              
774 0 0 0     0 if ( $self->{define} && @{$self->{define}} ) {
  0         0  
775 0         0 my %h = map { demarkup($_) => 1 } @used_chords;
  0         0  
776             @used_chords =
777 0 0       0 map { $h{$_->{name}} ? $_->{name} : () } @{$self->{define}};
  0         0  
  0         0  
778             }
779             else {
780 0         0 @used_chords = ();
781             }
782             }
783             else {
784 214         493 my %h;
785 945 100       2922 @used_chords = map { $h{$_}++ ? () : $_ }
786 214         701 map { demarkup($_) } @used_chords;
  945         2224  
787             }
788              
789 214 100       1979 if ( $config->{diagrams}->{sorted} ) {
790 7     7 0 20 sub byname { ChordPro::Chords::chordcompare($a,$b) }
791 1         12 @used_chords = sort byname @used_chords;
792             }
793              
794             # For headings, footers, table of contents, ...
795 214   50     1987 $self->{meta}->{chords} //= [ @used_chords ];
796 214         469 $self->{meta}->{numchords} = [ scalar(@{$self->{meta}->{chords}}) ];
  214         1060  
797              
798 214 100       793 if ( %memchords ) {
799 49 50       367 ::dump(\%memchords, as => "cc (atend)") if $config->{debug}->{chords};
800             }
801             else {
802             # Avoid clutter.
803 165         517 delete $self->{meta}->{cc};
804             }
805              
806 214 100       711 if ( %memchords ) {
807 49 50       203 ::dump(\%memchords, as => "cc (atend)") if $config->{debug}->{chords};
808             }
809             else {
810             # Avoid clutter.
811 165         443 delete $self->{meta}->{cc};
812             }
813              
814 214 100       1223 if ( $diagrams =~ /^(user|all)$/ ) {
815             $self->{chords} =
816 130         1332 { type => "diagrams",
817             origin => "song",
818             show => $diagrams,
819             chords => [ @used_chords ],
820             };
821              
822 130 50       528 if ( %warned_chords ) {
823 0         0 my @a = sort ChordPro::Chords::chordcompare
824             keys(%warned_chords);
825 0         0 my $l;
826 0 0       0 if ( @a > 1 ) {
827 0         0 my $a = pop(@a);
828 0         0 $l = '"' . join('", "', @a) . '" and "' . $a . '"';
829             }
830             else {
831 0         0 $l = '"' . $a[0] . '"';
832             }
833 0         0 do_warn( "No chord diagram defined for $l (skipped)\n" );
834             }
835             }
836              
837 214 50       1483 $self->dump(0) if $config->{debug}->{song} > 0;
838 214 50       1002 $self->dump(2) if $config->{debug}->{song} < 0;
839 214 50       904 $self->dump(1) if $config->{debug}->{songfull};
840              
841 214         1808 return $self;
842             }
843              
844             sub add {
845 1341     1341 0 2548 my $self = shift;
846 1341 50       3268 return if $skip_context;
847 1341         7800 push( @{$self->{body}},
848             { context => $in_context,
849 1341 100       2115 $lineinfo ? ( line => $diag->{line} ) : (),
850             @_ } );
851 1341 100       5905 if ( $in_context eq "chorus" ) {
852 110         462 push( @chorus, { context => $in_context, @_ } );
853 110         201 $chorus_xpose = $xpose;
854 110         469 $chorus_xpose_dir = $xpose_dir;
855             }
856             }
857              
858             # Parses a chord and adds it to the song.
859             # It understands markup, parenthesized chords and annotations.
860             # Returns the chord Appearance.
861             sub chord {
862 991     991 0 2559 my ( $self, $orig ) = @_;
863 991 50       2505 Carp::confess unless length($orig);
864              
865             # Intercept annotations.
866 991 100 66     6542 if ( $orig =~ /^\*(.+)/ || $orig =~ /^(\||\s+)$/ ) {
867 3         52 my $i = ChordPro::Chord::Annotation->new
868             ( { name => $orig, text => $1 } );
869             return
870 3         18 ChordPro::Chords::Appearance->new
871             ( key => $self->add_chord($i), info => $i, orig => $orig );
872             }
873              
874             # Check for markup.
875 988         2049 my $markup = $orig;
876 988         4032 my $c = demarkup($orig);
877 988 100       2871 if ( $markup eq $c ) { # no markup
878 981         1784 undef $markup;
879             }
880              
881             # Special treatment for parenthesized chords.
882 988         2070 $c =~ s/^\((.*)\)$/$1/;
883 988 50       2376 do_warn("Double parens in chord: \"$orig\"")
884             if $c =~ s/^\((.*)\)$/$1/;
885              
886             # We have a 'bare' chord now. Parse it.
887 988         3570 my $info = $self->parse_chord($c);
888 988 100       2475 unless ( defined $info ) {
889             # Warning was given.
890             # Make annotation.
891 8         66 my $i = ChordPro::Chord::Annotation->new
892             ( { name => $orig, text => $orig } );
893             return
894 8         42 ChordPro::Chords::Appearance->new
895             ( key => $self->add_chord($i), info => $i, orig => $orig );
896             }
897              
898 980         14904 my $ap = ChordPro::Chords::Appearance->new( orig => $orig );
899              
900             # Handle markup, if any.
901 980 100       22790 if ( $markup ) {
    50          
902 7 100 100     281 if ( $markup =~ s/\>\Q$c\E\%{formatted}
903             ||
904             $markup =~ s/\>\(\Q$c\E\)\(%{formatted})
905             }
906             else {
907 1         8 do_warn("Invalid markup in chord: \"$markup\"\n");
908             }
909 7         52 $ap->format = $markup;
910             }
911             elsif ( (my $m = $orig) =~ s/\Q$c\E/%{formatted}/ ) {
912 973 100       3503 $ap->format = $m unless $m eq "%{formatted}";
913             }
914              
915             # After parsing, the chord can be changed by transpose/code.
916             # info->name is the new key.
917 980         3945 $ap->key = $self->add_chord( $info, $c = $info->name );
918 980         3219 $ap->info = $info;
919              
920 980 100 100     3104 unless ( $info->is_nc || $info->is_note ) {
921             # if ( $info->is_keyboard ) {
922 966 50 0     4957 if ( $::config->{instrument}->{type} eq "keyboard" ) {
    100          
    50          
    0          
923 0         0 push( @used_chords, $c );
924             }
925             elsif ( $info->{origin} ) {
926             # Include if we have diagram info.
927 766 50       2125 push( @used_chords, $c ) if $info->has_diagram;
928             }
929             elsif ( $::running_under_test ) {
930             # Tests run without config and chords, so pretend.
931 200         652 push( @used_chords, $c );
932             }
933             elsif ( ! ( $info->is_rootless
934             || $info->has_diagram
935             || !$info->parser->has_diagrams
936             ) ) {
937             do_warn("Unknown chord: $c")
938 0 0       0 unless $warned_chords{$c}++;
939             }
940             }
941              
942 980         3460 return $ap;
943             }
944              
945             sub decompose {
946 608     608 0 1771 my ($self, $orig) = @_;
947 608         2422 my $line = fmt_subst( $self, $orig );
948 608 100       63936 undef $orig if $orig eq $line;
949 608         4393 $line =~ s/\s+$//;
950 608         8131 my @a = split( $re_chords, $line, -1);
951              
952 608 100       2278 if ( @a <= 1 ) {
953 249 50       1932 return ( phrases => [ $line ],
954             $orig ? ( orig => $orig ) : (),
955             );
956             }
957              
958             # For the exceptional case you need brackets [] in your lyrics
959             # or annotations.
960 359 50       1825 if ( my $a = $config->{parser}->{altbrackets} ) {
961 0         0 @a = map { eval "tr/$a/[]/r" } @a;
  0         0  
962             }
963              
964 359         726 my $dummy;
965 359 100       1144 shift(@a) if $a[0] eq "";
966 359 100       3280 unshift(@a, '[]'), $dummy++ if $a[0] !~ $re_chords;
967              
968 359         968 my @phrases;
969             my @chords;
970 359         949 while ( @a ) {
971 1084         3619 my $chord = shift(@a);
972 1084         2333 push(@phrases, shift(@a));
973              
974             # Normal chords.
975 1084 100 100     11435 if ( $chord =~ s/^\[(.*)\]$/$1/ && $chord ne "^" ) {
    100 66        
      66        
976 1040 100       4480 push(@chords, $chord eq "" ? "" : $self->chord($chord));
977 1040 100 100     4023 if ( $memchords && !$dummy && $chord !~ /^\*/ ) {
      66        
978 222 100       555 if ( $memcrdinx == 0 ) {
979 35         87 $memorizing++;
980             }
981 222 100       486 if ( $memorizing ) {
982 220 50       742 push( @$memchords, $chord eq "" ? "" : $chord );
983             warn("Chord memorized for $in_context\[$memcrdinx]: ",
984             $memchords->[-1], "\n")
985 220 50       768 if $config->{debug}->{chords};
986             }
987 222         447 $memcrdinx++;
988             }
989             }
990              
991             # Recall memorized chords.
992             elsif ( $memchords && $in_context && $chord !~ /^\*/ ) {
993 37 100 100     173 if ( $memcrdinx == 0 && @$memchords == 0 ) {
    50          
994 1         8 do_warn("No chords memorized for $in_context");
995 1         15 push( @chords, $self->chord($chord) );
996 1         3 undef $memchords;
997             }
998             elsif ( $memcrdinx >= @$memchords ) {
999 0         0 do_warn("Not enough chords memorized for $in_context");
1000 0         0 push( @chords, $self->chord($chord) );
1001             }
1002             else {
1003             warn("Chord recall $in_context\[$memcrdinx]: ", $memchords->[$memcrdinx], "\n")
1004 36 50       126 if $config->{debug}->{chords};
1005 36         137 push( @chords, $self->chord($memchords->[$memcrdinx]) );
1006             }
1007 37         90 $memcrdinx++;
1008             }
1009              
1010             # Not memorizing.
1011             else {
1012             # do_warn("No chords memorized for $in_context");
1013 7         25 push( @chords, $self->chord($chord) );
1014             }
1015 1084         3482 $dummy = 0;
1016             }
1017              
1018 359 100       2603 return ( phrases => \@phrases,
1019             chords => \@chords,
1020             $orig ? ( orig => $orig ) : (),
1021             );
1022             }
1023              
1024             sub cdecompose {
1025 134     134 0 395 my ( $self, $line ) = @_;
1026 134 50       676 $line = fmt_subst( $self, $line ) unless $no_substitute;
1027 134         13846 my %res = $self->decompose($line);
1028 134 100       940 return ( text => $line ) unless $res{chords};
1029 14         105 return %res;
1030             }
1031              
1032             sub decompose_grid {
1033 39     39 0 110 my ($self, $orig) = @_;
1034 39         202 my $line = fmt_subst( $self, $orig );
1035 39 50       4206 undef $orig if $orig eq $line;
1036 39         163 $line =~ s/^\s+//;
1037 39         272 $line =~ s/\s+$//;
1038 39 50       121 return ( tokens => [] ) if $line eq "";
1039 39         189 local $re_chords = qr/(\[.*?\])/;
1040 39         91 my $memchords = $memchords;
1041              
1042 39         83 my %res;
1043 39 50       165 if ( $line !~ /\|/ ) {
1044 0         0 $res{margin} = { $self->cdecompose($line), orig => $line };
1045 0         0 $line = "";
1046             }
1047             else {
1048 39 50       368 if ( $line =~ /(.*\|\S*)\s([^\|]*)$/ ) {
1049 0         0 $line = $1;
1050 0         0 $res{comment} = { $self->cdecompose($2), orig => $2 };
1051 0 0 0     0 do_warn( "No margin cell for trailing comment" )
1052             unless $in_context eq "grille" || $grid_cells->[2];
1053             }
1054 39 50       156 if ( $line =~ /^([^|]+?)\s*(\|.*)/ ) {
1055 0         0 $line = $2;
1056 0         0 $res{margin} = { $self->cdecompose($1), orig => $1 };
1057 0 0 0     0 do_warn( "No cell for margin text" )
1058             unless $in_context eq "grille" || $grid_cells->[1];
1059             }
1060             }
1061              
1062 39         67 my @tokens;
1063 39         274 my @t = split( ' ', $line );
1064              
1065             # Unfortunately, gets split too.
1066 39         141 while ( @t ) {
1067 663         1003 $_ = shift(@t);
1068 663         1279 push( @tokens, $_ );
1069 663 50       1517 if ( /\
1070 0         0 while ( @t ) {
1071 0         0 $_ = shift(@t);
1072 0         0 $tokens[-1] .= " " . $_;
1073 0 0 0     0 last if /\<\/span>/
1074             && ! /\<\/span>.*?\
1075             }
1076             }
1077             }
1078 39         92 my $nbt = 0; # non-bar tokens
1079 39         124 my $p0; # this bar chords
1080             my $p1; # prev chords (for % and %% repeat)
1081 39         0 my $p2; # pprev chords (for %% repeat)
1082 39         67 my $si = 0; # start index
1083              
1084 39         71 $grid_type = 0;
1085 39 50 33     248 if ( @tokens && uc($tokens[0]) =~ /^\|.*S/i ) {
1086 0         0 $grid_type = 1 + (chop($tokens[0]) eq "S"); # strum line
1087 0         0 $memchords = 0;
1088             }
1089              
1090             my $chord = sub {
1091 118     118   246 my $c = shift;
1092 118 50 33     263 if ( is_gridstrum($grid_type) && is_strum($c) ) {
1093 0         0 my $i = ChordPro::Chord::Strum->new( { name => $c } );
1094 0         0 ChordPro::Chords::Appearance->new
1095             ( key => $self->add_chord($i), info => $i );
1096             }
1097             else {
1098 118         480 $self->chord($c);
1099             }
1100 39         251 };
1101              
1102 39         110 foreach ( @tokens ) {
1103 663 50 33     6186 if ( $_ eq "|:" || $_ eq "{" ) {
    50 33        
    50 33        
    50          
    100          
    100          
    50          
    50          
    50          
    50          
    100          
1104 0         0 $_ = { symbol => $_, class => "bar" };
1105 0 0       0 $si = @$memchords if $memchords;
1106             }
1107             elsif ( /^\|(\d+)(>?)$/ ) {
1108 0         0 $_ = { symbol => '|', volta => $1, class => "bar" };
1109 0 0       0 $_->{align} = 1 if $2;
1110             }
1111             elsif ( $_ eq ":|" || $_ eq "}" ) {
1112 0         0 $_ = { symbol => $_, class => "bar" };
1113 0 0       0 if ( $memchords ) {
1114 0         0 push( @$memchords, @$memchords[ $si .. $#{$memchords} ] );
  0         0  
1115             }
1116             }
1117             elsif ( $_ eq ":|:" || $_ eq "}{" ) {
1118 0         0 $_ = { symbol => $_, class => "bar" };
1119 0 0       0 if ( $memchords ) {
1120 0         0 push( @$memchords, @$memchords[ $si .. $#{$memchords} ] );
  0         0  
1121 0         0 $si = @$memchords;
1122             }
1123             }
1124             elsif ( $_ eq "|" ) {
1125 149         513 $_ = { symbol => $_, class => "bar" };
1126             }
1127             elsif ( $_ eq "||" ) {
1128 2         13 $_ = { symbol => $_, class => "bar" };
1129             }
1130             elsif ( $_ eq "|." ) {
1131 0         0 $_ = { symbol => $_, class => "bar" };
1132             }
1133             elsif ( $_ eq "%" ) {
1134 0         0 $_ = { symbol => $_, class => "repeat1" };
1135 0 0 0     0 if ( $memchords && $p1 ) {
1136 0         0 push( @$memchords, @$p1 );
1137 0 0       0 if ( $config->{debug}->{chords} ) {
1138             warn("Chord memorized for $cctag\[$memcrdinx]: ",
1139             $_, "\n"), $memcrdinx++
1140 0         0 for @$p1;
1141             }
1142             }
1143             }
1144             elsif ( $_ eq '%%' ) {
1145 0         0 $_ = { symbol => $_, class => "repeat2" };
1146 0 0 0     0 if ( $memchords && $p1 ) {
1147 0 0       0 push( @$memchords, @$p2 ) if $p2;
1148 0         0 push( @$memchords, @$p1 );
1149 0 0       0 if ( $config->{debug}->{chords} ) {
1150             warn("Chord memorized for $cctag\[$memcrdinx]: ",
1151             $_, "\n"), $memcrdinx++
1152 0         0 for @$p2, @$p1;
1153             }
1154             }
1155             }
1156             elsif ( $_ eq "/" ) {
1157 0         0 $_ = { symbol => $_, class => "slash" };
1158             }
1159             elsif ( $_ eq "." ) {
1160 395         1306 $_ = { symbol => $_, class => "space" };
1161 395         619 $nbt++;
1162             }
1163             else {
1164             # Multiple chords in a cell?
1165 117         412 my @a = split( /~/, $_, -1 );
1166 117 100       298 if ( @a == 1) {
1167             # Normal case, single chord.
1168 116         307 $_ = { chord => $chord->($_), class => "chord" };
1169             }
1170             else {
1171             # Multiple chords.
1172             $_ = { chords =>
1173 1 50 33     4 [ map { ( $_ eq '.' || $_ eq '' )
  2 50       15  
1174             ? ''
1175             : $_ eq "/"
1176             ? "/"
1177             : $chord->($_) } @a ],
1178             class => "chords" };
1179             }
1180 117 50 33     533 if ( $memchords && !is_gridstrum($grid_type) ) {
1181 117         266 @a = grep { !m;^[/.]?$; } @a;
  118         851  
1182 117         296 push( @$memchords, @a );
1183 117         268 push( @$p0, @a );
1184 117 50       476 if ( $config->{debug}->{chords} ) {
1185             warn("Chord memorized for $cctag\[$memcrdinx]: ",
1186             $_, "\n"), $memcrdinx++
1187 0         0 for @a;
1188             }
1189             }
1190 117         238 $nbt++;
1191             }
1192 663 100       1693 if ( $_->{class} eq "bar" ) {
1193 151         255 $p2 = $p1; $p1 = $p0; undef $p0;
  151         262  
  151         362  
1194             }
1195             }
1196 39 50       141 if ( $nbt > $grid_cells->[0] ) {
1197 0         0 do_warn( "Too few cells for grid content" );
1198             }
1199 39 50       656 return ( tokens => \@tokens,
    50          
1200             $grid_type == 1 ? ( type => "strumline" ) : (),
1201             $grid_type == 2 ? ( type => "strumline", subtype => "cellbars" ) : (),
1202             %res );
1203             }
1204              
1205             ################ Parsing directives ################
1206              
1207             my %directives = (
1208             chord => \&define_chord,
1209             chorus => \&dir_chorus,
1210             column_break => \&dir_column_break,
1211             columns => \&dir_columns,
1212             comment => \&dir_comment,
1213             comment_box => \&dir_comment,
1214             comment_italic => \&dir_comment,
1215             define => \&define_chord,
1216             diagrams => \&dir_diagrams,
1217             end_of_bridge => undef,
1218             end_of_chorus => undef,
1219             end_of_grid => undef,
1220             end_of_grille => undef,
1221             end_of_tab => undef,
1222             end_of_verse => undef,
1223             grid => \&dir_grid,
1224             highlight => \&dir_comment,
1225             image => \&dir_image,
1226             meta => \&dir_meta,
1227             new_page => \&dir_new_page,
1228             new_physical_page => \&dir_new_page,
1229             new_song => \&dir_new_song,
1230             no_grid => \&dir_no_grid,
1231             pagesize => \&dir_papersize,
1232             pagetype => \&dir_papersize,
1233             start_of_bridge => undef,
1234             start_of_chorus => undef,
1235             start_of_grid => undef,
1236             start_of_grille => undef,
1237             start_of_tab => undef,
1238             start_of_verse => undef,
1239             subtitle => \&dir_subtitle,
1240             title => \&dir_title,
1241             titles => \&dir_titles,
1242             transpose => \&dir_transpose,
1243             );
1244             # NOTE: Flex: start_of_... end_of_... x_...
1245              
1246             my %abbrevs = (
1247             c => "comment",
1248             cb => "comment_box",
1249             cf => "chordfont",
1250             ci => "comment_italic",
1251             col => "colums",
1252             colb => "column_break",
1253             cs => "chordsize",
1254             eob => "end_of_bridge",
1255             eoc => "end_of_chorus",
1256             eog => "end_of_grid",
1257             eot => "end_of_tab",
1258             eov => "end_of_verse",
1259             g => "diagrams",
1260             ng => "no_grid",
1261             np => "new_page",
1262             npp => "new_physical_page",
1263             ns => "new_song",
1264             sob => "start_of_bridge",
1265             soc => "start_of_chorus",
1266             sog => "start_of_grid",
1267             sot => "start_of_tab",
1268             sov => "start_of_verse",
1269             st => "subtitle",
1270             t => "title",
1271             tf => "textfont",
1272             ts => "textsize",
1273             );
1274              
1275             # Use by: runtimeinfo.
1276 9     9   173 sub _directives { \%directives }
1277 9     9   44 sub _directive_abbrevs { \%abbrevs }
1278              
1279             my $dirpat;
1280              
1281             sub parse_directive {
1282 1772     1772 0 82837 my ( $self, $d ) = @_;
1283              
1284             # Pattern for all recognized directives.
1285 1772 100       5084 unless ( $dirpat ) {
1286             $dirpat =
1287             '(?:' .
1288             join( '|', keys(%directives),
1289 64         1007 @{$config->{metadata}->{keys}},
  64         1945  
1290             keys(%abbrevs),
1291             '(?:start|end)_of_\w+',
1292             "(?:$propitems_re".
1293             '(?:font|size|colou?r))',
1294             ) . ')';
1295 64         30694 $dirpat = qr/$dirpat/;
1296             }
1297              
1298             # $d is the complete directive line, without leading/trailing { }.
1299 1772 50 33     7443 if ( $options->{reference} and $d =~ s/^\s*:[: ]*//) {
1300 0         0 do_warn("Incorrect start of directive (':' not allowed at start)");
1301             }
1302 1772         5775 $d =~ s/^[: ]+//;
1303 1772         5768 $d =~ s/\s+$//;
1304 1772         4928 my $dir = lc($d);
1305 1772         3167 my $arg = "";
1306 1772 100       8700 if ( $d =~ /^(.*?)([: ])\s*(.*)/ ) {
1307 1129         5200 ( $dir, $arg ) = ( lc($1), $3 );
1308 1129 50       3085 if ( $options->{reference} ) {
1309 0 0       0 do_warn("Directive name must be followed by a ':'")
1310             unless $2 eq ":";
1311             }
1312             }
1313 1772         4504 $dir =~ s/[: ]+$//;
1314             # $dir is the lowcase directive name.
1315             # $arg is the rest, if any.
1316              
1317             # Check for xxx-yyy selectors.
1318 1772 100       38698 if ( $dir =~ /^($dirpat)-(.+)$/ ) {
1319 229   66     1447 $dir = $abbrevs{$1} // $1;
1320 229 100       830 unless ( $self->selected($2) ) {
1321 113 100       227 if ( $dir =~ /^start_of_/ ) {
1322 11         144 return { name => $dir, arg => $arg, omit => 2 };
1323             }
1324             else {
1325 102         521 return { name => $dir, arg => $arg, omit => 1 };
1326             }
1327             }
1328             }
1329             else {
1330 1543   66     7543 $dir = $abbrevs{$dir} // $dir;
1331             }
1332              
1333 1659 50 100     7365 if ( $dir =~ /^start_of_(.*)/
      66        
1334             && exists $config->{delegates}->{$1}
1335             && $config->{delegates}->{$1}->{type} eq 'omit' ) {
1336 0         0 return { name => $dir, arg => $arg, omit => 2 };
1337             }
1338              
1339 1659         10040 return { name => $dir, arg => $arg, omit => 0 }
1340             }
1341              
1342             # Process a selector.
1343             sub selected {
1344 232     232 0 667 my ( $self, $sel ) = @_;
1345 232 100       509 return 1 unless defined $sel;
1346 229         521 my $negate = $sel =~ s/\!$//;
1347             $sel = ( $sel eq lc($config->{instrument}->{type}) )
1348             ||
1349             ( $sel eq lc($config->{user}->{name})
1350             ||
1351 229   100     2243 ( $self->{meta}->{lc $sel} && is_true($self->{meta}->{lc $sel}->[0]) )
1352             );
1353 229 100       528 $sel = !$sel if $negate;
1354 229         727 return $sel;
1355             }
1356              
1357             sub directive {
1358 1204     1204 0 2987 my ( $self, $d ) = @_;
1359              
1360 1204         3767 my $dd = $self->parse_directive($d);
1361 1204 100       4141 return 1 if $dd->{omit} == 1;
1362              
1363 1202         2679 my $dir = $dd->{name};
1364 1202         2464 my $arg = $dd->{arg};
1365 1202 100       3263 if ( $arg ne "" ) {
1366 948         4622 $arg = fmt_subst( $self, $arg );
1367 948 50       146476 if ( $arg !~ /\S/ ) { # expansion yields empty
1368 0 0       0 if ( $dir =~ /^start_of_/ ) {
1369 0         0 $dd->{omit} = 2;
1370             }
1371             else {
1372 0         0 return 1;
1373             }
1374             }
1375             }
1376              
1377 1202 100       5285 if ( $directives{$dir} ) {
1378 688         3654 return $directives{$dir}->( $self, $dir, $arg, $dd->{arg} );
1379             }
1380              
1381             # Context flags.
1382              
1383 514 100       1870 if ( $dir =~ /^start_of_(\w+)$/ ) {
1384 97 50       316 do_warn("Already in " . ucfirst($in_context) . " context\n")
1385             if $in_context;
1386 97         275 $in_context = $1;
1387 97 100       382 if ( $dd->{omit} ) {
1388 2         6 $skip_context = 1;
1389             # warn("Skipping context: $in_context\n");
1390 2         11 return 1;
1391             }
1392 95 100       412 @chorus = (), $chorus_xpose = $chorus_xpose_dir = 0
1393             if $in_context eq "chorus";
1394 95         287 undef $cctag;
1395              
1396 95 100 33     828 if ( $in_context eq "grid"
    100 66        
    100          
1397             || ( $in_context eq "grille" && !exists $config->{delegates}->{$in_context} ) ) {
1398 26         61 $cctag = $in_context;
1399 26         152 my $kv = parse_kv( $arg, "shape" );
1400 26   50     103 my $shape = $kv->{shape} // "";
1401 26 50       297 if ( $in_context eq "grille" ) {
    100          
    50          
    0          
1402             }
1403             elsif ( $shape eq "" ) {
1404 4         34 $self->add( type => "set",
1405             name => "gridparams",
1406             value => [ @$grid_arg[0..3] ] );
1407             }
1408             elsif ( $shape =~ m/^
1409             (?: (\d+) \+)?
1410             (\d+) (?: x (\d+) )?
1411             (?:\+ (\d+) )?
1412             (?:[:\s+] (.*)? )? $/x ) {
1413 22 50       98 do_warn("Invalid grid params: $shape (must be non-zero)"), return
1414             unless $2;
1415 22   50     293 $grid_arg = [ $2, $3//1, $1//0, $4//0 ];
      50        
      100        
1416 22 50       94 push( @$grid_arg, $5 ) if defined $5;
1417 22         148 $self->add( type => "set",
1418             name => "gridparams",
1419             value => [ @$grid_arg ] );
1420 22 50       70 push( @labels, $5 ) if defined($5);
1421             }
1422             elsif ( $shape ne "" ) {
1423 0         0 $self->add( type => "set",
1424             name => "gridparams",
1425             value => [ @$grid_arg[0..3], $shape ] );
1426 0         0 push( @labels, $shape );
1427             }
1428 26 50 50     263 if ( ($kv->{label}//"") ne "" ) {
1429             $self->add( type => "set",
1430             name => "label",
1431 0         0 value => $kv->{label} );
1432 0         0 push( @labels, $kv->{label} );
1433             }
1434              
1435             # Grid sections always memorize unless "cc=".
1436 26 50 50     199 if ( ($kv->{cc}//="grid") ne "" ) {
1437 26         67 $cctag = $kv->{cc};
1438 26   100     147 $memchords = $memchords{$cctag} //= [];
1439 26         66 $memcrdinx = 0;
1440 26         51 $memorizing = 1;
1441             }
1442 26         163 $grid_cells = [ $grid_arg->[0] * $grid_arg->[1],
1443             $grid_arg->[2], $grid_arg->[3] ];
1444              
1445 26 50       83 @grille = ( $kv ) if $in_context eq "grille";
1446 26         197 return 1;
1447             }
1448             elsif ( exists $config->{delegates}->{$in_context} ) {
1449 2         6 my $d = $config->{delegates}->{$in_context};
1450 2         5 my %opts;
1451 2 50 33     15 if ( $xpose || $config->{settings}->{transpose} ) {
1452             $opts{transpose} =
1453 0   0     0 $xpose + ($config->{settings}->{transpose}//0 );
1454             }
1455 2         10 my $kv = parse_kv( $arg, "label" );
1456 2 50 50     12 delete $kv->{label} if ($kv->{label}//"") eq "";
1457             $self->add( type => "image",
1458             subtype => "delegate",
1459             delegate => $d->{module},
1460             handler => $d->{handler},
1461             data => [ ],
1462             opts => { %opts, %$kv },
1463 2 50       19 exists($kv->{id}) ? ( id => $kv->{id} ) : (),
1464             open => 1 );
1465 2 50       11 push( @labels, $kv->{label} ) if exists $kv->{label};
1466             }
1467             elsif ( $arg ne "" ) {
1468 2         9 my $kv = parse_kv( $arg, "label" );
1469 2         4 my $label = delete $kv->{label};
1470 2         5 my $chords = delete $kv->{cc};
1471 2 50       6 if ( %$kv ) {
    50          
1472             # Assume a mistake.
1473 0         0 do_warn("Garbage in start_of_$in_context: $arg (ignored)\n");
1474             }
1475             elsif ( $label ) {
1476 2         10 $self->add( type => "set",
1477             name => "label",
1478             value => $label );
1479             push( @labels, $label)
1480             unless $in_context eq "chorus"
1481 2 50 33     8 && !$config->{settings}->{choruslabels};
1482             }
1483 2 50       6 if ( $chords ) {
1484 0         0 $chords =~ s/^\s*(.*)\s*/$1/;
1485 0         0 $cctag = $in_context;
1486             # Do we have a name? Chords? Both?
1487             # name:C D E
1488             # :C D E
1489             # :
1490 0 0       0 if ( $chords =~ /^(\w*):(.*)/ ) {
    0          
    0          
1491             # Name, possibly empty.
1492 0 0       0 $cctag = $1 if length($1);
1493             # Chords, possibly empty.
1494 0         0 $chords = $2;
1495             }
1496             # C D E
1497             elsif ( $chords =~ /\s/ ) {
1498             # Whitespace separated chords.
1499             }
1500             # name
1501             elsif ( $chords =~ /^\w+$/ ) {
1502 0         0 $cctag = $chords;
1503 0         0 $chords = "";
1504             }
1505             # ???
1506             else {
1507 0 0       0 warn("Unrecognized cc value: \"$chords\"\n")
1508             if $chords;
1509 0         0 $chords = "";
1510             }
1511 0 0       0 if ( $chords ne "" ) {
1512 0         0 $memchords = [ split( ' ', $chords ) ];
1513 0         0 $memchords{$cctag} = $memchords;
1514 0         0 $memcrdinx = 0;
1515 0         0 $memorizing = 0;
1516 0 0       0 if ( $config->{debug}->{chords} ) {
1517 0         0 my $i = 0;
1518             warn("Chord memorized for $cctag\[$i]: ",
1519             $_, "\n"), $i++
1520 0         0 for @$memchords;
1521             }
1522 0         0 return 1;
1523             }
1524             }
1525             }
1526              
1527             # Enabling this always would allow [^] to recall anyway.
1528             # Feature?
1529 69         130 if ( 1 || $config->{settings}->{memorize} ) {
1530 69   33     569 $memchords = ($memchords{$cctag//$in_context} //= []);
      100        
1531 69         202 $memcrdinx = 0;
1532 69         146 $memorizing = 0;
1533             }
1534 69         352 return 1;
1535             }
1536              
1537 417 100       1649 if ( $dir =~ /^end_of_(\w+)$/ ) {
1538 95 50       391 do_warn("Not in " . ucfirst($1) . " context\n")
1539             unless $in_context eq $1;
1540 95         206 $grid_type = 0;
1541 95 50 33     424 if ( $in_context eq "grille" && @grille > 1 ) {
1542 0         0 my $opts = shift(@grille);
1543 0         0 my $id = $opts->{id};
1544 0 0       0 unless ( is_true($opts->{omit}) ) {
1545 0 0 0     0 if ( $opts->{align} && $opts->{x} && $opts->{x} =~ /\%$/ ) {
      0        
1546 0         0 do_warn( "Useless combination of x percentage with align (align ignored)" );
1547 0         0 delete $opts->{align};
1548             }
1549              
1550 0         0 my $def = !!$id;
1551 0   0     0 $id //= "_Image".$assetid++;
1552              
1553 0 0       0 if ( defined $opts->{spread} ) {
1554 0         0 $def++;
1555 0 0       0 if ( exists $self->{spreadimage} ) {
1556 0         0 do_warn("Skipping superfluous spread image");
1557             }
1558             else {
1559             $self->{spreadimage} =
1560 0         0 { id => $id, space => $opts->{spread} };
1561             warn("Got spread image $id with space=$opts->{spread}\n")
1562 0 0       0 if $config->{debug}->{images};
1563             }
1564             }
1565              
1566             # Move to assets.
1567             $self->{assets}->{$id} =
1568             { type => "image",
1569             subtype => "delegate",
1570             delegate => "Grille",
1571             handler => "grille2xo",
1572             opts => $opts,
1573             line => $grille[0]{line},
1574 0         0 data => \@grille,
1575             context => $in_context,
1576             };
1577 0 0       0 if ( $def ) {
1578 0         0 my $label = delete $a->{label};
1579 0 0       0 do_warn("Label \"$label\" ignored on non-displaying $in_context section\n")
1580             if $label;
1581             }
1582             else {
1583 0         0 my $label = delete $opts->{label};
1584 0 0 0     0 $self->add( type => "set",
1585             name => "label",
1586             value => $label )
1587             if $label && $label ne "";
1588 0         0 $self->add( type => "image",
1589             opts => $opts,
1590             id => $id );
1591 0 0       0 if ( $opts->{label} ) {
1592             push( @labels, $opts->{label} )
1593             unless $in_context eq "chorus"
1594 0 0 0     0 && !$config->{settings}->{choruslabels};
1595             }
1596             }
1597             }
1598             }
1599             else {
1600 95         358 $self->add( type => "set",
1601             name => "context",
1602             value => $def_context );
1603             }
1604 95         204 $in_context = $def_context;
1605 95         219 undef $memchords;
1606 95         444 return 1;
1607             }
1608              
1609             # Metadata extensions (legacy). Should use meta instead.
1610             # Only accept the list from config.
1611 322 100   3508   1877 if ( any { $_ eq $dir } @{ $config->{metadata}->{keys} } ) {
  3508         5427  
  322         2254  
1612 238         1460 return $self->dir_meta( "meta", "$dir $arg" );
1613             }
1614              
1615             # Formatting. {chordsize XX} and such.
1616 84 100       1433 if ( $dir =~ m/ ^( $propitems_re )
1617             ( font | size | colou?r )
1618             $/x ) {
1619 72         211 my $item = $1;
1620 72         129 my $prop = $2;
1621              
1622 72         301 $self->propset( $item, $prop, $arg );
1623              
1624             # Derived props.
1625 72 100       171 $self->propset( "chorus", $prop, $arg ) if $item eq "text";
1626              
1627             # ::dump( { %propstack, line => $diag->{line} } );
1628 72         341 return 1;
1629             }
1630             # More private hacks.
1631 12 50 33     200 if ( !$options->{reference} && $d =~ /^([-+])([-\w.]+)$/i ) {
1632 0 0       0 if ( $2 eq "dumpmeta" ) {
1633 0         0 warn(::dump($self->{meta}));
1634             }
1635 0 0       0 $self->add( type => "set",
1636             name => $2,
1637             value => $1 eq "+" ? 1 : 0,
1638             );
1639 0         0 return 1;
1640             }
1641              
1642 12 100 66     149 if ( !$options->{reference} && $dir =~ /^\+([-\w.]+(?:\.[<>])?)$/ ) {
1643 11         79 $self->add( type => "set",
1644             name => $1,
1645             value => $arg,
1646             );
1647              
1648 11         78 $config->unlock;
1649 11         25761 prpadd2cfg( $config, $1 => $arg );
1650 11         70 $config->lock;
1651              
1652 11         28360 upd_config();
1653              
1654 11         102 return 1;
1655             }
1656              
1657             # Warn about unknowns, unless they are x_... form.
1658             do_warn("Unknown directive: $d\n")
1659 1 50 33     4 if $config->{settings}->{strict} && $d !~ /^x_/;
1660 1         22 return;
1661             }
1662              
1663             sub dir_chorus {
1664 31     31 0 128 my ( $self, $dir, $arg ) = @_;
1665              
1666 31 50       105 if ( $in_context ) {
1667 0         0 do_warn("{chorus} encountered while in $in_context context -- ignored\n");
1668 0         0 return 1;
1669             }
1670              
1671             # Clone the chorus so we can modify the label, if required.
1672 31 100       7209 my $chorus = @chorus ? dclone(\@chorus) : [];
1673              
1674 31 50 66     58506 if ( @$chorus && $arg && $arg ne "" ) {
      33        
1675 0         0 my $kv = parse_kv( $arg, "label" );
1676 0         0 my $label = $kv->{label};
1677 0 0 0     0 if ( $chorus->[0]->{type} eq "set" && $chorus->[0]->{name} eq "label" ) {
    0          
1678 0         0 $chorus->[0]->{value} = $label;
1679             }
1680             elsif ( defined $label ) {
1681 0         0 unshift( @$chorus,
1682             { type => "set",
1683             name => "label",
1684             value => $label,
1685             context => "chorus",
1686             } );
1687             }
1688             push( @labels, $label )
1689 0 0       0 if $config->{settings}->{choruslabels};
1690             }
1691              
1692 31 100       160 if ( $chorus_xpose != ( my $xp = $xpose ) ) {
1693 17         54 $xp -= $chorus_xpose;
1694 17         64 for ( @$chorus ) {
1695 32 100       165 if ( $_->{type} eq "songline" ) {
1696 16         36 for ( @{ $_->{chords} } ) {
  16         68  
1697 61 100       218 next if $_ eq '';
1698 46         214 my $info = $self->{chordsinfo}->{$_->key};
1699 46 50       195 next if $info->is_annotation;
1700 46 50       228 $info = $info->transpose($xp, $xpose <=> 0) if $xp;
1701 46         196 $info = $info->new($info);
1702 46         226 $_ = ChordPro::Chords::Appearance->new
1703             ( key => $self->add_chord($info),
1704             info => $info,
1705             maybe format => $_->format
1706             );
1707             }
1708             }
1709             }
1710             }
1711              
1712 31 100       239 $self->add( type => "rechorus",
1713             @$chorus
1714             ? ( "chorus" => $chorus )
1715             : (),
1716             );
1717 31         338 return 1;
1718             }
1719              
1720             #### Directive handlers ####
1721              
1722             # Song settings.
1723              
1724             # Breaks.
1725              
1726             sub dir_column_break {
1727 15     15 0 60 my ( $self, $dir, $arg ) = @_;
1728 15         81 $self->add( type => "colb" );
1729 15         79 return 1;
1730             }
1731              
1732             sub dir_new_page {
1733 20     20 0 76 my ( $self, $dir, $arg ) = @_;
1734 20         88 $self->add( type => "newpage" );
1735 20         121 return 1;
1736             }
1737              
1738             sub dir_new_song {
1739 0     0 0 0 my ( $self, $dir, $arg ) = @_;
1740 0         0 die("FATAL - cannot start a new song now\n");
1741             }
1742              
1743             # Comments. Strictly speaking they do not belong here.
1744              
1745             sub dir_comment {
1746 134     134 0 524 my ( $self, $dir, $arg, $orig ) = @_;
1747 134 100       494 $dir = "comment" if $dir eq "highlight";
1748 134         1033 my %res = $self->cdecompose($arg);
1749 134         477 $res{orig} = $orig;
1750             $self->add( type => $dir, %res )
1751 134 50 66     1701 unless exists($res{text}) && $res{text} =~ /^[ \t]*$/;
1752 134         1056 return 1;
1753             }
1754              
1755             sub dir_image {
1756 7     7 0 29 my ( $self, $dir, $arg ) = @_;
1757 7 100 66     54 return 1 if $::running_under_test && !$arg;
1758 90     90   1550 use Text::ParseWords qw(quotewords);
  90         244  
  90         1761781  
1759 6         35 my @words = quotewords( '\s+', 1, $arg );
1760 6         1532 my $res;
1761             # Imply src= if word 0 is not kv.
1762 6 100 66     90 if ( @words && $words[0] !~ /\w+=/ ) {
1763 2         6 $words[0] = "src=" . $words[0];
1764 2         10 $res = parse_kv( \@words );
1765             }
1766             else {
1767 4         24 $res = parse_kv( \@words, "src" );
1768             }
1769              
1770 6         33 my $uri;
1771             my $id;
1772 6         0 my $chord;
1773 6         0 my $type;
1774 6         0 my %opts;
1775 6         35 while ( my($k,$v) = each(%$res) ) {
1776 20 100 66     332 if ( $k =~ /^(title)$/i && $v ne "" ) {
    100 66        
    100 66        
    100 66        
    100 66        
    50 66        
    100 66        
    100 33        
    50 33        
    50 33        
    50 33        
    50 0        
    0 0        
    0          
    0          
1777 1         4 $opts{lc($k)} = $v;
1778             }
1779             elsif ( $k =~ /^(border|spread|center|persist|omit)$/i
1780             && $v =~ /^(\d+)$/ ) {
1781 4 100 66     15 if ( $k eq "center" && $v ) {
1782 1         4 $opts{align} = $k;
1783             }
1784             else {
1785 3         13 $opts{lc($k)} = $v;
1786             }
1787             }
1788             elsif ( $k =~ /^(width|height)$/i
1789             && $v =~ /^(\d+(?:\.\d+)?\%?)$/ ) {
1790 2         7 $opts{lc($k)} = $v;
1791             }
1792             elsif ( $k =~ /^(x|y)$/i
1793             && $v =~ /^(?:base[+-])?([-+]?\d+(?:\.\d+)?\%?)$/ ) {
1794 2         12 $opts{lc($k)} = $v;
1795             }
1796             elsif ( $k =~ /^(scale)$/
1797             && $v =~ /^(\d+(?:\.\d+)?)(%)?(?:,(\d+(?:\.\d+)?)(%)?)?$/ ) {
1798 3 50       15 $opts{lc($k)} = [ $2 ? $1/100 : $1 ];
1799 3 0       23 $opts{lc($k)}->[1] = $3 ? $4 ? $3/100 : $3 : $opts{lc($k)}->[0];
    50          
1800             }
1801             elsif ( $k =~ /^(center|border|spread|persist|omit)$/i ) {
1802 0 0       0 if ( $k eq "center" ) {
1803 0         0 $opts{align} = $k;
1804             }
1805             else {
1806 0         0 $opts{lc($k)} = $v;
1807             }
1808             }
1809             elsif ( $k =~ /^(src|uri)$/i && $v ne "" ) {
1810 2         9 $uri = $v;
1811             }
1812             elsif ( $k =~ /^(id)$/i && $v ne "" ) {
1813 4         18 $id = $v;
1814             }
1815             elsif ( $k =~ /^(chord)$/i && $v ne "" ) {
1816 0         0 $chord = $v;
1817             }
1818             elsif ( $k =~ /^(type)$/i && $v ne "" ) {
1819 0         0 $opts{type} = $v;
1820             }
1821             elsif ( $k =~ /^(label|href)$/i && $v ne "" ) {
1822 0         0 $opts{lc($k)} = $v;
1823             }
1824             elsif ( $k =~ /^(anchor)$/i
1825             && $v =~ /^(paper|page|allpages|column|float|line)$/ ) {
1826 2         13 $opts{lc($k)} = lc($v);
1827             }
1828             elsif ( $k =~ /^(align)$/i
1829             && $v =~ /^(center|left|right)$/ ) {
1830 0         0 $opts{lc($k)} = lc($v);
1831             }
1832             elsif ( $k =~ /^(bordertrbl)$/i
1833             && $v =~ /^[trbl]*$/ ) {
1834 0         0 $opts{lc($k)} = lc($v);
1835             }
1836             elsif ( $uri ) {
1837 0         0 do_warn( "Unknown image attribute: $k\n" );
1838 0         0 next;
1839             }
1840             # Assume just an image file uri.
1841             else {
1842 0         0 $uri = $k;
1843             }
1844             }
1845              
1846 6 50       41 return if is_true($opts{omit});
1847              
1848 6 50 66     47 unless ( $uri || $id || $chord ) {
      33        
1849 0         0 do_warn( "Missing image source\n" );
1850 0         0 return;
1851             }
1852 6 50 66     24 if ( $opts{align} && $opts{x} && $opts{x} =~ /\%$/ ) {
      33        
1853 0         0 do_warn( "Useless combination of x percentage with align (align ignored)" );
1854 0         0 delete $opts{align};
1855             }
1856              
1857             # If the image uri does not have a directory, look it up
1858             # next to the song, and then in the images folder of the
1859             # resources.
1860 6 100 66     28 if ( $uri && CP->is_here($uri) ) {
1861             my $found = CP->siblingres( $diag->{file}, $uri, class => "images" )
1862 2   33     6 || CP->siblingres( $diag->{file}, $uri, class => "icons" );
1863 2 50       6 if ( $found ) {
1864 2         3 $uri = $found;
1865             }
1866             else {
1867 0         0 do_warn("Missing image for \"$uri\"");
1868 0         0 return;
1869             }
1870             }
1871 6 50       20 $uri = "chord:$chord" if $chord;
1872              
1873 6   66     28 my $aid = $id || "_Image".$assetid++;
1874              
1875 6 50       24 if ( defined $opts{spread} ) {
1876 0 0       0 if ( exists $self->{spreadimage} ) {
1877 0         0 do_warn("Skipping superfluous spread image");
1878             }
1879             else {
1880             $self->{spreadimage} =
1881 0         0 { id => $aid, space => $opts{spread} };
1882             warn("Got spread image $aid with $opts{spread} space\n")
1883 0 0       0 if $config->{debug}->{images};
1884             }
1885             }
1886              
1887             # Store as asset.
1888 6 100       16 if ( $uri ) {
1889 2         3 my $opts;
1890 2         5 for ( qw( type persist href ) ) {
1891 6 50       11 $opts->{$_} = $opts{$_} if defined $opts{$_};
1892 6         12 delete $opts{$_};
1893             }
1894 2         3 for ( qw( spread ) ) {
1895 2 50       6 $opts->{$_} = $opts{$_} if defined $opts{$_};
1896             }
1897              
1898 2 50 33     21 if ( $id && %opts ) {
1899 0         0 do_warn("Asset definition \"$id\" does not take attributes",
1900             " (" . join(" ",sort keys %opts) . ")");
1901 0         0 return;
1902             }
1903              
1904 2   50     13 $self->{assets} //= {};
1905 2         3 my $a;
1906 2 50 33     22 if ( $uri =~ /\.(\w+)$/ && exists $config->{delegates}->{$1} ) {
1907 0         0 my $d = $config->{delegates}->{$1};
1908             $a = { type => "image",
1909             subtype => "delegate",
1910             delegate => $d->{module},
1911             handler => $d->{handler},
1912 0         0 uri => $uri,
1913             };
1914             }
1915             else {
1916 2         10 $a = { type => "image",
1917             uri => $uri,
1918             };
1919             }
1920 2 50       6 $a->{opts} = $opts if $opts;
1921 2         6 $self->{assets}->{$aid} = $a;
1922              
1923 2 50       7 if ( $config->{debug}->{images} ) {
1924             warn("asset[$aid] type=image uri=$uri",
1925             $a->{subtype} ? " subtype=$a->{subtype}" : (),
1926             $a->{delegate} ? " delegate=$a->{delegate}" : (),
1927 0 0       0 $opts->{persist} ? " persist" : (),
    0          
    0          
1928             "\n");
1929             }
1930 2 50 33     11 return if $id || defined $opts{spread}; # defining only
1931             }
1932              
1933 6 50       19 if ( $opts{label} ) {
1934             $self->add( type => "set",
1935             name => "label",
1936             value => $opts{label},
1937 0         0 context => "image" );
1938 0         0 push( @labels, $opts{label} );
1939             }
1940              
1941 6         35 $self->add( type => "image",
1942             id => $aid,
1943             opts => \%opts );
1944 6         52 return 1;
1945             }
1946              
1947             sub dir_title {
1948 219     219 0 932 my ( $self, $dir, $arg ) = @_;
1949 219         776 $self->{title} = $arg;
1950 219         496 push( @{ $self->{meta}->{title} }, $arg );
  219         1125  
1951 219         1451 return 1;
1952             }
1953              
1954             sub dir_subtitle {
1955 51     51 0 194 my ( $self, $dir, $arg ) = @_;
1956 51         122 push( @{ $self->{subtitle} }, $arg );
  51         222  
1957 51         122 push( @{ $self->{meta}->{subtitle} }, $arg );
  51         187  
1958 51         510 return 1;
1959             }
1960              
1961             # Metadata.
1962              
1963             sub dir_meta {
1964 277     277 0 878 my ( $self, $dir, $arg ) = @_;
1965              
1966 277 50       1869 if ( $arg =~ /([^ :]+)[ :]+(.*)/ ) {
1967 277         1170 my $key = lc $1;
1968 277         1085 my @vals = ( $2 );
1969 277 100       1481 if ( $config->{metadata}->{autosplit} ) {
1970 270         892 @vals = map { s/s\+$//; $_ }
  270         1113  
1971 270         6348 split( quotemeta($config->{metadata}->{separator}), $vals[0] );
1972             }
1973             else {
1974 7 50       21 pop(@vals) if $vals[0] eq '';
1975             }
1976 277         851 my $m = $self->{meta};
1977              
1978             # User and instrument cannot be set here.
1979 277 50 33     1689 if ( $key eq "user" || $key eq "instrument" ) {
1980 0         0 do_warn("\"$key\" can be set from config only.\n");
1981 0         0 return 1;
1982             }
1983              
1984 277         787 for my $val ( @vals ) {
1985              
1986 277 100       849 if ( $key eq "key" ) {
1987 93         344 $val =~ s/[\[\]]//g;
1988 93         187 my $info = do {
1989             # When transcoding to nash/roman, parse_chord will
1990             # complain about a missing key. Fake one.
1991 93         546 local( $self->{meta}->{key} ) = [ '_dummy_' ];
1992 93         700 local( $self->{chordsinfo}->{_dummy_} ) = { root_ord => 0 };
1993 93         549 $self->parse_chord($val);
1994             };
1995 93 50       321 do_warn("Illegal key: \"$val\"\n"), next unless $info;
1996 93         295 my $name = $info->name;
1997 93         222 my $act = $name;
1998             $info->{key} = $name
1999 93 50       369 unless $config->{settings}->{'enharmonic-transpose'};
2000              
2001 93 50       326 if ( $capo ) {
2002 0         0 $act = $self->add_chord( $info->transpose($capo) );
2003 0 0       0 $name = $act if $decapo;
2004             }
2005              
2006 93         209 push( @{ $m->{key} }, $name );
  93         482  
2007 93         443 $m->{key_actual} = [ $act ];
2008             # warn("XX key=$name act=$act capo=",
2009             # $capo//""," decapo=$decapo\n");
2010 93         850 return 1;
2011             }
2012              
2013              
2014 184 100 100     921 if ( $key eq "capo" ) {
    100          
2015             do_warn("Multiple capo settings may yield surprising results.")
2016 17 100       70 if exists $m->{capo};
2017              
2018 17   100     87 $capo = $val || undef;
2019 17 50 66     172 if ( $capo && $m->{key} ) {
2020 16 100       62 if ( $decapo ) {
2021             my $key = $self->store_chord
2022 4         37 ($self->{chordsinfo}->{$m->{key}->[-1]}
2023             ->transpose($val));
2024 4         15 $m->{key}->[-1] = $key;
2025             $key = $self->store_chord
2026 4         22 ($self->{chordsinfo}->{$m->{key}->[-1]}
2027             ->transpose($xpose));
2028 4         24 $m->{key_actual} = [ $key ];
2029             }
2030             else {
2031 12         45 my $act = $m->{key_actual}->[-1];
2032 12         53 $m->{key_from} = [ $act ];
2033             my $key = $self->store_chord
2034 12         151 ($self->{chordsinfo}->{$act}->transpose($val));
2035 12         98 $m->{key_actual} = [ $key ];
2036             }
2037             }
2038             }
2039              
2040             elsif ( $key eq "duration" && $val ) {
2041 9         43 $val = duration($val);
2042             }
2043              
2044 184 50 33     798 if ( $config->{metadata}->{strict}
2045 1565     1565   2823 && ! any { $_ eq $key } @{ $config->{metadata}->{keys} } ) {
  184         912  
2046             # Unknown, and strict.
2047             do_warn("Unknown metadata item: $key")
2048 0 0       0 if $config->{settings}->{strict};
2049 0         0 return;
2050             }
2051              
2052 184 50       962 if ( defined $val ) {
2053             $self->{meta}->{$key} = [ $self->{meta}->{$key} ]
2054 184 50 66     844 if $self->{meta}->{$key} && !is_arrayref($self->{meta}->{$key});
2055 184         349 push( @{ $self->{meta}->{$key} }, $val );
  184         1089  
2056             }
2057             }
2058             }
2059             else {
2060             do_warn("Incomplete meta directive: $dir $arg\n")
2061 0 0       0 if $config->{settings}->{strict};
2062 0         0 return;
2063             }
2064 184         1390 return 1;
2065             }
2066              
2067             # Song / Global settings.
2068              
2069             sub dir_titles {
2070 23     23 0 91 my ( $self, $dir, $arg ) = @_;
2071              
2072 23 50       219 unless ( $arg =~ /^(left|right|center|centre)$/i ) {
2073 0         0 do_warn("Invalid argument for titles directive: $arg\n");
2074 0         0 return 1;
2075             }
2076 23 100       253 $self->{settings}->{titles} = lc($1) eq "centre" ? "center" : lc($1);
2077 23         148 return 1;
2078             }
2079              
2080             sub dir_columns {
2081 20     20 0 81 my ( $self, $dir, $arg ) = @_;
2082              
2083 20 50       166 unless ( $arg =~ /^(\d+)$/ ) {
2084 0         0 do_warn("Invalid argument for columns directive: $arg (should be a number)\n");
2085 0         0 return 1;
2086             }
2087             # If there a column specifications in the config, retain them
2088             # if the number of columns match.
2089 20 50 33     131 unless( ref($config->{settings}->{columns}) eq 'ARRAY'
2090 0         0 && $arg == @{$config->{settings}->{columns}}
2091             ) {
2092 20         75 $self->{settings}->{columns} = $arg;
2093             }
2094 20         117 return 1;
2095             }
2096              
2097             sub dir_papersize {
2098 4     4 0 13 my ( $self, $dir, $arg ) = @_;
2099 4         16 $self->{settings}->{papersize} = $arg;
2100 4         21 return 1;
2101             }
2102              
2103             sub dir_diagrams { # AKA grid
2104 3     3 0 13 my ( $self, $dir, $arg ) = @_;
2105              
2106 3 50       12 if ( $arg ne "" ) {
2107 3         18 $self->{settings}->{diagrams} = !!is_true($arg);
2108 3 100       16 $self->{settings}->{diagrampos} = lc($arg)
2109             if $arg =~ /^(right|bottom|top|below)$/i;
2110             }
2111             else {
2112 0         0 $self->{settings}->{diagrams} = 1;
2113             }
2114 3         16 return 1;
2115             }
2116              
2117             sub dir_grid {
2118 2     2 0 10 my ( $self, $dir, $arg ) = @_;
2119 2         7 $self->{settings}->{diagrams} = 1;
2120 2         11 return 1;
2121             }
2122              
2123             sub dir_no_grid {
2124 5     5 0 21 my ( $self, $dir, $arg ) = @_;
2125 5         26 $self->{settings}->{diagrams} = 0;
2126 5         28 return 1;
2127             }
2128              
2129             sub dir_transpose {
2130 55     55 0 218 my ( $self, $dir, $arg ) = @_;
2131              
2132 55   100     376 $propstack{transpose} //= [];
2133              
2134 55 100       372 if ( $arg =~ /^([-+]?\d+)\s*$/ ) {
2135 34         151 my $new = $1;
2136 34         85 push( @{ $propstack{transpose} }, [ $xpose, $xpose_dir ] );
  34         226  
2137 34         285 my %a = ( type => "control",
2138             name => "transpose",
2139             previous => [ $xpose, $xpose_dir ]
2140             );
2141 34         138 $xpose += $new;
2142 34         93 $xpose_dir = $new <=> 0;
2143 34         115 my $m = $self->{meta};
2144 34 100       182 if ( $m->{key} ) {
2145 23         83 my $key = $m->{key}->[-1];
2146 23         56 my $xp = $xpose;
2147 23 100       75 $xp += $capo if $capo;
2148 23         210 my $xpk = $self->{chordsinfo}->{$key}->transpose($xp, $xp <=> 0);
2149 23         141 $self->{chordsinfo}->{$xpk->name} = $xpk;
2150 23         135 $m->{key_from} = [ $m->{key_actual}->[0] ];
2151 23         82 $m->{key_actual} = [ $xpk->name ];
2152             }
2153 34 50       223 $self->add( %a, value => $xpose, dir => $xpose_dir )
2154             if $no_transpose;
2155             }
2156             else {
2157 21         146 my %a = ( type => "control",
2158             name => "transpose",
2159             previous => [ $xpose, $xpose_dir ]
2160             );
2161 21         78 my $m = $self->{meta};
2162 21         52 my ( $new, $dir );
2163 21 50       45 if ( @{ $propstack{transpose} } ) {
  21         93  
2164 21         43 ( $new, $dir ) = @{ pop( @{ $propstack{transpose} } ) };
  21         44  
  21         127  
2165             }
2166             else {
2167 0         0 $new = 0;
2168 0         0 $dir = $config->{settings}->{transpose} <=> 0;
2169             }
2170 21         59 $xpose = $new;
2171 21         52 $xpose_dir = $dir;
2172 21 100       105 if ( $m->{key} ) {
2173 15         86 $m->{key_from} = [ $m->{key_actual}->[0] ];
2174 15         109 my $xp = $xpose;
2175 15 50 66     88 $xp += $capo if $capo && $decapo;
2176             $m->{key_actual} =
2177 15         120 [ $self->{chordsinfo}->{$m->{key}->[-1]}->transpose($xp)->name ];
2178             }
2179 21 100       275 if ( !@{ $propstack{transpose} } ) {
  21         121  
2180 12         62 delete $m->{$_} for qw( key_from );
2181             }
2182 21 50       223 $self->add( %a, value => $xpose, dir => $dir )
2183             if $no_transpose;
2184             }
2185 55         618 return 1;
2186             }
2187              
2188             #### End of directive handlers ####
2189              
2190             sub propset {
2191 87     87 0 225 my ( $self, $item, $prop, $value ) = @_;
2192 87 100       172 $prop = "color" if $prop eq "colour";
2193 87         217 my $name = "$item-$prop";
2194 87   100     372 $propstack{$name} //= [];
2195              
2196 87 100       185 if ( $value eq "" ) {
2197 2         3 my @toadd;
2198             # Pop current value from stack.
2199 2 50       2 if ( @{ $propstack{$name} } ) {
  2         5  
2200 2         3 my $old = pop( @{ $propstack{$name} } );
  2         3  
2201             # A trailing number after a font directive means there
2202             # was also a size saved. Pop it.
2203 2 50 33     6 if ( $prop eq "font" && $old =~ /\s(\d+(?:\.\d+)?)$/ ) {
2204 0         0 pop( @{ $propstack{"$item-size"} } );
  0         0  
2205             # Resetting the size must follow the font reset.
2206             push( @toadd, type => "control",
2207             name => "$item-size",
2208             value =>
2209 0         0 @{ $propstack{"$item-size"} }
2210 0 0       0 ? $propstack{"$item-size"}->[-1]
2211             : undef );
2212             }
2213             }
2214             else {
2215 0         0 do_warn("No saved value for property $item$prop\n" )
2216             }
2217             # Use new current value, if any.
2218 2 50       3 if ( @{ $propstack{$name} } ) {
  2         3  
2219 2         3 $value = $propstack{$name}->[-1]
2220             }
2221             else {
2222 0         0 $value = undef;
2223             }
2224 2         5 $self->add( type => "control",
2225             name => $name,
2226             value => $value );
2227 2 50       4 $self->add( @toadd ) if @toadd;
2228 2         3 return 1;
2229             }
2230              
2231 85 100       154 if ( $prop eq "size" ) {
2232 24 50       153 unless ( $value =~ /^\d+(?:\.\d+)?\%?$/ ) {
2233 0         0 do_warn("Illegal value \"$value\" for $item$prop\n");
2234 0         0 return 1;
2235             }
2236             }
2237 85 100       149 if ( $prop eq "color" ) {
2238 37         55 my $v;
2239 37 50       86 unless ( $v = get_color($value) ) {
2240 0         0 do_warn("Illegal value \"$value\" for $item$prop\n");
2241 0         0 return 1;
2242             }
2243 37         58 $value = $v;
2244             }
2245 85 100       191 $value = $prop eq "font" ? $value : lc($value);
2246 85         260 $self->add( type => "control",
2247             name => $name,
2248             value => $value );
2249 85         175 push( @{ $propstack{$name} }, $value );
  85         197  
2250              
2251             # A trailing number after a font directive is an implicit size
2252             # directive.
2253 85 50 66     1033 if ( $prop eq 'font' && $value =~ /\s(\d+(?:\.\d+)?)$/ ) {
2254 0         0 $self->add( type => "control",
2255             name => "$item-size",
2256             value => $1 );
2257 0         0 push( @{ $propstack{"$item-size"} }, $1 );
  0         0  
2258             }
2259             }
2260              
2261             sub add_chord {
2262 1046     1046 0 2679 my ( $self, $info, $new_id ) = @_;
2263              
2264 1046 100       2426 if ( $new_id ) {
2265 989 100       2615 if ( $new_id eq "1" ) {
2266 11         41 state $id = "ch0000";
2267 11         22 $new_id = " $id";
2268 11         46 $id++;
2269             }
2270             }
2271             else {
2272 57         178 $new_id = $info->name;
2273             }
2274 1046         3438 $self->{chordsinfo}->{$new_id} = $info->new($info);
2275              
2276 1046         4892 return $new_id;
2277             }
2278              
2279             sub define_chord {
2280 72     72 0 277 my ( $self, $dir, $args ) = @_;
2281              
2282             # Split the arguments and keep a copy for error messages.
2283             # Note that quotewords returns an empty result if it gets confused,
2284             # so fall back to the ancient split method if so.
2285 72         256 $args =~ s/^\s+//;
2286 72         413 $args =~ s/\s+$//;
2287 72         355 my @a = quotewords( '[: ]+', 0, $args );
2288 72 100       26100 @a = split( /[: ]+/, $args ) unless @a;
2289              
2290 72         329 my @orig = @a;
2291 72         230 my $show = $dir eq "chord";
2292 72         139 my $fail = 0;
2293 72         159 my $name = shift(@a);
2294 72         430 my $strings = $config->diagram_strings;
2295              
2296             # Process the options.
2297 72         288 my %kv = ( name => $name );
2298 72         210 while ( @a ) {
2299 162         311 my $a = shift(@a);
2300              
2301             # Copy existing definition.
2302 162 100 66     1418 if ( $a eq "copy" || $a eq "copyall" ) {
    100 66        
    100 66        
    100 33        
    100          
    100          
    100          
    50          
2303 11 50       53 if ( my $i = ChordPro::Chords::known_chord($a[0]) ) {
2304 11         35 $kv{$a} = $a[0];
2305 11         27 $kv{orig} = $i;
2306 11         29 shift(@a);
2307             }
2308             else {
2309 0         0 do_warn("Unknown chord to copy: $a[0]\n");
2310 0         0 $fail++;
2311 0         0 last;
2312             }
2313             }
2314              
2315             # display
2316             elsif ( $a eq "display" && @a ) {
2317 2         15 $kv{display} = demarkup($a[0]);
2318             do_warn( "\"display\" should not contain markup, use \"format\"" )
2319 2 50       13 unless $kv{display} eq shift(@a);
2320 2         15 $kv{display} = $self->parse_chord($kv{display},1);
2321 2 50       20 delete $kv{display} unless defined $kv{display};
2322             }
2323              
2324             # format
2325             elsif ( $a eq "format" && @a ) {
2326 9         39 $kv{format} = shift(@a);
2327             }
2328              
2329             # base-fret N
2330             elsif ( $a eq "base-fret" ) {
2331 46 50       302 if ( $a[0] =~ /^\d+$/ ) {
2332 46         608 $kv{base} = shift(@a);
2333             }
2334             else {
2335 0         0 do_warn("Invalid base-fret value: $a[0]\n");
2336 0         0 $fail++;
2337 0         0 last;
2338             }
2339             }
2340             # frets N N ... N
2341             elsif ( $a eq "frets" ) {
2342 57         130 my @f;
2343 57   100     514 while ( @a && $a[0] =~ /^(?:-?[0-9]+|[-xXN])$/ && @f < $strings ) {
      66        
2344 342         1877 push( @f, shift(@a) );
2345             }
2346 57 50       142 if ( @f == $strings ) {
2347 57 100       144 $kv{frets} = [ map { $_ =~ /^\d+/ ? $_ : -1 } @f ];
  342         1275  
2348             }
2349             else {
2350 0         0 do_warn("Incorrect number of fret positions (" .
2351             scalar(@f) . ", should be $strings)\n");
2352 0         0 $fail++;
2353 0         0 last;
2354             }
2355             }
2356              
2357             # fingers N N ... N
2358             elsif ( $a eq "fingers" ) {
2359 16         77 my @f;
2360             # It is tempting to limit the fingers to 1..5 ...
2361 16   100     118 while ( @a && @f < $strings ) {
2362 96         194 local $_ = shift(@a);
2363 96 100       314 if ( /^[0-9]+$/ ) {
    50          
    50          
2364 90         415 push( @f, 0 + $_ );
2365             }
2366             elsif ( /^[A-MO-WYZ]$/ ) {
2367 0         0 push( @f, $_ );
2368             }
2369             elsif ( /^[-xNX]$/ ) {
2370 6         38 push( @f, -1 );
2371             }
2372             else {
2373 0         0 unshift( @a, $_ );
2374 0         0 last;
2375             }
2376             }
2377 16 50       49 if ( @f == $strings ) {
2378 16         121 $kv{fingers} = \@f;
2379             }
2380             else {
2381 0         0 do_warn("Incorrect number of finger settings (" .
2382             scalar(@f) . ", should be $strings)\n");
2383 0         0 $fail++;
2384 0         0 last;
2385             }
2386             }
2387              
2388             # keys N N ... N
2389             elsif ( $a eq "keys" ) {
2390 8         15 my @f;
2391 8   100     42 while ( @a && $a[0] =~ /^[0-9]+$/ ) {
2392 24         113 push( @f, shift(@a) );
2393             }
2394 8 50       21 if ( @f ) {
2395 8         27 $kv{keys} = \@f;
2396             }
2397             else {
2398 0         0 do_warn("Invalid or missing keys\n");
2399 0         0 $fail++;
2400 0         0 last;
2401             }
2402             }
2403              
2404             elsif ( $a eq "diagram" && @a > 0 ) {
2405 13 50 33     50 if ( $show && !is_true($a[0]) ) {
2406 0         0 do_warn("Useless diagram suppression");
2407 0         0 next;
2408             }
2409 13         47 $kv{diagram} = shift(@a);
2410             }
2411              
2412             # Wrong...
2413             else {
2414             # Insert a marker to show how far we got.
2415 0         0 splice( @orig, @orig-@a, 0, "<<<" );
2416 0         0 splice( @orig, @orig-@a-2, 0, ">>>" );
2417 0         0 do_warn("Invalid chord definition: @orig\n");
2418 0         0 $fail++;
2419 0         0 last;
2420             }
2421             }
2422              
2423 72 50       191 return 1 if $fail;
2424             # All options are verified and stored in %kv;
2425              
2426             # Result structure.
2427 72         297 my $res = { name => $name };
2428              
2429             # Try to find info.
2430 72         321 my $info = $self->parse_chord( $name, "def" );
2431 72 100       206 if ( $info ) {
2432             # Copy the chord info.
2433             $res->{$_} //= $info->{$_} // ''
2434 71   100     2877 for qw( parser root qual ext bass
      66        
2435             root_canon qual_canon ext_canon bass_canon
2436             root_ord root_mod bass_ord bass_mod
2437             );
2438 71 100       222 if ( $show ) {
2439             $res->{$_} //= $info->{$_}
2440 9   66     99 for qw( base frets fingers keys );
2441             }
2442             }
2443             else {
2444 1         6 $res->{parser} = ChordPro::Chords::get_parser();
2445             }
2446              
2447             # Copy existing definition.
2448 72   66     378 for ( $kv{copyall} // $kv{copy} ) {
2449 72 100       210 next unless defined;
2450 11         35 $res->{copy} = $_;
2451 11         25 my $orig = $res->{orig} = $kv{orig};
2452             $res->{$_} //= $orig->{$_}
2453 11   33     128 for qw( base frets fingers keys );
2454 11 50       41 if ( $kv{copyall} ) {
2455             $res->{$_} //= $orig->{$_}
2456 0   0     0 for qw( display format );
2457             }
2458             }
2459 72         149 for ( qw( display format ) ) {
2460 144 100       441 $res->{$_} = $kv{$_} if defined $kv{$_};
2461             }
2462              
2463             # If we've got diagram visibility, remove it if true.
2464 72 100       199 if ( defined $kv{diagram} ) {
2465 13         35 for ( my $v = $kv{diagram} ) {
2466 13 100       61 if ( is_true($v) ) {
2467 7 100       26 if ( is_ttrue($v) ) {
2468 6         19 next;
2469             }
2470             }
2471             else {
2472 6         16 $v = 0;
2473             }
2474 7         26 $res->{diagram} = $v;
2475             }
2476             }
2477              
2478             # Copy rest of options.
2479 72         151 for ( qw( base frets fingers keys display format ) ) {
2480 432 100       984 next unless defined $kv{$_};
2481 138         315 $res->{$_} = $kv{$_};
2482             }
2483              
2484             # At this time, $res is still just a hash. Time to make a chord.
2485 72   100     226 $res->{base} ||= 1;
2486 72 100       1409 $res = ChordPro::Chord::Common->new
2487             ( { %$res, origin => $show ? "inline" : "song" } );
2488 72   33     501 $res->{parser} //= ChordPro::Chords::get_parser();
2489              
2490 72 100       188 if ( $show) {
2491 9         70 my $ci = $res->clone;
2492 9         7951 my $chidx = $self->add_chord( $ci, 1 );
2493             # Combine consecutive entries.
2494 9 100 66     70 if ( defined($self->{body})
2495             && $self->{body}->[-1]->{type} eq "diagrams" ) {
2496 2         3 push( @{ $self->{body}->[-1]->{chords} }, $chidx );
  2         7  
2497             }
2498             else {
2499 7         36 $self->add( type => "diagrams",
2500             show => "user",
2501             origin => "chord",
2502             chords => [ $chidx ] );
2503             }
2504 9         141 return 1;
2505             }
2506              
2507 63         142 my $def = {};
2508 63         182 for ( qw( name base frets fingers keys display format diagram ) ) {
2509 504 100       1228 next unless defined $res->{$_};
2510 248         615 $def->{$_} = $res->{$_};
2511             }
2512 63         140 push( @{$self->{define}}, $def );
  63         214  
2513 63         283 my $ret = ChordPro::Chords::add_song_chord($res);
2514 63 50       168 if ( $ret ) {
2515 0         0 do_warn("Invalid chord: ", $res->{name}, ": ", $ret, "\n");
2516 0         0 return 1;
2517             }
2518 63         279 $info = ChordPro::Chords::known_chord($res->{name});
2519 63 50       182 croak("We just entered it?? ", $res->{name}) unless $info;
2520              
2521 63 50       275 $info->dump if $config->{debug}->{x1};
2522              
2523 63         1015 return 1;
2524             }
2525              
2526             sub duration {
2527 9     9 0 32 my ( $dur ) = @_;
2528              
2529 9 50       107 if ( $dur =~ /(?:(?:(\d+):)?(\d+):)?(\d+)/ ) {
2530 9 50       70 $dur = $3 + ( $2 ? 60 * $2 :0 ) + ( $1 ? 3600 * $1 : 0 );
    50          
2531             }
2532 9         77 my $res = sprintf( "%d:%02d:%02d",
2533             int( $dur / 3600 ),
2534             int( ( $dur % 3600 ) / 60 ),
2535             $dur % 60 );
2536 9         39 $res =~ s/^[0:]+//;
2537 9         27 return $res;
2538             }
2539              
2540             sub get_color {
2541 37     37 0 94 $_[0];
2542             }
2543              
2544             sub _diag {
2545 23     23   32455 my ( $self, %d ) = @_;
2546 23         213 $diag->{$_} = $d{$_} for keys(%d);
2547             }
2548              
2549             sub msg {
2550 4     4 0 18 my $m = join("", @_);
2551 4         26 $m =~ s/\n+$//;
2552 4         18 my $t = $diag->{format};
2553 4         11 $t =~ s/\\n/\n/g;
2554 4         11 $t =~ s/\\t/\t/g;
2555 4         29 $t =~ s/\%f/$diag->{file}/g;
2556 4         27 $t =~ s/\%n/$diag->{line}/g;
2557 4         17 $t =~ s/\%l/$diag->{orig}/g;
2558 4         22 $t =~ s/\%m/$m/g;
2559 4         34 $t;
2560             }
2561              
2562             sub do_warn {
2563 4     4 0 48 warn(msg(@_)."\n");
2564             }
2565              
2566             # Parse a chord.
2567             # Handles transpose/transcode.
2568             # Returns the chord object.
2569             # No parens or annotations, please.
2570             sub parse_chord {
2571 1169     1169 0 3202 my ( $self, $chord, $def ) = @_;
2572              
2573 1169         4277 my $debug = $config->{debug}->{chords};
2574              
2575 1169 50       2895 warn("Parsing chord: \"$chord\"\n") if $debug;
2576 1169         1850 my $info;
2577 1169         3407 my $xp = $xpose + $config->{settings}->{transpose};
2578 1169 100 100     3518 $xp += $capo if $capo && $decapo;
2579 1169         2787 my $xc = $config->{settings}->{transcode};
2580 1169         2816 my $global_dir = $config->{settings}->{transpose} <=> 0;
2581 1169         1798 my $unk;
2582              
2583             # When called from {define} ignore xc/xp.
2584 1169 100       2729 $xc = $xp = '' if $def;
2585              
2586 1169         4571 $info = ChordPro::Chords::known_chord($chord);
2587 1169 100       3025 if ( $info ) {
2588             warn( "Parsing chord: \"$chord\" found \"",
2589 881 50       2268 $info->name, "\" in ", $info->{_via}, "\n" ) if $debug > 1;
2590 881 100       3674 return ChordPro::Chord::NC->new( { name => $info->name } )
2591             if $info->is_nc;
2592 874 50       2154 $info->dump if $debug > 1;
2593             }
2594             else {
2595 288         975 $info = ChordPro::Chords::parse_chord($chord);
2596             warn( "Parsing chord: \"$chord\" parsed ok [",
2597             $info->{system},
2598 288 50 66     1808 "]\n" ) if $info && $debug > 1;
2599             }
2600 1162         2665 $unk = !defined $info;
2601              
2602 1162 100 100     7597 if ( ( $def || $xp || $xc )
      66        
      100        
2603             &&
2604             ! ($info && $info->is_xpxc ) ) {
2605 22         78 local $::config->{settings}->{chordnames} = "relaxed";
2606 22         62 $info = ChordPro::Chords::parse_chord($chord);
2607             }
2608              
2609 1162 100 66     4782 unless ( ( $info && $info->is_xpxc )
      33        
      66        
      100        
2610             ||
2611             ( $def && !( $xc || $xp ) ) ) {
2612             do_warn( "Cannot parse",
2613             $xp ? "/transpose" : "",
2614             $xc ? "/transcode" : "",
2615             " chord \"$chord\"\n" )
2616 8 0 33     85 if $xp || $xc || $config->{debug}->{chords};
    0 33        
    50          
2617             }
2618              
2619 1162 50 66     4262 if ( $xp && $info
      33        
      66        
2620             && !( $xc && ( $xc eq "nashville" || $xc eq "roman" ) ) ) {
2621             # For transpose/transcode, chord must be wellformed.
2622 158   100     916 my $i = $info->transpose( $xp,
2623             $xpose_dir // $global_dir);
2624             # Prevent self-references.
2625 158 50       986 $i->{xp} = $info unless $i eq $info;
2626 158         299 $info = $i;
2627             warn( "Parsing chord: \"$chord\" transposed ",
2628             sprintf("%+d", $xp), " to \"",
2629             $info->name, "\"",
2630 158 0       602 ( $self->{meta}->{key} ? (" key ".$self->{meta}->{key}->[-1]) : ()),
    50          
2631             "\n" ) if $debug > 1;
2632             }
2633             # else: warning has been given.
2634              
2635 1162 100       2630 if ( $info ) { # TODO roman?
2636             # Look it up now, the name may change by transcode.
2637 1153 100 33     3355 if ( my $i = ChordPro::Chords::known_chord($info) ) {
    50          
2638             warn( "Parsing chord: \"$chord\" found ",
2639             $i->name, " for ", $info->name,
2640 879 50       2049 " in ", $i->{_via}, "\n" ) if $debug > 1;
2641             $info = $i->new({ %$i, name => $info->name,
2642             $info->{xp} ? ( xp => $info->{xp} ) : (),
2643 879 100       6153 $info->{xc} ? ( xc => $info->{xc} ) : (),
    50          
2644             }) ;
2645 879         6380 $unk = 0;
2646             }
2647             elsif ( $config->{instrument}->{type} eq 'keyboard'
2648             && ( my $k = ChordPro::Chords::get_keys($info) ) ) {
2649 0 0       0 warn( "Parsing chord: \"$chord\" \"", $info->name, "\" not found ",
2650             "but we know what to do\n" ) if $debug > 1;
2651 0         0 $info = $info->new({ %$info, keys => $k }) ;
2652 0         0 $unk = 0;
2653             }
2654             else {
2655 274 50       704 warn( "Parsing chord: \"$chord\" \"", $info->name,
2656             "\" not found in song/config chords\n" ) if $debug;
2657             # warn("XX \'", $info->agnostic, "\'\n");
2658 274         660 $unk = 1;
2659             }
2660             }
2661              
2662 1162 100 66     3774 if ( $xc && $info ) {
2663 20         40 my $key_ord;
2664             $key_ord = $self->{chordsinfo}->{$self->{meta}->{key}->[-1]}->{root_ord}
2665 20 50       145 if $self->{meta}->{key};
2666 20 50 33     117 if ( $xcmov && !defined $key_ord ) {
2667 0         0 do_warn("Warning: Transcoding to $xc without key may yield unexpected results\n");
2668 0         0 undef $xcmov;
2669             }
2670 20         108 my $i = $info->transcode( $xc, $key_ord );
2671             # Prevent self-references.
2672 20 50       136 $i->{xc} = $info unless $i eq $info;
2673 20         41 $info = $i;
2674             warn( "Parsing chord: \"$chord\" transcoded to ",
2675             $info->name,
2676             " (", $info->{system}, ")",
2677 20 0       87 defined($key_ord) ? " key ".$self->{meta}->{key}->[-1] : "",
    50          
2678             "\n" ) if $debug > 1;
2679 20 100       87 if ( my $i = ChordPro::Chords::known_chord($info) ) {
2680 8 50       17 warn( "Parsing chord: \"$chord\" found \"",
2681             $info->name, "\" in song/config chords\n" ) if $debug > 1;
2682 8         21 $unk = 0;
2683             }
2684             }
2685             # else: warning has been given.
2686              
2687 1162 100       2879 if ( ! $info ) {
2688 9 50       35 if ( my $i = ChordPro::Chords::known_chord($chord) ) {
2689 0         0 $info = $i;
2690             warn( "Parsing chord: \"$chord\" found \"",
2691             $chord, "\" in ",
2692 0 0       0 $i->{_via}, "\n" ) if $debug > 1;
2693 0         0 $unk = 0;
2694             }
2695             }
2696              
2697 1162 100 100     3251 unless ( $info || $def ) {
2698 8 100 66     46 if ( $config->{debug}->{chords} || ! $warned_chords{$chord}++ ) {
2699 1 50       3 warn("Parsing chord: \"$chord\" unknown\n") if $debug;
2700 1 50       7 do_warn( "Unknown chord: \"$chord\"\n" )
2701             unless $chord =~ /^n\.?c\.?$/i;
2702             }
2703             }
2704              
2705 1162 100       2824 if ( $info ) {
2706             $info->{key} = $self->{meta}->{key}->[-1]
2707 1153 50       6710 unless $config->{settings}->{'enharmonic-transpose'};
2708 1153 0       3053 warn( "Parsing chord: \"$chord\" okay: \"",
    50          
2709             $info->name, "\" \"",
2710             $info->chord_display, "\"",
2711             $unk ? " but unknown" : "",
2712             "\n" ) if $debug > 1;
2713 1153         4701 $self->store_chord($info);
2714 1153         3824 return $info;
2715             }
2716              
2717 9 50       23 warn( "Parsing chord: \"$chord\" not found\n" ) if $debug;
2718 9         30 return;
2719             }
2720              
2721             sub store_chord {
2722 1173     1173 0 2613 my ( $self, $info ) = @_;
2723 1173         4349 $self->{chordsinfo}->{$info->name} = $info;
2724 1173         3605 $info->name;
2725             }
2726              
2727             sub structurize {
2728 13     13 0 38 my ( $self ) = @_;
2729              
2730 13 50       54 return if $self->{structure} eq "structured";
2731              
2732 13         47 my @body;
2733 13         36 my $context = $def_context;
2734              
2735 13         22 foreach my $item ( @{ $self->{body} } ) {
  13         52  
2736 239 100 66     724 if ( $item->{type} eq "empty" && $item->{context} eq $def_context ) {
2737 56         103 $context = $def_context;
2738 56         125 next;
2739             }
2740 183 100 100     520 if ( $item->{type} eq "songline" && $item->{context} eq '' ){ # A songline should have a context - non means verse
2741 36         58 $item->{context} = 'verse';
2742             }
2743 183 100       386 if ( $context ne $item->{context} ) {
2744 43         175 push( @body, { type => $context = $item->{context}, body => [] } );
2745             }
2746 183 100       309 if ( $context ) {
2747 135         185 push( @{ $body[-1]->{body} }, $item );
  135         304  
2748             }
2749             else {
2750 48         84 push( @body, $item );
2751             }
2752             }
2753 13         103 $self->{body} = [ @body ];
2754 13         67 $self->{structure} = "structured";
2755             }
2756              
2757             sub dump {
2758 0     0 0   my ( $self, $full ) = @_;
2759 0   0       $full ||= 0;
2760              
2761 0 0         if ( $full == 2 ) {
2762 0           return ::dump($self->{body});
2763             }
2764 0           my $a = dclone($self);
2765 0           $a->{config} = ref(delete($a->{config}));
2766 0 0         unless ( $full ) {
2767 0           for my $ci ( keys %{$a->{chordsinfo}} ) {
  0            
2768 0           $a->{chordsinfo}{$ci} = $a->{chordsinfo}{$ci}->simplify;
2769             }
2770             }
2771 0           ::dump($a);
2772             }
2773              
2774             1;