#!/usr/bin/perl -w
#
# PHPXref v0.7
#
# A utility for cross referencing PHP script files
#
# (c) 2000-2007 Gareth Watts  <gareth@omnipotent.net>
#
# Contributors:
# Gottfried Szing <goofy@yasd.dhs.org>
#
# Usage: phpxref.pl [-c <config file>]
# Looks for phpxref.cfg in the script directory by default
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#


$version="0.7";


#### Configurable bits - Best place to change this stuff is in the config file


# Default header/footer - override here or in the configuration file
$rundate=localtime();
$pageheader="<html><head><title>__TITLE__</title></head><meta http-equiv=\"content-type\" content=\"text/html;charset=__CHARSET__\"><body bgcolor=\"#ffffff\">\n";
$pagefooter="<br><hr><table width=100%><tr><td>Generated: $rundate</td><td align=\"right\"><i>Generated by PHPXref $version</i></td></tr></table></body></html>\n";

# PHP Servers to link to for documentation
@phpsites=('php.net','us2.php.net', 'uk.php.net','au.php.net','de.php.net');
    
# config defaults
%config=(
);    

my $dbh = 0;    # used for table references




#### Less configurable stuff hereon in (take a sick bag; this code hasn't so much evolved, as gone rotten)

use File::Find;
use File::Path;
use File::Basename;
use File::Copy;
use File::Spec;
use File::DosGlob 'glob'; # TinyPerl only seems to work with DosGlob :-/
use FileHandle;
use Getopt::Std;
use Config;


$scriptbase=dirname($0);
getopts('c:') || usage_die();

$debug=0;
$|=1;
$is_case_tolerant = File::Spec->case_tolerant();
$opt_c="$scriptbase/phpxref.cfg" unless $opt_c;
parse_config($opt_c) || die "Error parsing config file";
$output_dir=$config{'output'} || die "No OUTPUT directory specified";
$source_dir=$config{'source'} || die "No SOURCE directory specified";
$ext=defined($config{'extension'}) ? $config{'extension'} : 'html';
$charset=defined($config{'charset'}) ? $config{'charset'} : 'iso-8859-1';
$project_name=defined($config{'project'}) ? $config{'project'} : 'Unnamed Project';
$title="PHPXRef $version : $project_name :";
$func_prefixes = '(private|public|protected|static|final)';
$func_prefix_re = "$func_prefixes?\\s*$func_prefixes?\\s*$func_prefixes?\\s*$func_prefixes?\\s*$func_prefixes?";

$output_dir=File::Spec->rel2abs($output_dir);
$source_dir=File::Spec->rel2abs($source_dir);
$unknown_constants=();
$unknown_variables=();

-d $output_dir || die "Output directory not found";
-d $source_dir || die "Source directory not found";

if ($config{'compress'}) {
    $compress_files=1;
    $ext.='.gz';
    require IO::Zlib;
} else {
    $compress_files=0;
}

$starttime = time();

print "PHPXref $version \n";
print "(c) Gareth Watts 2000-2007\n";
print "\nProcessing $project_name...\n";

if ($config{'headerfile'}) { # read header
    open(HEADER,$config{'headerfile'}) || die "Error opening header file ".$config{'headerfile'}." for read: $!";
    $pageheader=join('',<HEADER>);
    $pageheader =~ s/__VERSION__/$version/g;
    $pageheader =~ s/__RUNDATE__/$rundate/g;
    $pageheader =~ s/__PROJECT__/$project_name/g;
    $pageheader .="\n<!-- Generated by PHPXref $version at $rundate -->\n";
    $pageheader .="<!-- PHPXref (c) 2000-2007 Gareth Watts - gareth\@omnipotent.net -->\n";
    $pageheader .="<!-- http://phpxref.sourceforge.net/ -->\n\n";
    close(HEADER);
}
if ($config{'footerfile'}) { # read footer
    open(FOOTER,$config{'footerfile'}) || die "Error opening footer file ".$config{'footerfile'}." for read: $!";
    $pagefooter=join('',<FOOTER>);
    $pagefooter =~ s/__VERSION__/$version/g;
    $pagefooter =~ s/__RUNDATE__/$rundate/g;
    $pageheader =~ s/__PROJECT__/$project_name/g;
    close(FOOTER);
}
if ($config{'db_use'} eq 'YES') {
    my $db_host    = $config{'db_host'};
    my $db_port    = $config{'db_port'};
    my $db_name    = $config{'db_name'};
    my $db_user    = $config{'db_user'};
    my $db_pass    = $config{'db_pass'};
    eval('require DBI') || die "DBI Perl Module Not Installed (Note the Windows .exe file does not support the DB_USE config option";
    require DBI::DBD;
    $dbh=DBI->connect("DBI:mysql:database=$db_name;host=$db_host;port=$db_port",$db_user,$db_pass);
}

if (length($config{'functionlist'})) { read_functions($config{'functionlist'}); }

# Copy a few icons
mkpath("$output_dir/_icons") unless (-d "$output_dir/_icons");
mkpath("$output_dir/_jstree") unless (-d "$output_dir/_jstree");
mkpath("$output_dir/_jstree/icons") unless (-d "$output_dir/_jstree/icons");;
copy("$scriptbase/folder.gif","$output_dir/_icons/folder.gif");
copy("$scriptbase/text.gif","$output_dir/_icons/text.gif");
copy("$scriptbase/phpxref.js","$output_dir/phpxref.js");
copyfiles("$scriptbase/jstree/*.js", "$output_dir/_jstree/");
copyfiles("$scriptbase/jstree/*.html", "$output_dir/_jstree/");
copyfiles("$scriptbase/jstree/icons/*.gif", "$output_dir/_jstree/icons/");
if ($config{'stylefile'}) {
    copy("$scriptbase/$config{stylefile}","$output_dir/$config{stylefile}");
} else {
    $config{'stylefile'}='';
}
if ($config{'printstylefile'}) {
    copy("$scriptbase/$config{printstylefile}","$output_dir/$config{printstylefile}");
} else {
    $config{'printstylefile'}='';
}

print "Source.............: $source_dir\n";
print "Target.............: $output_dir\n";
print "Scanning Tree......: ";

$do_follow=$Config{'d_lstat'} && 1 || 0;
find({ wanted => \&tagfile, follow_fast=> $do_follow},$source_dir);
$filecount=scalar(keys %allfiles);

print "OK - $filecount files to process\n";
print "Pass One...........: ";

$filenum=0;
$globallinecount=0;
foreach $path (keys %allfiles) {
    $path =~ m|^(.*?)([^/]+)$|;
    ($subdir,$filename)=($1,$2);
    $subdir =~ s|/+$||;
    scanfile($subdir,$filename);
    show_progress(++$filenum, $filecount);
}
    
print "\nGenerating Output..: ";

# Generate frame navigation bar
$stylelink=$config{'stylefile'} ? '<link rel="stylesheet" href="'.$config{'stylefile'}.'" type="text/css">' : '';
if ($compress_files) {
    $jstree_filename='navframe-js.js.gz';
} else {
    $jstree_filename='navframe-js.js';
}
$JSTREE=pxopen("$output_dir/$jstree_filename", 'w');
$DIRTREE=pxopen("$output_dir/navframe.$ext", 'w');
$JSINDEX=pxopen("$output_dir/navframe-js.$ext", 'w');
print $JSINDEX <<__EOD__;
<html>
<head>
<title>PHPXref Explorer</title>
<meta http-equiv="content-type" content="text/html;charset=$charset">
$stylelink
</head>
<body bgcolor="#ffffff">
<!-- Generated by PHPXref $version at $rundate -->
<!-- PHPXref (c) 2000-2007 Gareth Watts - gareth\@omnipotent.net -->
<!-- http://phpxref.sourceforge.net/ -->
<div id="slist">
<p class="dirsubtitle">Explorer</p>
<script language="JavaScript" type="text/javascript" src="_jstree/tree.js"></script>
<script language="JavaScript" type="text/javascript" src="$jstree_filename"></script>
<script language="JavaScript" type="text/javascript" src="_jstree/tree_tpl.js"></script>
<script language="JavaScript" type="text/javascript">
    root = new tree (TREE_ITEMS, tree_tpl);
</script>
</div>
</body>
</html>
__EOD__
$JSINDEX->close();

print $DIRTREE <<__EOD__;
<html>
<head>
<title>PHPXref Explorer</title>
<meta http-equiv="content-type" content="text/html;charset=$charset">
<script language="JavaScript" type="text/javascript">
  window.location='navframe-js.$ext';
</script>
$stylelink
<style type="text/css">
</style>
<body bgcolor="#ffffff">
<!-- Generated by PHPXref $version at $rundate -->
<!-- PHPXref (c) 2000-2007 Gareth Watts - gareth\@omnipotent.net -->
<!-- http://phpxref.sourceforge.net/ -->
<div id="slist">
<p class="dirsubtitle">Explorer</p>
__EOD__
print $DIRTREE "<ul class=\"dirlist\">\n";
print $DIRTREE "<li><a href=\"./index.$ext\">/</a></li>\n";
print $JSTREE "var TREE_ITEMS = [\n";
print $JSTREE "['TOP', './index.$ext'";
gendir_recurse("/",0);
print $JSTREE "]\n";
print $DIRTREE "</ul>\n";
sub gendir_recurse {
    my ($path,$depth) = @_;
    my $strippath=$path;
    $strippath =~ s/^\///;
    if (defined($dirtree{$path})) {
        print $DIRTREE "    <ul class=\"dirlist\">\n";
        $comma=',';
        foreach my $subdir (sort @{$dirtree{$path}}) {
            next if $subdir eq '.';
            print $DIRTREE "<li><a href=\"$strippath$subdir/index.$ext\" target=\"docview\">$subdir</a></li>\n";
            print $JSTREE "$comma ['$subdir', '$strippath$subdir/index.$ext'";
            gendir_recurse("$path$subdir/",$depth+1);
            print $JSTREE "]\n"; $comma=',';
        }
        print $DIRTREE "    </ul>\n";
    } 
    if (defined($files{$path})) {
        foreach $file (sort @{$files{$path}}) {
            if ($config{'explore_source'}) {
                print $JSTREE ", ['$file', '$strippath$file.source.$ext']\n";
            } else {
                print $JSTREE ", ['$file', '$strippath$file.$ext']\n";
            }
        }
    }
}
print $DIRTREE "</div>\n";
print $DIRTREE "</body></html>\n";
print $JSTREE "];\n";
$JSTREE->close();
$DIRTREE->close();


# Generate top level frame
$FRAMEHOLDER=pxopen("$output_dir/nav.$ext",'w');
print $FRAMEHOLDER <<__FRAME__;
<html>
<head>
<title>PHPXref $version: $project_name</title>
<meta http-equiv="content-type" content="text/html;charset=$charset">

<!-- Generated by PHPXref $version at $rundate -->
<!-- PHPXref (c) 2000-2007 Gareth Watts - gareth\@omnipotent.net -->
<!-- http://phpxref.sourceforge.net/ -->

<script language="JavaScript" type="text/javascript">
<!--
self.name='phpxref';
target='';
if ((o=window.location.href.indexOf('?'))>-1) {
    target=window.location.href.substr(o+1);
} 
function redir() {
    if (target) { docview.location=target; }
}
// --></script>
</head>
<frameset cols="180,*" bordercolor="#000033" frameborder="1" onLoad="redir()">
    <frame name="nav" src="navframe.$ext" marginwidth="0" marginheight="0">
    <frame name="docview" src="index.$ext" marginwidth="5" marginheight="0">
    <noframes>
        <p>No frames? <br>
        Don't worry - <a href="index.$ext">click here to view the main index; you're not missing too much ;-)</a></p>
    </noframes>
</frameset>
</html>
__FRAME__
$FRAMEHOLDER->close();

$filenum=0;
foreach $parent (keys %alldirs) {
    # print child dirs
    ($debug) && print "parent: $parent\n";
    my $depth=getdepth($parent);
    my $relroot="../" x $depth;
    $target=$output_dir.$parent;
    mkpath($target) unless -d $target;
    # Sub-directory list
    $INDEX=pxopen("$target/index.$ext",'w');
    my $newheader=$pageheader;
    $newheader =~ s/__TITLE__/$title $parent/g;
    $newheader =~ s/__CHARSET__/$charset/g;
    $newheader =~ s/__STYLEFILE__/$relroot$config{stylefile}/g if $config{'stylefile'};
    $newheader =~ s/__PRINTSTYLEFILE__/$relroot$config{printstylefile}/g if $config{'printstylefile'};
    $newheader =~ s/__RELROOT__/$relroot/g;
    $newheader.=javascript_header($depth, $parent, "index.$ext");
    print $INDEX $newheader;
    print $INDEX &navtoggle_html($relroot);
    print $INDEX &javascript_search($depth);
    print $INDEX "<div class=\"filelist\">\n";
    print $INDEX "<h2 class=\"filelist-title\">$parent</h2>\n";
    print $INDEX "<table width=\"100%\">";
    if ($depth) {
        print $INDEX "<tr><td class=\"filelist-filename\" nowrap=\"nowrap\" width=\"25\">";
        print $INDEX "<img src=\"${relroot}_icons/folder.gif\" alt=\"Folder\">";
        print $INDEX "</td><td class=\"filelist-filename\" nowrap=\"nowrap\">";
        print $INDEX "<a class=\"filelist-filename\" href=\"../index.$ext\">Up one level</a>\n";
        print $INDEX "<td>&nbsp;</td></tr>\n";
    }
    if (defined($dirtree{$parent})) {
        foreach $child (sort @{$dirtree{$parent}}) {
            print $INDEX "<tr><td valign=\"top\" class=\"filelist-filename\" nowrap=\"nowrap\" width=\"25\">";
            print $INDEX "<img src=\"${relroot}_icons/folder.gif\" alt=\"Folder\">";
            print $INDEX "</td><td valign=\"top\" class=\"filelist-filename\" nowrap=\"nowrap\">";
            print $INDEX "<a class=\"filelist-filename\" href=\"".uri_encode($child)."/index.$ext\">$child/</a><br>\n";
            print $INDEX "<td>&nbsp;</td></tr>\n";
        }
    }
    if (defined($files{$parent})) {
        # File list
        $maxfilelength=15;
        foreach $file (@{$files{$parent}}) {
            $filelength=length($file);
            $maxfilelength=$filelength if ($filelength>$maxfilelength);
        }
        foreach $file (sort @{$files{$parent}}) {
            show_progress(++$filenum, $filecount);
            print $INDEX "<tr><td valign=\"top\" class=\"filelist-filename\" nowrap=\"nowrap\" width=\"25\">";
            print $INDEX "<img src=\"${relroot}_icons/text.gif\" alt=\"File\">";
            print $INDEX "</td><td class=\"filelist-filename\" nowrap=\"nowrap\" valign=\"top\">";
            #print $INDEX "<span class=\"filelist-filename\">";
            print $INDEX "<a href=\"$file.$ext\">$file</a> ";
            $spaces=$maxfilelength-length($file);
            print $INDEX '&nbsp;'x$spaces;
            print $INDEX "[<a href=\"".uri_encode($file).".source.$ext\">source</a>]";
            $size=sprintf('%d lines',$fileinfo{"$parent$file"}{'lines'});
            print $INDEX " [$size]". '&nbsp;'x(12-length($size));
            print $INDEX "</td><td valign=\"top\" class=\"filelist-filename\" width=\"95%\">";
            if ($description=$fileinfo{"$parent$file"}{'description'}) {
                $description =~ s/^(.*?)(([ ]*[\n]){2}.*)/$1/s; # truncate after first blank line
                $description =~ s/\n/ /gso;
                print $INDEX "$description";
            }
            print $INDEX "</td></tr>\n";
            #print $INDEX "</span><br>\n";

            &processpage($parent,$file);
        }
        #print $INDEX "</pre>\n";
    }
    print $INDEX "</table>";
    print $INDEX "</div>\n";
    print $INDEX $pagefooter;
    $INDEX->close();
}

