diff options
| author | Magnus Auvinen <magnus.auvinen@gmail.com> | 2007-05-22 15:06:55 +0000 |
|---|---|---|
| committer | Magnus Auvinen <magnus.auvinen@gmail.com> | 2007-05-22 15:06:55 +0000 |
| commit | 9ba8e6cf38da5196ed7bc878fe452952f3e10638 (patch) | |
| tree | ea59837e22970274abac0ece2071e79189256901 /docs/doctool/Modules/NaturalDocs/Builder | |
| parent | 90bcda3c10411ee4c1c65a494ec7c08dfdea01b4 (diff) | |
| download | zcatch-9ba8e6cf38da5196ed7bc878fe452952f3e10638.tar.gz zcatch-9ba8e6cf38da5196ed7bc878fe452952f3e10638.zip | |
moved docs
Diffstat (limited to 'docs/doctool/Modules/NaturalDocs/Builder')
| -rw-r--r-- | docs/doctool/Modules/NaturalDocs/Builder/Base.pm | 316 | ||||
| -rw-r--r-- | docs/doctool/Modules/NaturalDocs/Builder/FramedHTML.pm | 294 | ||||
| -rw-r--r-- | docs/doctool/Modules/NaturalDocs/Builder/HTML.pm | 363 | ||||
| -rw-r--r-- | docs/doctool/Modules/NaturalDocs/Builder/HTMLBase.pm | 3075 |
4 files changed, 4048 insertions, 0 deletions
diff --git a/docs/doctool/Modules/NaturalDocs/Builder/Base.pm b/docs/doctool/Modules/NaturalDocs/Builder/Base.pm new file mode 100644 index 00000000..2d6cf468 --- /dev/null +++ b/docs/doctool/Modules/NaturalDocs/Builder/Base.pm @@ -0,0 +1,316 @@ +############################################################################### +# +# Class: NaturalDocs::Builder::Base +# +############################################################################### +# +# A base class for all Builder output formats. +# +############################################################################### + +# This file is part of Natural Docs, which is Copyright (C) 2003-2005 Greg Valure +# Natural Docs is licensed under the GPL + +use strict; +use integer; + +package NaturalDocs::Builder::Base; + + +############################################################################### +# Group: Notes + + +# +# Topic: Implementation +# +# Builder packages are implemented as blessed arrayrefs, not hashrefs. This is done for all objects in Natural Docs for +# efficiency reasons. You create members by defining constants via <NaturalDocs::DefineMembers> and using them as +# indexes into the array. +# + +# +# Topic: Function Order +# +# The functions in the build process will always be called in the following order. +# +# - <BeginBuild()> will always be called. +# - <PurgeFiles()> will be called next only if there's files that need to be purged. +# - <PurgeIndexes()> will be called next only if there's indexes that need to be purged. +# - <BuildFile()> will be called once for each file that needs to be built, if any. +# - <BuildIndex()> will be called once for each index that changed and is part of the menu, if any. +# - <UpdateMenu()> will be called next only if the menu changed. +# - <EndBuild()> will always be called. +# + +# +# Topic: How to Approach +# +# Here's an idea of how to approach making packages for different output types. +# +# +# Multiple Output Files, Embedded Menu: +# +# This example is for when you want to build one output file per source file, each with its own copy of the menu within it. +# This is how <NaturalDocs::Builder::HTML> works. +# +# Make sure you create a function that generates just the menu for a particular source file. We'll need to generate menus for +# both building a file from scratch and for updating the menu on an existing output file, so it's better to give it its own function. +# You may want to surround it with something that can be easily detected in the output file to make replacing easier. +# +# <BeginBuild()> isn't important. You don't need to implement it. +# +# Implement <PurgeFiles()> to delete the output files associated with the purged files. +# +# Implement <PurgeIndexes()> to delete the output files associated with the purged indexes. +# +# Implement <BuildFile()> to create an output file for the parsed source file. Use the menu function described earlier. +# +# Implement <BuildIndex()> to create an output file for each index. Use the menu function described earlier for each page. +# +# Implement <UpdateMenu()> to go through the list of unbuilt files and update their menus. You can get the list from +# <NaturalDocs::Project->UnbuiltFilesWithContent()>. You need to open their output files, replace the menu, and save it back +# to disk. Yes, it would be simpler from a programmer's point of view to just rebuild the file completely, but that would be +# _very_ inefficient since there could potentially be a _lot_ of files in this group. +# +# Also make sure <UpdateMenu()> goes through the unchanged indexes and updates them as well. +# +# <EndBuild()> isn't important. You don't need to implement it. +# +# +# Multiple Output Files, Menu in File: +# +# This example is for when you want to build one output file per source file, but keep the menu in its own separate file. This +# is how <NaturalDocs::Builder::FramedHTML> works. +# +# <BeginBuild()> isn't important. You don't need to implement it. +# +# Implement <PurgeFiles()> to delete the output files associated with the purged files. +# +# Implement <PurgeIndexes()> to delete the output files associated with the purged indexes. +# +# Implement <BuildFile()> to generate an output file from the parsed source file. +# +# Implement <BuildIndex()> to generate an output file for each index. +# +# Implement <UpdateMenu()> to rebuild the menu file. +# +# <EndBuild()> isn't important. You don't need to implement it. +# +# +# Single Output File using Intermediate Files: +# +# This example is for when you want to build one output file, such as a PDF file, but use intermediate files to handle differential +# building. This would be much like how a compiler compiles each source file into a object file, and then a linker stitches them +# all together into the final executable file. +# +# <BeginBuild()> isn't important. You don't need to implement it. +# +# Implement <PurgeFiles()> to delete the intermediate files associated with the purged files. +# +# Implement <PurgeIndexes()> to delete the intermediate files associated with the purged indexes. +# +# Implement <BuildFile()> to generate an intermediate file from the parsed source file. +# +# Implement <BuildIndex()> to generate an intermediate file for the specified index. +# +# Implement <UpdateMenu()> to generate the intermediate file for the menu. +# +# Implement <EndBuild()> so that if the project changed, it stitches the intermediate files together into the final +# output file. Make sure you check the parameter because the function will be called when nothing changes too. +# +# +# Single Output File using Direct Changes: +# +# This example is for when you want to build one output file, such as a PDF file, but engineering it in such a way that you don't +# need to use intermediate files. In other words, you're able to add, delete, and modify entries directly in the output file. +# +# Implement <BeginBuild()> so that if the project changed, it opens the output file and does anything it needs to do +# to get ready for editing. +# +# Implement <PurgeFiles()> to remove the entries associated with the purged files. +# +# Implement <PurgeIndexes()> to remove the entries associated with the purged indexes. +# +# Implement <BuildFile()> to add or replace a section of the output file with a new one generated from the parsed file. +# +# Implement <BuildIndex()> to add or replace an index in the output file with a new one generated from the specified index. +# +# Implement <EndBuild()> so that if the project changed, it saves the output file to disk. +# +# How you handle the menu depends on how the output file references other sections of itself. If it can do so by name, then +# you can implement <UpdateMenu()> to update the menu section of the file and you're done. If it has to reference itself +# by address or offset, it gets trickier. You should skip <UpdateMenu()> and instead rebuild the menu in <EndBuild()> if +# the parameter is true. This lets you do it whenever anything changes in a file, rather than just when the menu +# visibly changes. How you keep track of the locations and how they change is your problem. +# + + +############################################################################### +# +# Group: Required Interface Functions +# +# All Builder classes *must* define these functions. +# + + +# +# Function: INIT +# +# Define this function to call <NaturalDocs::Builder->Add()> so that <NaturalDocs::Builder> knows about this package. +# Packages are defined this way so that new ones can be added without messing around in other code. +# + + +# +# Function: CommandLineOption +# +# Define this function to return the text that should be put in the command line after -o to use this package. It cannot have +# spaces and is not case sensitive. +# +# For example, <NaturalDocs::Builder::HTML> returns 'html' so someone could use -o html [directory] to use that package. +# +sub CommandLineOption + { + NaturalDocs::Error->SoftDeath($_[0] . " didn't define CommandLineOption()."); + }; + + +# +# Function: BuildFile +# +# Define this function to convert a parsed file to this package's output format. This function will be called once for every source +# file that needs to be rebuilt. However, if a file hasn't changed since the last time Natural Docs was run, it will not be sent to +# this function. All packages must support differential build. +# +# Parameters: +# +# sourceFile - The name of the source file. +# parsedFile - The parsed source file, as an arrayref of <NaturalDocs::Parser::ParsedTopic> objects. +# +sub BuildFile #(sourceFile, parsedFile) + { + NaturalDocs::Error->SoftDeath($_[0] . " didn't define BuildFile()."); + }; + + +############################################################################### +# +# Group: Optional Interface Functions +# +# These functions can be implemented but packages are not required to do so. +# + + +# +# Function: New +# +# Creates and returns a new object. +# +# Note that this is the only function where the first parameter will be the package name, not the object itself. +# +sub New + { + my $package = shift; + + my $object = [ ]; + bless $object, $package; + + return $object; + }; + + +# +# Function: BeginBuild +# +# Define this function if the package needs to do anything at the beginning of the build process. This function will be called +# every time Natural Docs is run, even if the project hasn't changed. This allows you to manage dependencies specific +# to the output format that may change independently from the source tree and menu. For example, +# <NaturalDocs::Builder::HTML> needs to keep the CSS files in sync regardless of whether the source tree changed or not. +# +# Parameters: +# +# hasChanged - Whether the project has changed, such as source files or the menu file. If false, nothing else is going to be +# called except <EndBuild()>. +# +sub BeginBuild #(hasChanged) + { + }; + + +# +# Function: EndBuild +# +# Define this function if the package needs to do anything at the end of the build process. This function will be called every time +# Natural Docs is run, even if the project hasn't changed. This allows you to manage dependencies specific to the output +# format that may change independently from the source tree. For example, <NaturalDocs::Builder::HTML> needs to keep the +# CSS files in sync regardless of whether the source tree changed or not. +# +# Parameters: +# +# hasChanged - Whether the project has changed, such as source files or the menu file. If false, the only other function that +# was called was <BeginBuild()>. +# +sub EndBuild #(hasChanged) + { + }; + + +# +# Function: BuildIndex +# +# Define this function to create an index for the passed topic. You can get the index from +# <NaturalDocs::SymbolTable->Index()>. +# +# The reason it's not passed directly to this function is because indexes may be time-consuming to create. As such, they're +# generated on demand because some output packages may choose not to implement them. +# +# Parameters: +# +# topic - The <TopicType> to limit the index by. +# +sub BuildIndex #(topic) + { + }; + +# +# Function: PurgeFiles +# +# Define this function to make the package remove all output related to the passed files. These files no longer have Natural Docs +# content. +# +# Parameters: +# +# files - An existence hashref of the files to purge. +# +sub PurgeFiles #(files) + { + }; + + +# +# Function: PurgeIndexes +# +# Define this function to make the package remove all output related to the passed indexes. These indexes are no longer part +# of the menu. +# +# Parameters: +# +# indexes - An existence hashref of the <TopicTypes> of the indexes to purge. +# +sub PurgeIndexes #(indexes) + { + }; + + +# +# Function: UpdateMenu +# +# Define this function to make the package update the menu. It will only be called if the menu changed. +# +sub UpdateMenu + { + }; + + +1; diff --git a/docs/doctool/Modules/NaturalDocs/Builder/FramedHTML.pm b/docs/doctool/Modules/NaturalDocs/Builder/FramedHTML.pm new file mode 100644 index 00000000..7b615e4b --- /dev/null +++ b/docs/doctool/Modules/NaturalDocs/Builder/FramedHTML.pm @@ -0,0 +1,294 @@ +############################################################################### +# +# Package: NaturalDocs::Builder::FramedHTML +# +############################################################################### +# +# A package that generates output in HTML with frames. +# +# All functions are called with Package->Function() notation. +# +############################################################################### + +# This file is part of Natural Docs, which is Copyright (C) 2003-2005 Greg Valure +# Natural Docs is licensed under the GPL + + +use strict; +use integer; + +package NaturalDocs::Builder::FramedHTML; + +use base 'NaturalDocs::Builder::HTMLBase'; + + +############################################################################### +# Group: Implemented Interface Functions + + +# +# Function: INIT +# +# Registers the package with <NaturalDocs::Builder>. +# +sub INIT + { + NaturalDocs::Builder->Add(__PACKAGE__); + }; + + +# +# Function: CommandLineOption +# +# Returns the option to follow -o to use this package. In this case, "html". +# +sub CommandLineOption + { + return 'FramedHTML'; + }; + + +# +# Function: BuildFile +# +# Builds the output file from the parsed source file. +# +# Parameters: +# +# sourcefile - The <FileName> of the source file. +# parsedFile - An arrayref of the source file as <NaturalDocs::Parser::ParsedTopic> objects. +# +sub BuildFile #(sourceFile, parsedFile) + { + my ($self, $sourceFile, $parsedFile) = @_; + + my $outputFile = $self->OutputFileOf($sourceFile); + + + # 99.99% of the time the output directory will already exist, so this will actually be more efficient. It only won't exist + # if a new file was added in a new subdirectory and this is the first time that file was ever parsed. + if (!open(OUTPUTFILEHANDLE, '>' . $outputFile)) + { + NaturalDocs::File->CreatePath( NaturalDocs::File->NoFileName($outputFile) ); + + open(OUTPUTFILEHANDLE, '>' . $outputFile) + or die "Couldn't create output file " . $outputFile . "\n"; + }; + + print OUTPUTFILEHANDLE + + + + # IE 6 doesn't like any doctype here at all. Add one (strict or transitional doesn't matter) and it makes the page slightly too + # wide for the frame. Mozilla and Opera handle it like champs either way because they Don't Suck(tm). + + # '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ' + # . '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n" + + '<html><head>' + + . (NaturalDocs::Settings->CharSet() ? + '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '') + + . '<title>' + . $self->BuildTitle($sourceFile) + . '</title>' + + . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($outputFile, $self->MainCSSFile(), 1) . '">' + + . '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->MainJavaScriptFile(), 1) . '"></script>' + + . '</head><body class=FramedContentPage onLoad="NDOnLoad()">' + . $self->OpeningBrowserStyles() + + . $self->StandardComments() + + . $self->BuildContent($sourceFile, $parsedFile) + + . $self->BuildToolTips() + + . $self->ClosingBrowserStyles() + . '</body></html>'; + + + close(OUTPUTFILEHANDLE); + }; + + +# +# Function: BuildIndex +# +# Builds an index for the passed type. +# +# Parameters: +# +# type - The <TopicType> to limit the index to, or undef if none. +# +sub BuildIndex #(type) + { + my ($self, $type) = @_; + + my $indexTitle = $self->IndexTitleOf($type); + my $indexFile = $self->IndexFileOf($type); + + my $startPage = + + '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ' + . '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n" + + . '<html><head>' + + . (NaturalDocs::Settings->CharSet() ? + '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '') + + . '<title>'; + + if (defined NaturalDocs::Menu->Title()) + { $startPage .= $self->StringToHTML(NaturalDocs::Menu->Title()) . ' - '; }; + + $startPage .= + $indexTitle + . '</title>' + + . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($indexFile, $self->MainCSSFile(), 1) . '">' + + . '<script language=JavaScript src="' . $self->MakeRelativeURL($indexFile, $self->MainJavaScriptFile(), 1) . '"></script>' + + . '</head><body class=FramedIndexPage onLoad="NDOnLoad()">' + . $self->OpeningBrowserStyles() + + . $self->StandardComments() + + . '<div class=IPageTitle>' + . $indexTitle + . '</div>'; + + + my $endPage = $self->ClosingBrowserStyles() . '</body></html>'; + + + my $pageCount = $self->BuildIndexPages($type, NaturalDocs::SymbolTable->Index($type), $startPage, $endPage); + $self->PurgeIndexFiles($type, $pageCount + 1); + }; + + +# +# Function: UpdateMenu +# +# Builds the menu file. Also generates index.html. +# +sub UpdateMenu + { + my $self = shift; + + my $outputDirectory = NaturalDocs::Settings->OutputDirectoryOf($self); + my $outputFile = NaturalDocs::File->JoinPaths($outputDirectory, 'menu.html'); + + + open(OUTPUTFILEHANDLE, '>' . $outputFile) + or die "Couldn't create output file " . $outputFile . "\n"; + + my $title = 'Menu'; + if (defined $title) + { $title .= ' - ' . NaturalDocs::Menu->Title(); }; + + $title = $self->StringToHTML($title); + + + print OUTPUTFILEHANDLE + + + '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ' + . '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n" + + . '<html><head>' + + . (NaturalDocs::Settings->CharSet() ? + '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '') + + . '<title>' + . $title + . '</title>' + + . '<base target="Content">' + + . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($outputFile, $self->MainCSSFile(), 1) . '">' + + . '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->MainJavaScriptFile(), 1) . '"></script>' + + . '</head><body class=FramedMenuPage onLoad="NDOnLoad()">' + . $self->OpeningBrowserStyles() + + . $self->StandardComments() + + . $self->BuildMenu(undef, undef, 1) + + . '<div class=Footer>' + . $self->BuildFooter() + . '</div>' + + . $self->ClosingBrowserStyles() + . '</body></html>'; + + + close(OUTPUTFILEHANDLE); + + + # Update index.html + + my $firstMenuEntry = $self->FindFirstFile(); + my $indexFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index.html' ); + + # We have to check because it's possible that there may be no files with Natural Docs content and thus no files on the menu. + if (defined $firstMenuEntry) + { + open(INDEXFILEHANDLE, '>' . $indexFile) + or die "Couldn't create output file " . $indexFile . ".\n"; + + print INDEXFILEHANDLE + + '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" ' + . '"http://www.w3.org/TR/REC-html40/frameset.dtd">' + + . '<html>' + + . '<head>' + + . (NaturalDocs::Settings->CharSet() ? + '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '') + + . '<title>' + . $self->StringToHTML(NaturalDocs::Menu->Title()) + . '</title>' + + . '</head>' + + . $self->StandardComments() + + . '<frameset cols="185,*">' + . '<frame name=Menu src="menu.html">' + . '<frame name=Content src="' + . $self->MakeRelativeURL($indexFile, $self->OutputFileOf($firstMenuEntry->Target()), 1) . '">' + . '</frameset>' + + . '<noframes>' + . 'This documentation was designed for use with frames. However, you can still use it by ' + . '<a href="menu.html">starting from the menu page</a>.' + . "<script language=JavaScript><!--\n" + . 'location.href="menu.html";' + . "\n// --></script>" + . '</noframes>' + + . '</html>'; + + close INDEXFILEHANDLE; + } + + elsif (-e $indexFile) + { + unlink($indexFile); + }; + }; + + +1; diff --git a/docs/doctool/Modules/NaturalDocs/Builder/HTML.pm b/docs/doctool/Modules/NaturalDocs/Builder/HTML.pm new file mode 100644 index 00000000..92b4bd7f --- /dev/null +++ b/docs/doctool/Modules/NaturalDocs/Builder/HTML.pm @@ -0,0 +1,363 @@ +############################################################################### +# +# Package: NaturalDocs::Builder::HTML +# +############################################################################### +# +# A package that generates output in HTML. +# +# All functions are called with Package->Function() notation. +# +############################################################################### + +# This file is part of Natural Docs, which is Copyright (C) 2003-2005 Greg Valure +# Natural Docs is licensed under the GPL + + +use strict; +use integer; + +package NaturalDocs::Builder::HTML; + +use base 'NaturalDocs::Builder::HTMLBase'; + + +############################################################################### +# Group: Implemented Interface Functions + + +# +# Function: INIT +# +# Registers the package with <NaturalDocs::Builder>. +# +sub INIT + { + NaturalDocs::Builder->Add(__PACKAGE__); + }; + + +# +# Function: CommandLineOption +# +# Returns the option to follow -o to use this package. In this case, "html". +# +sub CommandLineOption + { + return 'HTML'; + }; + + +# +# Function: BuildFile +# +# Builds the output file from the parsed source file. +# +# Parameters: +# +# sourcefile - The <FileName> of the source file. +# parsedFile - An arrayref of the source file as <NaturalDocs::Parser::ParsedTopic> objects. +# +sub BuildFile #(sourceFile, parsedFile) + { + my ($self, $sourceFile, $parsedFile) = @_; + + my $outputFile = $self->OutputFileOf($sourceFile); + + + # 99.99% of the time the output directory will already exist, so this will actually be more efficient. It only won't exist + # if a new file was added in a new subdirectory and this is the first time that file was ever parsed. + if (!open(OUTPUTFILEHANDLE, '>' . $outputFile)) + { + NaturalDocs::File->CreatePath( NaturalDocs::File->NoFileName($outputFile) ); + + open(OUTPUTFILEHANDLE, '>' . $outputFile) + or die "Couldn't create output file " . $outputFile . "\n"; + }; + + print OUTPUTFILEHANDLE + + + '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" ' + . '"http://www.w3.org/TR/REC-html40/strict.dtd">' . "\n\n" + + . '<html><head>' + + . (NaturalDocs::Settings->CharSet() ? + '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '') + + . '<title>' + . $self->BuildTitle($sourceFile) + . '</title>' + + . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($outputFile, $self->MainCSSFile(), 1) . '">' + + . '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->MainJavaScriptFile(), 1) . '"></script>' + + . '</head><body class=UnframedPage onLoad="NDOnLoad()">' + . $self->OpeningBrowserStyles() + + . $self->StandardComments() + + # I originally had this part done in CSS, but there were too many problems. Back to good old HTML tables. + . '<table border=0 cellspacing=0 cellpadding=0 width=100%><tr>' + + . '<td class=MenuSection valign=top>' + + . $self->BuildMenu($sourceFile, undef, undef) + + . '</td>' . "\n\n" + + . '<td class=ContentSection valign=top>' + + . $self->BuildContent($sourceFile, $parsedFile) + + . '</td>' . "\n\n" + + . '</tr></table>' + + . '<div class=Footer>' + . $self->BuildFooter() + . '</div>' + + . $self->BuildToolTips() + + . $self->ClosingBrowserStyles() + . '</body></html>'; + + + close(OUTPUTFILEHANDLE); + }; + + +# +# Function: BuildIndex +# +# Builds an index for the passed type. +# +# Parameters: +# +# type - The <TopicType> to limit the index to, or undef if none. +# +sub BuildIndex #(type) + { + my ($self, $type) = @_; + + my $indexTitle = $self->IndexTitleOf($type); + my $indexFile = $self->IndexFileOf($type); + + my $startPage = + + '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" ' + . '"http://www.w3.org/TR/REC-html40/strict.dtd">' . "\n\n" + + . '<html><head>' + + . (NaturalDocs::Settings->CharSet() ? + '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '') + + . '<title>' + . $indexTitle; + + if (defined NaturalDocs::Menu->Title()) + { $startPage .= ' - ' . $self->StringToHTML(NaturalDocs::Menu->Title()); }; + + $startPage .= + '</title>' + + . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($indexFile, $self->MainCSSFile(), 1) . '">' + + . '<script language=JavaScript src="' . $self->MakeRelativeURL($indexFile, $self->MainJavaScriptFile(), 1) . '"></script>' + + . '</head><body class=UnframedPage onLoad="NDOnLoad()">' + . $self->OpeningBrowserStyles() + + . $self->StandardComments() + + # I originally had this part done in CSS, but there were too many problems. Back to good old HTML tables. + . '<table border=0 cellspacing=0 cellpadding=0 width=100%><tr>' + + . '<td class=MenuSection valign=top>' + + . $self->BuildMenu(undef, $type, undef) + + . '</td>' + + . '<td class=IndexSection valign=top>' + . '<div class=IPageTitle>' + . $indexTitle + . '</div>'; + + + my $endPage = + '</td>' + + . '</tr></table>' + + . '<div class=Footer>' + . $self->BuildFooter() + . '</div>' + + . $self->ClosingBrowserStyles() + . '</body></html>'; + + + my $pageCount = $self->BuildIndexPages($type, NaturalDocs::SymbolTable->Index($type), $startPage, $endPage); + $self->PurgeIndexFiles($type, $pageCount + 1); + }; + + +# +# Function: UpdateMenu +# +# Updates the menu in all the output files that weren't rebuilt. Also generates index.html. +# +sub UpdateMenu + { + my $self = shift; + + + # Update the menu on unbuilt files. + + my $filesToUpdate = NaturalDocs::Project->UnbuiltFilesWithContent(); + + foreach my $sourceFile (keys %$filesToUpdate) + { + $self->UpdateFile($sourceFile); + }; + + + # Update the menu on unchanged index files. + + my $indexes = NaturalDocs::Menu->Indexes(); + + foreach my $index (keys %$indexes) + { + if (!NaturalDocs::SymbolTable->IndexChanged($index)) + { + $self->UpdateIndex($index); + }; + }; + + + # Update index.html + + my $firstMenuEntry = $self->FindFirstFile(); + my $indexFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index.html' ); + + # We have to check because it's possible that there may be no files with Natural Docs content and thus no files on the menu. + if (defined $firstMenuEntry) + { + open(INDEXFILEHANDLE, '>' . $indexFile) + or die "Couldn't create output file " . $indexFile . ".\n"; + + print INDEXFILEHANDLE + '<html><head>' + . '<meta http-equiv="Refresh" CONTENT="0; URL=' + . $self->MakeRelativeURL( NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index.html'), + $self->OutputFileOf($firstMenuEntry->Target()), 1 ) . '">' + . '</head></html>'; + + close INDEXFILEHANDLE; + } + + elsif (-e $indexFile) + { + unlink($indexFile); + }; + }; + + + +############################################################################### +# Group: Support Functions + + +# +# Function: UpdateFile +# +# Updates an output file. Replaces the menu, HTML title, and footer. It opens the output file, makes the changes, and saves it +# back to disk, which is much quicker than rebuilding the file from scratch if these were the only things that changed. +# +# Parameters: +# +# sourceFile - The source <FileName>. +# +sub UpdateFile #(sourceFile) + { + my ($self, $sourceFile) = @_; + + my $outputFile = $self->OutputFileOf($sourceFile); + + if (open(OUTPUTFILEHANDLE, '<' . $outputFile)) + { + my $content; + + read(OUTPUTFILEHANDLE, $content, -s OUTPUTFILEHANDLE); + close(OUTPUTFILEHANDLE); + + + $content =~ s{<title>[^<]*<\/title>}{'<title>' . $self->BuildTitle($sourceFile) . '</title>'}e; + + $content =~ s/<!--START_ND_MENU-->.*?<!--END_ND_MENU-->/$self->BuildMenu($sourceFile, undef, undef)/es; + + $content =~ s/<!--START_ND_FOOTER-->.*?<!--END_ND_FOOTER-->/$self->BuildFooter()/e; + + + open(OUTPUTFILEHANDLE, '>' . $outputFile); + print OUTPUTFILEHANDLE $content; + close(OUTPUTFILEHANDLE); + }; + }; + + +# +# Function: UpdateIndex +# +# Updates an index's output file. Replaces the menu and footer. It opens the output file, makes the changes, and saves it +# back to disk, which is much quicker than rebuilding the file from scratch if these were the only things that changed. +# +# Parameters: +# +# type - The index <TopicType>, or undef if none. +# +sub UpdateIndex #(type) + { + my ($self, $type) = @_; + + my $page = 1; + + my $outputFile = $self->IndexFileOf($type, $page); + + my $newMenu = $self->BuildMenu(undef, $type, undef); + my $newFooter = $self->BuildFooter(); + + while (-e $outputFile) + { + open(OUTPUTFILEHANDLE, '<' . $outputFile) + or die "Couldn't open output file " . $outputFile . ".\n"; + + my $content; + + read(OUTPUTFILEHANDLE, $content, -s OUTPUTFILEHANDLE); + close(OUTPUTFILEHANDLE); + + + $content =~ s/<!--START_ND_MENU-->.*?<!--END_ND_MENU-->/$newMenu/es; + + $content =~ s/<div class=Footer>.*<\/div>/"<div class=Footer>" . $newFooter . "<\/div>"/e; + + + open(OUTPUTFILEHANDLE, '>' . $outputFile) + or die "Couldn't save output file " . $outputFile . ".\n"; + + print OUTPUTFILEHANDLE $content; + close(OUTPUTFILEHANDLE); + + $page++; + $outputFile = $self->IndexFileOf($type, $page); + }; + }; + + +1; diff --git a/docs/doctool/Modules/NaturalDocs/Builder/HTMLBase.pm b/docs/doctool/Modules/NaturalDocs/Builder/HTMLBase.pm new file mode 100644 index 00000000..52653a90 --- /dev/null +++ b/docs/doctool/Modules/NaturalDocs/Builder/HTMLBase.pm @@ -0,0 +1,3075 @@ +############################################################################### +# +# Package: NaturalDocs::Builder::HTMLBase +# +############################################################################### +# +# A base package for all the shared functionality in <NaturalDocs::Builder::HTML> and +# <NaturalDocs::Builder::FramedHTML>. +# +############################################################################### + +# This file is part of Natural Docs, which is Copyright (C) 2003-2005 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'; + + +############################################################################### +# 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 <AddDoubleSpaces()> won't put a second space +# after when followed by period-whitespace-capital letter. Yes, this is seriously over-engineered. +# +my %abbreviations = ( mr => 1, mrs => 1, ms => 1, dr => 1, + rev => 1, fr => 1, 'i.e' => 1, + maj => 1, gen => 1, pres => 1, sen => 1, rep => 1, + n => 1, s => 1, e => 1, w => 1, ne => 1, se => 1, nw => 1, sw => 1 ); + +# +# array: indexHeadings +# +# An array of the headings of all the index sections. First is for symbols, second for numbers, and the rest for each letter. +# +my @indexHeadings = ( '$#!', '0-9', 'A' .. 'Z' ); + +# +# array: indexAnchors +# +# An array of the HTML anchors of all the index sections. First is for symbols, second for numbers, and the rest for each letter. +# +my @indexAnchors = ( 'Symbols', 'Numbers', 'A' .. 'Z' ); + +# +# 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 <StringToHTML()> is clearer in the calling code. +# +use constant ADD_HIDDEN_BREAKS => 1; + + +############################################################################### +# Group: ToolTip Package Variables +# +# These variables are for the tooltip generation functions only. Since they're reset on every call to <BuildContent()> and +# <BuildIndexPages()>, 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 <BuildMenu()> 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 <menuGroupNumber>, +# <menuLength>, <menuGroupLengths>, and <menuGroupNumbers>, and <menuRootLength>. +# +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 <Menu Length Constants>. +# +my $menuLength; + + +# +# hash: menuGroupLengths +# +# A hash of the length of each group, *not* including any subgroup contents. The keys are references to each groups' +# <NaturalDocs::Menu::Entry> object, and the values are their lengths computed from the <Menu Length Constants>. +# +my %menuGroupLengths; +tie %menuGroupLengths, 'Tie::RefHash'; + + +# +# hash: menuGroupNumbers +# +# A hash of the number of each group, as managed by <menuGroupNumber>. The keys are references to each groups' +# <NaturalDocs::Menu::Entry> 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 <Menu Length Constants>. +# +my $menuRootLength; + + +# +# constants: Menu Length Constants +# +# Constants used to approximate the lengths of the menu or its groups. +# +# MENU_TITLE_LENGTH - The length of the title. +# MENU_SUBTITLE_LENGTH - The length of the subtitle. +# MENU_FILE_LENGTH - The length of one file entry. +# MENU_GROUP_LENGTH - The length of one group entry. +# MENU_TEXT_LENGTH - The length of one text entry. +# MENU_LINK_LENGTH - The length of one link entry. +# +# MENU_LENGTH_LIMIT - The limit of the menu's length. If the total length surpasses this limit, groups that aren't required +# to be open to show the selection will default to closed on browsers that support it. +# +use constant MENU_TITLE_LENGTH => 3; +use constant MENU_SUBTITLE_LENGTH => 1; +use constant MENU_FILE_LENGTH => 1; +use constant MENU_GROUP_LENGTH => 2; # because it's a line and a blank space +use constant MENU_TEXT_LENGTH => 1; +use constant MENU_LINK_LENGTH => 1; +use constant MENU_INDEX_LENGTH => 1; + +use constant MENU_LENGTH_LIMIT => 35; + + +############################################################################### +# Group: Implemented Interface Functions +# +# The behavior of these functions is shared between HTML output formats. +# + + +# +# Function: PurgeFiles +# +# Deletes the output files associated with the purged source files. +# +sub PurgeFiles + { + my $self = shift; + + my $filesToPurge = NaturalDocs::Project->FilesToPurge(); + + # Combine directories into a hash to remove duplicate work. + my %directoriesToPurge; + + foreach my $file (keys %$filesToPurge) + { + # It's possible that there may be files there that aren't in a valid input directory anymore. They won't generate an output + # file name so we need to check for undef. + my $outputFile = $self->OutputFileOf($file); + if (defined $outputFile) + { + unlink($outputFile); + $directoriesToPurge{ NaturalDocs::File->NoFileName($outputFile) } = 1; + }; + }; + + foreach my $directory (keys %directoriesToPurge) + { + NaturalDocs::File->RemoveEmptyTree($directory, NaturalDocs::Settings->OutputDirectoryOf($self)); + }; + }; + + +# +# Function: PurgeIndexes +# +# Deletes the output files associated with the purged source files. +# +# Parameters: +# +# indexes - An existence hashref of the index types to purge. The keys are the <TopicTypes> or * for the general index. +# +sub PurgeIndexes #(indexes) + { + my ($self, $indexes) = @_; + + foreach my $index (keys %$indexes) + { + $self->PurgeIndexFiles($index, undef); + }; + }; + + +# +# Function: BeginBuild +# +# Creates the necessary subdirectories in the output directory. +# +sub BeginBuild #(hasChanged) + { + my ($self, $hasChanged) = @_; + + foreach my $directory ( $self->JavaScriptDirectory(), $self->CSSDirectory(), $self->IndexDirectory() ) + { + if (!-d $directory) + { NaturalDocs::File->CreatePath($directory); }; + }; + }; + + +# +# Function: EndBuild +# +# Synchronizes the projects CSS and JavaScript files. +# +sub EndBuild #(hasChanged) + { + my ($self, $hasChanged) = @_; + + + # Update the style sheets. + + my $styles = NaturalDocs::Settings->Styles(); + my $changed; + + my $cssDirectory = $self->CSSDirectory(); + my $mainCSSFile = $self->MainCSSFile(); + + for (my $i = 0; $i < scalar @$styles; $i++) + { + my $outputCSSFile; + + if (scalar @$styles == 1) + { $outputCSSFile = $mainCSSFile; } + else + { $outputCSSFile = NaturalDocs::File->JoinPaths($cssDirectory, ($i + 1) . '.css'); }; + + + my $masterCSSFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->ProjectDirectory(), $styles->[$i] . '.css' ); + + if (! -e $masterCSSFile) + { $masterCSSFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->StyleDirectory(), $styles->[$i] . '.css' ); }; + + # We check both the date and the size in case the user switches between two styles which just happen to have the same + # date. Should rarely happen, but it might. + if (! -e $outputCSSFile || + (stat($masterCSSFile))[9] != (stat($outputCSSFile))[9] || + -s $masterCSSFile != -s $outputCSSFile) + { + if (!NaturalDocs::Settings->IsQuiet() && !$saidUpdatingCSSFile) + { + print "Updating CSS file...\n"; + $saidUpdatingCSSFile = 1; + }; + + NaturalDocs::File->Copy($masterCSSFile, $outputCSSFile); + + $changed = 1; + }; + }; + + + my $deleteFrom; + + if (scalar @$styles == 1) + { $deleteFrom = 1; } + else + { $deleteFrom = scalar @$styles + 1; }; + + for (;;) + { + my $file = NaturalDocs::File->JoinPaths($cssDirectory, $deleteFrom . '.css'); + + if (! -e $file) + { last; }; + + unlink ($file); + $deleteFrom++; + + $changed = 1; + }; + + + if ($changed) + { + if (scalar @$styles > 1) + { + open(FH_CSS_FILE, '>' . $mainCSSFile); + + for (my $i = 0; $i < scalar @$styles; $i++) + { + print FH_CSS_FILE '@import URL("' . ($i + 1) . '.css");' . "\n"; + }; + + close(FH_CSS_FILE); + }; + }; + + + + # Update the JavaScript file. + + my $jsMaster = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->JavaScriptDirectory(), 'NaturalDocs.js' ); + my $jsOutput = $self->MainJavaScriptFile(); + + # We check both the date and the size in case the user switches between two styles which just happen to have the same + # date. Should rarely happen, but it might. + if (! -e $jsOutput || + (stat($jsMaster))[9] != (stat($jsOutput))[9] || + -s $jsMaster != -s $jsOutput) + { + NaturalDocs::File->Copy($jsMaster, $jsOutput); + }; + }; + + + +############################################################################### +# Group: Section Functions + + +# +# function: BuildTitle +# +# Builds and returns the HTML page title of a file. +# +# Parameters: +# +# sourceFile - The source <FileName> to build the title of. +# +# Returns: +# +# The source file's title in HTML. +# +sub BuildTitle #(sourceFile) + { + my ($self, $sourceFile) = @_; + + # If we have a menu title, the page title is [menu title] - [file title]. Otherwise it is just [file title]. + + my $title = NaturalDocs::Project->DefaultMenuTitleOf($sourceFile); + + my $menuTitle = NaturalDocs::Menu->Title(); + if (defined $menuTitle && $menuTitle ne $title) + { $title .= ' - ' . $menuTitle; }; + + $title = $self->StringToHTML($title); + + return $title; + }; + +# +# function: BuildMenu +# +# Builds and returns the side menu of a file. +# +# Parameters: +# +# sourceFile - The source <FileName> to use if you're looking for a source file. +# indexType - The index <TopicType> to use if you're looking for an index. +# isFramed - Whether the menu will appear in a frame. If so, it assumes the <base> HTML tag is set to make links go to the +# appropriate frame. +# +# Both sourceFile and indexType may be undef. +# +# Returns: +# +# The side menu in HTML. +# +sub BuildMenu #(FileName sourceFile, TopicType indexType, bool isFramed) -> string htmlMenu + { + my ($self, $sourceFile, $indexType, $isFramed) = @_; + + if (!$menuNumbersAndLengthsDone) + { + $menuGroupNumber = 1; + $menuLength = 0; + %menuGroupLengths = ( ); + %menuGroupNumbers = ( ); + $menuRootLength = 0; + }; + + my $outputDirectory; + + if ($sourceFile) + { $outputDirectory = NaturalDocs::File->NoFileName( $self->OutputFileOf($sourceFile) ); } + elsif ($indexType) + { $outputDirectory = NaturalDocs::File->NoFileName( $self->IndexFileOf($indexType) ); } + else + { $outputDirectory = NaturalDocs::Settings->OutputDirectoryOf($self); }; + + + # Comment needed for UpdateFile(). + my $output = '<!--START_ND_MENU-->'; + + + if (!exists $prebuiltMenus{$outputDirectory}) + { + my $segmentOutput; + + ($segmentOutput, $menuRootLength) = + $self->BuildMenuSegment($outputDirectory, $isFramed, NaturalDocs::Menu->Content()); + + my $titleOutput; + + my $menuTitle = NaturalDocs::Menu->Title(); + if (defined $menuTitle) + { + if (!$menuNumbersAndLengthsDone) + { $menuLength += MENU_TITLE_LENGTH; }; + + $menuRootLength += MENU_TITLE_LENGTH; + + $titleOutput .= + '<div class=MTitle>' + . $self->StringToHTML($menuTitle); + + my $menuSubTitle = NaturalDocs::Menu->SubTitle(); + if (defined $menuSubTitle) + { + if (!$menuNumbersAndLengthsDone) + { $menuLength += MENU_SUBTITLE_LENGTH; }; + + $menuRootLength += MENU_SUBTITLE_LENGTH; + + $titleOutput .= + '<div class=MSubTitle>' + . $self->StringToHTML($menuSubTitle) + . '</div>'; + }; + + $titleOutput .= + '</div>'; + }; + + $prebuiltMenus{$outputDirectory} = $titleOutput . $segmentOutput; + $output .= $titleOutput . $segmentOutput; + } + else + { $output .= $prebuiltMenus{$outputDirectory}; }; + + + # Highlight the menu selection. + + if ($sourceFile) + { + # Dependency: This depends on how BuildMenuSegment() formats file entries. + my $outputFile = $self->OutputFileOf($sourceFile); + my $tag = '<div class=MFile><a href="' . $self->MakeRelativeURL($outputDirectory, $outputFile) . '">'; + my $tagIndex = index($output, $tag); + + if ($tagIndex != -1) + { + my $endIndex = index($output, '</a>', $tagIndex); + + substr($output, $endIndex, 4, ''); + substr($output, $tagIndex, length($tag), '<div class=MFile id=MSelected>'); + }; + } + elsif ($indexType) + { + # Dependency: This depends on how BuildMenuSegment() formats index entries. + my $outputFile = $self->IndexFileOf($indexType); + my $tag = '<div class=MIndex><a href="' . $self->MakeRelativeURL($outputDirectory, $outputFile) . '">'; + my $tagIndex = index($output, $tag); + + if ($tagIndex != -1) + { + my $endIndex = index($output, '</a>', $tagIndex); + + substr($output, $endIndex, 4, ''); + substr($output, $tagIndex, length($tag), '<div class=MIndex id=MSelected>'); + }; + }; + + + # 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 > MENU_LENGTH_LIMIT()) + { + my $menuSelectionHierarchy = $self->GetMenuSelectionHierarchy($sourceFile, $indexType); + + my $toExpand = $self->ExpandMenu($sourceFile, $indexType, $menuSelectionHierarchy, $menuRootLength); + + $output .= + + '<script language=JavaScript><!--' . "\n" + + # Using ToggleMenu here causes IE to sometimes say display is nothing instead of "block" or "none" on the first click. + # Whatever. This is just as good. + + . 'if (document.getElementById)' + . '{'; + + if (scalar @$toExpand) + { + $output .= + + 'for (var menu = 1; menu < ' . $menuGroupNumber . '; menu++)' + . '{' + . 'if (menu != ' . join(' && menu != ', @$toExpand) . ')' + . '{' + . 'document.getElementById("MGroupContent" + menu).style.display = "none";' + . '};' + . '};' + } + else + { + $output .= + + 'for (var menu = 1; menu < ' . $menuGroupNumber . '; menu++)' + . '{' + . 'document.getElementById("MGroupContent" + menu).style.display = "none";' + . '};' + }; + + $output .= + '}' + + . '// --></script>'; + }; + + # Comment needed for UpdateFile(). + $output .= '<!--END_ND_MENU-->'; + + $menuNumbersAndLengthsDone = 1; + + return $output; + }; + + +# +# Function: BuildMenuSegment +# +# A recursive function to build a segment of the menu. *Remember to reset the <Menu Package Variables> before calling this +# for the first time.* +# +# Parameters: +# +# outputDirectory - The output directory the menu is being built for. +# isFramed - Whether the menu will be in a HTML frame or not. Assumes that if it is, the <base> HTML tag will be set so that +# links are directed to the proper frame. +# menuSegment - An arrayref specifying the segment of the menu to build. Either pass the menu itself or the contents +# of a group. +# +# 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 +# <Menu Length Constants>. +# +sub BuildMenuSegment #(outputDirectory, isFramed, menuSegment) + { + my ($self, $outputDirectory, $isFramed, $menuSegment) = @_; + + my ($output, $groupLength); + + foreach my $entry (@$menuSegment) + { + if ($entry->Type() == ::MENU_GROUP()) + { + my ($entryOutput, $entryLength) = + $self->BuildMenuSegment($outputDirectory, $isFramed, $entry->GroupContent()); + + my $entryNumber; + + if (!$menuNumbersAndLengthsDone) + { + $entryNumber = $menuGroupNumber; + $menuGroupNumber++; + + $menuGroupLengths{$entry} = $entryLength; + $menuGroupNumbers{$entry} = $entryNumber; + } + else + { $entryNumber = $menuGroupNumbers{$entry}; }; + + $output .= + '<div class=MEntry>' + . '<div class=MGroup>' + + . '<a href="javascript:ToggleMenu(\'MGroupContent' . $entryNumber . '\')"' + . ($isFramed ? ' target="_self"' : '') . '>' + . $self->StringToHTML($entry->Title()) + . '</a>' + + . '<div class=MGroupContent id=MGroupContent' . $entryNumber . '>' + . $entryOutput + . '</div>' + + . '</div>' + . '</div>'; + + $groupLength += MENU_GROUP_LENGTH; + } + + elsif ($entry->Type() == ::MENU_FILE()) + { + my $targetOutputFile = $self->OutputFileOf($entry->Target()); + + # Dependency: BuildMenu() depends on how this formats file entries. + $output .= + '<div class=MEntry>' + . '<div class=MFile>' + . '<a href="' . $self->MakeRelativeURL($outputDirectory, $targetOutputFile) . '">' + . $self->StringToHTML( $entry->Title(), ADD_HIDDEN_BREAKS) + . '</a>' + . '</div>' + . '</div>'; + + $groupLength += MENU_FILE_LENGTH; + } + + elsif ($entry->Type() == ::MENU_TEXT()) + { + $output .= + '<div class=MEntry>' + . '<div class=MText>' + . $self->StringToHTML( $entry->Title() ) + . '</div>' + . '</div>'; + + $groupLength += MENU_TEXT_LENGTH; + } + + elsif ($entry->Type() == ::MENU_LINK()) + { + $output .= + '<div class=MEntry>' + . '<div class=MLink>' + . '<a href="' . $entry->Target() . '"' . ($isFramed ? ' target="_top"' : '') . '>' + . $self->StringToHTML( $entry->Title() ) + . '</a>' + . '</div>' + . '</div>'; + + $groupLength += MENU_LINK_LENGTH; + } + + elsif ($entry->Type() == ::MENU_INDEX()) + { + my $indexFile = $self->IndexFileOf($entry->Target); + + # Dependency: BuildMenu() depends on how this formats index entries. + $output .= + '<div class=MEntry>' + . '<div class=MIndex>' + . '<a href="' . $self->MakeRelativeURL( $outputDirectory, $self->IndexFileOf($entry->Target()) ) . '">' + . $self->StringToHTML( $entry->Title() ) + . '</a>' + . '</div>' + . '</div>'; + + $groupLength += MENU_INDEX_LENGTH; + }; + }; + + + if (!$menuNumbersAndLengthsDone) + { $menuLength += $groupLength; }; + + return ($output, $groupLength); + }; + + +# +# Function: BuildContent +# +# Builds and returns the main page content. +# +# Parameters: +# +# sourceFile - The source <FileName>. +# parsedFile - The parsed source file as an arrayref of <NaturalDocs::Parser::ParsedTopic> objects. +# +# Returns: +# +# The page content in HTML. +# +sub BuildContent #(sourceFile, parsedFile) + { + my ($self, $sourceFile, $parsedFile) = @_; + + $self->ResetToolTips(); + + my $output; + my $i = 0; + + while ($i < scalar @$parsedFile) + { + my $anchor = $self->SymbolToHTMLSymbol($parsedFile->[$i]->Symbol()); + + my $scope = NaturalDocs::Topics->TypeInfo($parsedFile->[$i]->Type())->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 .= + + '<div class=C' . NaturalDocs::Topics->NameOfType($parsedFile->[$i]->Type(), 0, 1) + . ($i == 0 ? ' id=MainTopic' : '') . '>' + + . '<div class=CTopic>' + + . '<' . $headerType . ' class=CTitle>' + . '<a name="' . $anchor . '"></a>' + . $self->StringToHTML( $parsedFile->[$i]->Title(), ADD_HIDDEN_BREAKS) + . '</' . $headerType . '>'; + + + my $hierarchy; + if (NaturalDocs::Topics->TypeInfo( $parsedFile->[$i]->Type() )->ClassHierarchy()) + { + $hierarchy = $self->BuildClassHierarchy($sourceFile, $parsedFile->[$i]->Symbol()); + }; + + my $summary; + if ($i == 0 || $scope == ::SCOPE_START() || $scope == ::SCOPE_END()) + { + $summary .= $self->BuildSummary($sourceFile, $parsedFile, $i); + }; + + my $hasBody; + if (defined $hierarchy || defined $summary || + defined $parsedFile->[$i]->Prototype() || defined $parsedFile->[$i]->Body()) + { + $output .= '<div class=CBody>'; + $hasBody = 1; + }; + + $output .= $hierarchy; + + if (defined $parsedFile->[$i]->Prototype()) + { + $output .= $self->BuildPrototype($parsedFile->[$i]->Type(), $parsedFile->[$i]->Prototype(), $sourceFile); + }; + + if (defined $parsedFile->[$i]->Body()) + { + $output .= $self->NDMarkupToHTML( $sourceFile, $parsedFile->[$i]->Body(), $parsedFile->[$i]->Symbol(), + $parsedFile->[$i]->Package(), $parsedFile->[$i]->Type(), + $parsedFile->[$i]->Using() ); + }; + + $output .= $summary; + + + if ($hasBody) + { $output .= '</div>'; }; + + $output .= + '</div>' # CTopic + . '</div>' # CType + . "\n\n"; + + $i++; + }; + + return $output; + }; + + +# +# Function: BuildSummary +# +# Builds a summary, either for the entire file or the current class/section. +# +# Parameters: +# +# sourceFile - The source <FileName> 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 <TopicType> 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 >= scalar @$parsedFile) + { return undef; }; + + + my $scope = NaturalDocs::Topics->TypeInfo($parsedFile->[$index]->Type())->Scope(); + + # Return nothing if there's only one entry. + if (!$completeSummary && ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) ) + { return undef; }; + + + my $indent = 0; + my $inGroup; + + # In a nice efficiency twist, these buggers will hold the opening div tags if true, undef if false. Not that this script is known + # for its efficiency. Not that Perl is known for its efficiency. Anyway... + my $isMarkedAttr; + my $entrySizeAttr = ' class=SEntrySize'; + my $descriptionSizeAttr = ' class=SDescriptionSize'; + + my $output = + '<!--START_ND_SUMMARY-->' + . '<div class=Summary><div class=STitle>Summary</div>' + + # Not all browsers get table padding right, so we need a div to apply the border. + . '<div class=SBorder>' + . '<table border=0 cellspacing=0 cellpadding=0 class=STable>'; + + while ($index < scalar @$parsedFile) + { + my $topic = $parsedFile->[$index]; + my $scope = NaturalDocs::Topics->TypeInfo($topic->Type())->Scope(); + + if (!$completeSummary && ($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; + $isMarkedAttr = undef; + } + elsif ($topic->Type() eq ::TOPIC_GROUP()) + { + if ($inGroup) + { $indent--; }; + + $inGroup = 0; + $isMarkedAttr = undef; + }; + + + $output .= + '<tr' . $isMarkedAttr . '><td' . $entrySizeAttr . '>' + . '<div class=S' . ($index == 0 ? 'Main' : NaturalDocs::Topics->NameOfType($topic->Type(), 0, 1)) . '>' + . '<div class=SEntry>'; + + + # Add any remaining modifiers to the HTML in the form of div tags. This modifier approach isn't the most elegant + # thing, but there's not a lot of options. It works. + + if ($indent) + { $output .= '<div class=SIndent' . $indent . '>'; }; + + + # Add the entry itself. + + my $toolTipProperties; + + # We only want a tooltip here if there's a protoype. Otherwise it's redundant. + + if (defined $topic->Prototype()) + { + my $tooltipID = $self->BuildToolTip($topic->Symbol(), $sourceFile, $topic->Type(), + $topic->Prototype(), $topic->Summary()); + $toolTipProperties = $self->BuildToolTipLinkProperties($tooltipID); + }; + + $output .= + '<a href="#' . $self->SymbolToHTMLSymbol($parsedFile->[$index]->Symbol()) . '" ' . $toolTipProperties . '>' + . $self->StringToHTML( $parsedFile->[$index]->Title(), ADD_HIDDEN_BREAKS) + . '</a>'; + + + # Close the modifiers. + + if ($indent) + { $output .= '</div>'; }; + + $output .= + '</div>' # Entry + . '</div>' # Type + + . '</td><td' . $descriptionSizeAttr . '>' + + . '<div class=S' . ($index == 0 ? 'Main' : NaturalDocs::Topics->NameOfType($topic->Type(), 0, 1)) . '>' + . '<div class=SDescription>'; + + + # Add the modifiers to the HTML yet again. + + if ($indent) + { $output .= '<div class=SIndent' . $indent . '>'; }; + + + if (defined $parsedFile->[$index]->Body()) + { + $output .= $self->NDMarkupToHTML($sourceFile, $parsedFile->[$index]->Summary(), + $parsedFile->[$index]->Symbol(), $parsedFile->[$index]->Package(), + $parsedFile->[$index]->Type(), $parsedFile->[$index]->Using()); + }; + + + # Close the modifiers again. + + if ($indent) + { $output .= '</div>'; }; + + + $output .= + '</div>' # Description + . '</div>' # Type + + . '</td></tr>'; + + + # Prepare the modifiers for the next entry. + + if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) + { + $indent = 1; + $inGroup = 0; + } + elsif ($topic->Type() eq ::TOPIC_GROUP()) + { + if (!$inGroup) + { + $indent++; + $inGroup = 1; + }; + }; + + if (!defined $isMarkedAttr) + { $isMarkedAttr = ' class=SMarked'; } + else + { $isMarkedAttr = undef; }; + + $entrySizeAttr = undef; + $descriptionSizeAttr = undef; + + $index++; + }; + + $output .= + '</table>' + . '</div>' # Border + . '</div>' # Summary + . "<!--END_ND_SUMMARY-->"; + + return $output; + }; + + +# +# Function: BuildPrototype +# +# Builds and returns the prototype as HTML. +# +# Parameters: +# +# type - The <TopicType> the prototype is from. +# prototype - The prototype to format. +# file - The <FileName> the prototype was defined in. +# +# Returns: +# +# The prototype in HTML. +# +sub BuildPrototype #(type, prototype, file) + { + my ($self, $type, $prototype, $file) = @_; + + my $language = NaturalDocs::Languages->LanguageOf($file); + my $prototypeObject = $language->ParsePrototype($type, $prototype); + + my $output; + + if ($prototypeObject->OnlyBeforeParameters()) + { + $output = + # A blockquote to scroll it if it's too long. + '<blockquote>' + # A surrounding table as a hack to make the div form-fit. + . '<table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td>' + . $self->ConvertAmpChars($prototypeObject->BeforeParameters()) + . '</td></tr></table>' + . '</blockquote>'; + } + + else + { + my $params = $prototypeObject->Parameters(); + my $beforeParams = $prototypeObject->BeforeParameters(); + my $afterParams = $prototypeObject->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->Name()); + + if ($param->Type()) + { + $hasType = 1; + $paramLength += length($param->Type()) + 1; + }; + if ($param->TypePrefix()) + { + $hasTypePrefix = 1; + $paramLength += length($param->TypePrefix()) + 1; + }; + if ($param->NamePrefix()) + { + $hasNamePrefix = 1; + $paramLength += length($param->NamePrefix()); + }; + if ($param->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->DefaultValue()) / 3; + + my @words = split(/ +/, $param->DefaultValue()); + my $maxWordLength = 0; + + foreach my $word (@words) + { + if (length($word) > $maxWordLength) + { $maxWordLength = length($word); }; + }; + + $paramLength += ($maxWordLength > $thirdLength ? $maxWordLength : $thirdLength) + 1; + }; + if ($param->DefaultValuePrefix()) + { + $hasDefaultValuePrefix = 1; + $paramLength += length($param->DefaultValuePrefix()) + 1; + }; + + if ($paramLength > $maxParamLength) + { $maxParamLength = $paramLength; }; + }; + + my $useCondensed = (length($beforeParams) + $maxParamLength + length($afterParams) > 80 ? 1 : 0); + my $parameterColumns = 1 + $hasType + $hasTypePrefix + $hasNamePrefix + + $hasDefaultValue + $hasDefaultValuePrefix + $useCondensed; + + $output = + '<blockquote><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td>' + + # Stupid hack to get it to work right in IE. + . '<table border=0 cellspacing=0 cellpadding=0><tr>' + + . '<td class=PBeforeParameters ' . ($useCondensed ? 'colspan=' . $parameterColumns : 'nowrap') . '>' + . $self->ConvertAmpChars($beforeParams); + + if ($beforeParams && $beforeParams !~ /[\(\[\{\<]$/) + { $output .= ' '; }; + + $output .= + '</td>'; + + for (my $i = 0; $i < scalar @$params; $i++) + { + if ($useCondensed) + { + $output .= '</tr><tr><td> </td>'; + } + elsif ($i > 0) + { + # Go to the next row and and skip the BeforeParameters cell. + $output .= '</tr><tr><td></td>'; + }; + + if ($language->TypeBeforeParameter()) + { + if ($hasTypePrefix) + { + my $htmlTypePrefix = $self->ConvertAmpChars($params->[$i]->TypePrefix()); + $htmlTypePrefix =~ s/ $/ /; + + $output .= + '<td class=PTypePrefix nowrap>' + . $htmlTypePrefix + . '</td>'; + }; + + if ($hasType) + { + $output .= + '<td class=PType nowrap>' + . $self->ConvertAmpChars($params->[$i]->Type()) . ' ' + . '</td>'; + }; + + if ($hasNamePrefix) + { + $output .= + '<td class=PParameterPrefix nowrap>' + . $self->ConvertAmpChars($params->[$i]->NamePrefix()) + . '</td>'; + }; + + $output .= + '<td class=PParameter nowrap' . ($useCondensed && !$hasDefaultValue ? ' width=100%' : '') . '>' + . $self->ConvertAmpChars($params->[$i]->Name()) + . '</td>'; + } + + else # !$language->TypeBeforeParameter() + { + $output .= + '<td class=PParameter nowrap>' + . $self->ConvertAmpChars( $params->[$i]->NamePrefix() . $params->[$i]->Name() ) + . '</td>'; + + if ($hasType || $hasTypePrefix) + { + my $typePrefix = $params->[$i]->TypePrefix(); + if ($typePrefix) + { $typePrefix .= ' '; }; + + $output .= + '<td class=PType nowrap' . ($useCondensed && !$hasDefaultValue ? ' width=100%' : '') . '>' + . ' ' . $self->ConvertAmpChars( $typePrefix . $params->[$i]->Type() ) + . '</td>'; + }; + }; + + if ($hasDefaultValuePrefix) + { + $output .= + '<td class=PDefaultValuePrefix>' + . ' ' . $self->ConvertAmpChars( $params->[$i]->DefaultValuePrefix() ) . ' ' + . '</td>'; + }; + + if ($hasDefaultValue) + { + $output .= + '<td class=PDefaultValue width=100%>' + . ($hasDefaultValuePrefix ? '' : ' ') . $self->ConvertAmpChars( $params->[$i]->DefaultValue() ) + . '</td>'; + }; + }; + + if ($useCondensed) + { $output .= '</tr><tr>'; }; + + $output .= + '<td class=PAfterParameters ' . ($useCondensed ? 'colspan=' . $parameterColumns : 'nowrap') . '>' + . $self->ConvertAmpChars($afterParams); + + if ($afterParams && $afterParams !~ /^[\)\]\}\>]/) + { $output .= ' '; }; + + $output .= + '</td>' + . '</tr></table>' + + # Hack. + . '</td></tr></table></blockquote>'; + }; + + return $output; + }; + + +# +# Function: BuildFooter +# +# Builds and returns the HTML footer for the page. +# +sub BuildFooter + { + my $self = shift; + my $footer = NaturalDocs::Menu->Footer(); + + if (defined $footer) + { + if (substr($footer, -1, 1) ne '.') + { $footer .= '.'; }; + + $footer =~ s/\(c\)/©/gi; + $footer =~ s/\(tm\)/™/gi; + $footer =~ s/\(r\)/®/gi; + + $footer .= ' Generated by <a href="' . NaturalDocs::Settings->AppURL() . '">Natural Docs</a>.' + } + else + { + $footer = 'Generated by <a href="' . NaturalDocs::Settings->AppURL() . '">Natural Docs</a>'; + }; + + return '<!--START_ND_FOOTER-->' . $footer . '<!--END_ND_FOOTER-->'; + }; + + +# +# Function: BuildToolTip +# +# Builds the HTML for a symbol's tooltip and stores it in <tooltipHTML>. +# +# Parameters: +# +# symbol - The target <SymbolString>. +# file - The <FileName> the target's defined in. +# type - The symbol <TopicType>. +# 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->SymbolToHTMLSymbol($symbol); + my $number = $tooltipSymbolsToNumbers{$htmlSymbol}; + + if (!defined $number) + { + $number = $tooltipNumber; + $tooltipNumber++; + + $tooltipSymbolsToNumbers{$htmlSymbol} = $number; + + $tooltipHTML .= + '<div class=CToolTip id="tt' . $number . '">' + . '<div class=C' . NaturalDocs::Topics->NameOfType($type, 0, 1) . '>'; + + if (defined $prototype) + { + $tooltipHTML .= $self->BuildPrototype($type, $prototype, $file); + }; + + if (defined $summary) + { + # Remove links, since people can't/shouldn't be clicking on tooltips anyway. + $summary =~ s/<\/?(?:link|url)>//g; + + # The fact that we don't have scope or using shouldn't matter because we removed the links. + $summary = $self->NDMarkupToHTML($file, $summary, undef, undef, $type, undef); + + # XXX - Hack. We want to remove e-mail links as well, but keep their obfuscation. So we leave the tags in there for + # the NDMarkupToHTML call, then strip out the link part afterwards. The text obfuscation should still be in place. + + $summary =~ s/<\/?a[^>]+>//g; + + $tooltipHTML .= $summary; + }; + + $tooltipHTML .= + '</div>' + . '</div>'; + }; + + 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<!--START_ND_TOOLTIPS-->\n" . $tooltipHTML . "<!--END_ND_TOOLTIPS-->\n\n"; + }; + +# +# Function: BuildClassHierarchy +# +# Builds and returns a class hierarchy diagram for the passed class, if applicable. +# +# Parameters: +# +# file - The source <FileName>. +# class - The class <SymbolString> to build the hierarchy of. +# +sub BuildClassHierarchy #(file, symbol) + { + my ($self, $file, $symbol) = @_; + + my @parents = NaturalDocs::ClassHierarchy->ParentsOf($symbol); + @parents = sort { ::StringCompare($a, $b) } @parents; + + my @children = NaturalDocs::ClassHierarchy->ChildrenOf($symbol); + @children = sort { ::StringCompare($a, $b) } @children; + + if (!scalar @parents && !scalar @children) + { return undef; }; + + my $output = + '<div class=ClassHierarchy>'; + + if (scalar @parents) + { + $output .='<table border=0 cellspacing=0 cellpadding=0><tr><td>'; + + foreach my $parent (@parents) + { + $output .= $self->BuildClassHierarchyEntry($file, $parent, 'CHParent', 1); + }; + + $output .= '</td></tr></table><div class=CHIndent>'; + }; + + $output .= + '<table border=0 cellspacing=0 cellpadding=0><tr><td>' + . $self->BuildClassHierarchyEntry($file, $symbol, 'CHCurrent', undef) + . '</td></tr></table>'; + + if (scalar @children) + { + $output .= + '<div class=CHIndent>' + . '<table border=0 cellspacing=0 cellpadding=0><tr><td>'; + + if (scalar @children <= 5) + { + for (my $i = 0; $i < scalar @children; $i++) + { $output .= $self->BuildClassHierarchyEntry($file, $children[$i], 'CHChild', 1); }; + } + else + { + for (my $i = 0; $i < 4; $i++) + { $output .= $self->BuildClassHierarchyEntry($file, $children[$i], 'CHChild', 1); }; + + $output .= '<div class=CHChildNote><div class=CHEntry>' . (scalar @children - 4) . ' other children</div></div>'; + }; + + $output .= + '</td></tr></table>' + . '</div>'; + }; + + if (scalar @parents) + { $output .= '</div>'; }; + + $output .= + '</div>'; + + return $output; + }; + + +# +# Function: BuildClassHierarchyEntry +# +# Builds and returns a single class hierarchy entry. +# +# Parameters: +# +# file - The source <FileName>. +# symbol - The class <SymbolString> whose entry is getting built. +# style - The style to apply to the entry, such as <CHParent>. +# 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->IdentifiersOf($symbol); + my $name = join(NaturalDocs::Languages->LanguageOf($file)->PackageSeparator(), @identifiers); + $name = $self->StringToHTML($name); + + my $output = '<div class=' . $style . '><div class=CHEntry>'; + + if ($link) + { + my $target = NaturalDocs::SymbolTable->Lookup($symbol, $file); + + if (defined $target) + { + my $targetFile; + + if ($target->File() ne $file) + { $targetFile = $self->MakeRelativeURL( $self->OutputFileOf($file), $self->OutputFileOf($target->File()), 1 ); }; + # else leave it undef + + my $targetTooltipID = $self->BuildToolTip($symbol, $target->File(), $target->Type(), + $target->Prototype(), $target->Summary()); + + my $toolTipProperties = $self->BuildToolTipLinkProperties($targetTooltipID); + + $output .= '<a href="' . $targetFile . '#' . $self->SymbolToHTMLSymbol($symbol) . '" ' + . 'class=L' . NaturalDocs::Topics->NameOfType($target->Type(), 0, 1) . ' ' . $toolTipProperties . '>' + . $name . '</a>'; + } + else + { $output .= $name; }; + } + else + { $output .= $name; }; + + $output .= '</div></div>'; + return $output; + }; + + +# +# Function: OpeningBrowserStyles +# +# Returns the JavaScript that will add opening browser styles if necessary. +# +sub OpeningBrowserStyles + { + my $self = shift; + + return + + '<script language=JavaScript><!--' . "\n" + + # IE 4 and 5 don't understand 'undefined', so you can't say '!= undefined'. + . 'if (browserType) {' + . 'document.write("<div class=" + browserType + ">");' + . 'if (browserVer) {' + . 'document.write("<div class=" + browserVer + ">"); }' + . '}' + + . '// --></script>'; + }; + + +# +# Function: ClosingBrowserStyles +# +# Returns the JavaScript that will close browser styles if necessary. +# +sub ClosingBrowserStyles + { + my $self = shift; + + return + + '<script language=JavaScript><!--' . "\n" + + . 'if (browserType) {' + . 'if (browserVer) {' + . 'document.write("</div>"); }' + . 'document.write("</div>");' + . '}' + + . '// --></script>'; + }; + + +# +# Function: StandardComments +# +# Returns the standard HTML comments that should be included in every generated file. This includes <IEWebMark()>, so this +# really is required for proper functionality. +# +sub StandardComments + { + my $self = shift; + + return "\n\n" + + . '<!-- Generated by Natural Docs, version ' . NaturalDocs::Settings->TextAppVersion() . ' -->' . "\n" + . '<!-- ' . NaturalDocs::Settings->AppURL() . ' -->' . "\n\n" + . $self->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 '<!-- saved from url=(0026)http://www.naturaldocs.org -->'; + }; + + + +############################################################################### +# Group: Index Functions + + +# +# Function: BuildIndexPages +# +# Builds an index file or files. +# +# Parameters: +# +# type - The <TopicType> the index is limited to, or undef for none. +# index - An arrayref of sections, each section being an arrayref <NaturalDocs::SymbolTable::IndexElement> objects. +# The first section is for symbols, the second for numbers, and the rest for A through Z. +# beginPage - All the content of the HTML page up to where the index content should appear. +# endPage - All the content of the HTML page past where the index should appear. +# +# Returns: +# +# The number of pages in the index. +# +sub BuildIndexPages #(type, index, beginPage, endPage) + { + my ($self, $type, $indexSections, $beginPage, $endPage) = @_; + + # Build the content. + + my ($indexHTMLSections, $tooltipHTMLSections) = $self->BuildIndexSections($indexSections, $self->IndexFileOf($type, 1)); + + + 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 => 50000; + + + # File the pages. + + for (my $i = 0; $i < scalar @$indexHTMLSections; $i++) + { + if (!defined $indexHTMLSections->[$i]) + { next; }; + + $pageSize += length($indexHTMLSections->[$i]) + length($tooltipHTMLSections->[$i]); + $pageLocations[$i] = $page; + + if ($pageSize + length($indexHTMLSections->[$i+1]) + length($tooltipHTMLSections->[$i+1]) > PAGESIZE_LIMIT) + { + $page++; + $pageSize = 0; + }; + }; + + + # Build the pages. + + my $indexFileName; + $page = -1; + my $oldPage = -1; + my $tooltips; + my $firstHeading; + + for (my $i = 0; $i < scalar @$indexHTMLSections; $i++) + { + if (!defined $indexHTMLSections->[$i]) + { next; }; + + $page = $pageLocations[$i]; + + # Switch files if we need to. + + if ($page != $oldPage) + { + if ($oldPage != -1) + { + print INDEXFILEHANDLE '</table>' . $tooltips . $endPage; + close(INDEXFILEHANDLE); + $tooltips = undef; + }; + + $indexFileName = $self->IndexFileOf($type, $page); + + open(INDEXFILEHANDLE, '>' . $indexFileName) + or die "Couldn't create output file " . $indexFileName . ".\n"; + + print INDEXFILEHANDLE $beginPage . $self->BuildIndexNavigationBar($type, $page, \@pageLocations) + . '<table border=0 cellspacing=0 cellpadding=0>'; + + $oldPage = $page; + $firstHeading = 1; + }; + + print INDEXFILEHANDLE + '<tr>' + . '<td class=IHeading' . ($firstHeading ? ' id=IFirstHeading' : '') . '>' + . '<a name="' . $indexAnchors[$i] . '"></a>' + . $indexHeadings[$i] + . '</td>' + . '<td></td>' + . '</tr>' + + . $indexHTMLSections->[$i]; + + $firstHeading = 0; + $tooltips .= $tooltipHTMLSections->[$i]; + }; + + if ($page != -1) + { + print INDEXFILEHANDLE '</table>' . $tooltips . $endPage; + close(INDEXFILEHANDLE); + } + + # Build a dummy page so there's something at least. + else + { + $indexFileName = $self->IndexFileOf($type, 1); + + open(INDEXFILEHANDLE, '>' . $indexFileName) + or die "Couldn't create output file " . $indexFileName . ".\n"; + + print INDEXFILEHANDLE + $beginPage + . $self->BuildIndexNavigationBar($type, 1, \@pageLocations) + . 'There are no entries in the ' . lc( NaturalDocs::Topics->NameOfType($type) ) . ' index.' + . $endPage; + + close(INDEXFILEHANDLE); + }; + + + return $page; + }; + + +# +# Function: BuildIndexSections +# +# Builds and returns index's sections in HTML. +# +# Parameters: +# +# index - An arrayref of sections, each section being an arrayref <NaturalDocs::SymbolTable::IndexElement> objects. +# The first section is for symbols, the second for numbers, and the rest for A through Z. +# outputFile - The output file the index is going to be stored in. Since there may be multiple files, just send the first file. The +# path is what matters, not the file name. +# +# Returns: +# +# The arrayref ( indexSections, tooltipSections ). +# +# 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 #(index, outputFile) + { + my ($self, $indexSections, $outputFile) = @_; + + $self->ResetToolTips(); + + my $contentSections = [ ]; + my $tooltipSections = [ ]; + + for (my $section = 0; $section < scalar @$indexSections; $section++) + { + if (defined $indexSections->[$section]) + { + my $total = scalar @{$indexSections->[$section]}; + + for (my $i = 0; $i < $total; $i++) + { + my $id; + + if ($i == 0) + { + if ($total == 1) + { $id = 'IOnlySymbolPrefix'; } + else + { $id = 'IFirstSymbolPrefix'; }; + } + elsif ($i == $total - 1) + { $id = 'ILastSymbolPrefix'; }; + + $contentSections->[$section] .= $self->BuildIndexElement($indexSections->[$section]->[$i], $outputFile, $id); + }; + + $tooltipSections->[$section] .= $self->BuildToolTips(); + $self->ResetToolTips(1); + }; + }; + + + return ( $contentSections, $tooltipSections ); + }; + + +# +# Function: BuildIndexElement +# +# Converts a <NaturalDocs::SymbolTable::IndexElement> to HTML and returns it. It will handle all sub-elements automatically. +# +# Parameters: +# +# element - The <NaturalDocs::SymbolTable::IndexElement> to build. +# outputFile - The output <FileName> this is appearing in. +# id - 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 <SymbolString> to use. +# package - If the element is below package level, the package <SymbolString> to use. +# hasPackage - Whether the element is below package level. Is necessary because package may need to be undef. +# +sub BuildIndexElement #(element, outputFile, id, symbol, package, hasPackage) + { + my ($self, $element, $outputFile, $id, $symbol, $package, $hasPackage) = @_; + + my $output; + + + # If we're doing a file sub-index entry... + + if ($hasPackage) + { + my ($inputDirectory, $relativePath) = NaturalDocs::Settings->SplitFromInputDirectory($element->File()); + + $output = + $self->BuildIndexLink($self->StringToHTML($relativePath, ADD_HIDDEN_BREAKS), $symbol, + $package, $element->File(), $element->Type(), $element->Prototype(), + $element->Summary(), $outputFile, 'IFile') + } + + + # If we're doing a package sub-index entry... + + elsif (defined $symbol) + { + my $text; + + if ($element->Package()) + { + $text = NaturalDocs::SymbolString->ToText($element->Package(), $element->PackageSeparator()); + $text = $self->StringToHTML($text, ADD_HIDDEN_BREAKS); + } + else + { $text = 'Global'; }; + + if (!$element->HasMultipleFiles()) + { + $output .= $self->BuildIndexLink($text, $symbol, $element->Package(), $element->File(), $element->Type(), + $element->Prototype(), $element->Summary(), $outputFile, 'IParent'); + } + + else + { + $output .= + '<span class=IParent>' + . $text + . '</span>' + . '<div class=ISubIndex>'; + + my $fileElements = $element->File(); + foreach my $fileElement (@$fileElements) + { + $output .= $self->BuildIndexElement($fileElement, $outputFile, $id, $symbol, $element->Package(), 1); + }; + + $output .= + '</div>'; + }; + } + + + # If we're doing a top-level symbol entry... + + else + { + my $symbolText = $self->StringToHTML($element->SortableSymbol(), ADD_HIDDEN_BREAKS); + my $symbolPrefix = $self->StringToHTML($element->IgnoredPrefix()); + + $output .= + '<tr>' + . '<td class=ISymbolPrefix' . ($id ? ' id=' . $id : '') . '>' + . ($symbolPrefix || ' ') + . '</td><td class=IEntry>'; + + if (!$element->HasMultiplePackages()) + { + my $packageText; + + if (defined $element->Package()) + { + $packageText = NaturalDocs::SymbolString->ToText($element->Package(), $element->PackageSeparator()); + $packageText = $self->StringToHTML($packageText, ADD_HIDDEN_BREAKS); + }; + + if (!$element->HasMultipleFiles()) + { + $output .= + $self->BuildIndexLink($symbolText, $element->Symbol(), $element->Package(), $element->File(), + $element->Type(), $element->Prototype(), $element->Summary(), $outputFile, 'ISymbol'); + + if (defined $packageText) + { + $output .= + ', <span class=IParent>' + . $packageText + . '</span>'; + }; + } + else # hasMultipleFiles but not mulitplePackages + { + $output .= + '<span class=ISymbol>' + . $symbolText + . '</span>'; + + if (defined $packageText) + { + $output .= + ', <span class=IParent>' + . $packageText + . '</span>'; + }; + + $output .= + '<div class=ISubIndex>'; + + my $fileElements = $element->File(); + foreach my $fileElement (@$fileElements) + { + $output .= $self->BuildIndexElement($fileElement, $outputFile, $id, $element->Symbol(), $element->Package(), 1); + }; + + $output .= + '</div>'; + }; + } + + else # hasMultiplePackages + { + $output .= + '<span class=ISymbol>' + . $symbolText + . '</span>' + . '<div class=ISubIndex>'; + + my $packageElements = $element->Package(); + foreach my $packageElement (@$packageElements) + { + $output .= $self->BuildIndexElement($packageElement, $outputFile, $id, $element->Symbol()); + }; + + $output .= + '</div>'; + }; + + $output .= '</td></tr>'; + }; + + + return $output; + }; + + +# +# 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 <IndexSymbolToHTML()> if necessary. +# symbol - The partial <SymbolString> to link to. +# package - The package <SymbolString> of the symbol. +# file - The <FileName> the symbol is defined in. +# type - The <TopicType> of the symbol. +# prototype - The prototype of the symbol, or undef if none. +# summary - The summary of the symbol, or undef if none. +# outputFile - The HTML <FileName> this link will appear in. +# style - The CSS style to apply to the link. +# +sub BuildIndexLink #(text, symbol, package, file, type, prototype, summary, outputFile, style) + { + my ($self, $text, $symbol, $package, $file, $type, $prototype, $summary, $outputFile, $style) = @_; + + $symbol = NaturalDocs::SymbolString->Join($package, $symbol); + + my $targetTooltipID = $self->BuildToolTip($symbol, $file, $type, $prototype, $summary); + my $toolTipProperties = $self->BuildToolTipLinkProperties($targetTooltipID); + + return '<a href="' . $self->MakeRelativeURL( $outputFile, $self->OutputFileOf($file), 1 ) + . '#' . $self->SymbolToHTMLSymbol($symbol) . '" ' . $toolTipProperties . ' ' + . 'class=' . $style . '>' . $text . '</a>'; + }; + + +# +# Function: BuildIndexNavigationBar +# +# Builds a navigation bar for a page of the index. +# +# Parameters: +# +# type - The <TopicType> 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 = '<div class=INavigationBar>'; + + for (my $i = 0; $i < scalar @indexHeadings; $i++) + { + if ($i != 0) + { $output .= ' · '; }; + + if (defined $locations->[$i]) + { + $output .= '<a href="'; + + if ($locations->[$i] != $page) + { $output .= $self->RelativeIndexFileOf($type, $locations->[$i]); }; + + $output .= '#' . $indexAnchors[$i] . '">' . $indexHeadings[$i] . '</a>'; + } + else + { + $output .= $indexHeadings[$i]; + }; + }; + + $output .= '</div>'; + + return $output; + }; + + + +############################################################################### +# Group: File Functions + + +# +# function: PurgeIndexFiles +# +# Removes all or some of the output files for an index. +# +# Parameters: +# +# type - The index <TopicType>. +# startingPage - If defined, only pages starting with this number will be removed. Otherwise all pages will be removed. +# +sub PurgeIndexFiles #(type, startingPage) + { + my ($self, $type, $page) = @_; + + if (!defined $page) + { $page = 1; }; + + for (;;) + { + my $file = $self->IndexFileOf($type, $page); + + if (-e $file) + { + unlink($file); + $page++; + } + else + { + last; + }; + }; + }; + + +# +# 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->SplitFromInputDirectory($sourceFile); + if (!defined $inputDirectory) + { return undef; }; + + my $outputDirectory = NaturalDocs::Settings->OutputDirectoryOf($self); + my $inputDirectoryName = NaturalDocs::Settings->InputDirectoryNameOf($inputDirectory); + + $outputDirectory = NaturalDocs::File->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/ /_/; + $relativeSourceFile .= '.html'; + + return NaturalDocs::File->JoinPaths($outputDirectory, $relativeSourceFile); + }; + + +# +# Function: IndexDirectory +# +# Returns the directory of the index files. +# +sub IndexDirectory + { + my $self = shift; + return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index', 1); + }; + + +# +# function: IndexFileOf +# +# Returns the output file name of the index file. +# +# Parameters: +# +# type - The <TopicType> of the index. +# page - The page number. Undef is the same as one. +# +sub IndexFileOf #(type, page) + { + my ($self, $type, $page) = @_; + return NaturalDocs::File->JoinPaths( $self->IndexDirectory(), $self->RelativeIndexFileOf($type, $page) ); + }; + +# +# function: RelativeIndexFileOf +# +# Returns the output file name of the index file, relative to other index files. +# +# Parameters: +# +# type - The <TopicType> of the index. +# page - The page number. Undef is the same as one. +# +sub RelativeIndexFileOf #(type, page) + { + my ($self, $type, $page) = @_; + return NaturalDocs::Topics->NameOfType($type, 1, 1) . (defined $page && $page != 1 ? $page : '') . '.html'; + }; + + +# +# function: CSSDirectory +# +# Returns the directory of the CSS files. +# +sub CSSDirectory + { + my $self = shift; + return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'styles', 1); + }; + + +# +# Function: MainCSSFile +# +# Returns the location of the main CSS file. +# +sub MainCSSFile + { + my $self = shift; + return NaturalDocs::File->JoinPaths( $self->CSSDirectory(), 'main.css' ); + }; + + +# +# function: JavaScriptDirectory +# +# Returns the directory of the JavaScript files. +# +sub JavaScriptDirectory + { + my $self = shift; + return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'javascript', 1); + }; + + +# +# Function: MainJavaScriptFile +# +# Returns the location of the main JavaScript file. +# +sub MainJavaScriptFile + { + my $self = shift; + return NaturalDocs::File->JoinPaths( $self->JavaScriptDirectory(), 'main.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->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 <FileName> in local format, *not* in URL format. +# targetFile - The target <FileName> 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) -> string relativeURL + { + my ($self, $baseFile, $targetFile, $baseHasFileName) = @_; + + if ($baseHasFileName) + { $baseFile = NaturalDocs::File->NoFileName($baseFile) }; + + my $relativePath = NaturalDocs::File->MakeRelativePath($baseFile, $targetFile); + + return $self->ConvertAmpChars( NaturalDocs::File->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 <ADD_HIDDEN_BREAKS> 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/&/&/g; + $string =~ s/</</g; + $string =~ s/>/>/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/^\'/‘/gm; + $string =~ s/([\ \(\[\{])\'/$1‘/g; + $string =~ s/\'/’/g; + + $string =~ s/^\"/“/gm; + $string =~ s/([\ \(\[\{])\"/$1“/g; + $string =~ s/\"/”/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->AddDoubleSpaces($string); + + if ($addHiddenBreaks) + { $string = $self->AddHiddenBreaks($string); }; + + return $string; + }; + + +# +# Function: SymbolToHTMLSymbol +# +# Converts a <SymbolString> 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 <ConvertAmpChars()>. +# +sub SymbolToHTMLSymbol #(symbol) + { + my ($self, $symbol) = @_; + + my @identifiers = NaturalDocs::SymbolString->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 + # "<>& with their amp chars. But alas, Mozilla shows them as %20, etc. instead. It would have made for nice looking URLs. + $htmlSymbol =~ tr/ \"<>\?&%/_/d; + + return $htmlSymbol; + }; + + +# +# Function: NDMarkupToHTML +# +# Converts a block of <NDMarkup> to HTML. +# +# Parameters: +# +# sourceFile - The source <FileName> the <NDMarkup> appears in. +# text - The <NDMarkup> text to convert. +# symbol - The topic <SymbolString> the <NDMarkup> appears in. +# package - The package <SymbolString> the <NDMarkup> appears in. +# type - The <TopicType> the <NDMarkup> appears in. +# using - An arrayref of scope <SymbolStrings> the <NDMarkup> also has access to, or undef if none. +# +# Returns: +# +# The text in HTML. +# +sub NDMarkupToHTML #(sourceFile, text, symbol, package, type, using) + { + my ($self, $sourceFile, $text, $symbol, $package, $type, $using) = @_; + + my $dlSymbolBehavior; + + if ($type == ::TOPIC_ENUMERATION()) + { $dlSymbolBehavior = NaturalDocs::Languages->LanguageOf($sourceFile)->EnumValues(); } + elsif (NaturalDocs::Topics->TypeInfo($type)->Scope() == ::SCOPE_ALWAYS_GLOBAL()) + { $dlSymbolBehavior = ::ENUM_GLOBAL(); } + else + { $dlSymbolBehavior = ::ENUM_UNDER_PARENT(); }; + + my $output; + my $inCode; + + my @splitText = split(/(<\/?code>)/, $text); + + while (scalar @splitText) + { + $text = shift @splitText; + + if ($text eq '<code>') + { + $output .= '<blockquote><pre class=CCode>'; + $inCode = 1; + } + elsif ($text eq '</code>') + { + $output .= '</pre></blockquote>'; + $inCode = undef; + } + elsif ($inCode) + { + $text =~ s/\n/<br>/g; + $output .= $text; + } + else + { + # Format non-code text. + + # Convert quotes to fancy quotes. + $text =~ s/^\'/‘/gm; + $text =~ s/([\ \(\[\{])\'/$1‘/g; + $text =~ s/\'/’/g; + + $text =~ s/^"/“/gm; + $text =~ s/([\ \(\[\{])"/$1“/g; + $text =~ s/"/”/g; + + # Copyright symbols. Prevent conversion when part of (a), (b), (c) lists. + if ($text !~ /\(a\)/i) + { $text =~ s/\(c\)/©/gi; }; + + # Trademark symbols. + $text =~ s/\(tm\)/™/gi; + $text =~ s/\(r\)/®/gi; + + # Resolve and convert links. + $text =~ s/<link>([^<]+)<\/link>/$self->BuildTextLink($1, $package, $using, $sourceFile)/ge; + $text =~ s/<url>([^<]+)<\/url>/$self->BuildURLLink($1)/ge; + $text =~ s/<email>([^<]+)<\/email>/$self->BuildEMailLink($1)/eg; + + # Add double spaces too. + $text = $self->AddDoubleSpaces($text); + + # Paragraphs + $text =~ s/<p>/<p class=CParagraph>/g; + + # Bulleted lists + $text =~ s/<ul>/<ul class=CBulletList>/g; + + # Headings + $text =~ s/<h>/<h4 class=CHeading>/g; + $text =~ s/<\/h>/<\/h4>/g; + + # Description Lists + $text =~ s/<dl>/<table border=0 cellspacing=0 cellpadding=0 class=CDescriptionList>/g; + $text =~ s/<\/dl>/<\/table>/g; + + $text =~ s/<de>/<tr><td class=CDLEntry>/g; + $text =~ s/<\/de>/<\/td>/g; + + if ($dlSymbolBehavior == ::ENUM_GLOBAL()) + { $text =~ s/<ds>([^<]+)<\/ds>/$self->MakeDescriptionListSymbol(undef, $1)/ge; } + elsif ($dlSymbolBehavior == ::ENUM_UNDER_PARENT()) + { $text =~ s/<ds>([^<]+)<\/ds>/$self->MakeDescriptionListSymbol($package, $1)/ge; } + else # ($dlSymbolBehavior == ::ENUM_UNDER_TYPE()) + { $text =~ s/<ds>([^<]+)<\/ds>/$self->MakeDescriptionListSymbol($symbol, $1)/ge; } + + sub MakeDescriptionListSymbol #(package, text) + { + my ($self, $package, $text) = @_; + + $text = NaturalDocs::NDMarkup->RestoreAmpChars($text); + my $symbol = NaturalDocs::SymbolString->FromText($text); + + if (defined $package) + { $symbol = NaturalDocs::SymbolString->Join($package, $symbol); }; + + return + '<tr>' + . '<td class=CDLEntry>' + # The anchors are closed, but not around the text, to prevent the :hover CSS style from kicking in. + . '<a name="' . $self->SymbolToHTMLSymbol($symbol) . '"></a>' + . $text + . '</td>'; + }; + + $text =~ s/<dd>/<td class=CDLDescription>/g; + $text =~ s/<\/dd>/<\/td><\/tr>/g; + + $output .= $text; + }; + }; + + return $output; + }; + + +# +# Function: BuildTextLink +# +# Creates a HTML link to a symbol, if it exists. +# +# Parameters: +# +# text - The link text +# package - The package <SymbolString> the link appears in, or undef if none. +# using - An arrayref of additional scope <SymbolStrings> the link has access to, or undef if none. +# sourceFile - The <FileName> the link appears in. +# +# 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 #(text, package, using, sourceFile) + { + my ($self, $text, $package, $using, $sourceFile) = @_; + + my $plainText = $self->RestoreAmpChars($text); + + my $symbol = NaturalDocs::SymbolString->FromText($plainText); + my $target = NaturalDocs::SymbolTable->References(::REFERENCE_TEXT(), $symbol, $package, $using, $sourceFile); + + if (defined $target) + { + my $targetFile; + + if ($target->File() ne $sourceFile) + { $targetFile = $self->MakeRelativeURL( $self->OutputFileOf($sourceFile), $self->OutputFileOf($target->File()), 1 ); }; + # else leave it undef + + my $targetTooltipID = $self->BuildToolTip($target->Symbol(), $sourceFile, $target->Type(), + $target->Prototype(), $target->Summary()); + + my $toolTipProperties = $self->BuildToolTipLinkProperties($targetTooltipID); + + return '<a href="' . $targetFile . '#' . $self->SymbolToHTMLSymbol($target->Symbol()) . '" ' + . 'class=L' . NaturalDocs::Topics->NameOfType($target->Type(), 0, 1) . ' ' . $toolTipProperties . '>' . $text . '</a>'; + } + else + { + return '<' . $text . '>'; + }; + }; + + +# +# Function: BuildURLLink +# +# Creates a HTML link to an external URL. Long URLs will have hidden breaks to allow them to wrap. +# +# Parameters: +# +# url - The URL to link to. +# +# Returns: +# +# The HTML link, complete with tags. +# +sub BuildURLLink #(url) + { + my ($self, $url) = @_; + + $url = $self->RestoreAmpChars($url); + + if (length $url < 50) + { return '<a href="' . $url . '" class=LURL>' . $self->ConvertAmpChars($url) . '</a>'; }; + + my @segments = split(/([\,\&\/])/, $url); + my $output = '<a href="' . $url . '" class=LURL>'; + + # Get past the first batch of slashes, since we don't want to break on things like http://. + + $output .= $self->ConvertAmpChars($segments[0]); + + my $i = 1; + while ($i < scalar @segments && ($segments[$i] eq '/' || !$segments[$i])) + { + $output .= $segments[$i]; + $i++; + }; + + # Now break on each one of those symbols. + + while ($i < scalar @segments) + { + # Spaces don't wrap in IE for some reason. Need to use dashes as well. + if ($segments[$i] eq ',' || $segments[$i] eq '/' || $segments[$i] eq '&') + { $output .= '<span class=HB>- </span>'; }; + + $output .= $self->ConvertAmpChars($segments[$i]); + $i++; + }; + + $output .= '</a>'; + 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: +# +# address - The e-mail address. +# +# Returns: +# +# The HTML e-mail link, complete with tags. +# +sub BuildEMailLink #(address) + { + my ($self, $address) = @_; + my @splitAddress; + + + # Hack the address up. We want two user pieces and two host pieces. + + my ($user, $host) = split(/\@/, $address); + + my $userSplit = length($user) / 2; + + push @splitAddress, substr($user, 0, $userSplit); + push @splitAddress, substr($user, $userSplit); + + push @splitAddress, '@'; + + my $hostSplit = length($host) / 2; + + push @splitAddress, substr($host, 0, $hostSplit); + push @splitAddress, 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. + + return + "<a href=\"#\" onClick=\"location.href='mai' + 'lto:' + '" . join("' + '", @splitAddress) . "'; return false;\" class=LEMail>" + . $splitAddress[0] . '<span style="display: none">.nosp@m.</span>' . $splitAddress[1] + . '<span>@</span>' + . $splitAddress[3] . '<span style="display: none">.nosp@m.</span>' . $splitAddress[4] + . '</a>'; + }; + + +# +# 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 <BuildToolTip()> 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 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. + + ("|&[lr][sd]quo;|[\'\"\]\}\)]?) # Tolerate closing quotes, parenthesis, etc. + ((?:<[^>]+>)*) # Tolerate tags + + \ # The space + (?![a-z]) # Not followed by a lowercase character. + + /$1$2$3 \ /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. + + \. + + ("|&[lr][sd]quo;|[\'\"\]\}\)]?) # Tolerate closing quotes, parenthesis, etc. + ((?:<[^>]+>)*) # 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]$/ && exists $abbreviations{ lc($leadWord) } ) + { return ' '; } + else + { return ' '; }; + }; + + 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/&/&/g; + $text =~ s/\"/"/g; + $text =~ s/</</g; + $text =~ s/>/>/g; + + return $text; + }; + + +# +# Function: RestoreAmpChars +# +# Restores all amp characters to their original state. This works with both <NDMarkup> amp chars and fancy quotes. +# +# Parameters: +# +# text - The text to convert. +# +# Returns: +# +# The converted text. +# +sub RestoreAmpChars #(text) + { + my ($self, $text) = @_; + + $text = NaturalDocs::NDMarkup->RestoreAmpChars($text); + $text =~ s/&[lr]squo;/'/g; + $text =~ s/&[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,})|:+|->|\\|\/))(\w)/$1 . '<span class=HB> <\/span>' . $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->Content(); }; + + foreach my $entry (@$arrayref) + { + if ($entry->Type() == ::MENU_FILE()) + { + return $entry; + } + elsif ($entry->Type() == ::MENU_GROUP()) + { + my $result = $self->FindFirstFile($entry->GroupContent()); + if (defined $result) + { return $result; }; + }; + }; + + return undef; + }; + + +# +# Function: ExpandMenu +# +# Determines which groups should be expanded. +# +# Parameters: +# +# sourceFile - The source <FileName> to use if you're looking for a source file. +# indexType - The index <TopicType> to use if you're looking for an index. +# selectionHierarchy - The <FileName> 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 <menuSelectionHierarchy>, though it may contain more. +# +sub ExpandMenu #(FileName sourceFile, TopicType indexType, NaturalDocs::Menu::Entry[] selectionHierarchy, int rootLength) -> 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 < 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->[0]->GroupContent(); } + else + { $content = NaturalDocs::Menu->Content(); }; + + $bottomIndex = 0; + + while ($bottomIndex < scalar @$content && + !($content->[$bottomIndex]->Type() == ::MENU_FILE() && + $content->[$bottomIndex]->Target() eq $sourceFile) && + !($content->[$bottomIndex]->Type() != ::MENU_INDEX() && + $content->[$bottomIndex]->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 && scalar @$menuSelectionHierarchy) + { + if (scalar @$menuSelectionHierarchy > 1) + { $content = $menuSelectionHierarchy->[1]->GroupContent(); } + else + { $content = NaturalDocs::Menu->Content(); }; + + $bottomIndex = 0; + + while ($bottomIndex < scalar @$content && + $content->[$bottomIndex] != $menuSelectionHierarchy->[0]) + { $bottomIndex++; }; + + $topIndex = $bottomIndex - 1; + $bottomIndex++; # Increment past our own group. + $hasSubGroups = undef; + } + else + { last; }; + } + + # No more passes. + else + { last; }; + + + while ( ($topIndex >= 0 || $bottomIndex < scalar @$content) && $length < MENU_LENGTH_LIMIT) + { + # We do the bottom first. + + while ($bottomIndex < scalar @$content && $content->[$bottomIndex]->Type() != ::MENU_GROUP()) + { $bottomIndex++; }; + + if ($bottomIndex < scalar @$content) + { + my $bottomEntry = $content->[$bottomIndex]; + $hasSubGroups = 1; + + if ($length + $menuGroupLengths{$bottomEntry} <= MENU_LENGTH_LIMIT) + { + $length += $menuGroupLengths{$bottomEntry}; + push @$toExpand, $menuGroupNumbers{$bottomEntry}; + $bottomIndex++; + } + else + { $bottomIndex = scalar @$content; }; + }; + + # Top next. + + while ($topIndex >= 0 && $content->[$topIndex]->Type() != ::MENU_GROUP()) + { $topIndex--; }; + + if ($topIndex >= 0) + { + my $topEntry = $content->[$topIndex]; + $hasSubGroups = 1; + + if ($length + $menuGroupLengths{$topEntry} <= 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 <FileName> to use if you're looking for a source file. +# indexType - The index <TopicType> to use if you're looking for an index. +# +# Returns: +# +# An arrayref of the <NaturalDocs::Menu::Entry> 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) -> NaturalDocs::Menu::Entry[] selectionHierarchy + { + my ($self, $sourceFile, $indexType) = @_; + + my $hierarchy = [ ]; + + $self->FindMenuSelection($sourceFile, $indexType, $hierarchy, NaturalDocs::Menu->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 <FileName> to use if you're looking for a source file. +# indexType - The index <TopicType> to use if you're looking for an index. +# hierarchyRef - A reference to the menu selection hierarchy. +# entries - An arrayref of <NaturalDocs::Menu::Entries> 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) -> bool hasSelection + { + my ($self, $sourceFile, $indexType, $hierarchyRef, $entries) = @_; + + foreach my $entry (@$entries) + { + if ($entry->Type() == ::MENU_GROUP()) + { + # If the subgroup has the selection... + if ( $self->FindMenuSelection($sourceFile, $indexType, $hierarchyRef, $entry->GroupContent()) ) + { + push @$hierarchyRef, $entry; + return 1; + }; + } + + elsif ($entry->Type() == ::MENU_FILE()) + { + if ($sourceFile eq $entry->Target()) + { return 1; }; + } + + elsif ($entry->Type() == ::MENU_INDEX()) + { + if ($indexType eq $entry->Target) + { return 1; }; + }; + }; + + return 0; + }; + + +# +# Function: ResetToolTips +# +# Resets the <ToolTip Package Variables> 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; |