Login   Register  
PHP Classes
elePHPant
Icontem

File: class-block_template.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of Jonathan Gotti  >  Block template  >  class-block_template.php  >  Download  
File: class-block_template.php
Role: Class source
Content type: text/plain
Description: main class file
Class: Block template
Template engine based in the concept of blocks
Author: By
Last change: made it public
Date: 2006-02-07 01:21
Size: 36,694 bytes
 

Contents

Class file image Download
<?php
/**
* manage the HTML output.
* block_template is a template parser/renderer see the template sample for more info on the way the template work
* @changelog 2005-12-07 - bugfixes _templatize didn't correctly send content parameter to _emit_signal method 
*            2005-09-30 - remove no more used footer property 
*                       - reset_ method now reset _sections property too
*                       - clean the box method
*            2005-09-28 - new parameter include_vars for method get_section_vars
*            2005-07-13 - new predefined tag parameter 'require' for required vars in section includes
*            2005-06-18 - new reset_ method
*            2005-06-17 - new predefined tag parameter 'tagcontent' for open/close tags
*                       - new predefined tag parameter 'preparse' to enable preparsing of parameter value
*            2005-06-07 - now register_callback() will return a pointer 
*                       - add new method unregister_callback()
*                       - new methods highlight_str() & highlight_template_file()
*            2005-06-01 - add callback support on 'on-output' signal 
*                       - output can return var or file
*                       - new set_safe_output method to protect block_template tags
*                       - bugfixes no more problem on file finishing on a sectioname (@#sectioname)
*            2005-05-24 - add new get_sections_select() method
*            2005-05-18 - add comment line support (with #)
*                       - new method choose_section
*                       - bugfix to avoid infinite loop problem when including section into themselves
*            2005-04-29 - removed some unused properties and method regarding sitename and page_title
*                         clean some error notice
*            2005-04-12 - add_css now support alternate stylesheets and direct css rules inputs, idem for js
*            2005-03-14 - remove some deprecated methods (no more menu related methods)
*                       - replace some deprecated method by some more logical ones
*            2005-03-10 - *NEW callback support 
*                       - tags can now be closed with a / or by a closing tag and %= are replaced with @=
*            2005-02-27 - add the content-type meta tag support and betterify the meta management
*                       - add support for parameters in template tags ie: %=tagname param='value'=%
*                       - menu entrys can now be passed as a full html by setting the URL to null
*/
class block_template{
  /** where all the body content will go */
  var $content;
  var $header;
  /** string favicon link tag */
  var $favicon;
  /** array of css link tags */
  var $css       = array();
  /** array of js link tags */
  var $js        = array(); 
  /** string css definition that will go in the <style></style> tags inside the header */
  var $_css;
  /** string javascript code that will go in the <script></script> tags inside the header */
  var $_js;
  /** @private metas tags parameters */
  var $metas     = array();
  