print "\n";
generate_references();
print "\n";
if (scalar(keys %unknown_constants) || scalar(keys %unknown_variables)) {
    print "\n";
    print "Some values were referenced in require and include statements\n";
    print "for which no value could be found in the configuration file so cross-referencing\n";
    print "may be incomplete: \n";
    if (scalar(keys %unknown_constants)) {
        print "Unknown constant names (ref count): ";
        @list=();
        foreach $constant (keys %unknown_constants) {
            push(@list, "$constant ($unknown_constants{$constant})");
        }
        print join(", ", @list);
        print "\n";
    }
    if (scalar(keys %unknown_variables)) {
        print "Unknown variable names (ref count): ";
        @list=();
        foreach $variable (keys %unknown_variables) {
            push(@list, "\$$variable ($unknown_variables{$variable})");
        }
        print join(", ", @list);
        print "\n";
    }
}

print "\nSummary statistics:\n";
print gen_stats();
gen_stats_page();
print "\nLocal front page URL: file://$output_dir/index.$ext\n";
print "\nDone.\n";


###########################################################################
###########################################################################
    
sub process_table_list {
    my ($tlist)=@_;
    my ($table);
    my @tables=split(/[, ]+/,$tlist);
    foreach $table (@tables) {
        next if $table =~ /^\s*$/;
        push(@{$table_references{$table}},
            {
            'subdir'    => "$subdir/",
            'filename'    => $filename,
            'line'        => $line
            }
        );
        $filetables{"$subdir/$filename"}{$table}=1;
        if (!$table_ids{$table}) {
            $table_ids{$table}=uri_encode($table);
        }
    }
}

# collapse '.' and '..' entries in a path name
sub collapse_path {
    my ($path)=@_;

    $path =~ s|/{2,}|/|g; 
    my @elements=split("/",$path);
    my @outelements=();
    FELOOP:foreach $element (@elements) {
        if ($element eq '.') {
            next FELOOP;
        }
        if ($element eq '..') {
            if (scalar(@outelements)>0) {
                pop(@outelements);
            }
            next FELOOP;
        }
        push(@outelements,$element);
    }
    return(join('/',@outelements));
}

sub resolve_path {
    my ($reqname)=@_;
    # force the root path to be relative to the source tree
    $reqname =~ s|^/+||g;
    foreach $p (@includepath) {
        if ($p eq ".") { 
            $pn="$subdir/$reqname";
        } elsif ($p =~ m|^[^/]|) {
            $pn="$subdir/$p/$reqname";
        } else {
            $pn="$p/$reqname";
        }
        $pn =~ s|/+|/|;
        $pn=collapse_path($pn);
        if ($allfiles{$pn}) {
            return($pn);
        }
    }
    #print "Failed to resolve $reqname\n";
    return("");
}

