class.temphplate.php

[Retour] [Voir]

<?php



/**
*Moteur de templates puissant et très extensible: Cette version n'implémente pas
*la gestion du cache interne.
*@package Temphplate
*@author Nicolas Chambrier <naholyr@yahoo.fr>
*@link http://temphplate.sf.net
*@license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
*@copyright Copyright (c) 2004, Nicolas Chambrier
**/



/*****************************************************************************/



/**
*Ce code d'erreur signifie qu'il n'y a eu aucune erreur (sic).
**/
define('TPHPL_ERR_NO_ERROR'0);

define('TPHPL_ERR_UNKNOWN_METHOD'1);
define('TPHPL_ERR_NEWDELIM_NOT_CHAR'2);
define('TPHPL_ERR_NEWDELIM_NOT_SAME'3);
define('TPHPL_ERR_INVALID_VARNAME'4);
define('TPHPL_ERR_FILE_NOT_FOUND'5);
define('TPHPL_ERR_UNDEF_OR_LOCAL'6);
define('TPHPL_ERROR_UNKNOWN_COMMAND'7);
define('TPHPL_ERR_UNKNOWN_STRUCTURE'8);
define('TPHPL_ERR_UNKNOWN_FUNCTION'9);
define('TPHPL_ERR_COMMAND_DEFINED'10);
define('TPHPL_PARSE_ERROR'11);
define('TPHPL_ERR_COMMAND_FAILED'12);
define('TPHPL_ERR_INC_CYCLE'13);
define('TPHPL_ERR_P_BOOLOP_EXPECTED'14);



/*****************************************************************************/



/**
*Classe de base. Elle définit le squelette principale de Temphplate.
*Sont gérés: les variables, les blocs, et toutes les opérations permettant
*d'étendre le moteur.
*Temphplate n'est qu'une extension de cette base.
*@package Temphplate
*@author Nicolas Chambrier <naholyr@yahoo.fr>
*@see Temphplate
*@version 1.1
*@deprecated N'utilisez cette classe que si vous voulez limiter les fonctionnalités de vos
*modèles (légère amélioration de performance, mais seule la base - les blocs et les variables -
*est accessible). Préférez l'utilisation de {@link Temphplate}
**/
class BasicTemphplate
{


    
/**
    *Code de la dernière erreur levée (>0 s'il y a eu une erreur).
    *@var int
    **/
    
var $last_error TPHPL_ERR_NO_ERROR;

    
/**
    *Message de la dernière erreur levée.
    *@var string
    **/
    
var $last_error_msg NULL;

    
/**
    *@access private
    *@var array
    **/
    
var $__errors;


    
/**
    *Pour chaque élément [clé]=>valeur de ce tableau, on remplace toutes les occurences de
    *"clé" par "valeur" AVANT l'analyse.
    *Cette opération est faite AVANT les traitements imposés par {@link call_before}.
    *@var array
    **/
    
var $replace_before = array();

    
/**
    *Pour chaque élément $method de ce tableau, on appellera la méthode de l'objet portant
    *ce nom sur le contenu du modèle, AVANT d'analyser le modèle.
    *Cette opération est faite APRES les traitements imposés par {@link replace_before}.
    *Une utilisation de cette propriété est illustrée par l'implémentation des structures
    *elseif et else dans Temphplate (n'hésitez pas à potasser le code source).
    *@var array
    **/
    
var $call_before = array();

    
/**
    *Pour chaque élément [clé]=>valeur de ce tableau, on remplace toutes les occurences de
    *"clé" par "valeur" APRES l'analyse.
    *Cette opération est faite AVANT les traitements imposés par {@link call_after}.
    *@see encodeAccents
    *@var array
    **/
    
var $replace_after = array();

    
/**
    *Pour chaque élément $method de ce tableau, on appellera la méthode de l'objet portant
    *ce nom sur le résultat du modèle APRES analyse.
    *Cette opération est faite APRES les traitements imposés par {@link replace_after}.
    *@var array
    **/
    
var $call_after = array();

    
/**
    *Liste des dossiers dans lesquels rechercher les modèles. Séparés par un point-virgule.
    *@var string
    **/
    
var $include_dir '';

    
/**
    *Indique si l'on doit supprimer toutes les variables non affectées du modèle après
    *analyse.
    *@var boolean
    **/
    
var $delete_unset_vars TRUE;

    
/**
    *Détermine la syntaxe des instructions internes de Temphplate. Vous pouvez personnaliser
    *la syntaxe de vos modèles grâce à cette instruction.
    *Exemple: <code>$tpl->syntax['endblock']='/block';</code> et vous devrez fermer les blocs
    *des modèles analysés par <b>$tpl</b> avec <b>{/block}</b> au lieu de
    *<b>{end:block}</b>.
    *@var mixed
    **/
    
var $syntax = array();

    
/**
    *Nom du dernier fichier analysé.
    *@var string
    **/
    
var $filename NULL;

    
/**
    *Résultat de l'analyse d'un modèle.
    *@see fetch
    *@see output
    *@var string
    **/
    
var $html_result NULL;

    
/**
    *Nom de la variable automatique indiquant le numéro d'occurence d'un bloc.
    *@see repeatBlock
    *@see setBlock, set
    *@var string
    **/
    
var $var_tphpl_num 'tphpl_num';

    
/**
    *Délimiteurs utilisés pour encadrer les instructions.
    *@access private
    *@var array
    **/
    
var $__delim NULL;

    
/**
    *Tags des instructions de structures de bloc.
    *@access private
    *@var array
    **/
    
var $__tags = array();

    
/**
    *Assignations [variable]=>$valeur, ou [.bloc]=>$assignations.
    *@access private
    *@var array
    **/
    
var $__assigns = array();

    
/**
    *Arbre représentant la structure générale du modèle analysé.
    *@access private
    *@var array
    **/
    
var $__tree = array();

    
/**
    *Référence sur les derniers assignements du dernier bloc analysé par __findBlockAssigns
    *Si $__p_block_assigns n'est pas NULL, on a avoir $__p_assigns['.bloc'] =& $__p_block_assigns
    *@see __findBlockAssigns, __p_block_assigns
    *@access private
    *@var array
    **/
    
var $__p_assigns NULL;

    
/**
    *Référence sur la liste complète des assignements du dernier bloc analysé par __findBlockAssigns
    *Si $__p_block_assigns n'est pas NULL, on a avoir $__p_assigns['.bloc'] =& $__p_block_assigns
    *@see __findBlockAssigns, __p_assigns
    *@access private
    *@var mixed
    **/
    
var $__p_block_assigns NULL;


    
/**
    *Constructeur.
    *@param string $include_dir Une liste de répertoires séparés par un point-virgule. Tous
    *les fichiers que Temphplate devra traiter, il les cherchera dans '.', puis dans ces
    *répertoires, l'un après l'autre.
    **/
    
function BasicTemphplate $include_dir '' )
    {
        
// messages d'erreurs
        
$this->setError(TPHPL_ERR_UNKNOWN_METHOD'La méthode "%1" n\'est pas définie'E_USER_ERROR);
        
$this->setError(TPHPL_ERR_NEWDELIM_NOT_CHAR'Les nouveaux délimiteurs doivent être de simples caractères'E_USER_ERROR);
        
$this->setError(TPHPL_ERR_NEWDELIM_NOT_SAME'Les nouveaux délimiteurs ne devraient pas être identiques'E_USER_WARNING);
        
$this->setError(TPHPL_ERR_INVALID_VARNAME'Le nom de variable "%1" est incorrect'E_USER_WARNING);
        
$this->setError(TPHPL_ERR_FILE_NOT_FOUND'Fichier "%1" introuvable'E_USER_ERROR);
        
$this->setError(TPHPL_ERR_INC_CYCLE'Cycle d\'inclusions détecté dans le fichier "%1"'E_USER_ERROR);
        
$this->setError(TPHPL_ERR_UNKNOWN_STRUCTURE'Le type de structure "%1" est inconnu'E_USER_WARNING);
        
$this->setError(TPHPL_PARSE_ERROR'Erreur lors de l\'analyse :  %1 'E_USER_ERROR);
        
// syntaxe
        
$this->addStructure('block');
        
$this->syntax['inc'] = 'inc';
        
$this->include_dir $include_dir;
        
$this->setDelimiters('{','}');
        
// encodage des accents
        
$this->encodeAccents();
    }

    
