You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by ch...@apache.org on 2009/05/10 19:44:50 UTC
svn commit: r773371 [2/2] - in /incubator/shindig/trunk/php/src: common/
common/sample/ gadgets/ gadgets/render/ gadgets/rewrite/ gadgets/templates/
Added: incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php?rev=773371&view=auto
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php (added)
+++ incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php Sun May 10 17:44:49 2009
@@ -0,0 +1,299 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//TODO for some reason the OSML spec stats you have to <Require feature="osml"> to use the os:Name etc tags yet no such feature exists, and for the code path's here it's not required at all..
+//TODO remove the os-templates javascript if all the templates are rendered on the server (saves many Kb's in gadget size)
+//TODO support for OSML tags (os:name, os:peopleselector, os:badge) and OSML functions (os:render, osx:flash, osx:parsejson, etc)
+//TODO add variable resolving by checking the order of precedence: ${Cur}, ${My} and then ${Top} (top == global data context)
+//TODO support os-template tags on OSML tags, ie this should work: <os:Html if="${Foo}" repeat="${Bar}" />
+
+require_once 'ExpressionParser.php';
+
+class TemplateParser {
+ private $dataContext;
+
+ /**
+ * Processes an os-template
+ *
+ * @param string $template
+ */
+ public function process(DOMnode &$osTemplate, $dataContext) {
+ $this->setDataContext($dataContext);
+ if ($osTemplate instanceof DOMElement) {
+ $this->parseNode($osTemplate);
+ }
+ }
+
+ /**
+ * Sets and initializes the data context to use while processing the template
+ *
+ * @param array $dataContext
+ */
+ private function setDataContext($dataContext) {
+ $this->dataContext = array();
+ $this->dataContext['Top'] = $dataContext;
+ $this->dataContext['Cur'] = array();
+ $this->dataContext['My'] = array();
+ $this->dataContext['Context'] = array('UniqueId' => uniqid());
+ }
+
+ private function parseNode(DOMNode &$node) {
+ if ($node instanceof DOMText) {
+ if (! $node->isWhitespaceInElementContent() && ! empty($node->wholeText)) {
+ $this->parseNodeText($node);
+ }
+ } else {
+ $tagName = $node->tagName;
+ if (substr($tagName, 0, 3) == 'os:' || substr($tagName, 0, 4) == 'osx:') {
+ $this->parseOsmlNode($node);
+ } else {
+ $this->parseNodeAttributes($node);
+ }
+ }
+ }
+
+ private function parseNodeText(DOMText &$node) {
+ if (strpos($node->wholeText, '${') !== false) {
+ $expressions = array();
+ preg_match_all('/(\$\{)(.*)(\})/imsxU', $node->wholeText, $expressions);
+ for ($i = 0; $i < count($expressions[0]); $i ++) {
+ $toReplace = $expressions[0][$i];
+ $expression = $expressions[2][$i];
+ $expressionResult = ExpressionParser::evaluate($expression, $this->dataContext);
+ $stringVal = htmlentities(ExpressionParser::stringValue($expressionResult), ENT_QUOTES, 'UTF-8');
+ $node->nodeValue = str_replace($toReplace, $stringVal, $node->wholeText);
+ }
+ }
+ }
+
+ private function parseNodeAttributes(DOMNode &$node) {
+ if ($node->hasAttributes()) {
+ foreach ($node->attributes as $attr) {
+ if (strpos($attr->value, '${') !== false) {
+ $expressions = array();
+ preg_match_all('/(\$\{)(.*)(\})/imsxU', $attr->value, $expressions);
+ for ($i = 0; $i < count($expressions[0]); $i ++) {
+ $toReplace = $expressions[0][$i];
+ $expression = $expressions[2][$i];
+ $expressionResult = ExpressionParser::evaluate($expression, $this->dataContext);
+ switch (strtolower($attr->name)) {
+ case 'repeat':
+ // Can only loop if the result of the expression was an array
+ if (! is_array($expressionResult)) {
+ throw new ExpressionException("Can't repeat on a singular var");
+ }
+ // Make sure the repeat variable doesn't show up in the cloned nodes (otherwise it would infinit recurse on this->parseNode())
+ $node->removeAttribute('repeat');
+ // For information on the loop context, see http://opensocial-resources.googlecode.com/svn/spec/0.9/OpenSocial-Templating.xml#rfc.section.10.1
+ $this->dataContext['Context']['Count'] = count($expressionResult);
+ foreach ($expressionResult as $index => $entry) {
+ $this->dataContext['Cur'] = $entry;
+ $this->dataContext['Context']['Index'] = $index;
+ // Clone this node and it's children
+ $newNode = $node->cloneNode(true);
+ // Append the parsed & expanded node to the parent
+ $newNode = $node->parentNode->insertBefore($newNode, $node);
+ // And parse it (using the global + loop context)
+ $this->parseNode($newNode, true);
+ }
+ // Remove the original (unparsed) node
+ $node->parentNode->removeChild($node);
+ // And remove the loop data context entries
+ $this->dataContext['Cur'] = array();
+ unset($this->dataContext['Context']['Index']);
+ unset($this->dataContext['Context']['Count']);
+ return;
+ break;
+
+ case 'if':
+ if (! $expressionResult) {
+ $node->parentNode->removeChild($node);
+ return; // Since this node is removed, no sense in evaluating it's other attributes and/or child nodes
+ }
+ break;
+
+ // These special cases that only apply for certain tag types
+ case 'selected':
+ if ($node->tagName == 'option') {
+ if ($expressionResult) {
+ $node->setAttribute('selected', 'selected');
+ } else {
+ $node->removeAttribute('selected');
+ }
+ } else {
+ throw new ExpressionException("Can only use selected on an option tag");
+ }
+ break;
+
+ case 'checked':
+ if ($node->tagName == 'input') {
+ if ($expressionResult) {
+ $node->setAttribute('checked', 'checked');
+ } else {
+ $node->removeAttribute('checked');
+ }
+ } else {
+ throw new ExpressionException("Can only use checked on an input tag");
+ }
+ break;
+
+ case 'disabled':
+ $disabledTags = array('input', 'button', 'select', 'textarea');
+ if (in_array($node->tagName, $disabledTags)) {
+ if ($expressionResult) {
+ $node->setAttribute('disabled', 'disabled');
+ } else {
+ $node->removeAttribute('disabled');
+ }
+ } else {
+ throw new ExpressionException("Can only use disabled on input, button, select and textarea tags");
+ }
+ break;
+
+ default:
+ // On non os-template spec attributes, do a simple str_replace with the evaluated value
+ $stringVal = htmlentities(ExpressionParser::stringValue($expressionResult), ENT_QUOTES, 'UTF-8');
+ $newAttrVal = str_replace($toReplace, $stringVal, $attr->value);
+ $node->setAttribute($attr->name, $newAttrVal);
+ break;
+ }
+ }
+ }
+ }
+ }
+ // if a repeat attribute was found, don't recurse on it's child nodes, the repeat handling already did that
+ if (isset($node->childNodes) && $node->childNodes->length > 0) {
+ // recursive loop to all this node's children
+ foreach ($node->childNodes as $childNode) {
+ $this->parseNode($childNode);
+ }
+ }
+ }
+
+ /**
+ * Function that handles the os: and osx: tags
+ *
+ * @param DOMNode $node
+ */
+ private function parseOsmlNode(DOMNode &$node) {
+ $tagName = strtolower($node->tagName);
+ switch ($tagName) {
+
+ // Control statements
+
+ case 'os:repeat':
+ if (!$node->getAttribute('expression')) {
+ throw new ExpressionException("Invalid os:Repeat tag, missing expression attribute");
+ }
+ $expressions = array();
+ preg_match_all('/(\$\{)(.*)(\})/imsxU', $node->getAttribute('expression'), $expressions);
+ $expression = $expressions[2][0];
+ $expressionResult = ExpressionParser::evaluate($expression, $this->dataContext);
+ if (! is_array($expressionResult)) {
+ throw new ExpressionException("Can't repeat on a singular var");
+ }
+ // For information on the loop context, see http://opensocial-resources.googlecode.com/svn/spec/0.9/OpenSocial-Templating.xml#rfc.section.10.1
+ $this->dataContext['Context']['Count'] = count($expressionResult);
+ foreach ($expressionResult as $index => $entry) {
+ $this->dataContext['Cur'] = $entry;
+ $this->dataContext['Context']['Index'] = $index;
+ foreach ($node->childNodes as $childNode) {
+ $newNode = $childNode->cloneNode(true);
+ $this->parseNode($newNode);
+ $node->parentNode->insertBefore($newNode, $node);
+ }
+ }
+ $node->parentNode->removeChild($node);
+ $this->dataContext['Cur'] = array();
+ unset($this->dataContext['Context']['Index']);
+ unset($this->dataContext['Context']['Count']);
+ break;
+
+ case 'os:if':
+ $expressions = array();
+ if (!$node->getAttribute('condition')) {
+ throw new ExpressionException("Invalid os:If tag, missing condition attribute");
+ }
+ preg_match_all('/(\$\{)(.*)(\})/imsxU', $node->getAttribute('condition'), $expressions);
+ $expression = $expressions[2][0];
+ $expressionResult = ExpressionParser::evaluate($expression, $this->dataContext);
+ if ($expressionResult) {
+ foreach ($node->childNodes as $childNode) {
+ $newNode = $childNode->cloneNode(true);
+ $this->parseNode($newNode);
+ $node->insertBefore($newNode);
+ }
+ }
+ $node->parentNode->removeChild($node);
+ break;
+
+ // OSML tags (os: name space)
+
+ case 'os:name':
+ break;
+
+ case 'os:peopleselector':
+ break;
+
+ case 'os:badge':
+ break;
+
+ case 'os:html':
+ if (!$node->getAttribute('code')) {
+ throw new ExpressionException("Invalid os:Html tag, missing code attribute");
+ }
+ //FIXME this seems to not work out to well, probably need to use the original domdocument to $doc->createTextNode() to make this work
+ $newNode = new DOMText();
+ $newNode->nodeValue = $node->getAttribute('code');
+ $node->parentNode->replaceChild($newNode, $node);
+ break;
+
+ case 'os:render':
+ break;
+
+ // Extension - Tags
+
+ case 'osx:flash':
+ break;
+
+ case 'osx:navigatetoapp':
+ break;
+
+ case 'osx:navigatetoperson':
+ break;
+
+ // Extension - Functions
+
+ case 'osx:parsejson':
+ break;
+
+ case 'osx:decodebase64':
+ break;
+
+ case 'osx:urlencode':
+ break;
+
+ case 'osx:urldecode':
+ break;
+
+ }
+ }
+
+}