<?

//*********************************************************//
// parse.php3
// Created By: "JP" <jprins@dds.nl>
// Modified By: "Spencer D. Mindlin" <smindlin@beaconeast.com>
//    Modify Date: 11/21/1999
// Modified By: "Travis Swicegood" <travis@domain51.com>
//    Modified Date: 02/25/2000
// Last Modified Date: 02/25/2000
//
//*********************************************************//
// Note from Spencer D. Mindlin
//*********************************************************//
// Special thanks to "JP" <jprins@dds.nl> for providing this
// concept and a solid source foundation for this solution.
// http://www.phpbuilder.com/columns/jprins20000201.php3
//*********************************************************//
// Reserved Tags:  REFRESH, EXPIRES, CACHE_NAME
//
//    1)    REFRESH -  
//            This tag will automatically check to see if this interval
//            has passed and refresh the cache appropriately.
//            I.E. QUARTERHOUR will automatically update the cache if  
//                    the cache is older that the latest quarter hour
//            arguments allowed:    MINUTE, QUARTERHOUR, HALFHOUR,
//                                HOUR, HALFDAY, DAY, MONTH
//    2)    EXPIRES -  
//            arguments allowed: Any interval in seconds
//
//        Please note the differences between REFRESH and EXPIRES.
//            EXPIRES - updates the content at the file's time of creation  
//                plus the interval
//            REFRESH - updates the content on the interval hour.   
//                HALFHOUR is 11:00 and 11:30.  The first refresh will occur  
//                at the first occurance that this interval is met after the  
//                cache as been created.
//                I.E.    a) Last cache creation @ 11:14
//                        b) Interval set at HALFHOUR
//                        c) Next refresh occurs at 11:30 (or the first  
//                            request afterwards)
//
//        refresh and expires may be used simultaneously if needed.
//
//    3)    CACHE_NAME - this argument can be used to name the cache file
//        in lieu of the default method of concatenating the arguments
//        in the tag.  This is useful when a tag contains characters
//        that are unusable for filenaming like slashes or colins as found
//        in date strings.
//
// Usage:
//        <my-style name='test value' expires="3600" cache_name="myStyleCache">
//
//    Note that code has been modified to use quoting (both, double and  
//    single quotes) and also modified to allow spaces to be used in values
//*********************************************************//
// End of Note from Spencer D. Mindlin
//*********************************************************//
//
//*********************************************************//
// Note from Travis Swicegood
//*********************************************************//
/*
* Most Recent Updates (02/25/2000 - Travis Swicegood):
*        1.    Expanded use of $DOCUMENT_ROOT - parse() and parse_it() both had
*            $doc_root strings added to their function.  Below are the modifications
*            for parse() calls
*                parse( $DOCUMENT_ROOT . $REDIRECT_URL ) becomes
*                parse( $DOCUMENT_ROOT, $REDIRECT_URL )
*        2.    Debug mode added - This is still being worked on as of 02/25/2000
*        3.    Code streamlined - Several while() statements were removed in favor of
*            for() statements.  Some for() statements called count() every
*            evaluation, this has been changed to only be called the first time.
*            Several redundant if() statements were combined.  if() statements
*            that could be used as elseif() were changed so they would not be
*            evaluated when unnecessary.  While some of theses changes may seem
*            small and inconsequential, if this script was in use on a virtual
*            server with several hundred users constantly using it, the
*            implications on server performance could be staggering
*        4.    Module file checking - No checking was done to insure that the correct
*            module file even existed, causing errors to be generated that caused
*            the system to halt.  Module files are now checked, and if they don't exist
*            the parse_it() returns a quoted (<!-- -->) error message which won't
*            return the desired effect, but won't cause the page to error. This
*            makes this parsing much easier to integrate with a large site were
*            confusing error messages popping up on websites can kill a site.
*        5.    CACHE attribute - The attribute CACHE has been added in order to force
*            or deny caching.  This attribute overrides settings from tags
*            EXPIRE and REFRESH.
*        6.    Tag closing - Support has been added for tag closing (ie. </my-style>)
*            Functionality is, however, limited.  Please see function
*            handle_close_xxx() for full documentation on how to use a closing
*            tag handler (the function has been commented to ensure that it does not
*            interfere with any other operations).
*        7.    Moved the call to parse() to the end of the file to ensure that all
*            functions are recognized (to ensure compatibility with all versions
*            of PHP)
*        8.    Dynamically generated tag-intro - This is probably the biggest feature
*            enhancement to this entire script, but was one of the last features
*            added (thus it is one of the later in this list).  Before you could
*            only have your tags read as "<my-xxx>", now the "my" has been replaced
*            to represent the file extention name.  Thus, a file called "index.my"
*            would use the custom tags of "<my-xxx>", or you can use "index.myhtml"
*            and make your tags "<myhtml-xxx>".  This creates an endless supply of
*            custom tag names.
*        9.    Setup instructions - Instructions to setup this parser are found at
*             the bottom of the file.  Included are examples of the '.htaccess' file,
*            handle_xxx(), handle_close_xxx().
///*********************************************************///