/**
    *Définit un code d'erreur utilisable.
    *@param int $type le code d'erreur
    *@param string $message le message d'erreur. Vous pouvez placer des "%i"
    *dans ce texte. %1 sera remplacé par le 1er argument supplémentaire passé à error() lors
    *de la levée de l'erreur, %2 le second, etc...
    *@param int $level le niveau de l'erreur, une valeur parmi E_USER_NOTICE, E_USER_WARNING
    *et E_USER_ERROR
    *@see error
    **/
    
function setError $type $message $level E_USER_WARNING )
    {
        
$this->__errors[$type] = array($message$level);
    }

    
/**
    *Ajoute une structure de bloc personnalisée.
    *@param string $structure_name Nom de la structure (vous devrez définir une méthode
    *parseStructureNomDeLaStructure).
    *@param string $open_tag Tag de l'instruction ouvrant un bloc (instruction complète:
    *<b>{OpenTag:Paramètre}</b>.
    *@param string $end_tag Tag de l'instruction fermant un bloc (instruction complète:
    *<b>{CloseTag}</b>).
    *@return boolean
    **/
    
function addStructure $structure_name $open_tag FALSE $end_tag FALSE )
    {
        
$method 'parseStructure'.ucfirst($structure_name);
        if (!
method_exists($this,$method)) {
            
$this->error(TPHPL_ERR_UNKNOWN_METHOD$method);
            return 
FALSE;
        }
        
$structure_name strtolower($structure_name);
        
$open_tag $open_tag strtolower($open_tag) : $structure_name;
        
$end_tag $end_tag strtolower($end_tag) : 'end:'.$open_tag;
        
$this->syntax[$structure_name] = $open_tag;
        
$this->syntax['end'.$structure_name] = $end_tag;
        
$this->__tags[] = $structure_name;
        return 
TRUE;
    }

    
/**
    *Modifie les délimiteurs ouvrant et fermant une instruction.
    *Exemple: <code>$tpl->setDelimiters('<','>');</code> et vous devrez écrire vos
    *instructions dans les modèles analysés par <b>$tpl</b> sous la forme
    *<b><Instruction></b> au lieu de <b>{Instruction}</b>.
    *@param char $open Délimiteur ouvrant.
    *@param char $close Délimiteur fermant.
    *@return boolean
    **/
    
function setDelimiters $open $close )
    {
        if (
strlen($open)!=|| strlen($close)!=1) {
            
$this->error(TPHPL_ERR_NEWDELIM_NOT_CHAR);
            return 
FALSE;
        }
        if (
$open==$close) {
            
$this->error(TPHPL_ERR_NEWDELIM_NOT_SAME);
        }
        if (
is_array($this->__delim)) {
            list(
$old_open,$old_close) = $this->__delim;
            unset(
$this->replace_before[$old_open.'-']);
            unset(
$this->replace_before['-'.$old_close]);
        }
        
$this->__delim = array($open,$close);
        
$this->replace_before[$open.'-'] = '&#'.ord($open).';';
        
$this->replace_before['-'.$close] = '&#'.ord($close).';';
        return 
TRUE;
    }

    
/**
    *@return string Version actuelle de la classe
    **/
    
function version ( )
    {
        return 
'1.1';
    }

    
/**
    *Définit les propriétés $__p_assigns et $__p_block_assigns.
    *@param string $path le chemin vers le bloc dont on veut récupérer les assignations.
    *@param string $dir paramètre-résultat: le chemin supérieur. (exemple: $path="blk1/blk2/blk3"
    *=> $dir="blk1/blk2")
    *@param string $base paramètre-résultat: la base du chemin. (exemple: $path="blk1/blk2/blk3"
    *=> $base="blk3")
    *@return boolean TRUE si $__p_block_assigns est non NULL, c'est à dire si le bloc $path existe
    *et a des assignations.
    *@see __p_assigns
    *@see __p_block_assigns
    *@access private
    **/
    
function __findBlockAssigns $path , &$dir , &$base )
    {
        
$p_assigns =& $this->__assigns;
        
$p_block_assigns NULL;
        
$dir '';
        
$base $path;
        if (
FALSE!==($pos=strpos($base,'/'))) {
            while (
FALSE!==($pos=strpos($base,'/'))) {
                
$root substr($base0$pos);
                
$dir .= '/'.$root;
                
$base substr($base$pos+1);
                if (!isset(
$p_assigns['.'.$root])) $p_assigns['.'.$root] = array(array());
                
$p_block_assigns =& $p_assigns['.'.$root];
                
$p_assigns =& $p_block_assigns[sizeof($p_block_assigns)-1];
            }
        }
        if (
$dir!=''$dir substr($dir1);
        if (isset(
$p_assigns['.'.$base])) $p_block_assigns =& $p_assigns['.'.$base];
        
$this->__p_assigns =& $p_assigns;
        
$this->__p_block_assigns =& $p_block_assigns;
        return 
NULL!==$p_block_assigns;
    }

    
/**
    *Effectuer une assignations.
    *<code>Modèle: ...{mavar}-{block:monbloc}...{mavar}...{end:block}-
    *$tpl->set('mavar', 'mavaleur');
    *$tpl->set('monbloc/mavar', 'occurence_1');
    *$tpl->set('monbloc/mavar', 'occurence_2');
    *Résultat: ...mavaleur-...occurence_1......occurence_2...-</code>
    *@param string $var la variable à assigner. Cela peut être un nom de variable ("var", ou
    *"blk1/blk2/var" si elle se trouve imbriqués dans des blocs), ou un nom de bloc ("blk1", ou
    *"blk1/blk2" s'il se trouve imbriqué dans d'autres blocs).
    *Si vous voulez affecter un bloc, vous devrez lui affecter un ensemble d'assignations.
    *Il s'agit d'une liste d'assignations, une assignations étant un tableau [$var]=>$val.
    *Note: On affecte l'assignation à la dernière occurence du bloc. Si la variable est déjà
    *affectée, alors on crée une nouvelle occurence du bloc.
    *@param mixed $val la valeur à assigner. s'il s'agit d'un tableau c'est un bloc qui
    *sera assigné.
    *@param boolean $new_block Si ce paramètre est mis à TRUE, et que $var est un bloc ou
    *un chemin vers une variable imbriqué, on force la création d'une nouvelle occurence
    *de ce bloc sans assignations. Les prochains appels à set concernant le même bloc affecteront
    *cette occurence.
    *@return boolean
    **/
    
function set $var $val $new_block FALSE )
    {
        
$var strtolower($var);
        
$block_var $this->__findBlockAssigns($var$dir$var);
        
$p_assigns =& $this->__p_assigns;
        
$p_block_assigns =& $this->__p_block_assigns;
        if (
$var!='') {
            
$open preg_quote($this->__delim[0],'/');
            
$close preg_quote($this->__delim[1],'/');
            if (
preg_match('/('.$open.')|('.$close.')|[\:\/\.\(\)]/i',$var)) {
                
$this->error(TPHPL_ERR_INVALID_VARNAME$var);
                return 
FALSE;
            }
            if (
is_array($val)) {
                
$this->__addBlockPrefix($val);
                
$var '.'.$var;
            }
            if (
$block_var && isset($p_assigns[$var])) {
                
$p_block_assigns[][$var] = $val;
            }
            else {
                
$p_assigns[$var] = $val;
            }
            if (
$block_var && $new_block$p_block_assigns[] = array();
        }
        return 
TRUE;
    }

    
/**
    *Supprime la dernière occurence du bloc.
    *<code>Modèle: ...{block:monbloc}-{mavar}{end:block}...
    *$tpl->set('monbloc/mavar', '1');
    *$tpl->set('monbloc/mavar', '2');
    *Résultat: ...-1-2...
    *$tpl->deleteLast('monbloc');
    *Résultat: ...-1...</code>
    *@param string $block le chemin vers le bloc à supprimer
    *@return boolean
    **/
    
function deleteLast $block )
    {
        
$block strtolower($block);
        
$this->__findBlockAssigns($block$path$block);
        if (isset(
$this->__p_assigns['.'.$block])) return @array_pop($this->__p_block_assigns);
        return 
FALSE;
    }

    
/**
    *Effectue une assignation en série. On fera simplement une suite d'appels à set.
    *<code>Modèle: ...{v1} {v2} {v3}...
    *$tpl->setVars(array('v1'=>'a','v2'=>'b','v3'=>'c'));
    *Résultat: ...a b c...</code>
    *Notez qu'il n'y a a priori pas de contre-indication à utiliser cette méthode
    *pour assigner une liste d'éléments d'un tableau.
    *<code>Modèle: ...{0} {1} {2}...
    *$tpl->setVars(array('a','b','c'));
    *Résultat: ...a b c...</code>
    *@param array $assign une assignation. Il s'agit d'un tableau [$var] => $val.
    *@return boolean
    *@see set
    **/
    
