#! /usr/bin/perl -w # # Script name: gatherHeaderDoc # Synopsis: Finds all HeaderDoc generated docs in an input # folder and creates a top-level HTML page to them # # Last Updated: $Date: 2012/10/11 16:16:46 $ # # Copyright (c) 1999-2004 Apple Computer, Inc. All rights reserved. # # @APPLE_LICENSE_HEADER_START@ # # This file contains Original Code and/or Modifications of Original Code # as defined in and that are subject to the Apple Public Source License # Version 2.0 (the 'License'). You may not use this file except in # compliance with the License. Please obtain a copy of the License at # http://www.opensource.apple.com/apsl/ and read it before using this # file. # # The Original Code and all software distributed under the License are # distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER # EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, # INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. # Please see the License for the specific language governing rights and # limitations under the License. # # @APPLE_LICENSE_HEADER_END@ # # $Revision: 1349997406 $ ###################################################################### # /*! # @header # The gatherHeaderDoc.pl tool (gatherheaderdoc # when installed) gathers up a folder full of HTML output from # headerDoc2HTML.pl (headerdoc2html) # and generates a master table of contents. # # This document provides API-level documentation # on the tool's internals. For user documentation, see # {@linkdoc //apple_ref/doc/uid/TP40001215 HeaderDoc User Guide}. # @indexgroup HeaderDoc Tools # */ # /*! @abstract # Usually a slash (/); in MacPerl, a colon(:). # */ my $pathSeparator; # /*! @abstract # A 1 if MacPerl, else 0. # */ my $isMacOS; # /*! @abstract # Path to the Perl modules in the source directory. # */ my $uninstalledModulesPath; # /*! @abstract # Path to the Perl modules in the developer tools package. # */ my $devtoolsModulesPath; # /*! @abstract # Indicates that an internal link resolution tool was found. # */ my $has_resolver; # /* */ sub resolveLinks($$$); # /*! # @abstract # Storage for the groupHierLimit config file field. # */ $HeaderDoc::groupHierLimit = undef; # /*! # @abstract # Storage for the groupHierSubgroupLimit config file field. # */ $HeaderDoc::groupHierSubgroupLimit = undef; BEGIN { use FindBin qw ($Bin); if ($^O =~ /MacOS/i) { $pathSeparator = ":"; $isMacOS = 1; #$Bin seems to return a colon after the path on certain versions of MacPerl #if it's there we take it out. If not, leave it be #WD-rpw 05/09/02 ($uninstalledModulesPath = $FindBin::Bin) =~ s/([^:]*):$/$1/; } else { $pathSeparator = "/"; $isMacOS = 0; } $uninstalledModulesPath = "$FindBin::Bin"."$pathSeparator"."Modules"; $devtoolsModulesPath = "$FindBin::Bin"."$pathSeparator".".."."$pathSeparator"."share"."$pathSeparator"."headerdoc"."$pathSeparator"."Modules"; $HeaderDoc::use_styles = 0; } use strict; # use Cwd; use File::Basename; use File::Find; use File::Copy; # use Carp qw(cluck); use lib $uninstalledModulesPath; use lib $devtoolsModulesPath; use POSIX; # /*! @abstract # Set if you pass the -d flag. # */ my $generateDocSet = 0; # /*! @abstract # Set if you pass the -n flag with the -d flag. # */ my $skipTOC = 0; # /*! @abstract # Set if -N flag is set (disable link resolution). # */ my $noResolve = 0; # /*! @abstract # Set if you pass the -w flag. # */ $HeaderDoc::useWhatIs = 0; $has_resolver = 1; eval "use HeaderDoc::LinkResolver qw (resolveLinks); 1" || do { $has_resolver = 0; }; # print STDERR "HR: $has_resolver\n"; if ($has_resolver) { print STDERR "LinkResolver will be used to resolve cross-references.\n"; } # Modules specific to gatherHeaderDoc use HeaderDoc::DocReference; use HeaderDoc::Utilities qw(findRelativePath safeName printArray printHash updateHashFromConfigFiles getHashFromConfigFile resolveLinks sanitize stripTags getDefaultEncoding); $HeaderDoc::modulesPath = $INC{'HeaderDoc/Utilities.pm'}; $HeaderDoc::modulesPath =~ s/Utilities.pm$//s; # print STDERR "MP: ".$HeaderDoc::modulesPath."\n"; # /*! @abstract # Always 1. # */ my $debugging = 1; ######################################## Design Overview ################################### # - We scan input directory for frameset files (index.html, by default). # - For each frameset file, we look for a special HTML comment (left by HeaderDoc) # that tell us the name of the header/class and the type (header or cppclass). # - We create a DocReference object to store this info, and also the path to the # frameset file. # - We run through array of DocRef objs and create a master TOC based on the info # - Finally, we visit each TOC file in each frameset and add a "[Top]" link # back to the master TOC. [This is fragile in the current implementation, since # we find TOCs based on searching for a file called "toc.html" in the frameset dir.] # ########################## Get command line arguments and flags ####################### # /*! @abstract # An array of HeaderDoc-generated files. # */ my @inputFiles; # /*! @abstract # An array of all HTML files. # */ my @contentFiles; # /*! @abstract # The diretory for input. # */ my $inputDir; # /*! @abstract # Storage for the externalXRefFiles config # file field. # */ my $externalXRefFiles = ""; # /*! @abstract # Storage for the contents of the "target=..." attribute # for generated links. # @discussion # Set by the -t flag on the command line. # */ my $linktarget = ""; use Getopt::Std; # /*! @abstract # Storage for getopt(). # */ my %options = (); # /*! @abstract # Per-output-file letter link status variable. # # @discussion # Used for determining whether to put in an anchor # for jumping to the first two letters of a given symbol. # (Only inserted the first time those two letters appear # in a given .) # */ my %letters_linked = (); # /*! @abstract # Per-group letter link status variable. # # @discussion # Used for determining whether to put in an anchor # for jumping to the first two letters of a given symbol. # (Only inserted the first time those two letters appear # in a given .) # */ my %group_letters_linked = (); getopts("Nc:dnwt:x:",\%options); # The options are handled after processing config file so they can # override behavior. However, we need to handle the options first # before checking for input file names (which we should do first # to avoid wasting a lot of time before telling the user he/she # did something wrong). # /*! @abstract # The main TOC file (e.g. index.html). # */ my $masterTOCFileName = ""; # my $bookxmlname = ""; if (($#ARGV == 0 || $#ARGV == 1 || $#ARGV == 2) && (-d $ARGV[0])) { $inputDir = $ARGV[0]; if ($#ARGV) { $masterTOCFileName = $ARGV[1]; } # if ($#ARGV > 1) { # $bookxmlname = $ARGV[2]; # } } else { die "You must specify a single input directory for processing.\n"; } ########################## Setup from Configuration File ####################### my $localConfigFileName = "headerDoc2HTML.config"; my $preferencesConfigFileName = "com.apple.headerDoc2HTML.config"; my $homeDir; my $usersPreferencesPath; my $systemPreferencesPath; #added WD-rpw 07/30/01 to support running on MacPerl #modified WD-rpw 07/01/02 to support the MacPerl 5.8.0 if ($^O =~ /MacOS/i) { eval { require "FindFolder.pl"; $homeDir = MacPerl::FindFolder("D"); #D = Desktop. Arbitrary place to put things $usersPreferencesPath = MacPerl::FindFolder("P"); #P = Preferences }; if ($@) { import Mac::Files; $homeDir = Mac::Files::FindFolder(kOnSystemDisk(), kDesktopFolderType()); $usersPreferencesPath = Mac::Files::FindFolder(kOnSystemDisk(), kPreferencesFolderType()); } $systemPreferencesPath = $usersPreferencesPath; } else { $homeDir = (getpwuid($<))[7]; $usersPreferencesPath = $homeDir.$pathSeparator."Library".$pathSeparator."Preferences"; $systemPreferencesPath = "/Library/Preferences"; } my $usrPreferencesPath = "/usr/share/headerdoc/conf"; my $devtoolsPreferencesPath = "$FindBin::Bin"."$pathSeparator".".."."$pathSeparator"."share"."$pathSeparator"."headerdoc"."$pathSeparator"."conf"; my $CWD = getcwd(); my @configFiles = ($devtoolsPreferencesPath.$pathSeparator.$preferencesConfigFileName, $systemPreferencesPath.$pathSeparator.$preferencesConfigFileName, $usersPreferencesPath.$pathSeparator.$preferencesConfigFileName, $Bin.$pathSeparator.$localConfigFileName, $CWD.$pathSeparator.$localConfigFileName); # ($Bin.$pathSeparator.$localConfigFileName, $usersPreferencesPath.$pathSeparator.$preferencesConfigFileName); # default configuration, which will be modified by assignments found in config files. # The default values listed in this hash must be the same as those in the identical # hash in headerDoc2HTML--so that links between the frameset and the masterTOC work. my %config = ( defaultFrameName => "index.html", masterTOCName => "MasterTOC.html", groupHierLimit => 0, groupHierSubgroupLimit => 0 ); if ($options{c}) { @configFiles = ( $options{c} ); } %config = &updateHashFromConfigFiles(\%config,\@configFiles); my $framesetFileName; my @TOCTemplateList = (); my @TOCNames = (); my $framework = ""; my $frameworknestlevel = -1; my $frameworkShortName = ""; my $frameworkpath = ""; my $headerpath = ""; my $frameworkrelated = ""; my $frameworkUID = ""; my $frameworkCopyrightString = ""; my $landingPageUID = ""; my $landingPageFrameworkUID = ""; my $stripDotH = 0; my $gather_functions = 0; my $gather_types = 0; my $gather_properties = 0; my $gather_globals_and_constants = 0; my $gather_man_pages = 0; my $apiUIDPrefix = "apple_ref"; my $compositePageName = "CompositePage.html"; my $classAsComposite = 0; my $externalAPIUIDPrefixes = ""; my %usedInTemplate = (); # /*! # @abstract # Controls whether to add the [Top] link above the TOC # when in frame-style or iframe-style TOC mode. # @discussion # This value is set based on the addTopLink # line in the configuration file, or 1 by default. # */ $HeaderDoc::addTopLink = 1; if (defined $config{"addTopLink"}) { $HeaderDoc::addTopLink = $config{"addTopLink"}; } if (defined $config{"dateFormat"}) { $HeaderDoc::datefmt = $config{"dateFormat"}; if ($HeaderDoc::datefmt !~ /\S/) { $HeaderDoc::datefmt = "%B %d, %Y"; } } else { $HeaderDoc::datefmt = "%B %d, %Y"; } use HeaderDoc::APIOwner; my $tocEncoding = getDefaultEncoding(); # Backwards compatibility with 8.7+patches. May be removed after 8.8. if (defined ($config{"tocTemplateEncoding"}) && length($config{"tocTemplateEncoding"})) { warn("The configuration key tocTemplateEncoding is deprecated.\nUse TOCTemplateEncoding instead."); $tocEncoding = $config{"tocTemplateEncoding"} } if (defined ($config{"TOCTemplateEncoding"}) && length($config{"TOCTemplateEncoding"})) { $tocEncoding = $config{"TOCTemplateEncoding"} } HeaderDoc::APIOwner::fix_date($tocEncoding); my ($sec,$min,$hour,$mday,$mon,$yr,$wday,$yday,$isdst) = localtime(time()); my $yearStamp = strftime("%Y", $sec, $min, $hour, $mday, $mon, $yr, $wday, $yday, $isdst); my $dateStamp = HeaderDoc::HeaderElement::strdate($mon, $mday, $yr + 1900, $tocEncoding); # die("DS: $dateStamp\n"); if (defined $config{"styleImports"}) { $HeaderDoc::styleImports = $config{"styleImports"}; $HeaderDoc::styleImports =~ s/[\n\r]/ /sgo; $HeaderDoc::use_styles = 1; } if (defined $config{"groupHierLimit"}) { $HeaderDoc::groupHierLimit = $config{"groupHierLimit"}; } if (defined $config{"groupHierSubgroupLimit"}) { $HeaderDoc::groupHierSubgroupLimit = $config{"groupHierSubgroupLimit"}; } if (defined $config{"tocStyleImports"}) { $HeaderDoc::tocStyleImports = $config{"tocStyleImports"}; $HeaderDoc::tocStyleImports =~ s/[\n\r]/ /sgo; $HeaderDoc::use_styles = 1; } if (defined $config{"textStyle"}) { HeaderDoc::APIOwner->setStyle("text", $config{"textStyle"}); } if (defined $config{"copyrightOwner"}) { # /*! # @abstract # The copyright owner (from the config file). # */ $HeaderDoc::copyrightOwner = $config{"copyrightOwner"}; } if (defined $config{"commentStyle"}) { HeaderDoc::APIOwner->setStyle("comment", $config{"commentStyle"}); } if (defined $config{"preprocessorStyle"}) { HeaderDoc::APIOwner->setStyle("preprocessor", $config{"preprocessorStyle"}); } if (defined $config{"funcNameStyle"}) { HeaderDoc::APIOwner->setStyle("function", $config{"funcNameStyle"}); } if (defined $config{"stringStyle"}) { HeaderDoc::APIOwner->setStyle("string", $config{"stringStyle"}); } if (defined $config{"charStyle"}) { HeaderDoc::APIOwner->setStyle("char", $config{"charStyle"}); } if (defined $config{"numberStyle"}) { HeaderDoc::APIOwner->setStyle("number", $config{"numberStyle"}); } if (defined $config{"keywordStyle"}) { HeaderDoc::APIOwner->setStyle("keyword", $config{"keywordStyle"}); } if (defined $config{"typeStyle"}) { HeaderDoc::APIOwner->setStyle("type", $config{"typeStyle"}); } if (defined $config{"paramStyle"}) { HeaderDoc::APIOwner->setStyle("param", $config{"paramStyle"}); } if (defined $config{"varStyle"}) { HeaderDoc::APIOwner->setStyle("var", $config{"varStyle"}); } if (defined $config{"templateStyle"}) { HeaderDoc::APIOwner->setStyle("template", $config{"templateStyle"}); } if (defined $config{"externalXRefFiles"}) { $externalXRefFiles = $config{"externalXRefFiles"}; } if (defined $config{"externalAPIUIDPrefixes"}) { $externalAPIUIDPrefixes = $config{"externalAPIUIDPrefixes"}; } if (defined $config{"defaultFrameName"}) { $framesetFileName = $config{"defaultFrameName"}; } if (defined $config{"apiUIDPrefix"}) { $apiUIDPrefix = $config{"apiUIDPrefix"}; } if (defined $config{"compositePageName"}) { $compositePageName = $config{"compositePageName"}; } if (defined $config{"classAsComposite"}) { $classAsComposite = $config{"classAsComposite"}; $classAsComposite =~ s/\s*//; } else { $classAsComposite = 0; } if (defined $config{"masterTOCName"} && $masterTOCFileName eq "") { $masterTOCFileName = $config{"masterTOCName"}; } if (defined $config{"stripDotH"}) { $stripDotH = $config{"stripDotH"}; } # /*! # @abstract # The background color for the built-in (default) template. # */ $GHD::bgcolor = "#ffffff"; my $TOCTemplateFile = "HEADERDOC_DEFAULT_INTERNAL_TEMPLATE"; if (defined $config{"TOCTemplateFile"}) { $TOCTemplateFile = $config{"TOCTemplateFile"}; } my $oldRecSep = $/; undef $/; # read in files as strings my @filelist = split(/\s/, $TOCTemplateFile); foreach my $file (@filelist) { my %used = (); my $TOCTemplate = ""; my $found = 0; my $foundpath = ""; if ($file eq "HEADERDOC_DEFAULT_INTERNAL_TEMPLATE") { $found = 1; $foundpath = "n/a"; $TOCTemplate = default_template(); } else { print STDERR "Searching for $file\n"; my @templateFiles = ($devtoolsPreferencesPath.$pathSeparator.$file, $usrPreferencesPath.$pathSeparator.$file, $systemPreferencesPath.$pathSeparator.$file, $usersPreferencesPath.$pathSeparator.$file, $Bin.$pathSeparator.$file, $file); foreach my $filename (@templateFiles) { if (open(TOCFILE, "<$filename")) { $TOCTemplate = ; close(TOCFILE); $found = 1; $foundpath = $filename; } } if (!$found) { die("Template file $file not found.\n"); } else { print STDERR "Found at $foundpath\n"; } } push(@TOCTemplateList, $TOCTemplate); push(@TOCNames, basename($file)); if ($TOCTemplate =~ /\$\$\s*typelist/) { $gather_types = 1; $used{type} = 1; } if ($TOCTemplate =~ /\$\$\s*proplist/) { $gather_properties = 1; $used{prop} = 1; } if ($TOCTemplate =~ /\$\$\s*datalist/) { $gather_globals_and_constants = 1; $used{data} = 1; } if ($TOCTemplate =~ /\$\$\s*functionlist/) { $gather_functions = 1; $used{function} = 1; } if ($TOCTemplate =~ /\$\$\s*manpagelist/) { $gather_man_pages = 1; $used{manpage} = 1; } if ($TOCTemplate =~ /\$\$\s*headerlist/) { $used{header} = 1; } if ($TOCTemplate =~ /\$\$\s*macrolist/) { $used{macro} = 1; } if ($TOCTemplate =~ /\$\$\s*protocollist/) { $used{protocol} = 1; } if ($TOCTemplate =~ /\$\$\s*categorylist/) { $used{category} = 1; } if ($TOCTemplate =~ /\$\$\s*classlist/) { $used{class} = 1; } if ($TOCTemplate =~ /\$\$\s*comintlist/) { $used{comint} = 1; } $usedInTemplate{$TOCTemplate} = \%used; } $/ = $oldRecSep; my $useBreadcrumbs = 0; if (defined $config{"useBreadcrumbs"}) { $useBreadcrumbs = $config{"useBreadcrumbs"}; } ########################## Handle command line flags ####################### if ($options{w}) { $HeaderDoc::useWhatIs = 1; } if ($options{d}) { $generateDocSet = 1; if ($options{n}) { $skipTOC = 1; } } if ($options{N}) { $noResolve = 1; } if ($options{x}) { $externalXRefFiles = $options{x}; } if ($options{t}) { $linktarget = " target=\"".$options{t}."\""; } ########################## Input Folder and Files ####################### if ($^O =~ /MacOS/i) { find(\&getFiles, $inputDir); $inputDir =~ s/([^:]*):$/$1/; #WD-rpw 07/01/02 } else { $inputDir =~ s|(.*)/$|$1|; # get rid of trailing slash, if any if ($inputDir !~ /^\//) { # not absolute path -- !!! should check for ~ my $cwd = getcwd(); $inputDir = $cwd.$pathSeparator.$inputDir; } &find({wanted => \&getFiles, follow => 1}, $inputDir); } unless (@inputFiles) { print STDERR "No valid input files specified. \n\n"; exit(-1)}; # print STDERR "GatherFunc: $gather_functions\n"; # print STDERR "TT: $TOCTemplate\n"; # /*! @abstract # Returns a list of the HTML files in the input directory. # */ sub getFiles { my $filePath = $File::Find::name; my $fileName = $_; my $localDebug = 0; my $basePath = dirname($filePath); my $dirName = basename($basePath); print STDERR "$fileName ($filePath): " if ($localDebug); if ($fileName =~ /$framesetFileName/) { print STDERR "HTML frameset\n" if ($localDebug); push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($dirName =~ /^(man|cat)[\w\d]+$/ && $gather_man_pages) { print STDERR "Man Page\n" if ($localDebug); push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($fileName =~ /Constants\.html$/ && $gather_globals_and_constants && !$classAsComposite) { print STDERR "Constants\n" if ($localDebug); push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($fileName =~ /Vars\.html$/ && $gather_globals_and_constants && !$classAsComposite) { print STDERR "Vars\n" if ($localDebug); push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($fileName =~ /DataTypes\.html$/ && $gather_types && !$classAsComposite) { print STDERR "DataTypes\n" if ($localDebug); push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($fileName =~ /Structs\.html$/ && $gather_types && !$classAsComposite) { print STDERR "Structs\n" if ($localDebug); push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($fileName =~ /Enums\.html$/ && $gather_types && !$classAsComposite) { print STDERR "Enums\n" if ($localDebug); push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($fileName =~ /Methods\.html$/ && $gather_functions && !$classAsComposite) { print STDERR "Methods\n" if ($localDebug); push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($fileName =~ /Functions\.html$/ && $gather_functions && !$classAsComposite) { print STDERR "Functions\n" if ($localDebug); push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($fileName =~ /doc\.html$/ && !$classAsComposite) { print STDERR "Framework Documentation\n" if ($localDebug); # Framework (maybe) push(@inputFiles, $filePath); push(@contentFiles, $filePath); } elsif ($fileName !~ /toc\.html$/) { print STDERR "Other Content\n" if ($localDebug); # Don't push the TOCs. if ($classAsComposite && $fileName =~ /\Q$compositePageName\E$/) { push(@inputFiles, $filePath); } push(@contentFiles, $filePath); } else { print STDERR "toc.\n" if ($localDebug); } } ########################## Find HeaderDoc Comments ####################### my @fileRefSets; my @headerFramesetRefs; my @propFramesetRefs; my @dataFramesetRefs; my @macroFramesetRefs; my @typeFramesetRefs; my @comintFramesetRefs; my @classFramesetRefs; my @manpageFramesetRefs; my @categoryFramesetRefs; my @protocolFramesetRefs; my @functionRefs; my $frameworkabstract = ""; my $frameworkdiscussion = ""; $oldRecSep = $/; undef $/; # read in files as strings my $localDebug = 0; my %groups = (); $groups{" "}=""; $| = 1; print STDERR "Processing..."; foreach my $file (@inputFiles) { my @perFileDocRefs = (); if (-f $file) { open (INFILE, "<$file") || die "Can't open $file: $!\n"; my $fileString = ; close INFILE; my $fileStringCopy = $fileString; while ($fileStringCopy =~ s/<\!--\s+(headerDoc\s*=.*?)-->(.*)/$2/s) { my $fullComment = $1; my $tail = $2; my $inDiscussion = 0; my $inFrameworkDiscussion = 0; my $inFrameworkAbstract = 0; my $inAbstract = 0; my $inDeclaration = 0; my $inPath = 0; my $inHeaderPath = 0; my $inRelated = 0; my $inFWUID = 0; my $inFWCopyright = 0; my @stockpairs = split(/;/, $fullComment); my @pairs = (); my $discussion = ""; my $abstract = ""; my $declaration = ""; my $temp = ""; # print STDERR "FC: $fullComment\n"; foreach my $stockpair (@stockpairs) { if (length($temp)) { $temp .= $stockpair; if ($temp !~ /\\$/) { push(@pairs, $temp); $temp = ""; } } else { if ($stockpair =~ /\\$/) { $temp = $stockpair; $temp =~ s/\\$/;/s; } else { push(@pairs, $stockpair); $temp = ""; } } } my $docRef = HeaderDoc::DocReference->new(); $docRef->path($file); # print STDERR "PATH: $file\n"; print STDERR "."; foreach my $pair (@pairs) { my ($key, $value) = split(/=/, $pair, 2); $key =~ s/^\s+|\s+$//; $value =~ s/^\s+|\s+$//; # print STDERR "KEY: $key VALUE: $value\n"; SWITCH: { ($key =~ /indexgroup/) && do { my $group = $value; $group =~ s/^\s*//sg; $group =~ s/\s*$//sg; $group =~ s/\\;/;/sg; $docRef->group($group); $groups{$group}=1; # print STDERR "SAW $group\n"; }; ($key =~ /headerDoc/) && do { $docRef->type($value); if ($value =~ /discussion/) { $inDiscussion = 1; } if ($value =~ /frameworkdiscussion/) { if (rightframework($file)) { $inFrameworkDiscussion = 1; } } if ($value =~ /declaration/) { $inDeclaration = 1; } if ($value =~ /abstract/) { $inAbstract = 1; } if ($value =~ /frameworkabstract/) { if (rightframework($file)) { $inFrameworkAbstract = 1; } } if ($value =~ /frameworkpath/) { if (rightframework($file)) { $inPath = 1; } } if ($value =~ /headerpath/) { if (rightframework($file)) { $inHeaderPath = 1; } } if ($value =~ /frameworkrelated/) { if (rightframework($file)) { $inRelated = 1; } } if ($value =~ /frameworkuid/) { # print STDERR "FWUID DETECTED ($value)\n"; if (rightframework($file)) { $inFWUID = 1; # print STDERR "RIGHT FILE\n"; # $frameworkUID = $value; # $frameworkUID =~ s/^\s*//sg; # $frameworkUID =~ s/\s*$//sg; } }; if ($value =~ /frameworkcopyright/) { # print STDERR "FWCopyright DETECTED ($value)\n"; if (rightframework($file)) { $inFWCopyright = 1; # print STDERR "RIGHT FILE\n"; # $frameworkUID = $value; # $frameworkUID =~ s/^\s*//sg; # $frameworkUID =~ s/\s*$//sg; } }; last SWITCH; }; ($key =~ /shortname/) && do { $docRef->shortname($value); last SWITCH; }; ($key =~ /uid/) && do { # print STDERR "DOCREF: $docRef NOW "; my $newDocRef = $docRef->uid($value, $file); if ($newDocRef->path() eq $file) { $docRef = $newDocRef; # } else { # die("DRPATH != NEWDRPATH\nCACHE: ".$newDocRef->path()."\nPROCESSING: $file\n"); } # print STDERR "$docRef\n"; }; ($key =~ /mansrc/) && do { # print STDERR "MAN SOURCE: \"$value\"\n"; $docRef->mansrc($value); }; ($key =~ /name/) && do { $docRef->name($value); if ($inFrameworkDiscussion && $value =~ /start/) { $frameworkdiscussion = $tail; $frameworkdiscussion =~ s/"; $out .= genTable($manpagesLinkString{"hd_master_letters_linked"}, $field, "hd_master_letters_linked", "man", 0); $out .= ""; } else { foreach my $group (groupsort(keys(%groups))) { if ($group =~ /\S/) { # All man pages are in a section, so don't emit noise for the empty group. my $group_id = $group; $group_id =~ s/\s/_/s; $group_id = "group_".$group_id; $out .= ""; $out .= genTable($manpagesLinkString{$group}, $field, $group, "man", 1); $out .= ""; } } } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*oocontainersection/) && do { if (!$seenComInts && !$seenClasses && !$seenCategories && !$seenProtocols) { $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/oocontainersection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*comintsection/) && do { if (!$seenComInts) { $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/comintsection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*comintlist/) && do { if ($seenComInts) { $field =~ s/\@\@.*//s; foreach my $group (groupsort(keys(%groups))) { $out .= genTable($comintsLinkString{$group}, $field, $group, "comint"); } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*classsection/) && do { if (!$seenClasses) { $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/classsection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*classlist/) && do { if ($seenClasses) { $field =~ s/\@\@.*//s; foreach my $group (groupsort(keys(%groups))) { $out .= genTable($classesLinkString{$group}, $field, $group, "class"); } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*categorysection/) && do { if (!$seenCategories) { # @@@ Debug checkpoint for categories $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/categorysection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*categorylist/) && do { if ($seenCategories) { $field =~ s/\@\@.*//s; foreach my $group (groupsort(keys(%groups))) { $out .= genTable($categoriesLinkString{$group}, $field, $group, "category"); } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*protocolsection/) && do { if (!$seenProtocols) { # @@@ Debug checkpoint for protocols $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/protocolsection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*protocollist/) && do { if ($seenProtocols) { $field =~ s/\@\@.*//s; foreach my $group (groupsort(keys(%groups))) { $out .= genTable($protocolsLinkString{$group}, $field, $group, "protocol"); } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*macrosection/) && do { if (!$seenMacros) { $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/macrosection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*macrolist/) && do { if ($seenMacros) { $field =~ s/\@\@.*//s; foreach my $group (groupsort(keys(%groups))) { $out .= genTable($macroLinkString{$group}, $field, $group, "macro"); } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*propsection/) && do { if (!$seenProp) { # @@@ Debug checkpoint for properties $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/propsection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*proplist/) && do { if ($seenProp) { $field =~ s/\@\@.*//s; foreach my $group (groupsort(keys(%groups))) { $out .= genTable($propLinkString{$group}, $field, $group, "prop"); } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*datasection/) && do { if (!$seenData) { # @@@ Debug checkpoint for data (const, etc.) $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/datasection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*datalist/) && do { if ($seenData) { $field =~ s/\@\@.*//s; foreach my $group (groupsort(keys(%groups))) { $out .= genTable($dataLinkString{$group}, $field, $group, "data"); } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*typesection/) && do { if (!$seenType) { # @@@ Debug checkpoint for data types $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/typesection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*year/) && do { $out .= $yearStamp; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*date/) && do { $out .= $dateStamp; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*copyright/) && do { if (length($frameworkCopyrightString)) { my $temp = $frameworkCopyrightString; $temp =~ s/\$\$year\@\@/\Q$yearStamp\E/g; $temp =~ s/\$\$date\@\@/\Q$dateStamp\E/g; $out .= $temp; } else { if ($HeaderDoc::copyrightOwner && length($HeaderDoc::copyrightOwner)) { $out .= "© ".$yearStamp." ".$HeaderDoc::copyrightOwner } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*typelist/) && do { if ($seenType) { $field =~ s/\@\@.*//s; foreach my $group (groupsort(keys(%groups))) { $out .= genTable($typeLinkString{$group}, $field, $group, "type"); } } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*functionsection/) && do { if (!$seenFunctions) { # @@@ Debug checkpoint for functions $include_in_output = 0; } $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*\/functionsection/) && do { $include_in_output = 1; $keep =~ s/.*?\@\@//s; last SWITCH; }; ($field =~ /^\s*functionlist/) && do { if ($seenFunctions) { $field =~ s/\@\@.*//s; foreach my $group (groupsort(keys(%groups))) { $out .= genTable($functionsLinkString{$group}, $field, $group, "function"); } } $keep =~ s/.*?\@\@//s; last SWITCH; }; { if ($first) { $first = 0; } else { warn "Unknown field: \$\$$field\n"; $out .= "\$\$"; } } } if ($include_in_output) { $out .= $keep; } } # print STDERR "HTML: $out\n"; # write out page print STDERR "gatherHeaderDoc.pl: writing master TOC to $masterTOC\n" if ($localDebug); if (!$template_number) { open(OUTFILE, ">$masterTOC") || die "Can't write $masterTOC.\n"; } else { open(OUTFILE, ">$outputDir$pathSeparator$frameworkShortName-$templatename.html") || die "Can't write $outputDir$pathSeparator$frameworkShortName-$templatename.html.\n"; } print OUTFILE $out; close OUTFILE; ++$template_number; } } # /*! # @abstract # Adds a link to the main TOC index from each of the HTML # frameset files. # */ sub addTopLinkToFramesetTOCs { my $masterTOC = $inputDir.$pathSeparator. $masterTOCFileName; my $tocFileName = "toc.html"; my @allFramesetRefs; push(@allFramesetRefs, @headerFramesetRefs); push(@allFramesetRefs, @comintFramesetRefs); push(@allFramesetRefs, @classFramesetRefs); push(@allFramesetRefs, @protocolFramesetRefs); push(@allFramesetRefs, @categoryFramesetRefs); my $localDebug = 0; foreach my $ref (@allFramesetRefs) { my $name = $ref->name(); my $type = $ref->type(); my $path = $ref->path(); my $tocFile = $path; # path to index.html my $fsName = quotemeta($framesetFileName); $tocFile =~ s/$fsName$/toc.html/; # path to toc.html if (-e "$tocFile" ) { my $oldRecSep = $/; undef $/; # read in file as string open(INFILE, "<$tocFile") || die "Can't read file $tocFile.\n"; my $fileString = ; close INFILE; $/ = $oldRecSep; my $uniqueMarker = "headerDoc=\"topLink\""; # Determine the style in which the TOC was built. my $newTOC = 0; if ($fileString =~ /headerDoc TOC style: (\d+)\s*-->/) { $newTOC = $1; } $HeaderDoc::newTOC = $newTOC; my $relPathToMasterTOC = &findRelativePath($tocFile, $masterTOC); my $breadcrumb_added = 0; if ($fileString =~ /.*/s) { my $breadcrumb = ""; $breadcrumb_added = 1; if ($newTOC) { # In a class, so the heading is already there. # $breadcrumb .= HeaderDoc::APIOwner->tocSeparator("Other Reference", $newTOC); $breadcrumb .= HeaderDoc::APIOwner->tocEntry("$relPathToMasterTOC", "$framework", "_top"); } else { # In a class, so the heading is already there. # $breadcrumb .= "

