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\"><< " . 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