/*    The parse() function just reads the file and calls parse_it for every line to build
*    up the output in $buf.  Changes made in the February 2000 version are:
*        1.    Debugging support added.
*        2.    File access changed - The file is now read in it's entirety via a file()
*             command, then parsed.  */
function parse( $doc_root, $file, $_debug = false) {
    if(
$_debug ) {
        print
"<pre>\n";
        print
"function <b>parse( \$doc_root,  \$file, \$_debug )</b> called with <b>debug</b> mode enabled\n";
        print
"    \$doc_root    = $doc_root\n";
        print
"    \$file    = $file\n";
        print
"    \$_debug    = $_debug\n";
    }

    unset(
$buf );
#    $buf =  "";

    
if( eregi( "[a-z0-9]\.([a-z0-9]*)", $file, $ext ) )
        
$extention = $ext[1];
    else
        return
"<!-- The file must have an extention to be passed through this parser -->\n";
    
    unset(
$tag_use );
    for(
$i = 0, $n = strlen( $extention ); $i < $n; $i++ ) {
        
$use_tag .= "[" . substr( $extention, $i, 1 ) . "]";
    }

    if(
$file_contents = file( $doc_root . $file ) ) {
        for(
$i = 0, $n = count( $file_contents ); $i < $n; $i++ ) {
            if(
$_debug ) print "\$file_contents[$i] = " . str_replace( ">", "&gt;", str_replace( "<", "&lt;", $file_contents[$i] ) );
            
$buf .= parse_it( $file_contents[$i], $doc_root, $use_tag, $_debug );
        }
    }

    if(
$_debug ) {
        print
"    \$buf = $buf\n";
        print
"\n<b>End of parse()</b></pre>\n";
    }
    return
$buf;
}


// Setup
    
$str_cache_file =  "";

