Location: PHPKode > projects > PHPTAL > PHPTAL-1.2.2/PHPTAL.php
<?php
/**
 * PHPTAL templating engine
 *
 * PHP Version 5
 *
 * @category HTML
 * @package  PHPTAL
 * @author   Laurent Bedubourg <hide@address.com>
 * @author   Kornel Lesiński <hide@address.com>
 * @license  http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
 * @version  SVN: $Id: PHPTAL.php 958 2010-06-27 22:47:38Z kornel $
 * @link     http://phptal.org/
 */

define('PHPTAL_VERSION', '1_2_2');

PHPTAL::autoloadRegister();

/**
 * PHPTAL template entry point.
 *
 * <code>
 * <?php
 * require_once 'PHPTAL.php';
 * try {
 *      $tpl = new PHPTAL('mytemplate.html');
 *      $tpl->title = 'Welcome here';
 *      $tpl->result = range(1, 100);
 *      ...
 *      echo $tpl->execute();
 * }
 * catch (Exception $e) {
 *      echo $e;
 * }
 * ?>
 * </code>
 *
 * @category HTML
 * @package  PHPTAL
 * @author   Laurent Bedubourg <hide@address.com>
 * @author   Kornel Lesiński <hide@address.com>
 * @license  http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
 * @link     http://phptal.org/
 */
class PHPTAL
{
    //{{{
    /**
     * constants for output mode
     * @see setOutputMode()
     */
    const XHTML = 11;
    const XML   = 22;
    const HTML5 = 55;

    /**
     * @see getPreFilters()
     */
    protected $prefilters = array();

    /**
     * Prefilters have been redesigned. Old property is no longer used.
     *
     * @deprecated
     */
    private $_prefilter = 'REMOVED: DO NOT USE';
    protected $_postfilter = null;

    /**
     *  list of template source repositories given to file source resolver
     */
    protected $_repositories = array();

    /**
     *  template path (path that has been set, not necessarily loaded)
     */
    protected $_path = null;

    /**
     *  template source resolvers (classes that search for templates by name)
     */
    protected $resolvers = array();

    /**
     *  template source (only set when not working with file)
     */
    protected $_source = null;

    /**
     * destination of PHP intermediate file
     */
    protected $_codeFile = null;

    /**
     * php function generated for the template
     */
    protected $_functionName = null;

    /**
     * set to true when template is ready for execution
     */
    protected $_prepared = false;

    /**
     * associative array of phptal:id => PHPTAL_Trigger
     */
    protected $_triggers = array();

    /**
     * i18n translator
     */
    protected $_translator = null;

    /**
     * global execution context
     */
    protected $_globalContext = null;

    /**
     * current execution context
     */
    protected $_context = null;

    /**
     * list of on-error caught exceptions
     */
    protected $_errors = array();

    /**
     * encoding used throughout
     */
    protected $_encoding = 'UTF-8';

    /**
     * type of syntax used in generated templates
     */
    protected $_outputMode = PHPTAL::XHTML;
    /**
     * should all comments be stripped
     */

    // configuration properties

    /**
     * don't use code cache
     */
    protected $_forceReparse = null;

    /**
     * directory where code cache is
     */
    private $_phpCodeDestination;
    private $_phpCodeExtension = 'php';

    /**
     * number of days
     */
    private $_cacheLifetime = 30;

    /**
     * 1/x
     */
    private $_cachePurgeFrequency = 30;

    /**
     * speeds up calls to external templates
     */
    private $externalMacroTemplatesCache = array();

    //}}}

    /**
     * PHPTAL Constructor.
     *
     * @param string $path Template file path.
     */
    public function __construct($path=false)
    {
        $this->_path = $path;
        $this->_globalContext = new stdClass();
        $this->_context = new PHPTAL_Context();
        $this->_context->setGlobal($this->_globalContext);

        if (function_exists('sys_get_temp_dir')) {
            $this->setPhpCodeDestination(sys_get_temp_dir());
        } elseif (substr(PHP_OS, 0, 3) == 'WIN') {
            if (file_exists('c:\\WINNT\\Temp\\')) {
                $this->setPhpCodeDestination('c:\\WINNT\\Temp\\');
            } else {
                $this->setPhpCodeDestination('c:\\WINDOWS\\Temp\\');
            }
        } else {
            $this->setPhpCodeDestination('/tmp/');
        }
    }

