Conditional text blocks in templates

Ideen für neue Funktionen in CONTENIDO?
Antworten
standingstone
Beiträge: 2
Registriert: Mi 25. Aug 2004, 00:48
Wohnort: Munich
Kontaktdaten:

Conditional text blocks in templates

Beitrag von standingstone » Fr 9. Feb 2007, 19:39

Hi all,

Ich stelle meine Frage in Englisch damit ich mich nicht zu sehr blamiere :). Nach dem ich aber die Funktionalität sowie so brauche habe ich auch gleich eine geänderte class.template.php beigefügt mit der entsprechende Erweiterung. Ich muss noch die regex Beschreibung anpassen nach dem es endlich funktioniert - morgen weiß ich wieder nicht warum es überhaupt funktioniert (siehe templateParseConditionalsRecursive und variable $regex).

Grüße, Simon

I often have the situation that I have content which only should be displayed if one of the variables has been set. An example would be to display a small icon of a telephone if a record contains a telephone number, otherwise not to display anything. Currently it is necessary to code this in php and and insert this as needed to the template as a variable.

What I have coded is the following:

Consider the following template:

Code: Alles auswählen

Conditional statustrainer <!-- CONDITIONAL statustrainer -->evaluated to true<!-- /CONDITIONAL --><br>
Conditional statusteacher <!-- CONDITIONAL statusteacher -->evaluated to true<!-- /CONDITIONAL --><br>
Conditional statusgroup <!-- CONDITIONAL statusgroup -->evaluated to true<!-- /CONDITIONAL --><br>
depending on the content of the variables statustrainer, statusteacher, statusgroup the according text segments are included or not.

setting statusteacher to "true" (or in fact any non empty string) results in:

Code: Alles auswählen

Conditional statustrainer <br>
Conditional statusteacher evaluated to true<br>
Conditional statusgroup <br>
Thanks to preg_replace_callback it is also possible to nest conditionals inside each other:

Code: Alles auswählen

Conditional statustrainer <!-- CONDITIONAL statustrainer -->evaluated to true<br>
----------> Conditional statusteacher <!-- CONDITIONAL statusteacher -->evaluated to true<br>
---------------------> Conditional statusgroup <!-- CONDITIONAL statusgroup -->evaluated to true<br>
            <!-- /CONDITIONAL -->
        <!-- /CONDITIONAL -->
    <!-- /CONDITIONAL -->
Warning / Achtung

I use this code standalone at the moment so it is not possible to just drop this onto the contenido distribution. My hope is that this or something similar could be included in an official release.

Here is my (slightly castrated) class.template.php file an example template file for testing and example php code:

Code: Alles auswählen

<?php

/* The following template class is based almost entirely on the template class used in contenido */
/* Modified by Simon Woods */

/*****************************************
*
* $Id: class.template.php,v 1.17 2005/08/30 09:24:34 timo.hummel Exp $
*
* File      :   $RCSfile: class.template.php,v $
* Project   :   Contenido
* Descr     :   Contenido Template Engine
*
* Author    :   $Author: timo.hummel $
* Modified  :   $Date: 2005/08/30 09:24:34 $
*
* © four for business AG, www.4fb.de
******************************************/


/**
 * templateConditionalsArray
 * @array
 */
$templateConditionalsArray;

/**
 * class Template
 *
 * Light template mechanism
 *
 * @author Jan Lengowski <Jan.Lengowski@4fb.de>
 * @copyright four for business <http://www.4fb.de>
 * @author Stefan Jelner (Optimizations)
 * @version 1.0
 */
class Template
{
	/**
	 * Needles (static)
	 * @var array
	 */
	var $needles = array ();

	/**
	 * Replacements (static)
	 * @var array
	 */
	var $replacements = array ();

	/**
	 * Conditionals (static)
	 * @var array
	 */
	var $conditionals = array ();

	/**
	 * Dyn_Needles (dynamic)
	 * @var array
	 */
	var $Dyn_needles = array ();

	/**
	 * Dyn_Replacements (dynamic)
	 * @var array
	 */
	var $Dyn_replacements = array ();