function setVars $assign )
    {
        foreach (
$assign as $var => $val ) {
            if (!
$this->set($var,$val)) return FALSE;
        }
        return 
TRUE;
    }

    
/**
    *Effectue une assignation groupée dans un bloc.
    *<code>Modèle: ...{block:a}-{x}.{y}{end:block}-...
    *$tpl->setBlock('a', array(array('x'=>'a','y'=>'A')));
    *$tpl->setBlock('a', array(array('x'=>'b','y'=>'B')));
    *Résultat: ...-a.A-b.B-...
    *$tpl->setBlock('a', array(array('x'=>'c','y'=>'C')), TRUE);
    *Résultat: ...-a.A-b.B-c.C-.-...</code>
    *Le "-." en trop après "-c.C" correspond à l'occurence vide qu'on a forcée avec le
    *3e paramètre à TRUE.
    *@param string $block le nom du bloc
    *@param array $assigns une liste d'assignations. Une assignation est un tableau de la
    *forme [$var] => $val.
    *@param boolean $new_block Si ce paramètre est mis à TRUE, on force la création d'une
    *nouvelle occurence de ce bloc sans assignations.
    *@see set
    *@return boolean
    **/
    
function setBlock $block $assigns $new_block FALSE )
    {
        if (
$new_block) {
            
$last_var NULL;
            foreach (
$assigns as $var => $val$last_var $var;
            
$last_val array_pop($assigns);
            if (!
$this->setBlock($block$assignsFALSE)) return FALSE;
            return 
$this->set($block.'/'.$last_var$last_valTRUE);
        }
        else {
            foreach (
$assigns as $var => $val) if (!$this->set($block.'/'.$var$val)) return FALSE;
        }
        return 
TRUE;
    }

    
/**
    *Répète un bloc sans assignations.
    *<code>Modèle: {block:point}.{end:block}
    *$tpl->repeatBlock('point', 5);
    *Résultat: .....</code>
    *Notez qu'il y a tout de même une affectation implicite de la
    *variable 'tphpl_num', qui est le numéro d'occurence du bloc.
    *<code>Modèle: {block:a}.{tphpl_num}{end:block}.
    *$tpl->repeatBlock('a', 5);
    *Résultat: .1.2.3.4.5.</code>
    *@param string $block le nom du bloc
    *@param int $n le nombre de répétitions
    *@return boolean
    **/
    
function repeatBlock $block $n )
    {
        
$block strtolower($block);
        
$this->__findBlockAssigns($block$path$block);
        if (!isset(
$this->__p_assigns['.'.$block])) {
            
$this->__p_assigns['.'.$block] = array();
            
$this->__p_block_assigns =& $this->__p_assigns['.'.$block];
        }
        for (
$i=1$i<=$n; ++$i$this->__p_block_assigns[] = array();
        return 
TRUE;
    }

    
/**
    *Ajoute le préfixe '.' aux clé des assignations quand on affecte un tableau (qui
    *est donc automatiquement compris comme étant une liste d'assignations).
    *@param array $assigns la liste des assignations à préfixer
    *@access private
    **/
    
function __addBlockPrefix ( &$assigns )
    {
        
$res = array();
        foreach (
$assigns as $var => $val) {
            
$var strtolower($var);
            if (
is_array($val)) {
                
$var '.'.$var;
                
$this->__addBlockPrefix($val);
            }
            
$res[$var] = $val;
        }
        
$assigns $res;
    }

    
/**
    *Le code HTML généré verra tous ses caractères spéciaux encodés selon leur équivalent
    *en entité HTML.
    *Les caractères suivants sont ignorés:
    *<code>'<'
    *'>'
    *'"'
    *'&'</code>
    *Par défaut, cet encodage est activé.
    *<code>Modèle: Je m&eacute;lange encodé et non <b>encodé</b>
    *Résultat: Je m&eacute;lange encod&eacute; et non <b>encod&eacute;</b></code>
    *<code>Modèle: Modèle: Je m&eacute;lange encodé et non <b>encodé</b>
    *$tpl->encodeAccents(FALSE);
    *Résultat: Je m&eacute;lange encodé et non <b>encodé</b></code>
    *Il s'agit en fait simplement de l'ajout ou du retrait de certaines règles de
    *transformation APRES analyse. {@source 3 13}
    *Note: si vous voulez transformer également certains autres caractères spéciaux,
    *ajoutez-les à la table de transformations:
    *<code>$tpl->replace_after['>'] = '&gt;';
    *$tpl->replace_after['<'] = '&lt;';
    *$tpl->replace_after['&'] = '&amp;';
    *$tpl->replace_after['"'] = '&quot;';</code>
    *@see replace_after
    *@param boolean $do activer (TRUE) ou désactiver (FALSE) l'encodage.
    **/
    
function encodeAccents $do TRUE )
    {
        
$table get_html_translation_table(HTML_ENTITIES);
        unset(
$table['<']);
        unset(
$table['>']);
        unset(
$table['"']);
        unset(
$table['&']);
        if (
$do) {
            foreach (
$table as $char => $code)
                
$this->replace_after[$char] = $code;
        }
        else {
            foreach (
$table as $char => $code)
                unset(
$this->replace_after[$char]);
        }
    }

    
/**
    *Renvoie le résultat de l'analyse du modèle.
    *<code>Modèle mavar.htm: ...{mavar}...
    *Code complet:
    *$tpl =& new BasicTemphplate;
    *$tpl->set('mavar', 'coucou');
    *$tpl->parse('mavar.htm');
    *$str = $tpl->fetch();
    *Contenu de $str: ...coucou...</code>
    *Si aucun modèle n'a été analysé, renvoie une chaîne vide.
    *Note: après un appel à fetch(), la propriété $html_result est le code HTML obtenu
    *avant les transformations imposées par la propriété $replace_after.
    *{@source 7 3}
    *@see replace_after
    *@see html_result
    *@return string
    **/
    
function fetch ( )
    {
        
// récupération du code HTML
        
if (FALSE===$this->html_result) {
            
$this->html_result '';
            
$this->parseTree($this->__tree,$this->__assigns);
        }
        
$html $this->html_result;
        
// post-parse: transforms
        
foreach ($this->replace_after as $from => $to$html str_replace($from,$to,$html);
        
// post-parse: callings
        
foreach ($this->call_after as $method) {
            if (
method_exists($this,$method)) $this->$method($html);
            else 
$this->error(TPHPL_ERR_METHOD_UNKNOWN$method);
        }
        return 
$html;
    }

    
/**
    *Affiche le résultat de l'analyse du modèle.
    *{@source 3 1}
    *@see fetch
    **/
    
function output ( )
    {
        echo 
$this->fetch();
    }

    
/**
    *Transforme les délimiteurs (par défaut '{' et '}') en leur équivalent HTML:
    *&#(code ascii);.
    *@param string $html texte à transformer
    *@return string
    **/
    
function htmlTransform $html )
    {
        
$html str_replace($this->__delim[0], '&#'.ord($this->__delim[0]).';'$html);
        
$html str_replace($this->__delim[1], '&#'.ord($this->__delim[1]).';'$html);
        return 
$html;
    }

    
/**
    *Analyse le modèle.
    *<code>$tpl->set('ici', 'là.');
    *$tpl->parseString('Mon modèle est {ici}');
    *$tpl->output();
    *Affiche: Mon modèle est là.</code>
    *Après l'appel à cette
    *@param string $str le modèle.
    *@return boolean
    **/
    