    /**
     * create
     * returns a new PHPTAL object
     *
     * @param string $path Template file path.
     *
     * @return PHPTAL
     */
    public static function create($path=false)
    {
        return new PHPTAL($path);
    }

    /**
     * Clone template state and context.
     *
     * @return void
     */
    public function __clone()
    {
        $context = $this->_context;
        $this->_context = clone $this->_context;
        $this->_context->setParent($context);
        $this->_context->setGlobal($this->_globalContext);
    }

    /**
     * Set template from file path.
     *
     * @param string $path filesystem path,
     *                     or any path that will be accepted by source resolver
     *
     * @return $this
     */
    public function setTemplate($path)
    {
        $this->_prepared = false;
        $this->_functionName = null;
        $this->_codeFile = null;
        $this->_path = $path;
        $this->_source = null;
        $this->_context->_docType = null;
        $this->_context->_xmlDeclaration = null;
        return $this;
    }

    /**
     * Set template from source.
     *
     * Should be used only with temporary template sources.
     * Use setTemplate() or addSourceResolver() whenever possible.
     *
     * @param string $src The phptal template source.
     * @param string $path Fake and 'unique' template path.
     *
     * @return $this
     */
    public function setSource($src, $path=false)
    {
        if (!$path) {
            // this prefix tells string source that path has been faked
            $path = PHPTAL_StringSource::NO_PATH_PREFIX.md5($src).'>';
        }

        $this->_prepared = false;
        $this->_functionName = null;
        $this->_codeFile = null;
        $this->_source = new PHPTAL_StringSource($src, $path);
        $this->_path = $path;
        $this->_context->_docType = null;
        $this->_context->_xmlDeclaration = null;
        return $this;
    }

    /**
     * Specify where to look for templates.
     *
     * @param mixed $rep string or Array of repositories
     *
     * @return $this
     */
    public function setTemplateRepository($rep)
    {
        if (is_array($rep)) {
            $this->_repositories = $rep;
        } else {
            $this->_repositories[] = $rep;
        }
        return $this;
    }

    /**
     * Get template repositories.
     *
     * @return array
     */
    public function getTemplateRepositories()
    {
        return $this->_repositories;
    }

    /**
     * Clears the template repositories.
     *
     * @return $this
     */
    public function clearTemplateRepositories()
    {
        $this->_repositories = array();
        return $this;
    }

    /**
     * Specify how to look for templates.
     *
     * @param PHPTAL_SourceResolver $resolver instance of resolver
     *
     * @return $this
     */
    public function addSourceResolver(PHPTAL_SourceResolver $resolver)
    {
        $this->resolvers[] = $resolver;
        return $this;
    }

    /**
     * Ignore XML/XHTML comments on parsing.
     * Comments starting with <!--! are always stripped.
     *
     * @param bool $bool if true all comments are stripped during parse
     *
     * @return $this
     */
    public function stripComments($bool)
    {
        $this->resetPrepared();

        if ($bool) {
            $this->prefilters['_phptal_strip_comments_'] = new PHPTAL_PreFilter_StripComments();
        } else {
            unset($this->prefilters['_phptal_strip_comments_']);
        }
        return $this;
    }

    /**
     * Set output mode
     * XHTML output mode will force elements like <link/>, <meta/> and <img/>, etc.
     * to be empty and threats attributes like selected, checked to be
     * boolean attributes.
     *
     * XML output mode outputs XML without such modifications
     * and is neccessary to generate RSS feeds properly.
     *
     * @param int $mode (PHPTAL::XML, PHPTAL::XHTML or PHPTAL::HTML5).
     *
     * @return $this
     */
    public function setOutputMode($mode)
    {
        $this->resetPrepared();

        if ($mode != PHPTAL::XHTML && $mode != PHPTAL::XML && $mode != PHPTAL::HTML5) {
            throw new PHPTAL_ConfigurationException('Unsupported output mode '.$mode);
        }
        $this->_outputMode = $mode;
        return $this;
    }

    /**
     * Get output mode
     * @see setOutputMode()
     *
     * @return output mode constant
     */
    public function getOutputMode()
    {
        return $this->_outputMode;
    }