	/**
	 * Dyn_Conditionals (dynamic)
	 * @var array
	 */
	var $Dyn_conditionals = array ();

	/**
	 * Template cache
	 * @var array
	 */
	var $tplcache;

	/**
	 * Template name cache
	 * @var array
	 */
	var $tplnamecache;

	/**
	 * Dynamic counter
	 * @var int
	 */
	var $dyn_cnt = 0;

	/**
	 * Tags array (for dynamic blocks);
	 * @var array
	 */
	var $tags = array ('static' => '{%s}', 'start' => '<!-- BEGIN:BLOCK -->', 'end' => '<!-- END:BLOCK -->');

	/**
	 * Constructor function
	 * @return void
	 */
	function Template($tags = false)
	{
		$this->tplcache = Array ();
		$this->tplnamecache = Array ();

		if (is_array($tags))
		{
			$this->tags = $tags;
		}
		
		$this->setEncoding("");
        $this->setDomain("contenido");		
	} // end function

    /**
     * setDomain
     *
     * Sets the gettext domain to use for translations in a template
     *
	 * @param $sDomain	string	Sets the domain to use for template translations
     * @return none
     */    
    function setDomain ($sDomain)
    {
    	$this->_sDomain = $sDomain;
    }
    
	/**
	 * Set Templates placeholders and values
	 *
	 * With this method you can replace the placeholders
	 * in the static templates with dynamic data.
	 *
	 * @param $which String 's' for Static or else dynamic
	 * @param $needle String Placeholder
	 * @param $replacement String Replacement String
	 *
	 * @return void
	 */
	function set($which = 's', $needle, $replacement)
	{
		if ($which == 's')
		{ // static
			$this->needles[] = sprintf($this->tags['static'], $needle);
			$this->replacements[] = $replacement;
			if (strlen($replacement) > 0)
			{
			    $this->conditionals[$needle] = true;
			}

		} else
		{ // dynamic
			$this->Dyn_needles[$this->dyn_cnt][] = sprintf($this->tags['static'], $needle);
			$this->Dyn_replacements[$this->dyn_cnt][] = $replacement;
			if (strlen($replacement) > 0)
			{
			    $this->Dyn_conditionals[$this->dyn_cnt][$needle] = true;
			}

		}
	}

    /**
     * Sets an encoding for the template's head block.
     *
     * @param $encoding string Encoding to set
     */    
    function setEncoding ($encoding)
    {
    	$this->_encoding = $encoding;
    }
    
	/**
	 * Iterate internal counter by one
	 *
	 * @return void
	 */
	function next()
	{
		$this->dyn_cnt++;
	}

	/**
	 * Reset template data
	 *
	 * @return void
	 */
	function reset()
	{
		$this->dyn_cnt = 0;
		$this->needles = array ();
		$this->replacements = array ();
		$this->conditionals = array ();
		$this->Dyn_needles = array ();
		$this->Dyn_replacements = array ();
		$this->Dyn_conditionals = array ();
	}

