1#!/usr/bin/env perl
2# -*- coding: utf-8; -*-
3
4### texi2html customization script for LilyPond
5### Author: Reinhold Kainhofer <reinhold@kainhofer.com>, 2008.
6###         Some code parts copied from texi2html and adapted. These functions
7###         were written mainly by Patrice Dumas
8### License: GPLv3+
9###
10###
11### Features implemented here:
12### -) For split manuals, the main page is index.html.
13### -) All @unnumbered* sections are placed into the same file
14###    (implemented by split_at_numbered_sections)
15### -) Use our custom CSS file, with IE-specific fixes in another CSS file,
16###    implemented by lilypond_css_lines
17### -) TOC (folded, with the current page highlighted) in an overflown <div>
18###    is added to every page; implemented by:
19###           lilypond_print_page_head -- start <div id="main">
20###           lilypond_print_page_foot -- closing id=main, output of footer & TOC
21### -) External refs are formatted only as "Text of the node" (not as >>see
22###    "NODE" section "SECTION" in "BOOK"<< like with default texi2html). Also,
23###    the leading "(book-name)" is removed.
24###    Implemented by overriding lilypond_external_ref
25### -) Custom navigation bars on top/bottom of the page and between sections;
26###    Implemented in lilypond_print_navigation
27### -) Different formatting than the default: example uses the same formatting
28###    as quote.
29### -) Allow translated section titles: All section titles can be translated,
30###    the original (English) title is associated with @translationof. This is
31###    needed, because the file name / anchor is generated from the original
32###    English title, since otherwise language-autoselection would break with
33###    posted links.
34###    Since it is then no longer possible to obtain the file name from the
35###    section title, I keep a sectionname<=>filename/anchor around. This way,
36###    xrefs from other manuals can simply load that map and retrieve the
37###    correct file name for the link. Implemented in:
38###           lilypond_unknown (handling of @translationof, in case
39###                             extract_texi_filenames.py messes up...)
40###           lilypond_element_file_name (correct file name: use the map)
41###           lilypond_element_target_name (correct anchor: use the map)
42###           lilypond_init_map (read in the externally created map from disk)
43###           lilypond_external_href (load the map for xrefs, use the correct
44###                                   link target)
45### -) The HTML anchors for all sections are derived from the node name /
46###    section title (pre-generated in the .xref-map file). Implemented by:
47###           lilypond_element_target_name (adjust section anchors)
48### -) Use the standard footnote format "<sup>nr</sup> text" instead of the
49###    ugly format of texi2html (<h3>(nr)</h3><p>text</p>). Implemented in
50###           makeinfo_like_foot_line_and_ref
51###           makeinfo_like_foot_lines
52###           makeinfo_like_paragraph
53### -) In tables, don't wrap <p> around the contents. Implemented in
54###           makeinfo_like_paragraph
55###
56###
57### Useful helper functions:
58### -) texinfo_file_name($node_name): returns a texinfo-compatible file name
59###    for the given string $node_name (whitespace trimmed/replaced by -,
60###    non-standard chars replaced by _xxxx (ascii char code) and forced to
61###    start with a letter by prepending t_g if necessary)
62
63package main;
64$original_normalise_node = \&normalise_node;
65
66sub t2h_default_normalise_node($)
67{
68    my $text = shift;
69    $original_normalise_node->($text);
70}
71
72*normalise_node = sub($)
73{
74    my $text = shift;
75    return &$Texi2HTML::Config::normalise_node($text);
76};
77
78package Texi2HTML::Config;
79##$normalise_node = \&t2h_default_normalise_node;
80$normalise_node = \&lilypond_normalise_node;
81
82
83use utf8;
84use Encode qw(decode);
85
86sub ly_get_language () {
87    my $lang = $Texi2HTML::THISDOC{'documentlanguage'};
88    # Old key for texi2html-1.82
89    $lang = $Texi2HTML::THISDOC{'current_lang'} if !defined $lang;
90    return $lang;
91}
92
93# Translations declared in lilypond-texi2html-lang.init.
94
95sub ly_get_string () {
96    my $lang = ly_get_language ();
97    my $string = shift;
98    if ($lang and $lang ne "en" and $LY_LANGUAGES->{$lang}->{$string}) {
99	return $LY_LANGUAGES->{$lang}->{$string};
100    } else {
101	return $string;
102    }
103}
104
105
106#############################################################################
107### FUNCTIONALITY FOR MAIN WEB PAGES
108#############################################################################
109
110our $web_manual;
111
112#############################################################################
113###  SETTINGS FOR TEXI2HTML
114#############################################################################
115
116# Validation fix for texi2html<=1.82
117$Texi2HTML::Config::DOCTYPE = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">';
118
119@Texi2HTML::Config::CSS_REFS = (
120    {FILENAME => "lilypond-manuals.css", TITLE => "Default style"}
121    );
122@Texi2HTML::Config::ALT_CSS_REFS      = (
123    );
124
125sub web_settings() {
126  print STDERR "Processing web site: [" . ly_get_language () . "]\n";
127  $Texi2HTML::Config::BODYTEXT = "";
128  @Texi2HTML::Config::CSS_REFS      = (
129      {FILENAME => "lilypond-website.css", TITLE => "Default style"}
130      );
131  @Texi2HTML::Config::ALT_CSS_REFS      = (
132      );
133}
134
135$Texi2HTML::Config::USE_ACCESSKEY = 1;
136$Texi2HTML::Config::USE_LINKS     = 1;
137$Texi2HTML::Config::USE_REL_REV   = 1;
138$Texi2HTML::Config::SPLIT_INDEX   = 0;
139$Texi2HTML::Config::SEPARATED_FOOTNOTES = 0; # Print footnotes on same page, not separated
140
141$DO_CONTENTS = 1;
142
143# The default has changed to 'class="no-bullet"' in texi2html 5.0.
144$NO_BULLET_LIST_CLASS = 'toc';
145
146my $bigpage = 0;
147my $have_index_entries = 0;
148if ($Texi2HTML::Config::SPLIT eq 'section' or
149    $Texi2HTML::Config::SPLIT eq 'node') {
150  $Texi2HTML::Config::element_file_name    = \&lilypond_element_file_name;
151  $bigpage = 0;
152} else {
153  $bigpage = 1;
154}
155
156$Texi2HTML::Config::anchor  = \&lilypond_anchor;
157$Texi2HTML::Config::element_target_name  = \&lilypond_element_target_name;
158$default_print_page_head = $Texi2HTML::Config::print_page_head;
159$Texi2HTML::Config::print_page_head      = \&lilypond_print_page_head;
160$Texi2HTML::Config::print_page_foot      = \&lilypond_print_page_foot;
161$Texi2HTML::Config::print_navigation     = \&lilypond_print_navigation;
162$Texi2HTML::Config::print_title          = \&lilypond_print_title;
163$Texi2HTML::Config::about_body           = sub { return ''; };
164$Texi2HTML::Config::external_ref         = \&lilypond_external_ref;
165$default_external_href = $Texi2HTML::Config::external_href;
166$Texi2HTML::Config::external_href        = \&lilypond_external_href;
167$Texi2HTML::Config::css_lines            = \&lilypond_css_lines;
168$default_unknown = $Texi2HTML::Config::unknown;
169$Texi2HTML::Config::unknown              = \&lilypond_unknown;
170# $Texi2HTML::Config::foot_line_and_ref    = \&lilypond_foot_line_and_ref;
171$Texi2HTML::Config::foot_line_and_ref  = \&makeinfo_like_foot_line_and_ref;
172$Texi2HTML::Config::foot_lines         = \&makeinfo_like_foot_lines;
173$Texi2HTML::Config::paragraph          = \&makeinfo_like_paragraph;
174
175
176
177# Examples should be formatted similar to quotes:
178$Texi2HTML::Config::complex_format_map->{'example'} = {
179  'begin' => q{"<blockquote>"},
180  'end' => q{"</blockquote>\n"},
181  'style' => 'code',
182 };
183
184%Texi2HTML::config::misc_pages_targets = (
185   'Overview' => 'Overview',
186   'Contents' => 'Contents',
187   'About' => 'About'
188);
189
190
191my @section_to_filename;
192
193
194
195
196#############################################################################
197###  DEBUGGING
198#############################################################################
199
200use Data::Dumper;
201$Data::Dumper::Maxdepth = 2;
202
203sub print_element_info($)
204{
205  my $element = shift;
206  print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
207  print "Element: $element\n";
208  print Dumper($element);
209}
210
211
212
213
214
215#############################################################################
216###  HELPER FUNCTIONS
217#############################################################################
218
219# only lc() the last portion of an href
220sub lc_last($)
221{
222  my $href = shift;
223  my @hrefsplit = split('/', $href);
224  # change the last portion (the filename), if it exists;
225  # if it is a plain filename with no path, change the string as a whole
226  if ($#hrefsplit > 0) {
227    @hrefsplit[$#hrefsplit] = lc( @hrefsplit[$#hrefsplit] );
228    $href = join("/", @hrefsplit);
229  } else {
230    $href = lc($href);
231  }
232  return $href;
233}
234
235# Convert a given node name to its proper file name (normalization as explained
236# in the texinfo manual:
237# http://www.gnu.org/software/texinfo/manual/texinfo/html_node/HTML-Xref-Node-Name-Expansion.html
238sub texinfo_file_name($)
239{
240  my $text = shift;
241  my $result = '';
242  # File name normalization by texinfo:
243  # 1/2: letters and numbers are left unchanged
244  # 3/4: multiple, leading and trailing whitespace is removed
245  $text = main::normalise_space($text);
246  # 5/6: all remaining spaces are converted to '-', all other 7- or 8-bit
247  #      chars are replaced by _xxxx (xxxx=ascii character code)
248  while ($text ne '') {
249    if ($text =~ s/^([A-Za-z0-9]+)//o) { # number or letter stay unchanged
250      $result .= $1;
251    } elsif ($text =~ s/^ //o) { # space -> '-'
252      $result .= '-';
253    } elsif ($text =~ s/^(.)//o) { # Otherwise use _xxxx (ascii char code)
254      my $ccode = ord($1);
255      if ( $ccode <= 0xFFFF ) {
256        $result .= sprintf("_%04x", $ccode);
257      } else {
258        $result .= sprintf("__%06x", $ccode);
259      }
260    }
261  }
262  # 7: if name does not begin with a letter, prepend 't_g' (so it starts with a letter)
263  if ($result !~ /^[a-zA-Z]/) {
264    $result = 't_g' . $result;
265  }
266  # DONE
267  return lc_last($result)
268}
269
270# Load a file containing a nodename<=>filename map (tab-sepatared, i.e.
271# NODENAME\tFILENAME\tANCHOR
272# Returns a ref to a hash "Node title" => ["FilenameWithoutExt", "Anchor"]
273sub load_map_file ($)
274{
275  my $mapfile = shift;
276  my $node_map = ();
277
278  # For some unknown reason, Perl on my system (5.10.0 on Fedora 12)
279  # refuses to open map files of translated documents with
280  # '<:encoding(utf8)', but decoding from UTF-8 line by line works. -jm
281  if (open(XREFFILE,'<', $mapfile)) {
282    my $line;
283    # print STDERR "*** PRINTING MAP FILE LINES ***\n";
284    while ( $line = decode ('UTF-8', <XREFFILE>) ) {
285      # parse the tab-separated entries and insert them into the map:
286      chomp($line);
287      my @entries = split(/\t/, $line);
288      if (scalar (@entries) == 3) {
289	$node_map->{$entries[0]} = [$entries[1], $entries[2]];
290	$, = " ";
291	# print STDERR @entries;
292	# print STDERR "\n";
293      } else {
294	print STDERR "Invalid entry in the node file $mapfile: $line\n";
295      }
296    }
297    close (XREFFILE);
298  } else {
299    print STDERR "WARNING: Unable to load the map file $mapfile\n";
300  }
301  return $node_map;
302}
303
304
305# Split the given path into dir and basename (with .texi removed). Used mainly
306# to get the path/basename of the original texi input file
307sub split_texi_filename ($)
308{
309  my $docu = shift;
310  my ($docu_dir, $docu_name);
311  if ($docu =~ /(.*\/)/) {
312    chop($docu_dir = $1);
313    $docu_name = $docu;
314    $docu_name =~ s/.*\///;
315  } else {
316     $docu_dir = '.';
317     $docu_name = $docu;
318  }
319  $docu_name =~ s/\.te?x(i|info)?$//;
320  return ($docu_dir, $docu_name);
321}
322
323
324
325
326
327#############################################################################
328###  CSS HANDLING
329#############################################################################
330
331# Include our standard CSS file, not hard-coded CSS code directly in the HTML!
332# For IE, add a second conditionally included CSS file.
333sub lilypond_css_lines ($$)
334{
335  my $import_lines = shift;
336  my $rule_lines = shift;
337  return if (defined($Texi2HTML::THISDOC{'CSS_LINES'}));
338  if (@$rule_lines or @$import_lines)
339  {
340    $Texi2HTML::THISDOC{'CSS_LINES'} = "<style type=\"text/css\">\n<!--\n";
341    $Texi2HTML::THISDOC{'CSS_LINES'} .= join('',@$import_lines) . "\n" if (@$import_lines);
342    $Texi2HTML::THISDOC{'CSS_LINES'} .= join('',@$rule_lines) . "\n" if (@$rule_lines);
343    $Texi2HTML::THISDOC{'CSS_LINES'} .= "-->\n</style>\n";
344  }
345  foreach my $ref (@CSS_REFS)
346  {
347    $Texi2HTML::THISDOC{'CSS_LINES'} .=
348	"<link rel=\"stylesheet\" type=\"text/css\" title=\"$ref->{TITLE}\" href=\"css/$ref->{FILENAME}\">\n";
349  }
350  foreach my $ref (@Texi2HTML::Config::ALT_CSS_REFS)
351  {
352    $Texi2HTML::THISDOC{'CSS_LINES'} .=
353	"<link rel=\"alternate stylesheet\" type=\"text/css\" href=\"css/$ref->{FILENAME}\" title=\"$ref->{TITLE}\">\n";
354  }
355}
356
357
358
359
360
361#############################################################################
362###  SPLITTING BASED ON NUMBERED SECTIONS
363#############################################################################
364
365my $lastfilename;
366my $docnr = 0;
367my $node_to_filename_map = ();
368$source_to_translationof_map = ();
369
370
371# This function makes sure that files are only generated for numbered sections,
372# but not for unnumbered ones. It is called after texi2html has done its own
373# splitting and simply returns the filename for the node given as first argument
374# Nodes with the same filename will be printed out to the same filename, so
375# this really all we need. Also, make sure that the file names for sections
376# are derived from the section title. We also might want to name the anchors
377# according to node titles, which works by simply overriding the id element of
378# the $element hash.
379# If an external nodename<=>filename/anchor map file is found (loaded in
380# the command handler, use the externally created values, otherwise use the
381# same logic here.
382sub lilypond_element_file_name($$$)
383{
384  my $element = shift;
385  my $type = shift;
386  my $docu_name = shift;
387  my $docu_ext = $Texi2HTML::Config::EXTENSION;
388
389  my $node_name = main::remove_texi($element->{'node_ref'}->{'texi'});
390  # the snippets page does not use nodes for the snippets, so in this case
391  # we'll have to use the section name!
392  if ($node_name eq '') {
393    $node_name = main::remove_texi($element->{'texi'});
394  }
395
396  # If we have an entry in the section<=>filename map, use that one, otherwise
397  # generate the filename/anchor here. In the latter case, external manuals
398  # will not be able to retrieve the file name for xrefs!!! Still, I already
399  # had that code, so I'll leave it in in case something goes wrong with the
400  # extract_texi_filenames.py script in the lilypond build process!
401  if (exists ($node_to_filename_map->{$node_name})) {
402    (my $filename, my $anchor) = @{$node_to_filename_map->{$node_name}};
403    $filename .= ".$docu_ext" if (defined($docu_ext));
404    # don't do lc_last here, otherwise the colors are messed up!
405    $filename = lc($filename);
406
407    # unnumbered sections (except those at top-level!) always go to the same
408    # file as the previous numbered section
409    if (not ($web_manual) and not ($element->{number})
410	and not ($lastfilename eq '') and ($element->{level} > 1)) {
411      $filename = $lastfilename;
412    }
413    if (($filename eq $lastfilename)) {
414      $$element{doc_nr} = $docnr;
415    } else {
416      $docnr += 1;
417      $$element{doc_nr} = $docnr;
418      $lastfilename = $filename;
419    }
420    #print STDERR "Output file name: $filename\n";
421    $filename = lc_last($filename);
422    return $filename;
423
424  } elsif ($type eq "top" or $type eq "toc" or $type eq "doc" or
425	   $type eq "stoc" or $type eq "foot" or $type eq "about") {
426    return;
427  } else {
428    print STDERR "WARNING: Node '$node_name' was NOT found in the map\n"
429        unless ($node_name eq '') or ($element->{'tag'} eq 'unnumberedsec')
430               or ($node_name =~ /NOT REALLY USED/);
431
432    # Numbered sections will get a filename Node_title, unnumbered sections will use
433    # the file name of the previous numbered section:
434    if (($element->{number}) or ($lastfilename eq '') or ($element->{level} == 1)) {
435      # normalize to the same file name as texinfo
436      if ($element->{translationof}) {
437        $node_name = main::remove_texi($element->{translationof});
438      }
439      my $filename = texinfo_file_name($node_name);
440      $filename .= ".$docu_ext" if (defined($docu_ext));
441      $filename = lc_last($filename);
442      $docnr += 1;
443      $$element{doc_nr} = $docnr;
444      $lastfilename = $filename;
445      print STDERR "File name: $filename\n";
446      return $filename;
447    } else {
448      $$element{doc_nr} = $docnr;
449      $filename = lc_last($lastfilename);
450      print STDERR "File name: $filename\n";
451      return $filename;
452    }
453  }
454
455  return;
456}
457
458sub lilypond_normalise_node($)
459{
460    my $text = shift;
461    my $norm = main::t2h_default_normalise_node($text);
462    if (exists ($source_to_translationof_map->{$text})) {
463	my $original = $source_to_translationof_map->{$text};
464	$norm = main::t2h_default_normalise_node($original);
465    }
466
467    return $norm;
468}
469
470# This function produces an anchor.
471#
472# arguments:
473# $name           :   anchor name
474# $href           :   anchor href
475# text            :   text displayed
476# extra_attribs   :   added to anchor attributes list
477sub lilypond_anchor($;$$$)
478{
479    my $name = shift;
480    my $href = shift;
481    my $text = shift;
482    my $attributes = shift;
483    $href = remove_unneeded_anchor($href);
484    if (!defined($attributes) or ($attributes !~ /\S/))
485    {
486        $attributes = '';
487    }
488    else
489    {
490        $attributes = ' ' . $attributes;
491    }
492    $name = '' if (!defined($name) or ($name !~ /\S/));
493    $href = '' if (!defined($href) or ($href !~ /\S/));
494    $text = '' if (!defined($text));
495    return $text if (($name eq '') and ($href eq ''));
496    $name = "name=\"$name\"" if ($name ne '');
497    $href = "href=\"$href\"" if ($href ne '');
498    $href = ' ' . $href if (($name ne '') and ($href ne ''));
499    return "<a ${name}${href}${attributes}>$text</a>";
500}
501
502
503sub lilypond_element_target_name($$$)
504{
505  my $element = shift;
506  my $target = shift;
507  my $id = shift;
508  # Target is based on node name (or sec name for secs without node attached)
509  my $node_name = main::remove_texi($element->{'node_ref'}->{'texi'});
510  if ($node_name eq '') {
511    $node_name = main::remove_texi($element->{'texi'});
512  }
513
514  # If we have an entry in the section<=>filename map, use that one, otherwise
515  # generate the anchor here.
516  if (exists ($node_to_filename_map->{$node_name})) {
517    (my $filename, $target) = @{$node_to_filename_map->{$node_name}};
518  } else {
519    my $anchor = $node_name;
520    if ($element->{translationof}) {
521      $anchor = main::remove_texi($element->{translationof});
522    }
523    # normalize to the same file name as texinfo
524    $target = texinfo_file_name($anchor);
525  }
526  # TODO: Once texi2html correctly prints out the target and not the id for
527  #       the sections, change this back to ($id, $target)
528  # I don't understand this comment, so I'm reluctant to delete it -gp
529  $target = lc_last($target);
530  $id = lc($target);
531# $id =~ s/-1$//i; # remove any trailing "-1"
532  return ($target, $id);
533}
534
535sub ly_map_filename ($)
536{
537    my $map_name = shift;
538    my $map_filename = main::locate_include_file ($map_name . '.xref-map');
539    return $map_filename;
540}
541
542## Load the map file for the corrently processed texi file. We do this
543#  using a command init handler, since texi2html does not have any
544#  other hooks that are called after THISDOC is filled but before phase 2
545#  of the texi2html conversion.
546sub lilypond_init_map ()
547{
548    my ($docu_dir, $docu_name) = split_texi_filename ($Texi2HTML::THISDOC{'input_file_name'});
549    my $map_filename = ly_map_filename ($docu_name);
550    #print STDERR "Map filename is: $map_filename\nDocu name is $docu_name\n";
551    if ($docu_name eq 'web') {
552	$web_manual = 1;
553	web_settings();
554    }
555    $node_to_filename_map = load_map_file ($map_filename);
556}
557push @Texi2HTML::Config::command_handler_init, \&lilypond_init_map;
558
559sub lilypond_bodytext ()
560{
561  ## This section makes the manual name visible to CSS through the body tag
562  ## so that styles can be applied per manual. It will add the manual
563  ## directory name (e.g., 'notation' or 'learning') as a CSS class.
564
565  # Parse the input file name to determine the manual we're dealing with.
566  my ($docu_dir, $docu_name) = split_texi_filename ($Texi2HTML::THISDOC{'input_file_name'});
567
568  # Create the extra information for the <body> tag.
569  # For example, the development Notation reference in English
570  # will output in HTML as <body lang='en' class='notation'>
571  my $bodytext = 'lang="' . ly_get_language () . '" class="' . $docu_name . '"';
572
573  $Texi2HTML::THISDOC{'BODYTEXT'} = $bodytext;
574  # Set the global config variable for texi2html-1.82 which sets the previous
575  # variable at the beginning of init_out.
576  $Texi2HTML::Config::BODYTEXT = $bodytext;
577}
578push @Texi2HTML::Config::command_handler_process, \&lilypond_bodytext;
579
580#############################################################################
581###  CLEANER LINK TITLE FOR EXTERNAL REFS
582#############################################################################
583
584# The default formatting of external refs returns e.g.
585# "(lilypond-internals)Timing_translator", so we remove all (...) from the
586# file_and_node argument. Also, we want only a very simple format, so we don't
587# even call the default handler!
588sub lilypond_external_ref($$$$$$)
589{
590  my $type = shift;
591  my $section = shift;
592  my $book = shift;
593  my $file_node = shift;
594  my $href = shift;
595
596  $href = lc_last($href);
597
598  my $cross_ref = shift;
599  my $args_texi = shift;
600  my $formatted_args = shift;
601  my $node = shift;
602
603  # In texi2html-1.82, the node was passed as the section in case that was not
604  # available. Since version 5.0, the node is passed as an additional argument.
605  if ($section eq '' and defined $node) {
606    $section = $node;
607  }
608
609  my $displaytext = '';
610
611  # 1) if we have a cross ref name, that's the text to be displayed:
612  # 2) For the top node, use the (printable) name of the manual, unless we
613  #    have an explicit cross ref name
614  # 3) In all other cases use the section name
615  if ($cross_ref ne '') {
616    $displaytext = $cross_ref;
617  } elsif (($section eq '') or ($section eq 'Top')) {
618    $displaytext = $book;
619  } else {
620    $displaytext = $section;
621  }
622
623  $displaytext = &$anchor('', $href, $displaytext) if ($displaytext ne '');
624  return $displaytext;
625}
626
627
628
629
630
631#############################################################################
632###  HANDLING TRANSLATED SECTIONS: handle @translationof, secname<->filename
633###                  map stored on disk, xrefs in other manuals load that map
634#############################################################################
635
636
637# Try to make use of @translationof to generate files according to the original
638# English section title...
639sub lilypond_unknown($$$$$)
640{
641    my $macro = shift;
642    my $line = shift;
643    my $pass = shift;
644    my $stack = shift;
645    my $state = shift;
646
647    # the @translationof macro provides the original English section title,
648    # which should be used for file/anchor naming, while the title will be
649    # translated to each language
650    # It is already used by extract_texi_filenames.py, so this should not be
651    # necessary here at all. Still, I'll leave the code in just in case the
652    # python script messed up ;-)
653    if ($pass == 1 and $macro eq "translationof") {
654      if (ref($state->{'element'}) eq 'HASH') {
655	  $state->{'element'}->{'translationof'} = main::normalise_space($line);
656	  my $source = main::normalise_space ($line);
657	  if (ref($state->{'node_ref'}) eq 'HASH') {
658	      my $translationof = $state->{'node_ref'}->{'texi'};
659	      our %source_to_translationof_map;
660	      $source_to_translationof_map->{$source} = $translationof;
661	  }
662      }
663      return ('', 1, undef, undef);
664    } else {
665      return &$default_unknown($macro, $line, $pass, $stack, $state);
666    }
667}
668
669
670my %translated_books = ();
671# Construct a href to an external source of information.
672# node is the node with texinfo @-commands
673# node_id is the node transliterated and transformed as explained in the
674#         texinfo manual
675# node_xhtml_id is the node transformed such that it is unique and can
676#     be used to make an html cross ref as explained in the texinfo manual
677# file is the file in '(file)node'
678sub lilypond_external_href($$$)
679{
680  my $node = shift;
681  my $node_id = shift;
682  my $node_xhtml_id = shift;
683  my $file = shift;
684
685  # 1) Keep a hash of book->section_map
686  # 2) if not file in keys hash => try to load the map (assign empty map if
687  #    non-existent => will load only once!)
688  # 3) if node in the section=>(file, anchor) map, replace node_id and
689  #    node_xhtml_id by the map's values
690  # 4) call the default_external_href with these values (or the old ones if not found)
691
692  if (($node_id ne '') and defined($file) and ($node_id ne 'Top')) {
693    my $map_name = $file;
694    $map_name =~ s/-big-page//;
695
696    # Load the map if we haven't done so already
697    if (!exists($translated_books{$map_name})) {
698      $map_filename = ly_map_filename ($map_name);
699      $translated_books{$map_name} = load_map_file ($map_filename);
700    }
701
702    # look up translation. use these values instead of the old filename/anchor
703    my $section_name_map = $translated_books{$map_name};
704    my $node_text = main::remove_texi($node);
705    if (defined($section_name_map->{$node_text})) {
706      ($node_id, $node_xhtml_id) = @{$section_name_map->{$node_text}};
707    } else {
708      print STDERR "WARNING: Unable to find node '$node_text' in book $map_name.\n";
709    }
710  }
711
712  if (defined $file) {
713    $href = &$default_external_href($node, $node_id, $node_xhtml_id, lc_last($file));
714    $href = remove_unneeded_anchor($href);
715
716    if ($web_manual) {
717      my $only_web_version = $ENV{ONLY_WEB_VERSION};
718      if ($only_web_version) {
719        $href = "../../doc/".$only_web_version."/Documentation/web/".$href;
720      }
721    }
722
723    return $href;
724  } else {
725    $href = &$default_external_href($node, $node_id, $node_xhtml_id);
726    $href = remove_unneeded_anchor($href);
727    return $href;
728  }
729}
730
731sub lilypond_print_title ()
732{
733  return "";
734}
735
736sub remove_unneeded_anchor($)
737{
738  my $href = shift;
739  my @hrefsplit = split("/", $href);
740  for ($i = 0; $i < @hrefsplit; $i++) {
741    $item = @hrefsplit[$i];
742    if ($item =~ /#/) {
743      @split = split(".html#", $item);
744      if (@split[0] eq @split[1]) {
745        @hrefsplit[$i] = @split[0] . ".html";
746      }
747    }
748  }
749  $href = join("/", @hrefsplit);
750  return $href
751}
752
753
754
755#############################################################################
756###  CUSTOM TOC FOR EACH PAGE (in a frame on the left)
757#############################################################################
758
759sub lilypond_print_toc_div ($)
760{
761    my $fh = shift;
762
763    print $fh "\n\n<div id=\"tocframe\">\n";
764
765    # Remove the leading "GNU LilyPond --- " from the manual title
766    my $topname = $Texi2HTML::NAME{'Top'};
767    $topname =~ s/^GNU LilyPond(:| &[mn]dash;) //;
768
769    # construct the top-level Docs index (relative path and including language!)
770    my $lang = ly_get_language ();
771    if ($lang and $lang ne "en") {
772      $lang .= ".";
773    } else {
774      $lang = "";
775    }
776    my $reldir = $ENV{DEPTH};
777    # add a / at the end if there isn't one.
778    if (substr ($reldir, -1) ne '/') {
779        $reldir .= '/';
780    }
781    my $uplink = $reldir."Documentation/web/manuals.${lang}html";
782
783    if (not $web_manual) {
784      print $fh "<p class=\"toc_uplink\"><a href=\"$uplink\"
785         title=\"Documentation Index\">&lt;&lt; " .
786	 &ly_get_string ('Back to Documentation Index') .
787	 "</a></p>\n";
788
789      print $fh '<h4 class="toc_header"> ' . &$anchor('',
790                                    $Texi2HTML::HREF{'Top'},
791                                    $topname . " <!-- Sidebar Version Tag  --> ",
792                                    'title="Start of the manual"'
793                                   ) . "</h4>\n";
794    }
795
796    # Find the path to the current element
797    my $element = $Texi2HTML::THIS_ELEMENT;
798    my %parentelements;
799    while ($element and not $element->{'top'}) {
800        # The Snippets documentation has @unnumbered at the top level
801        # that we do want to have marked as "toc_current".
802        # The section on the web pages are special and have no numbering
803        # at all, but should still be taken into account.
804        if ($element->{'number'} or $element->{'toc_level'} == 1 or $web_manual) {
805            $parentelements{$element->{'tocid'}} = 1;
806        }
807        $element = $element->{'sectionup'};
808    }
809
810    foreach my $line (@{$Texi2HTML::TOC_LINES}) {
811        if ($line =~ /<a name="(.*)" href/ and $parentelements{$1}) {
812            # Copy the line to avoid modifying the original string. More recent
813            # versions of Perl (5.14, released in 2011) support non-destructive
814            # substitutions via option /r. To support Perl 5.10 in GUB, use the
815            # old way of doing it.
816            my $current_line = $line;
817            $current_line =~ s/<li>/<li class="toc_current">/;
818            print $fh $current_line;
819        } else {
820            print $fh $line;
821        }
822    }
823
824    local $/=undef;
825    my $name = "search-box";
826    $lang = ly_get_language ();
827    open FILE, "$ENV{TOP_SRC_DIR}/Documentation/$lang/$name.ihtml" or
828        open FILE, "$ENV{TOP_SRC_DIR}/Documentation/$name.ihtml" or
829        die "no such file: $name.ihtml: $!";
830    my $search_string = decode ('UTF-8', <FILE>);
831    # We depend on an external entity here, which we cannot control.  Suppose
832    # we go from 2.15.x to 2.17.x and put the documentation under "v2.17".
833    # For some time, Google won't have the new location in its index, so the
834    # search would get nothing.  It is better to keep "v2.15" in the search for
835    # a while.
836    my $search_site = "lilypond.org/doc/v2.23";
837    if ($web_manual) {
838        $search_site = "lilypond.org";
839    }
840    $search_string =~ s/\{\{site\}\}/site:$search_site/g;
841    print $fh $search_string;
842    close FILE;
843
844    print $fh "</div>\n\n";
845}
846
847sub lilypond_print_page_head($)
848{
849    my $fh = shift;
850    &$default_print_page_head($fh);
851    print $fh "<div id=\"main\">\n";
852}
853
854# Print out the TOC in a <div> at the end of th page, which will be formatted as a
855# sidebar mimicking a TOC frame
856sub lilypond_print_page_foot($)
857{
858  my $fh = shift;
859  my $program_string = &$program_string();
860#   print $fh "<p><font size='-1'>$program_string</font><br>$PRE_BODY_CLOSE</p>\n";
861  print $fh "<!-- FOOTER -->\n\n";
862  print $fh "<!-- end div#main here -->\n</div>\n\n";
863  if ($web_manual) {
864    # FIXME: This div and p#languages need to be in div#footer.
865    #        Should we move this div to postprocess_html.py ?
866    print $fh "<div id=\"verifier_texinfo\">\n";
867    print $fh "<h3>Validation</h3>\n";
868    # FIXME: inlined text substitution, move to ly_get_string as soon as another case is needed
869    # this does the variable substitution ("quoting" in Perlish) after the localization
870    $hosting_thanks =~ s/(\$\{\w+\})/$1/eeg;
871    print $fh "<a href=\"https://validator.w3.org/check?uri=referer\">\n";
872    print $fh "<img src=\"https://www.w3.org/Icons/valid-html401\"\n";
873    print $fh "     alt=\"Valid HTML 4.01 Transitional\"\n";
874    print $fh "     height=\"31\" width=\"88\"></a></p>\n";
875    print $fh "</div>";
876  }
877
878  # Print the TOC frame after the contents (positioned correctly using CSS),
879  # so that browsers with CSS turned off still show the contents first.
880  lilypond_print_toc_div ($fh);
881
882  # Close the page:
883  print $fh "</body>\n</html>\n";
884}
885
886
887
888
889
890#############################################################################
891###  NICER / MORE FLEXIBLE NAVIGATION PANELS
892#############################################################################
893
894sub get_navigation_button
895{
896    my $button = shift;
897
898    my $text = $NAVIGATION_TEXT{$button};
899    if (($button eq 'Back') or ($button eq 'FastBack')) {
900        $text = $text . $Texi2HTML::NODE{$button} . ' ';
901    } elsif (($button eq 'Forward') or ($button eq 'FastForward')) {
902        $text = ' ' . $Texi2HTML::NODE{$button} . $text;
903    } elsif ($button eq 'Up') {
904        $text = $text . ': ' . $Texi2HTML::NODE{$button} . ' ';
905    }
906
907    my $attributes = '';
908    if ($BUTTONS_GOTO{$button}) {
909        $attributes .= 'title="' . $BUTTONS_GOTO{$button} . '"';
910    }
911    if (defined ($BUTTONS_ACCESSKEY{$button}) and
912        ($BUTTONS_ACCESSKEY{$button} ne '')) {
913        $attributes .= ' accesskey="' . $BUTTONS_ACCESSKEY{$button} . '"';
914    }
915    if (defined ($BUTTONS_REL{$button}) and ($BUTTONS_REL{$button} ne '')) {
916        $attributes .= ' rel="' . $BUTTONS_REL{$button} . '"';
917    }
918
919    return '[' . &$anchor ('', $Texi2HTML::HREF{$button}, $text, $attributes) . ']';
920}
921
922sub lilypond_print_navigation
923{
924    # No navigation buttons for the web pages.
925    if ($web_manual) {
926        return "\n";
927    }
928
929    my $buttons = shift;
930    # Compare the button reference with one of the expected values.
931    my $chapter_buttons = $buttons == \@CHAPTER_BUTTONS;
932    my $section_buttons = (
933        ($buttons == \@SECTION_BUTTONS) or
934        ($buttons == \@SECTION_FOOTER_BUTTONS) or
935        ($buttons == \@NODE_FOOTER_BUTTONS));
936    my $result = "<table class=\"nav_table\">\n";
937
938    # First row:
939    $result .= '<tr>';
940    if ($chapter_buttons or $section_buttons) {
941        $result .= '<td align="left">';
942        $result .= get_navigation_button ('FastBack');
943        $result .= '</td>';
944    }
945    $result .= '<td align="center">';
946    $result .= get_navigation_button ('Top');
947    $result .= get_navigation_button ('Contents');
948    # Not all manuals have an index.
949    if ($Texi2HTML::HREF{'Index'}) {
950        $result .= get_navigation_button ('Index');
951    }
952    $result .= '</td>';
953    if ($chapter_buttons or $section_buttons) {
954        $result .= '<td align="right">';
955        $result .= get_navigation_button ('FastForward');
956        $result .= '</td>';
957    }
958    $result .= "</tr>\n";
959
960    if ($chapter_buttons or $section_buttons) {
961        # (Optional) second row:
962        $result .= '<tr>';
963        $result .= '<td align="left">';
964        $result .= get_navigation_button ('Back');
965        $result .= '</td>';
966        $result .= '<td align="center">';
967        $result .= get_navigation_button ('Up');
968        $result .= '</td>';
969        $result .= '<td align="right">';
970        $result .= get_navigation_button ('Forward');
971        $result .= '</td>';
972        $result .= "</tr>\n";
973    }
974    $result .= "</table>\n";
975
976    return $result;
977}
978
979
980
981#############################################################################
982###  FOOTNOTE FORMATTING
983#############################################################################
984
985# Format footnotes in a nicer way: Instead of printing the number in a separate
986# (nr) heading line, use the standard way of prepending <sup>nr</sup> immediately
987# before the fn text.
988
989
990# The following code is copied from texi2html's examples/makeinfo.init and
991# should be updated when texi2html makes some changes there!
992
993my $makekinfo_like_footnote_absolute_number = 0;
994
995sub makeinfo_like_foot_line_and_ref($$$$$$$$)
996{
997    my $foot_num = shift;
998    my $relative_num = shift;
999    my $footid = shift;
1000    my $docid = shift;
1001    my $from_file = shift;
1002    my $footnote_file = shift;
1003    my $lines = shift;
1004    my $state = shift;
1005
1006    $makekinfo_like_footnote_absolute_number++;
1007
1008    # this is a bit obscure, this allows to add an anchor only if formatted
1009    # as part of the document.
1010    $docid = '' if ($state->{'outside_document'} or $state->{'multiple_pass'});
1011
1012    if ($from_file eq $footnote_file)
1013    {
1014        $from_file = $footnote_file = '';
1015    }
1016
1017    my $foot_anchor = "<sup>" .
1018	&$anchor($docid, "$footnote_file#$footid", $relative_num) . "</sup>";
1019    $foot_anchor = &$anchor($docid,
1020			    "$footnote_file#$footid",
1021			    "($relative_num)") if ($state->{'preformatted'});
1022
1023#    unshift @$lines, "<li>";
1024#    push @$lines, "</li>\n";
1025    return ($lines, $foot_anchor);
1026}
1027
1028sub makeinfo_like_foot_lines($)
1029{
1030    my $lines = shift;
1031    unshift @$lines, "<hr>\n<h4>$Texi2HTML::I18n::WORDS->{'Footnotes_Title'}</h4>\n";
1032#<ol type=\"1\">\n";
1033#    push @$lines, "</ol>";
1034    return $lines;
1035}
1036
1037my %makekinfo_like_paragraph_in_footnote_nr;
1038
1039sub makeinfo_like_paragraph ($$$$$$$$$$$$$)
1040{
1041    my $text = shift;
1042    my $align = shift;
1043    my $indent = shift;
1044    my $paragraph_command = shift;
1045    my $paragraph_command_formatted = shift;
1046    my $paragraph_number = shift;
1047    my $format = shift;
1048    my $item_nr = shift;
1049    my $enumerate_style = shift;
1050    my $number = shift;
1051    my $command_stack_at_end = shift;
1052    my $command_stack_at_begin = shift;
1053    my $state = shift;
1054#print STDERR "format: $format\n" if (defined($format));
1055#print STDERR "paragraph @$command_stack_at_end; @$command_stack_at_begin\n";
1056    $paragraph_command_formatted = '' if (!defined($paragraph_command_formatted) or
1057          exists($special_list_commands{$format}->{$paragraph_command}));
1058    return '' if ($text =~ /^\s*$/);
1059    foreach my $style(t2h_collect_styles($command_stack_at_begin))
1060    {
1061       $text = t2h_begin_style($style, $text);
1062    }
1063    foreach my $style(t2h_collect_styles($command_stack_at_end))
1064    {
1065       $text = t2h_end_style($style, $text);
1066    }
1067    if (defined($paragraph_number) and defined($$paragraph_number))
1068    {
1069         $$paragraph_number++;
1070         return $text  if (($format eq 'itemize' or $format eq 'enumerate') and
1071            ($$paragraph_number == 1));
1072    }
1073    # The cells of a table should not be wrapped in a <p> tag, so just return the text
1074    if (defined($command_stack_at_begin->[0]) and $command_stack_at_begin->[0] eq 'multitable')
1075    {
1076        return $text;
1077    }
1078
1079    # Adjust all footnotes so that they look like good old makeinfo
1080    my $open = '<p';
1081    if ($align)
1082    {
1083        $open .= " align=\"$paragraph_style{$align}\"";
1084    }
1085    my $footnote_text = '';
1086    if (defined($command_stack_at_begin->[0]) and $command_stack_at_begin->[0] eq 'footnote')
1087    {
1088        my $state = $Texi2HTML::THISDOC{'state'};
1089        $makekinfo_like_paragraph_in_footnote_nr{$makekinfo_like_footnote_absolute_number}++;
1090        if ($makekinfo_like_paragraph_in_footnote_nr{$makekinfo_like_footnote_absolute_number} <= 1)
1091        {
1092           $open.=' class="footnote"';
1093           my $document_file = $state->{'footnote_document_file'};
1094           if ($document_file eq $state->{'footnote_footnote_file'})
1095           {
1096               $document_file = '';
1097           }
1098           my $docid = $state->{'footnote_place_id'};
1099           my $doc_state = $state->{'footnote_document_state'};
1100           $docid = '' if ($doc_state->{'outside_document'} or $doc_state->{'multiple_pass'});
1101           my $foot_label = &$anchor($state->{'footnote_footnote_id'},
1102                 $document_file . "#$state->{'footnote_place_id'}",
1103                 "$state->{'footnote_number_in_page'}");
1104           $footnote_text = "<small>[${foot_label}]</small> ";
1105        }
1106    }
1107    return $open.'>'.$footnote_text.$text.'</p>';
1108}
1109
1110
1111#############################################################################
1112###  OTHER SETTINGS
1113#############################################################################
1114
1115# For split pages, use index.html as start page!
1116if ($Texi2HTML::Config::SPLIT eq 'section' or
1117    $Texi2HTML::Config::SPLIT eq 'node') {
1118  $Texi2HTML::Config::TOP_FILE = 'index.html';
1119}
1120
1121
1122return 1;
1123