    /**
     * Set input and ouput encoding. Encoding is case-insensitive.
     *
     * @param string $enc example: 'UTF-8'
     *
     * @return $this
     */
    public function setEncoding($enc)
    {
        $enc = strtoupper($enc);
        if ($enc != $this->_encoding) {
            $this->_encoding = $enc;
            if ($this->_translator) $this->_translator->setEncoding($enc);

            $this->resetPrepared();
        }
        return $this;
    }

    /**
     * Get input and ouput encoding.
     *
     * @param string $enc example: 'UTF-8'
     *
     * @return $this
     */
    public function getEncoding()
    {
        return $this->_encoding;
    }

    /**
     * Set the storage location for intermediate PHP files.
     * The path cannot contain characters that would be interpreted by glob() (e.g. *[]?)
     *
     * @param string $path Intermediate file path.
     *
     * @return $this
     */
    public function setPhpCodeDestination($path)
    {
        $this->_phpCodeDestination = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        $this->resetPrepared();
        return $this;
    }

    /**
     * Get the storage location for intermediate PHP files.
     *
     * @return string
     */
    public function getPhpCodeDestination()
    {
        return $this->_phpCodeDestination;
    }

    /**
     * Set the file extension for intermediate PHP files.
     *
     * @param string $extension The file extension.
     *
     * @return $this
     */
    public function setPhpCodeExtension($extension)
    {
        $this->_phpCodeExtension = $extension;
        $this->resetPrepared();
        return $this;
    }

    /**
     * Get the file extension for intermediate PHP files.
     */
    public function getPhpCodeExtension()
    {
        return $this->_phpCodeExtension;
    }

    /**
     * Flags whether to ignore intermediate php files and to
     * reparse templates every time (if set to true).
     *
     * DON'T USE IN PRODUCTION - this makes PHPTAL many times slower.
     *
     * @param bool $bool Forced reparse state.
     *
     * @return $this
     */
    public function setForceReparse($bool)
    {
        $this->_forceReparse = (bool) $bool;
        return $this;
    }

    /**
     * Get the value of the force reparse state.
     */
    public function getForceReparse()
    {
        return $this->_forceReparse;
    }

    /**
     * Set I18N translator.
     *
     * This sets encoding used by the translator, so be sure to use encoding-dependent
     * features of the translator (e.g. addDomain) _after_ calling setTranslator.
     *
     * @param PHPTAL_TranslationService $t instance
     *
     * @return $this
     */
    public function setTranslator(PHPTAL_TranslationService $t)
    {
        $this->_translator = $t;
        $t->setEncoding($this->getEncoding());
        return $this;
    }


    /**
     * Please use addPreFilter instead.
     *
     * This method and use of PHPTAL_Filter for prefilters are deprecated.
     *
     * @see PHPTAL::addPreFilter()
     * @deprecated
     */
    final public function setPreFilter(PHPTAL_Filter $filter)
    {
        $this->resetPrepared();
        $this->prefilters['_phptal_old_filter_'] = $filter;
    }

    /**
     * Add new prefilter to filter chain.
     * Prefilters are called only once template is compiled.
     *
     * PreFilters must inherit PHPTAL_PreFilter class.
     * (in future this method will allow string with filter name instead of object)
     *
     * @param mixed $filter PHPTAL_PreFilter object or name of prefilter to add
     *
     * @return PHPTAL
     */
    final public function addPreFilter($filter)
    {
        $this->resetPrepared();

        if (!$filter instanceof PHPTAL_PreFilter) {
            throw new PHPTAL_ConfigurationException("addPreFilter expects PHPTAL_PreFilter object");
        }

        $this->prefilters[] = $filter;
        return $this;
    }

    /**
     * Array with all prefilter objects *or strings* that are names of prefilter classes.
     * (the latter is not implemented in 1.2.1)
     *
     * Array keys may be non-numeric!
     *
     * @return array
     */
    protected function getPreFilters()
    {
        return $this->prefilters;
    }

    /**
     * Returns string that is unique for every different configuration of prefilters.
     * Result of prefilters may be cached until this string changes.
     *
     * You can override this function.
     *
     * @return string
     */
    private function getPreFiltersCacheId()
    {
        $cacheid = '';
        foreach($this->getPreFilters() as $key => $prefilter) {
            if ($prefilter instanceof PHPTAL_PreFilter) {
                $cacheid .= $key.$prefilter->getCacheId();
            } elseif ($prefilter instanceof PHPTAL_Filter) {
                $cacheid .= $key.get_class($prefilter);
            } else {
                $cacheid .= $key.$prefilter;
            }
        }
        return $cacheid;
    }

