Attributes.php 7.78 KB
<?php
/**
 * PHPTAL templating engine
 *
 * PHP Version 5
 *
 * @category HTML
 * @package  PHPTAL
 * @author   Laurent Bedubourg <lbedubourg@motion-twin.com>
 * @author   Kornel Lesiński <kornel@aardvarkmedia.co.uk>
 * @license  http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
 * @version  SVN: $Id: Attributes.php 671 2009-07-11 18:11:35Z kornel $
 * @link     http://phptal.org/
 */


/**
 * TAL Specifications 1.4
 *
 *       argument             ::= attribute_statement [';' attribute_statement]*
 *       attribute_statement  ::= attribute_name expression
 *       attribute_name       ::= [namespace ':'] Name
 *       namespace            ::= Name
 *
 * examples:
 *
 *      <a href="/sample/link.html"
 *         tal:attributes="href here/sub/absolute_url">
 *      <textarea rows="80" cols="20"
 *         tal:attributes="rows request/rows;cols request/cols">
 *
 * IN PHPTAL: attributes will not work on structured replace.
 *
 * @package PHPTAL
 * @subpackage Php.attribute.tal
 * @author Laurent Bedubourg <lbedubourg@motion-twin.com>
 */
class PHPTAL_Php_Attribute_TAL_Attributes
extends PHPTAL_Php_Attribute
implements PHPTAL_Php_TalesChainReader
{
    /** before creates several variables that need to be freed in after */
    private $vars_to_recycle = array();
    
    /**
     * value for default keyword
     */
    private $_default_escaped;
    
    public function before(PHPTAL_Php_CodeWriter $codewriter)
    {
        // split attributes using ; delimiter
        $attrs = $codewriter->splitExpression($this->expression);
        foreach($attrs as $exp) {
            list($qname, $expression) = $this->parseSetExpression($exp);
            if ($expression) {
                $this->prepareAttribute($codewriter, $qname, $expression);
            }
        }
    }

    private function prepareAttribute(PHPTAL_Php_CodeWriter $codewriter, $qname, $expression)
    {
        $tales_code = $this->extractEchoType($expression);
        $code = $codewriter->evaluateExpression($tales_code);

        // XHTML boolean attribute does not appear when empty or false
        if (PHPTAL_Dom_Defs::getInstance()->isBooleanAttribute($qname)) {
            
            // I don't want to mix code for boolean with chained executor
            // so compile it again to simple expression
            if (is_array($code)) {
                $code = PHPTAL_Php_TalesInternal::compileToPHPExpression($tales_code);
            }
            return $this->prepareBooleanAttribute($codewriter, $qname, $code);
        }

        // if $code is an array then the attribute value is decided by a
        // tales chained expression
        if (is_array($code)) {
            return $this->prepareChainedAttribute($codewriter, $qname, $code);
        }

        // i18n needs to read replaced value of the attribute, which is not possible if attribute is completely replaced with conditional code
        if ($this->phpelement->hasAttributeNS('http://xml.zope.org/namespaces/i18n', 'attributes')) {
            $this->prepareAttributeUnconditional($codewriter, $qname, $code);
        }
        else {
            $this->prepareAttributeConditional($codewriter, $qname, $code);
        }
    }

    /**
     * attribute will be output regardless of its evaluated value. NULL behaves just like "".
     */
    private function prepareAttributeUnconditional(PHPTAL_Php_CodeWriter $codewriter, $qname, $code)
    {
        // regular attribute which value is the evaluation of $code
        $attkey = $this->getVarName($qname, $codewriter);
        if ($this->_echoType == PHPTAL_Php_Attribute::ECHO_STRUCTURE)
            $value = $codewriter->stringifyCode($code);
        else
            $value = $codewriter->escapeCode($code);
        $codewriter->doSetVar($attkey, $value);
        $this->phpelement->getOrCreateAttributeNode($qname)->overwriteValueWithVariable($attkey);
    }

    /**
     * If evaluated value of attribute is NULL, it will not be output at all.
     */
    private function prepareAttributeConditional(PHPTAL_Php_CodeWriter $codewriter, $qname, $code)
    {
        // regular attribute which value is the evaluation of $code
        $attkey = $this->getVarName($qname, $codewriter);

        $codewriter->doIf("NULL !== ($attkey = ($code))");

        if ($this->_echoType !== PHPTAL_Php_Attribute::ECHO_STRUCTURE)
            $codewriter->doSetVar($attkey, $codewriter->str(" $qname=\"").".".$codewriter->escapeCode($attkey).".'\"'");
        else
            $codewriter->doSetVar($attkey, $codewriter->str(" $qname=\"").".".$codewriter->stringifyCode($attkey).".'\"'");

        $codewriter->doElse();
        $codewriter->doSetVar($attkey, "''");
        $codewriter->doEnd('if');

        $this->phpelement->getOrCreateAttributeNode($qname)->overwriteFullWithVariable($attkey);
    }

    private function prepareChainedAttribute(PHPTAL_Php_CodeWriter $codewriter, $qname, $chain)
    {
        $this->_default_escaped = false;
        $this->_attribute = $qname;
        if ($default_attr = $this->phpelement->getAttributeNode($qname)) {
            $this->_default_escaped = $default_attr->getValueEscaped();
        }
        $this->_attkey = $this->getVarName($qname, $codewriter);
        $executor = new PHPTAL_Php_TalesChainExecutor($codewriter, $chain, $this);
        $this->phpelement->getOrCreateAttributeNode($qname)->overwriteFullWithVariable($this->_attkey);
    }

    private function prepareBooleanAttribute(PHPTAL_Php_CodeWriter $codewriter, $qname, $code)
    {
        $attkey = $this->getVarName($qname, $codewriter);

        if ($codewriter->getOutputMode() === PHPTAL::HTML5) {
            $value  = "' $qname'";
        } else {
            $value  = "' $qname=\"$qname\"'";
        }
        $codewriter->doIf($code);
        $codewriter->doSetVar($attkey, $value);
        $codewriter->doElse();
        $codewriter->doSetVar($attkey, '\'\'');
        $codewriter->doEnd('if');
        $this->phpelement->getOrCreateAttributeNode($qname)->overwriteFullWithVariable($attkey);
    }

    private function getVarName($qname, PHPTAL_Php_CodeWriter $codewriter)
    {
        $var = $codewriter->createTempVariable();
        $this->vars_to_recycle[] = $var;
        return $var;
    }


    public function after(PHPTAL_Php_CodeWriter $codewriter)
    {
        foreach($this->vars_to_recycle as $var) $codewriter->recycleTempVariable($var);
    }

    public function talesChainNothingKeyword(PHPTAL_Php_TalesChainExecutor $executor)
    {
        $codewriter = $executor->getCodeWriter();
        $executor->doElse();
        $codewriter->doSetVar(
            $this->_attkey,
            "''"
        );
        $executor->breakChain();
    }

    public function talesChainDefaultKeyword(PHPTAL_Php_TalesChainExecutor $executor)
    {
        $codewriter = $executor->getCodeWriter();
        $executor->doElse();
        $attr_str = ($this->_default_escaped !== false)
            ? ' '.$this->_attribute.'='.$codewriter->quoteAttributeValue($this->_default_escaped)  // default value
            : '';                                 // do not print attribute
        $codewriter->doSetVar($this->_attkey, $codewriter->str($attr_str));
        $executor->breakChain();
    }

    public function talesChainPart(PHPTAL_Php_TalesChainExecutor $executor, $exp, $islast)
    {
        $codewriter = $executor->getCodeWriter();

        if (!$islast) {
            $condition = "!phptal_isempty($this->_attkey = ($exp))";
        }
        else {
            $condition = "NULL !== ($this->_attkey = ($exp))";
        }
        $executor->doIf($condition);

        if ($this->_echoType == PHPTAL_Php_Attribute::ECHO_STRUCTURE)
            $value = $codewriter->stringifyCode($this->_attkey);
        else
            $value = $codewriter->escapeCode($this->_attkey);

        $codewriter->doSetVar($this->_attkey, $codewriter->str(" {$this->_attribute}=\"").".$value.'\"'");
    }
}