# Richard A. DeVenezia # October 6, 2003 # http://www.devenezia.com # Improve SAS Online help - tested with version 8 and 9 # Browse extractDir\index.html after running the script my %param = ( modules => "common af fsp" , extractDir => "c:\\temp\\sas" , noiseLevel => 9 , pageLimit => 0 ); # This perl script was tested on a Windows 2000 machine # perl for windows can be downloaded from http://www.activestate.com # The source was edited using UltraEdit found at http://www.ultraedit.com # # I agree this script may contain stupid perl # # Runtime parameters # ----- # modules - space separated list of SAS help chm modules, # full list can be seen at !SASROOT\core\help # extractDir - local path where chm help modules get decompiled # noiseLevel - higher means more messages # pageLimit - 0 means process all files, otherwise process only # first N files of each module (only use N>0 when testing) # # module common is needed for style sheets # the program adds an A{} block to make links more visible # #------------------------------------- # What does the script do ? #------------------------------------- # # Modify html files extracted from SAS chm files: # - color links that refer to pages that refer back # - link to __ALL__ pages that are referers # - list dead links found on a page # - indicate if the page is an orphan # Generate an index that: # - links to table of contents # - links to list of keywords # - lists orphan pages # - lists pages with dead links # - lists pages with duplicate titles # # Requires: # Access to registry to determine SAS installation location # Microsoft Html Help (hh.exe) so that .chm files can be decompiled. # Following a link into an existing chm requires: # Internet Explorer with JavaScript enabled # #------------------------------------- # Background #------------------------------------- # # The SAS Online documentation is quite complete and very informative. # That does not mean it can not be improved. One area I find needing # improvement is back links. Often a keyword search will take me to a # page that does not have information to allow it to be 'located' in # the contents tree when the location button is pressed. Nor is there # a link to another page having 'parent' or 'aggregating' context. # # This is especially troublesome for AF programmers whose search places # them at a methods or attributes page. These pages do not have back # links to the class containing the method (ouch!). # # I would prefer each page have to link to _every_ page that links to it. # Doing so provides a much richer information net and lets me get a # taste of the oosphere. # # So, I am addressing the situation # A ---> B ( A is a referer of B ) # by altering B so the relations are # A <--> B ( force B to be a referer of A ) # # An even better improvement (not being done by this program) would be to # ensure the forced back link goes to the point in A where B is first referred to # # A more difficult yet equally useful navigation change would be to # enable some form of horizontal travesal. (Some sections of SAS help do # exhibit this feature.) # # Consider: # # A level 1 A # /|\ /|\ # / | \ / | \ # B C D level 2 B--C--D # # I prefer all nodes on level 2 have links to every other node on level 2. # At a minimum each node should provide a previous and next. In terms of # SAS/AF, it would mean when you are looking at method page, you are one # or two clicks away from another classes method or attribute page. # Anyway, that is for a later day... # # I studied the html files decompiled out of af and fsp chm and found # several things: # # 1. very good consistency # 2. consistency means simplistic pattern matching and replacement can # be used to extract information and manipulate the html files to my # own purposes. # #------------------------------------- # What are the patterns ? #------------------------------------- # # All link navigation is of form information. # destination is of form MS-ITS:.chm::/.hlp/. # The decompiled html files are placed in a .hlp subfolder. # Image SRC refer to a root absolute /.hlp/images/ instead of # relative ../.hlp/images # # With such good consistency we can # # 1. make changes to HREFs so online help works in decompiled form # 1a. some advanced mojo is used to change links to modules _not_ decompiled. # the links are changed to cause htmlhelp to open when the link is clicked. # the mojo only works in Internet Explorer browser. # 2. determine incoming and outgoing links of each page for processing # I.E. # - if a page P has links incoming from A,B,C,X,Y and has outgoing links to B,C,X,Y,Z # I want to add to page P outgoing links to A and colorized the outgoing links # B,C,X,Y # #------------------------------------- # How is the link data processed ? #------------------------------------- # # Regular Expressions and Hashes!!! # # Each file in the .hlp folders is scanned and information extracted. # At the same time, 'fixes' are made to the links so they work in decompiled form. # # There will be three conceptual hashes maintained # - pages - hash for page data, each file scanned has 'page data' # o page data - an array # - incoming, hash for page referers # - outgoing, hash for page href destinations # - title of page # # The data requires to passes # pass 1. fix necessary links and record linkages # pass 2. analyze linkages and update pages if necessary # # Once the data is in hashes, it is a relatively simple matter to # perform all the interesting set analysis we want. # #------------------------------------- # How big is this stuff ? #------------------------------------- # common, af and fsp ends up with ~7,500 files (36mb) # and requires about 100 seconds to process when run on a # Windows 2000 / Intel 3.06gHz / 1g ram / ata-100 system # # I have not tried recompiling the modified html back into # chm files. use strict; use Time::HiRes qw(gettimeofday); use Win32::TieRegistry; use DirHandle; #---------------------------------------------------------------------- #---- move parameters into variables my ($modules,$extractDir,$noiseLevel,$pageLimit) = @param{qw/modules extractDir noiseLevel pageLimit/}; if ( ! defined $noiseLevel ) { $noiseLevel = 0; } if ( ! defined $pageLimit ) { $pageLimit = 0; } if ( $modules !~ /\bcommon\b/ ) { die "Call me stubborn, no running without common module." ; } #---- check if hh.exe will run my $rc = system ( "hh.exe -decompile foo bar" ); if ( $rc ) { die "Problem running HtmlHelp decompiler.\n"; } #---- determine location of SAS installation my $sasRoot; my $saskey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\SAS Institute Inc.\\The SAS System\\"; my $keyname = "${saskey}CurrentVersion\\CurrentVersion"; my $curver = $Registry->{$keyname}; if (! $curver) { die "Problem retrieving SAS current version from registry.\n"; } $keyname = "${saskey}${curver}\\DefaultRoot"; $sasRoot = $Registry->{$keyname}; if (! $sasRoot) { die "Problem retrieving SAS root from registry.\n"; } undef $Registry; #---- is extract folder is available ? my $chmRoot = "${sasRoot}\\core\\help"; if ( ! -d $extractDir ) { die "Folder ${extractDir} does not exist, and I won't make it for you." ; } my $helpFolder = actualFolder ( $chmRoot ); my $htmlFolder = actualFolder ( $extractDir ); #---- my @modules = split / / , $modules; $modules = join ("|", @modules); # rx alternation my $folder = $htmlFolder; my $rxfolder = $folder; $rxfolder =~ s/\\/\\\\/g; # folder for use in rx patterns my $helpFldr = $helpFolder; $helpFldr =~ s#\\#/#g; # helpFolder for use in javascript:showHelp #---- for reporting my $readCount = 0; my $writeCount = 0; my $rereadCount = 0; my $tweakCount = 0; my $detweakCount = 0; #---- my %pages; # one array of page data per html file my %titles; # one array of pages for each page title; #---- constants for page data array my $_INCOMING = 0; my $_OUTGOING = 1; my $_TITLE = 2; my $_ISTWEAKED = 3; my $_SHOWHELP = 4; #---- array for recording timing splits my @timeMark; tmark(1); decompile(); translate_hhc(); translate_hhk(); fix_hyperlinks(); hash_titles(); report_incoming(); create_root_index(); report_files_read(); create_link_colorizer(); tweak_common_css(); install_back_links(); #------- The End tsplit(1,'magic'); print "\n\nmodules: " . join(" ", @modules) . "\nadjust read: " . $readCount . "\nadjust write: " . $writeCount . "\nimprove read: " . $rereadCount . "\nimprove write: " . $tweakCount . "\ndetweak write: " . $detweakCount ; exit 0; #---------------------------------------------------------------------- #---------------------------------------------------------------------- #---------------------------------------------------------------------- sub tmark { my ($n) = @_; $timeMark [ $n ] = gettimeofday; } #---------------------------------------------------------------------- sub tsplit { my ($n, $label) = @_; my $t = gettimeofday; my $e = $t - $timeMark[ $n ] ; $timeMark[$n] = $t; if ($n<=$noiseLevel && defined($label)) { print "\n[" . " "x(2*$n-1) . "$label: ${e}s ]"; } return $e; } #---------------------------------------------------------------------- sub actualFolder { my ($folder) = @_; return "$folder\\"; } #---------------------------------------------------------------------- sub readFile { my ($file, $content_ref) = @_; local(*INPUT, $/); open (INPUT, $file) || die "can't open $file: $!"; $$content_ref = ; } #---------------------------------------------------------------------- sub writeFile { my ($file, $content_ref) = @_; local(*OUTPUT, $/); open (OUTPUT, ">${file}") || die "can't open $file: $!"; print OUTPUT $$content_ref; } #---------------------------------------------------------------------- sub addToHtml { my ($file, $snippet) = @_; my ($gulp, $burp); readFile ( $file, \$gulp ); $rereadCount++; print "\n"," "x10,"read $file(".length($gulp).")" if $noiseLevel>=99; $burp = $gulp; $burp =~ s{\n?.*?\n?}{}gos; $burp =~ s{()}{\n${snippet}\n\n$1} if defined $snippet; if ($gulp ne $burp ) { writeFile ( $file, \$burp ); print "\n"," "x9,"write $file(".length($burp).") ".defined($snippet) if $noiseLevel>=99; return 1; } else { return 0 } } #---------------------------------------------------------------------- sub filesInFolder { my $folder = shift; my $dh = DirHandle->new($folder); return sort grep { -f } map { "$folder$_" } grep { /html?$/ } $dh->read(); } #---------------------------------------------------------------------- sub decompile { tmark(2); tmark(3); foreach my $module (@modules) { next if (-d "${folder}${module}.hlp"); system ("hh.exe -decompile ${folder} ${helpFolder}${module}.chm"); tsplit(3,"decompile $module"); } tsplit(2,'decompile sas help'); } #---------------------------------------------------------------------- sub translate_hhc { tmark(2); tmark(3); foreach my $module (@modules) { my $hhc = "${folder}${module}.hhc"; my $htm = "${folder}${module}.hlp\\${module}-contents.htm"; next if (! -e $hhc) ; my ($gulp, $burp); readFile ($hhc, \$gulp); $burp = $gulp; # change href from ms-its path to local relative path $burp =~ s{ \s*? \s*? \s*? \s*? } {$1}gox; # o-compile once # change remaining ms-its hrefs to javascript:showHelp hrefs $burp =~ s{ \s*? \s*? \s*? \s*? } {$1}gox; # change objects with no Local param to just show the name $burp =~ s{ \s*? \s*? \s*? } {$1}gox; # comment out remaining objects $burp =~ s{ () } {}goxs; # add a title $burp =~ s{} {$module table of contents}; # add a call for colorizing showHelp links $burp =~ s{} { }; writeFile ($htm, \$burp); tsplit(3,$htm); } tsplit(2,'translate hhc'); } #---------------------------------------------------------------------- sub translate_hhk { tmark(2); tmark(3); foreach my $module (@modules) { my $hhk = "${folder}${module}.hhk"; my $htm = "${folder}${module}.hlp\\${module}-keywords.htm"; next if (! -e $hhk) ; my ($gulp, $burp); readFile ($hhk, \$gulp); $burp = $gulp; # change first name/value param of OBJECT to be simple text $burp =~ s{ \s*? \s*? (.*?) } {$1 $2}goxs; #change remain pairs of Name/Local parameters to an \s*? \s*? } {\n
$1}gox; # add a title $burp =~ s{} {$module keywords}; # add a call for colorizing showHelp links $burp =~ s{} { }; writeFile ($htm, \$burp); tsplit(3,$htm); } tsplit(2,'translate hhk'); } #---------------------------------------------------------------------- sub fix_hyperlinks { tmark(2); tmark(3); foreach my $module (@modules) { my $pageCount = 0; my $hrefCount = 0; my $maxTime = -1; my $maxTimePage = ''; my $maxHrefCount = -1; my $maxHrefCountPage = ''; my $path = "${folder}${module}.hlp\\"; my @pages = filesInFolder ($path); # at noise level 4, at every 800 files a log line shows # number of files:hrefs, file that took the longest, # the file with the most hrefs and interval duration tmark(4); foreach my $page (@pages) { tmark(5); last if ( ++$pageCount > $pageLimit && $pageLimit > 0 ) ; my ($gulp, $burp); readFile ($page, \$gulp); $readCount ++; print "\n"," "x10,"read $page(".length($gulp).")" if $noiseLevel>=99; $burp = $gulp; # if href is a showhelp construct, then we are reprocessing a file # thus, restore it back to 'pristine' form prior to transforming per 'module' form $burp =~ s{"javascript:window.showHelp\('(ms-its:).*?(($modules)[.]chm.*?)'\)"} {"$1$2"}go; # if href is a module related construct, transform it to a local disk relative reference $burp =~ s{ms-its:($modules)[.]chm::[/]\1[.]hlp[/]} {../$1.hlp/}go; # if href is a non-module related construct, transform it to a showhelp local disk absolute reference $burp =~ s{"(ms-its:)((.*?)[.]chm::[/]\3[.]hlp[/].*?)"} {"javascript:window.showHelp('$1$helpFldr$2')"}go; # fix image references so they work when local disk browsing $burp =~ s{((.*?)\s*<\/TITLE>/mso ) ? $1 : "[no title]"; $pages{$page}[$_ISTWEAKED] = ( $burp =~ s{\n.*?}{}gos ); $pages{$page}[$_SHOWHELP] = ( $burp =~ /javascript:window[.]showHelp/ ); my $count = 0; # record all hrefs that have construct ../.hlp/* as construct /.hlp/* while ( $burp =~ m#HREF\s*=\s*"[.][.][/](($modules)[.]hlp[/].*?[.]htm)#goi ) { # convert relative href to absolute path print "\n", " "x9,"match ",$1 if $noiseLevel>=99; ($_ = $1) =~ s#/#\\#go; my $destination = $folder . $_; # this is the core # everything this program does is based on these two simple assignments $pages{$page} [$_OUTGOING]{$destination} = 1 if $page ne $destination; $pages{$destination}[$_INCOMING]{$page} = 1 if $page ne $destination; $count++; } $hrefCount += $count; if ( $maxTime < (my $e = tsplit (5)) ) { $maxTime = $e; $maxTimePage = $page } if ( $maxHrefCount < $count ) { $maxHrefCount = $count; $maxHrefCountPage = $page; } if ( $pageCount % 800 == 0 ) { $maxTimePage =~ s/.*\\//go; $maxHrefCountPage=~ s/.*\\//go; tsplit(4,"${pageCount}:${hrefCount} ${maxTimePage}(${maxTime}s) ${maxHrefCountPage}(${maxHrefCount}->)"); $maxTime = -1; $maxHrefCount = -1; } } if ($maxHrefCount) { $maxTimePage =~ s/.*\\//go; $maxHrefCountPage=~ s/.*\\//go; tsplit(4,"${pageCount}:${hrefCount} ${maxTimePage}(${maxTime}s) ${maxHrefCountPage}(${maxHrefCount}->)") } tsplit(3,$module); } tsplit(2,'fix hyperlinks'); } #---------------------------------------------------------------------- sub by_title { my $A = uc ( $pages{$a}[$_TITLE] ); my $B = uc ( $pages{$b}[$_TITLE] ); $A cmp $B; } #---------------------------------------------------------------------- sub hash_titles { foreach my $page ( keys %pages ) { next if ! defined (my $title = $pages{$page}[$_TITLE]); next if $title eq "[no title]"; push @{$titles{$title}}, $page; } } #---------------------------------------------------------------------- sub create_root_index { tmark(2); my $file = "${folder}index-rad.css"; open (OUTPUT, ">$file") || die "can't open $file: $!"; print OUTPUT <$file") || die "can't open $file: $!"; print OUTPUT <A web of decompiled SAS Online Help