function parseString $str )
    {
        
$this->html_result FALSE;
        
// pre-parse: include sub-templates
        
if (!$this->__includeSubTemplates($str)) return FALSE;
        
// pre-parse: transform some elements
        
foreach ($this->replace_before as $from => $to$str str_replace($from,$to,$str);
        
// pre-parse: callings
        
foreach ($this->call_before as $method) {
            if (
method_exists($this,$method)) $this->$method($str);
            else 
$this->error(TPHPL_ERR_METHOD_UNKNOWN$method);
        }
        
// analyze structure of document
        
$this->__tree $this->__tree($str);
        return 
FALSE!==$this->__tree;
    }

    
/**
    *Gère les inclusions des sous modèles
    *@param string $str le modèle à modifier
    *@param array $includers les sous-modèles qui ont déjà été inclus dans ce modèle
    *@access private
    **/
    
function __includeSubTemplates ( &$str $includers = array() )
    {
        
$open preg_quote($this->__delim[0], '/');
        
$close preg_quote($this->__delim[1], '/');
        
$inc preg_quote($this->syntax['inc'], '/');
        
preg_match_all('/'.$open.$inc.':(.*?)'.$close.'/Ssi'$str$includesPREG_SET_ORDER);
        foreach (
$includes as $include) {
            
$file $this->findFile($include[1]);
            if (!
$file) {
                
$this->error(TPHPL_ERR_FILE_NOT_FOUND$include[1].' (to include)');
                return 
FALSE;
            }
            if (
in_array($file,$includers)) {
                
$this->error(TPHPL_ERR_INC_CYCLE$file);
                return 
FALSE;
            }
            
$contents implode(''file($file));
            
$includers[] = $file;
            
$this->__includeSubTemplates($contents$includers);
            
$str str_replace($include[0], $contents$str);
        }
        return 
TRUE;
    }

    
/**
    *Analyse le modèle contenu dans un fichier. Ce fichier sera recherché dans le répertoire
    *courant, puis dans tous les répertoires spécifiés dans la propriété $include_dir.
    *<code>Mon modèle tpl/modele.htm: .[{mavar}].
    *$tpl->include_dir .= ';tpl'; // on ajoute le rép. 'tpl' à $include_dir
    *$tpl->parseFile('modele.htm'); // le fichier est dans un des rép. d'$include_dir
    *$tpl->set('mavar', '-');
    *$tpl->output();
    *Affiche: .[-].</code>
    *@see include_dir
    *@param string $filename le nom de fichier.
    *@return boolean
    **/
    
function parseFile $filename )
    {
        
$full_filename $this->findFile($filename);
        
$this->filename $full_filename;
        if (
FALSE!==$full_filename) {
            
$html implode(''file($full_filename));
            return 
$this->parseString($html);
        }
        else {
            
$this->error(TPHPL_ERR_FILE_NOT_FOUND$filename);
            return 
FALSE;
        }
    }

    
/**
    *Analyse un modèle directement ou un fichier.
    *@param string $str le contenu du modèle, ou bien le nom du fichier
    *@param string $type le type: 'file' si c'est un fichier, 'str' si c'est une
    *chaîne de caractères directe.
    *{@source 3 7}
    **/
    
function parse $str $type 'file' )
    {
        if (
$type=='file') {
            return 
$this->parseFile($str);
        }
        elseif (
$type=='string' || $type=='str') {
            return 
$this->parseString($str);
        }
        return 
FALSE;
    }

    
/**
    *Renvoie la structure du modèle sous forme d'arbre syntaxique.
    *@param string $html le modèle
    *@return array l'arbre syntaxique
    *@access private
    **/
    
function __tree $html )
    {
        
$get_type array_flip($this->syntax);
        
$tokens = array();
        
$types '';
        foreach (
$this->__tags as $type$types .= preg_quote($this->syntax[$type],'/').'|';
        
$types substr($types0, -1);
        
$open preg_quote($this->__delim[0], '/');
        while (
preg_match('/^(.*?)'.$open.'('.$types.')\:(.*)$/Ssi',$html,$m)) {
            
$before $m[1];
            if (
$before!=''$tokens[] = array( 'type'=>'html' 'contents'=>$before );
            
$html $m[3];
            
$type $get_type[$m[2]];
            
$param $this->__findClosingBracket($html);
            
$contents $this->__findClosingTag($html,$type);
            
$tokens[] = array( 'type'=>$type 'param'=>$param 'contents'=>$this->__tree($contents) );
        }
        if (
$html!=''$tokens[] = array( 'type'=>'html' 'contents'=>$html );
        return 
$tokens;
    }

    
/**
    *Analyse un modèle sous sa forme d'arbre syntaxique. Cette méthode est généralement appelée
    *par la méthode parseStructureMaStructure d'une classe mère définissant une nouvelle
    *structure de bloc "MaStructure".
    *@param array $tree arbre syntaxique
    *@param array $assigns liste des assignements pour cette partie du modèle
    *@param string $path le chemin qui a mené ici (type "bloc1/bloc2")
    *@return boolean
    **/
    
function parseTree $tree $assigns $path '' )
    {
        foreach (
$tree as $child) {
            if (
$child['type']=='html') {
                
$this->html_result .= $this->__replaceVars($child['contents'], $assigns$path);
            }
            else {
                
$method 'parseStructure'.ucfirst($child['type']);
                if (!
method_exists($this,$method)) {
                    
$this->error(TPHPL_ERR_UNKNOWN_STRUCTURE$child['type']);
                    return 
FALSE;
                }
                if (!
$this->$method($child$assigns$path)) return FALSE;
            }
        }
        return 
TRUE;
    }

    
/**
    *Méthode d'analyse permettant d'implémenter la structure de bloc.
    *{@source 3 11}
    *@param array $tree arbre syntaxique
    *@param array $assigns liste des assignements pour cette partie du modèle
    *@param string $path le chemin qui a mené ici (type "bloc1/bloc2")
    *@return boolean
    **/
    
function parseStructureBlock $tree $assigns $path )
    {
        
$name $tree['param'];
        if (isset(
$assigns['.'.$name])) {
            
$i 0;
            foreach (
$assigns['.'.$name] as $assign) {
                
$new_path $path=='' $name $path.'/'.$name;
                
$assign[$this->var_tphpl_num] = ++$i;
                if (!
$this->parseTree($tree['contents'],$assign,$new_path))
                    return 
FALSE;
            }
        }
        return 
TRUE;
    }

    
/**
    *Effectue les remplacements imposés par la liste d'assignations.
    *@param string $html le modèle dans lequel faire les remplacements
    *@param array $assigns une liste d'assignations
    *@param le chemin qui a mené ici (type "bloc1/bloc2");
    *@return string le résultat des assignations
    *@access private
    **/
    
function __replaceVars $html $assigns $path )
    {
        foreach (
$assigns as $var => $val) if ($var{0}!='.') {
            
$html str_replace($this->__delim[0].$var.$this->__delim[1], $val$html);
        }
        
$bracket preg_quote($this->__delim[0], '/');
        if (
$this->delete_unset_vars) {
            while (
preg_match('/^(.*?)'.$bracket.'(.*)$/Ssi',$html,$m)) {
                
$before $m[1];
                
$after $m[2];
                
$this->__findClosingBracket($after);
                
$html $before.$after;
            }
        }
        return 
$html;
    }

    
/**
    *Extrait le texte jusqu'au tag fermant, en tenant compte des imbrications.
    *Après l'appel à cette fonction, $str ne contient plus que ce qui suit ce tag fermant.
    *@param string $str la chaine à analyser / modifier
    *@param int $depth la profondeur de départ
    *@param string $type le type de tag à rechercher (Obligatoire: $syntax[$type] et
    *$syntax['end'.$type] sont définis).
    *@return string le texte extrait
    *@access private
    **/
    