Other Reference


\n"; $breadcrumb .= "      $framework
\n"; } $fileString =~ s/.*/$breadcrumb/s; } elsif ($fileString =~ /.*/s) { my $breadcrumb = ""; $breadcrumb_added = 1; if ($newTOC) { $breadcrumb .= HeaderDoc::APIOwner->tocSeparator("Other Reference", $newTOC); $breadcrumb .= HeaderDoc::APIOwner->tocEntry("$relPathToMasterTOC", "$framework", "_top"); } else { $breadcrumb .= "

Other Reference


\n"; $breadcrumb .= "      $framework
\n"; } $fileString =~ s/.*/$breadcrumb/s; } if ((($HeaderDoc::addTopLink && !$newTOC) || (!$breadcrumb_added)) && (!$useBreadcrumbs)) { if ($fileString !~ /$uniqueMarker/) { # we haven't been here before my $relPathToMasterTOC = &findRelativePath($tocFile, $masterTOC); my $topLink = "\n[Top]
\n"; $fileString =~ s/(]*>)/$1$topLink/i; } } open (OUTFILE, ">$tocFile") || die "Can't write file $tocFile.\n"; print OUTFILE $fileString; close (OUTFILE); } elsif ($debugging) { print STDERR "--> '$tocFile' doesn't exist!\n"; print STDERR "Cannot add [top] link for frameset doc reference:\n"; print STDERR " name: $name\n"; print STDERR " type: $type\n"; print STDERR " path: $path\n"; } } if ($useBreadcrumbs) { foreach my $file (@contentFiles) { # print STDERR "FILE: $file\n"; if (-e "$file" && ! -d "$file" ) { my $oldRecSep = $/; undef $/; # read in file as string open(INFILE, "<$file") || die "Can't read file $file.\n"; my $fileString = ; close INFILE; $/ = $oldRecSep; my $uniqueMarker = "headerDoc=\"topLink\""; # if ($fileString !~ /$uniqueMarker/) { # we haven't been here before if (length($framework)) { my $relPathToMasterTOC = &findRelativePath($file, $masterTOC); my $breadcrumb = "$framework"; $fileString =~ s/.*?/$breadcrumb/i; open (OUTFILE, ">$file") || die "Can't write file $file.\n"; print OUTFILE $fileString; close (OUTFILE); } else { warn "No framework (.hdoc) file found and breadcrumb specified. Breadcrumbs will\nnot be inserted.\n"; } # } } } } } # /*! # @abstract # Returns a relative link to a destination frameset # (a header, class, etc.) from the main TOC. # @param masterTOCFile # The path of the TOC file that this will go into. # @param dest # The filesystem path of the destination content. # @param name # The name of the destination as it should appear in the human-readable # text for the link. # @param group # The name of the group (from \@indexgroup) for # the destination. # @param typename # The name of the type of the destination (e.g. header, category, ...) # @discussion # In addition to being a link, the anchors returned may also # be jump link destinations for letter groups within long # sets of links. Thus, this function needs to know the name # of the group that the destination is in, as well as the # type (e.g. header, category...). # */ sub getLinkToFramesetFrom { my $masterTOCFile = shift; my $dest = shift; my $name = shift; my $group = shift; my $typename = shift; my $mansrc = shift; my $linkString; my %manPageTypes = ( "base" => "Mac OS X (client) manual page", "server" => "Mac OS X Server manual page", "devtools" => "Developer tools manual page", "chud" => "CHUD (part of developer tools) manual page", "internal" => "INTERNAL MANUAL PAGE" ); my $maninsert = ""; if ($mansrc) { $maninsert = ""; $mansrc = " mansrc=\"$mansrc\" class=\"manpage_source_$mansrc\" title=\"".$manPageTypes{$mansrc}."\" "; } my $relPath = &findRelativePath($masterTOCFile, $dest); my $namestring = getNameStringForLink($name, $group, $typename); $linkString = "$maninsert$name
\n"; return $linkString; } # /*! # @abstract # Returns the jump destination part of an anchor. # @param name # The name of the destination as it should appear in the human-readable # text for the link. # @param group # The name of the group (from \@indexgroup) for # the destination. # @param typename # The name of the type of the destination (e.g. header, category, ...) # @discussion # Used by {@link getLinkToFramesetFrom} and # {@link getLinkToFunctionFrom}. # */ sub getNameStringForLink { my $name = shift; my $group = shift; my $typename = shift; my $namestring = ""; my $groupns = $group; $groupns =~ s/\s/_/sg; my $grouptype = $groupns."_".$typename; my $firsttwo = uc($name); $firsttwo =~ s/^(..).*$/$1/s; # print STDERR "FIRSTTWO: $firsttwo\n"; # cluck("test\n"); if (!$letters_linked{$firsttwo}) { $namestring = "name=\"group_$grouptype"."_$firsttwo\""; $letters_linked{$firsttwo} = 1; # print STDERR "SET letters_linked{$firsttwo}\n"; } else { $letters_linked{$firsttwo}++; } return $namestring; } # /*! # @abstract # Returns a relative link to a function, data type, or other # non-API-owning API element (i.e. not an entire header or class). # @param masterTOCFile # The path of the TOC file that this will go into. # @param dest # The filesystem path of the destination content. # @param name # The name of the destination as it should appear in the human-readable # text for the link. # @param group # The name of the group (from \@indexgroup) for # the destination. # @param typename # The name of the type of the destination (e.g. header, category, ...) # @discussion # In addition to being a link, the anchors returned may also # be jump link destinations for letter groups within long # sets of links. Thus, this function needs to know the name # of the group that the destination is in, as well as the # type (e.g. header, category...). # */ sub getLinkToFunctionFrom { my $masterTOCFile = shift; my $dest = shift; my $name = shift; my $uid = shift; my $group = shift; my $typename = shift; my $linkString; $uid =~ s/^"//; $uid =~ s/"$//; my $relPath = &findRelativePath($masterTOCFile, $dest); my $ns = getNameStringForLink($name, $group, $typename); my $noClassName = $name; $noClassName =~ s/.*\:\://s; my $urlname = sanitize($noClassName); my $lp = ""; if ($uid && length($uid)) { $urlname = $uid; $lp = " logicalPath=\"$uid\""; } # print STDERR "UIDCHECK: $uid\n"; if ($uid =~ /\/\/apple_ref\/occ\/(clm|instm|intfcm|intfm)\//) { # Format Objective-C class name my $type = $1; $name =~ s/^(.*)\:\://; my $class = $1; my $plusmin = "+"; if ($type eq "instm") { $plusmin = "-"; } $name = $plusmin."[ $class $name ]"; } $linkString = "$name
\n"; return $linkString; } # /*! # @abstract # Sort helper for sorting objects by name. # @param obj1 # Object 1. # @param obj2 # Object 2. # */ sub objName { # for sorting objects by their names uc($a->name()) cmp uc($b->name()); } # /*! # @abstract # Returns a default (minimal) TOC template. # */ sub default_template { my $template = "\n"; my $stylesheet = ""; # my $he = HeaderElement::new; # my $stylesheet = $he->styleSheet(0); $template .= "\n\n \$\$title\@\@\n \n \n \n$stylesheet\n