	/**
	 * Generate the template and
	 * print/return it. (do translations sequentially to save memory!!!)
	 *
	 * @param $template string/file Template
	 * @param $return bool Return or print template
	 * @param $note bool Echo "Generated by ... " Comment
	 *
	 * @return string complete Template string
	 */
	function generate($template, $return = 0, $note = 1)
	{
		//check if the template is a file or a string
		if (!@ file_exists($template))
			$content = & $template; //template is a string (it is a reference to save memory!!!)
		else
			$content = implode("", file($template)); //template is a file

		$content = (($note) ? "<!-- Generated by Contenido Template file -->\n" : "").$content;

		$pieces = array();
		
		//if content has dynamic blocks
		if (preg_match("/^.*".preg_quote($this->tags['start'], "/").".*?".preg_quote($this->tags['end'], "/").".*$/s", $content))
		{
			//split everything into an array
			preg_match_all("/^(.*)".preg_quote($this->tags['start'], "/")."(.*?)".preg_quote($this->tags['end'], "/")."(.*)$/s", $content, $pieces);
			//safe memory
			array_shift($pieces);
			$content = "";
			//now combine pieces together

			//start block
			
			// Check for conditional blocks
			$pieces[0][0] = $this->parseConditionals($pieces[0][0], $this->conditionals);
			
			// Now replace the variables
			$pieces[0][0] = str_replace($this->needles, $this->replacements, $pieces[0][0]);
			$this->replacei18n($pieces[0][0], "i18n");
			$this->replacei18n($pieces[0][0], "trans");
			$content .= $pieces[0][0];
			unset ($pieces[0][0]);
			
			//generate dynamic blocks
			for ($a = 0; $a < $this->dyn_cnt; $a ++)
			{
        			// Check for conditional blocks
        			$temp = $this->parseConditionals($pieces[1][0], $this->Dyn_conditionals[$a]);
        			
				$temp = str_replace($this->Dyn_needles[$a], $this->Dyn_replacements[$a], $temp);
				$this->replacei18n($temp, "i18n");
				$this->replacei18n($temp, "trans");
				$content .= $temp;
			}
			unset ($temp);

			//end block

			// Check for conditional blocks
			$pieces[2][0] = $this->parseConditionals($pieces[2][0], $this->conditionals);
			
			$pieces[2][0] = str_replace($this->needles, $this->replacements, $pieces[2][0]);
			$this->replacei18n($pieces[2][0], "i18n");
			$this->replacei18n($pieces[2][0], "trans");
			$content .= $pieces[2][0];
			unset ($pieces[2][0]);
		} else
		{
			$content = str_replace($this->needles, $this->replacements, $content);
			$this->replacei18n($content, "i18n");
			$this->replacei18n($content, "trans");
		}

        if ($this->_encoding != "")
        {
        	$content = str_replace("</head>", '<meta http-equiv="Content-Type" content="text/html; charset='.$this->_encoding.'">'."\n".'</head>', $content);
        }
        
		if ($return)
			return $content;
		else
			echo $content;

	} # end function

	/**
	 * replacei18n()
	 *
	 * Replaces a named function with the translated variant
	 *
	 * @param $template string Contents of the template to translate (it is reference to save memory!!!)
	 * @param $functionName string Name of the translation function (e.g. i18n)
	 */
	function replacei18n(& $template, $functionName)
	{
	    
	    /*
		//if template contains functionName + parameter store all matches
		preg_match_all("/".preg_quote($functionName, "/")."\\(([\\\"\\'])(.*?)\\1\\)/s", $template, $matches);
		$matches = array_values(array_unique($matches[2]));
		for ($a = 0; $a < count($matches); $a ++)
		{
			$template = preg_replace("/".preg_quote($functionName, "/")."\\([\\\"\\']".preg_quote($matches[$a], "/")."[\\\"\\']\\)/s", i18n($matches[$a], $this->_sDomain), $template);
		}
            */
	}
	