    /**
     * Instantiate prefilters
     *
     * @return array of PHPTAL_[Pre]Filter objects
     */
    private function getPreFilterInstances()
    {
        $prefilters = $this->getPreFilters();

        foreach($prefilters as $prefilter) {
            if ($prefilter instanceof PHPTAL_PreFilter) {
                $prefilter->setPHPTAL($this);
            }
        }
        return $prefilters;
    }

    /**
     * Set template post filter.
     * It will be called every time after template generates output.
     *
     * See PHPTAL_PostFilter class.
     *
     * @param PHPTAL_Filter $filter filter instance
     */
    public function setPostFilter(PHPTAL_Filter $filter)
    {
        $this->_postfilter = $filter;
        return $this;
    }

    /**
     * Register a trigger for specified phptal:id.
     * @param string $id phptal:id to look for
     */
    public function addTrigger($id, PHPTAL_Trigger $trigger)
    {
        $this->_triggers[$id] = $trigger;
        return $this;
    }

    /**
     * Returns trigger for specified phptal:id.
     *
     * @param string $id phptal:id
     *
     * @return PHPTAL_Trigger or NULL
     */
    public function getTrigger($id)
    {
        if (array_key_exists($id, $this->_triggers)) {
            return $this->_triggers[$id];
        }
        return null;
    }

    /**
     * Set a context variable.
     * Use it by setting properties on PHPTAL object.
     *
     * @param string $varname
     * @param mixed $value
     *
     * @return void
     */
    public function __set($varname, $value)
    {
        $this->_context->__set($varname, $value);
    }

    /**
     * Set a context variable.
     *
     * @see PHPTAL::__set()
     * @param string $varname name of the variable
     * @param mixed $value value of the variable
     *
     * @return $this
     */
    public function set($varname, $value)
    {
        $this->_context->__set($varname, $value);
        return $this;
    }

    /**
     * Execute the template code and return generated markup.
     *
     * @return string
     */
    public function execute()
    {
        try
        {
            if (!$this->_prepared) {
                // includes generated template PHP code
                $this->prepare();
            }
            $this->_context->echoDeclarations(false);

            $templateFunction = $this->getFunctionName();

            try {
                ob_start();
                $templateFunction($this, $this->_context);
                $res = ob_get_clean();
            }
            catch (Exception $e)
            {
                ob_end_clean();
                throw $e;
            }

            // unshift doctype
            if ($this->_context->_docType) {
                $res = $this->_context->_docType . $res;
            }

            // unshift xml declaration
            if ($this->_context->_xmlDeclaration) {
                $res = $this->_context->_xmlDeclaration . "\n" . $res;
            }

            if ($this->_postfilter) {
                return $this->_postfilter->filter($res);
            }
        }
        catch (Exception $e)
        {
            PHPTAL_ExceptionHandler::handleException($e, $this->getEncoding());
        }

        return $res;
    }

    /**
     * Execute and echo template without buffering of the output.
     * This function does not allow postfilters nor DOCTYPE/XML declaration.
     *
     * @return NULL
     */
    public function echoExecute()
    {
        try {
            if (!$this->_prepared) {
                // includes generated template PHP code
                $this->prepare();
            }

            if ($this->_postfilter) {
                throw new PHPTAL_ConfigurationException("echoExecute() does not support postfilters");
            }

            $this->_context->echoDeclarations(true);

            $templateFunction = $this->getFunctionName();
            $templateFunction($this, $this->_context);
        }
        catch (Exception $e)
        {
            PHPTAL_ExceptionHandler::handleException($e, $this->getEncoding());
        }
    }

    /**
     * Execute a template macro.
     * Should be used only from within generated template code!
     *
     * @param string $path Template macro path
     */
    public function executeMacro($path)
    {
        $this->_executeMacroOfTemplate($path, $this);
    }

