/** * Parses the specified template library * * @param string $tagName * @param DOMNode $node */ private function parseLibrary($tagName, DOMNode &$node) { // Set the My context based on the node's attributes $myContext = $this->nodeAttributesToScope($node); // Template call has child nodes, those are params that can be used in a os:Render call, store them $oldNodeContext = isset($this->dataContext['_os_render_nodes']) ? $this->dataContext['_os_render_nodes'] : array(); $this->dataContext['_os_render_nodes'] = array(); if ($node->childNodes->length) { foreach ($node->childNodes as $childNode) { if (isset($childNode->tagName) && !empty($childNode->tagName)) { $nodeParam = ($pos = strpos($childNode->tagName, ':')) ? trim(substr($childNode->tagName, $pos + 1)) : trim($childNode->tagName); $this->dataContext['_os_render_nodes'][$nodeParam] = $childNode; $myContext[$nodeParam] = $this->nodeAttributesToScope($childNode); } } } // Parse the template library (store the My scope since this could be a nested call) $previousMy = $this->dataContext['My']; $this->dataContext['My'] = $myContext; $ret = $this->templateLibrary->parseTemplate($tagName, $this); $this->dataContext['My'] = $previousMy; $this->dataContext['_os_render_nodes'] = $oldNodeContext; if ($ret) { // And replace the node with the parsed output $ownerDocument = $node->ownerDocument; foreach ($ret->childNodes as $childNode) { $importedNode = $ownerDocument->importNode($childNode, true); $importedNode = $node->parentNode->insertBefore($importedNode, $node); } } }
/** * Does the parsing of the template/data script content, then it hands * the os-data parsing to the DataPipeling class, and os-template tags to * the TemplateParser, and then returns the expanded template content (or '' on data) * * @param string $template * @param TemplateLibrary $templateLibrary * @return string */ private function renderTemplate($template, TemplateLibrary $templateLibrary) { libxml_use_internal_errors(true); $this->doc = new DOMDocument(null, 'utf-8'); $this->doc->preserveWhiteSpace = true; $this->doc->formatOutput = false; $this->doc->strictErrorChecking = false; $this->doc->recover = false; $this->doc->resolveExternals = false; if (!$this->doc->loadXML($template)) { return "Error parsing os-template:\n" . XmlError::getErrors($template); } if ($this->doc->childNodes->length < 1 || $this->doc->childNodes->length >> 1) { return 'Invalid script block'; } $childNode = $this->doc->childNodes->item(0); if ($childNode->tagName == 'script' && $childNode->getAttribute('name') == null && $childNode->getAttribute('autoUpdate') != 'true') { // If the require tag is set, check to see if we have all required data parts, and if not leave it to the client to render if (($require = $childNode->getAttribute('require')) != null) { $requires = explode(',', $require); foreach ($requires as $val) { $val = trim($val); if (!isset($this->dataContext[$val])) { return false; } } } // if $childNode->tag exists, add to global $templateLibraries array, else parse normally $childNodeTag = $childNode->getAttribute('tag'); if (!empty($childNodeTag)) { if (isset($this->templateLibraries[$childNode->getAttribute('tag')])) { throw new ExpressionException("Template " . htmlentities($childNode->getAttribute('tag')) . " was already defined"); } $templateLibrary->addTemplateByNode($childNode); } else { // Everything checked out, proceeding to render the template $parser = new TemplateParser(); $parser->process($childNode, $this->dataContext, $templateLibrary); // unwrap the output, ie we only want the script block's content and not the main <script></script> node $output = new DOMDocument(null, 'utf-8'); foreach ($childNode->childNodes as $node) { $outNode = $output->importNode($node, true); $outNode = $output->appendChild($outNode); } // Restore single tags to their html variant, and remove the xml header $ret = str_replace(array('<?xml version="" encoding="utf-8"?>', '<br/>', '<script type="text/javascript"><![CDATA[', ']]></script>'), array('', '<br>', '<script type="text/javascript">', '</script>'), $output->saveXML()); return $ret; } } return false; }
/** * This function parses the os-template and os-data script tags. * It's of vital importance to call this function *before* the rewriteContent function * since the html/dom parser of the later breaks, mangles and otherwise destroys the * os template/data script tags. So we need to expand the templates to pure html * before we can proceeed to dom parse the resulting document * * @param string $content html to parse */ public function parseTemplates($content) { $osTemplates = array(); $osDataRequests = array(); // First extract all the os-data tags, and execute those in a single combined request, saves latency // and is consistent with other server implementations preg_match_all('/(<script[^>]*type="text\\/(os-data)"[^>]*>)(.*)(<\\/script>)/imsxU', $content, $osDataRequests); $osDataRequestsCombined = ''; foreach ($osDataRequests[0] as $match) { $osDataRequestsCombined .= $match . "\n"; // Remove the reference from the html document $content = str_replace($match, '', $content); } if (!empty($osDataRequestsCombined)) { $this->performDataRequests($osDataRequestsCombined); } preg_match_all('/(<script[^>]*type="text\\/(os-template)"[^>]*>)(.*)(<\\/script>)/imxsU', $content, $osTemplates); $templateLibrary = false; if (count($osTemplates[0])) { // only load the template parser if there's any templates in the gadget content require_once 'src/gadgets/templates/TemplateParser.php'; require_once 'src/gadgets/templates/TemplateLibrary.php'; $templateLibrary = new TemplateLibrary($this->gadget->gadgetContext); if ($this->gadget->gadgetSpec->templatesRequireLibraries) { foreach ($this->gadget->gadgetSpec->templatesRequireLibraries as $library) { $templateLibrary->addTemplateLibrary($library); } } foreach ($osTemplates[0] as $match) { if (!$this->gadget->gadgetSpec->templatesDisableAutoProcessing && ($renderedTemplate = $this->renderTemplate($match, $templateLibrary)) !== false) { // Template was rendered, insert the rendered html into the document $content = str_replace($match, $renderedTemplate, $content); } else { /* * The template could not be rendered, this could happen because: * - @require is present, and at least one of the required pieces of data is unavailable * - @name is present * - @autoUpdate == true * - $this->gadget->gadgetSpec->templatesDisableAutoProcessing is set to true * So set a magic marker (<template_$index>) that after the dom document parsing will be replaced with the original script content */ $index = count($this->unparsedTemplates); $this->unparsedTemplates[$index] = $match; $content = str_replace($match, "<template_{$index}></template_{$index}>", $content); } } } return $content; }