function NearTAL($template, $page, $variables = null, $directories = null) { $returnValue = array('errorCode' => constant('NearTAL_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(), 'nearTAL' => filemtime(__FILE__), 'nearTALCompiler' => filemtime('NearTAL.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 'NearTAL.Compiler.php'; NearTAL_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 { NearTAL_AddError($returnValue, constant('NearTAL_Error_Cache'), $directories['cache'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.php'); } } else { NearTAL_AddError($returnValue, constant('NearTAL_Error_Path'), null); } } else { NearTAL_AddError($returnValue, constant('NearTAL_Error_Template'), $directories['templates'] . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $page . '.html'); } return $returnValue; }
function NearTAL_Compiler(&$returnValue, $template, $page, $variables = null, $directories = null) { $compilerData = array('metal' => array(), 'keywords' => array('define', 'condition', 'repeat', 'replace', 'content', 'omit-tag', 'attributes')); 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 !== ($content = file_get_contents($directories['templates'] . '/' . $template . '.metal.html'))) { $parser = str_get_dom($content); NearTAL_Compiler_ParseMETAL($returnValue, $compilerData, $parser, false); unset($parser); } if (file_exists($directories['templates'] . '/' . $template . '/' . $page . '.html') && false !== ($content = file_get_contents($directories['templates'] . '/' . $template . '/' . $page . '.html'))) { $parser = str_get_dom($content); NearTAL_Compiler_ParseMETAL($returnValue, $compilerData, $parser, true); $nodes = $parser->select('[data-metal-use-macro]'); foreach ($nodes as $node) { $macroName = $node->getAttribute('data-metal-use-macro'); if (array_key_exists($macroName, $compilerData['metal'])) { $availableSlots = array(); $slots = $node('[data-metal-fill-slot]'); foreach ($slots as $slot) { $slotName = $slots->getAttribute('data-metal-fill-slot'); if (array_key_exists($slotName, $slots)) { NearTAL_AddError($returnValue, constant('NearTAL_Error_DuplicatedSlot'), $slotName); } $slot->deleteAttribute('data-metal-fill-slot'); $availableSlots[$slotName] = $slot->getOuterText(); } $node->setOuterText($compilerData['metal'][$macroName]); $slots = $node('[data-metal-define-slot]'); foreach ($slots as $slot) { $slotName = $slots->getAttribute('data-metal-define-slot'); if (array_key_exists($slotName, $availableSlots)) { $slot->setOuterText($availableSlots[$slotName]); } else { $slot->deleteAttribute('data-metal-define-slot'); NearTAL_AddError($returnValue, constant('NearTAL_Error_UnknownSlot'), $slotName); } } unset($availableSlots); } else { NearTAL_AddError($returnValue, constant('NearTAL_Error_UnknownMacro'), $macroName); } } $output = '<?php if(defined(\'NearTAL\')){$localVariables=array(\'defined\'=>array(),\'repeat\'=>array(),\'stack\'=>array(\'defined\'=>array(),\'repeat\'=>array()),\'template\'=>array()); ?>'; $localVariablesIndex = 0; $count = 0; $selectFilter = array(); foreach ($compilerData['keywords'] as $keyword) { $selectFilter[] = '[data-tal-' . $keyword . ']'; } $nodes = $parser->select(implode(',', $selectFilter)); $localVariablesIndex = 0; foreach ($nodes as $node) { $changes = array('attributes' => array(), 'outside' => array('pre' => null, 'post' => null), 'inside' => array('pre' => null, 'post' => null)); foreach ($compilerData['keywords'] as $keyword) { $attribute = trim($node->getAttribute('data-tal-' . $keyword)); if (!is_null($attribute)) { if (!is_array($attribute) && !empty($attribute)) { switch ($keyword) { case 'define': $tmpVariable = array(); $tmpVariable['count'] = preg_match_all('/[\\s]*(?:(?:(local|world)[\\s]+)?(.+?)[\\s]+(.+?)[\\s]*(?:(?<!;);(?!;)|$))+?/', $attribute, $tmpVariable['elements'], PREG_SET_ORDER); for ($j = 0; $j < $tmpVariable['count']; $j++) { $tmpVariable['name'] = str_replace('\'', ''', $tmpVariable['elements'][$j][2]); $fastAnswers = NearTAL_Compiler_ParseTALES($tmpVariable['compiledExpression'], $tmpVariable['elements'][$j][3], $node->attributes, '$tmpVariable'); $changes['outside']['pre'] .= '$localVariables[\'template\'][' . $localVariablesIndex . ']=(' . $tmpVariable['compiledExpression'] . '&&!$tmpVariable[1]);if($localVariables[\'template\'][' . $localVariablesIndex++ . ']){NearTAL_LocalVariablesPush($localVariables,\'defined\',\'' . $tmpVariable['name'] . '\',$tmpVariable[0]);}'; if ('world' != $tmpVariable['elements'][$j][1]) { $changes['outside']['post'] = 'if($localVariables[\'template\'][' . ($localVariablesIndex - 1) . ']){NearTAL_LocalVariablesPop($localVariables,\'defined\',\'' . $tmpVariable['name'] . '\');unset($localVariables[\'template\'][' . ($localVariablesIndex - 1) . ']);}' . $changes['outside']['post']; } } break; case 'condition': $fastAnswers = NearTAL_Compiler_ParseTALES($tmpVariable['compiledExpression'], $attribute, $node->attributes, '$localVariables[\'template\'][' . $localVariablesIndex . ']', true, false); if ($fastAnswers[1]) { $node->delete(); } else { $changes['outside']['pre'] .= 'if(' . $tmpVariable['compiledExpression'] . '){'; $changes['outside']['post'] = '}unset($localVariables[\'template\'][' . $localVariablesIndex++ . ']);' . $changes['outside']['post']; } break; case 'repeat': $tmpVariable['elements'] = explode(" ", $attribute, 2); $tmpVariable['name'] = str_replace('\'', ''', $tmpVariable['elements'][0]); $fastAnswers = NearTAL_Compiler_ParseTALES($tmpVariable['compiledExpression'], $tmpVariable['elements'][1], $node->attributes, '$localVariables[\'template\'][' . $localVariablesIndex . ']'); $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)&&NearTAL_LocalVariablesPush($localVariables,\'repeat\',\'%4$s\',null);($localVariables[\'template\'][%3$d][1]=true)&&NearTAL_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\']=NearTAL_NumberToLetter($localVariables[\'repeat\'][\'%4$s\'][\'index\']);$localVariables[\'repeat\'][\'%4$s\'][\'Letter\']=strtoupper($localVariables[\'repeat\'][\'%4$s\'][\'letter\']);}', $tmpVariable['compiledExpression'], $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]&&NearTAL_LocalVariablesPop($localVariables,\'repeat\',\'%3$s\');$localVariables[\'template\'][%2$d][1]&&NearTAL_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('data-tal-content', $node->attributes)) { $tmpVariable = array(); if ('structure ' == substr($attribute, 0, 10) || 'text ' == substr($attribute, 0, 5)) { $tmpVariable['parameters'] = explode(" ", $attribute, 2); } else { $tmpVariable['parameters'] = array("text", $attribute); } $fastAnswers = NearTAL_Compiler_ParseTALES($tmpVariable['compiledExpression'], $tmpVariable['parameters'][1], $node->attributes, '$localVariables[\'template\'][' . $localVariablesIndex . ']'); if (!$fastAnswers[0]) { if (!$fastAnswers[1]) { $tmpVariable['pre'] = 'if(' . $tmpVariable['compiledExpression'] . '&&!$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']; } } else { if ('content' == $keyword) { foreach ($node->children as $id => $child) { $node->deleteChild($id); } } else { $node->delete(); } } } } break; case 'attributes': $tmpVariable = array(); $tmpVariable['count'] = preg_match_all('/(?:[\\s]*(.+?)[\\s]+(.+?)[\\s]*(?:(?<!;);(?!;)|$))+?/', $attribute, $tmpVariable['elements'], PREG_SET_ORDER); for ($j = 0; $j < $tmpVariable['count']; $j++) { $changes['attributes'][$tmpVariable['elements'][$j][1]] = $tmpVariable['elements'][$j][2]; } break; case 'raw-attributes': $tmpVariable = array(); $tmpVariable['count'] = preg_match_all('/(?:[\\s]*(.+?)[\\s]*$)/', $attribute, $tmpVariable['elements'], PREG_SET_ORDER); if (1 == $tmpVariable['count']) { $changes['raw-attributes'] = $tmpVariable['elements'][$j][1]; } break; case 'omit-tag': $fastAnswers = NearTAL_Compiler_ParseTALES($tmpVariable['compiledExpression'], $attribute, $node->attributes, '$tmpVariable', true); if (!$fastAnswers[0] && !$fastAnswers[1]) { $changes['outside']['pre'] .= '$localVariables[\'template\'][' . $localVariablesIndex . ']=!(' . $tmpVariable['compiledExpression'] . ');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 (!$node->self_close || 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; } } $node->deleteAttribute('data-tal-' . $keyword); } } if (!empty($changes['attributes'])) { foreach ($changes['attributes'] as $name => $value) { $fastAnswers = NearTAL_Compiler_ParseTALES($tmpVariable['compiledExpression'], $value, $node->attributes, '$tmpVariable'); if (!$fastAnswers[0]) { if (!$fastAnswers[1]) { $tmpVariable['exists'] = array_key_exists($name, $node->attributes); $tmpVariable['attribute'] = '<?php if(' . $tmpVariable['compiledExpression'] . (!$tmpVariable['exists'] ? '&&!$tmpVariable[1]' : null) . '){?>' . $name . '="<?php echo(' . ($tmpVariable['exists'] ? '$tmpVariable[1]?\'' . str_replace('\'', ''', $node->attributes[$name]) . '\':' : null) . '(is_bool($tmpVariable[0])?($tmpVariable[0]?1:0):$tmpVariable[0]));?>"<?php }?>'; $node->attributes[$tmpVariable['attribute']] = $tmpVariable['attribute']; if ($tmpVariable['exists']) { unset($node->attributes[$name]); } } else { unset($node->attributes[$name]); } } } } if (!empty($changes['raw-attributes'])) { $fastAnswers = NearTAL_Compiler_ParseTALES($tmpVariable['compiledExpression'], $changes['raw-attributes'], $node->attributes, '$tmpVariable'); if (!$fastAnswers[0]) { if (!$fastAnswers[1]) { $tmpVariable['attribute'] = '<?php if(' . $tmpVariable['compiledExpression'] . '&&!$tmpVariable[1]' . '){?> <?phpecho((is_bool($tmpVariable[0])?($tmpVariable[0]?1:0):$tmpVariable[0]));}?>'; $node->attributes[$tmpVariable['attribute']] = null; } } } if (!is_null($changes['outside']['pre'])) { $position = $node->index(true); $node->parent->addXML('php', ' ' . $changes['outside']['pre'], array(), $position); } if (!is_null($changes['outside']['post'])) { $position = $node->index(true) + 1; $node->parent->addXML('php', ' ' . $changes['outside']['post'], array(), $position); } if (!is_null($changes['inside']['pre'])) { $position = 0; $node->addXML('php', ' ' . $changes['inside']['pre'], array(), $position); } if (!is_null($changes['inside']['post'])) { $position = -1; $node->addXML('php', ' ' . $changes['inside']['post'], array(), $position); } } $output .= str_replace("?>\n", "?>\n\n", str_replace("\r", '', $parser)) . '<?php }?>'; unset($parser); $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')) { NearTAL_AddError($returnValue, constant('NearTAL_Error_Rename'), $temporaryFilename); } } else { NearTAL_AddError($returnValue, constant('NearTAL_Error_Cache'), null); } } else { NearTAL_AddError($returnValue, constant('NearTAL_Error_TemporaryFile'), null); } } else { NearTAL_AddError($returnValue, constant('NearTAL_Error_Template'), null); } } else { NearTAL_AddError($returnValue, constant('NearTAL_Error_Path'), null); } }