    /**
     * This is PHPTAL's internal function that handles
     * execution of macros from templates.
     *
     * $this is caller's context (the file where execution had originally started)
     *
     * @param PHPTAL $local_tpl is PHPTAL instance of the file in which macro is defined
     *                          (it will be different from $this if it's external macro call)
     * @access private
     */
    final public function _executeMacroOfTemplate($path, PHPTAL $local_tpl)
    {
        // extract macro source file from macro name, if macro path does not
        // contain filename, then the macro is assumed to be local

        if (preg_match('/^(.*?)\/([a-z0-9_-]*)$/i', $path, $m)) {
            list(, $file, $macroName) = $m;

            if (isset($this->externalMacroTemplatesCache[$file])) {
                $tpl = $this->externalMacroTemplatesCache[$file];
            } else {
                $tpl = clone $this;
                array_unshift($tpl->_repositories, dirname($this->_source->getRealPath()));
                $tpl->setTemplate($file);
                $tpl->prepare();

                // keep it small (typically only 1 or 2 external files are used)
                if (count($this->externalMacroTemplatesCache) > 10) {
                    $this->externalMacroTemplatesCache = array();
                }
                $this->externalMacroTemplatesCache[$file] = $tpl;
            }

            $fun = $tpl->getFunctionName() . '_' . strtr($macroName, "-", "_");
            if (!function_exists($fun)) {
                throw new PHPTAL_MacroMissingException("Macro '$macroName' is not defined in $file", $this->_source->getRealPath());
            }

            $fun($tpl, $this);

        } else {
            // call local macro
            $fun = $local_tpl->getFunctionName() . '_' . strtr($path, "-", "_");
            if (!function_exists($fun)) {
                throw new PHPTAL_MacroMissingException("Macro '$path' is not defined", $local_tpl->_source->getRealPath());
            }
            $fun( $local_tpl, $this);
        }
    }

    /**
     * ensure that getCodePath will return up-to-date path
     */
    private function setCodeFile()
    {
        $this->findTemplate();
        $this->_codeFile = $this->getPhpCodeDestination() . $this->getFunctionName() . '.' . $this->getPhpCodeExtension();
    }

    protected function resetPrepared()
    {
        $this->_prepared = false;
        $this->_functionName = null;
        $this->_codeFile = null;
    }

    /**
     * Prepare template without executing it.
     */
    public function prepare()
    {
        // clear just in case settings changed and cache is out of date
        $this->externalMacroTemplatesCache = array();

        // find the template source file and update function name
        $this->setCodeFile();

        if (!function_exists($this->getFunctionName())) {
            // parse template if php generated code does not exists or template
            // source file modified since last generation or force reparse is set
            if ($this->getForceReparse() || !file_exists($this->getCodePath())) {

                // i'm not sure where that belongs, but not in normal path of execution
                // because some sites have _a lot_ of files in temp
                if ($this->getCachePurgeFrequency() && mt_rand()%$this->getCachePurgeFrequency() == 0) {
                    $this->cleanUpGarbage();
                }

                $result = $this->parse();

                if (!file_put_contents($this->getCodePath(), $result)) {
                    throw new PHPTAL_IOException('Unable to open '.$this->getCodePath().' for writing');
                }

                // the awesome thing about eval() is that parse errors don't stop PHP.
                // when PHP dies during eval, fatal error is printed and
                // can be captured with output buffering
                ob_start();
                try {
                    eval("?>\n".$result);
                }
                catch(Exception $e) {
                    ob_end_clean();
                    throw $e;
                }

                if (!function_exists($this->getFunctionName())) {
                    $msg = str_replace('eval()\'d code', $this->getCodePath(), ob_get_clean());

                    // greedy .* ensures last match
                    if (preg_match('/.*on line (\d+)$/m', $msg, $m)) $line=$m[1]; else $line=0;
                    throw new PHPTAL_TemplateException(trim($msg), $this->getCodePath(), $line);
                }
                ob_end_clean();

            } else {
                // eval trick is used only on first run,
                // just in case it causes any problems with opcode accelerators
                require $this->getCodePath();
            }
        }

        $this->_prepared = true;
        return $this;
    }

    /**
     * get how long compiled templates and phptal:cache files are kept, in days
     */
    public function getCacheLifetime()
    {
        return $this->_cacheLifetime;
    }

    /**
     * set how long compiled templates and phptal:cache files are kept
     *
     * @param $days number of days
     */
    public function setCacheLifetime($days)
    {
        $this->_cacheLifetime = max(0.5, $days);
        return $this;
    }

    /**
     * PHPTAL will scan cache and remove old files on every nth compile
     * Set to 0 to disable cleanups
     */
    public function setCachePurgeFrequency($n)
    {
        $this->_cachePurgeFrequency = (int)$n;
        return $this;
    }