	/**
	 * parseConditionals()
	 *
	 * Prepare the conditionals list prior to calling the templateParseConditionalsRecursive function
	 * Conditional blocks are defined by <!-- CONDITIONAL varname --> this is the text <!-- /CONDITIONAL -->
	 * Currently if a variable is defined and is non empty the condition evaluates to true.
	 * Defined but empty variables evaluate to false
	 * Static variable are only evaluated outside BLOCK statements, dynamic variables only in BLOCK statements
	 *
	 * @param $input string Text part which is to be searched for conditional blocks (it is reference to save memory!!!)
	 * @param $conditionals array Associative array of conditionals
	 */
	function parseConditionals(& $input, $conditionals)
	{
	    // Ideally we would store this information in a static varibale in the class
	    // and read this in the templateParseConditionalsRecursive static function. Was however
	    // unable to get this to work in php4 (should work in php5 though).
	    global $templateConditionalsArray;
	    
	    $templateConditionalsArray = $conditionals;
	    
	    return($this->templateParseConditionalsRecursive($input));
	}

	
        /**
         * templateParseConditionalsRecursive()
         *
         * Process the conditional blocks in the text piece. Conditional blocks are removed if the variables are not set
         *
         * @param $input string Text part which is to be searched for conditional blocks
         */
        function templateParseConditionalsRecursive($input)
        {
            global $templateConditionalsArray;
            
            # I keep forgetting how this works so here again as a reminder
            # <!--\ CONDITIONAL\ (\w*)\ --> - This is the start of a match string with a word in the middle which is put in [1]
            # The next bit is ((?:[^<]|<(?!<!--\ /?CONDITIONAL\ -->])|(?R))+) which gets put in [2] for further processing
            # <!--\ /CONDITIONAL\ --> is then the end tag
            # The trick is in the middle bit to allow nested blocks but to stop when the close block is present
            #
            # To break down (?:[^<]|<(?!<!--\ /?CONDITIONAL\ -->])|(?R))+)
            # ( (?:   [^<]   |    <  (?!<!--\ /?CONDITIONAL\ -->])  |  (?R)  )+  )
            #
            #
            $regex = '#<!--\sCONDITIONAL\s(\w*)\s-->((?:[^<]|<(?!!--\s/?CONDITIONAL(\s\w*)?\s-->)|(?R))+)<!--\s/CONDITIONAL\s-->#';
            
            if (is_array($input))
            {
                // [0] is the unfiltered input
                // [1] is the conditional variable name
                // [2] is the body inside the conditional
                // If the variable specified in the conditional exists and has content then we include the inner html
                // Otherwise it is ignored
                
                if (isset($templateConditionalsArray[ltrim($input[1])]))
                {
                    $input = $input[2];
                }
                else
                {
                    $input = "";
                }
            }
        
            return preg_replace_callback($regex, Array('Template', 'templateParseConditionalsRecursive'), $input);
        }
	
} # end class

?>

Code: Alles auswählen

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
 <title>Test Template</title>
</head>
<body>
    

<!-- Template: TestTemplate -->

Test sequential conditionals with no text in between : <br>
<!-- CONDITIONAL statustrainer -->Conditional statustrainer evaluated to true<br><!-- /CONDITIONAL --><!-- CONDITIONAL statusteacher -->Conditional statusteacher evaluated to true<br><!-- /CONDITIONAL -->
<!-- CONDITIONAL statusgroup -->Conditional statusgroup evaluated to true<br><!-- /CONDITIONAL -->
<br>

Test sequential conditionals with text in between : <br>
Conditional statustrainer <!-- CONDITIONAL statustrainer -->evaluated to true<!-- /CONDITIONAL --><br>
Conditional statusteacher <!-- CONDITIONAL statusteacher -->evaluated to true<!-- /CONDITIONAL --><br>
Conditional statusgroup <!-- CONDITIONAL statusgroup -->evaluated to true<!-- /CONDITIONAL --><br>
<br>

Test nested conditionals with text in between : <br>
Conditional statustrainer <!-- CONDITIONAL statustrainer -->evaluated to true<br>
----------> Conditional statusteacher <!-- CONDITIONAL statusteacher -->evaluated to true<br>
---------------------> Conditional statusgroup <!-- CONDITIONAL statusgroup -->evaluated to true<br>
            <!-- /CONDITIONAL -->
        <!-- /CONDITIONAL -->
    <!-- /CONDITIONAL -->



<!-- BEGIN:BLOCK -->
<!-- END:BLOCK -->

</body>
</html>

Example php code:

Code: Alles auswählen

<?php
require_once($_SERVER["DOCUMENT_ROOT"] . "/somewhere_over_the_rainbow/class.template.php");


$testTemplate = new Template;
$testTemplate->reset();

$testTemplate->set("s", "statustrainer", "true");
$testTemplate->set("s", "statusteacher", "true");
// $testTemplate->set("s", "statusgroup", "true");

echo $testTemplate->generate("testTemplate.html", 1);

echo "<br>";

?>

Dodger77
Beiträge: 3626
Registriert: Di 12. Okt 2004, 20:00
Wohnort: Voerde (Niederrhein)
Kontaktdaten:

Beitrag von Dodger77 » Fr 9. Feb 2007, 19:57

So was ähnliches hat es schonmal gegeben:

http://www.contenido.org/forum/viewtopi ... t=template

Aber integriert werden muss das ja nicht zwangsläufig. Man kann ja einfach in den verwendeten Modulen die eigene Klasse inkludieren anstatt der mitgelieferten.

Antworten