  /**
  * @param str $page_title 
  * @param str $template_name (the directory containing the template must have the same name (case sensitive))
  * @param str $template_dir  directory containing templates subdirs
  */
  function block_template($page_title,$template_name='default',$template_dir='template'){
    $this->template_dir = $template_dir.(substr($template_dir,-1)=='/'?'':'/');
    $this->template     = $template_name?$template_name:'default';
    $this->set_title($page_title);
    # essaie de charger le fichier de template
    $this->parse_template();
    # ajoute les css par defaut
    if(is_file($css = $this->template_dir.$this->template.'/css.css'))
      $this->add_css($css);
    elseif(is_file($css = $this->template_dir.$this->template.'/'.$this->template.'.css'))
      $this->add_css($css);
  }
  /**
  * send the rendered html code to STD-OUT (browser)
  * cette fonction emetra le signal on-output voici prototype de rappelle callback(&$block_template,$vars,$return)
  *       si la fonction de rapelle renvoie autrechose que FALSE alors la sortie sera remplacé 
  *       par la valeur retourné par la fonction de rappelle
  *@param array $vars same as setcionvars in *templatize* methods
  *@param mixed $return FALSE as default it will print the output to STDOUT as always
  *                     you can set it to true to get the output page return as a function returned value
  *                     and at last but not least you can pass a filename to save the document to.
  *@return void (if $return=FALSE)| string HTML code of the page (if $return=TRUE) | bool (if $return='/path/to/filesave');
  */
  function output($vars=null,$return=FALSE){
    $out = $this->_emit_signal('on-output',$vars,$return); # return callback value if any
    
    if(! $return){ # DEFAULT CASE PRINT ON STDOUT
      if($out){
        print($out);
      }else{
        print("<html>\n");
        print($this->_header());
        print($this->_body($vars));
        print('</html>');
      }
    }elseif(is_string($return)){
      if(! $fout = fopen($return,'w'))
        return FALSE;
      fwrite($fout,$out?$out:"<html>\n".$this->_header().$this->_body($vars).'</html>');
      fclose($fout);
      return TRUE;
    }else{
      return $out?$out:"<html>\n".$this->_header().$this->_body($vars).'</html>';
    }
  }
  /*
  * You will probably never use this one but if you need to display block_template tags (@=tagname/=@) in the output
  * you can use this method to set the output safe.
  * WARNING: this method won't work when the output mode is value (block_template::output(null,TRUE)).
  * other way to say the same: it will only work in STDOUT and file output mode.
  * @param bool $set set it either true or false
  * @return handler | bool
  */
  function set_safe_output($set=TRUE){
    static $handle;
    if($set){ # renvoie le pointeur de callback
      if(isset($handle))
        return $handle;
      else
        return $handle = $this->register_callback('on-output',array($this,'_safe_output')); # no need to work with reference as the emit_signal will send only a reference
    }elseif($handle){ # libere le pointeur de callback si existant
      $ret = $this->unregister_callback($handle);
      unset($handle);
      return TRUE;
    }
    return FALSE;
  }
  /**
  * internally used as callback to protect eventual block_template tags in the output
  * @see set_safe_output 
  * @private
  */
  function _safe_output($block_template,$vars,$return){
    if($return && !is_string($return) )
      return FALSE; # avoid inifnite loop so kill if not in stdout or file output mode
    # protect tags
    $block_template->content = str_replace('@=','@=__SAFE__',$block_template->content);
    if(! substr_count($block_template->content,'@=__SAFE__')) # no replacement so continue as always
      return FALSE;
    # some vars have been protected so we clean them
    return str_replace("@=__SAFE__","@=",$block_template->output($vars,1));
}
  /**
  *
  */
  /**
  * parse a template file 
  * @param string $template_file filepath to the template file
  * @param bool $overwrite will overwrite any previously loaded sections if a new one have the same name
  * @param bool $halt_on_error will stop the script execution on error else will return FALSE
  * @return bool
  * @todo add dynamic include support
  */
  function parse_template($template_file=null,$overwrite=TRUE,$halt_on_error=TRUE){
    if(! $template_file)
      $template_file = $this->template_dir.$this->template.'/template.php';
    elseif( (! file_exists($template_file)) && file_exists($this->template_dir.$this->template.'/'.$template_file) )
      $template_file = $this->template_dir.$this->template.'/'.$template_file;
    if(! ($template = @file_get_contents($template_file)) ){
      if(! $halt_on_error){
        return FALSE;
      }else{
        echo "<b style='color:red;'>[TEMPLATE ERROR] can't open '$this->template' template files</b>";
        exit();
      }
    }
    # suppression des lignes de commentaires
    $template = preg_replace("!^\s*#[^\n]*$!m",'',$template);
    if(! preg_match_all("!(?:@#(\w+)\s*((?:[^@]+|@[^#])+)+)!",$template."\n",$m,PREG_SET_ORDER)) # add \n to correct a bug on last empty section loading
      return FALSE;
    foreach($m as $v){
      if( (!$overwrite) && $this->section_exists($v[1]))
        continue;
      $this->_sections[$v[1]] = trim($v[2]);
    }
    return TRUE;
  }
  /**
  * clean all read template sections,all css rules and the template name.
  * @param string $template_name if passed then will load the new template files;
  * @param bool $full if set to true then will also clean page_title, favicon, content, js, metas, _header
  */
  function reset_($template_name=null,$full=FALSE){
    $this->_css       = null;
    $this->css        = array();
    $this->template   = null;
    $this->_sections  = array();
    if($full){ # reset others vars
      $this->_js = $this->favicon = $this->page_title = $this->content = $this->_header = null;
      $this->js = $this->metas = array();
    }
    if(is_string($template_name))
      $this->block_template($this->page_title,$template_name);
    
  }
  ##### SETTINGS METHODS#####
  /**
  * set the page title
  * @param string $title
  */
  function set_title($title){
    $this->page_title = strip_tags($title);
  }
  ###### ADD HEAD CONTENT METHODS #####
  /**
  * add a favicon to the page
  */
  function add_favicon($favicon){
    $this->favicon = '<link rel="shortcut icon" href="'.$favicon.'" type="image/'.substr($favicon,strrpos('.',$favicon)+1).'">';
  }
  /**
  * add an external css stylesheet or css header definition
  * @param string $css path to css file or css rules
  * @param string $title optionnal title for the stylesheet
  * @param bool   $alternate set to true for alternate stylesheet
  */
  function add_css($css,$title=null,$alternate=FALSE){
    if(preg_match('!{[^}]+}!',$css)) # in this case we consider that we received a css string
      @$this->_css .= $css."\n";
    else
      $this->css[] = '<link href="'.$css.'" rel="'.($alternate?'alternate ':'').'stylesheet" type="text/css" '
                      .(is_string($title)?"title=\"$title\" ":'').'/>';
  }
  /**
  * add a javascript file or function in the header
  * @param string $js path to js file or javascript code to include in the header
  */
  function add_js($js){
    if(substr_count($js,';')) # in this case we consider that we received a js string
      @$this->_js .= $js."\n";
    else
      $this->js[] = '<script src="'.$js.'" type="text/javascript" language="javascript"></script>';
  }
  /**
  * prepare meta tags
  * be aware that multiple calls to this methods for the same type will only append to the previous setted tag.
  * in a word you can only set asingle meta tag for each type (so you can add keywords by successives call of this method usefull 
  * when you want to dinamicly set it form your content)
  * @param string $content
  * @param string $type (publisher,keywords,robots,description,author,copyright,...) New refresh,no-cache
  */
  function add_meta($content,$type='description',$lang=null){
      $type = strtolower($type);
      if(! isset($this->metas[$type]))
        $this->metas[$type] = $content.($lang?"\" lang=\"$lang":'');
      else
        $this->metas[$type] .= $content.($lang?"\" lang=\"$lang":'');
  }
  /**
  * add a string to the header
  * @param string $str string to hadd in the <head></head> HTML tags
  */
  function add_header($str){
    @$this->_header .= "$str\n";
  }
  ###### ADD BODY CONTENT METHODS #####
  /**
  * ajoute du contenu dans la page
  */
  function add_content($content){
    if( func_num_args()>1) # @todo supprimer cette ligne # this is for old stuff detection you can safely delete those 2 lines
      show(print_r(func_get_args(),1).print_r(debug_backtrace(),1),red,1,1);
    $this->content .= $content;
  }
  /**
  * add content by calling the templatize method
  */
  function add_templatize_content($vars,$sectioname){
    if(! $this->section_exists($sectioname)){
      $this->content .= '<b style="color:red;">Missing section '.$sectioname.'</b>';
    }else{
      $this->content .= $this->templatize($vars,$sectioname);
    }
  }
  function add_templatize_string_content($string,$sectionvars=null){
    $this->content .= $this->templatize_string($string,$sectionvars=null);
  }
  /**
  * easy way to add a box to content, that's only a quick way of doing block_template::add_content(block_template::box($content,$title,$boxname))
  * see box for more details
  */
  function add_box_content($content,$title=null,$boxname=''){
    if($str = $this->box($content,$title,$boxname))
      $this->add_content($str);
  }
  /**
  * return a 'BOX_*' section in a more common way
  * BOX_* sections are section containings 2 vars boxtitle and boxcontent
  * this function is the same as calling block_template::templatize(array('boxtitle'=>'title','boxcontent'=>'content'),'BOX_NAME');
  * @param string $content
  * @param string $title may be ommit
  * @param string $boxname the BOX_ section to use
  */
  function box($content,$title='',$boxname=''){
    if(! $boxname)
      $boxname = 'BOX';
    if( substr($boxname,0,3) != 'BOX' )
      $boxname = 'BOX_'.$boxname;
    # maintenant la boite
    if( $boxname = $this->choose_section(array($boxname,'BOX')) )
      $out = $this->templatize(array('boxtitle'=>$title,'boxcontent'=>$content),$boxname);
    else
      $out = "<div id=\"$boxname\"'>".($title?"$title ":'')."$content</div>";
    return $out;
  }
  /**
  * add some attribute to the body tag, an onload  event for example
  * @param string $paramname name of the attribute
  * @param mixed  $paramvalue value of the attribute
  */
  function add_body_param($paramname,$paramvalue,$overwrite=FALSE){
    if($overwrite)
      $this->body_params[$paramname] = $paramvalue;
    else
      $this->body_params[$paramname] .= $paramvalue;
  }
  