    /**
     * how likely cache cleaning can happen
     * @see self::setCachePurgeFrequency()
     */
    public function getCachePurgeFrequency()
    {
        return $this->_cachePurgeFrequency;
    }


    /**
     * Removes all compiled templates from cache that
     * are older than getCacheLifetime() days
     */
    public function cleanUpGarbage()
    {
        $cacheFilesExpire = time() - $this->getCacheLifetime() * 3600 * 24;

        // relies on templates sorting order being related to their modification dates
        $upperLimit = $this->getPhpCodeDestination() . $this->getFunctionNamePrefix($cacheFilesExpire) . '_';
        $lowerLimit = $this->getPhpCodeDestination() . $this->getFunctionNamePrefix(0);

        // second * gets phptal:cache
        $cacheFiles = glob($this->getPhpCodeDestination() . 'tpl_????????_*.' . $this->getPhpCodeExtension() . '*');

        if ($cacheFiles) {
            foreach ($cacheFiles as $index => $file) {

                // comparison here skips filenames that are certainly too new
                if (strcmp($file, $upperLimit) <= 0 || substr($file, 0, strlen($lowerLimit)) === $lowerLimit) {
                    $time = filemtime($file);
                    if ($time && $time < $cacheFilesExpire) {
                        @unlink($file);
                    }
                }
            }
        }
    }

    /**
     * Removes content cached with phptal:cache for currently set template
     * Must be called after setSource/setTemplate.
     */
    public function cleanUpCache()
    {
        $filename = $this->getCodePath();
        $cacheFiles = glob($filename . '?*');
        if ($cacheFiles) {
            foreach ($cacheFiles as $file) {
                if (substr($file, 0, strlen($filename)) !== $filename) continue; // safety net
                @unlink($file);
            }
        }
        $this->_prepared = false;
    }

    /**
     * Returns the path of the intermediate PHP code file.
     *
     * The returned file may be used to cleanup (unlink) temporary files
     * generated by temporary templates or more simply for debug.
     *
     * @return string
     */
    public function getCodePath()
    {
        if (!$this->_codeFile) $this->setCodeFile();
        return $this->_codeFile;
    }

    /**
     * Returns the generated template function name.
     * @return string
     */
    public function getFunctionName()
    {
       // function name is used as base for caching, so it must be unique for
       // every combination of settings that changes code in compiled template

       if (!$this->_functionName) {

            // just to make tempalte name recognizable
            $basename = preg_replace('/\.[a-z]{3,5}$/', '', basename($this->_source->getRealPath()));
            $basename = substr(trim(preg_replace('/[^a-zA-Z0-9]+/', '_', $basename), "_"), 0, 20);

            $hash = md5(PHPTAL_VERSION . PHP_VERSION
                    . $this->_source->getRealPath()
                    . $this->getEncoding()
                    . $this->getPrefiltersCacheId()
                    . $this->getOutputMode(),
                    true
                    );

            // uses base64 rather than hex to make filename shorter.
            // there is loss of some bits due to name constraints and case-insensivity,
            // but that's still over 110 bits in addition to basename and timestamp.
            $hash = strtr(rtrim(base64_encode($hash),"="),"+/=","_A_");

            $this->_functionName = $this->getFunctionNamePrefix($this->_source->getLastModifiedTime()) .
                                   $basename . '__' . $hash;
        }
        return $this->_functionName;
    }

    /**
     * Returns prefix used for function name.
     * Function name is also base name for the template.
     *
     * @param int $timestamp unix timestamp with template modification date
     *
     * @return string
     */
    private function getFunctionNamePrefix($timestamp)
    {
        // tpl_ prefix and last modified time must not be changed,
        // because cache cleanup relies on that
        return 'tpl_' . sprintf("%08x", $timestamp) .'_';
    }

    /**
     * Returns template translator.
     * @return PHPTAL_TranslationService
     */
    public function getTranslator()
    {
        return $this->_translator;
    }

    /**
     * Returns array of exceptions caught by tal:on-error attribute.
     *
     * @return array<Exception>
     */
    public function getErrors()
    {
        return $this->_errors;
    }

    /**
     * Public for phptal templates, private for user.
     *
     * @return void
     * @access private
     */
    public function addError(Exception $error)
    {
        $this->_errors[] =  $error;
    }

