function SmallTAL_Compiler(&$returnValue, $template, $page, $variables = null, $directories = null) { $compilerData = array('metal' => array(), 'keywords' => array('define', 'condition', 'repeat', 'replace', 'content', 'omit-tag', 'attributes'), 'regexp' => array('tag' => '/<((?:\\w+:)?\\w+)(\\s+[^<>]+?)?\\s*(\\/)?>/i', 'tagWithTal' => '/<((?:\\w+:)?\\w+)(\\s+[^<>]+?)??\\s+tal:(?:define|condition|repeat|replace|content|attributes|omit-tag)=([\'"])(.*?)\\3(\\s+[^<>]+?)??\\s*(\\/)?>/i', 'tagWithAttribute' => '/<((?:\\w+:)?\\w+)(\\s+[^<>]+?)??\\s+%s=([\'"])(.*?)\\3(\\s+[^<>]+?)??\\s*(\\/)?>/i', 'xmlns' => '/<html(\\s+[^<>]+?)??\\s+xmlns:%s=([\'"])(.*?)\\2(\\s+[^<>]+?)??\\s*(\\/)?>/i', 'xmlDeclaration' => '/^(?:\\s*<(\\?xml.*?\\?>))/', 'tagAttributes' => '/(?<=\\s)((?:[\\w-]+\\:)?[\\w-]+)=(?:([\'"])(.*?)\\2|([^>\\s\'"]+))/')); if (file_exists($directories['templates']) && is_dir($directories['templates']) && file_exists($directories['templates'] . '/' . $template) && is_dir($directories['templates'] . '/' . $template) && (file_exists($directories['temp']) || mkdir($directories['temp'], 0700)) && is_dir($directories['temp'])) { if (file_exists($directories['templates'] . '/' . $template . '.metal.html') && false !== ($metalContent = file_get_contents($directories['templates'] . '/' . $template . '.metal.html'))) { SmallTAL_Compiler_ParseMETAL($returnValue, $compilerData, $metalContent, false); } if (file_exists($directories['templates'] . '/' . $template . '/' . $page . '.html') && false !== ($content = file_get_contents($directories['templates'] . '/' . $template . '/' . $page . '.html'))) { SmallTAL_Compiler_ParseMETAL($returnValue, $compilerData, $content, true); $results = array(); // Searching for the metal:use-macro attribute if (preg_match_all(sprintf($compilerData['regexp']['tagWithAttribute'], 'metal:use-macro'), $content, $results, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { for ($i = count($results) - 1; $i > -1; $i--) { if (array_key_exists($results[$i][4][0], $compilerData['metal'])) { $position = $results[$i][0][1] + strlen($results[$i][0][0]); if (!array_key_exists(6, $results[$i])) { $position = SmallTAL_Compiler_FindEndTag($content, $results[$i][1][0], $position); } $tmpVariable = substr($content, $results[$i][0][1], $position - $results[$i][0][1]); $macro = $compilerData['metal'][$results[$i][4][0]]; // Searching for slots if (!array_key_exists(6, $results[$i])) { $resultsSlots = array(); $slots = array(); if (preg_match_all(sprintf($compilerData['regexp']['tagWithAttribute'], 'metal:fill-slot'), $tmpVariable, $resultsSlots, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { for ($j = count($resultsSlots) - 1; $j > -1; $j--) { if (array_key_exists($resultsSlots[$j][4][0], $slots)) { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_DuplicatedSlot'), $slotName); } $slots[$resultsSlots[$j][4][0]] = preg_replace(sprintf($compilerData['regexp']['tagWithAttribute'], 'metal:fill-slot'), '<${1}${2}${5}${6}>', substr($tmpVariable, $resultsSlots[$j][0][1], SmallTAL_Compiler_FindEndTag($tmpVariable, $resultsSlots[$j][1][0], $resultsSlots[$j][0][1] + strlen($resultsSlots[$j][0][0])) - $resultsSlots[$j][0][1]), -1, $count); } } if (0 < count($slots)) { if (preg_match_all(sprintf($compilerData['regexp']['tagWithAttribute'], 'metal:define-slot'), $macro, $resultsSlots, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { for ($j = count($resultsSlots) - 1; $j > -1; $j--) { if (array_key_exists($resultsSlots[$j][4][0], $slots)) { $macro = str_replace(substr($macro, $resultsSlots[$j][0][1], SmallTAL_Compiler_FindEndTag($compilerData['metal'][$macroName], $resultsSlots[$j][1][0], $resultsSlots[$j][0][1] + strlen($resultsSlots[$j][0][0])) - $resultsSlots[$j][0][1]), $resultsSlots[$j][4][0], $macro); } else { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_UnknownSlot'), $results[$j][4][0]); } } } } } $content = str_replace($tmpVariable, $macro, $content); } else { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_UnknownMacro'), $results[$i][4][0]); } } } $output = '<?php if(defined(\'SmallTAL\')){$localVariables=array(\'defined\'=>array(),\'repeat\'=>array(),\'stack\'=>array(\'defined\'=>array(),\'repeat\'=>array()),\'template\'=>array());?>'; $localVariablesIndex = 0; $count = 0; //TODO: Variables optimization round if (null != $content) { if ($tagsCount = preg_match_all($compilerData['regexp']['tagWithTal'], $content, $tags, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { $localVariablesIndex = 0; for ($i = $tagsCount - 1; $i > -1; $i--) { $tagAttributes = array(); $attributes = array(); if ($attributesCount = preg_match_all($compilerData['regexp']['tagAttributes'], $tags[$i][0][0], $attributes, PREG_SET_ORDER)) { for ($j = 0; $j < $attributesCount; $j++) { $tagAttributes[$attributes[$j][1]] = empty($attributes[$j][2]) && array_key_exists(4, $attributes[$j]) ? $attributes[$j][4] : $attributes[$j][3]; } } unset($attributes); $changes = array('attributes' => array(), 'outside' => array('pre' => null, 'post' => null), 'inside' => array('pre' => null, 'post' => null)); foreach ($compilerData['keywords'] as $keyword) { if (array_key_exists('tal:' . $keyword, $tagAttributes)) { switch ($keyword) { case 'define': $tmpVariable = array(); $tmpVariable['count'] = preg_match_all('/[\\s]*(?:(?:(local|world)[\\s]+)?(.+?)[\\s]+(.+?)[\\s]*(?:(?<!;);(?!;)|$))+?/', $tagAttributes['tal:define'], $tmpVariable['elements'], PREG_SET_ORDER); for ($j = 0; $j < $tmpVariable['count']; $j++) { $tmpVariable['name'] = str_replace('\'', ''', $tmpVariable['elements'][$j][2]); $changes['outside']['pre'] .= '$localVariables[\'template\'][' . $localVariablesIndex . ']=(' . SmallTAL_Compiler_ParseTALES($tmpVariable['elements'][$j][3], $tagAttributes, '$tmpVariable') . '&&!$tmpVariable[1]);if($localVariables[\'template\'][' . $localVariablesIndex++ . ']){SmallTAL_LocalVariablesPush($localVariables,\'defined\',\'' . $tmpVariable['name'] . '\',$tmpVariable[0]);}unset($tmpVariable);'; if ('world' != $tmpVariable['elements'][$j][1]) { $changes['outside']['post'] = 'if($localVariables[\'template\'][' . ($localVariablesIndex - 1) . ']){SmallTAL_LocalVariablesPop($localVariables,\'defined\',\'' . $tmpVariable['name'] . '\');unset($localVariables[\'template\'][' . ($localVariablesIndex - 1) . ']);}' . $changes['outside']['post']; } } unset($tmpVariable); break; case 'condition': $changes['outside']['pre'] .= 'if(' . SmallTAL_Compiler_ParseTALES($tagAttributes['tal:condition'], $tagAttributes, '$localVariables[\'template\'][' . $localVariablesIndex . ']', true, true) . '){'; $changes['outside']['post'] = '}unset($localVariables[\'template\'][' . $localVariablesIndex++ . ']);' . $changes['outside']['post']; break; case 'repeat': $tmpVariable['elements'] = explode(" ", $tagAttributes['tal:repeat'], 2); $tmpVariable['name'] = str_replace('\'', ''', $tmpVariable['elements'][0]); $changes['outside']['pre'] .= sprintf('if((%1$s)&&($localVariables[\'template\'][%2$d][1]||is_array($localVariables[\'template\'][%2$d][0]))){$localVariables[\'template\'][%3$d]=array(false,false);if(!$localVariables[\'template\'][%2$d][1]){($localVariables[\'template\'][%3$d][0]=true)&&SmallTAL_LocalVariablesPush($localVariables,\'repeat\',\'%4$s\',null);($localVariables[\'template\'][%3$d][1]=true)&&SmallTAL_LocalVariablesPush($localVariables,\'defined\',\'%4$s\',null);$localVariables[\'repeat\'][\'%4$s\'][\'index\']=-1;$localVariables[\'repeat\'][\'%4$s\'][\'length\']=count($localVariables[\'template\'][%2$d][0]);}do{if(!$localVariables[\'template\'][%2$d][1]){$localVariables[\'defined\'][\'%4$s\']=array_shift($localVariables[\'template\'][%2$d][0]);$localVariables[\'repeat\'][\'%4$s\'][\'index\']++;$localVariables[\'repeat\'][\'%4$s\'][\'number\']=$localVariables[\'repeat\'][\'%4$s\'][\'index\']+1;$localVariables[\'repeat\'][\'%4$s\'][\'even\']=($localVariables[\'repeat\'][\'%4$s\'][\'number\']%%2?true:false);$localVariables[\'repeat\'][\'%4$s\'][\'odd\']=!$localVariables[\'repeat\'][\'%4$s\'][\'even\'];$localVariables[\'repeat\'][\'%4$s\'][\'start\']=($localVariables[\'repeat\'][\'%4$s\'][\'index\']?false:true);$localVariables[\'repeat\'][\'%4$s\'][\'end\']=(($localVariables[\'repeat\'][\'%4$s\'][\'number\']==$localVariables[\'repeat\'][\'%4$s\'][\'length\'])?true:false);$localVariables[\'repeat\'][\'%4$s\'][\'letter\']=SmallTAL_NumberToLetter($localVariables[\'repeat\'][\'%4$s\'][\'index\']);$localVariables[\'repeat\'][\'%4$s\'][\'Letter\']=strtoupper($localVariables[\'repeat\'][\'%4$s\'][\'letter\']);}', SmallTAL_Compiler_ParseTALES($tmpVariable['elements'][1], $tagAttributes, '$localVariables[\'template\'][' . $localVariablesIndex . ']'), $localVariablesIndex++, $localVariablesIndex, $tmpVariable['name']); $changes['outside']['post'] = sprintf('}while(!$localVariables[\'template\'][%1$d][1]&&!empty($localVariables[\'template\'][%1$d][0]));$localVariables[\'template\'][%2$d][0]&&SmallTAL_LocalVariablesPop($localVariables,\'repeat\',\'%3$s\');$localVariables[\'template\'][%2$d][1]&&SmallTAL_LocalVariablesPop($localVariables,\'defined\',\'%3$s\');unset($localVariables[\'template\'][%1$d],$localVariables[\'template\'][%2$d]);}', $localVariablesIndex - 1, $localVariablesIndex++, $tmpVariable['name']) . $changes['outside']['post']; break; case 'replace': case 'content': if ('content' == $keyword || 'replace' == $keyword && !array_key_exists('tal:content', $tagAttributes)) { $tmpVariable = array(); if ('structure ' == substr($tagAttributes['tal:' . $keyword], 0, 10) || 'text ' == substr($tagAttributes['tal:' . $keyword], 0, 5)) { $tmpVariable['parameters'] = explode(" ", 'content' == $keyword ? $tagAttributes['tal:content'] : $tagAttributes['tal:replace'], 2); } else { $tmpVariable['parameters'] = array("text", $tagAttributes['tal:' . $keyword]); } $tmpVariable['pre'] = 'if(' . SmallTAL_Compiler_ParseTALES($tmpVariable['parameters'][1], $tagAttributes, '$localVariables[\'template\'][' . $localVariablesIndex . ']') . '&&!$localVariables[\'template\'][' . $localVariablesIndex . '][1]&&!is_null($localVariables[\'template\'][' . $localVariablesIndex . '][0])){echo(' . ('text' == $tmpVariable['parameters'][0] ? 'str_replace(array(\'&\',\'<\',\'>\'),array(\'&\',\'<\',\'>\'),' : null) . '(is_bool($localVariables[\'template\'][' . $localVariablesIndex . '][0])?($localVariables[\'template\'][' . $localVariablesIndex . '][0]?1:0):$localVariables[\'template\'][' . $localVariablesIndex . '][0])' . ('text' == $tmpVariable['parameters'][0] ? ')' : null) . ');}elseif($localVariables[\'template\'][' . $localVariablesIndex . '][1]){'; $tmpVariable['post'] = '}unset($localVariables[\'template\'][' . $localVariablesIndex++ . ']);'; if ('content' == $keyword) { $changes['inside']['pre'] = $tmpVariable['pre'] . $changes['inside']['pre']; $changes['inside']['post'] .= $tmpVariable['post']; } else { $changes['outside']['pre'] .= $tmpVariable['pre']; $changes['outside']['post'] = $tmpVariable['post'] . $changes['outside']['post']; } unset($tmpVariable); } break; case 'attributes': if (!array_key_exists('tal:content', $tagAttributes)) { $tmpVariable = array(); $tmpVariable['count'] = preg_match_all('/(?:[\\s]*(.+?)[\\s]+(.+?)[\\s]*(?:(?<!;);(?!;)|$))+?/', $tagAttributes['tal:attributes'], $tmpVariable['elements'], PREG_SET_ORDER); for ($j = 0; $j < $tmpVariable['count']; $j++) { $changes['attributes'][$tmpVariable['elements'][$j][1]] = $tmpVariable['elements'][$j][2]; } } break; case 'omit-tag': $changes['outside']['pre'] .= '$localVariables[\'template\'][' . $localVariablesIndex . ']=!(' . SmallTAL_Compiler_ParseTALES($tagAttributes['tal:omit-tag'], $tagAttributes, '$tmpVariable', true) . ');unset($tmpVariable);if($localVariables[\'template\'][' . $localVariablesIndex . ']){'; // If isn't selfclosed OR if I already need to put some code between starting and closing tag, I put some data "inside" // TODO: a regexp to remove ? >< ?php and {} if (!array_key_exists(6, $tags[$i]) || isset($changes['inside']['pre']) || isset($changes['inside']['post']) || 0 < count($changes['attributes'])) { $changes['inside']['pre'] = '}' . $changes['inside']['pre']; $changes['inside']['post'] .= 'if($localVariables[\'template\'][' . $localVariablesIndex . ']){'; } $changes['outside']['post'] = '}unset($localVariables[\'template\'][' . $localVariablesIndex++ . ']);' . $changes['outside']['post']; break; } } } if (0 < count($changes['attributes']) || isset($changes['outside']['pre']) || isset($changes['outside']['post']) || isset($changes['inside']['pre']) || isset($changes['inside']['post'])) { $tmpVariable = array(); $tmpVariable['startTag'] = $tags[$i][0][0]; $tmpVariable['attributes'] = null; foreach ($changes['attributes'] as $name => $value) { $tmpVariable['attributes'] .= '<?php if(' . SmallTAL_Compiler_ParseTALES($value, $tagAttributes, '$tmpVariable') . (!array_key_exists($name, $tagAttributes) ? '&&!$tmpVariable[1]' : null) . '){?> ' . $name . '="<?php echo(' . (array_key_exists($name, $tagAttributes) ? '$tmpVariable[1]?\'' . str_replace('\'', ''', $tagAttributes[$name]) . '\':' : null) . '(is_bool($tmpVariable[0])?($tmpVariable[0]?1:0):$tmpVariable[0]));?>"<?php }unset($tmpVariable);?>'; } foreach ($tagAttributes as $name => $value) { if (array_key_exists($name, $changes['attributes']) || 'xmlns:tal' == $name || 'tal:' == substr($name, 0, 4)) { $tmpVariable['startTag'] = preg_replace(sprintf($compilerData['regexp']['tagWithAttribute'], $name), '<${1}${2}${5}${6}>', $tmpVariable['startTag']); } } if ($tags[$i][0][0] != $tmpVariable['startTag']) { $content = substr_replace($content, $tmpVariable['startTag'], $tags[$i][0][1], strlen($tags[$i][0][0])); } $tmpVariable['startLength'] = strlen($tmpVariable['startTag']); $tmpVariable['endPosition'] = $tags[$i][0][1] + $tmpVariable['startLength']; $tmpVariable['endLength'] = 0; $tmpVariable['startTagSelfClosed'] = array_key_exists(6, $tags[$i]); $tmpVariable['startTag'] = substr($tmpVariable['startTag'], 0, -($tmpVariable['startTagSelfClosed'] ? 2 : 1)); if ($tmpVariable['startTagSelfClosed'] && (isset($changes['inside']['pre']) || isset($changes['inside']['post']))) { $content = substr_replace($content, $tmpVariable['startTag'] . '></' . $tags[$i][1][0] . '>', $tags[$i][0][1], $tmpVariable['startLength']); $tmpVariable['startTagSelfClosed'] = false; $tmpVariable['endPosition'] += strlen($tags[$i][1][0]) + 2; $tmpVariable['endLength'] = strlen($tags[$i][1][0]) + 3; $tmpVariable['startLength'] -= 1; } elseif (!$tmpVariable['startTagSelfClosed']) { $tmpVariable['endPosition'] = SmallTAL_Compiler_FindEndTag($content, $tags[$i][1][0], $tmpVariable['endPosition'], $tmpVariable['endLength']); } // If is an empty node and we need to write inside it, we need an optimization round if ($tmpVariable['endPosition'] - $tmpVariable['endLength'] == $tags[$i][0][1] + strlen($tmpVariable['startTag']) + ($tmpVariable['startTagSelfClosed'] ? 2 : 1) && isset($changes['inside']['pre']) && isset($changes['inside']['post'])) { $changes['inside']['pre'] .= $changes['inside']['post']; $changes['inside']['post'] = null; $changes['inside']['pre'] = str_replace('else{}', '', $changes['inside']['pre']); } if (!$tmpVariable['startTagSelfClosed'] || isset($changes['inside']['post']) || isset($changes['outside']['post'])) { $content = substr_replace($content, (isset($changes['inside']['post']) ? '<?php ' . $changes['inside']['post'] . '?>' : null) . substr($content, $tmpVariable['endPosition'] - $tmpVariable['endLength'], $tmpVariable['endLength']) . (isset($changes['outside']['post']) ? '<?php ' . $changes['outside']['post'] . '?>' : null), $tmpVariable['endPosition'] - $tmpVariable['endLength'], $tmpVariable['endLength']); } if (isset($tmpVariable['attributes']) || isset($changes['outside']['pre']) || isset($changes['inside']['pre'])) { $content = substr_replace($content, (isset($changes['outside']['pre']) ? '<?php ' . $changes['outside']['pre'] . '?>' : null) . $tmpVariable['startTag'] . $tmpVariable['attributes'] . ($tmpVariable['startTagSelfClosed'] ? ' /' : null) . '>' . (isset($changes['inside']['pre']) ? '<?php ' . $changes['inside']['pre'] . '?>' : null), $tags[$i][0][1], $tmpVariable['startLength']); } unset($tmpVariable); } } } } $content = preg_replace(array(sprintf($compilerData['regexp']['xmlns'], 'tal'), sprintf($compilerData['regexp']['xmlns'], 'metal')), '<html${1}${4}${5}>', $content); $output .= str_replace("?>\n", "?>\n\n", str_replace("\r", '', $content)) . '<?php }?>'; $file = null; if (false !== ($temporaryFilename = tempnam($directories['temp'], 'TAL')) && false !== ($file = fopen($temporaryFilename, 'wb'))) { fwrite($file, $output); fclose($file); if ((file_exists($directories['cache']) || mkdir($directories['cache'], 0700)) && is_dir($directories['cache']) && (file_exists($directories['cache'] . '/' . $template) || mkdir($directories['cache'] . '/' . $template, 0700)) && is_dir($directories['cache'] . '/' . $template)) { if (!rename($temporaryFilename, $directories['cache'] . '/' . $template . '/' . $page . '.php')) { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_Rename'), $temporaryFilename); } } else { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_Cache'), null); } } else { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_TemporaryFile'), null); } } else { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_Template'), null); } } else { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_Path'), null); } }
function SmallTAL($template, $page, $variables = null, $directories = null) { $returnValue = array('errorCode' => constant('SmallTAL_Error_None')); if (is_null($variables) || !is_array($variables)) { $variables = array(); } if (array_key_exists('CONTEXT', $variables)) { unset($variables['CONTEXT']); } if (is_null($directories) || !is_array($directories)) { $directories = array(); } $elements = array('templates', 'cache', 'temp'); foreach ($elements as $element) { if (!array_key_exists($element, $directories)) { $directories[$element] = $element; } if (DIRECTORY_SEPARATOR == $directories[$element][strlen($directories[$element]) - 1]) { $directories[$element] = substr($directories[$element], 0, -1); } } if (file_exists($directories['templates'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.html')) { if ((file_exists($directories['cache']) || mkdir($directories['cache'], 0700)) && is_dir($directories['cache']) && ((file_exists($directories['cache'] . DIRECTORY_SEPARATOR . $template) || mkdir($directories['cache'] . DIRECTORY_SEPARATOR . $template, 0700)) && is_dir($directories['cache'] . DIRECTORY_SEPARATOR . $template))) { if (file_exists($directories['cache'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.php')) { $filesMTime = array('templateFile' => filemtime($directories['templates'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.html'), 'metalFile' => file_exists($tmpVariable = $directories['templates'] . DIRECTORY_SEPARATOR . $template . '.metal.html') ? filemtime($tmpVariable) : time(), 'smallTAL' => filemtime(__FILE__), 'smallTALCompiler' => filemtime('SmallTAL.Compiler.php')); $tmpVariable = filemtime($directories['cache'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.php'); $delete = false; while (!$delete && ($fileMTime = array_pop($filesMTime))) { if ($tmpVariable < $fileMTime) { $delete = true; } } if ($delete) { unlink($directories['cache'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.php'); } } if (!file_exists($directories['cache'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.php')) { require_once 'SmallTAL.Compiler.php'; SmallTAL_Compiler($returnValue, $template, $page, $variables, $directories); } if (file_exists($directories['cache'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.php')) { include $directories['cache'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.php'; } else { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_Cache'), $directories['cache'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.php'); } } else { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_Path'), null); } } else { SmallTAL_AddError($returnValue, constant('SmallTAL_Error_Template'), $directories['templates'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.html'); } return $returnValue; }