  ##### GESTION DES SIGNAUX #####
  /**
  * enregistre une fonction de callback qui sera appelé lors de certains signaux.
  * @param string $signame nom du signal (on-load, on-output, on-templatize)
  * @param mixed $callback nom de la fonction de callback (peut prendre un tableau array($object,$method))
  * @param string $tagtype type de tag sur lequel on applique le callback 
  *                         0|3|  applique le callback sur tout les types de tag 
  *                         1|    applique le callback uniquement au tag unique
  *                         2|    applique le callback sur des tags disposant de tag de fermeture
  * @param string $sectioname null par defaut, contraint le callback a la section précisé, ou toutes les sections si null
  * @see _emit_signal pour le prototype de rappelle
  * @return int callback handler id
  */
  function register_callback($signame,$callback,$tagtype=3,$sectioname=null){
    static $handlers=array() ,$hid=0; # gestion de pointeurs sur les signaux
    # dereferencement d'un signal pé-éxistant
    if( $signame=='-unregister-' && is_int($callback)){ # on detruit le pointeur sur ce signal
      if( count($handlers) <1  || ! is_array($h=$handlers[$callback]) ) # pointeur invalide
        return FALSE;
      unset($this->callbacks[$h[0]][$h[1]][$h[2]][$callback]);
      unset($handlers[$callback]);
      return TRUE;
    }
    # referencement du signal
    if($tagtype != 1 && $tagtype !=2)
      $tagtype = 3;
    if(! $sectioname>0)
      $sectioname = 'ALL';
    $hid++;
    $this->callbacks[$signame][$tagtype][$sectioname][$hid] = $callback;
    $handlers[$hid] = array($signame,$tagtype,$sectioname);
    return $hid;
  }
  /**
  * permet de dereferencer un callback associé a un signal par le biais de la methode register_callback()
  * @param int $cb_handler pointeur de callback renvoyé par la methode register_callback()
  * @see register_callback()
  * @return bool
  */
  function unregister_callback($cb_handler){
    return $this->register_callback('-unregister-',$cb_handler);
  }
  