    /**
     * Returns current context object.
     * Use only in Triggers.
     *
     * @return PHPTAL_Context
     */
    public function getContext()
    {
        return $this->_context;
    }

    /**
     * only for use in generated template code
     *
     * @access private
     */
    public function getGlobalContext()
    {
        return $this->_globalContext;
    }

    /**
     * only for use in generated template code
     *
     * @access private
     */
    final public function pushContext()
    {
        $this->_context = $this->_context->pushContext();
        return $this->_context;
    }

    /**
     * only for use in generated template code
     *
     * @access private
     */
    final public function popContext()
    {
        $this->_context = $this->_context->popContext();
        return $this->_context;
    }

    /**
     * Parse currently set template, prefilter and generate PHP code.
     *
     * @return string (compiled PHP code)
     */
    protected function parse()
    {
        // instantiate the PHPTAL source parser
        $data = $this->_source->getData();

        $prefilters = $this->getPreFilterInstances();

        foreach($prefilters as $prefilter) {
            $data = $prefilter->filter($data);
        }

        $parser = new PHPTAL_Dom_SaxXmlParser($this->_encoding);
        $builder = new PHPTAL_Dom_PHPTALDocumentBuilder();
        $realpath = $this->_source->getRealPath();

        $tree = $parser->parseString($builder, $data, $realpath)->getResult();

        foreach($prefilters as $prefilter) {
            if ($prefilter instanceof PHPTAL_PreFilter) {
                if ($prefilter->filterDOM($tree) !== NULL) {
                    throw new PHPTAL_ConfigurationException("Don't return value from filterDOM()");
                }
            }
        }

        $state = new PHPTAL_Php_State($this);

        $codewriter = new PHPTAL_Php_CodeWriter($state);
        $codewriter->doTemplateFile($this->getFunctionName(), $tree);

        return $codewriter->getResult();
    }

    /**
     * Search template source location.
     * @return void
     */
    protected function findTemplate()
    {
        if ($this->_path == false) {
            throw new PHPTAL_ConfigurationException('No template file specified');
        }

        // template source already defined
        if ($this->_source) {
            return;
        }

        if (!$this->resolvers && !$this->_repositories) {
            $this->_source = new PHPTAL_FileSource($this->_path);
        } else {
            foreach ($this->resolvers as $resolver) {
                $source = $resolver->resolve($this->_path);
                if ($source) {
                    $this->_source = $source;
                    return;
                }
            }

            $resolver = new PHPTAL_FileSourceResolver($this->_repositories);
            $this->_source = $resolver->resolve($this->_path);
        }

        if (!$this->_source) {
            throw new PHPTAL_IOException('Unable to locate template file '.$this->_path);
        }
    }

    /**
     * Removed
     *
     * @deprecated
     * @return void
     */
    final public static function setIncludePath()
    {
    }

    /**
     * Restore include path to state before PHPTAL modified it.
     *
     * @deprecated
     * @return void
     */
    final public static function restoreIncludePath()
    {
    }

    /**
     * Suitable for callbacks from SPL autoload
     *
     * @param string $class class name to load
     *
     * @return void
     */
    final public static function autoload($class)
    {
        if (version_compare(PHP_VERSION, '5.3', '>=') && __NAMESPACE__) {
            $class = str_replace(__NAMESPACE__, 'PHPTAL', $class);
            $class = strtr($class, '\\', '_');
        }

        if (substr($class, 0, 7) !== 'PHPTAL_') return;

        $path = dirname(__FILE__) . strtr("_".$class, "_", DIRECTORY_SEPARATOR) . '.php';

        require $path;
    }

    /**
     * Sets up PHPTAL's autoloader.
     *
     * If you have to use your own autoloader to load PHPTAL files,
     * use spl_autoload_unregister(array('PHPTAL','autoload'));
     *
     * @return void
     */
    final public static function autoloadRegister()
    {
        // spl_autoload_register disables oldschool autoload
        // even if it was added using spl_autoload_register!
        // this is intended to preserve old autoloader

        $uses_autoload = function_exists('__autoload')
            && (!($tmp = spl_autoload_functions()) || ($tmp[0] === '__autoload'));
        spl_autoload_register(array(__CLASS__,'autoload'));
        if ($uses_autoload) {
            spl_autoload_register('__autoload');
        }
    }
}
Return current item: PHPTAL