\$\$framework\@\@ Documentation



\n"; $template .= "

\$\$frameworkdiscussion\@\@

"; $template .= "\$\$headersection\@\@

Headers

\n
\n\$\$headerlist cols=2 order=down atts=border=\"0\" width=\"80%\"\@\@\n
\$\$/headersection\@\@\n"; $template .= "\$\$classsection\@\@

Classes

\n
\n\$\$classlist cols=2 order=down atts=border=\"0\" width=\"80%\"\@\@\n
\$\$/classsection\@\@\n"; $template .= "\$\$categorysection\@\@

Categories

\n
\n\$\$categorylist cols=2 order=down atts=border=\"0\" width=\"80%\"\@\@\n
\$\$/categorysection\@\@\n"; $template .= "\$\$protocolsection\@\@

Protocols

\n
\n\$\$protocollist cols=2 order=down atts=border=\"0\" width=\"80%\"\@\@\n
\$\$/protocolsection\@\@\n"; $template .= "\$\$functionsection\@\@

Functions

\n
\n\$\$functionlist cols=2 order=down atts=border=\"0\" width=\"80%\"\@\@\n
\$\$/functionsection\@\@\n"; $template .= "\$\$typesection\@\@

Data Types

\n
\n\$\$typelist cols=2 order=down atts=border=\"0\" width=\"80%\"\@\@\n
\$\$/typesection\@\@\n"; $template .= "\$\$datasection\@\@

