diff options
Diffstat (limited to 'lib/phptal/PHPTAL/Php/CodeWriter.php')
-rw-r--r-- | lib/phptal/PHPTAL/Php/CodeWriter.php | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/lib/phptal/PHPTAL/Php/CodeWriter.php b/lib/phptal/PHPTAL/Php/CodeWriter.php new file mode 100644 index 0000000..44ee063 --- /dev/null +++ b/lib/phptal/PHPTAL/Php/CodeWriter.php @@ -0,0 +1,511 @@ +<?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$ + * @link http://phptal.org/ + */ +/** + * Helps generate php representation of a template. + * + * @package PHPTAL + * @subpackage Php + * @author Laurent Bedubourg <lbedubourg@motion-twin.com> + */ +class PHPTAL_Php_CodeWriter +{ + /** + * max id of variable to give as temp + */ + private $temp_var_counter=0; + /** + * stack with free'd variables + */ + private $temp_recycling=array(); + + /** + * keeps track of seen functions for function_exists + */ + private $known_functions = array(); + + + public function __construct(PHPTAL_Php_State $state) + { + $this->_state = $state; + } + + public function createTempVariable() + { + if (count($this->temp_recycling)) return array_shift($this->temp_recycling); + return '$_tmp_'.(++$this->temp_var_counter); + } + + public function recycleTempVariable($var) + { + if (substr($var, 0, 6)!=='$_tmp_') throw new PHPTAL_Exception("Invalid variable recycled"); + $this->temp_recycling[] = $var; + } + + public function getCacheFilesBaseName() + { + return $this->_state->getCacheFilesBaseName(); + } + + public function getResult() + { + $this->flush(); + if (version_compare(PHP_VERSION, '5.3', '>=') && __NAMESPACE__) { + return '<?php use '.'PHPTALNAMESPACE as P; ?>'.trim($this->_result); + } else { + return trim($this->_result); + } + } + + /** + * set full '<!DOCTYPE...>' string to output later + * + * @param string $dt + * + * @return void + */ + public function setDocType($dt) + { + $this->_doctype = $dt; + } + + /** + * set full '<?xml ?>' string to output later + * + * @param string $dt + * + * @return void + */ + public function setXmlDeclaration($dt) + { + $this->_xmldeclaration = $dt; + } + + /** + * functions later generated and checked for existence will have this prefix added + * (poor man's namespace) + * + * @param string $prefix + * + * @return void + */ + public function setFunctionPrefix($prefix) + { + $this->_functionPrefix = $prefix; + } + + /** + * @return string + */ + public function getFunctionPrefix() + { + return $this->_functionPrefix; + } + + /** + * @see PHPTAL_Php_State::setTalesMode() + * + * @param string $mode + * + * @return string + */ + public function setTalesMode($mode) + { + return $this->_state->setTalesMode($mode); + } + + public function splitExpression($src) + { + preg_match_all('/(?:[^;]+|;;)+/sm', $src, $array); + $array = $array[0]; + foreach ($array as &$a) $a = str_replace(';;', ';', $a); + return $array; + } + + public function evaluateExpression($src) + { + return $this->_state->evaluateExpression($src); + } + + public function indent() + { + $this->_indentation ++; + } + + public function unindent() + { + $this->_indentation --; + } + + public function flush() + { + $this->flushCode(); + $this->flushHtml(); + } + + public function noThrow($bool) + { + if ($bool) { + $this->pushCode('$ctx->noThrow(true)'); + } else { + $this->pushCode('$ctx->noThrow(false)'); + } + } + + public function flushCode() + { + if (count($this->_codeBuffer) == 0) return; + + // special treatment for one code line + if (count($this->_codeBuffer) == 1) { + $codeLine = $this->_codeBuffer[0]; + // avoid adding ; after } and { + if (!preg_match('/\}\s*$|\{\s*$/', $codeLine)) + $this->_result .= '<?php '.$codeLine."; ?>\n"; // PHP consumes newline + else + $this->_result .= '<?php '.$codeLine." ?>\n"; // PHP consumes newline + $this->_codeBuffer = array(); + return; + } + + $this->_result .= '<?php '."\n"; + foreach ($this->_codeBuffer as $codeLine) { + // avoid adding ; after } and { + if (!preg_match('/[{};]\s*$/', $codeLine)) { + $codeLine .= ' ;'."\n"; + } + $this->_result .= $codeLine; + } + $this->_result .= "?>\n";// PHP consumes newline + $this->_codeBuffer = array(); + } + + public function flushHtml() + { + if (count($this->_htmlBuffer) == 0) return; + + $this->_result .= implode('', $this->_htmlBuffer); + $this->_htmlBuffer = array(); + } + + /** + * Generate code for setting DOCTYPE + * + * @param bool $called_from_macro for error checking: unbuffered output doesn't support that + */ + public function doDoctype($called_from_macro = false) + { + if ($this->_doctype) { + $code = '$ctx->setDocType('.$this->str($this->_doctype).','.($called_from_macro?'true':'false').')'; + $this->pushCode($code); + } + } + + /** + * Generate XML declaration + * + * @param bool $called_from_macro for error checking: unbuffered output doesn't support that + */ + public function doXmlDeclaration($called_from_macro = false) + { + if ($this->_xmldeclaration && $this->getOutputMode() !== PHPTAL::HTML5) { + $code = '$ctx->setXmlDeclaration('.$this->str($this->_xmldeclaration).','.($called_from_macro?'true':'false').')'; + $this->pushCode($code); + } + } + + public function functionExists($name) + { + return isset($this->known_functions[$this->_functionPrefix . $name]); + } + + public function doTemplateFile($functionName, PHPTAL_Dom_Element $treeGen) + { + $this->doComment("\n*** DO NOT EDIT THIS FILE ***\n\nGenerated by PHPTAL from ".$treeGen->getSourceFile()." (edit that file instead)"); + $this->doFunction($functionName, 'PHPTAL $tpl, PHPTAL_Context $ctx'); + $this->setFunctionPrefix($functionName . "_"); + $this->doSetVar('$_thistpl', '$tpl'); + $this->doInitTranslator(); + $treeGen->generateCode($this); + $this->doComment("end"); + $this->doEnd('function'); + } + + public function doFunction($name, $params) + { + $name = $this->_functionPrefix . $name; + $this->known_functions[$name] = true; + + $this->pushCodeWriterContext(); + $this->pushCode("function $name($params) {\n"); + $this->indent(); + $this->_segments[] = 'function'; + } + + public function doComment($comment) + { + $comment = str_replace('*/', '* /', $comment); + $this->pushCode("/* $comment */"); + } + + public function doInitTranslator() + { + if ($this->_state->isTranslationOn()) { + $this->doSetVar('$_translator', '$tpl->getTranslator()'); + } + } + + public function getTranslatorReference() + { + if (!$this->_state->isTranslationOn()) { + throw new PHPTAL_ConfigurationException("i18n used, but Translator has not been set"); + } + return '$_translator'; + } + + public function doEval($code) + { + $this->pushCode($code); + } + + public function doForeach($out, $source) + { + $this->_segments[] = 'foreach'; + $this->pushCode("foreach ($source as $out):"); + $this->indent(); + } + + public function doEnd($expects = null) + { + if (!count($this->_segments)) { + if (!$expects) $expects = 'anything'; + throw new PHPTAL_Exception("Bug: CodeWriter generated end of block without $expects open"); + } + + $segment = array_pop($this->_segments); + if ($expects !== null && $segment !== $expects) { + throw new PHPTAL_Exception("Bug: CodeWriter generated end of $expects, but needs to close $segment"); + } + + $this->unindent(); + if ($segment == 'function') { + $this->pushCode("\n}\n\n"); + $this->flush(); + $functionCode = $this->_result; + $this->popCodeWriterContext(); + $this->_result = $functionCode . $this->_result; + } elseif ($segment == 'try') + $this->pushCode('}'); + elseif ($segment == 'catch') + $this->pushCode('}'); + else + $this->pushCode("end$segment"); + } + + public function doTry() + { + $this->_segments[] = 'try'; + $this->pushCode('try {'); + $this->indent(); + } + + public function doSetVar($varname, $code) + { + $this->pushCode($varname.' = '.$code); + } + + public function doCatch($catch) + { + $this->doEnd('try'); + $this->_segments[] = 'catch'; + $this->pushCode('catch('.$catch.') {'); + $this->indent(); + } + + public function doIf($condition) + { + $this->_segments[] = 'if'; + $this->pushCode('if ('.$condition.'): '); + $this->indent(); + } + + public function doElseIf($condition) + { + if (end($this->_segments) !== 'if') { + throw new PHPTAL_Exception("Bug: CodeWriter generated elseif without if"); + } + $this->unindent(); + $this->pushCode('elseif ('.$condition.'): '); + $this->indent(); + } + + public function doElse() + { + if (end($this->_segments) !== 'if') { + throw new PHPTAL_Exception("Bug: CodeWriter generated else without if"); + } + $this->unindent(); + $this->pushCode('else: '); + $this->indent(); + } + + public function doEcho($code) + { + if ($code === "''") return; + $this->flush(); + $this->pushCode('echo '.$this->escapeCode($code)); + } + + public function doEchoRaw($code) + { + if ($code === "''") return; + $this->pushCode('echo '.$this->stringifyCode($code)); + } + + public function interpolateHTML($html) + { + return $this->_state->interpolateTalesVarsInHtml($html); + } + + public function interpolateCDATA($str) + { + return $this->_state->interpolateTalesVarsInCDATA($str); + } + + public function pushHTML($html) + { + if ($html === "") return; + $this->flushCode(); + $this->_htmlBuffer[] = $html; + } + + public function pushCode($codeLine) + { + $this->flushHtml(); + $codeLine = $this->indentSpaces() . $codeLine; + $this->_codeBuffer[] = $codeLine; + } + + /** + * php string with escaped text + */ + public function str($string) + { + return "'".strtr($string,array("'"=>'\\\'','\\'=>'\\\\'))."'"; + } + + public function escapeCode($code) + { + return $this->_state->htmlchars($code); + } + + public function stringifyCode($code) + { + return $this->_state->stringify($code); + } + + public function getEncoding() + { + return $this->_state->getEncoding(); + } + + public function interpolateTalesVarsInString($src) + { + return $this->_state->interpolateTalesVarsInString($src); + } + + public function setDebug($bool) + { + return $this->_state->setDebug($bool); + } + + public function isDebugOn() + { + return $this->_state->isDebugOn(); + } + + public function getOutputMode() + { + return $this->_state->getOutputMode(); + } + + public function quoteAttributeValue($value) + { + // FIXME: interpolation is done _after_ that function, so ${} must be forbidden for now + + if ($this->getEncoding() == 'UTF-8') // HTML 5: 8.1.2.3 Attributes ; http://code.google.com/p/html5lib/issues/detail?id=93 + { + // regex excludes unicode control characters, all kinds of whitespace and unsafe characters + // and trailing / to avoid confusion with self-closing syntax + $unsafe_attr_regex = '/^$|[&=\'"><\s`\pM\pC\pZ\p{Pc}\p{Sk}]|\/$|\${/u'; + } else { + $unsafe_attr_regex = '/^$|[&=\'"><\s`\0177-\377]|\/$|\${/'; + } + + if ($this->getOutputMode() == PHPTAL::HTML5 && !preg_match($unsafe_attr_regex, $value)) { + return $value; + } else { + return '"'.$value.'"'; + } + } + + public function pushContext() + { + $this->doSetVar('$ctx', '$tpl->pushContext()'); + } + + public function popContext() + { + $this->doSetVar('$ctx', '$tpl->popContext()'); + } + + // ~~~~~ Private members ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private function indentSpaces() + { + return str_repeat("\t", $this->_indentation); + } + + private function pushCodeWriterContext() + { + $this->_contexts[] = clone $this; + $this->_result = ""; + $this->_indentation = 0; + $this->_codeBuffer = array(); + $this->_htmlBuffer = array(); + $this->_segments = array(); + } + + private function popCodeWriterContext() + { + $oldContext = array_pop($this->_contexts); + $this->_result = $oldContext->_result; + $this->_indentation = $oldContext->_indentation; + $this->_codeBuffer = $oldContext->_codeBuffer; + $this->_htmlBuffer = $oldContext->_htmlBuffer; + $this->_segments = $oldContext->_segments; + } + + private $_state; + private $_result = ""; + private $_indentation = 0; + private $_codeBuffer = array(); + private $_htmlBuffer = array(); + private $_segments = array(); + private $_contexts = array(); + private $_functionPrefix = ""; + private $_doctype = ""; + private $_xmldeclaration = ""; +} + |