sub process_require {
    # Needs to handle a number of different formats.  Constants are often used in conjuction with include/require
    #  handles the most common idioms, but by no means foolproof:
    # include('subdir/somefile.php')   include 'subdir/somefile.php'
    # include INCLUDES.'somfile.php'
    # this check insists that constants are in UPPER case characters as good style dictates
    my ($reqname)=@_;
    my $orgreqname=$reqname;
    unless ($reqname =~ /^\s{0,3}(['"(A-Z_].{4,})$/) { # simple validity check/strip leading spaces
        return;
    }
    $reqname=$1;
    my @reqname=split('',$reqname);
    my $inconstant=$invariable=$inparens=$inquote=0;
    my $constant=$string='';
    my $ch;
    my @parts=(); # we'll concatenate the parts to produce an include path
    REQLOOP:for($i=0;$i<length($reqname);$i++) { # parse the require, character by character
        $ch=$reqname[$i];
        $repeat=1;
        while($repeat) {
            $repeat=0;
            if ($inconstant) {
                if ($ch =~ /[1-9A-Z_]/) {
                    $constant.=$ch;
                } else {
                    if (defined($config{lc $constant})) {
                        push(@parts,$config{lc $constant});
                    } else {
                        #print "unknown constant '$constant' in $reqname\n";
                        $unknown_constants{$constant}++;
                        push(@parts,'/'); # add an empty string, just in case that works
                    }
                    $inconstant=0;
                    $constant='';
                    $repeat=1; # loop over to process the current character
                }
            } elsif ($invariable) {
                if ($ch =~ /[\w_]/) {
                    $variable.=$ch;
                } else {
                    if (defined($config{lc $variable})) {
                        push(@parts,$config{lc $variable});
                    } else {
                        $unknown_variables{$variable}++;
                        push(@parts,'/');
                    }
                    $invariable=0;
                    $variable='';
                    $repeat=1;
                }
            } elsif ($inquote) {
                if ($ch ne $inquote) {
                    $string.=$ch;
                } else {
                    push(@parts,$string);
                    $inquote=0;
                    $string='';
                }
            } else {
                if ($inparens) {
                    if ($ch eq ')') {
                        last REQLOOP;
                    }
                }
                if ($ch =~ /[A-Z_]/) {
                    $inconstant=1;
                    $constant=$ch;
                } elsif ($ch eq '$') {
                    $invariable=1;
                } elsif ($ch eq '(') {
                    if ($i>3) { # too late
                        #print "Failed to parse require (1): $reqname\n";
                        return; # parse error
                    }
                    $inparens=1;
                } elsif ($ch =~ /["']/) {
                    $inquote=$ch;
                } elsif ($ch =~ /\s/) {
                    # skip it
                } elsif ($ch eq '.') {
                    if (!scalar(@parts)) {
                        #print "Failed to parse require (2): $reqname\n";
                        return; # parse error
                    }
                } elsif ($ch eq ';') {
                    last REQLOOP;
                } else {
                    #print "Failed to parse require (3): $reqname\n";
                    return;
                }
            }
        }
    }
    $reqname=join('',@parts);
    #print "Resolving $reqname\n";
    return unless $pn=resolve_path($reqname);
    push(@{$require_references{$pn}},
        {
        'subdir'    => "$subdir/",
        'filename'    => $filename,
        'line'        => $line
        }
    );
    $filerequires{"$subdir/$filename"}{$pn}=1;
    $parsed_requires{$orgreqname}=$pn;
    return 1;
}


##################################################################
sub tagfile {
    my $fext;
    $scanfile_filename=$_;
    $scanfile_filename_test=($is_case_tolerant && (lc $_)) || $_;
    $findbase=$source_dir;
    $fullname=(defined($File::Find::fullname) && $File::Find::fullname || $File::Find::name);
    $subdir=substr($File::Find::dir,length($findbase)-0);
    $subdir_test=($is_case_tolerant && (lc $subdir)) || $subdir;
    $dirname=substr($subdir,rindex($subdir,'/')+1);
    if (-d $fullname) {
        if ($bad_dirnames{$scanfile_filename_test} || $bad_pathnames{"$subdir_test/$scanfile_filename_test"}) {
            $File::Find::prune = 1;
            $_=$scanfile_filename;
            return;
        }
        ($debug) && print "DIR: $_\n"; 
        ($debug) && print "Got subdir = '$subdir'\n";
        push(@{$dirtree{"$subdir/"}},$_);
        if ($_ eq '.') {
            $alldirs{"$subdir/"}=1;
        } else {
            $alldirs{"$subdir/$_/"}=1;
        }
        $_=$scanfile_filename;
        return;
    }
    do {$_=$scanfile_filename;return;} if /~$/; # backup files often end in a tilde
    do {$_=$scanfile_filename;return;} unless (-f $_);
    do {$_=$scanfile_filename;return;} unless (-T $_); # Check that it's a text file
    do {$_=$scanfile_filename;return;} if (defined($config{'no_hidden'}) && substr($_,0,1) eq '.'); # reject hidden files
    do {$_=$scanfile_filename;return;} if $bad_filenames{$_};
    if (/\.([^.]+)$/) {
        $fext=($is_case_tolerant && (lc $1)) || $1;
        do {$_=$scanfile_filename;return;} if $bad_extensions{$fext};
    }
    if (defined(%good_extensions)) {
        do {$_=$scanfile_filename;return;} unless defined($fext);
        do {$_=$scanfile_filename;return;} unless defined($good_extensions{$fext});
    }
    $filename=$_;
    $allfiles{"$subdir/$filename"}=1;
    $_=$scanfile_filename;
}


# do a preliminary scan of a file
sub scanfile {
    my ($subdir,$filename)=@_;
    open(SFILE,"$source_dir$subdir/$filename") || die "Error opening $source_dir$subdir/$filename for read: $!";
    $state="NONE";
    ($debug) && print "Find::dir = ".$File::Find::dir."\n";
    ($debug) && print "Findbase = $findbase\n";
    #$subdir = $subdir ? $subdir : "__TOP__";
    my ($description,$comment,$briefcomment,$class)=("","","","");
    my %commenttags=();
    my %filetags=();
    $line=$commentendline=$commentcount=$funccount=$classcount=0;
    while(<SFILE>) {
        chomp;
        $line++; $globallinecount++;
        if ($state eq "NONE") {
            # CUSTOM: Detect the beginning of an old style function comment block ( '////' by default)
            if (!$config{'no_trad_doc'} && m|^\s*////\s*$|) {
                $state="COMMENT";
                $commentstyle="TRAD";
                $briefcomment=$comment="";
                $commentstartline=$line;
            } elsif (m|^\s*/\*\*\s*$|) {
                if ($commentcount==1 && $funccount==0 && $classcount==0) {
                    $description=$briefcomment.$comment;
                    %filetags=%commenttags;
                }
                $state="COMMENT";
                $commentstyle="PHPDOC";
                $briefcomment=$comment="";
                %commenttags=();
                $lasttag=$incomment=0;
                $commentstartline=$line;
            } elsif (m|\s*/\*+\s*$|) {
                $state="COMMENT";
                $commentstyle="OTHER";
                $commentstartline=$line;
            } elsif (m|^<!--|) {
                $state="HTMLCOMMENT";
            }
            # CUSTOM: Get the description of the file we're looking at 
            # you usually have this once at the top of the file
            else {(m|^\s*// $filename -\s*(.*)| && ($description.=$1));}
        } elsif ($state eq "COMMENT") {
            # CUSTOM: Get the short description of the function
            if ($commentstyle eq 'TRAD') {
                if (m|^\s*//\s*!(.*)|) {
                    $briefcomment=$1;
                    $commentendline=$line;
                # CUSTOM: or the fuller description
                } elsif (m|^\s*//\s*(.*)|) {
                    $comment.="$1\n";
                    $commentendline=$line;
                } 
                m|^\s*// TABLES:\s*(.*)| && process_table_list($1);
            } elsif ($commentstyle eq 'PHPDOC') {
                if (m|^.*\*/|) {
                    $state="NONE";
                    $commentcount++;
                    $commentendline=$line;
                    $incomment=0;
                    $briefcomment =~ s/\r\n/\n/gs;
                    $briefcomment =~ s/\r/\n/gs;
                    $comment =~ s/\r\n/\n/gs;
                    $comment =~ s/\r/\n/gs;
                } elsif (m|^\s*\*\s*@(\w+)\s*(.*)$|) {
                    $lasttag=lc $1;
                    push @{$commenttags{$lasttag}},$2;
                    ($lasttag eq 'tables') && (process_table_list($2));
                } else {
                    if (!$lasttag) {
                        if (m|^\s*\*+\s*(.*)$|) {
                            $cline=$1;
                            $cline =~ s/\s+$//;
                            if (!$incomment) {
                                if (!$cline) {
                                    if ($briefcomment) {
                                        $incomment=1;
                                    }
                                } else {
                                    $briefcomment.="$cline\n" unless /\$id[:\$]/io;
                                }
                            } else {
                                $comment.="$cline\n" unless (!$comment && $cline =~ /^\s*$/); # don't add blank lines to the top
                            }
                        }
                    }
                }
            } elsif ($commentstyle eq 'OTHER') {
                if (m|\*/|) {
                    $state="NONE";
                }
            }
        } elsif ($state eq "HTMLCOMMENT") {
            if (m|-->|) {
                $state="NONE";
            }
        }
        if ($state ne "COMMENT" && $state ne "HTMLCOMMENT") {
            m|^\s*// TABLES:\s*(.*)| && process_table_list($1);
            if (m%\b(require_once|include_once|require|include)([^;]+)%) {
                process_require($2);
            }
            # strip comments from the line
            s|/\*.*?\*/||g;
            s|[\};]\s*//.*?$||g;
            s|^\s*//.*?$||g;
            if (m%^\s*(class)\s+(\w+)\s*(\{|extends|implements|\Z)%i
                || m%^\s*(interface)\s+(\w+)%i) { # matched a class or interface definition
                $isinterface = lc($1) eq 'interface';
                $class=lc($2);
                $orgname = $2;
                if ($briefcomment && $line-$commentendline>3) { 
                    # too far away to tie to this class
                    # maybe the comment was a comment describing the entire file
                    if ($commentcount==1 && $funccount==0 && $classcount==0) {
                        $description="$briefcomment\n$comment";
                        %filetags=%commenttags;
                    }
                    $function=$funcargs=$comment=$briefcomment="";
                    %commenttags=();
                    $defline=$line;
                } elsif ($briefcomment) {
                    $defline=$commentstartline;
                } else {
                    $defline=$line;
                }
                push(@{$class_definitions{$class}},
                    {
                        'subdir' => $subdir,
                        'filename' => $filename,
                        'briefcomment' => $briefcomment,
                        'comment' => $comment,
                        'commenttags' => {%commenttags},
                        'line'    => $defline,
                        'orgcase' => $orgname,
                        'isinterface' => $isinterface,
                        }
                    );
                push(@{$fileclasses{"$subdir/$filename"}},
                        {
                        'name'    => $class,
                        'briefcomment'    => $briefcomment,
                        'comment'    => $comment,
                        'commenttags' => {%commenttags},
                        'line'    => $defline,
                        'orgcase' => $orgname,
                        'isinterface' => $isinterface,
                        }
                    );
                if (!$class_ids{$class}) {
                    $class_ids{$class}=uri_encode($class);
                }
                $function=$funcargs=$comment=$briefcomment="";
                $classcount++;
                %commenttags=();
            }

            # Variables are matched here to include those that
            # occur in function definitions.
            while (/\$\{{0,1}(\w+)/g) { # match variables 0.5
                push(@{$var_references{$1}},
                    {
                    'subdir'    => "$subdir/",
                    'filename'  => $filename,
                    'line'      => $line
                    }
                );
                if (!$var_ids{$1})  { $var_ids{$1}=uri_encode($1); }
                if (!$var_nums{$1}) { $var_nums{$1}=$var_num++; }
            }
            while (/\$\{{0,1}(\w+)\s*=[^=]/g) { # match variable definitions 0.5
                push(@{$var_definitions{$1}},
                    {
                    'subdir'    => "$subdir/",
                    'filename'  => $filename,
                    'line'      => $line
                    }
                );
                if (!$var_ids{$1})  { $var_ids{$1}=uri_encode($1); }
                if (!$var_nums{$1}) { $var_nums{$1}=$var_num++; }
            }
            while (/\$\w+->(\w+)[^\w(]/g) { # match class variables
                push(@{$var_references{$1}},
                    {
                    'subdir'    => "$subdir/",
                    'filename'  => $filename,
                    'line'      => $line
                    }
                );
                if (!$var_ids{$1})  { $var_ids{$1}=uri_encode($1); }
                if (!$var_nums{$1}) { $var_nums{$1}=$var_num++; }
            }
            while (/\b([A-Z\d_]{3,40})\b/g) { # match a constant reference
                push(@{$const_references{$1}},
                    {
                    'subdir'    => "$subdir/",
                    'filename'  => $filename,
                    'line'      => $line
                    }
                );
            }
            #if (/^\s*function/i || /^\s*(public|private|protected)(\s+static|\s+final|\s+static final|\s+final static)?\s+function/i) { # XXX
            if (/^\s*function/i || /^\s*(\s*public|\s*private|\s*protected|\s*static|\s*final)+\s+function/i) {
                $state="FUNCTION";
                if ($briefcomment && ($line-$commentendline>3)) { # too far away to tie to this class
                    # maybe the comment was a comment describing the entire file
                    if ($commentcount==1 && $funccount==0 && $classcount==0) {
                        $description="$briefcomment\n$comment";
                        %filetags=%commenttags;
                    }
                    $function=$funcargs=$comment=$briefcomment="";
                    %commenttags=();
                }
                $funccount++;
            } 
            if ($state eq "FUNCTION") {
                if (!$function && (/^(\s*)function\s+&{0,1}([_\w]+)\s*(.*)$/i || /^\s*(\s*public|\s*private|\s*protected|\s*static|\s*final)+\s+function\s+&{0,1}([_\w]+)\s*(.*)$/i)) {
                    # after a function name, the next character has to be
                    # either an open bracket for the function definition
                    # or the start of a comment or the end of the line
                    # (in the case that the parameter bracket is on the next line)
                    $function=lc($2); #function names are case insensitive
                    #print "function: $function\n";
                    $function_orgcase = $2;
                    chomp($remainder=$3);
                    $remainder =~ s%/\*.*?\*/%%g; # strip inline comments
                    next unless ($remainder eq '' || $remainder =~ m%^(//|#|/*|\()%);
                    if ($briefcomment && $commentstartline) {
                        $functionline=$commentstartline;
                    } else {
                        $functionline=$line;
                    }
                    if ($remainder=~ /^\(([^)]*)(.*)$/) {
                        chomp($funcargs=$1);
                        $remainder=$2;
                        $funcargs =~ s%(//|#).*%%; # strip trailing comments
                        $funcargs =~ s%/\*.*?\*/%%; # strip inline comments
                        if (index($remainder,')')>-1) {
                            $funcdone=1;
                        }
                    }
                } elsif ($function && /^\s*(\(\s*){0,1}([^)]*)(.*)$/) { # function split across lines 
                    chomp($funcarg=$2);
                    $remainder=$3;
                    $funcarg =~ s%(//|#).*%%; # strip trailing comments
                    $funcarg =~ s%/\*.*?\*/%%g; # strip inline comments
                    chomp($funcargs.=$funcarg);
                    if (index($remainder,')')>-1) {
                        $funcdone=1;
                    }
                }
                if ($funcdone) {
                    $funcdone=0;
                    $funcargs =~ s/\s+/ /g;
                    push(@{$func_definitions{$function}},
                        {
                        'subdir' => $subdir,
                        'filename' => $filename,
                        'args' => $funcargs,
                        'briefcomment' => $briefcomment,
                        'comment' => $comment,
                        'commenttags' => {%commenttags},
                        'class' => $class,
                        'line'    => $functionline,
                        'orgcase' => $function_orgcase
                        }
                    );
                    push(@{$filefunctions{"$subdir/$filename"}},
                        {
                        'name'    => $function,
                        'args'    => $funcargs,
                        'briefcomment'    => $briefcomment,
                        'comment'    => $comment,
                        'commenttags' => {%commenttags},
                        'class' => $class,
                        'line'    => $functionline,
                        'orgcase' => $function_orgcase
                        }
                    );
                    if (!$func_ids{$function}) {
                        $func_ids{$function}=uri_encode($function);
                    }
                    $function=$funcargs=$comment=$briefcomment="";
                    %commenttags=();
                    $state="NONE";
                }
            } else { # not in a function definition
                while (/\b(\w+)\s*\(/g) { # match a function reference
                    $funcname=lc($1); # treat without case sensitivity
                    push(@{$func_references{$funcname}},
                        {
                        'subdir'    => "$subdir/",
                        'filename'  => $filename,
                        'line'      => $line
                        }
                    );
                    if (!$func_ids{$funcname} && defined($php_functions{$funcname})) { 
                        $func_ids{$funcname}=uri_encode($funcname); 
                    }
                }
                while (/\bnew\s+(\w+)/gi) { # match a class reference
                    $classname=lc $1;
                    push(@{$class_references{$classname}},
                        {
                        'subdir'    => "$subdir/",
                        'filename'  => $filename,
                        'line'      => $line
                        }
                    );
                    #if (!$class_ids{$classname}) { $class_ids{$classname}=uri_encode($classname); }   
                }
                while (/\b(\w+)::\w+/g) { # match a class reference
                    $classname=lc $1;
                    push(@{$class_references{$classname}},
                        {
                        'subdir'    => "$subdir/",
                        'filename'  => $filename,
                        'line'      => $line
                        }
                    );
                    #if (!$class_ids{$classname}) { $class_ids{$classname}=uri_encode($classname); }   
                }
                while (/\w+\s+(extends|implements)\s+(\w+)/gi) {
                    $classname=lc $2;
                    push(@{$class_references{$classname}},
                        {
                        'subdir'    => "$subdir/",
                        'filename'  => $filename,
                        'line'      => $line
                        }
                    );
                    #if (!$class_ids{$classname}) { $class_ids{$classname}=uri_encode($classname); }   
                }
                while (/\b[dD][eE][fF][iI][nN][eE]\s{0,1}\(\s{0,2}['"]{0,1}([A-Z0-9_]{3,40})\b/g) {
                    push(@{$const_definitions{$1}},
                        {
                        'subdir' => $subdir,
                        'filename' => $filename,
                        'line'      => $line
                        }
                    );
                }
            }
        }
    }
    # Trap the file description in case it's been missed.
    if ($briefcomment && $commentcount==1 && $funccount==0 && $classcount==0) {
        $description="$briefcomment\n$comment";
    }
    @stat=stat(SFILE);
    close(SFILE);
    push(@{$files{"$subdir/"}},$filename);
    $fileinfo{"$subdir/$filename"}{'filename'}=$filename;
    $fileinfo{"$subdir/$filename"}{'description'}=$description;
    $fileinfo{"$subdir/$filename"}{'lines'}=$line;
    $fileinfo{"$subdir/$filename"}{'size'}=$stat[7];
    $fileinfo{"$subdir/$filename"}{'ctime'}=$stat[10];
    $fileinfo{"$subdir/$filename"}{'commenttags'}={%filetags};
    $files{"$subdir/$filename"}=$description;
}


sub processpage {
    my ($subdir,$filename)=@_;

    ($debug) && print "Processing $filename in $subdir\n";
    $htmldir=striptrailingslash("$output_dir/$subdir");
    my $depth=getdepth($subdir);
    $relroot="../" x (getdepth($subdir));
    ($debug) && print "htmldir = $htmldir\n";
    unless (-d $htmldir) {
        mkpath($htmldir) || die "Error creating directory $htmldir: $!";
    }
    my $pagefile="$htmldir/$filename.$ext";
    $FILEINFO=pxopen($pagefile,'w');
    my $newheader=$pageheader;
    $newheader =~ s/__TITLE__/$title Detail view of $filename/g;
    $newheader =~ s/__CHARSET__/$charset/g;
    $newheader =~ s/__STYLEFILE__/$relroot$config{stylefile}/g if $config{'stylefile'};
    $newheader =~ s/__PRINTSTYLEFILE__/$relroot$config{printstylefile}/g if $config{'printstylefile'};
    $newheader =~ s/__RELROOT__/$relroot/g;
    $newheader.=javascript_header($depth, $subdir, "$filename.$ext");
    my ($toplinks,$funcbody)=("","");
    print $FILEINFO $newheader;
    print $FILEINFO &navtoggle_html($relroot);
    print $FILEINFO &javascript_search($depth);
    print $FILEINFO "<h2 class=\"details-heading\"><a href=\"./index.$ext\">$subdir</a> -> <a href=\"$filename.source.$ext\">$filename</a> (summary)</h2>\n";
    print $FILEINFO "<div class=\"details-summary\">\n";
    print $FILEINFO "<p class=\"viewlinks\">[<a href=\"$filename.source.$ext\">Source view</a>]\n";
    print $FILEINFO "[<a href=\"javascript:window.print();\">Print</a>]\n"; 
    print $FILEINFO "[<a href=\"${relroot}_stats.$ext\">Project Stats</a>]</p>\n"; 
    if (length($description)) {
        $description =~ s/^\s+//s;
        $description =~ s/\s+$//s;
        print $FILEINFO "<p><b>".formatstr($description, 1)."</b></p>\n";
    } else {
        print $FILEINFO "<p><b>(no description)</b></p>\n";
    }
    $inccount=defined($require_references{"$subdir$filename"}) ? scalar(@{$require_references{"$subdir$filename"}}) : 0;
    $refcount=defined($file_references{"$subdir$filename"}) ? scalar(@{$file_references{"$subdir$filename"}}) : 0;
    print $FILEINFO "<table>\n";
    foreach $tag (sort keys %{$fileinfo{"$subdir$filename"}{'commenttags'}}) {
        next unless (defined($doc_file_tags{$tag}));
        foreach $tagval (@{$fileinfo{"$subdir$filename"}{'commenttags'}{$tag}}) {
            if ($config{'link_uri'}) {
                $tagval =~ s/[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}/<a href="mailto:$&">$&<\/a>/g;
                $tagval =~ s%(http|https)://[a-z0-9.-]+(:\d+)?/\S*%<a href="$&" target="_blank">$&</a>%gi;
            }
            print $FILEINFO "<tr><td align=\"right\">".(ucfirst($tag)).": </td><td>$tagval</td></tr>\n";
        }
    }
    printf $FILEINFO "<tr><td align=\"right\">File Size: </td><td>%d lines (%d kb)</td></tr>\n", $fileinfo{"$subdir$filename"}{"lines"}, $fileinfo{"$subdir$filename"}{"size"}/1000+0.5;
    if ($inccount) {
        printf $FILEINFO "<tr><td align=\"right\">Included or required: </td><td><a href=\"%s\">%d time%s</a></td></tr>\n", "$filename.xref.$ext", $inccount, $inccount==1 ? '' : 's';
    } else {
        print $FILEINFO "<tr><td align=\"right\">Included or required:</td><td>0 times</td></tr>\n";
    }
    if ($config{'fullxref'}) {
        printf $FILEINFO "<tr><td align=\"right\" valign=\"top\">Referenced: </td><td>%d time%s</td></tr>\n", $refcount, $refcount==1 ? '' : 's';
    }
    $reqcount=defined($filerequires{"$subdir$filename"}) ? scalar(keys %{$filerequires{"$subdir$filename"}}) : 0;
    print $FILEINFO "<tr><td align=\"right\" valign=\"top\">Includes or requires: </td><td>";
    printf $FILEINFO "%d file%s", $reqcount, $reqcount==1 ? '' : 's';
    if ($filerequires{"$subdir$filename"}) {
        foreach $reqfile (keys %{$filerequires{"$subdir$filename"}}) {
            $reqfile =~ s|^/+||;
            print $FILEINFO "<br>&nbsp;<a href=\"${relroot}$reqfile.$ext\">$reqfile</a>\n";
        }
    } 
    print $FILEINFO "</td></tr>\n";
    
    if ($filetables{"$subdir$filename"}) {
        print $FILEINFO "<tr><td valign=\"top\" align=\"right\">Tables referenced: </td><td>\n";
        $x=0;
        foreach $table (sort keys %{$filetables{"$subdir$filename"}}) {
            print $FILEINFO "<br>\n" if $x; $x=1;
            print $FILEINFO "&nbsp;<a href=\"${relroot}_tables/".$table_ids{$table}.".$ext\">$table</a>";
        }
        print $FILEINFO "</td></tr>\n";
    }

    print $FILEINFO "</table>\n";
    
    %classlinks=();
    %classmethods=();
    %classbody=();
    if (defined($filefunctions{"$subdir$filename"})) {
        ($debug) && print "filefunctions{$subdir/$filename}\n";
        $funcbody='';
        foreach $functionref (@{$filefunctions{"$subdir$filename"}}) {
            $funcname=$functionref->{'name'};
            $funcorgname = $functionref->{'orgcase'};
            $classname=$functionref->{'class'};
            if (!$func_ids{$funcname}) {
                $func_ids{$funcname}=uri_encode($funcname);
            }
            $funcloc="$filename.source.$ext#l".$functionref->{"line"};
            $classlinks{$classname}.="&nbsp;&nbsp;<a href=\"#$funcname\">$funcorgname</a>()<br>\n";
            $classmethods{$classname}+=1;
            $args=formatstr($functionref->{"args"}, 1);
            $args =~ s/\$\{{0,1}(\w+)/"<a href=\"${relroot}_variables\/".uri_encode($1).".$ext\">\$$1<\/a>"/eg;
            $classbody{$classname}.="<table border=\"0\" width=\"80%\" class=\"funcinfo\"><tr class=\"funcinfo-title\"><td>\n";
            $classbody{$classname}.="<a name=\"$funcname\" onClick=\"logFunction('$funcname', '$funcloc')\" href=\"$funcloc\">";
            $classbody{$classname}.="$funcorgname</a>($args)&nbsp;&nbsp;\n";
            $classbody{$classname}.="<a href=\"${relroot}_functions/".$func_ids{$funcname}.".$ext\"><small>X-Ref</small></a>\n";
            $classbody{$classname}.="</td></tr><tr class=\"funcinfo-body\"><td>";
            if (length($functionref->{"briefcomment"})||length($functionref->{"comment"})||scalar(keys %{$functionref->{"commenttags"}})) {
                $classbody{$classname}.="<b>".formatstr($functionref->{"briefcomment"}, 1)."</b><BR>";
                $classbody{$classname}.=formatstr($functionref->{"comment"}, 1);
                if (defined(%doc_tags)) {
                    foreach $tag (keys %doc_tags) {
                        foreach $tagval (@{$functionref->{"commenttags"}->{$tag}}) {
                            $tagval = formatstr($tagval, 1);
                            if ($config{'link_uri'}) {
                                $tagval =~ s/[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}/<a href="mailto:$&">$&<\/a>/g;
                                $tagval =~ s%(http|https)://[a-z0-9.-]+(:\d+)?/\S*%<a href="$&" target="_blank">$&</a>%gi;
                            }
                            $classbody{$classname}.="<b>$tag:</b> $tagval<br>\n";
                        }
                    }
                }
            } else {
                $classbody{$classname}.="<i>No description</i>";
            }
            $classbody{$classname}.="</td></tr></table>\n\n";
            $classbody{$classname}.="<br>\n";
            #$body.="</UL><BR><BR>\n";
        }
        if (defined($fileclasses{"$subdir$filename"}) && scalar(@{$fileclasses{"$subdir$filename"}})>0) {
            $classcount=scalar(@{$fileclasses{"$subdir$filename"}});
            printf $FILEINFO "<h3>Defines %d class%s</h3>\n<div class=\"inset\">\n",$classcount,$classcount==1 ? '' : 'es';
            foreach $classdata (@{$fileclasses{"$subdir$filename"}}) {
                $classname=$classdata->{'name'};
                $classorgname=$classdata->{'orgcase'};
                $methodcount = defined($classmethods{$classname}) ? $classmethods{$classname} : 0;
                printf $FILEINFO "<p><b>%s::</b> (%d method%s):<br>\n", $classorgname, $methodcount, $methodcount==1 ? '' : 's';
                print $FILEINFO $classlinks{$classname} if defined($classlinks{$classname});
                print $FILEINFO "</p>\n";
                $funcbody.="<div class=\"details-classinfo\">\n";
                if ($classdata->{'isinterface'}) {
                    $funcbody.="<p class=\"details-classtitle\">Interface: <a name=\"$classname\"><b>$classorgname</b></a>";
                } else {
                    $funcbody.="<p class=\"details-classtitle\">Class: <a name=\"$classname\"><b>$classorgname</b></a>";
                }
                $funcbody.="&nbsp;&nbsp;- <a href=\"${relroot}_classes/".$class_ids{$classname}.".$ext\"><small>X-Ref</small></a>\n";
                $funcbody.="</p>\n";
                if (length($classdata->{"briefcomment"})||length($classdata->{"comment"})||scalar(keys %{$classdata->{"commenttags"}})) {
                    $funcbody.="<b>".formatstr($classdata->{'briefcomment'}, 1)."</b><br>\n";
                    $funcbody.=formatstr($classdata->{'comment'}, 1);
                    if (defined(%doc_tags)) {
                        foreach $tag (keys %doc_tags) {
                            foreach $tagval (@{$functionref->{"commenttags"}->{$tag}}) {
                                $tagval = formatstr($tagval, 1);
                                if ($config{'link_uri'}) {
                                    $tagval =~ s/[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}/<a href="mailto:$&">$&<\/a>/g;
                                    $tagval =~ s%(http|https)://[a-z0-9.-]+(:\d+)?/\S*%<a href="$&" target="_blank">$&</a>%gi;
                                }
                                $classbody{$classname}.="<b>$tag:</b> $tagval<br>\n";
                            }
                        }
                    }
                }
                if (defined($classbody{$classname})) {
                    $funcbody.="<div class=\"inset\">";
                    $funcbody.=$classbody{$classname};
                    $funcbody.="</div>\n";
                }
                $funcbody.="</div>\n";
            }
            print $FILEINFO "</div>\n";
        }
        if (defined($classlinks{''})) {
            $funccount = defined($classmethods{$classname}) ? $classmethods{$classname} : 0;
            printf $FILEINFO "<h3>Defines %d function%s</h3>\n<div class=\"inset\">\n", $funccount, $funccount==1 ? '' : 's';
            print $FILEINFO $classlinks{''};
            print $FILEINFO "</div>\n";
            $funcbody.="<div class=\"details-classtitle\"><a name=\"functions\"><b>Functions</b></a></div>\n";
            $funcbody.="<div class=\"details-classinfo\">\n";
            $funcbody.="Functions that are not part of a class:<br><br>\n";
            $funcbody.="<div class=\"inset\">";
            $funcbody.=$classbody{''};
            $funcbody.="</div>";
            $funcbody.="</div>\n";
        }
        #print $FILEINFO "</UL><HR><BR>\n";
        #print $FILEINFO $body;
    } else {
        #print $FILEINFO "No functions defined in this file<br>\n";
    }
    my $ti=0;
    print $FILEINFO "</div>\n";
    # Generate class/function details

    print $FILEINFO "<br><div class=\"details-funclist\">\n";
    print $FILEINFO "$funcbody\n";
    print $FILEINFO "</div>\n";
    print $FILEINFO $pagefooter;
    $FILEINFO->close();

    if ($require_references{"$subdir$filename"} || $file_references{"$subdir$filename"}) {
        $xreffile="$htmldir/$filename.xref.$ext";
        $FILEXREF=pxopen($xreffile,'w');
        $newheader=$pageheader;
        $newheader =~ s/__TITLE__/$title Require cross reference for $filename/g;
        $newheader =~ s/__CHARSET__/$charset/g;
        $newheader =~ s/__STYLEFILE__/$relroot$config{stylefile}/g if $config{'stylefile'};
        $newheader =~ s/__PRINTSTYLEFILE__/$relroot$config{printstylefile}/g if $config{'printstylefile'};
        $newheader =~ s/__RELROOT__/$relroot/g;
        $newheader.=javascript_header($depth, $subdir, "$filename.$ext");
        print $FILEXREF $newheader;
        print $FILEXREF &navtoggle_html($relroot);
        print $FILEXREF "<br>Directory: <a href=\"./index.$ext\">$subdir</a><br>\n";
        print $FILEXREF &javascript_search($depth); 
        print $FILEXREF "<h1><a href=\"$filename.$ext\">$filename</a></h1>\n";
        if ($require_references{"$subdir$filename"}) {
            print $FILEXREF "Included or required ".scalar(@{$require_references{"$subdir$filename"}})." times by other pages:<BR>\n";
            print $FILEXREF "<ul>\n";
            foreach $xref (@{$require_references{"$subdir$filename"}}) {
                $file=$xref->{'filename'};
                $fs=$xref->{'subdir'};
                $line=$xref->{'line'};
                $fs =~ s|^/+||;
                $fs =~ s|/+$||;
                print $FILEXREF "<li>";
                print $FILEXREF "<a href=\"${relroot}$fs/$file.$ext\">$fs/$file</a>";
                print $FILEXREF " -> <a href=\"${relroot}$fs/$file.source.$ext#l$line\">line $line</a>";
                print $FILEXREF "</li>\n";
            }
            print $FILEXREF "</ul>\n";
        }
        if ($file_references{"$subdir$filename"}) {
            print $FILEXREF "Referenced ".scalar(@{$file_references{"$subdir$filename"}})." times by other pages:<br>\n";
            print $FILEXREF "<ul>\n";
            foreach $xref (@{$file_references{"$subdir$filename"}}) {
                $file=$xref->{'filename'};
                $fs=$xref->{'subdir'};
                $line=$xref->{'line'};
                $fs =~ s|^/+||;
                $fs =~ s|/+$||;
                print $FILEXREF "<li>";
                print $FILEXREF "<a href=\"${relroot}$fs/$file.$ext\">$fs/$file</a>";
                print $FILEXREF " -> <a href=\"${relroot}$fs/$file.source.$ext#l$line\">line $line</a>";
                print $FILEXREF "</li>\n";
            }
            print $FILEXREF "</ul>\n";
        }
        print $FILEXREF $pagefooter;
        $FILEXREF->close();
    }

    if ($compress_files) {
        $TXT=pxopen("$htmldir/$filename.source.txt.gz", "w");
        open(SOURCE,"$source_dir/$subdir/$filename") || die "Error opening source: $!";
        while(<SOURCE>) {
            print $TXT $_;
        }
        close SOURCE;
        $TXT->close();
    } else {
        copy("$source_dir/$subdir/$filename", "$htmldir/$filename.source.txt");
    }
    open(SOURCE,"$source_dir/$subdir/$filename") || die "Error opening source: $!";
    $SYNTAX=pxopen("$htmldir/$filename.source.$ext",'w');
    $line=1; 
    $incomment=0;
    $newheader=$pageheader;
    $newheader =~ s/__TITLE__/$title $subdir$filename source/g;
    $newheader =~ s/__CHARSET__/$charset/g;
    $newheader =~ s/__STYLEFILE__/$relroot$config{stylefile}/g if $config{'stylefile'};
    $newheader =~ s/__PRINTSTYLEFILE__/$relroot$config{printstylefile}/g if $config{'printstylefile'};
    $newheader =~ s/__RELROOT__/$relroot/g;
    $newheader.=javascript_header($depth, $subdir, "$filename.source.$ext");
    print $SYNTAX $newheader;
    print $SYNTAX &navtoggle_html($relroot);
    print $SYNTAX &javascript_search($depth);
    print $SYNTAX "<h2 class=\"listing-heading\"><a href=\"./index.$ext\">$subdir</a> -> <a href=\"$filename.$ext\">$filename</a> (source)</h2>\n";
    print $SYNTAX "<div class=\"listing\">\n";
    print $SYNTAX "<p class=\"viewlinks\">[<a href=\"$filename.$ext\">Summary view</a>]\n";
    print $SYNTAX "[<a href=\"javascript:window.print();\">Print</a>]\n"; 
    if ($compress_files) {
        print $SYNTAX "[<a href=\"$filename.source.txt.gz\" target=\"_new\">Text view</a>]\n";  #0.5.0
    } else {
        print $SYNTAX "[<a href=\"$filename.source.txt\" target=\"_new\">Text view</a>]\n";  #0.5.0
    }
    print $SYNTAX "</p>\n";
    print $SYNTAX "<pre>\n"; # 0.3.1
    %seen_funcs=();
    %seen_phpfuncs=();
    %seen_classes=();
    %seen_consts=();
    $dirdepth=getdepth($subdir);
    while(<SOURCE>) {
        $relroot="../" x $dirdepth;
        chomp;
        #undef %refed;
        $outline='';
        @slices=();
        $slicenum=100;
        %slicerepl=();
        if (m%^\s*(#|//)% # match lines beginning with a hash or slash slash
            || (m%^\s*/\*(.*)\*/\s*$% && $1 !~ m%\*/%) # match lines such as "/* comment here */"
            || ($incomment && ! m|\*/|) # if we're in a /* comment, make sure there's no */ in this line
            ) {  # comment
            $outline="<span class=\"comment\">".formatstr($_, 0)."</span>";
        } else {
            if ($incomment) {
                # we know from the above check that the comment gets terminated on this line
                m%^(.*?\*/)(.*)$%;
                push(@slices,$1);
                $_=$2;
                $incomment=0;
            }
            while (length($_)) { # slice the line up into commented bits and uncommented bits
                if (m%^(.*?)(/\*.*?\*/)(.*)$%) {
                    # match a complete comment inside a line
                    push(@slices,$1);
                    push(@slices,$2);
                    $_=$3; # parse the rest of the line
                } elsif (m%^(.*)(/\*.*)$%) { #/
                    # line ends in a comment
                    push(@slices,$1);
                    push(@slices,$2);
                    $incomment=1;
                    $_='';
                } elsif (m%^(.*[;\}]\s*)(//|#)(.*)$%) { 
                    # Line ends in a # or // style comment
                    push(@slices, $1);
                    push(@slices, "$2$3");
                    $_='';
                } else {
                    # no comments in the line
                    push(@slices,$_);
                    $_='';
                }
            }
            foreach $slice (@slices) {
                if ($slice =~ m%^/\*% || $slice =~ m%\*/$% || $slice =~ m%^(//|#)%) { # start or end of a comment
                    $id='~SLICE'.$slicenum++.'~';
                    $slicerepl{$id}="<span class=\"comment\">".formatstr($slice, 0)."</span>";
                    $slice=$id;
                    next;
                }
                $slice =~ s%\b(require_once|include_once|require|include)([^;]+)%
                    ($req,$path)=($1,$2);
                    #print "\nLOOP ON $req $path\n";
                    if (defined($parsed_requires{$path})) {
                        $reqpath=$parsed_requires{$path};
                        $reqpath =~ s|^/+||;
                        $reqpath =~ m|([^/]+)$|;
                        $tailpath=$1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<span class=\"keyword\">$req<\/span> <a class=\"filename\" href=\"${relroot}$reqpath.$ext\" onMouseOver=\"reqPopup(event, '$tailpath', '${relroot}".formatstr($reqpath)."')\">".formatstr($path, 0)."</a>";
                        $id;
                    } else {
                        $&;
                    }
                    %ieg;
                $slice =~ s/\b(new\s+)(\w+)/
                    if ($class_ids{lc $2}) {
                        $seen_classes{lc $2}=1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<span class=\"keyword\">$1<\/span><a class=\"class\" onClick=\"logClass('$2')\" href=\"${relroot}_classes\/".$class_ids{lc $2}.".$ext\" onMouseOver=\"classPopup(event,'".$class_ids{lc $2}."')\">$2<\/a>";
                        $id;
                    } else {
                        $&;
                    }
                    /gie; # if $sub_class;
                $slice =~ s/\b(\w+)(::)(?=\w+)/ # all hail the positive lookahead assertion
                    if ($class_ids{lc $1}) {
                        $seen_classes{lc $1}=1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<a class=\"class\" onClick=\"logClass('$1')\" href=\"${relroot}_classes\/".$class_ids{lc $1}.".$ext\" onMouseOver=\"classPopup(event,'".$class_ids{lc $1}."')\">$1<\/a>$2";
                        $id;
                    } else {
                        $&;
                    }
                    /gie; # if $sub_class2;
                $slice =~ s/\b(extends)(\s+)(\w+)/
                    if ($class_ids{lc $3}) {
                        $seen_classes{lc $3}=1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<span class=\"keyword\">$1<\/span>$2<a class=\"class\" onClick=\"logClass('$3')\" href=\"${relroot}_classes\/".$class_ids{lc $3}.".$ext\" onMouseOver=\"classPopup(event,'".$class_ids{lc $3}."')\">$3<\/a>";
                        $id;
                    } else {
                        $&;
                    }
                    /gie; # if $sub_class3;
                $slice =~ s/\b(implements)(\s+)(\w+)/
                    $classname=lc $3;
                    if ($class_ids{$classname}) {
                        $int=0;
                        foreach $def (@{$class_definitions{$classname}}) {
                            if ($def->{'isinterface'}) {
                                $int=1;
                            }
                        }
                        if ($int) {
                            $seen_classes{lc $3}=1;
                            $id='~SLICE'.$slicenum++.'~';
                            $slicerepl{$id}="<span class=\"keyword\">$1<\/span>$2<a class=\"class\" onClick=\"logClass('$3')\" href=\"${relroot}_classes\/".$class_ids{lc $3}.".$ext\" onMouseOver=\"classPopup(event,'".$class_ids{lc $3}."')\">$3<\/a>";
                            $id;
                        } else {
                            $&;
                        }
                    } else {
                        $&;
                    }
                    /gie; # if $sub_class3;
                $slice =~ s/(\$\w+->)(\w+)([^\w(])/ # Link class variables
                    if (defined($var_ids{$2})) {
                        if (!defined($var_nums{$1})) { $var_nums{$1}=$var_num++; }
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<a onClick=\"logVariable('$2')\" class=\"var it$var_nums{$1}\" onMouseOver=\"hilite($var_nums{$1})\" onMouseOut=\"lolite()\" href=\"${relroot}_variables\/$var_ids{$2}.$ext\">$2<\/a>";
                        $1.$id.$3;
                    } else {
                        $&;
                    }
                    /ge;
                $slice =~ s/\$\{{0,1}(\w+)/ # Link variables
                    if (defined($var_ids{$1})) {
                        if (!defined($var_nums{$1})) { $var_nums{$1}=$var_num++; }
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<a class=\"var it$var_nums{$1}\" onMouseOver=\"hilite($var_nums{$1})\" onMouseOut=\"lolite()\" onClick=\"logVariable('$1')\" href=\"${relroot}_variables\/$var_ids{$1}.$ext\">\$$1<\/a>";
                        $id;
                    } else {
                        $&;
                    }
                    /ge;
                $slice =~ s/\b([A-Z\d_]{3,40})\b/ # Link constants
                    if (defined($const_definitions{$1})) {
                        $seen_consts{$1}=1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<a class=\"constant\" onClick=\"logConstant('$1')\" href=\"${relroot}_constants\/$1.$ext\" onMouseOver=\"constPopup(event,'$1')\">$1<\/a>";
                        $id;
                    } else {
                        $&;
                    }
                    /ge;
                $slice =~ s/^(\s*function\s+)(\w{3,45})/ # Link function definitions
                    $funcname=$2;
                    if (defined($func_ids{lc $funcname})) {
                        $seen_funcs{lc $funcname}=1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="$1<a class=\"function\" onClick=\"logFunction('$funcname')\" href=\"${relroot}_functions\/".$func_ids{lc $funcname}.".$ext\" onMouseOver=\"funcPopup(event,'".$func_ids{lc $funcname}."')\">$funcname<\/a>";
                        $id;
                    } else {
                        $&;
                    }
                    /ge;
                #$slice =~ s/^(\s*)(private|public|protected)(\sstatic|\s+static final|\s+final static)?(\s+function\s+)(\w{3,45})/ # Link function definitions
                #$slice =~ s/^(\s*)(\s*private|\s*public|\s*protected|\s*static|\s*final)+(\s+function\s+)(\w{3,45})/ # Link function definitions
                $slice =~ s/^(\s*)($func_prefix_re\s+)(function\s+)(\w{3,45})/ # Link function definitions
                    $prefix="";
                    $prefix = "$1$2$8";
                    $funcname=$9;
                    #print "funcname: $funcname\n";
                    if (defined($func_ids{lc $funcname})) {
                        $seen_funcs{lc $funcname}=1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="$prefix<a class=\"function\" onClick=\"logFunction('$funcname')\" href=\"${relroot}_functions\/".$func_ids{lc $funcname}.".$ext\" onMouseOver=\"funcPopup(event,'".$func_ids{lc $funcname}."')\">$funcname<\/a>";
                        $id;
                    } else {
                        $&;
                    }
                    /geo;
                $slice =~ s/\b(\w+)(\s{0,2}\()/ # Link functions
                    $funcname=$1;
                    if (defined($php_functions{lc $funcname}) && defined($func_ids{lc $funcname})) {
                        $seen_phpfuncs{lc $funcname}=1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<a class=\"phpfunction\" onClick=\"logFunction('$funcname')\" href=\"${relroot}_functions\/".$func_ids{lc $funcname}.".$ext\" onMouseOver=\"phpfuncPopup(event,'".$func_ids{lc $funcname}."')\">$funcname<\/a>$2";
                        $id;
                        
                    } elsif (defined($func_ids{lc $funcname})) {
                        $seen_funcs{lc $funcname}=1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<a class=\"function\" onClick=\"logFunction('$funcname')\" href=\"${relroot}_functions\/".$func_ids{lc $funcname}.".$ext\" onMouseOver=\"funcPopup(event,'".$func_ids{lc $funcname}."')\">$funcname<\/a>$2";
                        $id;
                    }else {
                        #print "failed to find function: '$funcname'\n" unless $funcname =~ m|SLICE|;
                        $&;
                    }
                    /ge;
                $slice =~ s/\b(class\s{1,5}|interface\s{1,5})(\w+)/ # Link class definitions
                    if (defined($class_ids{lc $2})) {
                        $seen_classes{lc $2}=1;
                        $id='~SLICE'.$slicenum++.'~';
                        $slicerepl{$id}="<span class=\"keyword\">$1<\/span><a class=\"class\" onClick=\"logClass('$2')\" href=\"${relroot}_classes\/".$class_ids{lc $2}.".$ext\" onMouseOver=\"classPopup(event,'".$class_ids{lc $2}."')\">$2<\/a>";
                        $id;
                    } else {
                        $&;
                    }
                    /ige;
                    
                if ($config{'fullxref'}) {
                    $slice =~ s%(["'])([\w/._-]+)(\1)%
                        if ($path=$pn=resolve_path($2)) {
                            ($quote,$orgpath)=($1,$2);
                            $path =~ s|^/+||;
                            $id='~SLICE'.$slicenum++.'~';
                            $slicerepl{$id}="$quote<a class=\"filename\" href=\"${relroot}$path.$ext\">".formatstr($orgpath, 0)."</a>$quote";
                            push(@{$file_references{$pn}},
                                {
                                'subdir'    => $subdir,
                                'filename'  => $filename,
                                'line'      => $line
                                }
                            );
                            $id;
                        } else {
                            $&;
                        }
                        %ieg;
                    }
                        
            }
            foreach $slice (@slices) {
                $slice=formatstr($slice, 0); # format *before* interpolation
                foreach $replid (keys %slicerepl) {
                    $replace=$slicerepl{$replid};
                    $slice =~ s/$replid/$replace/g;
                }
                $outline.=$slice;
            }
        }
        $xline=(' ' x (4-length($line))).$line;
        print $SYNTAX "<a name=\"l$line\"><span class=\"linenum\">$xline</span></a>  $outline\n";
        $line++;
    }
    print $SYNTAX "</pre>\n"; # 0.3.1
    print $SYNTAX "</div>\n";
    # generate javascript array of referenced functions
    print $SYNTAX "<script language=\"JavaScript\" type=\"text/javascript\">\nFUNC_DATA={\n";
    $x=0;
    foreach $funcname (keys %seen_funcs) {
        print $SYNTAX ",\n" if ($x++); # IE won't deal with the final entry ending with a comma
        $enc=uri_encode($funcname);
        if (defined($func_definitions{$funcname})) {
            ($desc,$deflist)=dump_popup_data(@{$func_definitions{$funcname}});
        } else {
            $desc='';
            $deflist='';
        }
        if (defined($func_references{$funcname})) {
            $refcount=scalar(@{$func_references{$funcname}});
        } else {
            $refcount=0;
        }
        print $SYNTAX "'$enc': ['$funcname', '$desc', [$deflist], $refcount]";
    }
    foreach $funcname (keys %seen_phpfuncs) {
        print $SYNTAX ",\n" if ($x++); # IE won't deal with the final entry ending with a comma
        $enc=uri_encode($funcname);
        if (defined($func_references{$funcname})) {
            $refcount=scalar(@{$func_references{$funcname}});
        } else {
            $refcount=0;
        }
        print $SYNTAX "'$enc': ['$funcname', '', [], $refcount]";
    }
    print $SYNTAX "};\n";
    print $SYNTAX "CLASS_DATA={\n";
    $x=0;
    foreach $classname (keys %seen_classes) {
        print $SYNTAX ",\n" if ($x++); # IE won't deal with the final entry ending with a comma
        $enc=uri_encode($classname);
        if (defined($class_definitions{$classname})) {
            ($desc,$deflist)=dump_popup_data(@{$class_definitions{$classname}});
        } else {
            $desc='';
            $deflist='';
        }
        if (defined($class_references{$classname})) {
            $refcount=scalar(@{$class_references{$classname}});
        } else {
            $refcount=0;
        }
        print $SYNTAX "'$enc': ['$classname', '$desc', [$deflist], $refcount]";
    }
    print $SYNTAX "};\n";
    print $SYNTAX "CONST_DATA={\n";
    $x=0;
    foreach $constname (keys %seen_consts) {
        print $SYNTAX ",\n" if ($x++); # IE won't deal with the final entry ending with a comma
        if (defined($const_definitions{$constname})) {
            ($desc,$deflist)=dump_popup_data(@{$const_definitions{$constname}});
        } else {
            $desc='';
            $deflist='';
        }
        if (defined($const_references{$constname})) {
            $refcount=scalar(@{$const_references{$constname}});
        } else {
            $refcount=0;
        }
        print $SYNTAX "'$constname': ['$constname', '$desc', [$deflist], $refcount]";
    }
    print $SYNTAX "};\n";

    print $SYNTAX "</script>\n";
    print $SYNTAX "<div id=\"func-popup\" class=\"funcpopup\"><p id=\"func-title\" class=\"popup-title\">title</p><p id=\"func-desc\" class=\"popup-desc\">Description</p><p id=\"func-body\" class=\"popup-body\">Body</p></div>\n";
    print $SYNTAX "<div id=\"class-popup\" class=\"funcpopup\"><p id=\"class-title\" class=\"popup-title\">title</p><p id=\"class-desc\" class=\"popup-desc\">Description</p><p id=\"class-body\" class=\"popup-body\">Body</p></div>\n";
    print $SYNTAX "<div id=\"const-popup\" class=\"funcpopup\"><p id=\"const-title\" class=\"popup-title\">title</p><p id=\"const-desc\" class=\"popup-desc\">Description</p><p id=\"const-body\" class=\"popup-body\">Body</p></div>\n";
    print $SYNTAX "<div id=\"req-popup\" class=\"funcpopup\"><p id=\"req-title\" class=\"popup-title\">title</p><p id=\"req-body\" class=\"popup-body\">Body</p></div>\n";
    print $SYNTAX $pagefooter;
    $SYNTAX->close();
    close(SOURCE);
}

sub dump_popup_data {
    my @definition=@_;

    my $desc=$definition[0]->{'briefcomment'};
    if (!defined($desc)) { $desc = ''; }
    $desc =~ s/'/\\'/g;
    $desc =~ s/[\r\n]/ /g;
    my @x=();
    foreach my $def (@definition) {
        $subdir=$def->{'subdir'};
        $subdir =~ s/^\///;
        push(@x,"['$subdir','".$def->{filename}."',".$def->{line}."]");
    }
    my $deflist=join(',',@x);
    return ($desc, $deflist);
}

sub generate_references {
    # Generate variable references
    print "Variable references: ";
    mkpath("$output_dir/_variables") unless (-d "$output_dir/_variables");
    my $v=0;
    $vardefs=$varrefs=0;
    $funccount=$funcdefs=$funcrefs=$constdefs=$constrefs=0;
    $classcount=$classdefs=$classrefs=0;
    $tablecount=$tablerefs=0;
    $varcount=scalar(keys %var_ids);
    foreach $varname (sort(keys %var_ids)) {
        $v++;
        show_progress($v, $varcount);
        if (defined($var_definitions{$varname})) {
            push(@varlist_defs,$varname);
        } else {
            push(@varlist_other,$varname);
        }
        $VARINDEX=pxopen("$output_dir/_variables/".$var_ids{$varname}.".$ext",'w');
        my $varnameid=$var_ids{$varname};
        my $newheader=$pageheader;
        $newheader =~ s/__TITLE__/$title Variable Reference: \$$varname/g;
        $newheader =~ s/__CHARSET__/$charset/g;
        $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
        $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
        $newheader =~ s/__RELROOT__/..\//g;
        $newheader.=javascript_header(1, '_variables', $var_ids{$varname}.".$ext", "logVariable('$varname');");
        print $VARINDEX $newheader;
        print $VARINDEX &navtoggle_html('../');
        print $VARINDEX "[<a href=\"../index.$ext\">Top level directory</a>]<br>\n";
        print $VARINDEX &javascript_search(1);
        print $VARINDEX "<h3>Variable Cross Reference</h3>\n";
        print $VARINDEX "<h2><a href=\"index.$ext#$varnameid\">\$$varname</a></h2>\n\n";
        if (defined($var_definitions{$varname})) {
            print $VARINDEX "<b>Defined at:</b><ul>\n";
            foreach $def (@{$var_definitions{$varname}}) {
                $vardefs++;
                print $VARINDEX "<li>";
                print $VARINDEX "<a href=\"..".$def->{'subdir'}.$def->{'filename'}.".$ext\">";
                print $VARINDEX $def->{'subdir'}.$def->{'filename'}."</A> ";
                print $VARINDEX " -> ";
                print $VARINDEX "<a href=\"..".$def->{'subdir'}.$def->{'filename'}.".source.$ext#l".$def->{'line'}."\">";
                print $VARINDEX " line ".$def->{'line'}."</A>";
                print $VARINDEX "</li>\n";
            }
            print $VARINDEX "</ul>\n";
        }
        if (defined($var_references{$varname})) {
            print $VARINDEX "<br><b>Referenced ".scalar(@{$var_references{$varname}})." times:</b><ul>\n";
        } else {
            print $VARINDEX "<br><b>No references found.</b><br><br>\n";
        }
        foreach $ref (@{$var_references{$varname}}) {
            $varrefs++;
            print $VARINDEX "<li>";
            print $VARINDEX "<a href=\"..".$ref->{'subdir'}.$ref->{'filename'}.".$ext\">";
            print $VARINDEX $ref->{'subdir'}.$ref->{'filename'}."</a> ";
            print $VARINDEX " -> ";
            print $VARINDEX "<a href=\"..".$ref->{'subdir'}.$ref->{'filename'}.".source.$ext#l".$ref->{'line'}."\">";
            print $VARINDEX " line ".$ref->{'line'}."</a>";
            print $VARINDEX "</li>\n";
        }
        print $VARINDEX "</ul>\n";
        print $VARINDEX $pagefooter;
        $VARINDEX->close();
    }

    # Generate variable index
    $INDEX=pxopen("$output_dir/_variables/index.$ext",'w');
    my $newheader=$pageheader;
    $newheader =~ s/__TITLE__/$title Full Variable Index/g;
    $newheader =~ s/__CHARSET__/$charset/g;
    $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
    $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
    $newheader =~ s/__RELROOT__/..\//g;
    $newheader.=javascript_header(1, '_variables', "index.$ext");
    print $INDEX $newheader;
    print $INDEX &navtoggle_html('../');
    print $INDEX "[<a href=\"../index.$ext\">Top level directory</a>] ";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_classes/index.$ext\">Classes</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_functions/index.$ext\">Functions</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_constants/index.$ext\">Constants</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_tables/index.$ext\">Tables</a>]";
    print $INDEX "<br>\n";
    print $INDEX &javascript_search(1);
    print $INDEX "<h2>Variable List (alphabetical)</h2>\n";
    print $INDEX "Total unique variables names: ".scalar(keys %var_ids)."<ul>\n";
    if (defined(@varlist_defs)) {
        print $INDEX "<li><a href=\"#def\">Variables defined on the site</a>: ".scalar(@varlist_defs)."</li>\n";
    }
    if (defined(@varlist_other)) {
        print $INDEX "<li><a href=\"#other\">Other Variables</a>: ".scalar(@varlist_other)."</li>\n";
    }
    print $INDEX "</ul>\n";
    if (scalar(@varlist_defs)) {
        print $INDEX "<a name=\"def\">";
        print $INDEX "<b>Variables defined on the site</b>";
        print $INDEX "</a><ul>\n";
        foreach $varname (@varlist_defs) {
            $varnameid=$var_ids{$varname};
            print $INDEX "<li>";
            print $INDEX "<a name=\"$varnameid\" href=\"$varnameid.$ext\">\$$varname</A>";
            print $INDEX "&nbsp;&nbsp;Definitions: ".scalar(@{$var_definitions{$varname}});
            print $INDEX "&nbsp; References: ".scalar(@{$var_references{$varname}});
            print $INDEX "</li>\n";
        }
        print $INDEX "</ul><br>\n";
    }
    if (scalar(@varlist_other)) {
        print $INDEX "<a name=\"other\">";
        print $INDEX "<b>Other variables</b>";
        print $INDEX "</a><ul>\n";
        foreach $varname (@varlist_other) {
            $varnameid=$var_ids{$varname};
            print $INDEX "<li>";
            print $INDEX "<a name=\"$varnameid\" href=\"$varnameid.$ext\">\$$varname</a>";
            print $INDEX "&nbsp;&nbsp;References: ".scalar(@{$var_references{$varname}});
            print $INDEX "</li>\n";
        }
        print $INDEX "</ul><br>\n";
    }
    print $INDEX $pagefooter;
    $INDEX->close();

    # Generate function references
    print "\nFunction references: ";
    mkpath("$output_dir/_functions") unless (-d "$output_dir/_functions");
    my $f=0;
    $funccount=scalar(keys %func_ids);
    foreach $funcname (sort(keys %func_ids)) {
        $f++;
        show_progress($f,$funccount);
        $FUNCINDEX=pxopen("$output_dir/_functions/".$func_ids{$funcname}.".$ext",'w');
        my $funcnameid=$func_ids{$funcname};
        if ($php_functions{$funcname}) {
            push(@funclist_php,$funcname);
        } elsif (defined($func_definitions{$funcname}) && scalar(@{$func_definitions{$funcname}})) {
            push(@funclist_defs,$funcname);
        } else {
            push(@funclist_other,$funcname);
        }
        my $newheader=$pageheader;
        $newheader =~ s/__TITLE__/$title Function Reference: $funcname\(\)/g;
        $newheader =~ s/__CHARSET__/$charset/g;
        $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
        $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
        $newheader =~ s/__RELROOT__/..\//g;
        $newheader.=javascript_header(1, '_functions', $func_ids{$funcname}.".$ext", "logFunction('$funcname');");
        print $FUNCINDEX $newheader;
        print $FUNCINDEX &navtoggle_html('../');
        print $FUNCINDEX "[<a href=\"../index.$ext\">Top level directory</a>]<br>\n";
        print $FUNCINDEX &javascript_search(1);
        print $FUNCINDEX "<h3>Function and Method Cross Reference</h3>\n";
        print $FUNCINDEX "<h2><a href=\"index.$ext#$funcnameid\">$funcname()</a></h2>\n\n";
        if (defined($php_functions{$funcname})) {
            print $FUNCINDEX "<p><b>This function is provided by PHP directly.</b><br>\n";
            print $FUNCINDEX "You can lookup the documentation for it here: <br>&nbsp;&nbsp;";
            print $FUNCINDEX &make_php_doclinks($funcname);
            print $FUNCINDEX "</p>\n";
        }
        if (defined($func_definitions{$funcname})) {
            print $FUNCINDEX "<b>Defined at:</b><ul>\n";
            foreach $def (@{$func_definitions{$funcname}}) {
                $funcdefs++;
                $sourceloc=$def->{'subdir'}."/".$def->{'filename'}.".source.$ext#l".$def->{'line'};
                print $FUNCINDEX "<li>";
                print $FUNCINDEX "<a href=\"..".$def->{'subdir'}."/".$def->{'filename'}.".$ext#$funcnameid\">";
                print $FUNCINDEX $def->{'subdir'}."/".$def->{'filename'}."</a> ";
                print $FUNCINDEX " -> ";
                print $FUNCINDEX "<a onClick=\"logFunction('$funcname', '$sourceloc')\" href=\"..$sourceloc\">";
                #print $FUNCINDEX "<a href=\"..".$def->{'subdir'}."/".$def->{'filename'}.".source.$ext#l".$def->{'line'}."\">";
                print $FUNCINDEX " line ".$def->{'line'}."</a>";
                print $FUNCINDEX "</li>\n";
            }
            print $FUNCINDEX "</ul>\n";
        }
        if (defined($func_references{$funcname})) {
            print $FUNCINDEX "<b>Referenced ".scalar(@{$func_references{$funcname}})." times:</b><ul>\n";
        } else {
            print $FUNCINDEX "<b>No references found.</b><br><br>\n";
        }
        foreach $def (@{$func_references{$funcname}}) {
            $funcrefs++;
            print $FUNCINDEX "<li>";
            print $FUNCINDEX "<a href=\"..".$def->{'subdir'}.$def->{'filename'}.".$ext\">";
            print $FUNCINDEX $def->{'subdir'}.$def->{'filename'}."</a> ";
            print $FUNCINDEX " -> ";
            print $FUNCINDEX "<a href=\"..".$def->{'subdir'}.$def->{'filename'}.".source.$ext#l".$def->{'line'}."\">";
            print $FUNCINDEX " line ".$def->{'line'}."</a>";
            print $FUNCINDEX "</li>\n";
        }
        print $FUNCINDEX "</ul>\n";
        print $FUNCINDEX $pagefooter;
        $FUNCINDEX->close();
    }
    # Generate function index
    $INDEX=pxopen("$output_dir/_functions/index.$ext",'w');
    $newheader=$pageheader;
    $newheader =~ s/__TITLE__/$title Full Function Index/g;
    $newheader =~ s/__CHARSET__/$charset/g;
    $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
    $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
    $newheader =~ s/__RELROOT__/..\//g;
    $newheader.=javascript_header(1, '_functions', "index.$ext");
    print $INDEX $newheader;
    print $INDEX &navtoggle_html('../');
    print $INDEX "[<a href=\"../index.$ext\">Top level directory</a>] ";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_classes/index.$ext\">Classes</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_variables/index.$ext\">Variables</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_constants/index.$ext\">Constants</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_tables/index.$ext\">Tables</a>]";
    print $INDEX "<br>\n";
    print $INDEX &javascript_search(1);
    print $INDEX "<h2>Function List (alphabetical)</h2>\n"; 
    print $INDEX "Total unique function names: ".scalar(keys %func_ids)."<ul>\n";
    if (defined(@funclist_defs)) {
        print $INDEX "<li><a href=\"#def\">Functions defined on the site</A>: ".scalar(@funclist_defs)."</li>\n";
    }
    if (defined(@funclist_php)) {
        print $INDEX "<li><a href=\"#php\">PHP functions used on the site</a>: ".scalar(@funclist_php)."</li>\n";
    }
    if (defined(@funclist_other)) {
        print $INDEX "<li><a href=\"#other\">Undefined functions</a>: ".scalar(@funclist_other)."</li>\n";
    }
    print $INDEX "</ul>\n";
    if (scalar(@funclist_defs)) {
        print $INDEX "<a name=\"def\">";
        print $INDEX "<b>Functions defined on the site</b>";
        print $INDEX "</a><ul>\n";
        foreach $funcname (@funclist_defs) {
            $funcnameid=$func_ids{$funcname};
            print $INDEX "<li>";
            print $INDEX "<a name=\"$funcnameid\" HREF=\"$funcnameid.$ext\">$funcname()</a>";
            print $INDEX "&nbsp;&nbsp;Definitions: ".scalar(@{$func_definitions{$funcname}});
            print $INDEX "&nbsp; References: ".scalar(@{$func_references{$funcname}});
            print $INDEX "</li>\n";
        }
        print $INDEX "</ul><br>\n";
    }
    if (scalar(@funclist_php)) {
        print $INDEX "<a name=\"php\">";
        print $INDEX "<b>PHP functions used on the site</b>";
        print $INDEX "</a><ul>\n";
        foreach $funcname (@funclist_php) {
            $funcnameid=$func_ids{$funcname};
            print $INDEX "<li>";
            print $INDEX "<a name=\"$funcnameid\" href=\"$funcnameid.$ext\">$funcname()</a>";
            print $INDEX "&nbsp;&nbsp;References: ".scalar(@{$func_references{$funcname}});
            print $INDEX "</li>\n";
        }
        print $INDEX "</ul><br>\n";
    }
    # redundant as of 0.5.0
    if (scalar(@funclist_other)) {
        print $INDEX "<a name=\"other\">";
        print $INDEX "<b>Undefined functions</b>";
        print $INDEX "</a><br>\n";
        print $INDEX "<i>Most will be bogus functions, triggered by SQL queries etc</i><ul>\n";
        foreach $funcname (@funclist_other) {
            $funcnameid=$func_ids{$funcname};
            print $INDEX "<li>";
            print $INDEX "<a name=\"$funcnameid\" href=\"$funcnameid.$ext\">$funcname()</a>";
            print $INDEX "&nbsp;&nbsp;References: ".scalar(@{$func_references{$funcname}});
            print $INDEX "</li>\n";
        }
        print $INDEX "</ul>\n";
    }
    print $INDEX $pagefooter;
    $INDEX->close();

    # Generate class references
    print "\nClass references...: ";
    mkpath("$output_dir/_classes") unless (-d "$output_dir/_classes");
    $f=0;
    $classcount=scalar(keys %class_ids);
    foreach $classname (sort(keys %class_ids)) {
        $f++;
        show_progress($f,$classcount);
        $CLASSINDEX=pxopen("$output_dir/_classes/".$class_ids{$classname}.".$ext",'w');
        my $classnameid=$class_ids{$classname};
        my $newheader=$pageheader;
        $newheader =~ s/__TITLE__/$title Class Reference: $classname/g;
        $newheader =~ s/__CHARSET__/$charset/g;
        $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
        $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
        $newheader =~ s/__RELROOT__/..\//g;
        $newheader.=javascript_header(1, '_classes', $class_ids{$classname}.".$ext", "logClass('$classname');");
        print $CLASSINDEX $newheader;
        print $CLASSINDEX &navtoggle_html('../');
        print $CLASSINDEX "[<a href=\"../index.$ext\">Top level directory</a>]<br>\n";
        print $CLASSINDEX &javascript_search(1);
        print $CLASSINDEX "<h3>Class Cross Reference</h3>\n";
        print $CLASSINDEX "<h2><a href=\"index.$ext#$classnameid\">$classname</a></h2>\n\n";
        if (defined($class_definitions{$classname})) {
            print $CLASSINDEX "<b>Defined at:</b><ul>\n";
            foreach $def (@{$class_definitions{$classname}}) {
                $classdefs++;
                $sourceloc=$def->{'subdir'}."/".$def->{'filename'}.".source.$ext#l".$def->{'line'};
                print $CLASSINDEX "<li>";
                print $CLASSINDEX "<a href=\"..".$def->{'subdir'}."/".$def->{'filename'}.".$ext#$classnameid\">";
                print $CLASSINDEX $def->{'subdir'}."/".$def->{'filename'}."</a> ";
                print $CLASSINDEX " -> ";
                print $CLASSINDEX "<a onClick=\"logClass('$classname', '$sourceloc')\" href=\"..$sourceloc\">";
                #print $CLASSINDEX "<a href=\"..".$def->{'subdir'}."/".$def->{'filename'}.".source.$ext#l".$def->{'line'}."\">";
                print $CLASSINDEX " line ".$def->{'line'}."</a>";
                print $CLASSINDEX "</li>\n";
            }
            print $CLASSINDEX "</ul>\n";
        }
        if (defined($class_references{$classname})) {
            print $CLASSINDEX "<br><b>Referenced ".scalar(@{$class_references{$classname}})." times:</b><ul>\n";
        } else {
            print $CLASSINDEX "<br><b>No references found.</b><br><br>\n";
        }
        foreach $ref (@{$class_references{$classname}}) {
            $classrefs++;
            print $CLASSINDEX "<li>";
            print $CLASSINDEX "<a href=\"..".$ref->{'subdir'}.$ref->{'filename'}.".$ext\">";
            print $CLASSINDEX $ref->{'subdir'}.$ref->{'filename'}."</a> ";
            print $CLASSINDEX " -> ";
            print $CLASSINDEX "<a href=\"..".$ref->{'subdir'}.$ref->{'filename'}.".source.$ext#l".$ref->{'line'}."\">";
            print $CLASSINDEX " line ".$ref->{'line'}."</A>";
            print $CLASSINDEX "</li>\n";
        }
        print $CLASSINDEX "</ul>\n";
        print $CLASSINDEX $pagefooter;
        $CLASSINDEX->close();
    }
    # Generate class index
    $INDEX=pxopen("$output_dir/_classes/index.$ext",'w');
    $newheader=$pageheader;
    $newheader =~ s/__TITLE__/$title Full Class Index/g;
    $newheader =~ s/__CHARSET__/$charset/g;
    $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
    $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
    $newheader =~ s/__RELROOT__/..\//g;
    $newheader.=javascript_header(1, '_classes', "index.$ext");
    print $INDEX $newheader;
    print $INDEX &navtoggle_html('../');
    print $INDEX "[<a href=\"../index.$ext\">Top level directory</a>] ";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_functions/index.$ext\">Functions</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_variables/index.$ext\">Variables</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_constants/index.$ext\">Constants</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_tables/index.$ext\">Tables</a>]";
    print $INDEX "<br>\n";
    print $INDEX &javascript_search(1);
    print $INDEX "<h2>Class List (alphabetical)</h2>\n";
    print $INDEX "Total unique names: ".scalar(keys %class_ids)."<div class=\"inset\">\n";
    if (scalar(keys %class_ids)) {
        print $INDEX "<a name=\"def\">";
        print $INDEX "<b>Classes defined on the site</b></a>\n";
        print $INDEX "<ul>\n";
        foreach $classname (sort keys %class_ids) {
            $classnameid=$class_ids{$classname};
            print $INDEX "<li>";
            print $INDEX "<a name=\"$classnameid\" href=\"$classnameid.$ext\">$classname</a>";
            print $INDEX "&nbsp;&nbsp;Definitions: ".scalar(@{$class_definitions{$classname}});
            print $INDEX "&nbsp; References: ".scalar(@{$class_references{$classname}}); 
            print $INDEX "</li>\n";
        }
        print $INDEX "</ul><br>\n";
    }
    print $INDEX "</div><br>\n";
    print $INDEX $pagefooter;
    $INDEX->close();


    # Generate constant references
    print "\nConstant references: ";
    mkpath("$output_dir/_constants") unless (-d "$output_dir/_constants");
    $f=0;
    $constcount=scalar(keys %const_definitions);
    foreach $constname (sort(keys %const_definitions)) {
        $f++;
        show_progress($f,$constcount);
        $CONSTINDEX=pxopen("$output_dir/_constants/$constname.$ext",'w');
        my $newheader=$pageheader;
        $newheader =~ s/__TITLE__/$title Constant Reference: $constname/g;
        $newheader =~ s/__CHARSET__/$charset/g;
        $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
        $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
        $newheader =~ s/__RELROOT__/..\//g;
        $newheader.=javascript_header(1, '_constants', $constname.".$ext", "logConstant('$constname');");
        print $CONSTINDEX $newheader;
        print $CONSTINDEX &navtoggle_html('../');
        print $CONSTINDEX "[<a href=\"../index.$ext\">Top level directory</a>]<br>\n";
        print $CONSTINDEX &javascript_search(1);
        print $CONSTINDEX "<h3>Constant Cross Reference</h3>\n";
        print $CONSTINDEX "<h2><a href=\"index.$ext#$constname\">$constname</a></h2>\n\n";
        if (defined($const_definitions{$constname})) {
            print $CONSTINDEX "<b>Defined at:</b><ul>\n";
            foreach $def (@{$const_definitions{$constname}}) {
                $constdefs++;
                $sourceloc=$def->{'subdir'}."/".$def->{'filename'}.".source.$ext#l".$def->{'line'};
                print $CONSTINDEX "<li>";
                print $CONSTINDEX "<a href=\"..".$def->{'subdir'}."/".$def->{'filename'}.".$ext#$constname\">";
                print $CONSTINDEX $def->{'subdir'}."/".$def->{'filename'}."</a> ";
                print $CONSTINDEX " -> ";
                print $CONSTINDEX "<a onClick=\"logConstant('$constname', '$sourceloc')\" href=\"..$sourceloc\">";
                print $CONSTINDEX " line ".$def->{'line'}."</a>";
                print $CONSTINDEX "</li>\n";
            }
            print $CONSTINDEX "</ul>\n";
        }
        if (defined($const_references{$constname})) {
            print $CONSTINDEX "<br><b>Referenced ".scalar(@{$const_references{$constname}})." times:</b><ul>\n";
        } else {
            print $CONSTINDEX "<br><b>No references found.</b><br><br>\n";
        }
        foreach $ref (@{$const_references{$constname}}) {
            $constrefs++;
            print $CONSTINDEX "<li>";
            print $CONSTINDEX "<a href=\"..".$ref->{'subdir'}.$ref->{'filename'}.".$ext\">";
            print $CONSTINDEX $ref->{'subdir'}.$ref->{'filename'}."</a> ";
            print $CONSTINDEX " -> ";
            print $CONSTINDEX "<a href=\"..".$ref->{'subdir'}.$ref->{'filename'}.".source.$ext#l".$ref->{'line'}."\">";
            print $CONSTINDEX " line ".$ref->{'line'}."</A>";
            print $CONSTINDEX "</li>\n";
        }
        print $CONSTINDEX "</ul>\n";
        print $CONSTINDEX $pagefooter;
        $CONSTINDEX->close();
    }
    # Generate const index
    $INDEX=pxopen("$output_dir/_constants/index.$ext",'w');
    $newheader=$pageheader;
    $newheader =~ s/__TITLE__/$title Full Constant Index/g;
    $newheader =~ s/__CHARSET__/$charset/g;
    $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
    $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
    $newheader =~ s/__RELROOT__/..\//g;
    $newheader.=javascript_header(1, '_constants', "index.$ext");
    print $INDEX $newheader;
    print $INDEX &navtoggle_html('../');
    print $INDEX "[<a href=\"../index.$ext\">Top level directory</a>] ";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_functions/index.$ext\">Functions</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_classes/index.$ext\">Classes</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_variables/index.$ext\">Variables</a>]";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_tables/index.$ext\">Tables</a>]";
    print $INDEX "<br>\n";
    print $INDEX &javascript_search(1);
    print $INDEX "<h2>Constant List (alphabetical)</h2>\n";
    print $INDEX "Total unique names: ".scalar(keys %const_definitions)."<div class=\"inset\">\n";
    if (scalar(keys %const_definitions)) {
        print $INDEX "<a name=\"def\">";
        print $INDEX "<b>Constants defined on the site</b></a>\n";
        print $INDEX "<ul>\n";
        foreach $constname (sort keys %const_definitions) {
            print $INDEX "<li>";
            print $INDEX "<a name=\"$constname\" href=\"$constname.$ext\">$constname</a>";
            print $INDEX "&nbsp;&nbsp;Definitions: ".scalar(@{$const_definitions{$constname}});
            print $INDEX "&nbsp; References: ".scalar(@{$const_references{$constname}}); 
            print $INDEX "</li>\n";
        }
        print $INDEX "</ul><br>\n";
    }
    print $INDEX "</div><br>\n";
    print $INDEX $pagefooter;
    $INDEX->close();

    # Generate table references
    print "\nTable references...: ";
    mkpath("$output_dir/_tables") unless (-d "$output_dir/_tables");
    $f=0;
    $tablecount=scalar(keys %table_ids);
    foreach $tablename (sort(keys %table_ids)) {
        $f++;
        show_progress($f,$tablecount);
        my  $tablenameid=$table_ids{$tablename};
        $TABLEINDEX=pxopen("$output_dir/_tables/$tablenameid.$ext",'w');
        $newheader=$pageheader;
        $newheader =~ s/__TITLE__/$title Table Reference: $tablename/g;
        $newheader =~ s/__CHARSET__/$charset/g;
        $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
        $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
        $newheader =~ s/__RELROOT__/..\//g;
        $newheader.=javascript_header(1, '_tables', "$tablenameid.$ext");
        print $TABLEINDEX $newheader;
        print $TABLEINDEX &navtoggle_html('../');
        print $TABLEINDEX &javascript_search(1);
        print $TABLEINDEX "[<a href=\"../index.$ext\">Top level directory</a>]<br>\n";
        print $TABLEINDEX "<h3>SQL Table Cross Reference</h3>\n";
        print $TABLEINDEX "<h2><a href=\"index.$ext#$tablenameid\">$tablename</a></h2>\n\n";

        print $TABLEINDEX "<b>Referenced ".scalar(@{$table_references{$tablename}})." times:</b><ul>\n";
        foreach $def (@{$table_references{$tablename}}) {
            $tablerefs++;
            $sourceloc=$def->{'subdir'}.$def->{'filename'}.".source.$ext#l".$def->{'line'};
            print $TABLEINDEX "<li>";
            print $TABLEINDEX "<a href=\"..".$def->{'subdir'}.$def->{'filename'}.".$ext\">";
            print $TABLEINDEX $def->{'subdir'}.$def->{'filename'}."</a> ";
            print $TABLEINDEX " -> ";
            print $TABLEINDEX "<a onClick=\"logTable('$tablename', '$sourceloc')\" href=\"..$sourceloc\">";
            #print $TABLEINDEX "<a href=\"..".$def->{'subdir'}.$def->{'filename'}.".source.$ext#l".$def->{'line'}."\">";
            print $TABLEINDEX " line ".$def->{'line'}."</a>";
            print $TABLEINDEX "</li>\n";
        }
        print $TABLEINDEX "</ul>\n";
        print $TABLEINDEX "<h3>Table Description</h3>\n";
        if ($dbh != 0) {
            my $sth = $dbh->prepare("describe $tablename");
            $sth->execute;
            my $numRows = $sth->rows;
            if ($numRows > 0) {
                print $TABLEINDEX "<table border=1>";
                while (my @row = $sth->fetchrow_array()) {
                    print $TABLEINDEX "<tr>";
                    foreach my $col (@row) {
                            $col = "&nbsp;" unless defined $col;
                            $col = "&nbsp;" if ($col eq "");
                            print $TABLEINDEX "<td>$col</td>";
                    }
                    print $TABLEINDEX "</tr>\n";
                }
                print $TABLEINDEX "</table>";
            }
            $sth->finish;
        } else {
            print $TABLEINDEX "Table description not available!<br><br>";
        }
        print $TABLEINDEX $pagefooter;
        $TABLEINDEX->close();
    }
    # Generate table index
    $INDEX=pxopen("$output_dir/_tables/index.$ext",'w');
    $newheader=$pageheader;
    $newheader =~ s/__TITLE__/$title Full Table Index/g;
    $newheader =~ s/__CHARSET__/$charset/g;
    $newheader =~ s/__STYLEFILE__/..\/$config{stylefile}/g if $config{'stylefile'};
    $newheader =~ s/__PRINTSTYLEFILE__/..\/$config{printstylefile}/g if $config{'printstylefile'};
    $newheader =~ s/__RELROOT__/..\//g;
    $newheader.=javascript_header(1, '_tables', "index.$ext");
    print $INDEX $newheader;
    print $INDEX &navtoggle_html('../');
    print $INDEX "[<a href=\"../index.$ext\">Top level directory</a>] ";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_classes/index.$ext\">Classes</a>] ";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_functions/index.$ext\">Functions</a>] ";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_variables/index.$ext\">Variables</a>]<br>";
    print $INDEX " &nbsp;&nbsp;&nbsp; ";
    print $INDEX "[<a href=\"../_constants/index.$ext\">Constants</a>]";
    print $INDEX &javascript_search(1);
    print $INDEX "<h2>Table List (alphabetical)</h2>\n"; 
    print $INDEX "Total unique names: ".scalar(keys %table_ids)."<br>\n";
    print $INDEX "<br><b>Tables referenced on the site:</b><br>";
    print $INDEX "<ul>\n";
    foreach $tablename (sort keys %table_references) {
        $tablenameid=$table_ids{$tablename};
        print $INDEX "<li>";
        print $INDEX "<a name=\"$tablenameid\" href=\"$tablenameid.$ext\">$tablename</A>";
        print $INDEX "&nbsp;&nbsp;References: ".scalar(@{$table_references{$tablename}});
        print $INDEX "</li>\n";
    }
    print $INDEX "</ul><br>\n";

    print $INDEX $pagefooter;
    $INDEX->close();
}


# Format a string for html
sub formatstr {
    ($_, $encws)=@_;
    my $copy=$_;

    #s|^\s+||gs;
    #s|\s+$||gs; # strip leading/trailing spaces and newlines
    s|&|&amp;|g;
    s|<|&lt;|g;
    s|>|&gt;|g;
    s|\"|&quot;|g; #"
    s|\n|<BR>\n|g;
    if ($encws) {
        s|(\t+)|"&nbsp;" x (length($&)*4)|eg; #0.3.1
        s|[ ]{2,}|"&nbsp;" x length($&)|eg; # 0.3.1
    } else {
        s|\t|    |g;
    }

    my $result=$_;
    $_=$copy;
    return($result);
}

sub getdepth {
    my $dir=shift;
    $dir =~ s|/+|/|g;
    $dir =~ s|/$||;
    $dir =~ s|^/||;
    return(0) unless length($dir);
    $dir =~ s|[^/]+||g;
    return(length($dir)+1);
}

sub uri_encode {
    my $str=shift;
    $str =~ s/^(con|prn|aux|clock\$|nul|lpt\d|com\d)(\.|\z)/-$1$2/i; # you have no idea how much i hate windows.
    $str =~ s/([\x00-\x20"#%;<>?{}|\\\\^~`\[\]\x7F-\xFF])/sprintf '%%%.2X' => ord $1/eg;
    return $str;
}

sub usage_die {
    my $error=shift;

    print "Usage: phpxref [-c <config filename>]\n\n";
    print "Error: $error\n\n" unless (!defined($error));

    exit 1;
}

sub copyfiles {
    my ($source, $target) = @_;

    for my $fn (glob $source) {
        copy($fn, $target) || die "Failed to copy $fn to $target: $!";
    }
}

sub read_functions {
    my $filename=shift;

    open(FUNCTIONS,$filename) || die "Error opening $filename: $!";
    while(<FUNCTIONS>) {
        chomp;
        $php_functions{$_}=1;
    }
    close(FUNCTIONS);
}

sub make_php_doclinks {
    my $func=uri_encode(shift);

    my $link="";
    foreach $host (@phpsites) {
        $link.="[<A HREF=\"http://$host/manual-lookup.php?function=$func\">$host</A>] ";
    }
    return($link);
}

sub parse_config {
    my $filename=shift;
    my ($var,$val);

    open(CONFIG,$filename) || usage_die("Error opening $filename: $!");
    while(<CONFIG>) {
        next if /^\s*#/;
        next unless /(\w+)\s*=\s*(.+)/;
        ($var,$val)=(lc $1,$2);
        $fileval=($is_case_tolerant && (lc $val)) || $val; # convert to lowercase for Windows
        if ($var eq 'bad_dirname') {
            $bad_dirnames{$fileval}=1;
        } elsif ($var eq 'bad_pathname') {
            $val =~ s|/+$||;
            $bad_pathnames{$fileval}=1;
        } elsif ($var eq 'bad_ext') {
            $bad_extensions{$fileval}=1;
        } elsif ($var eq 'good_ext') {
            $good_extensions{$fileval}=1;
        } elsif ($var eq 'bad_filename') {
            $bad_filenames{$fileval}=1;
        } elsif ($var eq 'doc_tag') {
            $doc_tags{$val}=1;
        } elsif ($var eq 'doc_file_tag') {
            $doc_file_tags{$val}=1;
        } elsif ($var eq 'includepath') {
            foreach $ip (split(/:/,$val)) {
                $ip =~ s|/+$||; # strip trailing slashes
                push(@includepath,$ip);
            }
        } else {
            $config{$var}=$val;
        }
    }
    $config{'no_trad_doc'}=0 unless (defined($config{'no_trad_doc'}));

    close(CONFIG);
}

sub gen_stats {
    $result="Files Scanned: ".scalar(keys %allfiles)." containing $globallinecount lines\n";
    $result.="$varcount variable names in $vardefs definitions and $varrefs references.\n";
    $result.="$funccount function/method names in $funcdefs definitions and $funcrefs references.\n";
    $result.="$constcount constant names in $constdefs definitions and $constrefs references.\n";
    $result.="$classcount class names in $classdefs definitions and $classrefs references.\n";
    $result.="$tablecount table names in $tablerefs references.\n";
}

sub gen_stats_page {
    my $STATSFILE = pxopen("$output_dir/_stats.$ext", "w");
    my $newheader=$pageheader;
    $runtime = time() - $starttime;
    $newheader =~ s/__TITLE__/$title Project Statistics/g;
    $newheader =~ s/__CHARSET__/$charset/g;
    $newheader =~ s/__STYLEFILE__/$config{stylefile}/g if $config{'stylefile'};
    $newheader =~ s/__PRINTSTYLEFILE__/$config{printstylefile}/g if $config{'printstylefile'};
    $newheader =~ s/__RELROOT__/.\//g;
    $newheader.=javascript_header(1, $subdir, "$filename.$ext");
    my ($toplinks,$funcbody)=("","");
    print $STATSFILE $newheader;
    print $STATSFILE &navtoggle_html($relroot);
    print $STATSFILE "<h2 class=\"details-heading\">Project Statistics</h2>\n";
    print $STATSFILE "<div class=\"details-summary\">\n";
    print $STATSFILE "<p><br>Statistics generated by PHPXref:<br><br> </p>\n";
    print $STATSFILE "<table>\n";
    print $STATSFILE "<tr><td align=\"right\">Generated: </td><td>$rundate in $runtime seconds</td></tr>\n";
    print $STATSFILE "<tr><td align=\"right\">Files scanned: </td><td>".scalar(keys %allfiles)."</td></tr>\n";
    print $STATSFILE "<tr><td align=\"right\">Lines scanned: </td><td>$globallinecount</td></tr>\n";
    print $STATSFILE "<tr><td align=\"right\">Classes: </td><td>$classcount class names in $classdefs definitions and $classrefs references</td></tr>\n";
    print $STATSFILE "<tr><td align=\"right\">Functions/Methods: </td><td>$funccount function/method names in $funcdefs definitions and $funcrefs references</td></tr>\n";
    print $STATSFILE "<tr><td align=\"right\">Variables: </td><td>$varcount variable names in $vardefs definitions and $varrefs references</td></tr>\n";
    print $STATSFILE "<tr><td align=\"right\">Constants: </td><td>$constcount constant names in $constdefs definitions and $constrefs references</td></tr>\n";
    print $STATSFILE "<tr><td align=\"right\">Tables: </td><td>$tablecount table names in $tablerefs references</td></tr>\n";
    print $STATSFILE "</table>\n";
    print $STATSFILE $pagefooter;
    $STATSFILE->close();
}

# 0%..10%..20%..30%..40%..50%..60%..70%..80%..90%..100%
sub show_progress {
    my ($current, $total) = @_;
    return if ($current>$total);
    if ($total==1) {
        print "100%";
        return;
    }
    if ($current==1) {
        print "0%..";
        return;
    }
    if ($current==$total) {
        print "100%";
        return;
    }
    my $percent=($current/$total*100);
    my $percent_int=int $percent;
    return unless ($percent_int % 10 == 0);
    if ((($current-1)/$total*100)<$percent_int) {
        print "$percent_int\%..";
    }
}

sub javascript_search {
    my $depth=shift;
    my $relbase="../" x $depth;
    my $j=<<__EOS__;
<script language="JavaScript" type="text/javascript">
<!--

document.writeln('<table align="right" class="searchbox-link"><tr><td><a class="searchbox-link" href="javascript:void(0)" onMouseOver="showSearchBox()">Search</a><br>');
document.writeln('<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" class=\"searchbox\" id=\"searchbox\">');
document.writeln('<tr><td class=\"searchbox-title\">');
document.writeln('<a class="searchbox-title" href="javascript:showSearchPopup()">Search History +</a>');
document.writeln('<\\/td><\\/tr>');

document.writeln('<tr><td class=\"searchbox-body\" id=\"searchbox-body\">');
document.writeln('<form name="search" style="margin:0px; padding:0px" onSubmit=\\'return jump()\\'>');
document.writeln('<a class="searchbox-body" href="${relbase}_classes/index.$ext">Class<\\/a>: ');
document.writeln('<input type="text" size=10 value="" name="classname"><br>');
document.writeln('<a id="funcsearchlink" class="searchbox-body" href="${relbase}_functions/index.$ext">Function<\\/a>: ');
document.writeln('<input type="text" size=10 value="" name="funcname"><br>');
document.writeln('<a class="searchbox-body" href="${relbase}_variables/index.$ext">Variable<\\/a>: ');
document.writeln('<input type="text" size=10 value="" name="varname"><br>');
document.writeln('<a class="searchbox-body" href="${relbase}_constants/index.$ext">Constant<\\/a>: ');
document.writeln('<input type="text" size=10 value="" name="constname"><br>');
document.writeln('<a class="searchbox-body" href="${relbase}_tables/index.$ext">Table<\\/a>: ');
document.writeln('<input type="text" size=10 value="" name="tablename"><br>');
document.writeln('<input type="submit" class="searchbox-button" value="Search">');
document.writeln('<\\/form>');
document.writeln('<\\/td><\\/tr><\\/table>');
document.writeln('<\\/td><\\/tr><\\/table>');
// -->
</script>
<div id="search-popup" class="searchpopup"><p id="searchpopup-title" class="searchpopup-title">title</p><div id="searchpopup-body" class="searchpopup-body">Body</div><p class="searchpopup-close"><a href="javascript:gwCloseActive()">[close]</a></p></div>
__EOS__
    return($j);
}

sub javascript_header {
    my ($depth, $subdir, $filename, $callroutine)=@_;
    my $cookiekey;

    if (!defined($callroutine)) {
        $callroutine='';
    }
    my $relbase="../" x $depth;
    $subdir =~ s/^\/+//;
    $subdir =~ s/\/+$//;
    if (defined($config{'cookie'})) {
        $cookiekey = uri_encode($config{'cookie'});
    } else {
        $cookiekey = 'default';
    }
    my $header=<<__EOS__;
<script src="${relbase}phpxref.js" type="text/javascript"></script>
<script language="JavaScript" type="text/javascript">
<!--
ext='.$ext';
relbase='$relbase';
subdir='$subdir';
filename='$filename';
cookiekey='$cookiekey';
handleNavFrame(relbase, subdir, filename);
$callroutine
// -->
</script>
__EOS__
  return($header);
}

sub navtoggle_html {
    my ($relroot) = @_;
    my $html=<<__EOD__;
<script language="JavaScript" type="text/javascript">
if (gwGetCookie('xrefnav')=='off')
  document.write('<p class="navlinks">[ <a href="javascript:navOn()">Show Explorer<\\/a> ]<\\/p>');
else
  document.write('<p class="navlinks">[ <a href="javascript:navOff()">Hide Explorer<\\/a> ]<\\/p>');
</script>
<noscript>
<p class="navlinks">
[ <a href="${relroot}nav.$ext" target="_top">Show Explorer</a> ]
[ <a href="index.$ext" target="_top">Hide Navbar</a> ]
</p>
</noscript>
__EOD__
    return $html;
}

sub pxopen {
    my ($filename, $mode)=@_;
    
    my $fh;
    if ($compress_files) {
        #print "opening ($filename,".$mode.'b'.") ($gzerrno)\n";
        $fh=IO::Zlib->new($filename,$mode.'b');
    } else {
        $fh=FileHandle->new($filename,$mode);
    }
    if (!defined($fh)) {
        die "Failed to open $filename: $!";
    }
    return $fh;
}

sub striptrailingslash {
    my ($dirname) = @_;
    my $l = length($dirname);
    while($l) {
        if (substr($dirname, $l-1, 1) ne '/') {
            return substr($dirname, 0, $l);
        }
        $l--;
    }
    return '';
}