  /**
  * le prototype de la fonction de rappelle sera function callback(block_template object,$section,$tagname,$params,$content)
  * @private
  * cette methode est utilisé par l'objet en interne afin de gerer les signaux 
  * @param string $signame nom du signal emis (on-templatize,on-output);
  * @see _templatize output
  */
  function _emit_signal($signame,$params=null,$tagname=null,$content=null,$section=null){
    $tagtype = (strlen($content)?2:1);
    if(! isset($this->callbacks[$signame]))
      return FALSE;
    # on test d'abord les callbacks restreint a cette section et a ce type de tag
    if(@is_array($this->callbacks[$signame][$tagtype][$section])){
      foreach($this->callbacks[$signame][$tagtype][$section] as $cb){
        $res = call_user_func($cb,&$this,$section,$tagname,$params,$content);
        if($res !== FALSE && $res !==null )
          return $res;
      }
    }
    # ensuite ceux restreint a cette section quel que soit le type de tag
    if(@is_array($this->callbacks[$signame][3][$section])){
      foreach($this->callbacks[$signame][3][$section] as $cb){
        $res = call_user_func($cb,&$this,$section,$tagname,$params,$content);
        if($res !== FALSE && $res !==null )
          return $res;
      }
    }
    # puis ceux restreint a ce type de tag
    if(@is_array($this->callbacks[$signame][$tagtype]['ALL'])){
      foreach($this->callbacks[$signame][$tagtype]['ALL'] as $cb){
        $res = call_user_func($cb,&$this,$section,$tagname,$params,$content);
        if($res !== FALSE && $res !==null )
          return $res;
      }
    }
    # puis enfin les generalistes qui se foutent de la section ou du type de tag
    if(@is_array($this->callbacks[$signame][3]['ALL'])){
      foreach($this->callbacks[$signame][3]['ALL'] as $cb){
        $res = call_user_func($cb,&$this,$section,$tagname,$params,$content);
        if($res !== FALSE && $res !==null )
          return $res;
      }
    }
    return FALSE;
  }
  
