/** * Processes the opt:root node. * @internal * @param Opt_Xml_Node $node The recognized node. */ public function processNode(Opt_Xml_Node $node) { if ($node->getParent()->getType() != 'Opt_Xml_Root') { throw new Opt_InstructionInvalidParent_Exception($node->getXmlName(), 'ROOT'); } $params = array('escaping' => array(0 => self::OPTIONAL, self::BOOL, NULL), 'include' => array(0 => self::OPTIONAL, self::STRING, NULL), 'dynamic' => array(0 => self::OPTIONAL, self::BOOL, false)); $this->_extractAttributes($node, $params); // Compile-time inclusion support if (!is_null($params['include'])) { $file = $params['include']; if ($params['dynamic']) { if (is_null($file = $this->_compiler->inherits($this->_compiler->get('currentTemplate')))) { $file = $params['include']; } } $this->_compiler->addDependantTemplate($file); $compiler = new Opt_Compiler_Class($this->_compiler); $compiler->compile($this->_tpl->_getSource($file), $file, NULL, $this->_compiler->get('mode')); $this->_compiler->importDependencies($compiler); } // Escaping control support if (!is_null($params['escaping'])) { $this->_compiler->set('escaping', $params['escaping']); } $this->_process($node); }
/** * Compiles the current text block between two XML tags, creating a * complete Opt_Xml_Text node. It looks for the expressions in the * curly brackets, extracts them and packs as separate nodes. * * Moreover, it replaces the entities with the corresponding characters. * * @internal * @param Opt_Xml_Node $current The current XML node. * @param String $text The text block between two tags. * @param Boolean $noExpressions=false If true, do not look for the expressions. * @return Opt_Xml_Node The current XML node. */ protected function _treeTextCompile($current, $text, $noExpressions = false) { // Yes, we parse entities, but the text itself should not contain // any special characters. if (strcspn($text, '<>') != strlen($text)) { throw new Opt_XmlInvalidCharacter_Exception(htmlspecialchars($text)); } if ($noExpressions) { $current = $this->_treeTextAppend($current, $this->_compiler->parseEntities($text)); } preg_match_all($this->_rExpressionTag, $text, $result, PREG_SET_ORDER); $resultSize = sizeof($result); $offset = 0; for ($i = 0; $i < $resultSize; $i++) { $id = strpos($text, $result[$i][0], $offset); if ($id > $offset) { $current = $this->_treeTextAppend($current, $this->_compiler->parseEntities(substr($text, $offset, $id - $offset))); } $offset = $id + strlen($result[$i][0]); $current = $this->_treeTextAppend($current, new Opt_Xml_Expression($this->_compiler->parseEntities($result[$i][2]))); } $i--; // Now the remaining content of the file if (strlen($text) > $offset) { $current = $this->_treeTextAppend($current, $this->_compiler->parseEntities(substr($text, $offset, strlen($text) - $offset))); } return $current; }
/** * @dataProvider dataProvider */ public function testExpression($assign, $src, $dst, $result) { try { $info = $this->cpl->compileExpression($src, $assign, Opt_Compiler_Class::ESCAPE_BOTH); if ($result == 0) { $this->assertEquals($dst, $info[0]); return true; } $this->fail('Exception NOT returned, but should be: ' . $result); } catch (Opt_Exception $e) { if ($result !== 0) { if ($result != get_class($e)) { $this->fail('Invalid exception returned: #' . get_class($e) . ', ' . $result . ' expected.'); } return true; } $this->fail('Exception returned: #' . get_class($e) . ': ' . $e->getMessage() . ' (line: ' . $e->getLine() . ')'); } }
/** * Tries to extract a single attribute, using the specified value type. * * @final * @internal * @param Opt_Xml_Element $item The scanned XML element. * @param Opt_Xml_Attribute $attr The parsed attribute * @param Int $type The requested value type. * @return Mixed The extracted attribute value */ private final function _extractAttribute(Opt_Xml_Element $item, Opt_Xml_Attribute $attr, $type, $exprType = null) { $value = (string) $attr; switch ($type) { // An identifier, but with empty values allowed. case self::ID_EMP: if ($value == '') { return $value; } // An identifier // An identifier case self::ID: if (!preg_match('/^[a-zA-Z0-9\\_\\.]+$/', $value)) { throw new Opt_InvalidAttributeType_Exception($attr->getXmlName(), $item->getXmlName(), 'identifier'); } return $value; // A number // A number case self::NUMBER: if (!preg_match('/^\\-?([0-9]+\\.?[0-9]*)|(0[xX][0-9a-fA-F]+)$/', $value)) { throw new Opt_InvalidAttributeType_Exception($attr->getXmlName(), $item->getXmlName(), 'number'); } return $value; // Boolean value: "yes" or "no" // Boolean value: "yes" or "no" case self::BOOL: if ($value != 'yes' && $value != 'no') { throw new Opt_InvalidAttributeType_Exception($attr->getXmlName(), $item->getXmlName(), '"yes" or "no"'); } return $value == 'yes'; // A string packed into PHP expression. Can be switched to EXPRESSION. // A string packed into PHP expression. Can be switched to EXPRESSION. case self::STRING: return $value; break; // An OPT expression. // An OPT expression. case self::EXPRESSION: case self::ASSIGN_EXPR: if (strlen(trim($value)) == 0) { throw new Opt_AttributeEmpty_Exception($attr->getXmlName(), $item->getXmlName()); } if (preg_match('/^([a-zA-Z0-9\\_]{2,})\\:([^\\:].*)$/', $value, $found)) { $result = $this->_compiler->parseExpression($found[2], $found[1]); } else { $result = $this->_compiler->parseExpression($value, $exprType); } if ($result['type'] == Opt_Expression_Interface::ASSIGNMENT && $type != self::ASSIGN_EXPR) { Opt_ExpressionOptionDisabled_Exception('Assignments', 'compiler requirements'); } return $result['bare']; } }
/** * Uses the specified variable within the context. * * @param string $variable The variable name. * @param boolean $isGlobal Is the variable global or local? * @param string $contextFormat The format enforced by the occurence context. * @return array */ public function useVariable($variable, $type, $isGlobal, $contextFormat = null) { if (!isset($this->_variables[$type . $variable])) { // In this case the variable has not been used yet. We must check // if the user have not selected any format for it, calculate the // default format and modify the context format. $manager = $this->_compiler->getCdfManager(); try { // In case of template variables, we should not check the data formats // because we would run into several problems. We immediately jump to the // format resolution algorithm. if ($type == '@') { throw new Exception(); } // OK, look for a variable $this->_variables[$type . $variable] = $manager->getFormat('variable', $variable, $this); return array('format' => $this->_variables[$type . $variable], 'replacement' => null, 'cast' => null); } catch (Exception $exception) { if ($contextFormat === null) { // This is very strange. It seems that someone has used // an uninitialized variable, so we can set it to null and // report it as an unused variable. Opt_Support::warning('Uninitialized variable ' . $variable . ' - casting to NULL'); return array('format' => null, 'replacement' => 'null'); } else { $manager->addFormat('variable', $variable, $contextFormat, $this->getElementLocation('variable', $variable)); $this->_variables[$type . $variable] = $manager->getFormat('variable', $variable, $this); return array('format' => $this->_variables[$type . $variable], 'replacement' => null, 'cast' => null); } } } else { // The variable has already been used. We must match the // previously selected format. if ($contextFormat !== null) { return array('format' => $this->_variables[$type . $variable], 'replacement' => null, 'cast' => $this->_variables[$type . $variable]->getName()); } else { return array('format' => $this->_variables[$type . $variable], 'replacement' => null); } } }
/** * Processes some extra objective calls: cloning and creating * new objects. * * @param string $action The action: 'new' or 'clone' * @param mixed $what The action argument * @param int $weight The action weight * @return SplFixedArray */ public function _objective($action, $what, $weight) { if (!$this->_tpl->allowObjects || !$this->_tpl->allowObjectCreation) { throw new Opt_NotSupported_Exception('object creation', 'disabled by the configuration'); } switch ($action) { case 'clone': $answer = $what; $answer[0] = 'clone ' . $answer[0]; $answer[1] += $weight; $answer[3] = 0; break; case 'new': if (($compiled = $this->_compiler->isClass($what[0])) === null) { throw new Opt_ItemNotAllowed_Exception('Class', $what[0]); } $answer = new SplFixedArray(4); $answer[0] = 'new ' . $compiled; $answer[1] = $weight; $answer[3] = 0; // Process the constructor arguments if (sizeof($what[1]) > 0) { $answer[0] .= '('; $first = true; foreach ($what[1] as $item) { if (!$first) { $answer[0] .= ','; } else { $first = false; } $answer[0] .= $item[0]; $answer[1] += $item[1]; } $answer[0] .= ')'; } } return $answer; }
/** * The compilation launcher. It executes the proper compilation steps * according to the inheritance rules etc. * * @param String $code The source code to be compiled. * @param String $filename The source template filename. * @param String $compiledFilename The output template filename. * @param Int $mode The compilation mode. */ public function compile($code, $filename, $compiledFilename, $mode) { $manager = $this->getCdfManager(); // Initialize the context. $this->_contextStack->push(new Opt_Compiler_Context($this, Opt_Compiler_Context::TEMPLATE_CTX, $filename)); try { // First, we select a parser. if (!isset($this->_parsers[$mode])) { $this->_parsers[$mode] = new $mode(); if (!$this->_parsers[$mode] instanceof Opt_Parser_Interface) { throw new Opt_InvalidParser_Exception($mode); } } $parser = $this->_parsers[$mode]; $parser->setCompiler($this); // We cannot compile two templates at the same time if (!is_null($this->_template)) { throw new Opt_CompilerLocked_Exception($filename, $this->_template); } // Detecting recursive inclusion if (is_null(self::$_recursionDetector)) { self::$_recursionDetector = array(0 => $filename); $weFree = true; } else { if (in_array($filename, self::$_recursionDetector)) { $exception = new Opt_CompilerRecursion_Exception($filename); $exception->setData(self::$_recursionDetector); throw $exception; } self::$_recursionDetector[] = $filename; $weFree = false; } // Cleaning up the processors foreach ($this->_processors as $proc) { $proc->reset(); } // Initializing the template launcher $this->set('template', $this->_template = $filename); $this->set('mode', $mode); $this->set('currentTemplate', $this->_template); array_push(self::$_templates, $filename); $this->_stack = new SplStack(); $i = 0; $extend = $filename; $memory = 0; // The inheritance loop do { // Stage 1 - code compilation if ($this->_tpl->debugConsole) { $initial = memory_get_usage(); $tree = $parser->parse($extend, $code); // Migration stage - only if backwards compatibility is on if ($this->_tpl->backwardCompatibility) { $tree = $this->_migrate($tree); } // Stage 2 - PHP tree processing $this->_stack = null; $this->_stage2($tree); $this->set('escape', NULL); unset($this->_stack); $memory += memory_get_usage() - $initial; unset($code); } else { $tree = $parser->parse($extend, $code); unset($code); // Migration stage - only if backward compatibility is on if ($this->_tpl->backwardCompatibility) { $this->_migrate($tree); } // Stage 2 - PHP tree processing $this->_stack = array(); $this->_stage2($tree); $this->set('escape', NULL); unset($this->_stack); } // if the template extends something, load it and also process if (isset($extend) && $extend != $filename) { $this->addDependantTemplate($extend); } if (!is_null($snippet = $tree->get('snippet'))) { $tree->dispose(); unset($tree); // Change the specified snippet into a root node. $tree = new Opt_Xml_Root(); $attribute = new Opt_Xml_Attribute('opt:use', $snippet); $this->processor('snippet')->processAttribute($tree, $attribute); $this->processor('snippet')->postprocessAttribute($tree, $attribute); $this->_stage2($tree, true); break; } if (!is_null($extend = $tree->get('extend'))) { $tree->dispose(); unset($tree); $this->set('currentTemplate', $extend); array_pop(self::$_templates); array_push(self::$_templates, $extend); $code = $this->_tpl->_getSource($extend); } $i++; } while (!is_null($extend)); // There are some dependant templates. We must add a suitable PHP code // to the output. if (sizeof($this->_dependencies) > 0) { $this->_addDependencies($tree); } if ($this->_tpl->debugConsole) { Opt_Support::addCompiledTemplate($this->_template, $memory); } // Stage 3 - linking the last tree if (!is_null($compiledFilename)) { $this->_output = ''; $this->_newQueue = null; $this->_dynamicBlocks = array(); $this->_stage3($output, $tree); $tree->dispose(); unset($tree); $this->_output = str_replace('?><' . '?php', '', $this->_output); // Build the directories, if needed. if (($pos = strrpos($compiledFilename, '/')) !== false) { $path = $this->_tpl->compileDir . substr($compiledFilename, 0, $pos); if (!is_dir($path)) { mkdir($path, 0750, true); } } // Save the file if (sizeof($this->_dynamicBlocks) > 0) { file_put_contents($this->_tpl->compileDir . $compiledFilename . '.dyn', serialize($this->_dynamicBlocks)); } file_put_contents($this->_tpl->compileDir . $compiledFilename, $this->_output); $this->_output = ''; $this->_dynamicBlocks = null; } else { $tree->dispose(); } array_pop(self::$_templates); $this->_inheritance = array(); if ($weFree) { // Do the cleanup. $this->_dependencies = array(); self::$_recursionDetector = NULL; foreach ($this->_processors as $processor) { $processor->reset(); } } $this->_template = NULL; $manager->clearLocals(); // Free the context. while ($this->_contextStack->count() > 0) { $ctx = $this->_contextStack->pop(); $ctx->dispose(); } // Run the new garbage collector, if it is available. /* if(version_compare(PHP_VERSION, '5.3.0', '>=')) { gc_collect_cycles(); }*/ } catch (Exception $e) { // Free the context while ($this->_contextStack->count() > 0) { $ctx = $this->_contextStack->pop(); $ctx->dispose(); } // Free the memory if (isset($tree)) { $tree->dispose(); } // Clean the compiler state in case of exception $this->_template = NULL; $this->_dependencies = array(); self::$_recursionDetector = NULL; foreach ($this->_processors as $processor) { $processor->reset(); } $manager->clearLocals(); // And throw it forward. throw $e; } }
/** * This function is executed by the compiler during the third compilation stage, * linking. */ public function preLink(Opt_Compiler_Class $compiler) { $compiler->appendOutput($this->buildCode(Opt_Xml_Buffer::TAG_BEFORE)); // $this->_closeComments($item, $output); }
/** * Parses the input code and returns the OPT XML tree. * * @param String $filename The file name (for debug purposes) * @param String &$code The code to parse * @return Opt_Xml_Root */ public function parse($filename, &$code) { $debug = array(XMLReader::NONE => 'NONE', XMLReader::ELEMENT => 'ELEMENT', XMLReader::ATTRIBUTE => 'ATTRIBUTE', XMLReader::TEXT => 'TEXT', XMLReader::CDATA => 'CDATA', XMLReader::ENTITY_REF => 'ENTITY_REF', XMLReader::ENTITY => 'ENTITY', XMLReader::PI => 'PI', XMLReader::COMMENT => 'COMMENT', XMLReader::DOC => 'DOC', XMLReader::DOC_TYPE => 'DOC_TYPE', XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT', XMLReader::NOTATION => 'NOTATION', XMLReader::WHITESPACE => 'WHITESPACE', XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE', XMLReader::END_ELEMENT => 'END_ELEMENT', XMLReader::END_ENTITY => 'END_ENTITY', XMLReader::XML_DECLARATION => 'XML_DECLARATION'); libxml_use_internal_errors(true); $reader = new XMLReader(); $reader->xml($code); // $reader->setParserProperty(XMLReader::LOADDTD, true); // $reader->setParserProperty(XMLReader::VALIDATE, true); $reader->setParserProperty(XMLReader::SUBST_ENTITIES, true); $root = $current = new Opt_Xml_Root(); $firstElementMatched = false; $depth = 0; // Thanks, Oh Great PHP for your excellent WARNINGS!!! >:( while (@$reader->read()) { if ($reader->depth < $depth) { $current = $current->getParent(); } elseif ($reader->depth > $depth) { $current = $optNode; } // Opl_Debug::write($debug[$reader->nodeType].': '.$reader->name.', '.$reader->value); switch ($reader->nodeType) { // XML elements case XMLReader::ELEMENT: $optNode = new Opt_Xml_Element($reader->name); // Parse element attributes, if you manage to get there if ($reader->moveToFirstAttribute()) { do { // "xmlns" special namespace must be handler somehow differently. if ($reader->prefix == 'xmlns') { $ns = str_replace('xmlns:', '', $reader->name); $root->addNamespace($ns, $reader->value); // Let this attribute to appear, if it does not represent an OPT special // namespace if (!$this->_compiler->isNamespace($ns)) { $optAttribute = new Opt_Xml_Attribute($reader->name, $reader->value); $optNode->addAttribute($optAttribute); } } else { $optAttribute = new Opt_Xml_Attribute($reader->name, $reader->value); $optNode->addAttribute($optAttribute); } } while ($reader->moveToNextAttribute()); $reader->moveToElement(); } // Set "rootNode" flag if (!$firstElementMatched) { $optNode->set('rootNode', true); $firstElementMatched = true; } // Set "single" flag if ($reader->isEmptyElement) { $optNode->set('single', true); } $current->appendChild($optNode); break; case XMLReader::TEXT: $this->_treeTextCompile($current, $reader->value); break; case XMLReader::COMMENT: $optNode = new Opt_Xml_Comment($reader->value); $current->appendChild($optNode); break; case XMLReader::CDATA: $cdata = new Opt_Xml_Cdata($reader->value); $cdata->set('cdata', true); if ($current instanceof Opt_Xml_Text) { $current->appendChild($cdata); } else { $text = new Opt_Xml_Text(); $text->appendChild($cdata); $current->appendChild($text); $current = $text; } break; /* case XMLReader::SIGNIFICANT_WHITESPACE: $cdata = new Opt_Xml_Cdata($reader->value); $cdata->set('cdata', true); if($current instanceof Opt_Xml_Text) { $current->appendChild($cdata); } else { $text = new Opt_Xml_Text(); $text->appendChild($cdata); $current->appendChild($text); $current = $text; } break; */ } $depth = $reader->depth; } // Error checking $errors = libxml_get_errors(); if (sizeof($errors) > 0) { libxml_clear_errors(); foreach ($errors as $error) { echo $error->message . ' (' . $error->line . ')<br/>'; } } return $root; }
/** * Looks for special OPT attributes in the element attribute list and * processes them. Returns the list of nodes that need to be postprocessed. * * @internal * @param Opt_Compiler_Class $compiler The compiler. * @param Boolean $specialNs Do we recognize special namespaces? */ protected function _processXml(Opt_Compiler_Class $compiler, $specialNs = true) { if (!$this->hasAttributes()) { return array(); } $attributes = $this->getAttributes(); $this->_postprocess = array(); $opt = Opl_Registry::get('opt'); // Look for special OPT attributes foreach ($attributes as $attr) { if ($compiler->isNamespace($attr->getNamespace())) { $xml = $attr->getXmlName(); if (($processor = $compiler->isOptAttribute($xml)) !== null) { $processor->processAttribute($this, $attr); if ($attr->get('postprocess')) { $this->_postprocess[] = array($processor, $attr); } } $this->removeAttribute($xml); } else { $compiler->compileAttribute($attr); } } }
/** * This function is executed by the compiler during the third compilation stage, * linking, after linking the child nodes. */ public function postLink(Opt_Compiler_Class $compiler) { $compiler->appendOutput($this->buildCode(Opt_Xml_Buffer::TAG_AFTER)); }
/** * The informator that prints the currently compiled template name * for the compilation errors. * * @internal * @param Opt_Exception $exception Exception */ protected function _printTemplateInfo($exception) { echo "\t\t<p class=\"directive\">Template: <span>" . Opt_Compiler_Class::getCurrentTemplate() . "</span></p>\r\n"; }
/** * This function is executed by the compiler during the third compilation stage, * linking. */ public function preLink(Opt_Compiler_Class $compiler) { if ($this->get('cdata')) { $compiler->appendOutput($this->buildCode(Opt_Xml_Buffer::TAG_BEFORE) . '<![CDATA[' . $this . ']]>' . $this->buildCode(Opt_Xml_Buffer::TAG_AFTER)); return; } $compiler->appendOutput($this->buildCode(Opt_Xml_Buffer::TAG_BEFORE)); $tpl = Opl_Registry::get('opt'); // We strip the white spaces at the linking level. if ($tpl->stripWhitespaces) { // The CDATA composed of white characters only is reduced to a single space. if (ctype_space((string) $this)) { // TODO: Wtf is this? Compare with OPT 2.0 code... /*if($wasElement) { $output .= ' '; }*/ } else { // In the opposite case reduce all the groups of the white characters // to single spaces in the text. if ($this->get('noEntitize') === true) { $compiler->appendOutput(preg_replace('/\\s\\s+/', ' ', (string) $this)); } else { $compiler->appendOutput($compiler->parseSpecialChars(preg_replace('/(\\s){1,}/', ' ', (string) $this))); } } } else { $compiler->appendOutput($this->get('noEntitize') ? (string) $this : $compiler->parseSpecialChars($this)); } $compiler->appendOutput($this->buildCode(Opt_Xml_Buffer::TAG_AFTER)); // $this->_closeComments($item, $output); }