Root index

Generated by program written by Richard A. DeVenezia
modules:$modules, pageLimit:$pageLimit

Module Contents

Each link takes you to a translation of the modules .hhc file

    THINGY1 print OUTPUT map "
  • $_
  • ", @modules; print OUTPUT <

    Module Keywords

    Each link takes you to a translation of the modules .hhk file

      THINGY2 print OUTPUT map "
    • $_
    • ", @modules; print OUTPUT <

      Status reports

      Pages with no incoming links (within network of above listed modules)

      THINGY3 report_orphans(); print OUTPUT <

      Pages with links to pages that do not exist.

      THINGY4 report_dead_links(); print OUTPUT <

      Different pages with the same title.

      THINGY5 report_duplicate_titles(); print OUTPUT "

      modules:$modules, pageLimit:$pageLimit

      "; tsplit(2,'create root index'); } #---------------------------------------------------------------------- sub report_incoming { tmark(2); my $file = "${folder}incoming-report.txt"; open (OUTPUT, ">$file") || die "can't open $file: $!"; foreach my $page ( sort keys %pages ) { print OUTPUT "\n",$page,":"; print OUTPUT -e $page ? "*" : "!" if ! defined $pages{$page}[$_TITLE]; foreach my $referer ( keys %{$pages{$page}[$_INCOMING]} ) { print OUTPUT "\t$referer"; } } tsplit(2,'report incoming'); close OUTPUT; } #---------------------------------------------------------------------- sub report_orphans { tmark(3); my $message = '

      How is one expected to get there ? By searching alone ? Are they abandoned pages after being cloned (see duplicates) ?' . '
      I would prefer each module be entirely self contained and not have module anonymous pages that are destinations of module external pages

      ' . '
        '; my $haveOrphans = 0; foreach my $page ( sort by_title keys %pages ) { my ($href, $title, $state, $a, $sub, $other_page); next if keys %{$pages{$page}[$_INCOMING]}; $haveOrphans = print OUTPUT $message if ! $haveOrphans ; $_ = $page; s#\\#/#go if s#^$rxfolder(.*)#$1#o; $href = $_; $title = defined $pages{$page}[$_TITLE] ? $pages{$page}[$_TITLE] : "unparsed - $href"; $title.= " - $href" if $title eq '[no title]'; $a = "$title"; # look for other pages with same title that are not orphans $sub = ""; foreach $other_page (@{$titles{$title}}) { next if $other_page eq $page; next if ! keys %{$pages{$other_page}[$_INCOMING]}; $_ = $other_page; s#\\#/#go if s#^$rxfolder(.*)#$1#o; $href = $_; $sub .= "\n
      • same title - not orphan ($_)
      • "; } print OUTPUT "\n
      • "; print OUTPUT $a; print OUTPUT "
          ", $sub, "
        " if $sub; print OUTPUT "
      • "; } print OUTPUT $haveOrphans ? '
      ' : '

      All pages have at least one referer, good job!

      '; tsplit(3,'report orphans'); } #---------------------------------------------------------------------- sub report_dead_links { tmark(3); my $haveDeadLinks = 0; foreach my $page ( sort keys %pages ) { my $started = 0; foreach my $destination ( keys %{$pages{$page}[$_OUTGOING]} ) { next if defined $pages{$destination}[$_TITLE]; next if -e $destination; if (!$started) { $haveDeadLinks = print OUTPUT "
        " if ! $haveDeadLinks; $_ = $page; s#\\#/#go if s#^$rxfolder(.*)#$1#o; my $href = $_; print OUTPUT "
      • $pages{$page}[$_TITLE]
        • "; $started = 1; } $_ = $destination; s#\\#/#go if s#^$rxfolder(.*)#$1#o; my $href = $_; print OUTPUT "
        • $href
        • "; } if ($started) { print OUTPUT "
        "; $started = 0; } } print OUTPUT $haveDeadLinks ? '
      ' : '

      There are no dead links today, good job!

      '; tsplit(3,'dead links'); } #---------------------------------------------------------------------- sub report_duplicate_titles { tmark(3); my $haveDups = 0; foreach my $title ( sort keys %titles ) { next if @{$titles{$title}} < 2; $haveDups = print OUTPUT "
        " if ! $haveDups ; print OUTPUT "
      • $title
          "; foreach my $page (@{$titles{$title}}) { my $orphan = keys %{$pages{$page}[$_INCOMING]} ? '' : ' (orphan)'; $_ = $page; s#\\#/#go if s#^$rxfolder(.*)#$1#o; my $href = $_; print OUTPUT "\n
        • $page$orphan
        • "; } print OUTPUT "
      • "; } print OUTPUT $haveDups ? "
      " : "

      No duplicate titles observed.

      ";; tsplit(3,'duplicate titles'); } #---------------------------------------------------------------------- sub report_files_read { tmark(2); my $file = "${folder}files-read.html"; open (OUTPUT, ">$file") || die "can't open $file: $!"; print OUTPUT 'Files read' , '' , '

      Files read

      Sorted by filename. Goto main index

        '; foreach my $page ( sort keys %pages ) { my ($href, $title, $orphan); $_ = $page; s#\\#/#go if s#^$rxfolder(.*)#$1#o; $href = $_; $title = defined $pages{$page}[$_TITLE] ? $pages{$page}[$_TITLE] : "unparsed - $href"; $title.= " - $href" if $title eq '[no title]'; $orphan = keys %{$pages{$page}[$_INCOMING]} ? '' : ' (orphan)'; print OUTPUT "\n
      1. $href - $title$orphan
      2. " } print OUTPUT "
      "; print OUTPUT "

      modules:$modules, pageLimit:$pageLimit

      "; tsplit(2,'create files read'); } #---------------------------------------------------------------------- sub create_link_colorizer { tmark(2); my $file = "${folder}colorizer.js"; open (OUTPUT, ">$file") || die "can't open $file: $!"; print OUTPUT < -1) { a[i].style.backgroundColor = "#227722" a[i].style.color = "#FFFFFF" } } } } function colorShowHelp () { var a = document.getElementsByTagName ('a') for (var i=0; i -1) { a[i].style.backgroundColor = "#6699CC" a[i].style.color = "#FFFFFF" } } } THINGY6 tsplit(2,'create colorizer.js'); } #---------------------------------------------------------------------- sub tweak_common_css { tmark(2); my $file = "${folder}common.hlp\\ss\\style.css"; my ($gulp, $burp); readFile ( $file, \$gulp); $burp = $gulp; $burp =~ s{/\*-- rad --\*/.*?/\*-- rad --\*/}{}gs; $burp .= "/*-- rad --*/ A { background-color: #E8E8E8; border: 1px dashed #AAA; } /*-- rad --*/"; writeFile ( $file, \$burp ) if ( $burp ne $gulp ); tsplit(2,'tweak css'); } #---------------------------------------------------------------------- sub install_back_links { tmark(2); my $pageCount = 0; my $maxTime = -1; my $maxTimePage = ''; foreach my $page ( keys %pages ) { tmark(3); my $outgoing = $pages{$page}[$_OUTGOING]; my $incoming = $pages{$page}[$_INCOMING]; my $title = $pages{$page}[$_TITLE]; my $newlinks = ''; my $reflinks = ''; my $deadlinks = ''; my $orphanmsg = ''; if ( ! defined $title ) { $orphanmsg = 'This page was not parsed.'; next if ! -e $page; } elsif ( ! keys %$incoming ) { $orphanmsg = 'This page is an orphan'; } else { #-- check for referers that are not linked to foreach my $referer ( sort by_title keys %$incoming ) { my $r_title = $pages{$referer}[$_TITLE]; my $r_isOrphan = ! keys %{$pages{$referer}[$_INCOMING]}; $_ = $referer; s#\\#/#go if s#^$rxfolder(.*)#../$1#o; my $href = $_; my $status = ''; if ( ! defined $r_title ) { $status = 'not parsed'; } elsif ( $r_isOrphan ) { $status = 'orphan'; } if ( ! exists $outgoing->{$referer} ) { $status = " ($status)" if $status; $newlinks .= "\n
    • $r_title$status
    • "; } else { $reflinks .= ",[\"$href\",\"$status\"]"; } $outgoing->{$referer} = 0; # 0 indicates backlink established; } } # check for dead links foreach my $destination ( keys %$outgoing) { next if $outgoing->{$destination} == 0; next if -e $destination; $_ = $destination; s#\\#/#go if s#^$rxfolder(.*)#$1#o; my $href = $_; $deadlinks .= "\n
    • $href
    • "; } if ($orphanmsg || $newlinks || $deadlinks || $reflinks || $pages{$page}[$_SHOWHELP]) { my $div = ''; if ( $orphanmsg ) { $div .= "\n
      " . "\n

       ${orphanmsg}

      " . "

       Goto main index

      " . "
      "; } if ( $reflinks || $pages{$page}[$_SHOWHELP] ) { $div .= '' } if ( $pages{$page}[$_SHOWHELP] ) { $div .= '' } if ( $reflinks || $newlinks ) { $div .= "\n
      "; $div .= "\n" . "\n

       Colored links are to pages that link back

      " if $reflinks; $div .= "\n

       Other pages that link to this page

      " . "\n
        ${newlinks}
      " if $newlinks; $div .= "

       Goto main index

      " . "
      "; } if ( $deadlinks ne '' ) { $div .= "\n
      " . "\n

       Dead links found on this page

      " . "\n
        ${deadlinks}
      " . "

       Goto main index

      " . "
      "; } $tweakCount += addToHtml ( $page, $div ); } elsif ( $pages{$page}[$_ISTWEAKED] ) { $detweakCount += addToHtml ( $page ); } if ( $maxTime < (my $e = tsplit (3)) ) { $maxTime = $e; $maxTimePage = $page } if ( ++$pageCount % 800 == 0 ) { $maxTimePage =~ s/.*\\//go; tsplit(4,"${pageCount}: ${maxTimePage}(${maxTime}s)"); $maxTime = -1; } } tsplit(2,'install back links'); }