function __findClosingTag ( &$str $type $depth )
    {
        
$res '';
        
$open preg_quote($this->syntax[$type], '/');
        
$close preg_quote($this->syntax['end'.$type], '/');
        
$bracket preg_quote($this->__delim[0], '/');
        while (
preg_match('/^(.*?)('.$bracket.'(?:'.$open.'\:)|(?:'.$close.'))(.*)$/Ssi',$str,$m)) {
            
$res .= $m[1];
            if (
preg_match('/^'.$bracket.$open.'\:$/Ssi',$m[2])) $depth++;
            else 
$depth--;
            
$str $m[3];
            
$params $this->__findClosingBracket($str).$this->__delim[1];
            if (
$depth>0$res .= $m[2].$params;
            else break;
        }
        return 
substr($res,0,-1);
    }

    
/**
    *Extrait le texte jusqu'au délimiteur fermant, en tenant compte des imbrications.
    *Après l'appel à cette fonction, $str ne contient plus que ce qui suit ce délimiteur fermant.
    *@param string $str la chaine à analyser / modifier
    *@param int $depth la profondeur de départ
    *@return string le texte extrait
    *@access private
    **/
    
function __findClosingBracket ( &$str $depth $open FALSE $close FALSE )
    {
        
$res '';
        if (!
$open)  $open =  $this->__delim[0];
        if (!
$close$close $this->__delim[1];
        
$popen preg_quote($open'/');
        
$pclose preg_quote($close'/');
        while (
preg_match('/^(.*?)((?:'.$popen.')|(?:'.$pclose.'))(.*)$/Ssi',$str,$m)) {
            if (
$m[2]==$open)  $depth++;
            if (
$m[2]==$close$depth--;
            
$res .= $m[1] . ( $depth>$m[2] : '' );
            
$str $m[3];
            if (
$depth==0) break;
        }
        return 
$res;
    }

    
/**
    *Cherche un fichier dans le répertoire courant, puis dans les répertoires
    *défini par la propriété $include_dir.
    *@see include_dir
    *@param string $filename le nom du fichier
    *@return mixed le nom complet du fichire, ou FALSE en cas d'échec.
    **/
    
function findFile $filename )
    {
        
$dirs explode(';'$this->include_dir);
        
array_unshift($dirs'.');
        foreach (
$dirs as $dir) {
            
$dir trim($dir);
            if (
$dir!='') {
                
$full_name $dir.'/'.$filename;
                if (
file_exists($full_name)) return $full_name;
            }
        }
        return 
FALSE;
    }

    
/**
    *Lève l'erreur correspondant au code d'erreur donné.
    *Cette fonction peut prendre des paramètres supplémentaires qui seront alors
    *utilisé pour complété le message d'erreur.
    *<code>$tpl->setError(1000, 'Millième message, %1.');
    *$tpl->error(1000, 'grumpf');
    *Résultat: Warning: Millième message, grumpf. in XXX on line YYY.</code>
    *Note: après une levée d'erreur, les propriétés $last_error et $last_error_msg
    *sont définies.
    *@see setError
    *@see last_error
    *@see last_error_msg
    *@param int $type un code d'erreur (une des constantes TPHPL_ERR_*).
    **/
    
function error $type )
    {
        
$this->last_error $type;
        
$args func_get_args();
        
$msg call_user_func_array(array($this,'errorMessage'), $args);
        
$this->last_error_msg $msg;
        
trigger_error($msg$this->__errors[$type][1]);
    }

    
/**
    *Les message correspondant au code d'erreur donné.
    *Cette fonction peut prendre des paramètres supplémentaires qui seront alors
    *utilisé pour complété le message d'erreur.
    *<code>$tpl->setError(1000, 'Millième message, %1.');
    *echo $tpl->errorMessage(1000, 'grumpf');
    *Résultat: Millième message, grumpf.</code>
    *@see setError
    *@param int $type un code d'erreur (une des constantes TPHPL_ERR_*).
    *@return string le message d'erreur complété
    **/
    
function errorMessage $type )
    {
        
$msg $this->__errors[$type][0];
        
$args func_get_args();
        foreach (
$args as $i => $var$msg str_replace('%'.$i$var$msg);
        return 
$msg;
    }


}



/*****************************************************************************/



/**
*Extension officielle de BasicTemphplate implémentant les commandes et les structures
*conditionnelles. Il s'agit de la classe principale du moteur Temphplate.
*@package Temphplate
*@author Nicolas Chambrier <naholyr@yahoo.fr>
*@version 1.1
**/
class Temphplate extends BasicTemphplate
{


    
/**
    *Si cette propriété est mise à FALSE on déactive la reconnaissance des expressions conditionnelles
    *complexes. Cette désactivation ne permettra plus d'avoir des conditions du type a AND b,
    *{myvar}OR{var}={x}, etc... Mais vous permettra de récupérer quelques ressources.
    *@var boolean
    **/
    
var $allow_complex_conditions TRUE;

    
/**
    *Si cette propriété est mise à FALSE, on ignorera les éléments {else:} et {elseif:condition}.
    *Si vous êtes sûr que votre modèle n'utilise pas ces structures, vous pourrez gagner en performance
    *en choisissant cette option.
    *@var boolean
    **/
    
var $parse_else_and_elseif TRUE;

    
/**
    *On utilise un subterfuge pour l'insertion du temps de génération de la page. En effet, pour que cette
    *valeur soit réellement représentative, il faut l'attribuer tout à la fin du processus. Voilà pourquoi
    *le remplacement de {std:gen_time} se fait en X étapes:
    *1. on remplace {std:gen_time} par la propriété $var_gen_time.
    *2. on effectue tous les traitemens.
    *3. on surcharge la méthode fetch de façon à récupérer le contenu générer, et d'y remplacer les
    *occurences de $var_gen_time par le temps effectif passé entre l'étape 1 et 3.
    *Vous pouvez modifier cette valeur si vous l'utilisez dans vos pages.
    *@var string
    **/
    
var $var_gen_time '34.533ms'// '#'.md5('gen_time').'#';


    /**
    *Liste des commmandes déclarées.
    *@access private
    *@var array
    **/
    
var $__commands = array();

    
/**
    *Timestamp UNIX précis (secondes+microsecondes) affecté à la construction de l'objet.
    *@access private
    *@var float
    **/
    
var $__start_time;


    
/**
    *Constructeur
    *@param string $include_dir
    *@see BasicTemphplate::BasicTemphplate
    **/
    
function Temphplate $include_dir '' )
    {
        
parent::BasicTemphplate($include_dir);
        
// messages d'erreurs
        
$this->setError(TPHPL_ERROR_UNKNOWN_COMMAND'La commande "%1" est inconnue'E_USER_WARNING);
        
$this->setError(TPHPL_ERR_UNKNOWN_FUNCTION'La fonction "%1" est indéfinie'E_USER_WARNING);
        
$this->setError(TPHPL_ERR_COMMAND_DEFINED'La commande "%1" est déjà définie'E_USER_WARNING);
        
$this->setError(TPHPL_ERR_COMMAND_FAILED'La commande "%1" a échoué :  %2 'E_USER_ERROR);
        
$this->setError(TPHPL_ERR_UNDEF_OR_LOCAL'La variable "%1" est indéfinie ou n\'est pas globale'E_USER_NOTICE);
        
$this->setError(TPHPL_ERR_P_BOOLOP_EXPECTED'Opérateur booléen attendu : %1 'E_USER_ERROR);
        
// structures conditionnelles
        
$this->addStructure('if');
        
$this->syntax['else'] = 'else';
        
$this->syntax['elseif'] = 'elseif';
        
$this->call_before[] = '__preParseReplaceElseAndElseif';
        
// commandes prédéfinies
        
$this->addCommand('std',    '__tphpl_cmd');
        
$this->addCommand('alt',    '__tphpl_cmd');
        
$this->addCommand('get',    '__tphpl_cmd');
        
$this->addCommand('post',   '__tphpl_cmd');
        
$this->addCommand('glob',   '__tphpl_cmd');
        
$this->addCommand('date',   '__tphpl_cmd');
        
$this->addCommand('exec',   '__tphpl_cmd');
        
$this->addCommand('html',   '__tphpl_cmd');
        
$this->addCommand('parse',  '__tphpl_cmd');
        
$this->addCommand('urlenc''__tphpl_cmd');
        
$this->addCommand('strpad''__tphpl_cmd');
        
// temps de génération de la page
        
list($ms,$s) = explode(' ',microtime());
        
$this->__start_time = (float)$s + (float)$ms;
    }

    
/**
    *@return string Version de BasicTemphplate dont hérite Temphplate
    **/
    
function basic_version ( )
    {
        return 
parent::version();
    }

    
/**
    *@return string Version actuelle de la classe
    **/
    
function version ( )
    {
        return 
'1.1';
    }

    
/**
    *Ajoute une commande reconnue par le moteur.
    *<code>Modèle: {add:1:2}+{add:4:7}={add:1:2:4:7}.
    *function somme ( $tpl , $path , $params , &$success )
    *{
    *    // faire la somme des paramètres
    *    $command_name = array_shift($params); // on ignore le paramètre 0: "somme"
    *    $res = 0;
    *    foreach ($params as $param) $res += $param;
    *    return $res;
    *}
    *$tpl->addCommand('add', 'somme');
    *Résultat: 3+11=14</code>
    *@param string $command Nom de la commande à enregistrer. La syntaxe de la commande
    *sera {Commande:Param1:Param2:etc...}.
    *@param string $function Fonction de callback. Il s'agit d'une fonction prenant
    *4 paramètres $tpl (instance du moteur), $path (chemin de type "bloc1/bloc2" de
    *la commande exécutée), $params (paramètres passés à la commande, [0]=>Commande,
    *[1]=>Param1, [2]=>Param2, etc...), &$success (paramètre-résultat booléen indiquant
    *le succès de la commande) et qui renvoie une chaîne de caractères (message d'erreur
    *si $success est mis à FALSE, valeur de la commande sinon).
    *@return boolean
    **/
    
