/** * 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; }
/** * This helper method is the default instruction attribute handler in OPT. * It allows to parse the list of attributes using the specified rules. * The attribute configuration is passed as a second argument by reference, * and OPT returns the compiled attribute values in the same way. * * If the attribute specification contains "__UNKNOWN__" element, the node * may contain an undefined number of attributes. The undefined attributes * must match to the rules in "__UNKNOWN__" element and are returned by the * method as a separate array. For details, see the OPT user manual. * * @final * @param Opt_Xml_Element $subitem The scanned XML element. * @param Array &$config The reference to the attribute configuration * @return Array|Null The list of undefined attributes, if "__UNKNOWN__" is set. */ protected final function _extractAttributes(Opt_Xml_Element $subitem, array &$config) { $required = array(); $optional = array(); $unknown = null; // Decide, what is what. foreach ($config as $name => &$data) { if (!isset($data[3])) { $data[3] = null; } if ($name == '__UNKNOWN__') { $unknown =& $data; } elseif ($data[0] == self::REQUIRED) { $required[$name] =& $data; } elseif ($data[0] == self::OPTIONAL) { $optional[$name] =& $data; } } $config = array(); $return = array(); $exprEngines = $this->_compiler->getExpressionEngines(); // Parse required attributes $attrList = $subitem->getAttributes(false); foreach ($required as $name => &$data) { $ok = false; if (isset($attrList[$name])) { $aname = $name; $ok = true; } elseif (($data[1] == self::EXPRESSION || $data[1] == self::ASSIGN_EXPR) && $this->_tpl->backwardCompatibility == true) { // DEPRECATED: Legacy code for compatibility with OPT 2.0 foreach ($exprEngines as $eeName => $eeValue) { if (isset($attrList[$eeName . ':' . $name])) { $aname = $eeName . ':' . $name; $data[3] = $eeName; $ok = true; break; } } } if (!$ok) { throw new Opt_AttributeNotDefined_Exception($name, $subitem->getXmlName()); } $config[$name] = $this->_extractAttribute($subitem, $attrList[$aname], $data[1], $data[3]); unset($attrList[$aname]); } // Parse optional attributes foreach ($optional as $name => &$data) { $ok = false; if (isset($attrList[$name])) { $aname = $name; $ok = true; } elseif (($data[1] == self::EXPRESSION || $data[1] == self::ASSIGN_EXPR) && $this->_tpl->backwardCompatibility == true) { // DEPRECATED: Legacy code for compatibility with OPT 2.0 foreach ($exprEngines as $eeName => $eeValue) { if (isset($attrList[$eeName . ':' . $name])) { $aname = $eeName . ':' . $name; $data[3] = $eeName; $ok = true; break; } } } if (!$ok) { // We can't use isset() because the default data might be "NULL" if (!array_key_exists(2, $data)) { throw new Opt_APIMissingDefaultValue_Exception($name, $subitem->getXmlName()); } $config[$name] = $data[2]; continue; } $config[$name] = $this->_extractAttribute($subitem, $attrList[$aname], $data[1], $data[3]); unset($attrList[$aname]); } // The remaining tags must be processed using $unknown rule, however it // must be defined. if (!is_null($unknown)) { $type = $unknown[1]; foreach ($attrList as $name => $attr) { try { if (($type == self::EXPRESSION || $type == self::ASSIGN_EXPR) && $this->_tpl->backwardCompatibility == true) { // DEPRECATED: Legacy code for compatibility with OPT 2.0 $exprType = $unknown[3]; foreach ($exprEngines as $eeName => $eeValue) { if (isset($attrList[$eeName . ':' . $name])) { $name = substr($name, strlen($eeName), strlen($name) - strlen($eeName)); $exprType = $eeName; // Skip the tag, if this is a special OPT namespace. if ($this->_compiler->isNamespace($exprType)) { throw new Exception(); } break; } } } $return[$name] = $this->_extractAttribute($subitem, $attr, $type, $exprType); } catch (Exception $e) { /* null */ } } } return $return; }
/** * 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); } } }