<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">###############################################################################
#
#   Package: NaturalDocs::Builder::HTMLBase
#
###############################################################################
#
#   A base package for all the shared functionality in &lt;NaturalDocs::Builder::HTML&gt; and
#   &lt;NaturalDocs::Builder::FramedHTML&gt;.
#
###############################################################################

# 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 &lt;NDMarkupToHTML()&gt;.
#
#   NDMARKUPTOHTML_GENERAL - General style.
#   NDMARKUPTOHTML_SUMMARY - For summaries.
#   NDMARKUPTOHTML_TOOLTIP - For tooltips.
#
use constant NDMARKUPTOHTML_GENERAL =&gt; undef;
use constant NDMARKUPTOHTML_SUMMARY =&gt; 1;
use constant NDMARKUPTOHTML_TOOLTIP =&gt; 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 &lt;AddDoubleSpaces()&gt; won't put a second space
#   after when followed by period-whitespace-capital letter.  Yes, this is seriously over-engineered.
#
my %abbreviations = ( mr =&gt; 1, mrs =&gt; 1, ms =&gt; 1, dr =&gt; 1,
                                  rev =&gt; 1, fr =&gt; 1, 'i.e' =&gt; 1,
                                  maj =&gt; 1, gen =&gt; 1, pres =&gt; 1, sen =&gt; 1, rep =&gt; 1,
                                  n =&gt; 1, s =&gt; 1, e =&gt; 1, w =&gt; 1, ne =&gt; 1, se =&gt; 1, nw =&gt; 1, sw =&gt; 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 &lt;StringToHTML()&gt; is clearer in the calling code.
#
use constant ADD_HIDDEN_BREAKS =&gt; 1;


###############################################################################
# Group: ToolTip Package Variables
#
#   These variables are for the tooltip generation functions only.  Since they're reset on every call to &lt;BuildContent()&gt; and
#   &lt;BuildIndexSections()&gt;, 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 &lt;BuildMenu()&gt; 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 &lt;menuGroupNumber&gt;,
#   &lt;menuLength&gt;, &lt;menuGroupLengths&gt;, and &lt;menuGroupNumbers&gt;, and &lt;menuRootLength&gt;.
#
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 &lt;Menu Length Constants&gt;.
#
my $menuLength;


#
#   hash: menuGroupLengths
#
#   A hash of the length of each group, *not* including any subgroup contents.  The keys are references to each groups'
#   &lt;NaturalDocs::Menu::Entry&gt; object, and the values are their lengths computed from the &lt;Menu Length Constants&gt;.
#
my %menuGroupLengths;
tie %menuGroupLengths, 'Tie::RefHash';


#
#   hash: menuGroupNumbers
#
#   A hash of the number of each group, as managed by &lt;menuGroupNumber&gt;.  The keys are references to each groups'
#   &lt;NaturalDocs::Menu::Entry&gt; 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 &lt;Menu Length Constants&gt;.
#
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 =&gt; 3;
use constant MENU_SUBTITLE_LENGTH =&gt; 1;
use constant MENU_FILE_LENGTH =&gt; 1;
use constant MENU_GROUP_LENGTH =&gt; 2; # because it's a line and a blank space
use constant MENU_TEXT_LENGTH =&gt; 1;
use constant MENU_LINK_LENGTH =&gt; 1;
use constant MENU_INDEX_LENGTH =&gt; 1;

use constant MENU_LENGTH_LIMIT =&gt; 35;


###############################################################################
# Group: Image Package Variables
#
#   These variables are for the image generation functions only.  Since they're reset on every call to &lt;BuildContent()&gt;,
#   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 &lt;BuildIndexSections()&gt; 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-&gt;SUPER::New();
    $object-&gt;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 &lt;FileName&gt;
#
sub UpdateImage #(file)
    {
    my ($self, $file) = @_;

    my $outputFile = $self-&gt;OutputImageOf($file);
    my $outputDirectory = NaturalDocs::File-&gt;NoFileName($outputFile);

    if (!-d $outputDirectory)
        {  NaturalDocs::File-&gt;CreatePath($outputDirectory);  };

    NaturalDocs::File-&gt;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-&gt;OutputFileOf($file);
        if (defined $outputFile)
            {
            unlink($outputFile);
            $directoriesToPurge{ NaturalDocs::File-&gt;NoFileName($outputFile) } = 1;
            };
        };

    foreach my $directory (keys %directoriesToPurge)
        {
        NaturalDocs::File-&gt;RemoveEmptyTree($directory, NaturalDocs::Settings-&gt;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 &lt;TopicTypes&gt; or * for the general index.
#
sub PurgeIndexes #(indexes)
    {
    my ($self, $indexes) = @_;

    foreach my $index (keys %$indexes)
        {
        $self-&gt;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 &lt;FileNames&gt; 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-&gt;OutputImageOf($file);
        if (defined $outputFile)
            {
            unlink($outputFile);
            $directoriesToPurge{ NaturalDocs::File-&gt;NoFileName($outputFile) } = 1;
            };
        };

    foreach my $directory (keys %directoriesToPurge)
        {
        NaturalDocs::File-&gt;RemoveEmptyTree($directory, NaturalDocs::Settings-&gt;OutputDirectoryOf($self));
        };
    };


#
#   Function: BeginBuild
#
#   Creates the necessary subdirectories in the output directory.
#
sub BeginBuild #(hasChanged)
    {
    my ($self, $hasChanged) = @_;

    foreach my $directory ( $self-&gt;JavaScriptDirectory(), $self-&gt;CSSDirectory(), $self-&gt;IndexDirectory(),
                                       $self-&gt;SearchResultsDirectory() )
        {
        if (!-d $directory)
            {  NaturalDocs::File-&gt;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-&gt;Styles();
    my $changed;

    my $cssDirectory = $self-&gt;CSSDirectory();
    my $mainCSSFile = $self-&gt;MainCSSFile();

    for (my $i = 0; $i &lt; scalar @$styles; $i++)
        {
        my $outputCSSFile;

        if (scalar @$styles == 1)
            {  $outputCSSFile = $mainCSSFile;  }
        else
            {  $outputCSSFile = NaturalDocs::File-&gt;JoinPaths($cssDirectory, ($i + 1) . '.css');  };


        my $masterCSSFile = NaturalDocs::File-&gt;JoinPaths( NaturalDocs::Settings-&gt;ProjectDirectory(), $styles-&gt;[$i] . '.css' );

        if (! -e $masterCSSFile)
            {  $masterCSSFile = NaturalDocs::File-&gt;JoinPaths( NaturalDocs::Settings-&gt;StyleDirectory(), $styles-&gt;[$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-&gt;IsQuiet() &amp;&amp; !$saidUpdatingCSSFile)
                {
                print "Updating CSS file...\n";
                $saidUpdatingCSSFile = 1;
                };

            NaturalDocs::File-&gt;Copy($masterCSSFile, $outputCSSFile);

            $changed = 1;
            };
        };


    my $deleteFrom;

    if (scalar @$styles == 1)
        {  $deleteFrom = 1;  }
    else
        {  $deleteFrom = scalar @$styles + 1;  };

    for (;;)
        {
        my $file = NaturalDocs::File-&gt;JoinPaths($cssDirectory, $deleteFrom . '.css');

        if (! -e $file)
            {  last;  };

        unlink ($file);
        $deleteFrom++;

        $changed = 1;
        };


    if ($changed)
        {
        if (scalar @$styles &gt; 1)
            {
            open(FH_CSS_FILE, '&gt;' . $mainCSSFile);

            for (my $i = 0; $i &lt; 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-&gt;JoinPaths( NaturalDocs::Settings-&gt;JavaScriptDirectory(), 'NaturalDocs.js' );
    my $jsOutput = $self-&gt;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-&gt;Copy($jsMaster, $jsOutput);
        };


    my @indexes = keys %{NaturalDocs::Menu-&gt;Indexes()};

    open(FH_INDEXINFOJS, '&gt;' . NaturalDocs::File-&gt;JoinPaths( $self-&gt;JavaScriptDirectory(), 'searchdata.js'));

    print FH_INDEXINFOJS
    "var indexSectionsWithContent = {\n";

    for (my $i = 0; $i &lt; scalar @indexes; $i++)
        {
        if ($i != 0)
            {  print FH_INDEXINFOJS ",\n";  };

        print FH_INDEXINFOJS '   "' . NaturalDocs::Topics-&gt;NameOfType($indexes[$i], 1, 1) . "\": {\n";

        my $content = NaturalDocs::SymbolTable-&gt;IndexSectionsWithContent($indexes[$i]);
        for (my $contentIndex = 0; $contentIndex &lt; 28; $contentIndex++)
            {
            if ($contentIndex != 0)
                {  print FH_INDEXINFOJS ",\n";  };

            print FH_INDEXINFOJS '      "' . $searchExtensions[$contentIndex] . '": ' . ($content-&gt;[$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 &lt;FileName&gt; 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-&gt;DefaultMenuTitleOf($sourceFile);

    my $menuTitle = NaturalDocs::Menu-&gt;Title();
    if (defined $menuTitle &amp;&amp; $menuTitle ne $title)
        {  $title .= ' - ' . $menuTitle;  };

    $title = $self-&gt;StringToHTML($title);

    return $title;
    };

#
#   Function: BuildMenu
#
#   Builds and returns the side menu of a file.
#
#   Parameters:
#
#       sourceFile - The source &lt;FileName&gt; to use if you're looking for a source file.
#       indexType - The index &lt;TopicType&gt; to use if you're looking for an index.
#
#       Both sourceFile and indexType may be undef.
#
#   Returns:
#
#       The side menu in HTML.
#
#   Dependencies:
#
#       - &lt;Builder::HTML::UpdateFile()&gt; and &lt;Builder::HTML::UpdateIndex()&gt; require this section to be surrounded with the exact
#         strings "&lt;div id=Menu&gt;" and "&lt;/div&gt;&lt;!--Menu--&gt;".
#       - This function depends on the way &lt;BuildMenuSegment()&gt; formats file and index entries.
#
sub BuildMenu #(FileName sourceFile, TopicType indexType) -&gt; string htmlMenu
    {
    my ($self, $sourceFile, $indexType) = @_;

    if (!$menuNumbersAndLengthsDone)
        {
        $menuGroupNumber = 1;
        $menuLength = 0;
        %menuGroupLengths = ( );
        %menuGroupNumbers = ( );
        $menuRootLength = 0;
        };

    my $outputDirectory;

    if ($sourceFile)
        {  $outputDirectory = NaturalDocs::File-&gt;NoFileName( $self-&gt;OutputFileOf($sourceFile) );  }
    elsif ($indexType)
        {  $outputDirectory = NaturalDocs::File-&gt;NoFileName( $self-&gt;IndexFileOf($indexType) );  }
    else
        {  $outputDirectory = NaturalDocs::Settings-&gt;OutputDirectoryOf($self);  };


    # Comment needed for UpdateFile().
    my $output = '&lt;div id=Menu&gt;';


    if (!exists $prebuiltMenus{$outputDirectory})
        {
        my $segmentOutput;

        ($segmentOutput, $menuRootLength) =
            $self-&gt;BuildMenuSegment($outputDirectory, NaturalDocs::Menu-&gt;Content(), 1);

        my $titleOutput;

        my $menuTitle = NaturalDocs::Menu-&gt;Title();
        if (defined $menuTitle)
            {
            if (!$menuNumbersAndLengthsDone)
                {  $menuLength += MENU_TITLE_LENGTH;  };

            $menuRootLength += MENU_TITLE_LENGTH;

            $titleOutput .=
            '&lt;div class=MTitle&gt;'
                . $self-&gt;StringToHTML($menuTitle);

            my $menuSubTitle = NaturalDocs::Menu-&gt;SubTitle();
            if (defined $menuSubTitle)
                {
                if (!$menuNumbersAndLengthsDone)
                    {  $menuLength += MENU_SUBTITLE_LENGTH;  };

                $menuRootLength += MENU_SUBTITLE_LENGTH;

                $titleOutput .=
                '&lt;div class=MSubTitle&gt;'
                    . $self-&gt;StringToHTML($menuSubTitle)
                . '&lt;/div&gt;';
                };

            $titleOutput .=
            '&lt;/div&gt;';
            };

        my $searchOutput;

        if (scalar keys %{NaturalDocs::Menu-&gt;Indexes()})
            {
            $searchOutput =
            '&lt;script type="text/javascript"&gt;&lt;!--' . "\n"
                . 'var searchPanel = new SearchPanel("searchPanel", "' . $self-&gt;CommandLineOption() . '", '
                    . '"' . $self-&gt;MakeRelativeURL($outputDirectory, $self-&gt;SearchResultsDirectory()) . '");' . "\n"
            . '--&gt;&lt;/script&gt;'

            . '&lt;div id=MSearchPanel class=MSearchPanelInactive&gt;'
                . '&lt;input type=text id=MSearchField value=Search '
                    . 'onFocus="searchPanel.OnSearchFieldFocus(true)" onBlur="searchPanel.OnSearchFieldFocus(false)" '
                    . 'onKeyUp="searchPanel.OnSearchFieldChange()"&gt;'
                . '&lt;select id=MSearchType '
                    . 'onFocus="searchPanel.OnSearchTypeFocus(true)" onBlur="searchPanel.OnSearchTypeFocus(false)" '
                    . 'onChange="searchPanel.OnSearchTypeChange()"&gt;';

                my @indexes = keys %{NaturalDocs::Menu-&gt;Indexes()};
                @indexes = sort
                    {
                    if ($a eq ::TOPIC_GENERAL())  {  return -1;  }
                    elsif ($b eq ::TOPIC_GENERAL())  {  return 1;  }
                    else  {  return (NaturalDocs::Topics-&gt;NameOfType($a, 1) cmp NaturalDocs::Topics-&gt;NameOfType($b, 1))  };
                    }  @indexes;

                foreach my $index (@indexes)
                    {
                    my ($name, $extra);
                    if ($index eq ::TOPIC_GENERAL())
                        {
                        $name = 'Everything';
                        $extra = ' id=MSearchEverything selected ';
                        }
                    else
                        {  $name = $self-&gt;ConvertAmpChars(NaturalDocs::Topics-&gt;NameOfType($index, 1));  }

                    $searchOutput .=
                    '&lt;option ' . $extra . 'value="' . NaturalDocs::Topics-&gt;NameOfType($index, 1, 1) . '"&gt;'
                        . $name
                    . '&lt;/option&gt;';
                    };

                $searchOutput .=
                '&lt;/select&gt;'
            . '&lt;/div&gt;';
            };

        $prebuiltMenus{$outputDirectory} = $titleOutput . $segmentOutput . $searchOutput;
        $output .= $titleOutput . $segmentOutput . $searchOutput;
        }
    else
        {  $output .= $prebuiltMenus{$outputDirectory};  };


    # Highlight the menu selection.

    if ($sourceFile)
        {
        # Dependency: This depends on how BuildMenuSegment() formats file entries.
        my $outputFile = $self-&gt;OutputFileOf($sourceFile);
        my $tag = '&lt;div class=MFile&gt;&lt;a href="' . $self-&gt;MakeRelativeURL($outputDirectory, $outputFile) . '"&gt;';
        my $tagIndex = index($output, $tag);

        if ($tagIndex != -1)
            {
            my $endIndex = index($output, '&lt;/a&gt;', $tagIndex);

            substr($output, $endIndex, 4, '');
            substr($output, $tagIndex, length($tag), '&lt;div class=MFile id=MSelected&gt;');
            };
        }
    elsif ($indexType)
        {
        # Dependency: This depends on how BuildMenuSegment() formats index entries.
        my $outputFile = $self-&gt;IndexFileOf($indexType);
        my $tag = '&lt;div class=MIndex&gt;&lt;a href="' . $self-&gt;MakeRelativeURL($outputDirectory, $outputFile) . '"&gt;';
        my $tagIndex = index($output, $tag);

        if ($tagIndex != -1)
            {
            my $endIndex = index($output, '&lt;/a&gt;', $tagIndex);

            substr($output, $endIndex, 4, '');
            substr($output, $tagIndex, length($tag), '&lt;div class=MIndex id=MSelected&gt;');
            };
        };


    # If the completely expanded menu is too long, collapse all the groups that aren't in the selection hierarchy or near the
    # selection.  By doing this instead of having them default to closed via CSS, any browser that doesn't support changing this at
    # runtime will keep the menu entirely open so that its still usable.

    if ($menuLength &gt; MENU_LENGTH_LIMIT())
        {
        my $menuSelectionHierarchy = $self-&gt;GetMenuSelectionHierarchy($sourceFile, $indexType);

        my $toExpand = $self-&gt;ExpandMenu($sourceFile, $indexType, $menuSelectionHierarchy, $menuRootLength);

        $output .=

        '&lt;script language=JavaScript&gt;&lt;!--' . "\n"

        . 'HideAllBut([' . join(', ', @$toExpand) . '], ' . $menuGroupNumber . ');'

        . '// --&gt;&lt;/script&gt;';
        };

    $output .= '&lt;/div&gt;&lt;!--Menu--&gt;';

    $menuNumbersAndLengthsDone = 1;

    return $output;
    };


#
#   Function: BuildMenuSegment
#
#   A recursive function to build a segment of the menu.  *Remember to reset the &lt;Menu Package Variables&gt; before calling this
#   for the first time.*
#
#   Parameters:
#
#       outputDirectory - The output directory the menu is being built for.
#       menuSegment - An arrayref specifying the segment of the menu to build.  Either pass the menu itself or the contents
#                               of a group.
#       topLevel - Whether the passed segment is the top level segment or not.
#
#   Returns:
#
#       The array ( menuHTML, length ).
#
#       menuHTML - The menu segment in HTML.
#       groupLength - The length of the group, *not* including the contents of any subgroups, as computed from the
#                            &lt;Menu Length Constants&gt;.
#
#   Dependencies:
#
#       - &lt;BuildMenu()&gt; depends on the way this function formats file and index entries.
#
sub BuildMenuSegment #(outputDirectory, menuSegment, topLevel)
    {
    my ($self, $outputDirectory, $menuSegment, $topLevel) = @_;

    my $output;
    my $groupLength = 0;

    foreach my $entry (@$menuSegment)
        {
        if ($entry-&gt;Type() == ::MENU_GROUP())
            {
            my ($entryOutput, $entryLength) =
                $self-&gt;BuildMenuSegment($outputDirectory, $entry-&gt;GroupContent());

            my $entryNumber;

            if (!$menuNumbersAndLengthsDone)
                {
                $entryNumber = $menuGroupNumber;
                $menuGroupNumber++;

                $menuGroupLengths{$entry} = $entryLength;
                $menuGroupNumbers{$entry} = $entryNumber;
                }
            else
                {  $entryNumber = $menuGroupNumbers{$entry};  };

            $output .=
            '&lt;div class=MEntry&gt;'
                . '&lt;div class=MGroup&gt;'

                    . '&lt;a href="javascript:ToggleMenu(\'MGroupContent' . $entryNumber . '\')"'
                         . ($self-&gt;CommandLineOption() eq 'FramedHTML' ? ' target="_self"' : '') . '&gt;'
                        . $self-&gt;StringToHTML($entry-&gt;Title())
                    . '&lt;/a&gt;'

                    . '&lt;div class=MGroupContent id=MGroupContent' . $entryNumber . '&gt;'
                        . $entryOutput
                    . '&lt;/div&gt;'

                . '&lt;/div&gt;'
            . '&lt;/div&gt;';

            $groupLength += MENU_GROUP_LENGTH;
            }

        elsif ($entry-&gt;Type() == ::MENU_FILE())
            {
            my $targetOutputFile = $self-&gt;OutputFileOf($entry-&gt;Target());

        # Dependency: BuildMenu() depends on how this formats file entries.
            $output .=
            '&lt;div class=MEntry&gt;'
                . '&lt;div class=MFile&gt;'
                    . '&lt;a href="' . $self-&gt;MakeRelativeURL($outputDirectory, $targetOutputFile) . '"&gt;'
                        . $self-&gt;StringToHTML( $entry-&gt;Title(), ADD_HIDDEN_BREAKS)
                    . '&lt;/a&gt;'
                . '&lt;/div&gt;'
            . '&lt;/div&gt;';

            $groupLength += MENU_FILE_LENGTH;
            }

        elsif ($entry-&gt;Type() == ::MENU_TEXT())
            {
            $output .=
            '&lt;div class=MEntry&gt;'
                . '&lt;div class=MText&gt;'
                    . $self-&gt;StringToHTML( $entry-&gt;Title() )
                . '&lt;/div&gt;'
            . '&lt;/div&gt;';

            $groupLength += MENU_TEXT_LENGTH;
            }

        elsif ($entry-&gt;Type() == ::MENU_LINK())
            {
            $output .=
            '&lt;div class=MEntry&gt;'
                . '&lt;div class=MLink&gt;'
                    . '&lt;a href="' . $entry-&gt;Target() . '"' . ($self-&gt;CommandLineOption() eq 'FramedHTML' ? ' target="_top"' : '') . '&gt;'
                        . $self-&gt;StringToHTML( $entry-&gt;Title() )
                    . '&lt;/a&gt;'
                . '&lt;/div&gt;'
            . '&lt;/div&gt;';

            $groupLength += MENU_LINK_LENGTH;
            }

        elsif ($entry-&gt;Type() == ::MENU_INDEX())
            {
            my $indexFile = $self-&gt;IndexFileOf($entry-&gt;Target);

        # Dependency: BuildMenu() depends on how this formats index entries.
            $output .=
            '&lt;div class=MEntry&gt;'
                . '&lt;div class=MIndex&gt;'
                    . '&lt;a href="' . $self-&gt;MakeRelativeURL( $outputDirectory, $self-&gt;IndexFileOf($entry-&gt;Target()) ) . '"&gt;'
                        . $self-&gt;StringToHTML( $entry-&gt;Title() )
                    . '&lt;/a&gt;'
                . '&lt;/div&gt;'
            . '&lt;/div&gt;';

            $groupLength += MENU_INDEX_LENGTH;
            };
        };


    if (!$menuNumbersAndLengthsDone)
        {  $menuLength += $groupLength;  };

    return ($output, $groupLength);
    };


#
#   Function: BuildContent
#
#   Builds and returns the main page content.
#
#   Parameters:
#
#       sourceFile - The source &lt;FileName&gt;.
#       parsedFile - The parsed source file as an arrayref of &lt;NaturalDocs::Parser::ParsedTopic&gt; objects.
#
#   Returns:
#
#       The page content in HTML.
#
sub BuildContent #(sourceFile, parsedFile)
    {
    my ($self, $sourceFile, $parsedFile) = @_;

    $self-&gt;ResetToolTips();
    $imageAnchorNumber = 1;
    $imageContent = undef;

    my $output = '&lt;div id=Content&gt;';
    my $i = 0;

    while ($i &lt; scalar @$parsedFile)
        {
        my $anchor = $self-&gt;SymbolToHTMLSymbol($parsedFile-&gt;[$i]-&gt;Symbol());

        my $scope = NaturalDocs::Topics-&gt;TypeInfo($parsedFile-&gt;[$i]-&gt;Type())-&gt;Scope();


        # The anchors are closed, but not around the text, so the :hover CSS style won't accidentally kick in.

        my $headerType;

        if ($i == 0)
            {  $headerType = 'h1';  }
        elsif ($scope == ::SCOPE_START() || $scope == ::SCOPE_END())
            {  $headerType = 'h2';  }
        else
            {  $headerType = 'h3';  };

        $output .=

        '&lt;div class="C' . NaturalDocs::Topics-&gt;NameOfType($parsedFile-&gt;[$i]-&gt;Type(), 0, 1) . '"&gt;'
            . '&lt;div class=CTopic' . ($i == 0 ? ' id=MainTopic' : '') . '&gt;'

                . '&lt;' . $headerType . ' class=CTitle&gt;'
                    . '&lt;a name="' . $anchor . '"&gt;&lt;/a&gt;'
                    . $self-&gt;StringToHTML( $parsedFile-&gt;[$i]-&gt;Title(), ADD_HIDDEN_BREAKS)
                . '&lt;/' . $headerType . '&gt;';


        my $hierarchy;
        if (NaturalDocs::Topics-&gt;TypeInfo( $parsedFile-&gt;[$i]-&gt;Type() )-&gt;ClassHierarchy())
            {
            $hierarchy = $self-&gt;BuildClassHierarchy($sourceFile, $parsedFile-&gt;[$i]-&gt;Symbol());
            };

        my $summary;
        if ($i == 0 || $scope == ::SCOPE_START() || $scope == ::SCOPE_END())
            {
            $summary .= $self-&gt;BuildSummary($sourceFile, $parsedFile, $i);
            };

        my $hasBody;
        if (defined $hierarchy || defined $summary ||
            defined $parsedFile-&gt;[$i]-&gt;Prototype() || defined $parsedFile-&gt;[$i]-&gt;Body())
            {
            $output .= '&lt;div class=CBody&gt;';
            $hasBody = 1;
            };

        $output .= $hierarchy;

        if (defined $parsedFile-&gt;[$i]-&gt;Prototype())
            {
            $output .= $self-&gt;BuildPrototype($parsedFile-&gt;[$i]-&gt;Type(), $parsedFile-&gt;[$i]-&gt;Prototype(), $sourceFile);
            };

        if (defined $parsedFile-&gt;[$i]-&gt;Body())
            {
            $output .= $self-&gt;NDMarkupToHTML( $sourceFile, $parsedFile-&gt;[$i]-&gt;Body(), $parsedFile-&gt;[$i]-&gt;Symbol(),
                                                                  $parsedFile-&gt;[$i]-&gt;Package(), $parsedFile-&gt;[$i]-&gt;Type(),
                                                                  $parsedFile-&gt;[$i]-&gt;Using() );
            };

        $output .= $summary;


        if ($hasBody)
            {  $output .= '&lt;/div&gt;';  };

        $output .=
            '&lt;/div&gt;' # CTopic
        . '&lt;/div&gt;' # CType
        . "\n\n";

        $i++;
        };

    $output .= '&lt;/div&gt;&lt;!--Content--&gt;';

    return $output;
    };


#
#   Function: BuildSummary
#
#   Builds a summary, either for the entire file or the current class/section.
#
#   Parameters:
#
#       sourceFile - The source &lt;FileName&gt; the summary appears in.
#
#       parsedFile - A reference to the parsed source file.
#
#       index   - The index into the parsed file to start at.  If undef or zero, it builds a summary for the entire file.  If it's the
#                    index of a &lt;TopicType&gt; that starts or ends a scope, it builds a summary for that scope
#
#   Returns:
#
#       The summary in HTML.
#
sub BuildSummary #(sourceFile, parsedFile, index)
    {
    my ($self, $sourceFile, $parsedFile, $index) = @_;
    my $completeSummary;

    if (!defined $index || $index == 0)
        {
        $index = 0;
        $completeSummary = 1;
        }
    else
        {
        # Skip the scope entry.
        $index++;
        };

    if ($index + 1 &gt;= scalar @$parsedFile)
        {  return undef;  };


    my $scope = NaturalDocs::Topics-&gt;TypeInfo($parsedFile-&gt;[$index]-&gt;Type())-&gt;Scope();

    # Return nothing if there's only one entry.
    if (!$completeSummary &amp;&amp; ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) )
        {  return undef;  };


    my $indent = 0;
    my $inGroup;

    my $isMarked = 0;

    my $output =
    '&lt;!--START_ND_SUMMARY--&gt;'
    . '&lt;div class=Summary&gt;&lt;div class=STitle&gt;Summary&lt;/div&gt;'

        # Not all browsers get table padding right, so we need a div to apply the border.
        . '&lt;div class=SBorder&gt;'
        . '&lt;table border=0 cellspacing=0 cellpadding=0 class=STable&gt;';

        while ($index &lt; scalar @$parsedFile)
            {
            my $topic = $parsedFile-&gt;[$index];
            my $scope = NaturalDocs::Topics-&gt;TypeInfo($topic-&gt;Type())-&gt;Scope();

            if (!$completeSummary &amp;&amp; ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) )
                {  last;  };


            # Remove modifiers as appropriate for the current entry.

            if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END())
                {
                $indent = 0;
                $inGroup = 0;
                $isMarked = 0;
                }
            elsif ($topic-&gt;Type() eq ::TOPIC_GROUP())
                {
                if ($inGroup)
                    {  $indent--;  };

                $inGroup = 0;
                $isMarked = 0;
                };


            $output .=
             '&lt;tr class="S' . ($index == 0 ? 'Main' : NaturalDocs::Topics-&gt;NameOfType($topic-&gt;Type(), 0, 1))
                . ($indent ? ' SIndent' . $indent : '') . ($isMarked ? ' SMarked' : '') .'"&gt;'
                . '&lt;td class=SEntry&gt;';

           # Add the entry itself.

            my $toolTipProperties;

            # We only want a tooltip here if there's a protoype.  Otherwise it's redundant.

            if (defined $topic-&gt;Prototype())
                {
                my $tooltipID = $self-&gt;BuildToolTip($topic-&gt;Symbol(), $sourceFile, $topic-&gt;Type(),
                                                                     $topic-&gt;Prototype(), $topic-&gt;Summary());
                $toolTipProperties = $self-&gt;BuildToolTipLinkProperties($tooltipID);
                };

            $output .=
            '&lt;a href="#' . $self-&gt;SymbolToHTMLSymbol($parsedFile-&gt;[$index]-&gt;Symbol()) . '" ' . $toolTipProperties . '&gt;'
                . $self-&gt;StringToHTML( $parsedFile-&gt;[$index]-&gt;Title(), ADD_HIDDEN_BREAKS)
            . '&lt;/a&gt;';


            $output .=
            '&lt;/td&gt;&lt;td class=SDescription&gt;';

            if (defined $parsedFile-&gt;[$index]-&gt;Body())
                {
                $output .= $self-&gt;NDMarkupToHTML($sourceFile, $parsedFile-&gt;[$index]-&gt;Summary(),
                                                                     $parsedFile-&gt;[$index]-&gt;Symbol(), $parsedFile-&gt;[$index]-&gt;Package(),
                                                                     $parsedFile-&gt;[$index]-&gt;Type(), $parsedFile-&gt;[$index]-&gt;Using(),
                                                                     NDMARKUPTOHTML_SUMMARY);
                };


            $output .=
            '&lt;/td&gt;&lt;/tr&gt;';


            # Prepare the modifiers for the next entry.

            if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END())
                {
                $indent = 1;
                $inGroup = 0;
                }
            elsif ($topic-&gt;Type() eq ::TOPIC_GROUP())
                {
                if (!$inGroup)
                    {
                    $indent++;
                    $inGroup = 1;
                    };
                };

            $isMarked ^= 1;
            $index++;
            };

        $output .=
        '&lt;/table&gt;'
    . '&lt;/div&gt;' # Body
    . '&lt;/div&gt;' # Summary
    . "&lt;!--END_ND_SUMMARY--&gt;";

    return $output;
    };


#
#   Function: BuildPrototype
#
#   Builds and returns the prototype as HTML.
#
#   Parameters:
#
#       type - The &lt;TopicType&gt; the prototype is from.
#       prototype - The prototype to format.
#       file - The &lt;FileName&gt; the prototype was defined in.
#
#   Returns:
#
#       The prototype in HTML.
#
sub BuildPrototype #(type, prototype, file)
    {
    my ($self, $type, $prototype, $file) = @_;

    my $language = NaturalDocs::Languages-&gt;LanguageOf($file);
    my $prototypeObject = $language-&gt;ParsePrototype($type, $prototype);

    my $output;

    if ($prototypeObject-&gt;OnlyBeforeParameters())
        {
        $output =
        # A blockquote to scroll it if it's too long.
        '&lt;blockquote&gt;'
            # A surrounding table as a hack to make the div form-fit.
            . '&lt;table border=0 cellspacing=0 cellpadding=0 class=Prototype&gt;&lt;tr&gt;&lt;td&gt;'
                . $self-&gt;ConvertAmpChars($prototypeObject-&gt;BeforeParameters())
            . '&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;'
        . '&lt;/blockquote&gt;';
        }

    else
        {
        my $params = $prototypeObject-&gt;Parameters();
        my $beforeParams = $prototypeObject-&gt;BeforeParameters();
        my $afterParams = $prototypeObject-&gt;AfterParameters();


        # Determine what features the prototype has and its length.

        my ($hasType, $hasTypePrefix, $hasNamePrefix, $hasDefaultValue, $hasDefaultValuePrefix);
        my $maxParamLength = 0;

        foreach my $param (@$params)
            {
            my $paramLength = length($param-&gt;Name());

            if ($param-&gt;Type())
                {
                $hasType = 1;
                $paramLength += length($param-&gt;Type()) + 1;
                };
            if ($param-&gt;TypePrefix())
                {
                $hasTypePrefix = 1;
                $paramLength += length($param-&gt;TypePrefix()) + 1;
                };
            if ($param-&gt;NamePrefix())
                {
                $hasNamePrefix = 1;
                $paramLength += length($param-&gt;NamePrefix());
                };
            if ($param-&gt;DefaultValue())
                {
                $hasDefaultValue = 1;

                # The length of the default value part is either the longest word, or 1/3 the total, whichever is longer.  We do this
                # because we don't want parameter lines wrapping to more than three lines, and there's no guarantee that the line will
                # wrap at all.  There's a small possibility that it could still wrap to four lines with this code, but we don't need to go
                # crazy(er) here.

                my $thirdLength = length($param-&gt;DefaultValue()) / 3;

                my @words = split(/ +/, $param-&gt;DefaultValue());
                my $maxWordLength = 0;

                foreach my $word (@words)
                    {
                    if (length($word) &gt; $maxWordLength)
                        {  $maxWordLength = length($word);  };
                    };

                $paramLength += ($maxWordLength &gt; $thirdLength ? $maxWordLength : $thirdLength) + 1;
                };
            if ($param-&gt;DefaultValuePrefix())
                {
                $hasDefaultValuePrefix = 1;
                $paramLength += length($param-&gt;DefaultValuePrefix()) + 1;
                };

            if ($paramLength &gt; $maxParamLength)
                {  $maxParamLength = $paramLength;  };
            };

        my $useCondensed = (length($beforeParams) + $maxParamLength + length($afterParams) &gt; 80 ? 1 : 0);
        my $parameterColumns = 1 + $hasType + $hasTypePrefix + $hasNamePrefix +
                                               $hasDefaultValue + $hasDefaultValuePrefix + $useCondensed;

        $output =
        '&lt;blockquote&gt;&lt;table border=0 cellspacing=0 cellpadding=0 class=Prototype&gt;&lt;tr&gt;&lt;td&gt;'

            # Stupid hack to get it to work right in IE.
            . '&lt;table border=0 cellspacing=0 cellpadding=0&gt;&lt;tr&gt;'

            . '&lt;td class=PBeforeParameters ' . ($useCondensed ? 'colspan=' . $parameterColumns : 'nowrap') . '&gt;'
                . $self-&gt;ConvertAmpChars($beforeParams);

                if ($beforeParams &amp;&amp; $beforeParams !~ /[\(\[\{\&lt;]$/)
                    {  $output .= '&amp;nbsp;';  };

            $output .=
            '&lt;/td&gt;';

            for (my $i = 0; $i &lt; scalar @$params; $i++)
                {
                if ($useCondensed)
                    {
                    $output .= '&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/td&gt;';
                    }
                elsif ($i &gt; 0)
                    {
                    # Go to the next row and and skip the BeforeParameters cell.
                    $output .= '&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;';
                    };

                if ($language-&gt;TypeBeforeParameter())
                    {
                    if ($hasTypePrefix)
                        {
                        my $htmlTypePrefix = $self-&gt;ConvertAmpChars($params-&gt;[$i]-&gt;TypePrefix());
                        $htmlTypePrefix =~ s/ $/&amp;nbsp;/;

                        $output .=
                        '&lt;td class=PTypePrefix nowrap&gt;'
                            . $htmlTypePrefix
                        . '&lt;/td&gt;';
                        };

                    if ($hasType)
                        {
                        $output .=
                        '&lt;td class=PType nowrap&gt;'
                            . $self-&gt;ConvertAmpChars($params-&gt;[$i]-&gt;Type()) . '&amp;nbsp;'
                        . '&lt;/td&gt;';
                        };

                    if ($hasNamePrefix)
                        {
                        $output .=
                        '&lt;td class=PParameterPrefix nowrap&gt;'
                            . $self-&gt;ConvertAmpChars($params-&gt;[$i]-&gt;NamePrefix())
                        . '&lt;/td&gt;';
                        };

                    $output .=
                    '&lt;td class=PParameter nowrap' . ($useCondensed &amp;&amp; !$hasDefaultValue ? ' width=100%' : '') . '&gt;'
                        . $self-&gt;ConvertAmpChars($params-&gt;[$i]-&gt;Name())
                    . '&lt;/td&gt;';
                    }

                else # !$language-&gt;TypeBeforeParameter()
                    {
                    $output .=
                    '&lt;td class=PParameter nowrap&gt;'
                        . $self-&gt;ConvertAmpChars( $params-&gt;[$i]-&gt;NamePrefix() . $params-&gt;[$i]-&gt;Name() )
                    . '&lt;/td&gt;';

                    if ($hasType || $hasTypePrefix)
                        {
                        my $typePrefix = $params-&gt;[$i]-&gt;TypePrefix();
                        if ($typePrefix)
                            {  $typePrefix .= ' ';  };

                        $output .=
                        '&lt;td class=PType nowrap' . ($useCondensed &amp;&amp; !$hasDefaultValue ? ' width=100%' : '') . '&gt;'
                            . '&amp;nbsp;' . $self-&gt;ConvertAmpChars( $typePrefix . $params-&gt;[$i]-&gt;Type() )
                        . '&lt;/td&gt;';
                        };
                    };

                if ($hasDefaultValuePrefix)
                    {
                    $output .=
                    '&lt;td class=PDefaultValuePrefix&gt;'

                       . '&amp;nbsp;' . $self-&gt;ConvertAmpChars( $params-&gt;[$i]-&gt;DefaultValuePrefix() ) . '&amp;nbsp;'
                    . '&lt;/td&gt;';
                    };

                if ($hasDefaultValue)
                    {
                    $output .=
                    '&lt;td class=PDefaultValue width=100%&gt;'
                        . ($hasDefaultValuePrefix ? '' : '&amp;nbsp;') . $self-&gt;ConvertAmpChars( $params-&gt;[$i]-&gt;DefaultValue() )
                    . '&lt;/td&gt;';
                    };
                };

            if ($useCondensed)
                {  $output .= '&lt;/tr&gt;&lt;tr&gt;';  };

            $output .=
            '&lt;td class=PAfterParameters ' . ($useCondensed ? 'colspan=' . $parameterColumns : 'nowrap') . '&gt;'
                 . $self-&gt;ConvertAmpChars($afterParams);

                if ($afterParams &amp;&amp; $afterParams !~ /^[\)\]\}\&gt;]/)
                    {  $output .= '&amp;nbsp;';  };

            $output .=
            '&lt;/td&gt;'
        . '&lt;/tr&gt;&lt;/table&gt;'

        # Hack.
        . '&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;';
       };

    return $output;
    };


#
#   Function: BuildFooter
#
#   Builds and returns the HTML footer for the page.
#
#   Parameters:
#
#       multiline - Whether it should be formatted on multiple lines or not.
#
#   Dependencies:
#
#       &lt;Builder::HTML::UpdateFile()&gt; and &lt;Builder::HTML::UpdateIndex()&gt; require this section to be surrounded with the exact
#       strings "&lt;div id=Footer&gt;" and "&lt;/div&gt;&lt;!--Footer--&gt;".
#
sub BuildFooter #(bool multiline)
    {
    my ($self, $multiline) = @_;

    my $footer = NaturalDocs::Menu-&gt;Footer();
    my $timestamp = NaturalDocs::Menu-&gt;TimeStamp();
    my $divider;

    if ($multiline)
        {  $divider = '&lt;/p&gt;&lt;p&gt;';  }
    else
        {  $divider = '&amp;nbsp; &amp;middot;&amp;nbsp; ';  };


    my $output = '&lt;div id=Footer&gt;';
    if ($multiline)
        {  $output .= '&lt;p&gt;';  };

    if (defined $footer)
        {
        $footer =~ s/\(c\)/&amp;copy;/gi;
        $footer =~ s/\(tm\)/&amp;trade;/gi;
        $footer =~ s/\(r\)/&amp;reg;/gi;

        $output .= $footer . $divider;
        };

    if (defined $timestamp)
        {
        $output .= $timestamp . $divider;
        };

    $output .=
    '&lt;a href="' . NaturalDocs::Settings-&gt;AppURL() . '"&gt;'
        . 'Generated by Natural Docs'
    . '&lt;/a&gt;';

    if ($multiline)
        {  $output .= '&lt;/p&gt;';  };

    $output .=
    '&lt;/div&gt;&lt;!--Footer--&gt;';

    return $output;
    };


#
#   Function: BuildToolTip
#
#   Builds the HTML for a symbol's tooltip and stores it in &lt;tooltipHTML&gt;.
#
#   Parameters:
#
#       symbol - The target &lt;SymbolString&gt;.
#       file - The &lt;FileName&gt; the target's defined in.
#       type - The symbol &lt;TopicType&gt;.
#       prototype - The target prototype, or undef for none.
#       summary - The target summary, or undef for none.
#
#   Returns:
#
#       If a tooltip is necessary for the link, returns the tooltip ID.  If not, returns undef.
#
sub BuildToolTip #(symbol, file, type, prototype, summary)
    {
    my ($self, $symbol, $file, $type, $prototype, $summary) = @_;

    if (defined $prototype || defined $summary)
        {
        my $htmlSymbol = $self-&gt;SymbolToHTMLSymbol($symbol);
        my $number = $tooltipSymbolsToNumbers{$htmlSymbol};

        if (!defined $number)
            {
            $number = $tooltipNumber;
            $tooltipNumber++;

            $tooltipSymbolsToNumbers{$htmlSymbol} = $number;

            $tooltipHTML .=
            '&lt;div class=CToolTip id="tt' . $number . '"&gt;'
                . '&lt;div class=C' . NaturalDocs::Topics-&gt;NameOfType($type, 0, 1) . '&gt;';

            if (defined $prototype)
                {
                $tooltipHTML .= $self-&gt;BuildPrototype($type, $prototype, $file);
                };

            if (defined $summary)
                {
                # The fact that we don't have scope or using shouldn't matter because links shouldn't be included in the style anyway.
                $summary = $self-&gt;NDMarkupToHTML($file, $summary, undef, undef, $type, undef, NDMARKUPTOHTML_TOOLTIP);
                $tooltipHTML .= $summary;
                };

            $tooltipHTML .=
                '&lt;/div&gt;'
            . '&lt;/div&gt;';
            };

        return 'tt' . $number;
        }
    else
        {  return undef;  };
    };

#
#   Function: BuildToolTips
#
#   Builds and returns the tooltips for the page in HTML.
#
sub BuildToolTips
    {
    my $self = shift;
    return "\n&lt;!--START_ND_TOOLTIPS--&gt;\n" . $tooltipHTML . "&lt;!--END_ND_TOOLTIPS--&gt;\n\n";
    };

#
#   Function: BuildClassHierarchy
#
#   Builds and returns a class hierarchy diagram for the passed class, if applicable.
#
#   Parameters:
#
#       file - The source &lt;FileName&gt;.
#       class - The class &lt;SymbolString&gt; to build the hierarchy of.
#
sub BuildClassHierarchy #(file, symbol)
    {
    my ($self, $file, $symbol) = @_;

    my @parents = NaturalDocs::ClassHierarchy-&gt;ParentsOf($symbol);
    @parents = sort { ::StringCompare($a, $b) } @parents;

    my @children = NaturalDocs::ClassHierarchy-&gt;ChildrenOf($symbol);
    @children = sort { ::StringCompare($a, $b) } @children;

    if (!scalar @parents &amp;&amp; !scalar @children)
        {  return undef;  };

    my $output =
    '&lt;div class=ClassHierarchy&gt;';

    if (scalar @parents)
        {
        $output .='&lt;table border=0 cellspacing=0 cellpadding=0&gt;&lt;tr&gt;&lt;td&gt;';

        foreach my $parent (@parents)
            {
            $output .= $self-&gt;BuildClassHierarchyEntry($file, $parent, 'CHParent', 1);
            };

        $output .= '&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;div class=CHIndent&gt;';
        };

    $output .=
    '&lt;table border=0 cellspacing=0 cellpadding=0&gt;&lt;tr&gt;&lt;td&gt;'
        . $self-&gt;BuildClassHierarchyEntry($file, $symbol, 'CHCurrent', undef)
    . '&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;';

    if (scalar @children)
        {
        $output .=
        '&lt;div class=CHIndent&gt;'
            . '&lt;table border=0 cellspacing=0 cellpadding=0&gt;&lt;tr&gt;&lt;td&gt;';

        if (scalar @children &lt;= 5)
            {
            for (my $i = 0; $i &lt; scalar @children; $i++)
                {  $output .= $self-&gt;BuildClassHierarchyEntry($file, $children[$i], 'CHChild', 1);  };
            }
        else
            {
            for (my $i = 0; $i &lt; 4; $i++)
                {  $output .= $self-&gt;BuildClassHierarchyEntry($file, $children[$i], 'CHChild', 1);  };

           $output .= '&lt;div class=CHChildNote&gt;&lt;div class=CHEntry&gt;' . (scalar @children - 4) . ' other children&lt;/div&gt;&lt;/div&gt;';
            };

        $output .=
        '&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;'
        . '&lt;/div&gt;';
        };

    if (scalar @parents)
        {  $output .= '&lt;/div&gt;';  };

    $output .=
    '&lt;/div&gt;';

    return $output;
    };


#
#   Function: BuildClassHierarchyEntry
#
#   Builds and returns a single class hierarchy entry.
#
#   Parameters:
#
#       file - The source &lt;FileName&gt;.
#       symbol - The class &lt;SymbolString&gt; whose entry is getting built.
#       style - The style to apply to the entry, such as &lt;CHParent&gt;.
#       link - Whether to build a link for this class or not.  When building the selected class' entry, this should be false.  It will
#               automatically handle whether the symbol is defined or not.
#
sub BuildClassHierarchyEntry #(file, symbol, style, link)
    {
    my ($self, $file, $symbol, $style, $link) = @_;

    my @identifiers = NaturalDocs::SymbolString-&gt;IdentifiersOf($symbol);
    my $name = join(NaturalDocs::Languages-&gt;LanguageOf($file)-&gt;PackageSeparator(), @identifiers);
    $name = $self-&gt;StringToHTML($name);

    my $output = '&lt;div class=' . $style . '&gt;&lt;div class=CHEntry&gt;';

    if ($link)
        {
        my $target = NaturalDocs::SymbolTable-&gt;Lookup($symbol, $file);

        if (defined $target)
            {
            my $targetFile;

            if ($target-&gt;File() ne $file)
                {  $targetFile = $self-&gt;MakeRelativeURL( $self-&gt;OutputFileOf($file), $self-&gt;OutputFileOf($target-&gt;File()), 1 );  };
            # else leave it undef

            my $targetTooltipID = $self-&gt;BuildToolTip($symbol, $target-&gt;File(), $target-&gt;Type(),
                                                                          $target-&gt;Prototype(), $target-&gt;Summary());

            my $toolTipProperties = $self-&gt;BuildToolTipLinkProperties($targetTooltipID);

            $output .= '&lt;a href="' . $targetFile . '#' . $self-&gt;SymbolToHTMLSymbol($symbol) . '" '
                            . 'class=L' . NaturalDocs::Topics-&gt;NameOfType($target-&gt;Type(), 0, 1) . ' ' . $toolTipProperties . '&gt;'
                            . $name . '&lt;/a&gt;';
            }
        else
            {  $output .= $name;  };
        }
    else
        {  $output .= $name;  };

    $output .= '&lt;/div&gt;&lt;/div&gt;';
    return $output;
    };


#
#   Function: OpeningBrowserStyles
#
#   Returns the JavaScript that will add opening browser styles if necessary.
#
sub OpeningBrowserStyles
    {
    my $self = shift;

    return

    '&lt;script language=JavaScript&gt;&lt;!--' . "\n"

        # IE 4 and 5 don't understand 'undefined', so you can't say '!= undefined'.
        . 'if (browserType) {'
            . 'document.write("&lt;div class=" + browserType + "&gt;");'
            . 'if (browserVer) {'
                . 'document.write("&lt;div class=" + browserVer + "&gt;"); }'
            . '}'

    . '// --&gt;&lt;/script&gt;';
    };


#
#   Function: ClosingBrowserStyles
#
#   Returns the JavaScript that will close browser styles if necessary.
#
sub ClosingBrowserStyles
    {
    my $self = shift;

    return

    '&lt;script language=JavaScript&gt;&lt;!--' . "\n"

        . 'if (browserType) {'
            . 'if (browserVer) {'
                . 'document.write("&lt;/div&gt;"); }'
            . 'document.write("&lt;/div&gt;");'
            . '}'

    . '// --&gt;&lt;/script&gt;';
    };


#
#   Function: StandardComments
#
#   Returns the standard HTML comments that should be included in every generated file.  This includes &lt;IEWebMark()&gt;, so this
#   really is required for proper functionality.
#
sub StandardComments
    {
    my $self = shift;

    return "\n\n"

        . '&lt;!--  Generated by Natural Docs, version ' . NaturalDocs::Settings-&gt;TextAppVersion() . ' --&gt;' . "\n"
        . '&lt;!--  ' . NaturalDocs::Settings-&gt;AppURL() . '  --&gt;' . "\n\n"
        . $self-&gt;IEWebMark() . "\n\n";
    };


#
#   Function: IEWebMark
#
#   Returns the HTML comment necessary to get around the security warnings in IE starting with Windows XP Service Pack 2.
#
#   With this mark, the HTML page is treated as if it were in the Internet security zone instead of the Local Machine zone.  This
#   prevents the lockdown on scripting that causes an error message to appear with each page.
#
#   More Information:
#
#       - http://www.microsoft.com/technet/prodtechnol/winxppro/maintain/sp2brows.mspx#EHAA
#       - http://www.phdcc.com/xpsp2.htm#markoftheweb
#
sub IEWebMark
    {
    my $self = shift;

    return '&lt;!-- saved from url=(0026)http://www.naturaldocs.org --&gt;';
    };



###############################################################################
# Group: Index Functions


#
#   Function: BuildIndexPages
#
#   Builds an index file or files.
#
#   Parameters:
#
#       type - The &lt;TopicType&gt; the index is limited to, or undef for none.
#       indexSections  - An arrayref of sections, each section being an arrayref &lt;NaturalDocs::SymbolTable::IndexElement&gt;
#                               objects.  The first section is for symbols, the second for numbers, and the rest for A through Z.
#       beginIndexPage - All the content of the HTML page up to where the index content should appear.
#       endIndexPage - All the content of the HTML page past where the index should appear.
#       beginSearchResultsPage - All the content of the HTML page up to where the search results content should appear.
#       endSearchResultsPage - All the content of the HTML page past where the search results content should appear.
#
#   Returns:
#
#       The number of pages in the index.
#
sub BuildIndexPages #(TopicType type, NaturalDocs::SymbolTable::IndexElement[] indexSections, string beginIndexPage, string endIndexPage, string beginSearchResultsPage, string endSearchResultsPage) =&gt; int
    {
    my ($self, $type, $indexSections, $beginIndexPage, $endIndexPage, $beginSearchResultsPage, $endSearchResultsPage) = @_;


    # Build the content.

    my ($indexHTMLSections, $tooltipHTMLSections, $searchResultsHTMLSections) = $self-&gt;BuildIndexSections($indexSections);


    # Generate the search result pages.

   for (my $i = 0; $i &lt; 28; $i++)
        {
        if ($searchResultsHTMLSections-&gt;[$i])
            {
            my $searchResultsFileName = $self-&gt;SearchResultsFileOf($type, $searchExtensions[$i]);

            open(INDEXFILEHANDLE, '&gt;' . $searchResultsFileName)
                or die "Couldn't create output file " . $searchResultsFileName . ".\n";

            print INDEXFILEHANDLE

            $beginSearchResultsPage

            . '&lt;div class=SRStatus id=Loading&gt;Loading...&lt;/div&gt;'

            . '&lt;table border=0 cellspacing=0 cellpadding=0&gt;'
                . $searchResultsHTMLSections-&gt;[$i]
            . '&lt;/table&gt;'

            . '&lt;div class=SRStatus id=Searching&gt;Searching...&lt;/div&gt;'
            . '&lt;div class=SRStatus id=NoMatches&gt;No Matches&lt;/div&gt;'

            . '&lt;script type="text/javascript"&gt;&lt;!--' . "\n"
                . 'document.getElementById("Loading").style.display="none";' . "\n"
                . 'document.getElementById("NoMatches").style.display="none";' . "\n"

                . 'var searchResults = new SearchResults("searchResults", "' . $self-&gt;CommandLineOption() . '");' . "\n"
                . 'searchResults.Search();' . "\n"
            . '--&gt;&lt;/script&gt;'

            . $endSearchResultsPage;

            close(INDEXFILEHANDLE);
            };
        };

    if (!$self-&gt;MadeEmptySearchResultsPage())
        {
        my $emptySearchResultsFileName = NaturalDocs::File-&gt;JoinPaths( $self-&gt;SearchResultsDirectory(), 'NoResults.html' );

        open(INDEXFILEHANDLE, '&gt;' . $emptySearchResultsFileName)
            or die "Couldn't create output file " . $emptySearchResultsFileName . ".\n";

        print INDEXFILEHANDLE

        $beginSearchResultsPage
        . '&lt;div class=SRStatus id=NoMatches&gt;No Matches&lt;/div&gt;'
        . $endSearchResultsPage;

        close(INDEXFILEHANDLE);

        $self-&gt;SetMadeEmptySearchResultsPage(1);
        };


    # Generate the index pages.

    my $page = 1;
    my $pageSize = 0;
    my @pageLocations;

    # The maximum page size acceptable before starting a new page.  Note that this doesn't include beginPage and endPage,
    # because we don't want something like a large menu screwing up the calculations.
    use constant PAGESIZE_LIMIT =&gt; 50000;


    # File the pages.

    for (my $i = 0; $i &lt; scalar @$indexHTMLSections; $i++)
        {
        if (!defined $indexHTMLSections-&gt;[$i])
            {  next;  };

        $pageSize += length($indexHTMLSections-&gt;[$i]) + length($tooltipHTMLSections-&gt;[$i]);
        $pageLocations[$i] = $page;

        if ($pageSize + length($indexHTMLSections-&gt;[$i+1]) + length($tooltipHTMLSections-&gt;[$i+1]) &gt; PAGESIZE_LIMIT)
            {
            $page++;
            $pageSize = 0;
            };
        };


    # Build the pages.

    my $indexFileName;
    $page = -1;
    my $oldPage = -1;
    my $tooltips;
    my $firstHeading;

    for (my $i = 0; $i &lt; scalar @$indexHTMLSections; $i++)
        {
        if (!defined $indexHTMLSections-&gt;[$i])
            {  next;  };

        $page = $pageLocations[$i];

        # Switch files if we need to.

        if ($page != $oldPage)
            {
            if ($oldPage != -1)
                {
                print INDEXFILEHANDLE '&lt;/table&gt;' . $tooltips . $endIndexPage;
                close(INDEXFILEHANDLE);
                $tooltips = undef;
                };

            $indexFileName = $self-&gt;IndexFileOf($type, $page);

            open(INDEXFILEHANDLE, '&gt;' . $indexFileName)
                or die "Couldn't create output file " . $indexFileName . ".\n";

            print INDEXFILEHANDLE $beginIndexPage . $self-&gt;BuildIndexNavigationBar($type, $page, \@pageLocations)
                                              . '&lt;table border=0 cellspacing=0 cellpadding=0&gt;';

            $oldPage = $page;
            $firstHeading = 1;
            };

        print INDEXFILEHANDLE
        '&lt;tr&gt;'
            . '&lt;td class=IHeading' . ($firstHeading ? ' id=IFirstHeading' : '') . '&gt;'
                . '&lt;a name="' . $indexAnchors[$i] . '"&gt;&lt;/a&gt;'
                 . $indexHeadings[$i]
            . '&lt;/td&gt;'
            . '&lt;td&gt;&lt;/td&gt;'
        . '&lt;/tr&gt;'

        . $indexHTMLSections-&gt;[$i];

        $firstHeading = 0;
        $tooltips .= $tooltipHTMLSections-&gt;[$i];
        };

    if ($page != -1)
        {
        print INDEXFILEHANDLE '&lt;/table&gt;' . $tooltips . $endIndexPage;
        close(INDEXFILEHANDLE);
        }

    # Build a dummy page so there's something at least.
    else
        {
        $indexFileName = $self-&gt;IndexFileOf($type, 1);

        open(INDEXFILEHANDLE, '&gt;' . $indexFileName)
            or die "Couldn't create output file " . $indexFileName . ".\n";

        print INDEXFILEHANDLE
            $beginIndexPage
            . $self-&gt;BuildIndexNavigationBar($type, 1, \@pageLocations)
            . 'There are no entries in the ' . lc( NaturalDocs::Topics-&gt;NameOfType($type) ) . ' index.'
            . $endIndexPage;

        close(INDEXFILEHANDLE);
        };


    return $page;
    };


#
#   Function: BuildIndexSections
#
#   Builds and returns the index and search results sections in HTML.
#
#   Parameters:
#
#       index  - An arrayref of sections, each section being an arrayref &lt;NaturalDocs::SymbolTable::IndexElement&gt; objects.
#                   The first section is for symbols, the second for numbers, and the rest for A through Z.
#
#   Returns:
#
#       The arrayref ( indexSections, tooltipSections, searchResultsSections ).
#
#       Index 0 is the symbols, index 1 is the numbers, and each following index is A through Z.  The content of each section
#       is its HTML, or undef if there is nothing for that section.
#
sub BuildIndexSections #(NaturalDocs::SymbolTable::IndexElement[] index) =&gt; ( string[], string[], string[] )
    {
    my ($self, $indexSections) = @_;

    $self-&gt;ResetToolTips();
    %searchResultIDs = ( );

    my $contentSections = [ ];
    my $tooltipSections = [ ];
    my $searchResultsSections = [ ];

    for (my $section = 0; $section &lt; scalar @$indexSections; $section++)
        {
        if (defined $indexSections-&gt;[$section])
            {
            my $total = scalar @{$indexSections-&gt;[$section]};

            for (my $i = 0; $i &lt; $total; $i++)
                {
                my $id;

                if ($i == 0)
                    {
                    if ($total == 1)
                        {  $id = 'IOnlySymbolPrefix';  }
                    else
                        {  $id = 'IFirstSymbolPrefix';  };
                    }
                elsif ($i == $total - 1)
                    {  $id = 'ILastSymbolPrefix';  };

                my ($content, $searchResult) = $self-&gt;BuildIndexElement($indexSections-&gt;[$section]-&gt;[$i], $id);
                $contentSections-&gt;[$section] .= $content;
                $searchResultsSections-&gt;[$section] .= $searchResult;
                };

            $tooltipSections-&gt;[$section] .= $self-&gt;BuildToolTips();
            $self-&gt;ResetToolTips(1);
            };
        };


    return ( $contentSections, $tooltipSections, $searchResultsSections );
    };


#
#   Function: BuildIndexElement
#
#   Converts a &lt;NaturalDocs::SymbolTable::IndexElement&gt; to HTML and returns it.  It will handle all sub-elements automatically.
#
#   Parameters:
#
#       element - The &lt;NaturalDocs::SymbolTable::IndexElement&gt; to build.
#       cssID - The CSS ID to apply to the prefix.
#
#   Recursion-Only Parameters:
#
#       These parameters are used internally for recursion, and should not be set.
#
#       symbol - If the element is below symbol level, the &lt;SymbolString&gt; to use.
#       package - If the element is below package level, the package &lt;SymbolString&gt; to use.
#       hasPackage - Whether the element is below package level.  Is necessary because package may need to be undef.
#
#   Returns:
#
#       The array ( indexHTML, searchResultHTML ) which is the element in the respective HTML forms.
#
sub BuildIndexElement #(NaturalDocs::SymbolTable::IndexElement element, string cssID, SymbolString symbol, SymbolString package, bool hasPackage) =&gt; ( string, string )
    {
    my ($self, $element, $cssID, $symbol, $package, $hasPackage) = @_;


    # If we're doing a file sub-index entry...

    if ($hasPackage)
        {
        my ($inputDirectory, $relativePath) = NaturalDocs::Settings-&gt;SplitFromInputDirectory($element-&gt;File());

        return $self-&gt;BuildIndexLink($self-&gt;StringToHTML($relativePath, ADD_HIDDEN_BREAKS), $symbol,
                                                                                 $package, $element-&gt;File(), $element-&gt;Type(),
                                                                                 $element-&gt;Prototype(), $element-&gt;Summary(), 'IFile');
        }


    # If we're doing a package sub-index entry...

    elsif (defined $symbol)

        {
        my $text;

        if ($element-&gt;Package())
            {
            $text = NaturalDocs::SymbolString-&gt;ToText($element-&gt;Package(), $element-&gt;PackageSeparator());
            $text = $self-&gt;StringToHTML($text, ADD_HIDDEN_BREAKS);
            }
        else
            {  $text = 'Global';  };

        if (!$element-&gt;HasMultipleFiles())
            {
            return $self-&gt;BuildIndexLink($text, $symbol, $element-&gt;Package(), $element-&gt;File(), $element-&gt;Type(),
                                                      $element-&gt;Prototype(), $element-&gt;Summary(), 'IParent');
            }

        else
            {
            my $indexHTML =
            '&lt;span class=IParent&gt;'
                . $text
            . '&lt;/span&gt;'
            . '&lt;div class=ISubIndex&gt;';

            my $searchResultHTML = $indexHTML;

            my $fileElements = $element-&gt;File();
            foreach my $fileElement (@$fileElements)
                {
                my ($i, $s) = $self-&gt;BuildIndexElement($fileElement, $cssID, $symbol, $element-&gt;Package(), 1);
                $indexHTML .= $i;
                $searchResultHTML .= $s;
                };

            $indexHTML .= '&lt;/div&gt;';
            $searchResultHTML .= '&lt;/div&gt;';

            return ($indexHTML, $searchResultHTML);
            };
        }


    # If we're doing a top-level symbol entry...

    else
        {
        my $symbolText = $self-&gt;StringToHTML($element-&gt;SortableSymbol(), ADD_HIDDEN_BREAKS);
        my $symbolPrefix = $self-&gt;StringToHTML($element-&gt;IgnoredPrefix());
        my $searchResultID = $self-&gt;StringToSearchResultID($element-&gt;SortableSymbol());

        my $indexHTML =
        '&lt;tr&gt;'
            . '&lt;td class=ISymbolPrefix' . ($cssID ? ' id=' . $cssID : '') . '&gt;'
                . ($symbolPrefix || '&amp;nbsp;')
            . '&lt;/td&gt;&lt;td class=IEntry&gt;';

        my $searchResultsHTML =
        '&lt;div class=SRResult id=' . $searchResultID . '&gt;&lt;div class=IEntry&gt;';

            if ($symbolPrefix)
                {  $searchResultsHTML .= '&lt;span class=ISymbolPrefix&gt;' . $symbolPrefix . '&lt;/span&gt;';  };

        if (!$element-&gt;HasMultiplePackages())
            {
            my $packageText;

            if (defined $element-&gt;Package())
                {
                $packageText = NaturalDocs::SymbolString-&gt;ToText($element-&gt;Package(), $element-&gt;PackageSeparator());
                $packageText = $self-&gt;StringToHTML($packageText, ADD_HIDDEN_BREAKS);
                };

            if (!$element-&gt;HasMultipleFiles())
                {
                my ($i, $s) =
                    $self-&gt;BuildIndexLink($symbolText, $element-&gt;Symbol(), $element-&gt;Package(), $element-&gt;File(),
                                                     $element-&gt;Type(), $element-&gt;Prototype(), $element-&gt;Summary(), 'ISymbol');
                $indexHTML .= $i;
                $searchResultsHTML .= $s;

                if (defined $packageText)
                    {
                    $indexHTML .=
                    ', &lt;span class=IParent&gt;'
                        . $packageText
                    . '&lt;/span&gt;';

                    $searchResultsHTML .=
                    ', &lt;span class=IParent&gt;'
                        . $packageText
                    . '&lt;/span&gt;';
                    };
                }
            else # hasMultipleFiles but not multiplePackages
                {
                $indexHTML .=
                '&lt;span class=ISymbol&gt;'
                    . $symbolText
                . '&lt;/span&gt;';

                $searchResultsHTML .=
                q{&lt;a href="javascript:searchResults.Toggle('} . $searchResultID . q{')" class=ISymbol&gt;}
                    . $symbolText
                . '&lt;/a&gt;';

                my $output;

                if (defined $packageText)
                    {
                    $output .=
                    ', &lt;span class=IParent&gt;'
                        . $packageText
                    . '&lt;/span&gt;';
                    };

                $output .=
                '&lt;div class=ISubIndex&gt;';

                $indexHTML .= $output;
                $searchResultsHTML .= $output;

                my $fileElements = $element-&gt;File();
                foreach my $fileElement (@$fileElements)
                    {
                    my ($i, $s) = $self-&gt;BuildIndexElement($fileElement, $cssID, $element-&gt;Symbol(), $element-&gt;Package(), 1);
                    $indexHTML .= $i;
                    $searchResultsHTML .= $s;
                    };

                $indexHTML .= '&lt;/div&gt;';
                $searchResultsHTML .= '&lt;/div&gt;';
                };
            }

        else # hasMultiplePackages
            {
            $indexHTML .=
            '&lt;span class=ISymbol&gt;'
                . $symbolText
            . '&lt;/span&gt;'
            . '&lt;div class=ISubIndex&gt;';

            $searchResultsHTML .=
            q{&lt;a href="javascript:searchResults.Toggle('} . $searchResultID . q{')" class=ISymbol&gt;}
                . $symbolText
            . '&lt;/a&gt;'
            . '&lt;div class=ISubIndex&gt;';

            my $packageElements = $element-&gt;Package();
            foreach my $packageElement (@$packageElements)
                {
                my ($i, $s) = $self-&gt;BuildIndexElement($packageElement, $cssID, $element-&gt;Symbol());
                $indexHTML .= $i;
                $searchResultsHTML .= $s;
                };

            $indexHTML .= '&lt;/div&gt;';
            $searchResultsHTML .= '&lt;/div&gt;';
            };

        $indexHTML .= '&lt;/td&gt;&lt;/tr&gt;';
        $searchResultsHTML .= '&lt;/div&gt;&lt;/div&gt;';

        return ($indexHTML, $searchResultsHTML);
        };
    };


#
#   Function: BuildIndexLink
#
#   Builds and returns the HTML associated with an index link.  The HTML will be the a href tag, the text, and the closing tag.
#
#   Parameters:
#
#       text - The text of the link *in HTML*.  Use &lt;IndexSymbolToHTML()&gt; if necessary.
#       symbol - The partial &lt;SymbolString&gt; to link to.
#       package - The package &lt;SymbolString&gt; of the symbol.
#       file - The &lt;FileName&gt; the symbol is defined in.
#       type - The &lt;TopicType&gt; of the symbol.
#       prototype - The prototype of the symbol, or undef if none.
#       summary - The summary of the symbol, or undef if none.
#       style - The CSS style to apply to the link.
#
#   Returns:
#
#       The array ( indexHTML, searchResultHTML ) which is the link in the respective forms.
#
sub BuildIndexLink #(string text, SymbolString symbol, SymbolString package, FileName file, TopicType type, string prototype, string summary, string style) =&gt; ( string, string )
    {
    my ($self, $text, $symbol, $package, $file, $type, $prototype, $summary, $style) = @_;

    $symbol = NaturalDocs::SymbolString-&gt;Join($package, $symbol);

    my $targetTooltipID = $self-&gt;BuildToolTip($symbol, $file, $type, $prototype, $summary);
    my $toolTipProperties = $self-&gt;BuildToolTipLinkProperties($targetTooltipID);

    my $indexHTML = '&lt;a href="' . $self-&gt;MakeRelativeURL( $self-&gt;IndexDirectory(), $self-&gt;OutputFileOf($file) )
                                         . '#' . $self-&gt;SymbolToHTMLSymbol($symbol) . '" ' . $toolTipProperties . ' '
                                . 'class=' . $style . '&gt;' . $text . '&lt;/a&gt;';
    my $searchResultHTML = '&lt;a href="' . $self-&gt;MakeRelativeURL( $self-&gt;SearchResultsDirectory(), $self-&gt;OutputFileOf($file) )
                                         . '#' . $self-&gt;SymbolToHTMLSymbol($symbol) . '" '
                                         . ($self-&gt;CommandLineOption eq 'HTML' ? 'target=_parent ' : '')
                                . 'class=' . $style . '&gt;' . $text . '&lt;/a&gt;';

    return ($indexHTML, $searchResultHTML);
    };


#
#   Function: BuildIndexNavigationBar
#
#   Builds a navigation bar for a page of the index.
#
#   Parameters:
#
#       type - The &lt;TopicType&gt; of the index, or undef for general.
#       page - The page of the index the navigation bar is for.
#       locations - An arrayref of the locations of each section.  Index 0 is for the symbols, index 1 for the numbers, and the rest
#                       for each letter.  The values are the page numbers where the sections are located.
#
sub BuildIndexNavigationBar #(type, page, locations)
    {
    my ($self, $type, $page, $locations) = @_;

    my $output = '&lt;div class=INavigationBar&gt;';

    for (my $i = 0; $i &lt; scalar @indexHeadings; $i++)
        {
        if ($i != 0)
            {  $output .= ' &amp;middot; ';  };

        if (defined $locations-&gt;[$i])
            {
            $output .= '&lt;a href="';

            if ($locations-&gt;[$i] != $page)
                {  $output .= $self-&gt;RelativeIndexFileOf($type, $locations-&gt;[$i]);  };

            $output .= '#' . $indexAnchors[$i] . '"&gt;' . $indexHeadings[$i] . '&lt;/a&gt;';
            }
        else
            {
            $output .= $indexHeadings[$i];
            };
        };

    $output .= '&lt;/div&gt;';

    return $output;
    };



###############################################################################
# Group: File Functions


#
#   Function: PurgeIndexFiles
#
#   Removes all or some of the output files for an index.
#
#   Parameters:
#
#       type  - The index &lt;TopicType&gt;.
#       indexSections  - An arrayref of sections, each section being an arrayref &lt;NaturalDocs::SymbolTable::IndexElement&gt;
#                               objects.  The first section is for symbols, the second for numbers, and the rest for A through Z.  May be
#                               undef.
#       startingPage - If defined, only pages starting with this number will be removed.  Otherwise all pages will be removed.
#
sub PurgeIndexFiles #(TopicType type, optional NaturalDocs::SymbolTable::IndexElement[] indexSections, optional int startingPage)
    {
    my ($self, $type, $indexSections, $page) = @_;

    # First the regular index pages.

    if (!defined $page)
        {  $page = 1;  };

    for (;;)
        {
        my $file = $self-&gt;IndexFileOf($type, $page);

        if (-e $file)
            {
            unlink($file);
            $page++;
            }
        else
            {
            last;
            };
        };


    # Next the search results.

    for (my $i = 0; $i &lt; 28; $i++)
        {
        if (!$indexSections || !$indexSections-&gt;[$i])
            {
            my $file = $self-&gt;SearchResultsFileOf($type, $searchExtensions[$i]);

            if (-e $file)
                {  unlink($file);  };
            };
        };
    };


#
#   Function: OutputFileOf
#
#   Returns the output file name of the source file.  Will be undef if it is not a file from a valid input directory.
#
sub OutputFileOf #(sourceFile)
    {
    my ($self, $sourceFile) = @_;

    my ($inputDirectory, $relativeSourceFile) = NaturalDocs::Settings-&gt;SplitFromInputDirectory($sourceFile);
    if (!defined $inputDirectory)
        {  return undef;  };

    my $outputDirectory = NaturalDocs::Settings-&gt;OutputDirectoryOf($self);
    my $inputDirectoryName = NaturalDocs::Settings-&gt;InputDirectoryNameOf($inputDirectory);

    $outputDirectory = NaturalDocs::File-&gt;JoinPaths( $outputDirectory,
                                                                            'files' . ($inputDirectoryName != 1 ? $inputDirectoryName : ''), 1 );

    # We need to change any extensions to dashes because Apache will think file.pl.html is a script.
    # We also need to add a dash if the file doesn't have an extension so there'd be no conflicts with index.html,
    # FunctionIndex.html, etc.

    if (!($relativeSourceFile =~ tr/./-/))
        {  $relativeSourceFile .= '-';  };

    $relativeSourceFile =~ tr/ &amp;?(){};/_/;
    $relativeSourceFile .= '.html';

    return NaturalDocs::File-&gt;JoinPaths($outputDirectory, $relativeSourceFile);
    };


#
#   Function: OutputImageOf
#
#   Returns the output image file name of the source image file.  Will be undef if it is not a file from a valid input directory.
#
sub OutputImageOf #(sourceImageFile)
    {
    my ($self, $sourceImageFile) = @_;

    my $outputDirectory = NaturalDocs::Settings-&gt;OutputDirectoryOf($self);
    my $topLevelDirectory;

    my ($inputDirectory, $relativeImageFile) = NaturalDocs::Settings-&gt;SplitFromInputDirectory($sourceImageFile);

    if (defined $inputDirectory)
        {
        my $inputDirectoryName = NaturalDocs::Settings-&gt;InputDirectoryNameOf($inputDirectory);
        $topLevelDirectory = 'files' . ($inputDirectoryName != 1 ? $inputDirectoryName : '');
        }
    else
        {
        ($inputDirectory, $relativeImageFile) = NaturalDocs::Settings-&gt;SplitFromImageDirectory($sourceImageFile);

        if (!defined $inputDirectory)
            {  return undef;  };

        my $inputDirectoryName = NaturalDocs::Settings-&gt;ImageDirectoryNameOf($inputDirectory);
        $topLevelDirectory = 'images' . ($inputDirectoryName != 1 ? $inputDirectoryName : '');
        }


    $outputDirectory = NaturalDocs::File-&gt;JoinPaths($outputDirectory, $topLevelDirectory, 1);

    $relativeImageFile =~ tr/ /_/;

    return NaturalDocs::File-&gt;JoinPaths($outputDirectory, $relativeImageFile);
    };


#
#   Function: IndexDirectory
#
#   Returns the directory of the index files.
#
sub IndexDirectory
    {
    my $self = shift;
    return NaturalDocs::File-&gt;JoinPaths( NaturalDocs::Settings-&gt;OutputDirectoryOf($self), 'index', 1);
    };


#
#   Function: IndexFileOf
#
#   Returns the output file name of the index file.
#
#   Parameters:
#
#       type  - The &lt;TopicType&gt; of the index.
#       page  - The page number.  Undef is the same as one.
#
sub IndexFileOf #(type, page)
    {
    my ($self, $type, $page) = @_;
    return NaturalDocs::File-&gt;JoinPaths( $self-&gt;IndexDirectory(), $self-&gt;RelativeIndexFileOf($type, $page) );
    };


#
#   Function: RelativeIndexFileOf
#
#   Returns the output file name of the index file, relative to other index files.
#
#   Parameters:
#
#       type  - The &lt;TopicType&gt; of the index.
#       page  - The page number.  Undef is the same as one.
#
sub RelativeIndexFileOf #(type, page)
    {
    my ($self, $type, $page) = @_;
    return NaturalDocs::Topics-&gt;NameOfType($type, 1, 1) . (defined $page &amp;&amp; $page != 1 ? $page : '') . '.html';
    };


#
#   Function: SearchResultsDirectory
#
#   Returns the directory of the search results files.
#
sub SearchResultsDirectory
    {
    my $self = shift;
    return NaturalDocs::File-&gt;JoinPaths( NaturalDocs::Settings-&gt;OutputDirectoryOf($self), 'search', 1);
    };


#
#   Function: SearchResultsFileOf
#
#   Returns the output file name of the search result file.
#
#   Parameters:
#
#       type  - The &lt;TopicType&gt; of the index.
#       extra - The string to add to the end of the file name, such as "A" or "Symbols".
#
sub SearchResultsFileOf #(TopicType type, string extra)
    {
    my ($self, $type, $extra) = @_;

    my $fileName = NaturalDocs::Topics-&gt;NameOfType($type, 1, 1) . $extra . '.html';

    return NaturalDocs::File-&gt;JoinPaths( $self-&gt;SearchResultsDirectory(), $fileName );
    };


#
#   Function: CSSDirectory
#
#   Returns the directory of the CSS files.
#
sub CSSDirectory
    {
    my $self = shift;
    return NaturalDocs::File-&gt;JoinPaths( NaturalDocs::Settings-&gt;OutputDirectoryOf($self), 'styles', 1);
    };


#
#   Function: MainCSSFile
#
#   Returns the location of the main CSS file.
#
sub MainCSSFile
    {
    my $self = shift;
    return NaturalDocs::File-&gt;JoinPaths( $self-&gt;CSSDirectory(), 'main.css' );
    };


#
#   Function: JavaScriptDirectory
#
#   Returns the directory of the JavaScript files.
#
sub JavaScriptDirectory
    {
    my $self = shift;
    return NaturalDocs::File-&gt;JoinPaths( NaturalDocs::Settings-&gt;OutputDirectoryOf($self), 'javascript', 1);
    };


#
#   Function: MainJavaScriptFile
#
#   Returns the location of the main JavaScript file.
#
sub MainJavaScriptFile
    {
    my $self = shift;
    return NaturalDocs::File-&gt;JoinPaths( $self-&gt;JavaScriptDirectory(), 'main.js' );
    };


#
#   Function: SearchDataJavaScriptFile
#
#   Returns the location of the search data JavaScript file.
#
sub SearchDataJavaScriptFile
    {
    my $self = shift;
    return NaturalDocs::File-&gt;JoinPaths( $self-&gt;JavaScriptDirectory(), 'searchdata.js' );
    };



###############################################################################
# Group: Support Functions


#
#   Function: IndexTitleOf
#
#   Returns the page title of the index file.
#
#   Parameters:
#
#       type  - The type of index.
#
sub IndexTitleOf #(type)
    {
    my ($self, $type) = @_;

    return ($type eq ::TOPIC_GENERAL() ? '' : NaturalDocs::Topics-&gt;NameOfType($type) . ' ') . 'Index';
    };

#
#   Function: MakeRelativeURL
#
#   Returns a relative path between two files in the output tree and returns it in URL format.
#
#   Parameters:
#
#       baseFile    - The base &lt;FileName&gt; in local format, *not* in URL format.
#       targetFile  - The target &lt;FileName&gt; of the link in local format, *not* in URL format.
#       baseHasFileName - Whether baseFile has a file name attached or is just a path.
#
#   Returns:
#
#       The relative URL to the target.
#
sub MakeRelativeURL #(FileName baseFile, FileName targetFile, bool baseHasFileName) -&gt; string relativeURL
    {
    my ($self, $baseFile, $targetFile, $baseHasFileName) = @_;

    if ($baseHasFileName)
        {  $baseFile = NaturalDocs::File-&gt;NoFileName($baseFile)  };

    my $relativePath = NaturalDocs::File-&gt;MakeRelativePath($baseFile, $targetFile);

    return $self-&gt;ConvertAmpChars( NaturalDocs::File-&gt;ConvertToURL($relativePath) );
    };

#
#   Function: StringToHTML
#
#   Converts a text string to HTML.  Does not apply paragraph tags or accept formatting tags.
#
#   Parameters:
#
#       string - The string to convert.
#       addHiddenBreaks - Whether to add hidden breaks to the string.  You can use &lt;ADD_HIDDEN_BREAKS&gt; for this parameter
#                                   if you want to make the calling code clearer.
#
#   Returns:
#
#       The string in HTML.
#
sub StringToHTML #(string, addHiddenBreaks)
    {
    my ($self, $string, $addHiddenBreaks) = @_;

    $string =~ s/&amp;/&amp;amp;/g;
    $string =~ s/&lt;/&amp;lt;/g;
    $string =~ s/&gt;/&amp;gt;/g;

    # Me likey the fancy quotes.  They work in IE 4+, Mozilla, and Opera 5+.  We've already abandoned NS4 with the CSS
    # styles, so might as well.
    $string =~ s/^\'/&amp;lsquo;/gm;
    $string =~ s/([\ \(\[\{])\'/$1&amp;lsquo;/g;
    $string =~ s/\'/&amp;rsquo;/g;

    $string =~ s/^\"/&amp;ldquo;/gm;
    $string =~ s/([\ \(\[\{])\"/$1&amp;ldquo;/g;
    $string =~ s/\"/&amp;rdquo;/g;

    # Me likey the double spaces too.  As you can probably tell, I like print-formatting better than web-formatting.  The indented
    # paragraphs without blank lines in between them do become readable when you have fancy quotes and double spaces too.
    $string = $self-&gt;AddDoubleSpaces($string);

    if ($addHiddenBreaks)
        {  $string = $self-&gt;AddHiddenBreaks($string);  };

    return $string;
    };


#
#   Function: SymbolToHTMLSymbol
#
#   Converts a &lt;SymbolString&gt; to a HTML symbol, meaning one that is safe to include in anchor and link tags.  You don't need
#   to pass the result to &lt;ConvertAmpChars()&gt;.
#
sub SymbolToHTMLSymbol #(symbol)
    {
    my ($self, $symbol) = @_;

    my @identifiers = NaturalDocs::SymbolString-&gt;IdentifiersOf($symbol);
    my $htmlSymbol = join('.', @identifiers);

    # If only Mozilla was nice about putting special characters in URLs like IE and Opera are, I could leave spaces in and replace
    # "&lt;&gt;&amp; with their amp chars.  But alas, Mozilla shows them as %20, etc. instead.  It would have made for nice looking URLs.
    $htmlSymbol =~ tr/ \"&lt;&gt;\?&amp;%/_/d;

    return $htmlSymbol;
    };


#
#   Function: StringToSearchResultID
#
#   Takes a text string and translates it into something that can be used as a CSS ID.
#
#   Parameters:
#
#       string - The string to convert
#       dontIncrement - If set, it reuses the last generated ID.  Otherwise it generates a new one if it matches a previously
#                               generated one in a case-insensitive way.
#
sub StringToSearchResultID #(string string, bool dontIncrement = 0) =&gt; string
    {
    my ($self, $string, $dontIncrement) = @_;

    $string =~ s/\_/_und/g;
    $string =~ s/ +/_spc/g;

    my %translation = ( '~' =&gt; '_til', '!' =&gt; '_exc', '@' =&gt; '_att', '#' =&gt; '_num', '$' =&gt; '_dol', '%' =&gt; '_pct', '^' =&gt; '_car',
                                  '&amp;' =&gt; '_amp', '*' =&gt; '_ast', '(' =&gt; '_lpa', ')' =&gt; '_rpa', '-' =&gt; '_min', '+' =&gt; '_plu', '=' =&gt; '_equ',
                                  '{' =&gt; '_lbc', '}' =&gt; '_rbc', '[' =&gt; '_lbk', ']' =&gt; '_rbk', ':' =&gt; '_col', ';' =&gt; '_sco', '"' =&gt; '_quo',
                                  '\'' =&gt; '_apo', '&lt;' =&gt; '_lan', '&gt;' =&gt; '_ran', ',' =&gt; '_com', '.' =&gt; '_per', '?' =&gt; '_que', '/' =&gt; '_sla' );

    $string =~ s/([\~\!\@\#\$\%\^\&amp;\*\(\)\-\+\=\{\}\[\]\:\;\"\'\&lt;\&gt;\,\.\?\/])/$translation{$1}/ge;
    $string =~ s/[^a-z0-9_]/_zzz/gi;

    my $number = $searchResultIDs{lc($string)};

    if (!$number)
        {  $number = 1;  }
    elsif (!$dontIncrement)
        {  $number++;  };

    $searchResultIDs{lc($string)} = $number;

    return 'SR' . ($number == 1 ? '' : $number) . '_' . $string;
    };


#
#   Function: NDMarkupToHTML
#
#   Converts a block of &lt;NDMarkup&gt; to HTML.
#
#   Parameters:
#
#       sourceFile - The source &lt;FileName&gt; the &lt;NDMarkup&gt; appears in.
#       text    - The &lt;NDMarkup&gt; text to convert.
#       symbol - The topic &lt;SymbolString&gt; the &lt;NDMarkup&gt; appears in.
#       package  - The package &lt;SymbolString&gt; the &lt;NDMarkup&gt; appears in.
#       type - The &lt;TopicType&gt; the &lt;NDMarkup&gt; appears in.
#       using - An arrayref of scope &lt;SymbolStrings&gt; the &lt;NDMarkup&gt; also has access to, or undef if none.
#       style - Set to one of the &lt;NDMarkupToHTML Styles&gt; or leave undef for general.
#
#   Returns:
#
#       The text in HTML.
#
sub NDMarkupToHTML #(sourceFile, text, symbol, package, type, using, style)
    {
    my ($self, $sourceFile, $text, $symbol, $package, $type, $using, $style) = @_;

    my $dlSymbolBehavior;

    if ($type == ::TOPIC_ENUMERATION())
        {  $dlSymbolBehavior = NaturalDocs::Languages-&gt;LanguageOf($sourceFile)-&gt;EnumValues();  }
    elsif (NaturalDocs::Topics-&gt;TypeInfo($type)-&gt;Scope() == ::SCOPE_ALWAYS_GLOBAL())
        {  $dlSymbolBehavior = ::ENUM_GLOBAL();  }
    else
        {  $dlSymbolBehavior = ::ENUM_UNDER_PARENT();  };

    my $output;
    my $inCode;

    my @splitText = split(/(&lt;\/?code&gt;)/, $text);

    while (scalar @splitText)
        {
        $text = shift @splitText;

        if ($text eq '&lt;code&gt;')
            {
            $output .= '&lt;blockquote&gt;&lt;pre&gt;';
            $inCode = 1;
            }
        elsif ($text eq '&lt;/code&gt;')
            {
            $output .= '&lt;/pre&gt;&lt;/blockquote&gt;';
            $inCode = undef;
            }
        elsif ($inCode)
            {
            # Leave line breaks in.
            $output .= $text;
            }
        else
            {
            # Format non-code text.

            # Convert linked images.
            if ($text =~ /&lt;img mode=\"link\"/)
                {
                if ($style == NDMARKUPTOHTML_GENERAL)
                    {
                    # Split by tags we would want to see the linked images appear after.  For example, an image link appearing in
                    # the middle of a paragraph would appear after the end of that paragraph.
                    my @imageBlocks = split(/(&lt;p&gt;.*?&lt;\/p&gt;|&lt;dl&gt;.*?&lt;\/dl&gt;|&lt;ul&gt;.*?&lt;\/ul&gt;)/, $text);
                    $text = undef;

                    foreach my $imageBlock (@imageBlocks)
                        {
                        $imageBlock =~ s{&lt;img mode=\"link\" target=\"([^\"]*)\" original=\"([^\"]*)\"&gt;}
                                                {$self-&gt;BuildImage($sourceFile, 'link', $1, $2)}ge;

                        $text .= $imageBlock . $imageContent;
                        $imageContent = undef;
                        };
                    }

                # Use only the text for tooltips and summaries.
                else
                    {
                    $text =~ s{&lt;img mode=\"link\" target=\"[^\"]*\" original=\"([^\"]*)\"&gt;}{$1}g;
                    };
                };

            # Convert quotes to fancy quotes.  This has to be done before links because some of them may have JavaScript
            # attributes that use the apostrophe character.
            $text =~ s/^\'/&amp;lsquo;/gm;
            $text =~ s/([\ \(\[\{])\'/$1&amp;lsquo;/g;
            $text =~ s/\'/&amp;rsquo;/g;

            $text =~ s/^&amp;quot;/&amp;ldquo;/gm;
            $text =~ s/([\ \(\[\{])&amp;quot;/$1&amp;ldquo;/g;
            $text =~ s/&amp;quot;/&amp;rdquo;/g;

            # Resolve and convert links, except for tooltips.
            if ($style != NDMARKUPTOHTML_TOOLTIP)
                {
                $text =~ s{&lt;link target=\"([^\"]*)\" name=\"([^\"]*)\" original=\"([^\"]*)\"&gt;}
                               {$self-&gt;BuildTextLink($1, $2, $3, $package, $using, $sourceFile)}ge;
                $text =~ s/&lt;url target=\"([^\"]*)\" name=\"([^\"]*)\"&gt;/$self-&gt;BuildURLLink($1, $2)/ge;
                }
            else
                {
                $text =~ s{&lt;link target=\"[^\"]*\" name=\"([^\"]*)\" original=\"[^\"]*\"&gt;}{$1}g;
                $text =~ s{&lt;url target=\"[^\"]*\" name=\"([^\"]*)\"&gt;}{$1}g;
                };

            # We do full e-mail links anyway just so the obfuscation remains.
            $text =~ s/&lt;email target=\"([^\"]*)\" name=\"([^\"]*)\"&gt;/$self-&gt;BuildEMailLink($1, $2)/ge;


            # Convert inline images, but only for the general style.
            if ($style == NDMARKUPTOHTML_GENERAL)
                {
                $text =~ s{&lt;img mode=\"inline\" target=\"([^\"]*)\" original=\"([^\"]*)\"&gt;}
                               {$self-&gt;BuildImage($sourceFile, 'inline', $1, $2)}ge;
                }
            else
                {
                $text =~ s{&lt;img mode=\"inline\" target=\"[^\"]*\" original=\"([^\"]*)\"&gt;}{$1}g;
                };

            # Copyright symbols.  Prevent conversion when part of (a), (b), (c) lists.
            if ($text !~ /\(a\)/i)
                {  $text =~ s/\(c\)/&amp;copy;/gi;  };

            # Trademark symbols.
            $text =~ s/\(tm\)/&amp;trade;/gi;
            $text =~ s/\(r\)/&amp;reg;/gi;

            # Add double spaces too.
            $text = $self-&gt;AddDoubleSpaces($text);

            # Headings
            $text =~ s/&lt;h&gt;/&lt;h4 class=CHeading&gt;/g;
            $text =~ s/&lt;\/h&gt;/&lt;\/h4&gt;/g;

            # Description Lists
            $text =~ s/&lt;dl&gt;/&lt;table border=0 cellspacing=0 cellpadding=0 class=CDescriptionList&gt;/g;
            $text =~ s/&lt;\/dl&gt;/&lt;\/table&gt;/g;

            $text =~ s/&lt;de&gt;/&lt;tr&gt;&lt;td class=CDLEntry&gt;/g;
            $text =~ s/&lt;\/de&gt;/&lt;\/td&gt;/g;

            if ($dlSymbolBehavior == ::ENUM_GLOBAL())
                {  $text =~ s/&lt;ds&gt;([^&lt;]+)&lt;\/ds&gt;/$self-&gt;MakeDescriptionListSymbol(undef, $1)/ge;  }
            elsif ($dlSymbolBehavior == ::ENUM_UNDER_PARENT())
                {  $text =~ s/&lt;ds&gt;([^&lt;]+)&lt;\/ds&gt;/$self-&gt;MakeDescriptionListSymbol($package, $1)/ge;  }
            else # ($dlSymbolBehavior == ::ENUM_UNDER_TYPE())
                {  $text =~ s/&lt;ds&gt;([^&lt;]+)&lt;\/ds&gt;/$self-&gt;MakeDescriptionListSymbol($symbol, $1)/ge;  }

            sub MakeDescriptionListSymbol #(package, text)
                {
                my ($self, $package, $text) = @_;

                $text = NaturalDocs::NDMarkup-&gt;RestoreAmpChars($text);
                my $symbol = NaturalDocs::SymbolString-&gt;FromText($text);

                if (defined $package)
                    {  $symbol = NaturalDocs::SymbolString-&gt;Join($package, $symbol);  };

                return
                '&lt;tr&gt;'
                    . '&lt;td class=CDLEntry&gt;'
                        # The anchors are closed, but not around the text, to prevent the :hover CSS style from kicking in.
                        . '&lt;a name="' . $self-&gt;SymbolToHTMLSymbol($symbol) . '"&gt;&lt;/a&gt;'
                        . $text
                    . '&lt;/td&gt;';
                };

            $text =~ s/&lt;dd&gt;/&lt;td class=CDLDescription&gt;/g;
            $text =~ s/&lt;\/dd&gt;/&lt;\/td&gt;&lt;\/tr&gt;/g;

            $output .= $text;
            };
        };

    return $output;
    };


#
#   Function: BuildTextLink
#
#   Creates a HTML link to a symbol, if it exists.
#
#   Parameters:
#
#       target  - The link text.
#       name - The link name.
#       original - The original text as it appears in the source.
#       package  - The package &lt;SymbolString&gt; the link appears in, or undef if none.
#       using - An arrayref of additional scope &lt;SymbolStrings&gt; the link has access to, or undef if none.
#       sourceFile  - The &lt;FileName&gt; the link appears in.
#
#       Target, name, and original are assumed to still have &lt;NDMarkup&gt; amp chars.
#
#   Returns:
#
#       The link in HTML, including tags.  If the link doesn't resolve to anything, returns the HTML that should be substituted for it.
#
sub BuildTextLink #(target, name, original, package, using, sourceFile)
    {
    my ($self, $target, $name, $original, $package, $using, $sourceFile) = @_;

    my $plainTarget = $self-&gt;RestoreAmpChars($target);

    my $symbol = NaturalDocs::SymbolString-&gt;FromText($plainTarget);
    my $symbolTarget = NaturalDocs::SymbolTable-&gt;References(::REFERENCE_TEXT(), $symbol, $package, $using, $sourceFile);

    if (defined $symbolTarget)
        {
        my $symbolTargetFile;

        if ($symbolTarget-&gt;File() ne $sourceFile)
            {
            $symbolTargetFile = $self-&gt;MakeRelativeURL( $self-&gt;OutputFileOf($sourceFile),
                                                                               $self-&gt;OutputFileOf($symbolTarget-&gt;File()), 1 );
            };
        # else leave it undef

        my $symbolTargetTooltipID = $self-&gt;BuildToolTip($symbolTarget-&gt;Symbol(), $sourceFile, $symbolTarget-&gt;Type(),
                                                                                 $symbolTarget-&gt;Prototype(), $symbolTarget-&gt;Summary());

        my $toolTipProperties = $self-&gt;BuildToolTipLinkProperties($symbolTargetTooltipID);

        return '&lt;a href="' . $symbolTargetFile . '#' . $self-&gt;SymbolToHTMLSymbol($symbolTarget-&gt;Symbol()) . '" '
                    . 'class=L' . NaturalDocs::Topics-&gt;NameOfType($symbolTarget-&gt;Type(), 0, 1) . ' ' . $toolTipProperties . '&gt;'
                        . $name
                    . '&lt;/a&gt;';
        }
    else
        {
        return $original;
        };
    };


#
#   Function: BuildURLLink
#
#   Creates a HTML link to an external URL.  Long URLs will have hidden breaks to allow them to wrap.
#
#   Parameters:
#
#       target - The URL to link to.
#       name - The label of the link.
#
#       Both are assumed to still have &lt;NDMarkup&gt; amp chars.
#
#   Returns:
#
#       The HTML link, complete with tags.
#
sub BuildURLLink #(target, name)
    {
    my ($self, $target, $name) = @_;

    # Don't restore amp chars on the target.

    if (length $name &lt; 50 || $name ne $target)
        {  return '&lt;a href="' . $target . '" class=LURL target=_top&gt;' . $name . '&lt;/a&gt;';  };

    my @segments = split(/([\,\/]|&amp;amp;)/, $target);
    my $output = '&lt;a href="' . $target . '" class=LURL target=_top&gt;';

    # Get past the first batch of slashes, since we don't want to break on things like http://.

    $output .= $segments[0];

    my $i = 1;
    while ($i &lt; scalar @segments &amp;&amp; ($segments[$i] eq '/' || !$segments[$i]))
        {
        $output .= $segments[$i];
        $i++;
        };

    # Now break on each one of those symbols.

    while ($i &lt; scalar @segments)
        {
        if ($segments[$i] eq ',' || $segments[$i] eq '/' || $segments[$i] eq '&amp;amp;')
            {  $output .= '&lt;wbr&gt;';  };

        $output .= $segments[$i];
        $i++;
        };

    $output .= '&lt;/a&gt;';
    return $output;
    };


#
#   Function: BuildEMailLink
#
#   Creates a HTML link to an e-mail address.  The address will be transparently munged to protect it (hopefully) from spambots.
#
#   Parameters:
#
#       target  - The e-mail address.
#       name - The label of the link.
#
#       Both are assumed to still have &lt;NDMarkup&gt; amp chars.
#
#   Returns:
#
#       The HTML e-mail link, complete with tags.
#
sub BuildEMailLink #(target, name)
    {
    my ($self, $target, $name) = @_;
    my @splitAddress;


    # Hack the address up.  We want two user pieces and two host pieces.

    my ($user, $host) = split(/\@/, $self-&gt;RestoreAmpChars($target));

    my $userSplit = length($user) / 2;

    push @splitAddress, NaturalDocs::NDMarkup-&gt;ConvertAmpChars( substr($user, 0, $userSplit) );
    push @splitAddress, NaturalDocs::NDMarkup-&gt;ConvertAmpChars( substr($user, $userSplit) );

    push @splitAddress, '@';

    my $hostSplit = length($host) / 2;

    push @splitAddress, NaturalDocs::NDMarkup-&gt;ConvertAmpChars( substr($host, 0, $hostSplit) );
    push @splitAddress, NaturalDocs::NDMarkup-&gt;ConvertAmpChars( substr($host, $hostSplit) );


    # Now put it back together again.  We'll use spans to split the text transparently and JavaScript to split and join the link.

    my $output =
    "&lt;a href=\"#\" onClick=\"location.href='mai' + 'lto:' + '" . join("' + '", @splitAddress) . "'; return false;\" class=LEMail&gt;";

    if ($name eq $target)
        {
        $output .=
        $splitAddress[0] . '&lt;span style="display: none"&gt;.nosp@m.&lt;/span&gt;' . $splitAddress[1]
        . '&lt;span&gt;@&lt;/span&gt;'
        . $splitAddress[3] . '&lt;span style="display: none"&gt;.nosp@m.&lt;/span&gt;' . $splitAddress[4];
        }
    else
        {  $output .= $name;  };

    $output .= '&lt;/a&gt;';
    return $output;
    };


#
#   Function: BuildImage
#
#   Builds the HTML for an image.
#
#   Parameters:
#
#       sourceFile - The source &lt;FileName&gt; this image appears in.
#       mode - Either "inline" or "link".
#       target - The target.
#       original - The original text.
#
#       All are assumed to still have &lt;NDMarkup&gt; amp chars.
#
#   Returns:
#
#       The result in HTML.  If the mode was "link", the target image's HTML is added to &lt;imageContent&gt;.
#
sub BuildImage #(sourceFile, mode, target, original)
    {
    my ($self, $sourceFile, $mode, $target, $original) = @_;

    my $targetNoAmp = $self-&gt;RestoreAmpChars($target);

    my $image = NaturalDocs::ImageReferenceTable-&gt;GetReferenceTarget($sourceFile, $targetNoAmp);

    if ($image)
        {
        my ($width, $height) = NaturalDocs::Project-&gt;ImageFileDimensions($image);

        if ($mode eq 'inline')
            {
            return
            '&lt;img src="' . $self-&gt;MakeRelativeURL($self-&gt;OutputFileOf($sourceFile),
                                                                       $self-&gt;OutputImageOf($image), 1) . '"'

            . ($width &amp;&amp; $height ? ' width="' . $width . '" height="' . $height . '"' : '')
            . '&gt;';
            }
        else # link
            {
            # Make the text a little more friendly in the output by removing any folders and file extensions.
            # (see images/Table1.gif) will be turned into (see Table1).
            my $originalNoAmp = $self-&gt;RestoreAmpChars($original);
            my $targetIndex = index($originalNoAmp, $targetNoAmp);
            my ($shortTarget, $shortTargetNoAmp, $shortOriginal);

            if ($targetIndex != -1)
                {
                $shortTargetNoAmp = (NaturalDocs::File-&gt;SplitPath($targetNoAmp))[2];
                $shortTargetNoAmp = NaturalDocs::File-&gt;NoExtension($shortTargetNoAmp);

                substr($originalNoAmp, $targetIndex, length($targetNoAmp), $shortTargetNoAmp);

                $shortOriginal = NaturalDocs::NDMarkup-&gt;ConvertAmpChars($originalNoAmp);
                $shortTarget = NaturalDocs::NDMarkup-&gt;ConvertAmpChars($shortTargetNoAmp);
                };

            my $output =
            '&lt;a href="#Image' . $imageAnchorNumber . '" class=CImageLink&gt;'
                . ($shortOriginal || $original)
            . '&lt;/a&gt;';

            $imageContent .=
            '&lt;blockquote&gt;'
            . '&lt;div class=CImage&gt;'
                . '&lt;a name="Image' . $imageAnchorNumber . '"&gt;&lt;/a&gt;'
                . '&lt;div class=CImageCaption&gt;' . ($shortTarget || $target) . '&lt;/div&gt;'
                . '&lt;img src="' . $self-&gt;MakeRelativeURL($self-&gt;OutputFileOf($sourceFile),
                                                                           $self-&gt;OutputImageOf($image), 1) . '"'

                . ($width &amp;&amp; $height ? ' width="' . $width . '" height="' . $height . '"' : '')
                . '&gt;'

            . '&lt;/div&gt;&lt;/blockquote&gt;';

            $imageAnchorNumber++;
            return $output;
            };
        }
    else # !$image
        {
        if ($mode eq 'inline')
            {  return '&lt;p&gt;' . $original . '&lt;/p&gt;';  }
        else #($mode eq 'link')
            {  return $original;  };
        };
    };


#
#   Function: BuildToolTipLinkProperties
#
#   Returns the properties that should go in the link tag to add a tooltip to it.  Because the function accepts undef, you can
#   call it without checking if &lt;BuildToolTip()&gt; returned undef or not.
#
#   Parameters:
#
#       toolTipID - The ID of the tooltip.  If undef, the function will return undef.
#
#   Returns:
#
#       The properties that should be put in the link tag, or undef if toolTipID wasn't specified.
#
sub BuildToolTipLinkProperties #(toolTipID)
    {
    my ($self, $toolTipID) = @_;

    if (defined $toolTipID)
        {
        my $currentNumber = $tooltipLinkNumber;
        $tooltipLinkNumber++;

        return 'id=link' . $currentNumber . ' '
                . 'onMouseOver="ShowTip(event, \'' . $toolTipID . '\', \'link' . $currentNumber . '\')" '
                . 'onMouseOut="HideTip(\'' . $toolTipID . '\')"';
        }
    else
        {  return undef;  };
    };


#
#   Function: AddDoubleSpaces
#
#   Adds second spaces after the appropriate punctuation with &amp;nbsp; so they show up in HTML.  They don't occur if there isn't at
#   least one space after the punctuation, so things like class.member notation won't be affected.
#
#   Parameters:
#
#       text - The text to convert.
#
#   Returns:
#
#       The text with double spaces as necessary.
#
sub AddDoubleSpaces #(text)
    {
    my ($self, $text) = @_;

    # Question marks and exclamation points get double spaces unless followed by a lowercase letter.

    $text =~ s/  ([^\ \t\r\n] [\!\?])  # Must appear after a non-whitespace character to apply.

                      (&amp;quot;|&amp;[lr][sd]quo;|[\'\"\]\}\)]?)  # Tolerate closing quotes, parenthesis, etc.
                      ((?:&lt;[^&gt;]+&gt;)*)  # Tolerate tags

                      \   # The space
                      (?![a-z])  # Not followed by a lowercase character.

                   /$1$2$3&amp;nbsp;\ /gx;


    # Periods get double spaces if it's not followed by a lowercase letter.  However, if it's followed by a capital letter and the
    # preceding word is in the list of acceptable abbreviations, it won't get the double space.  Yes, I do realize I am seriously
    # over-engineering this.

    $text =~ s/  ([^\ \t\r\n]+)  # The word prior to the period.

                      \.

                      (&amp;quot;|&amp;[lr][sd]quo;|[\'\"\]\}\)]?)  # Tolerate closing quotes, parenthesis, etc.
                      ((?:&lt;[^&gt;]+&gt;)*)  # Tolerate tags

                      \   # The space
                      ([^a-z])   # The next character, if it's not a lowercase letter.

                  /$1 . '.' . $2 . $3 . MaybeExpand($1, $4) . $4/gex;

    sub MaybeExpand #(leadWord, nextLetter)
        {
        my ($leadWord, $nextLetter) = @_;

        if ($nextLetter =~ /^[A-Z]$/ &amp;&amp; exists $abbreviations{ lc($leadWord) } )
            { return ' '; }
        else
            { return '&amp;nbsp; '; };
        };

    return $text;
    };


#
#   Function: ConvertAmpChars
#
#   Converts certain characters to their HTML amp char equivalents.
#
#   Parameters:
#
#       text - The text to convert.
#
#   Returns:
#
#       The converted text.
#
sub ConvertAmpChars #(text)
    {
    my ($self, $text) = @_;

    $text =~ s/&amp;/&amp;amp;/g;
    $text =~ s/\"/&amp;quot;/g;
    $text =~ s/&lt;/&amp;lt;/g;
    $text =~ s/&gt;/&amp;gt;/g;

    return $text;
    };


#
#   Function: RestoreAmpChars
#
#   Restores all amp characters to their original state.  This works with both &lt;NDMarkup&gt; amp chars and fancy quotes.
#
#   Parameters:
#
#       text - The text to convert.
#
#   Returns:
#
#       The converted text.
#
sub RestoreAmpChars #(text)
    {
    my ($self, $text) = @_;

    $text = NaturalDocs::NDMarkup-&gt;RestoreAmpChars($text);
    $text =~ s/&amp;[lr]squo;/\'/g;
    $text =~ s/&amp;[lr]dquo;/\"/g;

    return $text;
    };


#
#   Function: AddHiddenBreaks
#
#   Adds hidden breaks to symbols.  Puts them after symbol and directory separators so long names won't screw up the layout.
#
#   Parameters:
#
#       string - The string to break.
#
#   Returns:
#
#       The string with hidden breaks.
#
sub AddHiddenBreaks #(string)
    {
    my ($self, $string) = @_;

    # \.(?=.{5,}) instead of \. so file extensions don't get breaks.
    # :+ instead of :: because Mac paths are separated by a : and we want to get those too.

    $string =~ s/(\w(?:\.(?=.{5,})|:+|-&gt;|\\|\/))(\w)/$1 . '&lt;wbr&gt;' . $2/ge;

    return $string;
    };


#
#   Function: FindFirstFile
#
#   A function that finds and returns the first file entry in the menu, or undef if none.
#
sub FindFirstFile
    {
    # Hidden parameter: arrayref
    # Used for recursion only.

    my ($self, $arrayref) = @_;

    if (!defined $arrayref)
        {  $arrayref = NaturalDocs::Menu-&gt;Content();  };

    foreach my $entry (@$arrayref)
        {
        if ($entry-&gt;Type() == ::MENU_FILE())
            {
            return $entry;
            }
        elsif ($entry-&gt;Type() == ::MENU_GROUP())
            {
            my $result = $self-&gt;FindFirstFile($entry-&gt;GroupContent());
            if (defined $result)
                {  return $result;  };
            };
        };

    return undef;
    };


#
#   Function: ExpandMenu
#
#   Determines which groups should be expanded.
#
#   Parameters:
#
#       sourceFile - The source &lt;FileName&gt; to use if you're looking for a source file.
#       indexType - The index &lt;TopicType&gt; to use if you're looking for an index.
#       selectionHierarchy - The &lt;FileName&gt; the menu is being built for.  Does not have to be on the menu itself.
#       rootLength - The length of the menu's root group, *not* including the contents of subgroups.
#
#   Returns:
#
#       An arrayref of all the group numbers that should be expanded.  At minimum, it will contain the numbers of the groups
#       present in &lt;menuSelectionHierarchy&gt;, though it may contain more.
#
sub ExpandMenu #(FileName sourceFile, TopicType indexType, NaturalDocs::Menu::Entry[] selectionHierarchy, int rootLength) -&gt; int[] groupsToExpand
    {
    my ($self, $sourceFile, $indexType, $menuSelectionHierarchy, $rootLength) = @_;

    my $toExpand = [ ];


    # First expand everything in the selection hierarchy.

    my $length = $rootLength;

    foreach my $entry (@$menuSelectionHierarchy)
        {
        $length += $menuGroupLengths{$entry};
        push @$toExpand, $menuGroupNumbers{$entry};
        };


    # Now do multiple passes of group expansion as necessary.  We start from bottomIndex and expand outwards.  We stop going
    # in a direction if a group there is too long -- we do not skip over it and check later groups as well.  However, if one direction
    # stops, the other can keep going.

    my $pass = 1;
    my $hasSubGroups;

    while ($length &lt; MENU_LENGTH_LIMIT)
        {
        my $content;
        my $topIndex;
        my $bottomIndex;


        if ($pass == 1)
            {
            # First pass, we expand the selection's siblings.

            if (scalar @$menuSelectionHierarchy)
                {  $content = $menuSelectionHierarchy-&gt;[0]-&gt;GroupContent();  }
            else
                {  $content = NaturalDocs::Menu-&gt;Content();  };

            $bottomIndex = 0;

            while ($bottomIndex &lt; scalar @$content &amp;&amp;
                     !($content-&gt;[$bottomIndex]-&gt;Type() == ::MENU_FILE() &amp;&amp;
                       $content-&gt;[$bottomIndex]-&gt;Target() eq $sourceFile) &amp;&amp;
                     !($content-&gt;[$bottomIndex]-&gt;Type() != ::MENU_INDEX() &amp;&amp;
                       $content-&gt;[$bottomIndex]-&gt;Target() eq $indexType) )
                {  $bottomIndex++;  };

            if ($bottomIndex == scalar @$content)
                {  $bottomIndex = 0;  };
            $topIndex = $bottomIndex - 1;
            }

        elsif ($pass == 2)
            {
            # If the section we just expanded had no sub-groups, do another pass trying to expand the parent's sub-groups.  The
            # net effect is that groups won't collapse as much unnecessarily.  Someone can click on a file in a sub-group and the
            # groups in the parent will stay open.

            if (!$hasSubGroups &amp;&amp; scalar @$menuSelectionHierarchy)
                {
                if (scalar @$menuSelectionHierarchy &gt; 1)
                    {  $content = $menuSelectionHierarchy-&gt;[1]-&gt;GroupContent();  }
                else
                    {  $content = NaturalDocs::Menu-&gt;Content();  };

                $bottomIndex = 0;

                while ($bottomIndex &lt; scalar @$content &amp;&amp;
                         $content-&gt;[$bottomIndex] != $menuSelectionHierarchy-&gt;[0])
                    {  $bottomIndex++;  };

                $topIndex = $bottomIndex - 1;
                $bottomIndex++;  # Increment past our own group.
                $hasSubGroups = undef;
                }
            else
                {  last;  };
            }

        # No more passes.
        else
            {  last;  };


        while ( ($topIndex &gt;= 0 || $bottomIndex &lt; scalar @$content) &amp;&amp; $length &lt; MENU_LENGTH_LIMIT)
            {
            # We do the bottom first.

            while ($bottomIndex &lt; scalar @$content &amp;&amp; $content-&gt;[$bottomIndex]-&gt;Type() != ::MENU_GROUP())
                {  $bottomIndex++;  };

            if ($bottomIndex &lt; scalar @$content)
                {
                my $bottomEntry = $content-&gt;[$bottomIndex];
                $hasSubGroups = 1;

                if ($length + $menuGroupLengths{$bottomEntry} &lt;= MENU_LENGTH_LIMIT)
                    {
                    $length += $menuGroupLengths{$bottomEntry};
                    push @$toExpand, $menuGroupNumbers{$bottomEntry};
                    $bottomIndex++;
                    }
                else
                    {  $bottomIndex = scalar @$content;  };
                };

            # Top next.

            while ($topIndex &gt;= 0 &amp;&amp; $content-&gt;[$topIndex]-&gt;Type() != ::MENU_GROUP())
                {  $topIndex--;  };

            if ($topIndex &gt;= 0)
                {
                my $topEntry = $content-&gt;[$topIndex];
                $hasSubGroups = 1;

                if ($length + $menuGroupLengths{$topEntry} &lt;= MENU_LENGTH_LIMIT)
                    {
                    $length += $menuGroupLengths{$topEntry};
                    push @$toExpand, $menuGroupNumbers{$topEntry};
                    $topIndex--;
                    }
                else
                    {  $topIndex = -1;  };
                };
            };


        $pass++;
        };

    return $toExpand;
    };


#
#   Function: GetMenuSelectionHierarchy
#
#   Finds the sequence of menu groups that contain the current selection.
#
#   Parameters:
#
#       sourceFile - The source &lt;FileName&gt; to use if you're looking for a source file.
#       indexType - The index &lt;TopicType&gt; to use if you're looking for an index.
#
#   Returns:
#
#       An arrayref of the &lt;NaturalDocs::Menu::Entry&gt; objects of each group surrounding the selected menu item.  First entry is the
#       group immediately encompassing it, and each subsequent entry works its way towards the outermost group.
#
sub GetMenuSelectionHierarchy #(FileName sourceFile, TopicType indexType) -&gt; NaturalDocs::Menu::Entry[] selectionHierarchy
    {
    my ($self, $sourceFile, $indexType) = @_;

    my $hierarchy = [ ];

    $self-&gt;FindMenuSelection($sourceFile, $indexType, $hierarchy, NaturalDocs::Menu-&gt;Content());

    return $hierarchy;
    };


#
#   Function: FindMenuSelection
#
#   A recursive function that deterimes if it or any of its sub-groups has the menu selection.
#
#   Parameters:
#
#       sourceFile - The source &lt;FileName&gt; to use if you're looking for a source file.
#       indexType - The index &lt;TopicType&gt; to use if you're looking for an index.
#       hierarchyRef - A reference to the menu selection hierarchy.
#       entries - An arrayref of &lt;NaturalDocs::Menu::Entries&gt; to search.
#
#   Returns:
#
#       Whether this group or any of its subgroups had the selection.  If true, it will add any subgroups to the menu selection
#       hierarchy but not itself.  This prevents the topmost entry from being added.
#
sub FindMenuSelection #(FileName sourceFile, TopicType indexType, NaturalDocs::Menu::Entry[] hierarchyRef, NaturalDocs::Menu::Entry[] entries) -&gt; bool hasSelection
    {
    my ($self, $sourceFile, $indexType, $hierarchyRef, $entries) = @_;

    foreach my $entry (@$entries)
        {
        if ($entry-&gt;Type() == ::MENU_GROUP())
            {
            # If the subgroup has the selection...
            if ( $self-&gt;FindMenuSelection($sourceFile, $indexType, $hierarchyRef, $entry-&gt;GroupContent()) )
                {
                push @$hierarchyRef, $entry;
                return 1;
                };
            }

        elsif ($entry-&gt;Type() == ::MENU_FILE())
            {
            if ($sourceFile eq $entry-&gt;Target())
                {  return 1;  };
            }

        elsif ($entry-&gt;Type() == ::MENU_INDEX())
            {
            if ($indexType eq $entry-&gt;Target)
                {  return 1;  };
            };
        };

    return 0;
    };


#
#   Function: ResetToolTips
#
#   Resets the &lt;ToolTip Package Variables&gt; for a new page.
#
#   Parameters:
#
#       samePage  - Set this flag if there's the possibility that the next batch of tooltips may be on the same page as the last.
#
sub ResetToolTips #(samePage)
    {
    my ($self, $samePage) = @_;

    if (!$samePage)
        {
        $tooltipLinkNumber = 1;
        $tooltipNumber = 1;
        };

    $tooltipHTML = undef;
    %tooltipSymbolsToNumbers = ( );
    };


1;
</pre></body></html>