  ##### TEMPLATIZE METHODS #####
  /*
  * will templatize your datas you can have as much vars as you like
  *   optionnal sub section tag can be used by adding a * to the sectioname (ie: sectioname*) 
  *   if so thoose sections will be checked that there's either subsection or at least one not null (null or false but not 0) value in it
  * @param array $sectionvars is an indexed array with index in $sectionkey giving the correspoding datas
  * @param string $sectioname self explain
  * @param bool $section_optional (used in internal for optionnal sub section you'll normally never use this one by your own)
  */
  function templatize($sectionvars,$sectioname,$section_optional=FALSE){
    $sectioname = strtoupper($sectioname);
    if(! $this->section_exists($sectioname) )
      return FALSE;
    $section      = $this->_sections[$sectioname]; # recupere le contenu de la section
#    $section_keys = $this->get_section_vars($sectioname); # recupere les tags de la section
    return $this->_templatize($section,$sectionvars,$sectioname,$section_optional);
  }
  /** 
  * parse la chaine $string comme une section de template. 
  * Cela permet au programmeur de parser une chaine recue d'une base de données ou encore généré dynamiquement.
  * @note Cette méthode à été ajouter car elle me parassait apporter une certaine souplesse dans l'utilisation de block_template,
  *       cependant n'en ayant jamais fait usage, je serais tres interresser d'etre informé de l'utilisation que vous pouriez en faire.
  */
  function templatize_string($string,$sectionvars=null){
#    $section_tags = $this->get_string_vars($string);
    return $this->_templatize($string,$sectionvars);
  }
  /**
  * methode interne pour les methodes 'templatize'
  * @see templatize,templatize_string
  * @private
  * @param string $section     contenu de la section (ou chaine si on a appeller templatize_string)
  * @param array  $sectionvars variables en provenance du programmeur
  * @param string $sectioname  le nom de la section sur laquelle on travaille
  * @param bool   $section_optional flag pour le traitement des sections incluse (par un tag)
  * @return string
  */
  function _templatize($section,$sectionvars,$sectioname=null,$section_optional=FALSE){
    # recuperation des variables 
    if($sectioname && ($keys = $this->get_section_vars($sectioname)) )
      $section_keys = &$keys;
    elseif($section && ($keys = $this->get_string_vars($section)) )
      $section_keys = &$keys;

    if( @is_array($section_keys) ){ # ne traite les variables que si il y en a
      # on verifie que les sections optionnelles recoivent au moins une variable non null
      if( $section_optional){
        if(isset($sectionvars['require'])){ # verifie les prérequis
          $required = explode(',',$sectionvars['require']);
          foreach($required as $req){
            if(! (@$sectionvars[$req] || $this->section_exists($req)))
              return FALSE;
          }
        }else{
          foreach($section_keys as $key){
            if( $this->section_exists($key['tagname']) || (isset($sectionvars[$key['tagname']]) && $sectionvars[$key['tagname']]!='') ){ $vars=TRUE;break; }
          }
          if(! isset($vars))
            return FALSE;
        }
      }
      
      if(! is_array($sectionvars)) $sectionvars = array();
      
      # on remplace les tags
      foreach($section_keys as $var){
        #preparation des parametres de la fonction de callback
        $_params = array_merge((array) @$var['params'],$sectionvars);
        
        # peparsing des parametre si demandé
        if(isset($_params['preparse'])){
          if($preparse = explode(',',$_params['preparse'])){
            unset($_params['preparse']);
            foreach($preparse as $k){
              $_params[$k] = $this->templatize_string($_params[$k],$_params);
            }
          }
        }
        
        # emission du signal on-templatize sur ce tag
        $tmp_str = $this->_emit_signal('on-templatize',$_params,$var['tagname'] ,@$_params['tagcontent'],$sectioname);
        if($tmp_str!==FALSE){
          $section = str_replace($var['str_replace'],$tmp_str,$section);
          continue;
        }
        
        # si le tag porte le nom d'une section on l'inclus
        if(isset($this->_sections[$var['tagname']])){
          if( (! $var['is_optionnal']) && isset($_params['require'])) $var['is_optionnal']=TRUE;
          $section = str_replace($var['str_replace'],$this->templatize($_params,$var['tagname'],$var['is_optionnal']),$section);
          continue;
        }
        # prepare la valeur de remplacement 
        if( isset($_params[$var['tagname']]) ){ # cherche un parametre du meme nom
          $value = $_params[$var['tagname']];
          # on verifie que ce n'est pas une reference vers un autre parametre
          if( $value && $value[0]=='@' && isset($_params[substr($value,1)]) ){
            $value = $_params[substr($value,1)];
          }
        }elseif( isset($_params['default']) ){ # sinon prend la valeur par defaut
          $value = $_params['default'];
        }else{
          $value = '' ;
        }
        # si c'est le nom d'une section on remplace par la section (pas d'inclusion optionnelle ici)
        if($this->section_exists($value) && $value !== $sectioname)
          $section = str_replace($var['str_replace'],$this->templatize($_params,$value),$section);
        else # sinon on remplace par sa valeur
          $section = str_replace($var['str_replace'],$value,$section);
      }
    }
    
    return $section;
  }
  ##### GETTING VARS #####
  /**
  * renvoie les variables attendus par la section $sectioname du template
  * @param string $sectioname le nom de la section
  * @param bool $nocache  par defaut vaut false ainsi les resultats sont gardé en cache de facon a accellerer les futures requetes.
  *                       il peut s'averer utile de ne pas caché ses resultats (pour une section appeller une seule fois par exemple)
  *                       auquel cas il suffit de passer cette option a TRUE
  * @param bool $include_vars par defaut vaut FALSE si passer a TRUE alors retourne aussi les variables attendu par les sections incluses ou imbriqués.
  * @return array or FALSE if none 
  *         the array will contain arrays(tagname,params,str_replace,is_optionnal[,content]) for complex vars,
  *         where sectionvarname is equal to the sting return for single vars, params is an array of parameter array('paramname or int'=>'value',...)
  *         and finally the original_tag which is the complete tag as it is in the template (will be use for string replacement)
  */
  function get_section_vars($sectioname,$nocache=FALSE,$include_vars=FALSE){
    static $sectionvars,$fullsectionvars;
    
    if(! $this->section_exists($sectioname)) # first of all check that the section exists
      return FALSE;
    
    if($include_vars){ # recursively append all included sections vars to the result
      if( (!$nocache) && @is_array($fullsectionvars[$sectioname]) ) # return cached datas if exists according to $nocache
        return $fullsectionvars[$sectioname];
      # get first level sectionvars using or not cached datas
      if(! is_array($svars = $this->get_section_vars($sectioname,$nocache,FALSE)) )
        return FALSE;
      # walk thru sectionvars to get vars of included sections.
      foreach($svars as $var){ 
        if($this->section_exists($var['tagname']) && is_array($vars_ = $this->get_section_vars($var['tagname'],$nocache,TRUE)) )
          foreach($vars_ as $vars__) $svars[] = $vars__; 
      }
      if($nocache)
        return $svars;
      else
        return $fullsectionvars[$sectioname] = $svars;
    }
    
    if($nocache) # no cache so give the result as is
      return $this->get_string_vars($this->_sections[$sectioname]);
    
    # return cached content and create it if needed
    if(isset($sectionvars[$sectioname]) && is_array($sectionvars[$sectioname]))
      return $sectionvars[$sectioname];
    else
      return $sectionvars[$sectioname] = $this->get_string_vars($this->_sections[$sectioname]);
  }
  /**
  * renvoie les variables contenue dans la chaine $string comme si c'était une section de template
  * @param string string
  * @todo devrait fonctionner selon un principe de 'pile' afin de permettre la lecture de tags imbriqués
  */
  function get_string_vars($string){
    # $tagexp = "!@=(\w+)(\*)?" # tagname
              # ."((?:[^@]+|[^=]@)+)?" # params
              #// ."(/)?=@((?(4)(.*?)@=/\\1=@))!s" ;
              # ."(?:/=@|=@(.*?)@=/\\2=@)!s" ;
    $tagexp = "!@=(\w+)(\*)?((?:\s+\w+(?:\s*=\s*([\"']).*?\\4))+)?\s*(?:/=@|=@(.*?)@=/\\1=@)!s";
    # check single vars as needed
    if(! preg_match_all($tagexp,$string,$m,PREG_SET_ORDER)){
      $vars =  FALSE;
    }else{
      foreach($m as $k=>$tagdata){
        $vars[$k]['tagname']       = $tagdata[1];
        $vars[$k]['is_optionnal']  = (bool) @$tagdata[2];
        # $vars[$k]['is_optionnal']  = (bool) (substr($tagdata[1],-1)==='*');
        if(isset($tagdata[3])){
          preg_match_all("!\s+(\w+)(?:\s*=\s*(([\"'])?(?(3).*?\\3|[^\s=]+)))?!s",$tagdata[3],$m2,PREG_SET_ORDER);
          unset($params);
          foreach($m2 as $k2=>$v2){
            if(! isset($v2[2])){
              $params[] = $v2[1];
            }else{
              if(! isset($v2[3]))
                $params[$v2[1]] = $v2[2];
              else
                eval('$params[$v2[1]] = '.$v2[2].';');
            }
          }
          $vars[$k]['params'] = @$params;
        }
        if(isset($tagdata[5])){
          # $vars[$k]['content']     = $tagdata[5];
          # $vars[$k]['params']['tagcontent'] = $vars[$k]['content']; # Quick and dirty hack to enable the 'tagcontent' parameter
          $vars[$k]['params']['tagcontent'] = $tagdata[5];
        }
        $vars[$k]['str_replace'] = $tagdata[0];
      }
    }
    return $vars;
  }
  ##### MANIPULATION DES SECTIONS #####
  /**
  * renvoie un tableau contenant le nom des sections disponibles dans le template
  * @return array or FALSE si pas de sections
  */
  function get_sections(){
    if(! is_array($this->_sections)) return FALSE;
    return array_keys($this->_sections);
  }
  /**
  * retourne le code html d'un element select contenant la liste des sections du template.
  * @param string $name     nom de l'element de formulaire (valeur de l'attribut name de la balise select)
  * @param string $selected nom de la section preselectionnée (si null alors essaie de trouver une occurence dans _POST ou _GET)
  * @param bool   $autoselect si oui alors ajoute un submit onchange
  * @param string $id attribut id optionnel de la balise select.
  * @return string HTML code
  */
  function get_sections_select($name='template_section',$selected=null,$autoselect=TRUE,$id=null){
    if(! $sections = $this->get_sections())
      return "<b style='color:silver;'>Aucune section disponible</b>";
    # try to find the selected value
    if(is_null($selected) && (isset($_GET[$name])||isset($_POST[$name])) )
      $selected = (@$_POST[$name]?$_POST[$name]:(@$_GET[$name]?$_GET[$name]:null));
    # make option list
    $opts[] = "<option value=\"\">- Sections du template $this->template -</option>";
    foreach($sections as $sec){
      $opts[] = "<option value=\"$sec\"".(($selected && $sec==$selected)?' selected="selected"':'')." >$sec</option>";
    }
    return "\n\t<select name=\"$name\"".($autoselect?' onchange="if(this.value!=\'\')this.form.submit();"':'').(is_string($id)?" id=\"$id\"":'')." >\n\t\t".implode("\n\t\t",$opts)."\n\t</select>";
  }
  /**
  * retourne le contenue d'une section de template tel quel sans le parser
  * @param string $sectioname
  * @return string
  */
  function get_section($sectioname){
    return $this->section_exists($sectioname)?$this->_sections[$sectioname]:FALSE;
  }
  /**
  * verifie qu'une section existe
  * @param string $sectioname nom de la section dont on veut verifier l'existence
  * @param bool   $returnname si passer a TRUE alors la fonction retournera le nom de la section en cas de succes au lieu de TRUE
  * @return bool
  */
  function section_exists($sectioname,$returnname=FALSE){
    $ret = isset($this->_sections[$sectioname]);
    if($returnname && $ret)
      return $sectioname;
    return $ret;
  }
  /**
  * prend une liste de nom de section sous forme de tableau ou de chaine et retourne la premiere qui est trouvé.
  * cette fonction peut etre utile si votre application veut permettre l'utilisation de section par defaut au cas ou le
  * template ne fournirais pas la section données par exemple: block_template->choose_section('SEC1|SEC2|SEC3) testera successivement
  * les sections passé en arguments et retournera la premiere section existante.
  * @param mixed $section_list string('SEC1|SEC2...') ou array(SEC1,SEC2..)
  * @return string first section name matching an existing section or '' if no section match and false on input error
  */
  function choose_section($section_list){
    if(is_string($section_list))
      $section_list = explode('|',$section_list);
    if(! is_array($section_list))
      return FALSE;
    foreach($section_list as $v){
      if($this->section_exists($v) )
        return $v;
    }
    return '';
  }
  
