From: Andrey Kutejko Date: Tue, 30 Jul 2013 21:53:28 +0000 (+0300) Subject: simplify directory layout X-Git-Url: https://git.andy128k.dev/?a=commitdiff_plain;h=07bfaa650ebe3aac4a30a85ef982882ff1766496;p=ipf-template.git simplify directory layout --- diff --git a/ipf/exception.php b/ipf/exception.php deleted file mode 100644 index 2aa9755..0000000 --- a/ipf/exception.php +++ /dev/null @@ -1,6 +0,0 @@ -environment = $environment; - } - - abstract public function __toString(); - - abstract protected function content(); - - public function compile() - { - $compiler = new IPF_Template_Compiler($this->content(), $this->environment); - return $compiler->getCompiledTemplate(); - } - - public function render($c=null) - { - $compiled_template = $this->environment->getCompiledTemplateName($this); - ob_start(); - $t = $c; - try { - include $compiled_template; - } catch (Exception $e) { - ob_clean(); - throw $e; - } - $a = ob_get_contents(); - ob_end_clean(); - return $a; - } -} - diff --git a/ipf/template/compiler.php b/ipf/template/compiler.php deleted file mode 100644 index 003e1c2..0000000 --- a/ipf/template/compiler.php +++ /dev/null @@ -1,440 +0,0 @@ -templateContent = $templateContent; - $this->environment = $environment; - } - - private function compile() - { - $this->compileBlocks(); - $tplcontent = $this->templateContent; - $tplcontent = preg_replace('!{\*(.*?)\*}!s', '', $tplcontent); - $tplcontent = preg_replace('!<\?php(.*?)\?>!s', '', $tplcontent); - preg_match_all('!{literal}(.*?){/literal}!s', $tplcontent, $_match); - $this->_literals = $_match[1]; - $tplcontent = preg_replace("!{literal}(.*?){/literal}!s", '{literal}', $tplcontent); - // Core regex to parse the template - $result = preg_replace_callback('/{((.).*?)}/s', - array($this, '_callback'), - $tplcontent); - if (count($this->_blockStack)) { - trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); - } - return $result; - } - - public function getCompiledTemplate() - { - $result = $this->compile(); - $result = str_replace(array('?>', ''), '', $result); - $result = str_replace("?>\n", "?>\n\n", $result); - return $result; - } - - private static function toplevelBlocks($content) - { - preg_match_all("!{block\s*([^} \r\t\n]*)}|{/block}!", $content, $tags, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); - - $count = 0; - $result = array(); - foreach ($tags as $tag) { - $text = $tag[0][0]; - - if (substr($text, 0, 6) === '{block') { - if ($count == 0) { - $result[] = array( - 'name' => $tag[1][0], - 'start' => $tag[1][1] + strlen($tag[1][0]) + 1, - ); - } - $count++; - } elseif (substr($text, 0, 7) === '{/block') { - $count--; - if ($count == 0) { - $result[count($result)-1]['finish'] = $tag[0][1]; - } - } - } - - if ($count != 0) - throw new IPF_Template_Exception('Blocks are not nested properly.'); - - return $result; - } - - function compileBlocks() - { - $tplcontent = $this->templateContent; - $extendedTemplate = ''; - // Match extends on the first line of the template - if (preg_match("!{extends\s['\"](.*?)['\"]}!", $tplcontent, $_match)) { - $extendedTemplate = $_match[1]; - } - // Get the blocks in the current template - $blocks = self::toplevelBlocks($this->templateContent); - $cnt = count($blocks); - // Compile the blocks - for ($i=0; $i<$cnt; $i++) { - $blockName = $blocks[$i]['name']; - if (!isset($this->_extendBlocks[$blockName]) or false !== strpos($this->_extendBlocks[$blockName], '~~{~~superblock~~}~~')) { - $compiler = clone($this); - $compiler->templateContent = substr($this->templateContent, $blocks[$i]['start'], $blocks[$i]['finish'] - $blocks[$i]['start']); - $_tmp = $compiler->compile(); - if (!isset($this->_extendBlocks[$blockName])) { - $this->_extendBlocks[$blockName] = $_tmp; - } else { - $this->_extendBlocks[$blockName] = str_replace('~~{~~superblock~~}~~', $_tmp, $this->_extendBlocks[$blockName]); - } - } - } - if (strlen($extendedTemplate) > 0) { - // The template of interest is now the extended template - // as we are not in a base template - $this->templateContent = $this->environment->loadTemplateFile($extendedTemplate); - $this->compileBlocks(); //It will recurse to the base template. - } else { - // Replace the current blocks by a place holder - if ($cnt) { - $this->templateContent = preg_replace("!{block\s(\S+?)}(.*?){/block}!s", "{block $1}", $tplcontent, -1); - } - } - } - - private function _callback($matches) - { - list(,$tag, $firstcar) = $matches; - if (!preg_match('/^\$|[\'"]|[a-zA-Z\/]$/', $firstcar)) { - trigger_error(sprintf('Invalid tag syntax: %s', $tag), E_USER_ERROR); - return ''; - } - if (in_array($firstcar, array('$', '\'', '"'))) { - if ('blocktrans' !== end($this->_blockStack)) { - return '_parseVariable($tag).'); ?>'; - } else { - $tok = explode('|', $tag); - $this->_transStack[substr($tok[0], 1)] = $this->_parseVariable($tag); - return '%%'.substr($tok[0], 1).'%%'; - } - } else { - if (!preg_match('/^(\/?[a-zA-Z0-9_]+)(?:(?:\s+(.*))|(?:\((.*)\)))?$/', $tag, $m)) { - trigger_error(sprintf('Invalid function syntax: %s', $tag), E_USER_ERROR); - return ''; - } - if (count($m) == 4){ - $m[2] = $m[3]; - } - if (!isset($m[2])) $m[2] = ''; - if($m[1] == 'ldelim') return '{'; - if($m[1] == 'rdelim') return '}'; - if ($m[1] != 'include') { - return '_parseFunction($m[1], $m[2]).'?>'; - } else { - return $this->_parseFunction($m[1], $m[2]); - } - } - } - - private function _parseVariable($expr) - { - $tok = explode('|', $expr); - $res = $this->_parseFinal(array_shift($tok), self::$allowedInVar); - foreach ($tok as $modifier) { - if (!preg_match('/^(\w+)(?:\:(.*))?$/', $modifier, $m)) { - trigger_error(sprintf('Invalid modifier syntax: (%s) %s', $expr, $modifier), E_USER_ERROR); - return ''; - } - - if ($this->environment->hasModifier($m[1])) { - trigger_error(sprintf('Unknown modifier: (%s) %s', $expr, $m[1]), E_USER_ERROR); - return ''; - } - - $mod = $this->environment->getModifier($m[1]); - - if (isset($m[2])) { - $res = $mod.'('.$res.','.$m[2].')'; - } else { - $res = $mod.'('.$res.')'; - } - } - return $res; - } - - private function _parseFunction($name, $args) - { - switch ($name) { - case 'if': - $res = 'if ('.$this->_parseFinal($args, self::$allowedInExpr).'): '; - $this->_blockStack[] = 'if'; - break; - case 'else': - if (end($this->_blockStack) != 'if') { - trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); - } - $res = 'else: '; - break; - case 'elseif': - if (end($this->_blockStack) != 'if') { - trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); - } - $res = 'elseif('.$this->_parseFinal($args, self::$allowedInExpr).'):'; - break; - case 'foreach': - $tokens = $this->tokenize($args, self::$allowedForeach, array(';')); - $asFound = false; - $scopeVars = array('foreach_counter0', 'foreach_counter', 'foreach_first'); - foreach ($tokens as $token) { - if (!is_array($token)) - continue; - if ($asFound) { - if ($token[0] == T_VARIABLE) - $scopeVars[] = substr($token[1], 1); - } else { - if ($token[0] == T_AS) - $asFound = true; - } - } - $res = - '$t->push(\'' . implode('\', \'', $scopeVars) . '\'); ' . - '$t->_vars[\'foreach_counter0\'] = 0;' . - '$t->_vars[\'foreach_counter\'] = 1;' . - '$t->_vars[\'foreach_first\'] = true;' . - 'foreach ('.$this->compileExpression($tokens).'): '; - $this->_blockStack[] = 'foreach'; - break; - case 'while': - $res = 'while('.$this->_parseFinal($args, self::$allowedInExpr).'):'; - $this->_blockStack[] = 'while'; - break; - case '/foreach': - if(end($this->_blockStack) != 'foreach'){ - trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); - } - array_pop($this->_blockStack); - $res = - '$t->_vars[\'foreach_counter0\'] = $t->_vars[\'foreach_counter0\'] + 1;' . - '$t->_vars[\'foreach_counter\'] = $t->_vars[\'foreach_counter\'] + 1;' . - '$t->_vars[\'foreach_first\'] = false;' . - 'endforeach; ' . - '$t->pop(); '; - break; - case '/if': - case '/while': - $short = substr($name,1); - if(end($this->_blockStack) != $short){ - trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); - } - array_pop($this->_blockStack); - $res = 'end'.$short.'; '; - break; - case 'assign': - $res = $this->_parseFinal($args, self::$allowedAssign).'; '; - break; - case 'literal': - if (count($this->_literals)) { - $res = '?>'.array_shift($this->_literals).'_extendBlocks[$args])) - $res = '?>'.$this->_extendBlocks[$args].'~~{~~superblock~~}~~_parseFinal($args, self::$allowedAssign); - $res = 'echo(__('.$argfct.'));'; - break; - case 'blocktrans': - $this->_blockStack[] = 'blocktrans'; - $res = ''; - $this->_transStack = array(); - if ($args) { - $this->_transPlural = true; - $_args = $this->_parseFinal($args, self::$allowedAssign, array(';', '[', ']')); - $res .= '$_b_t_c='.trim($_args).'; '; - } - $res .= 'ob_start(); '; - break; - case '/blocktrans': - $short = substr($name,1); - if(end($this->_blockStack) != $short){ - trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); - } - $res = ''; - if ($this->_transPlural) { - $res .= '$_b_t_p=ob_get_contents(); ob_end_clean(); echo('; - $res .= 'IPF_Translation::sprintf(_n($_b_t_s, $_b_t_p, $_b_t_c), array('; - $_tmp = array(); - foreach ($this->_transStack as $key=>$_trans) { - $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_SafeString::value('.$_trans.')'; - } - $res .= implode(', ', $_tmp); - unset($_trans, $_tmp); - $res .= ')));'; - $this->_transStack = array(); - } else { - $res .= '$_b_t_s=ob_get_contents(); ob_end_clean(); '; - if (count($this->_transStack) == 0) { - $res .= 'echo(__($_b_t_s)); '; - } else { - $res .= 'echo(IPF_Translation::sprintf(__($_b_t_s), array('; - $_tmp = array(); - foreach ($this->_transStack as $key=>$_trans) { - $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_SafeString::value('.$_trans.')'; - } - $res .= implode(', ', $_tmp); - unset($_trans, $_tmp); - $res .= '))); '; - $this->_transStack = array(); - } - } - $this->_transPlural = false; - array_pop($this->_blockStack); - break; - case 'plural': - $res = '$_b_t_s=ob_get_contents(); ob_end_clean(); ob_start(); '; - break; - case 'include': - $argfct = preg_replace('!^[\'"](.*)[\'"]$!', '$1', $args); - $includedTemplateContent = $this->environment->loadTemplateFile($argfct); - $_comp = new IPF_Template_Compiler($includedTemplateContent, $this->environment); - $res = $_comp->compile(); - break; - default: - $_start = true; - $oname = $name; - if (substr($name, 0, 1) == '/') { - $_start = false; - $name = substr($name, 1); - } - // Here we should allow custom blocks. - - // Here we start the template tag calls at the template tag - // {tag ...} is not a block, so it must be a function. - if (!$this->environment->isTagAllowed($name)) { - trigger_error(sprintf('The function tag "%s" is not allowed.', $name), E_USER_ERROR); - } - // $argfct is a string that can be copy/pasted in the PHP code - // but we need the array of args. - $argfct = $this->_parseFinal($args, self::$allowedAssign); - - $res = '$_extra_tag = new '.$this->environment->getTag($name).'($t);'; - if ($_start) { - $res .= '$_extra_tag->start('.$argfct.'); '; - } else { - $res .= '$_extra_tag->end('.$argfct.'); '; - } - } - return $res; - } - - private function _parseFinal($string, &$allowed, $exceptchar=array(';')) - { - $tokens = $this->tokenize($string, $allowed, $exceptchar); - return $this->compileExpression($tokens); - } - - private function tokenize($string, $allowed, $exceptchar) - { - $tokens = token_get_all(''); - $result = array(); - foreach ($tokens as $tok) { - if (is_array($tok)) { - list($type, $str) = $tok; - if ($type == T_OPEN_TAG || $type == T_CLOSE_TAG) { - // skip - } elseif (in_array($type, $allowed)) { - $result[] = $tok; - } else { - throw new IPF_Template_Exception(sprintf('Invalid syntax: (%s) %s.', $string, $str)); - } - } else { - if (in_array($tok, $exceptchar)) { - trigger_error(sprintf('Invalid character: (%s) %s.', $string, $tok), E_USER_ERROR); - } else { - $result[] = $tok; - } - } - } - return $result; - } - - private function compileExpression($tokens) - { - $result = ''; - foreach ($tokens as $tok) { - if (is_array($tok)) { - list($type, $str) = $tok; - if ($type == T_VARIABLE) { - $result .= '$t->_vars[\''.substr($str, 1).'\']'; - } else { - $result .= $str; - } - } else { - if ($tok == '.') { - $result .= '->'; - } elseif ($tok == '~') { - $result .= '.'; - } else { - $result .= $tok; - } - } - } - return $result; - } -} - -IPF_Template_Compiler::init(); - diff --git a/ipf/template/context.php b/ipf/template/context.php deleted file mode 100644 index c6fd6c0..0000000 --- a/ipf/template/context.php +++ /dev/null @@ -1,43 +0,0 @@ -_vars = new IPF_Template_ContextVars($vars); - } - - function get($var) - { - if (isset($this->_vars[$var])) { - return $this->_vars[$var]; - } - return ''; - } - - function set($var, $value) - { - $this->_vars[$var] = $value; - } - - public function push() - { - $vars = func_get_args(); - $frame = array(); - foreach ($vars as $var) { - $frame[$var] = $this->get($var); - } - $this->stack[] = $frame; - } - - public function pop() - { - $frame = array_pop($this->stack); - foreach ($frame as $var => $val) - $this->set($var, $val); - } -} - diff --git a/ipf/template/contextvars.php b/ipf/template/contextvars.php deleted file mode 100644 index 637c12e..0000000 --- a/ipf/template/contextvars.php +++ /dev/null @@ -1,26 +0,0 @@ -offsetExists($prop)) { - return ''; - } - return parent::offsetGet($prop); - } - - function __get($prop) - { - if (isset($this->$prop)) { - return $this->$prop; - } else { - return $this->offsetGet($prop); - } - } - - function __toString() - { - return var_export($this, true); - } -} diff --git a/ipf/template/environment.php b/ipf/template/environment.php deleted file mode 100644 index eb7834f..0000000 --- a/ipf/template/environment.php +++ /dev/null @@ -1,61 +0,0 @@ - class) - public $tags = array(); - - public function isTagAllowed($name) - { - return isset($this->tags[$name]); - } - - public function getTag($name) - { - if (isset($this->tags[$name])) - return $this->tags[$name]; - else - throw new IPF_Template_Exception('Tag '.$name.' is not defined.'); - } - - // Dictionary of modifiers (modifier name => function) - public $modifiers = array( - 'upper' => 'strtoupper', - 'lower' => 'strtolower', - 'escxml' => 'htmlspecialchars', - 'escape' => 'IPF_Template_Modifier::escape', - 'strip_tags' => 'strip_tags', - 'escurl' => 'rawurlencode', - 'capitalize' => 'ucwords', - 'debug' => 'print_r', // Not var_export because of recursive issues. - 'fulldebug' => 'var_export', - 'count' => 'count', - 'nl2br' => 'nl2br', - 'trim' => 'trim', - 'unsafe' => 'IPF_Template_SafeString::markSafe', - 'safe' => 'IPF_Template_SafeString::markSafe', - 'date' => 'IPF_Template_Modifier::dateFormat', - 'time' => 'IPF_Template_Modifier::timeFormat', - 'floatformat' => 'IPF_Template_Modifier::floatFormat', - 'limit_words' => 'IPF_Template_Modifier::limitWords', - 'limit_chars' => 'IPF_Template_Modifier::limitCharacters', - ); - - public function hasModifier($name) - { - return isset($this->modifiers[$name]); - } - - public function getModifier($name) - { - if (isset($this->modifiers[$name])) - return $this->modifiers[$name]; - else - throw new IPF_Template_Exception('Modifier '.$name.' is not defined.'); - } -} - diff --git a/ipf/template/environment/filesystem.php b/ipf/template/environment/filesystem.php deleted file mode 100644 index 2d4f4b8..0000000 --- a/ipf/template/environment/filesystem.php +++ /dev/null @@ -1,51 +0,0 @@ -folders as $folder) { - if (file_exists($folder.'/'.$filename)) { - return file_get_contents($folder.'/'.$filename); - } - } - throw new IPF_Template_Exception(sprintf('Template file not found: %s', $filename)); - } - - public function getCompiledTemplateName($template) - { - $_tmp = var_export($this->folders, true); - $filename = $this->cache.'/IPF_Template-'.md5($_tmp.(string)$template).'.phps'; - if ($this->debug or !file_exists($filename)) { - $this->write($filename, $template->compile()); - } - return $filename; - } - - private function write($filename, $content) - { - $fp = @fopen($filename, 'a'); - if ($fp !== false) { - flock($fp, LOCK_EX); - ftruncate($fp, 0); - rewind($fp); - fwrite($fp, $content, strlen($content)); - flock($fp, LOCK_UN); - fclose($fp); - @chmod($filename, 0777); - return true; - } else { - throw new IPF_Template_Exception(sprintf('Cannot write the compiled template: %s', $filename)); - } - return false; - } -} - diff --git a/ipf/template/file.php b/ipf/template/file.php deleted file mode 100644 index 2c813eb..0000000 --- a/ipf/template/file.php +++ /dev/null @@ -1,23 +0,0 @@ -tpl = $template; - } - - public function __toString() - { - return $this->tpl; - } - - protected function content() - { - return $this->environment->loadTemplateFile($this->tpl); - } -} - diff --git a/ipf/template/modifier.php b/ipf/template/modifier.php deleted file mode 100644 index 0c837da..0000000 --- a/ipf/template/modifier.php +++ /dev/null @@ -1,82 +0,0 @@ -= $n) { - $out = trim($out); - return (strlen($out) == strlen($str)) ? $out : $out.$end_char; - } - } - } -} - diff --git a/ipf/template/safestring.php b/ipf/template/safestring.php deleted file mode 100644 index 0bd145b..0000000 --- a/ipf/template/safestring.php +++ /dev/null @@ -1,31 +0,0 @@ -value; - if ($safe) - return $mixed; - return htmlspecialchars($mixed, ENT_COMPAT, 'UTF-8'); - } - - function __construct($mixed, $safe=false) - { - $this->value = self::value($mixed, $safe); - } - - function __toString() - { - return $this->value; - } - - public static function markSafe($string) - { - return new IPF_Template_SafeString($string, true); - } -} - diff --git a/ipf/template/string.php b/ipf/template/string.php deleted file mode 100644 index 835e55a..0000000 --- a/ipf/template/string.php +++ /dev/null @@ -1,23 +0,0 @@ -tpl = $template; - } - - public function __toString() - { - return $this->tpl; - } - - protected function content() - { - return $this->tpl; - } -} - diff --git a/ipf/template/tag.php b/ipf/template/tag.php deleted file mode 100644 index 1a1ced2..0000000 --- a/ipf/template/tag.php +++ /dev/null @@ -1,22 +0,0 @@ -context = $context; - } - - // variable list of arguments - function start() - { - } - - // variable list of arguments - function end() - { - } -} - diff --git a/lib/compiler.php b/lib/compiler.php new file mode 100644 index 0000000..003e1c2 --- /dev/null +++ b/lib/compiler.php @@ -0,0 +1,440 @@ +templateContent = $templateContent; + $this->environment = $environment; + } + + private function compile() + { + $this->compileBlocks(); + $tplcontent = $this->templateContent; + $tplcontent = preg_replace('!{\*(.*?)\*}!s', '', $tplcontent); + $tplcontent = preg_replace('!<\?php(.*?)\?>!s', '', $tplcontent); + preg_match_all('!{literal}(.*?){/literal}!s', $tplcontent, $_match); + $this->_literals = $_match[1]; + $tplcontent = preg_replace("!{literal}(.*?){/literal}!s", '{literal}', $tplcontent); + // Core regex to parse the template + $result = preg_replace_callback('/{((.).*?)}/s', + array($this, '_callback'), + $tplcontent); + if (count($this->_blockStack)) { + trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); + } + return $result; + } + + public function getCompiledTemplate() + { + $result = $this->compile(); + $result = str_replace(array('?>', ''), '', $result); + $result = str_replace("?>\n", "?>\n\n", $result); + return $result; + } + + private static function toplevelBlocks($content) + { + preg_match_all("!{block\s*([^} \r\t\n]*)}|{/block}!", $content, $tags, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); + + $count = 0; + $result = array(); + foreach ($tags as $tag) { + $text = $tag[0][0]; + + if (substr($text, 0, 6) === '{block') { + if ($count == 0) { + $result[] = array( + 'name' => $tag[1][0], + 'start' => $tag[1][1] + strlen($tag[1][0]) + 1, + ); + } + $count++; + } elseif (substr($text, 0, 7) === '{/block') { + $count--; + if ($count == 0) { + $result[count($result)-1]['finish'] = $tag[0][1]; + } + } + } + + if ($count != 0) + throw new IPF_Template_Exception('Blocks are not nested properly.'); + + return $result; + } + + function compileBlocks() + { + $tplcontent = $this->templateContent; + $extendedTemplate = ''; + // Match extends on the first line of the template + if (preg_match("!{extends\s['\"](.*?)['\"]}!", $tplcontent, $_match)) { + $extendedTemplate = $_match[1]; + } + // Get the blocks in the current template + $blocks = self::toplevelBlocks($this->templateContent); + $cnt = count($blocks); + // Compile the blocks + for ($i=0; $i<$cnt; $i++) { + $blockName = $blocks[$i]['name']; + if (!isset($this->_extendBlocks[$blockName]) or false !== strpos($this->_extendBlocks[$blockName], '~~{~~superblock~~}~~')) { + $compiler = clone($this); + $compiler->templateContent = substr($this->templateContent, $blocks[$i]['start'], $blocks[$i]['finish'] - $blocks[$i]['start']); + $_tmp = $compiler->compile(); + if (!isset($this->_extendBlocks[$blockName])) { + $this->_extendBlocks[$blockName] = $_tmp; + } else { + $this->_extendBlocks[$blockName] = str_replace('~~{~~superblock~~}~~', $_tmp, $this->_extendBlocks[$blockName]); + } + } + } + if (strlen($extendedTemplate) > 0) { + // The template of interest is now the extended template + // as we are not in a base template + $this->templateContent = $this->environment->loadTemplateFile($extendedTemplate); + $this->compileBlocks(); //It will recurse to the base template. + } else { + // Replace the current blocks by a place holder + if ($cnt) { + $this->templateContent = preg_replace("!{block\s(\S+?)}(.*?){/block}!s", "{block $1}", $tplcontent, -1); + } + } + } + + private function _callback($matches) + { + list(,$tag, $firstcar) = $matches; + if (!preg_match('/^\$|[\'"]|[a-zA-Z\/]$/', $firstcar)) { + trigger_error(sprintf('Invalid tag syntax: %s', $tag), E_USER_ERROR); + return ''; + } + if (in_array($firstcar, array('$', '\'', '"'))) { + if ('blocktrans' !== end($this->_blockStack)) { + return '_parseVariable($tag).'); ?>'; + } else { + $tok = explode('|', $tag); + $this->_transStack[substr($tok[0], 1)] = $this->_parseVariable($tag); + return '%%'.substr($tok[0], 1).'%%'; + } + } else { + if (!preg_match('/^(\/?[a-zA-Z0-9_]+)(?:(?:\s+(.*))|(?:\((.*)\)))?$/', $tag, $m)) { + trigger_error(sprintf('Invalid function syntax: %s', $tag), E_USER_ERROR); + return ''; + } + if (count($m) == 4){ + $m[2] = $m[3]; + } + if (!isset($m[2])) $m[2] = ''; + if($m[1] == 'ldelim') return '{'; + if($m[1] == 'rdelim') return '}'; + if ($m[1] != 'include') { + return '_parseFunction($m[1], $m[2]).'?>'; + } else { + return $this->_parseFunction($m[1], $m[2]); + } + } + } + + private function _parseVariable($expr) + { + $tok = explode('|', $expr); + $res = $this->_parseFinal(array_shift($tok), self::$allowedInVar); + foreach ($tok as $modifier) { + if (!preg_match('/^(\w+)(?:\:(.*))?$/', $modifier, $m)) { + trigger_error(sprintf('Invalid modifier syntax: (%s) %s', $expr, $modifier), E_USER_ERROR); + return ''; + } + + if ($this->environment->hasModifier($m[1])) { + trigger_error(sprintf('Unknown modifier: (%s) %s', $expr, $m[1]), E_USER_ERROR); + return ''; + } + + $mod = $this->environment->getModifier($m[1]); + + if (isset($m[2])) { + $res = $mod.'('.$res.','.$m[2].')'; + } else { + $res = $mod.'('.$res.')'; + } + } + return $res; + } + + private function _parseFunction($name, $args) + { + switch ($name) { + case 'if': + $res = 'if ('.$this->_parseFinal($args, self::$allowedInExpr).'): '; + $this->_blockStack[] = 'if'; + break; + case 'else': + if (end($this->_blockStack) != 'if') { + trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); + } + $res = 'else: '; + break; + case 'elseif': + if (end($this->_blockStack) != 'if') { + trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); + } + $res = 'elseif('.$this->_parseFinal($args, self::$allowedInExpr).'):'; + break; + case 'foreach': + $tokens = $this->tokenize($args, self::$allowedForeach, array(';')); + $asFound = false; + $scopeVars = array('foreach_counter0', 'foreach_counter', 'foreach_first'); + foreach ($tokens as $token) { + if (!is_array($token)) + continue; + if ($asFound) { + if ($token[0] == T_VARIABLE) + $scopeVars[] = substr($token[1], 1); + } else { + if ($token[0] == T_AS) + $asFound = true; + } + } + $res = + '$t->push(\'' . implode('\', \'', $scopeVars) . '\'); ' . + '$t->_vars[\'foreach_counter0\'] = 0;' . + '$t->_vars[\'foreach_counter\'] = 1;' . + '$t->_vars[\'foreach_first\'] = true;' . + 'foreach ('.$this->compileExpression($tokens).'): '; + $this->_blockStack[] = 'foreach'; + break; + case 'while': + $res = 'while('.$this->_parseFinal($args, self::$allowedInExpr).'):'; + $this->_blockStack[] = 'while'; + break; + case '/foreach': + if(end($this->_blockStack) != 'foreach'){ + trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); + } + array_pop($this->_blockStack); + $res = + '$t->_vars[\'foreach_counter0\'] = $t->_vars[\'foreach_counter0\'] + 1;' . + '$t->_vars[\'foreach_counter\'] = $t->_vars[\'foreach_counter\'] + 1;' . + '$t->_vars[\'foreach_first\'] = false;' . + 'endforeach; ' . + '$t->pop(); '; + break; + case '/if': + case '/while': + $short = substr($name,1); + if(end($this->_blockStack) != $short){ + trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); + } + array_pop($this->_blockStack); + $res = 'end'.$short.'; '; + break; + case 'assign': + $res = $this->_parseFinal($args, self::$allowedAssign).'; '; + break; + case 'literal': + if (count($this->_literals)) { + $res = '?>'.array_shift($this->_literals).'_extendBlocks[$args])) + $res = '?>'.$this->_extendBlocks[$args].'~~{~~superblock~~}~~_parseFinal($args, self::$allowedAssign); + $res = 'echo(__('.$argfct.'));'; + break; + case 'blocktrans': + $this->_blockStack[] = 'blocktrans'; + $res = ''; + $this->_transStack = array(); + if ($args) { + $this->_transPlural = true; + $_args = $this->_parseFinal($args, self::$allowedAssign, array(';', '[', ']')); + $res .= '$_b_t_c='.trim($_args).'; '; + } + $res .= 'ob_start(); '; + break; + case '/blocktrans': + $short = substr($name,1); + if(end($this->_blockStack) != $short){ + trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); + } + $res = ''; + if ($this->_transPlural) { + $res .= '$_b_t_p=ob_get_contents(); ob_end_clean(); echo('; + $res .= 'IPF_Translation::sprintf(_n($_b_t_s, $_b_t_p, $_b_t_c), array('; + $_tmp = array(); + foreach ($this->_transStack as $key=>$_trans) { + $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_SafeString::value('.$_trans.')'; + } + $res .= implode(', ', $_tmp); + unset($_trans, $_tmp); + $res .= ')));'; + $this->_transStack = array(); + } else { + $res .= '$_b_t_s=ob_get_contents(); ob_end_clean(); '; + if (count($this->_transStack) == 0) { + $res .= 'echo(__($_b_t_s)); '; + } else { + $res .= 'echo(IPF_Translation::sprintf(__($_b_t_s), array('; + $_tmp = array(); + foreach ($this->_transStack as $key=>$_trans) { + $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_SafeString::value('.$_trans.')'; + } + $res .= implode(', ', $_tmp); + unset($_trans, $_tmp); + $res .= '))); '; + $this->_transStack = array(); + } + } + $this->_transPlural = false; + array_pop($this->_blockStack); + break; + case 'plural': + $res = '$_b_t_s=ob_get_contents(); ob_end_clean(); ob_start(); '; + break; + case 'include': + $argfct = preg_replace('!^[\'"](.*)[\'"]$!', '$1', $args); + $includedTemplateContent = $this->environment->loadTemplateFile($argfct); + $_comp = new IPF_Template_Compiler($includedTemplateContent, $this->environment); + $res = $_comp->compile(); + break; + default: + $_start = true; + $oname = $name; + if (substr($name, 0, 1) == '/') { + $_start = false; + $name = substr($name, 1); + } + // Here we should allow custom blocks. + + // Here we start the template tag calls at the template tag + // {tag ...} is not a block, so it must be a function. + if (!$this->environment->isTagAllowed($name)) { + trigger_error(sprintf('The function tag "%s" is not allowed.', $name), E_USER_ERROR); + } + // $argfct is a string that can be copy/pasted in the PHP code + // but we need the array of args. + $argfct = $this->_parseFinal($args, self::$allowedAssign); + + $res = '$_extra_tag = new '.$this->environment->getTag($name).'($t);'; + if ($_start) { + $res .= '$_extra_tag->start('.$argfct.'); '; + } else { + $res .= '$_extra_tag->end('.$argfct.'); '; + } + } + return $res; + } + + private function _parseFinal($string, &$allowed, $exceptchar=array(';')) + { + $tokens = $this->tokenize($string, $allowed, $exceptchar); + return $this->compileExpression($tokens); + } + + private function tokenize($string, $allowed, $exceptchar) + { + $tokens = token_get_all(''); + $result = array(); + foreach ($tokens as $tok) { + if (is_array($tok)) { + list($type, $str) = $tok; + if ($type == T_OPEN_TAG || $type == T_CLOSE_TAG) { + // skip + } elseif (in_array($type, $allowed)) { + $result[] = $tok; + } else { + throw new IPF_Template_Exception(sprintf('Invalid syntax: (%s) %s.', $string, $str)); + } + } else { + if (in_array($tok, $exceptchar)) { + trigger_error(sprintf('Invalid character: (%s) %s.', $string, $tok), E_USER_ERROR); + } else { + $result[] = $tok; + } + } + } + return $result; + } + + private function compileExpression($tokens) + { + $result = ''; + foreach ($tokens as $tok) { + if (is_array($tok)) { + list($type, $str) = $tok; + if ($type == T_VARIABLE) { + $result .= '$t->_vars[\''.substr($str, 1).'\']'; + } else { + $result .= $str; + } + } else { + if ($tok == '.') { + $result .= '->'; + } elseif ($tok == '~') { + $result .= '.'; + } else { + $result .= $tok; + } + } + } + return $result; + } +} + +IPF_Template_Compiler::init(); + diff --git a/lib/context.php b/lib/context.php new file mode 100644 index 0000000..09dc03c --- /dev/null +++ b/lib/context.php @@ -0,0 +1,68 @@ +_vars = new IPF_Template_ContextVars($vars); + } + + function get($var) + { + if (isset($this->_vars[$var])) { + return $this->_vars[$var]; + } + return ''; + } + + function set($var, $value) + { + $this->_vars[$var] = $value; + } + + public function push() + { + $vars = func_get_args(); + $frame = array(); + foreach ($vars as $var) { + $frame[$var] = $this->get($var); + } + $this->stack[] = $frame; + } + + public function pop() + { + $frame = array_pop($this->stack); + foreach ($frame as $var => $val) + $this->set($var, $val); + } +} + +class IPF_Template_ContextVars extends ArrayObject +{ + function offsetGet($prop) + { + if (!$this->offsetExists($prop)) { + return ''; + } + return parent::offsetGet($prop); + } + + function __get($prop) + { + if (isset($this->$prop)) { + return $this->$prop; + } else { + return $this->offsetGet($prop); + } + } + + function __toString() + { + return var_export($this, true); + } +} + diff --git a/lib/environment.php b/lib/environment.php new file mode 100644 index 0000000..eb7834f --- /dev/null +++ b/lib/environment.php @@ -0,0 +1,61 @@ + class) + public $tags = array(); + + public function isTagAllowed($name) + { + return isset($this->tags[$name]); + } + + public function getTag($name) + { + if (isset($this->tags[$name])) + return $this->tags[$name]; + else + throw new IPF_Template_Exception('Tag '.$name.' is not defined.'); + } + + // Dictionary of modifiers (modifier name => function) + public $modifiers = array( + 'upper' => 'strtoupper', + 'lower' => 'strtolower', + 'escxml' => 'htmlspecialchars', + 'escape' => 'IPF_Template_Modifier::escape', + 'strip_tags' => 'strip_tags', + 'escurl' => 'rawurlencode', + 'capitalize' => 'ucwords', + 'debug' => 'print_r', // Not var_export because of recursive issues. + 'fulldebug' => 'var_export', + 'count' => 'count', + 'nl2br' => 'nl2br', + 'trim' => 'trim', + 'unsafe' => 'IPF_Template_SafeString::markSafe', + 'safe' => 'IPF_Template_SafeString::markSafe', + 'date' => 'IPF_Template_Modifier::dateFormat', + 'time' => 'IPF_Template_Modifier::timeFormat', + 'floatformat' => 'IPF_Template_Modifier::floatFormat', + 'limit_words' => 'IPF_Template_Modifier::limitWords', + 'limit_chars' => 'IPF_Template_Modifier::limitCharacters', + ); + + public function hasModifier($name) + { + return isset($this->modifiers[$name]); + } + + public function getModifier($name) + { + if (isset($this->modifiers[$name])) + return $this->modifiers[$name]; + else + throw new IPF_Template_Exception('Modifier '.$name.' is not defined.'); + } +} + diff --git a/lib/environment_filesystem.php b/lib/environment_filesystem.php new file mode 100644 index 0000000..2d4f4b8 --- /dev/null +++ b/lib/environment_filesystem.php @@ -0,0 +1,51 @@ +folders as $folder) { + if (file_exists($folder.'/'.$filename)) { + return file_get_contents($folder.'/'.$filename); + } + } + throw new IPF_Template_Exception(sprintf('Template file not found: %s', $filename)); + } + + public function getCompiledTemplateName($template) + { + $_tmp = var_export($this->folders, true); + $filename = $this->cache.'/IPF_Template-'.md5($_tmp.(string)$template).'.phps'; + if ($this->debug or !file_exists($filename)) { + $this->write($filename, $template->compile()); + } + return $filename; + } + + private function write($filename, $content) + { + $fp = @fopen($filename, 'a'); + if ($fp !== false) { + flock($fp, LOCK_EX); + ftruncate($fp, 0); + rewind($fp); + fwrite($fp, $content, strlen($content)); + flock($fp, LOCK_UN); + fclose($fp); + @chmod($filename, 0777); + return true; + } else { + throw new IPF_Template_Exception(sprintf('Cannot write the compiled template: %s', $filename)); + } + return false; + } +} + diff --git a/lib/exception.php b/lib/exception.php new file mode 100644 index 0000000..2aa9755 --- /dev/null +++ b/lib/exception.php @@ -0,0 +1,6 @@ += $n) { + $out = trim($out); + return (strlen($out) == strlen($str)) ? $out : $out.$end_char; + } + } + } +} + diff --git a/lib/safestring.php b/lib/safestring.php new file mode 100644 index 0000000..0bd145b --- /dev/null +++ b/lib/safestring.php @@ -0,0 +1,31 @@ +value; + if ($safe) + return $mixed; + return htmlspecialchars($mixed, ENT_COMPAT, 'UTF-8'); + } + + function __construct($mixed, $safe=false) + { + $this->value = self::value($mixed, $safe); + } + + function __toString() + { + return $this->value; + } + + public static function markSafe($string) + { + return new IPF_Template_SafeString($string, true); + } +} + diff --git a/lib/tag.php b/lib/tag.php new file mode 100644 index 0000000..1a1ced2 --- /dev/null +++ b/lib/tag.php @@ -0,0 +1,22 @@ +context = $context; + } + + // variable list of arguments + function start() + { + } + + // variable list of arguments + function end() + { + } +} + diff --git a/lib/template.php b/lib/template.php new file mode 100644 index 0000000..269d435 --- /dev/null +++ b/lib/template.php @@ -0,0 +1,80 @@ +environment = $environment; + } + + abstract public function __toString(); + + abstract protected function content(); + + public function compile() + { + $compiler = new IPF_Template_Compiler($this->content(), $this->environment); + return $compiler->getCompiledTemplate(); + } + + public function render($c=null) + { + $compiled_template = $this->environment->getCompiledTemplateName($this); + ob_start(); + $t = $c; + try { + include $compiled_template; + } catch (Exception $e) { + ob_clean(); + throw $e; + } + $a = ob_get_contents(); + ob_end_clean(); + return $a; + } +} + +class IPF_Template_File extends IPF_Template +{ + private $filename; + + function __construct($filename, IPF_Template_Environment $environment) + { + parent::__construct($environment); + $this->filename = $filename; + } + + public function __toString() + { + return $this->filename; + } + + protected function content() + { + return $this->environment->loadTemplateFile($this->filename); + } +} + +class IPF_Template_String extends IPF_Template +{ + private $template; + + function __construct($template, IPF_Template_Environment $environment) + { + parent::__construct($environment); + $this->template = $template; + } + + public function __toString() + { + return $this->template; + } + + protected function content() + { + return $this->template; + } +} +