<?php

class Compiler  {

    
/**
     *  holds the template object
     *  @private
     */
    
var $tpl;

    
/**
     *  counter for fake variables
     *  @private
     */
    
var $counter 1;

    
/**
     *  compile the template
     *  @object Template
     *  @return null
     *  @public
     **/
    
    
function compile(&$template)  {
        
$this->tpl =&  $template;
        
$this->_parse_includes();
        if (
preg_match("#(<tpl:list\s+?src=\"(.*?)\">)(.*)<\/tpl:list>#s"$this->tpl->template$matches) )    {
            
$tpl                    =   $this->_parse_loops($matches);
            
$this->tpl->template    =   str_replace($matches[0], $tpl$this->tpl->template); 
        }
        
$this->tpl->template    =   $this->_parse_variables($this->tpl->template);
        
$this->_compile();
    }

    
/**
     *  write the template
     *  @return null
     *  @private
     **/
    
    
function _compile() {
        if (!
is_writeable($this->tpl->compiled_path) or !is_dir($this->tpl->compiled_path) )    {
            
trigger_error("Compiler: '{$this->tpl->compiled_path}' is not writeable, or not a directory"E_USER_ERROR);
        }
        
$template_hash              =   '<?php $TEMPLATE_HASH = "'.$this->tpl->template_hash.'"; ?>'."\n";
        
$function                   =   '<?php function tpl'.$this->tpl->template_hash.'(&$record) { ?>'."\n";
        
$this->tpl->template        =   $template_hash $function $this->tpl->template "\n<?php\n}\n?>";
        
    
//  create directories if necessary
        
$dirs   =   str_replace("\\"'/'dirname($this->tpl->template_name) );
        if (
$dirs !== '.')  {
        
//  error suppression. bad. should implement real recursive directory checks
            
@mkdir($this->tpl->_join($this->tpl->compiled_path$dirs), 0777true);
        }
    
//  write template file
        
$fp =   fopen($this->tpl->_join($this->tpl->compiled_path$this->tpl->template_name), "w+");
        if (!
fwrite($fp$this->tpl->templatestrlen($this->tpl->template) ) )   {
            
trigger_error("Compiler: '{$this->tpl->template_name}' could not be written"E_USER_ERROR);
        }
    }

    
/**
     *  parse file includes to build a full template
     *  @return null
     *  @private
     **/

    
function _parse_includes()  {
        
preg_match_all("#<tpl:include\s+?src=\"(.*)?\"\s+?\/>#"$this->tpl->template$matchesPREG_SET_ORDER);
        if (
is_array($matches) )    {
        
//  we expect two matches
        //  0   =>  the full include tag
        //  1   =>  the attribute of "src"
            
foreach($matches as $key=>$match)   {
                
$tag    =   $match[0];
                
$file   =   $match[1];
                if (!
is_readable($file) )    {
                    
trigger_error("File '{$file}' does not exist, or could not be read"E_USER_ERROR);
                    break; 
                }
                
$temp   =   implode("\n"file($file) );
                
$this->tpl->template    =   str_replace($tag$temp$this->tpl->template);
            }
        }
    }

    
/**
     *  parse looping template section
     *  @param  array   regex'ed match data
     *  @param  array   optional parent data
     *  @return string  template (or sections of)
     *  @private
     **/
    
    
function _parse_loops($matches$parent null) {
    
//  we expect 4 matches:
    //  0   =>  the largest <tpl:list> section
    //  1   =>  the opening <tpl:list> tag
    //  2   =>  the attribute of the first "src"
    //  3   =>  the section of tempate minus outside <tpl:list> tags
        
$template   =   $matches[0];
        
$var        =   $this->_create_variable();
        
$scope      =   isset($parent) ? $parent 'record';
    
//  create if code
        
$phpif      =   '<?php if ($'.$scope.'->_isset(\''.$matches[2].'\') ): ?>';
        
$template   =   str_replace($matches[1], $phpif$template);
    
//  create looping code
        
$phpfor     =   '<?php foreach($'.$scope.'->_get(\''.$matches[2].'\') as $'.$var.'): ?>';
        
$template   =   preg_replace("#<tpl:item>#"$phpfor$template1);
        
$matches[3] =   preg_replace("#<tpl:item>#"$phpfor$matches[3], 1);
    
//  look for additional lists we may need to parse
        
if (strpos($matches[3], 'tpl:list src=') !== false) {
            
preg_match("#(<tpl:list\s+?src=\"(.*?)\">)(.*)<\/tpl:list>#s"$matches[3], $recursive);
            
$tpl        =   $this->_parse_loops($recursive$var);
            
$template   =   str_replace($recursive[0], $tpl$template);
        }
    
//  close off loop/if tags
        
$template   =   str_replace('<tpl:default />''<?php else: ?>'$template);
        
$template   =   str_replace('</tpl:item>''<?php endforeach; ?>'$template);
        
$template   =   str_replace('</tpl:list>''<?php endif; ?>'$template);
    
//  parse variables
        
$template   =   $this->_parse_variables($template$var);
        return 
$template;
    }

    
/**
     *  parse conditionals
     *  @param  string  template section
     *  @param  string  optional parent data
     *  @return string  conditionals parsed template
     *  @private
     **/
    
    
function _parse_conditionals($template$parent null) {
        
$scope      =   isset($parent) ? $parent 'record';
        if (
strpos($template'tpl:optional src=') !== false)   {
        
//  we expect 2 matches:
        //  0   =>  the entire <tpl:optional> tag
        //  1   =>  the attribute of "src"
            
preg_match_all("#<tpl:optional\s+?src=\"(.*?)\">#s"$template$matchesPREG_SET_ORDER);
            if (
is_array($matches) )    {
                foreach(
$matches as $match) {
                    
$php        =   '<?php if ($'.$scope.'->_isset(\''.$match[1].'\') ): ?>';
                    
$template   =   str_replace($match[0], $php$template);
                }
            }
        }

        if (
strpos($template'tpl:default src=') !== false)   {
        
//  we expect 2 matches:
        //  0   =>  the entire <tpl:default> tag
        //  1   =>  the attribute of "src"
            
preg_match_all("#<tpl:default\s+?src=\"(.*?)\">#s"$template$matchesPREG_SET_ORDER);
            if (
is_array($matches) )    {
                foreach(
$matches as $match) {
                    
$php        =   '<?php if (!$'.$scope.'->_isset(\''.$match[1].'\') ): ?>';
                    
$template   =   str_replace($match[0], $php$template);
                }
            }
        }

        
$template   =   str_replace('</tpl:optional>''<?php endif; ?>'$template);
        
$template   =   str_replace('</tpl:default>''<?php endif; ?>'$template);
        return 
$template;
    }
    
    
/**
     *  parse variables in the current scope
     *  @param  string  template section
     *  @param  string  optional parent data
     *  @return string  variable parsed template
     *  @private
     **/
    
    
function _parse_variables($template$parent null) {
        
$scope      =   isset($parent) ? $parent 'record';
        
$pattern    =   "#".preg_quote('{$')."(.*?)".preg_quote('}')."#";
        
preg_match_all($pattern$this->tpl->template$matchesPREG_SET_ORDER);
        if (
is_array($matches) )    {
            foreach(
$matches as $match) {
            
//  0   =>  full variable
            //  1   =>  variable name
                
$phpget     =   '<?php echo $'.$scope.'->_get(\''.$match[1].'\'); ?>';
                
$template   =   str_replace($match[0], $phpget$template);
            }
        }
    
//  parse possible conditionals
        
$template   =   $this->_parse_conditionals($template$parent);
        return 
$template;
    }    

    
/**
     *  create a temporary variable modifier and return it's name to the compiler
     *  @return string  temp variable modifier
     *  @private
     **/
    
    
function _create_variable()    {
        
$var    =   'value'.(string)$this->counter;
        
$this->counter++;
        return 
$var;
    }

}

?>