/*    The parse_it() function searches the information in $str to look for tags that
*    meet the <my-xxx> format.  When they are found, a search for the correct module
*    is started, and if found it is executed.  The following changes have been made
*    to parse_it():
*        1.    Debugging support added
*        2.    $def_cache_time was added to the function call so that the default
*            cache time could be set programmatically (this would require editing
*            the parse() function).
*        3.    Several multiple if() statements were combined where possible.  Some
*            switch() statements were adapted over if() statements where it made the
*            function easier to read.
*        4.    Support for CACHE=write/read/no added - This attribute to a tag will
*            override any other cache settings, forcing a read or write of the cache.
*            Obviously, if the 'write' attribute is used, this forces a read of new
*            information instead of reading the cached version.  Likewise, the 'read'
*            attribute attribute forces an attempt to read from the cache file
*            (useful if another program sets the cached information).  The 'no'
*            attribute makes the site read new information, and doesn't allow it to
*            cache the retrieved information.  This is extremely important when used
*            in areas of a website that demand stringent security.
*        5.    $end comment was removed from the return - An unchanged version of the
*             return is still available, but has been commented.  To enable this feature
*            search for "return $pre_HTML . $start . $buf . $post_HTML;" and remove the
*            comment from the line above it.  This might be useful for modules that
*            return a lot of information, but for testing purposes was redundant.
*        6.    Caching method changed - The method of caching that Spencer used seemed
*            be rather redundant.  I've removed all but the neccessities for caching,
*            and it no longer states in the webpage (via comments) that a cached copy
*            is being used.
*        7.    Closing tags - In earlier versions, you could not close a custom tag.
*             This would not be a problem unless you were using your custom tags in
*            to setup
*        8.    Dynamically generated tag-intro - This is probably the biggest feature
*            enhancement to this entire script, but was one of the last features
*            added (thus it is one of the later in this list).  Before you could
*            only have your tags read as "<my-xxx>", now the "my" has been replaced
*            to represent the file extention name.  Thus, a file called "index.my"
*            would use the custom tags of "<my-xxx>", or you can use "index.myhtml"
*            and make your tags "<myhtml-xxx>".  This creates an endless supply of
*            custom tag names.
*        9.    Streamlined eregi() functions - eregi() is case insensitive, it had
*            listed as "[A-Za-z...", this wasn't necessary, so it was removed in
*             favor or all uppercase or lowercase.
*        10.    Other HTML on the same line - This used to "eat" any HTML/text that was
*             on the same line as the special tag.  Updates have been made to return
*            anything HTML/text that is outside the tag, along with the new code from
*            the module.  Closing tags can be on the same line as well.
*/
function parse_it( $str, $doc_root, $use_tag, $_debug = false, $def_cache_time = 3600 ) {
    global
$arr_loaded; // Listing of loaded modules
    
global $str_cache_file;
    
$int_default_cache_time = $def_cache_time;

/*#    This can be uncommented if needbe, but it provides generally useless information.
    if( $_debug ) {
        print "\nfunction <b>parse_it( \$str, \$_debug )</b>\n    \$str = $str    \$doc_root = $doc_root\n    \$_debug = $_debug\n";
        print "    \$arr_loaded on line " . (__LINE__ - 1) . " = $arr_loaded\n";
        print "    \$str_cache_file on line " . (__LINE__ - 1) . " = $str_cache_file\n";
        print "    \$int_default_cache_time on line " . (__LINE__ - 1) . " = $int_default_cache_time\n";
    } */

    // Setup regular expression match for HTML before and/or after the custom tags.
    
$HTML_regs = '[a-z0-9<> _=\'\"\\\/\!\.\?\#-]*';

    if(
eregi( "($HTML_regs)(<$use_tag-([A-Z0-9_]*) ([^>]*)[>])($HTML_regs)", $str, $regs ) ) {
        if(
$_debug ) print "Custom start tag found on line " . (__LINE__ - 1) . "\n";
        
$str_tag = $regs[3];
        
$pre_HTML    = $regs[1];    // Anything on the same line as the tag before it starts
            
if( $_debug ) print "    \$pre_HTML on line " . (__LINE__ - 1) . " = $pre_HTML\n";
        
$post_HTML    = $regs[5];    // Anything on the same line as the tag after it ends
            
if( $_debug ) print "    \$post_HTML on line " . (__LINE__ - 1) . " = $post_HTML\n";

        if( !
file_exists( $doc_root . "/res/$str_tag.php3" ) ) {
            
$return_str = $pre_HTML . "\n<!-- my-$str_tag cannot be processed - (__LINE__) -->\n" . $str . $post_HTML;
                if(
$_debug ) print "    <b>parse_it()</b> preparing to return on line " . (__LINE__ - 1) . ".  Below is what would be returned\n<b>[start return]</b>\n " . str_replace( ">", "&gt;", str_replace( "<", "&lt;", $return_str ) ) . "\n<b>[end return]</b>";
            return
$return_str;
        } elseif( !
$arr_loaded[$str_tag] ) { // If module hasn't already been loaded, and exists, load now
            
include( $doc_root . "/res/$str_tag.php3" );    // For Unix
            #include( "res\$str_tag\$str_tag.php3");        // For Win
            
$arr_loaded[$str_tag] = 1;    // Mark loaded
        
}
        
        
$str_func    = "handle_$str_tag";
            if(
$_debug ) print "    \$str_func on line " . (__LINE__ - 1) . " = $str_func\n";
        
$arr_list    = explode( " ", strtolower( $regs[4] ) );
            if(
$_debug )
                for(
$i = 0, $n = count( $arr_list ); $i < $n; $i++ ) {
                    print
"    \$arr_list[$i] on line " . (__LINE__ - 1) . " = $arr_list[$i]\n";
                }
        
$str_cache_file    = "cache/$str_tag";        // For Unix
//        $str_cache_file = "cache\$str_tag";        // For Win

        
$flag_quote    = 0;

        for(
$i = 0, $n = count( $arr_list ); $i < $n; $i++ ) {
            
/*    We must depart from the $str_cache_file name for a moment to check and see which
             *    type of quotes, if any, are being used on each attribute.  This is done
             *    repeativitly to insure that all attributes are handled correctly. */
            
if( $flag_quote == 0 ) {
                if(
$argname = strtok( $arr_list[$i], "=" ) ) {
                    
$val = strtok( "=" );
                    switch(
$val[0] ) {
                        case
"'" :
                            
$char_quotetype        = "'";
                            
$flag_quote            = 1;
                            
$arglist[$argname]    = substr( $val, 1 );
                            break;
                        case
"\"" :
                            
$char_quotetype        = "\"";
                            
$flag_quote            = 1;
                            
$arglist[$argname]    = substr($val, 1);
                            break;
                        default :
                            
$flag_quote = 0;
                            
$arglist[$argname] = $val;
                    }
                    if(
substr( $arglist[$argname], -1 ) == $char_quotetype ) {
                        
$flag_quote            = 0;
                        
$arglist[$argname]    = substr($arglist[$argname], 0, -1);
                    }
                }
            } elseif(
$flag_quote == 1 ) {
                if(
substr( $arr_list[$i], -1 ) == $char_quotetype ) {
                    
$arglist[$argname] .=  " " . substr($arr_list[$i], 0, -1);
                    
$flag_quote = 0;
                } else {
                    
$arglist[$argname] .=  " " . $arr_list[$i];
                }
            }
            if(
$_debug ) print "    \$arglist[\"$argname\"] as of line " . (__LINE__) . " = " . $arglist["$argname"] . "\n";
        }

        
/*    Check to see if "cache" is set to "no", if so, it ignore this block. */
        
if( $arglist["cache"] != "no" ) {
            if(
$_debug ) print "    Attribute \"cache\" is not set to \"no\" on line " . (__LINE__ - 1) . "\n";
            if( isset(
$arglist["cache_name"] ) ) {
                
/*    If "cache_name" has been set, it will be used and cache name will not
                 *    be generated. */
                
$str_cache_file = $arglist["cache_name"];
            } else {
                for(
$i = 0, $n = count( $arr_list ); $i < $n; $i++ ) {
                    
/*    All other cache name options have failed, so a cache name must be
                     *    generated.  The cache name is generated by using the tag name, plus
                     *    all of the attributes excluding the attributes "expires", "refresh",
                     *    "cache_name", and "cache". */
                    
if(    ( $argname != "expires" ) &&
                        (
$argname != "refresh" ) &&
                        (
$argname != "cache_name" ) &&
                        (
$argname != "cache" ) ) {  
                        
$str_cache_file .=   "_" . $argname .   "=" . $arglist[$argname];  
                    }
                }
            }
        }

        
// Add $doc_root to $str_cache_file for Unix filesystem.
        
$str_cache_file = $doc_root . "/" . $str_cache_file;
            if(
$_debug ) print "    \$str_cache_file on line " . (__LINE__ - 1) . " = $str_cache_file\n";
        
        
/*    This section checks to see if the cache file is being used, by checking for
         *    the cache_filename + .lock.  This file is generated while the cache is being
         *    written to.  If the file exists, the system waits in 1 second intervals, and
         *    checks again.  After 20 seconds, if the lock file has not been removed, the
         *    function returns with a commented error message.  Generally, if the cached
         *    locked for more than 20 seconds, an error has occurred which is prohibitting
         *    the lock file from being removed.  If such an error occurred, and the while()
         *    doesn't terminate within a reasonable time it would create a potentially
         *    system crashing infinite loop.
         */
        
clearstatcache();
        if(
file_exists($str_cache_file . '.lock') ) {
            
register_shutdown_function( "remove_cache_lock" );
        }
        
        
$total_intervals = 0;
        while(
file_exists( $str_cache_file . '.lock' ) ) {
            if(
$_debug ) print "    Lock file check $total_intervals\n";
            if(
$total_intervals == 20 ) {
                
$return_str = "\n<!-- my-$str_tag error: cache file has been locked for over 20 seconds -->\n$str";
                    if(
$_debug ) print "    <b>parse_it()</b> preparing to return on line " . (__LINE__ - 1) . ".  Below is what would be returned\n<b>[start return]</b>\n " . str_replace( ">", "&gt;", str_replace( "<", "&lt;", $return_str ) ) . "\n<b>[end return]</b>";
                return
$return_str;
            }
            
sleep(2);
            
clearstatcache();
            
$total_intervals++;
            continue;
        }
        
        
clearstatcache();
        
        
// Setup the variables for reading and writing to the cache
        
$flag_read_cache            = 0;  
        
$flag_write_cache            = 0;
        
$flag_expires_read_cache    = 0;
        
$flag_expires_write_cache    = 0;
        
$flag_refresh_write_cache    = 0;
        
$flag_refresh_read_cache    = 0;
        
$flag_cache_write_cache        = 0;
        
$flag_cache_read_cache        = 0;
        
$flag_cache_no_cache        = 0;
        
        
// Check if the developer is using the 'expires' tag
        
if( !( isset( $arglist["expires"] ) && ( $arglist["expires"] < 10 ) ) ) {
            if(
$_debug ) print "    Attribute \"expires\" is set on line " . (__LINE__ - 1) . ".\n";
            
$flag_expires_write_cache = 1;
            if(
file_exists( $str_cache_file ) ) {
                if( ( !isset(
$arglist["expires"] ) && ( filemtime( $str_cache_file ) + $int_default_cache_time ) > date( "U" ) ) ||
                    (
filemtime( $str_cache_file ) + $arglist["expires"] ) > date ( "U" ) ) {
                    
$flag_expires_read_cache    = 1;
                    
$flag_expires_write_cache    = 0;
                        if(
$_debug ) print "    \$flag_expires_read_cache on line " . (__LINE__ - 2) . " = $flag_expires_read_cache\n    \$flag_expires_write_cache on line " . (__LINE__ - 1) . " = $flag_expires_write_cache\n";
                }
            }
        }
        
        
// Check if the developer is using the 'refresh' tag
        
if( isset( $arglist["refresh"] ) ) {
            if(
$_debug ) print "    Attribute \"refresh\" is set on line " . (__LINE__ - 1) . ".\n";
            
$flag_refresh_write_cache = 1;
            if(
file_exists( $str_cache_file ) ) {
                
$flag_refresh_read_cache    = 1;
                
$flag_refresh_write_cache    = 0;
                switch(
strtoupper( $arglist["refresh"] ) ) {
                    case
'MINUTE':
                        if ( !
checkRefreshMinute( mktime( ), filemtime( $str_cache_file ) ) ) {
                            
$flag_refresh_write_cache = 1;
                            
$flag_refresh_read_cache = 0;
                        }
                        break;
                    case
'QUARTERHOUR':
                        if (!
checkRefreshQuarterHour(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache = 1;
                            
$flag_refresh_read_cache = 0;
                        }
                        break;
                    case
'HALFHOUR':
                        if (!
checkRefreshHalfHour(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache = 1;
                            
$flag_refresh_read_cache = 0;
                        }
                        break;
                    case
'HOUR':
                        if (!
checkRefreshHour(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache = 1;
                            
$flag_refresh_read_cache = 0;
                        }
                        break;
                    case
'HALFDAY':
                        if (!
checkExpiredHalfDay(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache = 1;
                            
$flag_refresh_read_cache = 0;
                        }
                        break;
                    case
'DAY':
                        if (!
checkRefreshDay(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache = 1;
                            
$flag_refresh_read_cache = 0;
                        }
                        break;
                    case
'MONTH':
                        if (!
checkRefreshMonth(mktime(), filemtime($str_cache_file))) {
                            
$flag_refresh_write_cache = 1;
                            
$flag_refresh_read_cache = 0;
                        }
                        break;
                    default:
                        echo
"Invalid REFRESH attribute<BR>\n";
                        break;
                }
            } else {
                
$flag_refresh_write_cache    = 1;
                
$flag_refresh_read_cache    = 0;
            }
        }

        
// Check if the developer is using the 'cache' tag
        
if( isset( $arglist["cache"] ) ) {
            if(
$_debug ) print "    Attribute \"cache\" is set on line " . (__LINE__ - 1) . ".\n";
            switch(
$arglist["cache"] ) {
                case
"write" :
                    
$flag_cache_write_cache    = 1;
                        if(
$_debug ) print "    \$flag_cache_write_cache on line " . (__LINE__ - 1) . " = $flag_cache_write_cache\n";
//                    $flag_cache_read_cache    = 0;
                    
break;
                case
"read" :
//                    $flag_cache_write_cache    = 0;
                    
$flag_cache_read_cache    = 1;
                        if(
$_debug ) print "    \$flag_cache_read_cache on line " . (__LINE__ - 1) . " = $flag_cache_read_cache\n";
                    break;
                case
"no" :
//                    $flag_cache_write_cache = 0;
//                    $flag_cache_read_cache    = 0;
                    
$flag_cache_no_cache    = 1;
                        if(
$_debug ) print "    \$flag_cache_no_cache on line " . (__LINE__ - 1) . " = $flag_cache_no_cache\n";
                    break;
            }
        }

        
// Check for either cache update tags for reading and writing the cache
        
if( $flag_cache_no_cache ) {
            if(
$_debug ) print "    Caching has been overruled as of line " . (__LINE__ - 1). "\n";
            
/*    $flag_cache_no_cache overrides all other caching options, so if it is set
             *    evaluation of the other xxx_cache is useless, thus not done. */
            
$flag_write_cache    = 0;
            
$flag_read_cache    = 0;
        } elseif(    ( ( (
$flag_expires_write_cache || $flag_refresh_write_cache ) && !file_exists( $str_cache_file .  '.lock' ) ) ||
                    (
$flag_cache_write_cache && !$flag_cache_read_cache ) ) &&
                    !
$flag_cache_no_cache ) {
            if(
$_debug ) print "    Caching will be performed as of line " . (__LINE__ - 3) . "\n";
            
$flag_write_cache = 1;
            
touch( $str_cache_file . '.lock' );
            
register_shutdown_function( "remove_cache_lock" );
        } elseif(    ( ( (
$flag_expires_read_cache || $flag_refresh_read_cache ) &&  !$flag_write_cache ) ||
                    (
$flag_cache_read_cache && !$flag_cache_write_cache ) ) &&
                    !
$flag_cache_no_cache ) {
            if(
$_debug ) print "    Cache will be read as of line " . (__LINE__ - 3) . "\n";
            
$flag_read_cache = 1;
        }

        
$start = "\n<!-- $str_tag starts here -->\n";
        if(
$flag_read_cache || ( !strlen( $buf .= $str_func( $arglist ) ) ) ) {
            if(
$_debug ) print "    The cached version is going to be used as of line " . (__LINE__ - 1) . "\n";
            if(
$file_cache = file( $str_cache_file ) ) {
                if(
$_debug ) print "    A cached version has been found as of line " . (__LINE__ - 1) . "\n";
                for(
$i = 0, $n = count( $file_cache ); $i < $n; $i++ ) {
                    
$buf .= $file_cache[$i];
                }
                
$start .= "<!-- $str_tag: using cached version -->\n";
            } else {
                if(
$_debug ) print "    A cached version was attempted, but the file was empty as of line " . (__LINE__ - 1) . "\n";
                
$buf .=   "<!-- $str_tag: error - attempted to use empty cache -->\n";  
            }
        }
        
$end .=   "\n<!-- $str_tag ends here -->\n";  
        
        
        if(
$flag_write_cache && ( $f = fopen( $str_cache_file,   "w" ) ) ) {  
            if(
$_debug ) print "    A cache file is being made with the output from $str_func\n";
            
fputs( $f, $buf );
            
fclose( $f );
            
remove_cache_lock();
        }  
//        $return_str = $pre_HTML . $start . $buf . $end . $post_HTML;
        
$return_str = $pre_HTML . $start . $buf . $post_HTML;
            if(
$_debug ) print "    <b>parse_it()</b> preparing to return on line " . (__LINE__ - 1) . ".  Below is what would be returned\n<b>[start return]</b>\n    " . str_replace( ">", "&gt;", str_replace( "<", "&lt;", $return_str ) ) . "\n<b>[end return]</b>\n";
        return
$return_str;
    } elseif(
eregi( "($HTML_regs)(</$use_tag-([A-Za-z0-9]*)([^>]*)[>]*)($HTML_regs)", $str, $regs ) ) {
        if(
$_debug ) print "Closing tag found on line" . (__LINE__ - 1) . "\n";
        
/*    A closing tag has been found, this code will proceed to process the information in
         *    a similar fashion to that of the opening tag.  All of the same arguments that were
         *    saved in the opening tag have been applied to the closing tags.  These commands can
         *    be cached in the same fashion as the opening tag, except the following preceeds any
         *    cache names: "close_" */

        
$pre_HTML    = $regs[1];    // Anything on the same line as the tag before it starts
            
if( $_debug ) print "    \$pre_HTML on line " . (__LINE__ - 1) . " = $pre_HTML\n";
        
$post_HTML    = $regs[5];    // Anything on the same line as the tag after it ends
            
if( $_debug ) print "    \$post_HTML on line " . (__LINE__ - 1) . " = $post_HTML\n";
        
$str_tag = $regs[3];
            if(
$_debug ) print "    \$str_tag on line " . (__LINE__ - 1) . " = str_tag\n";
        
$str_func = "handle_close_" . $regs[3];
            if(
$_debug ) print "    \$str_func on line " . (__LINE__ - 1) . " = str_func\n";
        
$arr_list    = explode( " ", chop( strtolower( $regs[4] ) ) );
            if(
$_debug )
                for(
$i = 0, $n = count( $arr_list ); $i < $n; $i++ ) {
                    print
"    \$arr_list[$i] on line " . (__LINE__ - 3) . " = $arr_list[$i]\n";
                }

        
$str_cache_file    = "cache/close_$str_tag";        // For Unix
//        $str_cache_file = "cache\close_$str_tag";        // For Win

        
$flag_quote    = 0;

        for(
$i = 0, $n = count( $arr_list ); $i < $n; $i++ ) {
            
/*    We must depart from the $str_cache_file name for a moment to check and see which
             *    type of quotes, if any, are being used on each attribute.  This is done
             *    repeativitly to insure that all attributes are handled correctly. */
            
if( $flag_quote == 0 ) {
                if(
$argname = strtok( $arr_list[$i], "=" ) ) {
                    
$val = strtok( "=" );
                    switch(
$val[0] ) {
                        case
"'" :
                            
$char_quotetype        = "'";
                            
$flag_quote            = 1;
                            
$arglist[$argname]    = substr( $val, 1 );
                            break;
                        case
"\"" :
                            
$char_quotetype        = "\"";
                            
$flag_quote            = 1;
                            
$arglist[$argname]    = substr($val, 1);
                            break;
                        default :
                            
$flag_quote = 0;
                            
$arglist[$argname] = $val;
                    }
                    if(
substr( $arglist[$argname], -1 ) == $char_quotetype ) {
                        
$flag_quote            = 0;
                        
$arglist[$argname]    = substr($arglist[$argname], 0, -1);
                    }
                }
            } elseif(
$flag_quote == 1 ) {
                if(
substr( $arr_list[$i], -1 ) == $char_quotetype ) {
                    
$arglist[$argname] .=  " " . substr($arr_list[$i], 0, -1);
                    
$flag_quote = 0;
                } else {
                    
$arglist[$argname] .=  " " . $arr_list[$i];
                }
            }
            if(
$_debug ) print "    \$arglist[\"$argname\"] as of line " . (__LINE__) . " = " . $arglist["$argname"] . "\n";
        }

        
/*    Check to see if "cache" is set to "no", if so, it ignore this block. */
        
if( $arglist["cache"] != "no" ) {
            if(
$_debug ) print "    Attribute \"cache\" is not set to \"no\" on line " . (__LINE__ - 1) . "\n";
            if( isset(
$arglist["cache_name"] ) ) {
                
/*    If "cache_name" has been set, it will be used and cache name will not
                 *    be generated. */
                
$str_cache_file = $arglist["cache_name"];
            } else {
                for(
$i = 0, $n = count( $arr_list ); $i < $n; $i++ ) {
                    
/*    All other cache name options have failed, so a cache name must be
                     *    generated.  The cache name is generated by using the tag name, plus
                     *    all of the attributes excluding the attributes "expires", "refresh",
                     *    "cache_name", and "cache". */
                    
if(    ( $argname != "expires" ) &&
                        (
$argname != "refresh" ) &&
                        (
$argname != "cache_name" ) &&
                        (
$argname != "cache" ) ) {  
                        
$str_cache_file .=   "_" . $argname .   "=" . $arglist[$argname];  
                  &nb