function addCommand $command $function )
    {
        
$command strtolower($command);
        if (!
function_exists($function)) {
            
$this->error(TPHPL_ERR_UNKNOWN_FUNCTION$function);
            return 
FALSE;
        }
        if (isset(
$this->__commands[$command])) {
            
$this->error(TPHPL_ERR_COMMAND_DEFINED$command);
            return 
FALSE;
        }
        
$this->__commands[$command] = array(
            
'command'   =>  $command,
            
'callback'  =>  $function
        
);
        return 
TRUE;
    }

    
/**
    *Méthode automatiquement appelée avant l'analyse syntaxique du modèle.
    *On remplace le modèle par son équivalent sans aucun bloc ELSE et ELSEIF.
    *Règles de remplacement:
    *<code>... {if:c1}t1{elseif:c2}t2{end:if} ... =>  ... {if:c1}t1{else:}{if:c2}t2{end:if}{end:if} ...
    *... {if:c1}t1{else:}t2{end:if} ...     =>  ... {if:c1}t1{end:if}{if:not c1}t2{end:if} ...</code>
    *@param string html le modèle
    *@access private
    **/
    
function __preParseReplaceElseAndElseif ( &$html )
    {
        if (
$this->parse_else_and_elseif) {
            
// add conditional blocks support: pre-parse to delete "else" and "elseif" in html code.
            // we only want 'if(condition)...endif' conditional blocks, which greatly ease analyze
            // tokenizing
            
$tokens $this->__condHtmlToTokens($html);
            
// replacing elseif and else
            
if (FALSE === ($tokens=$this->__condReplaceElseif($tokens))) return FALSE;
            if (
FALSE === ($tokens=$this->__condReplaceElse($tokens)))   return FALSE;
            
// merge resulting html, it's a template html code, with the expected structure
            
$html '';
            foreach (
$tokens as $token) {
                if (
is_array($token)) {
                    
$html .= $this->__delim[0].$this->syntax[$token[0]].':'.$token[1].$this->__delim[1];
                }
                else 
$html .= $token;
            }
        }
    }

    
/**
    *Applique la règle de transformation ELSE
    *@param array $tokens arbre syntaxique des structures conditionnelles du modèle
    *@return array arbre syntaxique des structures conditionnelles du modèle sans aucun bloc ELSE
    *@access private
    **/
    
function __condReplaceElse $tokens )
    {
        if (!
$this->__findFirstWithCorrespondingIf($tokens,'else',$found,$if)) {
            return 
FALSE;
        }
        if (
$found) {
            
// we found an 'else' at index $found, and its corresponding 'if' at index $if
            // replace
            
$before array_slice($tokens,0,$found);
            
$after array_slice($tokens,$found+1);
            
$endif = array('endif''');
            
$ifnot = array('if''!'.$tokens[$if][1]);
            
$tokens array_merge($before,array($endif,$ifnot),$after);
            
$tokens $this->__condReplaceElse($tokens);
        }
        return 
$tokens;
    }

    
/**
    *Applique la règle de transformation ELSE
    *@param array $tokens arbre syntaxique des structures conditionnelles du modèle
    *@return array arbre syntaxique des structures conditionnelles du modèle sans aucun bloc ELSE
    *@access private
    **/
    
function __condReplaceElseif $tokens )
    {
        if (!
$this->__findFirstWithCorrespondingIf($tokens,'elseif',$found,$if)) {
            return 
FALSE;
        }
        if (
$found) {
            
// we found an 'elseif' at index $found, and its corresponding 'if' at index $if
            // find contents of the elseif
            
$depth 1;
            
$last $found+1;
            
$size sizeof($tokens);
            while (
$last<$size && $depth>0) {
                
$token $tokens[$last];
                if (
is_array($token)) {
                    if (
$token[0]=='if'$depth++;
                    if (
$token[0]=='endif'$depth--;
                    if (
$depth==0) {
                        
$last--;
                        break;
                    }
                }
                
$last++;
            }
            
// replace
            
$cond $tokens[$found][1];
            
$before array_slice($tokens,0,$found);
            
$contents array_slice($tokens,$found+1,$last-$found);
            
$after array_slice($tokens,$last+1);
            
$elseif = array(array('else',''),array('if',$cond));
            
$end = array(array('endif',''));
            
$tokens array_merge($before,$elseif,$contents,$end,$after);
            
$tokens $this->__condReplaceElseif($tokens);
        }
        return 
$tokens;
    }

    
/**
    *Retrouver le IF qui correspond au ELSE ou ELSEIF ouvert.
    *@param array $tokens arbre syntaxique des structures conditionnelles du modèle
    *@param string $type type de structure conditionnelle dont on veut chercher le IF correspondant
    *(else ou elseif).
    *@param boolean $found paramètre-résultat
    *@param int $if paramètre-résultat
    *@return boolean
    *@access private
    **/
    
function __findFirstWithCorrespondingIf$tokens $type , &$found , &$if )
    {
        
// find first $type, with its corresponding 'if'
        
$found FALSE;
        
$path = array();
        foreach (
$tokens as $i => $token) {
            if (
is_array($token)) {
                if (
$token[0]=='if'$path[] = $i;
                if (
$token[0]=='endif'array_pop($path);
                if (
$token[0]==$type) {
                    if (
sizeof($path)==0) {
                        
$this->error(TPHPL_PARSE_ERROR'"'.$type.'" with no corresponding "if"');
                        return 
FALSE;
                    }
                    
$found $i;
                    break;
                }
            }
        }
        if (
$found) {
            
$if array_pop($path);
        }
        return 
TRUE;
    }

    
/**
    *Conversion du modèle en arbre syntaxique pour les structures conditionnelles.
    *@param string $html le modèle
    *@return array l'arbre syntaxique
    *@access private
    **/
    
function __condHtmlToTokens $html )
    {
        
$tokens = array();
        
$html $html;
        
$if preg_quote($this->syntax['if'],'/');
        
$else preg_quote($this->syntax['else'],'/');
        
$elseif preg_quote($this->syntax['elseif'],'/');
        
$endif preg_quote($this->syntax['endif'],'/');
        
$open preg_quote($this->__delim[0],'/');
        while (
preg_match('/'.$open.'((?:(?:'.$if.'|'.$else.'|'.$elseif.')\:)|(?:'.$endif.'\}))(.*)$/Ssi',$html,$matches)) {
            
// html before the conditional block
            
$tokens[] = substr($html0, -strlen($matches[0]));
            
// conditional block
            
$type substr($matches[1],0,-1);
            
$html $matches[0];
            switch (
$type) {
                case 
$this->syntax['if']:       $type 'if';       break;
                case 
$this->syntax['elseif']:   $type 'elseif';   break;
                case 
$this->syntax['else']:     $type 'else';     break;
                case 
$this->syntax['endif']:    $type 'endif';    break;
            }
            
// extracting condition
            
$depth 1;
            
$pos 1+strlen($this->syntax[$type]);
            
$cond '';
            while (
$depth>0) {
                
$chr $html{$pos++};
                if (
$chr==$this->__delim[0]) {
                    
$depth++;
                }
                elseif (
$chr==$this->__delim[1]) {
                    
$depth--;
                }
                if (
$depth>0$cond .= $chr;
            }
            if (
$type=='if' || $type=='elseif'$cond substr($cond,1);
            
$tokens[] = array($type,$cond);
            
$html substr($html,$pos);
        }
        
$tokens[] = $html;
        return 
$tokens;
    }

    
/**
    *Méthode d'analyse permettant d'implémenter la structure de bloc conditionnel.
    *{@source 3 4}
    *@param array $tree arbre syntaxique
    *@param array $assigns liste des assignements pour cette partie du modèle
    *@param string $path le chemin qui a mené ici (type "bloc1/bloc2")
    *@return boolean
    **/
    
function parseStructureIf $tree $assigns $path )
    {
        if (
$this->__trueCondition($tree['param'],$assigns,$path)) {
            return 
$this->parseTree($tree['contents'],$assigns,$path);
        }
        else return 
TRUE;
    }

    
/**
    *Surcharge de la méthode de remplacement de variables: on assigne d'abord
    *les résultats des commandes.
    *@param string $str le modèle dans lequel faire les remplacements
    *@param array $assigns une liste d'assignations
    *@param le chemin qui a mené ici (type "bloc1/bloc2");
    *@return string le résultat des assignations
    *@access private
    **/
    
function __replaceVars $str $assigns $path '' )
    {
        if (!
$this->__parseCommands($str,$assigns,$path)) return FALSE;
        return 
parent::__replaceVars($str,$assigns,$path);
    }

    
/**
    *Assignations des résultats des commandes
    *@param string $html paramètre-résultat, le modèle dans lequel faire les remplacements
    *@param array $assigns une liste d'assignations
    *@param le chemin qui a mené ici (type "bloc1/bloc2");
    *@return boolean
    *@access private
    **/
    
function __parseCommands ( &$html $assigns $path '' )
    {
        
$open preg_quote($this->__delim[0], '/');
        foreach (
$this->__commands as $exec) {
            
$command preg_quote($exec['command'],'/');
            
$foo $exec['callback'];
            while (
preg_match('/('.$open.$command.'\:)(.*)$/Ssi',$html,$matches)) {
                
$param $this->__findClosingBracket($matches[2]);
                
$search $this->__delim[0].$exec['command'].':'.$param.$this->__delim[1];
                if (
FALSE!==strpos($param,'{')) $param $this->__replaceVars($param,$assigns);
                
$params explode(':',$param);
                
$success TRUE;
                
array_unshift($params$command);
                
$res $foo($this$path$params$success);
                if (!
$success) {
                    
$this->error(TPHPL_ERR_COMMAND_FAILED$command$res);
                    return 
FALSE;
                }
                
$html str_replace($search,$res,$html);
            }
        }
        return 
TRUE;
    }

    
/**
    *@param string $cond la condition (chaîne dans laquelle on fera des remplacements
    *de variables)
    *@param array $assigns une liste d'assignations
    *@param string $path le chemin qui a mené ici (type "bloc1/bloc2");
    *@return boolean Valeur de la condition
    *@access private
    **/
    
function __trueCondition $cond $assigns $path '' )
    {
        if (
$this->allow_complex_conditions) {
            
$tree $this->__condExprTree($cond$assigns$path);
            return 
$this->__recTrueCondition($tree);
        }
        else {
            
$cond $this->__replaceVars($cond$assigns$path);
            return 
$this->__recTrueCondition($cond);
        }
    }

    
/**
    *@param mixed $cond la condition feuille (string) ou noeuf (array)
    *@return boolean la valeur de l'expression booléenne
    *@access private
    **/
    
function __recTrueCondition $cond )
    {
        if (
is_array($cond)) {
            
$left $this->__recTrueCondition($cond[1]);
            
$right $this->__recTrueCondition($cond[2]);
            return 
$cond[0]=='|' $left || $right $left && $right;
        }
        else {
            
// unary operator: not expr, !expr
            
$not FALSE;
            while (
preg_match('/^\s*((?:not\W)|(?:!.))(.*)$/Ssi'$cond$m)) {
                
$cond ltrim($m[1]{strlen($m[1])-1}.$m[2]);
                
$not = !$not;
            }
            if (
$cond=='not' || $cond=='!') {
                
$cond FALSE;
                
$not = !$not;
            }
            
// accepted operands: =, ==, <, >, <=, >=
            
if (preg_match('/^(.*)(=|==|<|>|<=|>=|<>|!=)(.*)$/',$cond,$m)) {
                switch (
$m[2]) {
                    case 
'=':
                    case 
'==':  $val $m[1] == $m[3];  break;
                    case 
'<':   $val $m[1] < $m[3];   break;
                    case 
'>':   $val $m[1] > $m[3];   break;
                    case 
'<=':  $val $m[1] <= $m[3];  break;
                    case 
'>=':  $val $m[1] >= $m[3];  break;
                    case 
'<>':
                    case 
'!=':  $val $m[1] != $m[3];  break;
                    default:    
$val FALSE;           break;
                }
            }
            else 
$val = (bool) $cond;
            return 
$not ? !$val $val;
        }
    }

    