  /**
  * permet l'ajout dynamique de section par le programmeur 
  * @param string $sectioname le nom de la section
  * @param string $section_content le contenu de la section
  * @param bool   $overwrite si passe a TRUE alors ecrasera une eventuelle section existante au lieu de renvoyer FALSE
  */
  function add_section($sectioname,$section_content='',$overwrite=FALSE){
    if($overwrite || !$this->section_exists($sectioname) )
      $this->_sections[$sectioname] = $section_content;
    else
      return FALSE;
  }
  /**
  * ajoute du contenu a la fin d'une section
  * @param string $sectioname le nom de la section
  * @param string $section_content contenu a ajouter
  * @param bool   $autocreate TRUE par defaut, si FALSE ne creera pas la section si elle n'existe pas
  */
  function append_section($sectioname,$section_content,$autocreate=TRUE){
    if( (!$this->section_exists($sectioname)) && !$autocreate )
      return FALSE;
    $this->_sections[$sectioname] = ($this->section_exists($sectioname)?$this->_sections[$sectioname]:'').$section_content;
  }
  /**
  * ajoute du contenu au debut d'une section
  * @param string $sectioname le nom de la section
  * @param string $section_content contenu a ajouter
  * @param bool   $autocreate TRUE par defaut, si FALSE ne creera pas la section si elle n'existe pas
  */
  function prepend_section($sectioname,$section_content,$autocreate=TRUE){
    if( (!$this->section_exists($sectioname)) && !$autocreate )
      return FALSE;
    $this->_sections[$sectioname] = $section_content.($this->section_exists($sectioname)?$this->_sections[$sectioname]:'');
  }
  ##### PREPARATION DES SORIES #####
  /**
  *Prepare the header to output
  *@access Private
  */
  function _header(){
    $this->header  = "<head>\n";
    $this->header .= "<title>$this->page_title</title>\n";
    # add the meta tags
    if($this->metas){
      $equivs = array('content-type','refresh','pragma','expires');
      foreach($this->metas as $name=>$value){
        if(in_array($name ,$equivs))
          $this->header .= "<meta http-equiv='$name' content='$value' />\n";
        else
          $this->header .= "<meta name=\"$name\" content=\"$value\" />\n";
      }
    }
    # add favicon
    if(is_string($this->favicon))
      $this->header .= $this->favicon."\n";
    # add css 
    if(is_array($this->css))
      $this->header .= implode("\n",$this->css);
    if(is_string($this->_css))
      $this->header .= '<style type="text/css">'.$this->_css.'</style>';
    # add js
    if(is_array($this->js))
        $this->header .= implode("\n",$this->js);
    if(is_string($this->_js))
      $this->header .= "<script type=\"text/javascript\" language=\"javascript\">\n<!--\n".$this->_js."\n//-->\n</script>";
    # add user specific content
    $this->header .= (isset($this->_header)?$this->_header:'')."\n</head>\n";
    return $this->header;
  }
  
