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;
+
+    }
+  }
+
+}