/**
    *@param mixed $tree l'arbre syntaxique de l'expression booléenne
    *@param array $assigns une liste d'assignations
    *@param string $path le chemin qui a mené ici (type "bloc1/bloc2");
    *@access private
    **/
    
function __recReplaceVarsInCondExprTree ( &$tree $assigns $path )
    {
        if (
is_array($tree)) {
            
$this->__recReplaceVarsInCondExprTree($tree[1], $assigns$path);
            
$this->__recReplaceVarsInCondExprTree($tree[2], $assigns$path);
        }
        elseif (
strlen($tree)>&& $tree{0}=='(' && $tree{strlen($tree)-1}==')') {
            
$tree substr($tree1, -1);
            
$this->__recReplaceVarsInCondExprTree($tree$assigns$path);
        }
        else 
$tree $this->__replaceVars($tree$assigns$path);
    }

    
/**
    *@param string $cond expression booléenne
    *@param array $assigns une liste d'assignations
    *@param string $path le chemin qui a mené ici (type "bloc1/bloc2");
    *@return array
    *@access private
    **/
    
function __condExprTree $cond $assigns $path )
    {
        
$cond preg_replace('/&&|\bAND\b/i''&'$cond);
        
$cond preg_replace('/\|\||\bOR\b/i''|'$cond);
        
$this->__condExprOpTree($cond);
        
$this->__recReplaceVarsInCondExprTree($cond$assigns$path);
        return 
$cond;
    }

    
/**
    *@param mixed $cond expression conditionnelle. Après l'appel à cette fonction il s'agit
    *de l'arbre syntaxique de l'expression.
    *@param string $op '&' ou '|'. Il s'agit de l'opérateur non prioritaire.
    *@access private
    **/
    
function __condExprOpTree ( &$cond $op '|' )
    {
        if (
is_string($cond)) {
            
$cond trim($cond);
            if (
FALSE===strpos($cond,$op)) {
                if (
$op=='|'$this->__condExprOpTree($cond,'&');
                return;
            }
            if (
preg_match('/^\((.*)\)$/'$cond$m)) {
                
$tmp substr($cond,1);
                
$sub substr($cond1, -1);
                if (
$sub==$this->__findClosingBracket($tmp1'('')')) {
                    
$cond $sub;
                    
$this->__condExprOpTree($cond,$op);
                    return;
                }
            }
            
// on trouve le premier OP au niveau 0
            
$pop preg_quote($op'/');
            
$left '';
            
$res $cond;
            while (
preg_match('/^([^'.$pop.']*?)\(/'$res$m)) {
                
$left .= $m[1];
                
$res substr($resstrlen($m[1])+1);
                
$left .= '('.$this->__findClosingBracket($res1'('')').')';
            }
            if (
preg_match('/^(.*?)'.$pop.'(.*)$/'$res$m)) {
                
$left .= $m[1];
                
$right $m[2];
                
$this->__condExprOpTree($left,$op);
                
$this->__condExprOpTree($right,$op);
                
$cond = array($op$left$right);
            }
            elseif (
$op=='|' || $res=='') {
                
$this->__condExprOpTree($cond,$op=='|'?'&':'|');
            }
        }
        else {
            
$this->__condExprOpTree($cond[1],$op);
            
$this->__condExprOpTree($cond[2],$op);
        }
    }

    
/**
    *Renvoie le résultat de l'analyse du modèle.
    *<code>Modèle mavar.htm: ...{mavar}...
    *Code complet:
    *$tpl =& new BasicTemphplate;
    *$tpl->set('mavar', 'coucou');
    *$tpl->parse('mavar.htm');
    *$str = $tpl->fetch();
    *Contenu de $str: ...coucou...</code>
    *Si aucun modèle n'a été analysé, renvoie une chaîne vide.
    *La mise en cache s'effectue à ce moment.
    *@return string
    **/
    
function fetch ( )
    {
        
$res parent::fetch();
        
// calculate generation time
        
list($ms,$s) = explode(' ',microtime());
        
$end_time = (float)$s + (float)$ms;
        
$time $end_time $this->__start_time;
        
$unit 's';
        if (
$time<1) { $time *= 1000$unit 'ms'; }
        if (
$time<1) { $time *= 1000$unit 'us'; }
        
$res str_replace($this->var_gen_timeround($time,3).$unit$res);
        return 
$res;
    }


}



/*****************************************************************************/