  /**
  *Prepare the body content to output
  *@access Private
  *@todo supprimer les variables logo et consorts
  */
  function _body($bodyvars=null){
    if(! is_array($bodyvars)) $bodyvars = array();
    # prepare les parametres du tag body
    if(@is_array($this->body_params)){
      foreach($this->body_params as $paramname=>$paramvalue)
        $bodytag .= " $paramname=\"$paramvalue\"";
      $bodyvars['bodyparams'] = $bodytag;
    }
    $bodyvars['content'] = isset($this->content)?$this->content:'';
    return $this->body = $this->templatize($bodyvars,'BODY');
  }
  /**
  * retourne le code de la chaine comme un code source de template avec coloration syntaxique au format html
  * @param str $str chaine de caractere a colorer
  * @param str $outfile nom de fichier de sortie optionnel, si fournis alors la sortie sera sauvegarder dans un fichier.
  */
  function highlight_str($str,$outfile=null){
    if(! is_string($str))
      return FALSE;
      
    $str = htmlspecialchars($str);
    $str = preg_replace('!(@#\w+)!',"<b style='color:#3333cc;'>$1</b>",$str);
    $str = preg_replace('!(@=\w+)!',"<b style='color:#33cc33;'>$1",$str);
    $str = preg_replace('!(=@)!',"$1</b>",$str);
    
    # return "$str";
    return "<code><pre>$str</pre></code>";
  }
  function highlight_section($sectioname,$outfile=null){
    return $this->highlight_str($this->_sections[$sectioname],$outfile);
  }
  function highlight_template_file($file=null,$outfile=null){
    if(is_null($file))
      $file = $this->template_dir.$this->template.DIRECTORY_SEPARATOR.'template.php';
    return $this->highlight_str(join('',file($file)),$outfile);
  }
}
?>