Globals and Constants

\n
\n\$\$datalist cols=2 order=down atts=border=\"0\" width=\"80%\"\@\@\n
\$\$/datasection\@\@\n"; $template .= "\$\$propsection\@\@

Globals and Constants

\n
\n\$\$proplist cols=2 order=down atts=border=\"0\" width=\"80%\"\@\@\n
\$\$/propsection\@\@\n"; $template .= "\$\$copyright\@\@\n"; $template .= "\n\n"; $gather_globals_and_constants = 1; $gather_types = 1; $gather_functions = 1; return $template; } # /*! # @abstract # Returns a link string linking to a jump destination for a # given letter group within an \@indexgroup # group within a data type family (e.g. headers, # functions, etc.). # @param group # The \@indexgroup group that this will appear under. # @param linkletter # A name for the letter range suitable for inclusion in an anchor's # "name" attribute. # @param letter # The first letter of the letter range. # @param typename # The name of the type of the destination (e.g. header, category, ...) # @param optional_last # The end of the letter range. (Optional.) # # If omitted, for a single-character letter, the last part of # the range is omitted. For example, instead of showing "A-B", # it would just show "A". If the start of the range (letter) is # two characters, in which case the range is automatically assumed # to end with a Z (AM-AZ, for example). # # */ sub gethierlinkstring { my $group = shift; my $linkletter = shift; my $letter = shift; my $typename = shift; my $optional_last = ""; if (@_) { $optional_last = shift; $optional_last = "-$optional_last"; } elsif ($letter =~ /../) { $optional_last = $letter; $optional_last =~ s/(.)./$1Z/s; $optional_last = "-$optional_last"; } my $groupns = $group; $groupns =~ s/\s/_/sg; my $grouptype = $groupns."_".$typename; return "$letter$optional_last"; } # /*! # @abstract # Generates a multi-column table of links. # @param inputstring # A pile of links, one per line. # @param settings # The table parameters from the template file. (These are # documented in the template section of the # {@linkdoc //apple_ref/doc/uid/TP40001215 HeaderDoc User Guide}). # @param groupname # The \@indexgroup group that this will appear under. # @param typename # The name of the type of the destination (e.g. header, category, ...) # @param isman # Set to 1 for manual pages. This causes the code to generate # multiple tables, one per manual section. # */ sub genTable { my $inputstring = shift; my $settings = shift; my $groupname = shift; my $typename = shift; my $isman = 0; if (@_) { $isman = shift; } my $ncols = 0; my $order = "down"; my $attributes = "border=\"0\" width=\"100%\""; my $tdclass = ""; my $trclass = ""; my $localDebug = 0; my $addempty = 0; my $notable = 0; print STDERR "genTable(IS: [omitted], SET: $settings, GN: $groupname, TN: $typename, IM: $isman)\n" if ($localDebug); my $mansectiontext = ""; if ($isman) { my $mansectionname = $groupname; $mansectionname =~ s/^\s*Section\s+//s; my $filename="sectiondesc/man$mansectionname".".html"; if (open(SECTIONTEXT, "<$filename")) { my $lastrs = $/; $/ = undef; $mansectiontext = ; $/ = $lastrs; close(SECTIONTEXT); } else { warn "No file for man section $mansectionname\n"; } } if (!defined($inputstring)) { return ""; } my @lines = split(/\n/, $inputstring); my $nlines = scalar(@lines); my $addHierarchicalLinks = 0; my $hierstring = ""; if ($HeaderDoc::groupHierLimit && ($nlines > $HeaderDoc::groupHierLimit)) { $addHierarchicalLinks = 1; my $splitparts = 0; my $attempts = 0; my $subgroupLimit = $HeaderDoc::groupHierSubgroupLimit; my $minsplit = 5; while ($splitparts < $minsplit && $attempts < 5) { my $linkletter = ""; my $prevletter = ""; my $prevtwoletter = ""; $splitparts = 0; # Count the number of entries and reduce the limit as needed to ensure no singleton lists. $hierstring = "
\n"; print STDERR "GROUPNAME: $groupname\n" if ($localDebug); my $groupns = $groupname; $groupns =~ s/\s/_/sg; my $grouptype = $groupns."_".$typename; print STDERR "GROUPTYPE: \"$grouptype\"\n" if ($localDebug); my %twoletterlinkcounts = %{$group_letters_linked{$grouptype}}; print STDERR "GLLCHECK: ".scalar(keys %{$group_letters_linked{$grouptype}})."\n" if ($localDebug); my %oneletterlinkcounts = (); foreach my $twoletter (sort keys %twoletterlinkcounts) { # print STDERR "TL: $twoletter\n"; my $firstletter = $twoletter; $firstletter =~ s/^(.).*$/$1/s; if (!$oneletterlinkcounts{$firstletter}) { # print STDERR "FIRST $firstletter; linkletter -> $twoletter\n"; $oneletterlinkcounts{$firstletter} = $twoletterlinkcounts{$twoletter}; if ($prevletter ne "") { $hierstring .= gethierlinkstring($groupname, $linkletter, $prevletter, $typename)." | \n"; } $prevletter = $firstletter; $linkletter = $twoletter; $splitparts++; } elsif ($oneletterlinkcounts{$firstletter} + $twoletterlinkcounts{$twoletter} > $subgroupLimit) { # print STDERR "LIMIT $firstletter; linkletter -> $twoletter\n"; $hierstring .= gethierlinkstring($groupname, $linkletter, $prevletter, $typename, $prevtwoletter)." | \n"; $prevletter = $twoletter; $linkletter = $twoletter; $splitparts++; } $prevtwoletter = $twoletter; } if ($prevletter ne "") { $hierstring .= gethierlinkstring($groupname, $linkletter, $prevletter, $typename); } $hierstring .= "
\n"; # Reduce the subgroup limit and increase the attempt count so that if # we execute this code again, we will probably get more subgroups. # Use the attempts count to ensure that this loop isn't infinite if # all entries have the same first letter. if ($splitparts < $minsplit) { print STDERR "Minimum split count $minsplit not reached. Split count was $splitparts. Reducing split count.\n" if ($localDebug); $subgroupLimit = $subgroupLimit / $minsplit; } $attempts++; } print STDERR "SPLITPARTS: $splitparts\n" if ($localDebug); if ($splitparts <= 1) { print STDERR "Could not split list at all. Dropping singleton.\n" if ($localDebug); $hierstring = ""; # eliminate singleton lists. } # } else { # print STDERR "Not over limit: $groupname\n"; } # print STDERR "HIERSTRING: $hierstring\n"; my $ngroups = scalar(keys(%groups)); my $groupnamestring = ""; if ($groupname =~ /\S/) { my $groupnospc = $groupname; $groupnospc =~ s/\s/_/sg; $groupnamestring = "

$groupname

\n"; } if ($groupname eq "hd_master_letters_linked") { $groupnamestring = ""; } my $groupheadstring = "
\n"; my $grouptailstring = "
\n"; if (!$ngroups) { $groupheadstring = ""; $grouptailstring = ""; } $settings =~ s/^\s*(\w+list)\s*//; my $name = $1; if ($settings =~ s/^nogroups\s+//) { $ngroups = 0; } if ($settings =~ s/^cols=(\d+)\s+//) { $ncols = $1; } if ($settings =~ s/^order=(\w+)\s+//) { $order = $1; if (!$ncols) { $ncols = 1; } } if ($settings =~ s/^trclass=(\w+)\s+//) { $trclass = " class=\"$1\""; if (!$ncols) { $ncols = 1; } } if ($settings =~ s/^tdclass=(\w+)\s+//) { $tdclass = " class=\"$1\""; if (!$ncols) { $ncols = 1; } } if ($settings =~ s/^notable//) { $notable = 1; $ncols = 1; } if ($settings =~ s/^addempty=(\d+)//) { $addempty = $1; } if ($settings =~ s/^atts=//) { $attributes = $settings; $settings = ""; if (!$ncols) { $ncols = 1; } } if ($ncols) { if (!$nlines) { return ""; } my @columns = (); my $loopindex = $ncols; while ($loopindex--) { my @column = (); push(@columns, \@column); } my $curcolumn = 0; my $curline = 0; my $lines_per_column = int(($nlines + $ncols - 1) / $ncols); # ceil(nlines/ncols) my $blanks = ($lines_per_column * $ncols) - $nlines; $nlines += $blanks; while ($blanks) { push(@lines, ""); $blanks--; } warn "NLINES: $nlines\n" if ($localDebug); warn "Lines per column: $lines_per_column\n" if ($localDebug); foreach my $line (@lines) { warn "columns[$curcolumn] : adding line\n" if ($localDebug); my $columnref = $columns[$curcolumn]; push(@{$columnref}, $line); $curline++; if ($order eq "across") { $curcolumn = ($curcolumn + 1) % $ncols; } elsif ($curline >= $lines_per_column) { $curline = 0; $curcolumn++; } } if ($localDebug) { $loopindex = 0; while ($loopindex < $ncols) { warn "Column ".$loopindex.":\n"; foreach my $line (@{$columns[$loopindex]}) { warn "$line\n"; } $loopindex++; } } # warn("TABLE $attributes\n"); my $outstring = ""; if (!$notable) { $outstring .= ""; } $curline = 0; $curcolumn = 0; my $currow = 0; my $first = 1; while ($curline < $nlines) { if (!$curcolumn) { if ($first) { $first = 0; if (!$notable) { $outstring .= ""; } } else { if (!$notable) { $outstring .= "\n"; } $currow++; } } else { if ($addempty) { if (!$notable) { $outstring .= "\n"; } } } my $line = ${$columns[$curcolumn]}[$currow]; my $val = floor(100/$ncols); if ($notable) { $outstring .= "$line
\n"; } else { $outstring .= "$line\n"; } $curline++; $curcolumn = ($curcolumn + 1) % $ncols; } if (!$notable) { $outstring .= "
 
\n"; } return $groupnamestring.$mansectiontext.$hierstring.$groupheadstring.$outstring.$grouptailstring; } else { return $groupnamestring.$mansectiontext.$hierstring.$groupheadstring.$inputstring.$grouptailstring; } } # /*! # @abstract # Returns the number of parts in a path. # @param string The path to check. # */ sub pathparts { my $string = shift; my $count = 0; while ($string =~ s/\///) { $count++; } # print STDERR "PATHPARTS FOR $string: $count\n"; return $count; } # /*! # @abstract # Returns whether the framework discussion is at the # same nesting level (pathwise) as the framework UID you # just read from elsewhere in that folder. # @discussion # Although not a complete guarantee, this prevents the most # common cause of getting the wrong framework discussion, # which is nesting multiple trees worth of HeaderDoc output # inside one another and building an outer set of docs that # incudes the inner set. # */ sub rightframework { my $filename = shift; my $count = pathparts($filename); if ($frameworknestlevel == -1) { $frameworknestlevel = $count; return 1; } if ($frameworknestlevel < $count) { return 0; } return 1; } # /*! # @abstract # Takes a string containing link requests and assembles an array of just the link requests. # @param string # The input string. # */ sub docListFromString { my $string = shift; my @parts = split(///s; my $string = "$name\n"; push(@list, $string); } } return @list; } # /*! # @abstract # Takes a string containing link requests and returns a single-column # table containing only the link requests. # @param inputstring # The input string. # @param field # Passed as the "settings" argument to {@link genTable}. # */ sub relatedDocs { my $inputstring = shift; my $field = shift; my $retstring = ""; my $tmpstring = ""; if (length($inputstring)) { my @lines = docListFromString($inputstring); foreach my $line (@lines) { # print STDERR "LINE IS \"$line\"\n"; if (length($line)) { $tmpstring .= $line . "\n"; } } $tmpstring =~ s/\n$//s; $retstring .= genTable($tmpstring, $field, "", ""); } return $retstring; } # /*! # @abstract # Returns a list of links to \@indexgroup groups # within the TOC. # */ sub groupList { my $string = ""; my $first = 1; my @list = groupsort(keys(%groups)); foreach my $group (@list) { if ($group !~ /\S/) { next; } if ($first) { $first = 0; } else { $string .= "  |  \n"; } my $groupnospc = $group; $groupnospc =~ s/\s/_/sg; my $groupnobr = $group; $groupnobr =~ s/\s/ /sg; $string .= "$groupnobr"; } # $string .= "
\n"; return $string; } # /*! # @abstract # Prints a list of the keys in the "letters linked" hash for debugging purposees. # @param arrayref # A reference to the "letters linked" hash. # @param group # The name of the group (for printing only). # */ sub printll { my $arrayref = shift; my $group = shift; my %arr = %{$arrayref}; print STDERR "FOR GROUP \"$group\"\n"; foreach my $key (sort keys %arr) { print STDERR "$key\n"; } print STDERR "\n"; } # sub writeAPIOwner # { # my $apioRef = shift; # my $file = shift; # # my $name = $apioRef->name(); # my $type = $apioRef->type(); # my $path = $apioRef->path(); # my $uid = $apioRef->uid(); # # print $file "\n"; # print $file " $name\n"; # print $file " $path\n"; # print $file " $uid\n"; # } # /*! # @abstract # Generates an Xcode-compatible pair of DocSet XML files for # this documentation set. # @param outputDir # The input/output directory. # */ sub generateDocSetFile { my $outputDir = shift; my $masterTOCFile = $outputDir.$pathSeparator.$masterTOCFileName; # my @allFramesetRefs; # my @headerFramesetRefs; # my @dataFramesetRefs; # my @macroFramesetRefs; # my @typeFramesetRefs; # my @comintFramesetRefs; # my @classFramesetRefs; # my @manpageFramesetRefs; # my @categoryFramesetRefs; # my @protocolFramesetRefs; # my @functionRefs; # foreach my $header (@headerFramesetRefs) { # writeAPIOwner($header, OUTFILE); # } open(OUTFILE, ">$inputDir/Nodes.xml") || die("Could not write Nodes.xml file.\n"); print OUTFILE "\n"; print OUTFILE "\n"; print OUTFILE " \n"; print OUTFILE " \n"; print OUTFILE " $framework\n"; print OUTFILE " $masterTOCFileName\n"; print OUTFILE " \n"; print OUTFILE " \n"; print OUTFILE "\n"; close(OUTFILE); open(OUTFILE, ">$inputDir/Tokens.xml") || die("Could not write Tokens.xml file.\n"); print OUTFILE "\n"; print OUTFILE "\n"; my %refToObj = (); my %refToRelpath = (); my %allRelatedRefs = (); foreach my $header (@fileRefSets) { # warn("Header: ".$header->path()."\n"); my $path = $header->path(); my $relPath = &findRelativePath($masterTOCFile, $path); my $arrayRef = $header->group(); my @refs = @{$arrayRef}; foreach my $ref (@refs) { # $ref->dbprint(); my $uid = $ref->uid(); # warn("Registering UID: $uid PATH: ".$ref->path()."\n"); $uid =~ s/^"//; $uid =~ s/"$//; my $keep = 1; if ($refToObj{$uid}) { $keep = chooseBest($refToObj{$uid}, $ref, $uid); } if ($keep) { $refToObj{$uid} = $ref; $refToRelpath{$uid} = $relPath; } } } foreach my $uid (sort keys %refToObj) { my $ref = $refToObj{$uid}; my $relPath = $refToRelpath{$uid}; # warn("REF: $uid : ".$ref->path()." : $relPath\n"); # warn("ABS: ".$ref->abstract()."\n"); if ($uid =~ /\/\/apple_ref\/doc\//) { next; } print OUTFILE "\n"; print OUTFILE " $uid\n"; print OUTFILE " $relPath\n"; my $abs = XMLTokenAbstract($ref); print OUTFILE " $abs\n"; my $dec = textToXML($ref->declaration()); if ($dec) { print OUTFILE " $dec\n"; } # TODO: Availability my @relatedrefs = $ref->seealso(); if (scalar(@relatedrefs)) { print OUTFILE " \n"; foreach my $ref (@relatedrefs) { print OUTFILE " $ref\n"; $allRelatedRefs{$ref} = $ref; } print OUTFILE " \n"; } print OUTFILE "\n"; } foreach my $ref (sort keys %allRelatedRefs) { my $refobj = $refToObj{$ref}; my $title = XMLTokenAbstract($refobj); print OUTFILE " \n"; print OUTFILE " $ref\n"; print OUTFILE " \n"; } print OUTFILE "\n"; close(OUTFILE); } # /*! # @abstract # Sorts the names of groups. # @param \@_ # The names to sort. # @discussion # This forces the "Section legacy" to appear after all of # the numeric man page sections when processing manual pages. # For all other cases, it just does a simple sort. # */ sub groupsort(@) { my @arr = @_; # foreach my $temp (@arr) { print STDERR "GROUP: $temp\n"; } if ($manPageMode) { my @resorted = (); my $seenLegacy = 0; foreach my $group (sort @arr) { if ($group eq "Section legacy") { $seenLegacy = 1; } else { push(@resorted, $group); } } if ($seenLegacy) { push(@resorted, "Section legacy"); } return @resorted; } else { return sort @arr; } } # /*! # @abstract # Converts a string of text to XML (minimally). # */ sub textToXML { my $textdata = shift; $textdata =~ s/&/&/sgo; $textdata =~ s//>/sgo; return $textdata; } # /*! # @abstract # Returns the abstract that goes into the Tokens.xml file. # @discussion # This returns the abstract if there was one, stripping off # any HTML tags in the process. If there is no abstract, # it attempts to scrape the first paragraph from the # discussion. # */ sub XMLTokenAbstract { my $ref = shift; my $abs = stripTags($ref->abstract()); $abs =~ s/\s+/ /sg; $abs =~ s/"/"/sg; if ($abs) { return $abs; } else { # Punt and use the discussion my $disc = stripTags($ref->discussion()); # Strip leading newlines. $disc =~ s/^[\n\r]*//s; # Limit to one paragraph. $disc =~ s/\n\n.*$//s; if ($disc) { return $disc; } } return ""; } # /*! # @abstract # Chooses the best object for a UID. # @discussion # Used by the doc set generation code to choose which HTML file to # associate with a given apple_ref in the event of a conflict. # # This is primarily used when generating manual page content because # multiple manual pages often describe the same functions. In # general, apple_ref tags should always be unique. Do not rely # on this logic remaining as it is today. # @param firstObj # The first {@link //apple_ref/perl/cl/HeaderDoc::DocReference DocReference} object to compare. # @param secondObj # The second {@link //apple_ref/perl/cl/HeaderDoc::DocReference DocReference} object to compare. # @param uid # The uid in question. # */ sub chooseBest($$$) { my $firstObj = shift; my $secondObj = shift; my $uid = shift; my $localDebug = 0; my @uidparts = split(/\//, $uid); my $count = scalar(@uidparts); my $pos = $count; # There are 2 bogus leading parts due to the leading //. if ($count == 2 + 7) { # e.g. intfm, instm, clm, etc. w/ signature and return type $pos = $count - 2; # } elsif ($count == 2 + 5) { # e.g. structs and vars in classes, etc. # $pos = $count; } my $name = $uidparts[$count - 1]; # For example, foo matches blah/foo.html first. if ($firstObj->path =~ /\/\Q$name\E\./) { warnRetCause($uid, $firstObj, $secondObj, "$name is ideal (/$name\./).") if ($localDebug); return $firstObj; } if ($secondObj->path =~ /\/\Q$name\E\./) { warnRetCause($uid, $secondObj, $firstObj, "$name is ideal (/$name\./).") if ($localDebug); return $secondObj; } # For example, foo matches blah/foo or blah/foo/ next. if ($firstObj->path =~ /\/\Q$name\E(\/|$)/) { warnRetCause($uid, $firstObj, $secondObj, "$name is a path component (\/$name\/).") if ($localDebug); return $firstObj; } if ($secondObj->path =~ /\/\Q$name\E(\/|$)/) { warnRetCause($uid, $secondObj, $firstObj, "$name is a path component (\/$name\/).") if ($localDebug); return $secondObj; } # Check on any arbitrary word boundaries next. if ($firstObj->path =~ /\b\Q$name\E\b/) { warnRetCause($uid, $firstObj, $secondObj, "$name is between word boundaries.") if ($localDebug); return $firstObj; } if ($secondObj->path =~ /\b\Q$name\E\b/) { warnRetCause($uid, $secondObj, $firstObj, "$name is between word boundaries.") if ($localDebug); return $secondObj; } # Check on a leading word boundary next. if ($firstObj->path =~ /\b\Q$name\E/) { warnRetCause($uid, $firstObj, $secondObj, "$name starts at a word boundary (with trailing garbage).") if ($localDebug); return $firstObj; } if ($secondObj->path =~ /\b\Q$name\E/) { warnRetCause($uid, $secondObj, $firstObj, "$name starts at a word boundary (with trailing garbage).") if ($localDebug); return $secondObj; } # Check anywhere in the path. if ($firstObj->path =~ /\Q$name\E/) { warnRetCause($uid, $firstObj, $secondObj, "$name is in the path.") if ($localDebug); return $firstObj; } if ($secondObj->path =~ /\Q$name\E/) { warnRetCause($uid, $secondObj, $firstObj, "$name is in the path.") if ($localDebug); return $secondObj; } # Punt warn("Punting\n") if ($localDebug); return $firstObj; } # /*! # @abstract # Helper function to simplify warning code used for debugging # {@link chooseBest}. # */ sub warnRetCause($$$) { my $uid = shift; my $obj = shift; my $overobj = shift; my $reason = shift; warn("Chose ".$obj->path()." ($obj) over ".$overobj->path()." ($overobj) for $uid because $reason\n"); }