/**
*Méthode gérant les commandes prédéfinis de Temphplate
*@access private
**/
function __tphpl_cmd ( &$tpl $path $params , &$success )
{
    
$cmd array_shift($params);
    switch (
strtolower($cmd)) {

        case 
'std':
            
$param implode(':',$params);
            switch (
strtolower($param)) {
                case 
'version':             return $tpl->version();
                case 
'basic_version':       return $tpl->basic_version();
                case 
'phpversion':          return phpversion();
                case 
'now':                 return time();
                case 
'template_basename':   return basename($tpl->filename);
                case 
'template_dirname':    return dirname($tpl->filename);
                case 
'template':            return $tpl->filename;
                case 
'script_basename':     return basename(getenv('SCRIPT_NAME'));
                case 
'script_dirname':      return dirname(getenv('SCRIPT_NAME'));
                case 
'script':              return getenv('SCRIPT_NAME');
                case 
'gen_time':            return $tpl->var_gen_time;
                default: 
$success FALSE;  return 'std:'.$param.' is undefined';
            }

        case 
'get':
            
$param implode(':',$params);
            return isset(
$_GET[$param]) ? $_GET[$param] : '';

        case 
'post':
            
$param implode(':',$params);
            return isset(
$_POST[$param]) ? $_GET[$param] : '';

        case 
'glob':
            
$param implode(':',$params);
            if (!isset(
$GLOBALS[$param])) {
                
$success FALSE;
                return 
$tpl->errorMessage(TPHPL_ERR_UNDEF_OR_LOCAL$param);
            }
            else return 
''.$GLOBALS[$param];

        case 
'date':
            
$time array_shift($params);
            
$format implode(':',$params);
            if (
preg_match('/(\d+)-(\d+)-(\d+)(?:(\d+)-(\d+)(?:-(\d+))?)?/'$time$m)) {
                
$year = (int) $m[1];
                
$month = (int) $m[2];
                
$day = (int) $m[3];
                if (isset(
$m[4])) {
                    
$hour = (int) $m[4];
                    
$minute = (int) $m[5];
                    
$second = isset($m[6]) ? (int)$m[6] : 0;
                }
                else {
                    
$hour 0;
                    
$minute 0;
                    
$second 0;
                }
                
$time mktime($hour$minute$second$month$day$year);
            }
            elseif (
preg_match('/^\d+$/',$time)) {
                
$time = (int) $time;
            }
            else {
                
$time FALSE;
            }
            
$date FALSE!==$time date($format$time) : date($format);
            return 
$date;

        case 
'exec':
            
$filename $tpl->findFile($params[0]);
            if (
FALSE!==$filename) {
                return 
__tphpl_execphp($filename,$success);
            }
            else {
                
$success FALSE;
                return 
$tpl->errorMessage(TPHPL_ERR_FILE_NOT_FOUND$params[0].' (to exec)');
            }

        case 
'parse':
            
$old_filename $tpl->filename;
            if (
$success $tpl->parseFile($params[0])) {
                
$res $tpl->fetch();
            }
            else 
$res $tpl->last_error_message;
            
$tpl->filename $old_filename;
            return 
$res;

        case 
'html':
            return 
htmlentities(implode(':',$params));

        case 
'urlenc':
            return 
urlencode(implode(':',$params));

        case 
'strpad':
            
$str = isset($params[0])&&$params[0]!='' $params[0] : '';
            
$len = isset($params[1])&&$params[1]!='' ? (int)$params[1] : 0;
            
$pad = isset($params[2])&&$params[2]!='' $params[2] : '&nbsp;';
            
$type = isset($params[3])&&$params[3]!='' $params[3] : 'left';
            switch (
$type) {
                case 
'left':  $type STR_PAD_LEFT;  break;
                case 
'right'$type STR_PAD_RIGHT; break;
                case 
'both':  $type STR_PAD_BOTH;  break;
                default:      
$type STR_PAD_LEFT;  break;
            }
            return 
str_pad($str$len$pad$type);

        case 
'alt':
            
$alternator_name 'tphpl-alt:'.implode(':',$params);
            if (isset(
$GLOBALS[$alternator_name])) {
                
$alternator =& $GLOBALS[$alternator_name];
            }
            else {
                
$alternator =& new TphplAlternator;
                foreach (
$params as $param$alternator->addValue($param);
                
$GLOBALS[$alternator_name] =& $alternator;
            }
            
$GLOBALS['tphpl-lastalternator'] =& $alternator;
            return 
$alternator->get();

        default:
            
$success FALSE;
            return 
$tpl->errorMessage(TPHPL_ERROR_UNKNOWN_COMMAND$cmd);

    }
    return 
'';
}



/*****************************************************************************/



/**
*Exécuter un script PHP dans un environnement restreint
*@access private
*@return string La sortie résultant de l'exécution du script
*@param string $TPHPfilename le nom du script à exécuter
*@param boolean $TPHPLsuccess vaut FALSE en cas d'erreur
**/
function  __tphpl_execphp $TPHPLfilename , &$TPHPLsuccess )
{
    
ob_start();
    include 
$TPHPLfilename;
    
$result ob_get_contents();
    
ob_end_clean();
    return 
$result;
}



/*****************************************************************************/



/**
*Classe permettant de simplifier l'alternance de valeurs. Très utile pour
*alterner les couleurs de lignes dans une table par exemple. C'est cette
*classe qui est utilisée dans la commande prédéfinie {alt} de Temphplate.
*<code>$alt = new TphplAlternator(1, 2, 3);
*echo $alt->get();
*echo $alt->get();
*echo $alt->get();
*echo $alt->get();
*Résultat: 1231.</code>
*@package Temphplate
*@author Nicolas Chambrier <naholyr@yahoo.fr>
*@see Temphplate
*@version 1.0
**/
class TphplAlternator
{


    
/**
    *Valeurs à alterner
    *@access private
    *@var array
    **/
    
var $__values;

    
/**
    *Index max = sizeof({@link __values})-1
    *@access private
    *@var int
    **/
    
var $__max_index;

    
/**
    *Index en cours. C'est la valeur ayant cet index qui est retournée par {@link get}
    *@access private
    *@var int
    **/
    
var $__index;

    
/**
    *Dernière valeur retournée par {@link get}
    *@access private
    *@var mixed
    **/
    
var $__last_value;


    
/**
    *Constructeur. Peut prendre un nombre indéfini d'arguments, qui seront autant
    *de valeurs à alterner.
    **/
    
function TphplAlternator ( )
    {
        
$this->__index 0;
        
$this->__max_index = -1;
        
$this->__last_value NULL;
        
$args func_get_args();
        foreach (
$args as $arg$this->addValue($arg);
    }

    
/**
    *Ajoute une valeur d'alternance en fin de file.
    *<code>$alt = new TphplAlternator(1,2);
    *$alt->get(); // 1, prochain attendu: 2
    *$alt->get(); // 2, prochain attendu: 1
    *$alt->addValue(3);
    *$alt->get(); // 1, prochain attendu: 2
    *$alt->get(); // 2, prochain attendu: 3
    *$alt->get(); // 3, prochain attendu: 1
    *etc...</code>
    *@param mixed $value la valeur à ajouter
    **/
    
function addValue $value )
    {
        
$this->__values[] = $value;
        
$this->__max_index++;
    }

    
/**
    *Renvoie la valeur courante, et passe à la valeur suivante.
    *<code>$alt = new TphplAlternator(1,2,3);
    *$alt->get(); // 1, prochain attendu: 2
    *$alt->get(FALSE); // 2, prochain attendu: 2
    *$alt->get(); // 2, prochain attendu: 3
    *etc...</code>
    *@param boolean $next Si ce paramètre vaut FALSE, on ne passe pas à la valeur suivante.
    *le prochain appel à get renverra la même valeur.
    **/
    
function get $next TRUE )
    {
        if (
$this->__max_index==-1) die('No value !');
        
$res $this->__values[$this->__index];
        if (
$next) {
            
$this->__index++;
            if (
$this->__index>$this->__max_index$this->reset();
        }
        
$this->__last_value $res;
        return 
$res;
    }

    
/**
    *@return mixed la dernière valeur renvoyée par un appel à get.
    *@see get
    **/
    
function lastValue ( )
    {
        return 
$this->__last_value;
    }

    
/**
    *Remet à 0 l'alternance des valeurs
    *<code>$alt = new TphplAlternator(1,2,3);
    *$alt->get(); // 1, prochain attendu: 2
    *$alt->get(); // 2, prochain attendu: 3
    *$alt->reset();
    *$alt->get(); // 1, prochain attendu: 2
    *$alt->get(); // 2, prochain attendu: 3
    *etc...</code>
    **/
    
function reset ()
    {
        
$this->__index 0;
    }

    
/**
    *@return int le nombre de valeurs ajoutées
    **/
    
function nbValues ( )
    {
        return 
$this->__max_index+1;
    }


}



?>


Generation time: 34.533ms