############################################################################### # # Package: NaturalDocs::Builder::HTMLBase # ############################################################################### # # A base package for all the shared functionality in and # . # ############################################################################### # This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure # Natural Docs is licensed under the GPL use Tie::RefHash; use strict; use integer; package NaturalDocs::Builder::HTMLBase; use base 'NaturalDocs::Builder::Base'; use NaturalDocs::DefineMembers 'MADE_EMPTY_SEARCH_RESULTS_PAGE', 'MadeEmptySearchResultsPage()', 'SetMadeEmptySearchResultsPage()'; ############################################################################### # Group: Object Variables # # Constants: Members # # The object is implemented as a blessed arrayref, with the follow constants as indexes. # # MADE_EMPTY_SEARCH_RESULTS_PAGE - Whether the search results page for searches with no results was generated. # # # Constants: NDMarkupToHTML Styles # # These are the styles used with . # # NDMARKUPTOHTML_GENERAL - General style. # NDMARKUPTOHTML_SUMMARY - For summaries. # NDMARKUPTOHTML_TOOLTIP - For tooltips. # use constant NDMARKUPTOHTML_GENERAL => undef; use constant NDMARKUPTOHTML_SUMMARY => 1; use constant NDMARKUPTOHTML_TOOLTIP => 2; ############################################################################### # Group: Package Variables # These variables are shared by all instances of the package so don't change them. # # handle: FH_CSS_FILE # # The file handle to use when updating CSS files. # # # Hash: abbreviations # # An existence hash of acceptable abbreviations. These are words that won't put a second space # after when followed by period-whitespace-capital letter. Yes, this is seriously over-engineered. # my %abbreviations = ( mr => 1, mrs => 1, ms => 1, dr => 1, rev => 1, fr => 1, 'i.e' => 1, maj => 1, gen => 1, pres => 1, sen => 1, rep => 1, n => 1, s => 1, e => 1, w => 1, ne => 1, se => 1, nw => 1, sw => 1 ); # # array: indexHeadings # # An array of the headings of all the index sections. First is for symbols, second for numbers, and the rest for each letter. # my @indexHeadings = ( '$#!', '0-9', 'A' .. 'Z' ); # # array: indexAnchors # # An array of the HTML anchors of all the index sections. First is for symbols, second for numbers, and the rest for each letter. # my @indexAnchors = ( 'Symbols', 'Numbers', 'A' .. 'Z' ); # # array: searchExtensions # # An array of the search file name extensions for all the index sections. First is for symbols, second for numbers, and the rest # for each letter. # my @searchExtensions = ( 'Symbols', 'Numbers', 'A' .. 'Z' ); # # bool: saidUpdatingCSSFile # # Whether the status message "Updating CSS file..." has been displayed. We only want to print it once, no matter how many # HTML-based targets we are building. # my $saidUpdatingCSSFile; # # constant: ADD_HIDDEN_BREAKS # # Just a synonym for "1" so that setting the flag on is clearer in the calling code. # use constant ADD_HIDDEN_BREAKS => 1; ############################################################################### # Group: ToolTip Package Variables # # These variables are for the tooltip generation functions only. Since they're reset on every call to and # , and are only used by them and their support functions, they can be shared by all instances of the # package. # # int: tooltipLinkNumber # # A number used as part of the ID for each link that has a tooltip. Should be incremented whenever one is made. # my $tooltipLinkNumber; # # int: tooltipNumber # # A number used as part of the ID for each tooltip. Should be incremented whenever one is made. # my $tooltipNumber; # # hash: tooltipSymbolsToNumbers # # A hash that maps the tooltip symbols to their assigned numbers. # my %tooltipSymbolsToNumbers; # # string: tooltipHTML # # The generated tooltip HTML. # my $tooltipHTML; ############################################################################### # Group: Menu Package Variables # # These variables are for the menu generation functions only. Since they're reset on every call to and are # only used by it and its support functions, they can be shared by all instances of the package. # # # hash: prebuiltMenus # # A hash that maps output directonies to menu HTML already built for it. There will be no selection or JavaScript in the menus. # my %prebuiltMenus; # # bool: menuNumbersAndLengthsDone # # Set when the variables that only need to be calculated for the menu once are done. This includes , # , , and , and . # my $menuNumbersAndLengthsDone; # # int: menuGroupNumber # # The current menu group number. Each time a group is created, this is incremented so that each one will be unique. # my $menuGroupNumber; # # int: menuLength # # The length of the entire menu, fully expanded. The value is computed from the . # my $menuLength; # # hash: menuGroupLengths # # A hash of the length of each group, *not* including any subgroup contents. The keys are references to each groups' # object, and the values are their lengths computed from the . # my %menuGroupLengths; tie %menuGroupLengths, 'Tie::RefHash'; # # hash: menuGroupNumbers # # A hash of the number of each group, as managed by . The keys are references to each groups' # object, and the values are the number. # my %menuGroupNumbers; tie %menuGroupNumbers, 'Tie::RefHash'; # # int: menuRootLength # # The length of the top-level menu entries without expansion. The value is computed from the . # my $menuRootLength; # # constants: Menu Length Constants # # Constants used to approximate the lengths of the menu or its groups. # # MENU_TITLE_LENGTH - The length of the title. # MENU_SUBTITLE_LENGTH - The length of the subtitle. # MENU_FILE_LENGTH - The length of one file entry. # MENU_GROUP_LENGTH - The length of one group entry. # MENU_TEXT_LENGTH - The length of one text entry. # MENU_LINK_LENGTH - The length of one link entry. # # MENU_LENGTH_LIMIT - The limit of the menu's length. If the total length surpasses this limit, groups that aren't required # to be open to show the selection will default to closed on browsers that support it. # use constant MENU_TITLE_LENGTH => 3; use constant MENU_SUBTITLE_LENGTH => 1; use constant MENU_FILE_LENGTH => 1; use constant MENU_GROUP_LENGTH => 2; # because it's a line and a blank space use constant MENU_TEXT_LENGTH => 1; use constant MENU_LINK_LENGTH => 1; use constant MENU_INDEX_LENGTH => 1; use constant MENU_LENGTH_LIMIT => 35; ############################################################################### # Group: Image Package Variables # # These variables are for the image generation functions only. Since they're reset on every call to , # and are only used by it and its support functions, they can be shared by all instances of thepackage. # # var: imageAnchorNumber # Incremented for each image link in the file that requires an anchor. # my $imageAnchorNumber; # # var: imageContent # # The actual embedded image HTML for all image links. When generating an image link, the link HTML is returned and # the HTML for the target image is added here. Periodically, such as after the end of the paragraph, this content should # be added to the page and the variable set to undef. # my $imageContent; ############################################################################### # Group: Search Package Variables # # These variables are for the search generation functions only. Since they're reset on every call to and # are only used by them and their support functions, they can be shared by all instances of the package. # # hash: searchResultIDs # # A hash mapping lowercase-only search result IDs to the number of times they've been used. This is to work around an IE # bug where it won't correctly reference IDs if they differ only in case. # my %searchResultIDs; ############################################################################### # Group: Object Functions # # Function: New # Creates and returns a new object. # sub New { my $class = shift; my $object = $class->SUPER::New(); $object->SetMadeEmptySearchResultsPage(0); return $object; }; # Function: MadeEmptySearchResultsPage # Returns whether the empty search results page was created or not. # Function: SetMadeEmptySearchResultsPage # Sets whether the empty search results page was created or not. ############################################################################### # Group: Implemented Interface Functions # # The behavior of these functions is shared between HTML output formats. # # # Function: UpdateImage # # Define this function to add or update the passed image in the output. # # Parameters: # # file - The image # sub UpdateImage #(file) { my ($self, $file) = @_; my $outputFile = $self->OutputImageOf($file); my $outputDirectory = NaturalDocs::File->NoFileName($outputFile); if (!-d $outputDirectory) { NaturalDocs::File->CreatePath($outputDirectory); }; NaturalDocs::File->Copy($file, $outputFile); }; # # Function: PurgeFiles # # Deletes the output files associated with the purged source files. # sub PurgeFiles #(filesToPurge) { my ($self, $filesToPurge) = @_; # Combine directories into a hash to remove duplicate work. my %directoriesToPurge; foreach my $file (keys %$filesToPurge) { # It's possible that there may be files there that aren't in a valid input directory anymore. They won't generate an output # file name so we need to check for undef. my $outputFile = $self->OutputFileOf($file); if (defined $outputFile) { unlink($outputFile); $directoriesToPurge{ NaturalDocs::File->NoFileName($outputFile) } = 1; }; }; foreach my $directory (keys %directoriesToPurge) { NaturalDocs::File->RemoveEmptyTree($directory, NaturalDocs::Settings->OutputDirectoryOf($self)); }; }; # # Function: PurgeIndexes # # Deletes the output files associated with the purged source files. # # Parameters: # # indexes - An existence hashref of the index types to purge. The keys are the or * for the general index. # sub PurgeIndexes #(indexes) { my ($self, $indexes) = @_; foreach my $index (keys %$indexes) { $self->PurgeIndexFiles($index, undef, undef); }; }; # # Function: PurgeImages # # Define this function to make the package remove all output related to the passed image files. These files are no longer used # by the documentation. # # Parameters: # # files - An existence hashref of the image to purge. # sub PurgeImages #(files) { my ($self, $filesToPurge) = @_; # Combine directories into a hash to remove duplicate work. my %directoriesToPurge; foreach my $file (keys %$filesToPurge) { # It's possible that there may be files there that aren't in a valid input directory anymore. They won't generate an output # file name so we need to check for undef. my $outputFile = $self->OutputImageOf($file); if (defined $outputFile) { unlink($outputFile); $directoriesToPurge{ NaturalDocs::File->NoFileName($outputFile) } = 1; }; }; foreach my $directory (keys %directoriesToPurge) { NaturalDocs::File->RemoveEmptyTree($directory, NaturalDocs::Settings->OutputDirectoryOf($self)); }; }; # # Function: BeginBuild # # Creates the necessary subdirectories in the output directory. # sub BeginBuild #(hasChanged) { my ($self, $hasChanged) = @_; foreach my $directory ( $self->JavaScriptDirectory(), $self->CSSDirectory(), $self->IndexDirectory(), $self->SearchResultsDirectory() ) { if (!-d $directory) { NaturalDocs::File->CreatePath($directory); }; }; }; # # Function: EndBuild # # Synchronizes the projects CSS and JavaScript files. Also generates the search data JavaScript file. # sub EndBuild #(hasChanged) { my ($self, $hasChanged) = @_; # Update the style sheets. my $styles = NaturalDocs::Settings->Styles(); my $changed; my $cssDirectory = $self->CSSDirectory(); my $mainCSSFile = $self->MainCSSFile(); for (my $i = 0; $i < scalar @$styles; $i++) { my $outputCSSFile; if (scalar @$styles == 1) { $outputCSSFile = $mainCSSFile; } else { $outputCSSFile = NaturalDocs::File->JoinPaths($cssDirectory, ($i + 1) . '.css'); }; my $masterCSSFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->ProjectDirectory(), $styles->[$i] . '.css' ); if (! -e $masterCSSFile) { $masterCSSFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->StyleDirectory(), $styles->[$i] . '.css' ); }; # We check both the date and the size in case the user switches between two styles which just happen to have the same # date. Should rarely happen, but it might. if (! -e $outputCSSFile || (stat($masterCSSFile))[9] != (stat($outputCSSFile))[9] || -s $masterCSSFile != -s $outputCSSFile) { if (!NaturalDocs::Settings->IsQuiet() && !$saidUpdatingCSSFile) { print "Updating CSS file...\n"; $saidUpdatingCSSFile = 1; }; NaturalDocs::File->Copy($masterCSSFile, $outputCSSFile); $changed = 1; }; }; my $deleteFrom; if (scalar @$styles == 1) { $deleteFrom = 1; } else { $deleteFrom = scalar @$styles + 1; }; for (;;) { my $file = NaturalDocs::File->JoinPaths($cssDirectory, $deleteFrom . '.css'); if (! -e $file) { last; }; unlink ($file); $deleteFrom++; $changed = 1; }; if ($changed) { if (scalar @$styles > 1) { open(FH_CSS_FILE, '>' . $mainCSSFile); for (my $i = 0; $i < scalar @$styles; $i++) { print FH_CSS_FILE '@import URL("' . ($i + 1) . '.css");' . "\n"; }; close(FH_CSS_FILE); }; }; # Update the JavaScript files my $jsMaster = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->JavaScriptDirectory(), 'NaturalDocs.js' ); my $jsOutput = $self->MainJavaScriptFile(); # We check both the date and the size in case the user switches between two styles which just happen to have the same # date. Should rarely happen, but it might. if (! -e $jsOutput || (stat($jsMaster))[9] != (stat($jsOutput))[9] || -s $jsMaster != -s $jsOutput) { NaturalDocs::File->Copy($jsMaster, $jsOutput); }; my @indexes = keys %{NaturalDocs::Menu->Indexes()}; open(FH_INDEXINFOJS, '>' . NaturalDocs::File->JoinPaths( $self->JavaScriptDirectory(), 'searchdata.js')); print FH_INDEXINFOJS "var indexSectionsWithContent = {\n"; for (my $i = 0; $i < scalar @indexes; $i++) { if ($i != 0) { print FH_INDEXINFOJS ",\n"; }; print FH_INDEXINFOJS ' "' . NaturalDocs::Topics->NameOfType($indexes[$i], 1, 1) . "\": {\n"; my $content = NaturalDocs::SymbolTable->IndexSectionsWithContent($indexes[$i]); for (my $contentIndex = 0; $contentIndex < 28; $contentIndex++) { if ($contentIndex != 0) { print FH_INDEXINFOJS ",\n"; }; print FH_INDEXINFOJS ' "' . $searchExtensions[$contentIndex] . '": ' . ($content->[$contentIndex] ? 'true' : 'false'); }; print FH_INDEXINFOJS "\n }"; }; print FH_INDEXINFOJS "\n }"; close(FH_INDEXINFOJS); }; ############################################################################### # Group: Section Functions # # Function: BuildTitle # # Builds and returns the HTML page title of a file. # # Parameters: # # sourceFile - The source to build the title of. # # Returns: # # The source file's title in HTML. # sub BuildTitle #(sourceFile) { my ($self, $sourceFile) = @_; # If we have a menu title, the page title is [menu title] - [file title]. Otherwise it is just [file title]. my $title = NaturalDocs::Project->DefaultMenuTitleOf($sourceFile); my $menuTitle = NaturalDocs::Menu->Title(); if (defined $menuTitle && $menuTitle ne $title) { $title .= ' - ' . $menuTitle; }; $title = $self->StringToHTML($title); return $title; }; # # Function: BuildMenu # # Builds and returns the side menu of a file. # # Parameters: # # sourceFile - The source to use if you're looking for a source file. # indexType - The index to use if you're looking for an index. # # Both sourceFile and indexType may be undef. # # Returns: # # The side menu in HTML. # # Dependencies: # # - and require this section to be surrounded with the exact # strings "". # - This function depends on the way formats file and index entries. # sub BuildMenu #(FileName sourceFile, TopicType indexType) -> string htmlMenu { my ($self, $sourceFile, $indexType) = @_; if (!$menuNumbersAndLengthsDone) { $menuGroupNumber = 1; $menuLength = 0; %menuGroupLengths = ( ); %menuGroupNumbers = ( ); $menuRootLength = 0; }; my $outputDirectory; if ($sourceFile) { $outputDirectory = NaturalDocs::File->NoFileName( $self->OutputFileOf($sourceFile) ); } elsif ($indexType) { $outputDirectory = NaturalDocs::File->NoFileName( $self->IndexFileOf($indexType) ); } else { $outputDirectory = NaturalDocs::Settings->OutputDirectoryOf($self); }; # Comment needed for UpdateFile(). my $output = '