Author: chabotc
Date: Wed Jul 22 23:11:50 2009
New Revision: 796908

URL: http://svn.apache.org/viewvc?rev=796908&view=rev
Log:
First step of template libraries support, calling simple libraries works now, 
os:render and external libraries still have to be implemented (for a demo try 
http://test.chabotc.com/template.xml)

Added:
    incubator/shindig/trunk/php/src/gadgets/templates/TemplateLibrary.php
Modified:
    incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php
    incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php

Modified: incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php?rev=796908&r1=796907&r2=796908&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php 
(original)
+++ incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php Wed 
Jul 22 23:11:50 2009
@@ -19,10 +19,10 @@
  */
 
 require_once 'src/gadgets/templates/DataPipelining.php';
-require_once 'src/gadgets/templates/TemplateParser.php';
 
-//TODO check if the opensocial-templates feature has disableAutoProcessing = 
true as param, if so don't
 
+//TODO check if the opensocial-templates feature has disableAutoProcessing = 
true as param, if so don't
+//TODO if all templates were processed server side, also remove the templates 
with tag="foo" params to clean up the output
 
 class EmptyClass {
 }
@@ -36,6 +36,7 @@
   public $dataContext = array();
   public $unparsedTemplates = array();
   public $dataInserts = array();
+  public $templateLibraries = array();
 
   /**
    * Sets the $this->gadget property, and populates Msg, UserPref and 
ViewParams dataContext
@@ -103,8 +104,15 @@
       $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();
+    }
     foreach ($osTemplates[0] as $match) {
-      if (($renderedTemplate = $this->renderTemplate($match)) !== false) {
+      if (($renderedTemplate = $this->renderTemplate($match, 
$templateLibrary)) !== false) {
         // Template was rendered, insert the rendered html into the document
         $content = str_replace($match, $renderedTemplate, $content);
       } else {
@@ -186,7 +194,7 @@
    * @param string $template
    * @return string
    */
-  private function renderTemplate($template) {
+  private function renderTemplate($template, $templateLibrary) {
     libxml_use_internal_errors(true);
     $this->doc = new DOMDocument(null, 'utf-8');
     $this->doc->preserveWhiteSpace = true;
@@ -212,19 +220,28 @@
           }
         }
       }
-      // Everything checked out, proceeding to render the template
-      $parser = new TemplateParser();
-      $parser->process($childNode, $this->dataContext);
-      // 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);
-        $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/>'), array('', '<br>'), 
$output->saveXML());
-      return $ret;
+      // 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);
+               $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/>'), array('', 
'<br>'), $output->saveXML());
+             return $ret;
+      }
     }
     return false;
   }
@@ -252,11 +269,11 @@
    * @return string script
    */
   public function getBodyScript() {
-    $script = "gadgets.util.runOnLoadHandlers();";
     if ($this instanceof GadgetHrefRenderer) {
-      $script .= " 
window.setTimeout(function(){gadgets.window.adjustHeight()}, 10);";
+      return " window.setTimeout(function(){gadgets.window.adjustHeight()}, 
10);";
+    } else {
+      return "gadgets.util.runOnLoadHandlers();";
     }
-    return $script;
   }
 
   /**
@@ -269,6 +286,7 @@
     $script = $this->getBodyScript();
     $scriptNode = $doc->createElement('script');
     $scriptNode->setAttribute('type', 'text/javascript');
+    $scriptNode->appendChild($doc->createTextNode($script));
     $scriptNode->nodeValue = str_replace('&', '&amp;', $script);
     $node->appendChild($scriptNode);
   }

Added: incubator/shindig/trunk/php/src/gadgets/templates/TemplateLibrary.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/templates/TemplateLibrary.php?rev=796908&view=auto
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/templates/TemplateLibrary.php 
(added)
+++ incubator/shindig/trunk/php/src/gadgets/templates/TemplateLibrary.php Wed 
Jul 22 23:11:50 2009
@@ -0,0 +1,199 @@
+<?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.
+ */
+
+/**
+ * Class that manages & loads template libraries (either inline, osml or 
external)
+ * See 
http://opensocial-resources.googlecode.com/svn/spec/0.9/OpenSocial-Templating.xml#rfc.section.13
 for details
+ * Template libraries can be loaded from the gadget spec using:
+ * <Require feature="opensocial-templates">
+ *   <Param name="requireLibrary">http://www.example.com/templates.xml</Param>
+ * </Require>
+ */
+class TemplateLibrary {
+  private $osmlTags = array('os:Name', 'os:PeopleSelector', 'os:badge');
+  private $templates = array();
+  private $osmlLoaded = false;
+
+  public function parseTemplate($tag, $caller) {
+    $template = $this->getTemplate($tag);
+    $ret = '';
+    if ($template->dom instanceof DOMElement) {
+      $templateDomCopy = new DOMDocument(null, 'utf-8');
+      // If this template pulls in any new style and/or javascript, add those 
to the document
+      if ($style = $template->getStyle()) {
+        $styleNode = $templateDomCopy->createElement('style');
+        $styleNode->appendChild($templateDomCopy->createTextNode($style));
+        $templateDomCopy->appendChild($styleNode);
+      }
+      if ($script = $template->getScript()) {
+        $scriptNode = $templateDomCopy->createElement('script');
+        $scriptNode->setAttribute('type', 'text/javascript');
+        $scriptNode->appendChild($templateDomCopy->createTextNode($script));
+        $templateDomCopy->appendChild($scriptNode);
+      }
+      // Copy the DOM structure since parseNode() modifies the DOM structure 
directly
+      foreach ($template->dom->childNodes as $node) {
+        $importedNode = $templateDomCopy->importNode($node, true);
+        $templateDomCopy->appendChild($importedNode);
+      }
+      // Parse the template's DOM using our current data context (which 
includes the My context for templates)
+      $caller->parseNode($templateDomCopy);
+      return $templateDomCopy;
+    }
+    return false;
+  }
+
+  /**
+   * Add template by DOMElement node, this function is primary called from
+   * the GadgetBaseRenderer class when it comes accross a script block with
+   * type=os-template & tag="some:name"
+   *
+   * @param DOMElement $node
+   */
+  public function addTemplateByNode(DOMElement &$node) {
+    $tag = $node->getAttribute('tag');
+    $this->templates[$tag] = new TemplateLibraryEntry($node);
+  }
+
+  /**
+   * Add an external template library by URL
+   *
+   * @param string $libraryUrl (ie: 'http://www.example.com/templates.xml')
+   */
+  public function addTemplateLibrary($libraryUrl) {// add library by external 
URL and addTemplate for each entry contained in it
+}
+
+  /**
+   * Check to see if a template with name $tag exists
+   *
+   * @param string $tag
+   * @return boolean
+   */
+  public function hasTemplate($tag) {
+    if (in_array($tag, $this->osmlTags)) {
+      if (! $this->osmlLoaded) {
+        $this->loadOsmlLibrary();
+      }
+      return true;
+    }
+    return isset($this->templates[$tag]);
+  }
+
+  public function getTemplate($tag) {
+    if (! $this->hasTemplate($tag)) {
+      throw new ExpressionException("Invalid template tag");
+    }
+    return $this->templates[$tag];
+  }
+
+  private function loadOsmlLibrary() {
+    $this->osmlLoaded = true;
+    // preload osml lib, see container config key for location (gadget 
context->container config->osml->library
+  /*
+    "osml": {
+     // OSML library resource.  Can be set to null or the empty string to 
disable OSML
+     // for a container.
+     "library": "config/OSML_library.xml"
+   }
+   */
+  }
+}
+
+/**
+ * Misc class that holds the template information, an inline template
+ * will only contain a text blob (stored as parsed $dom node), however
+ * external and OSML library templates can also container script and
+ * style blocks
+ *
+ */
+class TemplateLibraryEntry {
+  public $dom;
+  public $style = array();
+  public $script = array();
+
+  public function __construct($dom = false) {
+    $this->dom = $dom;
+  }
+
+  /**
+   * Adds a javascript blob to this template
+   *
+   * @param unknown_type $script
+   */
+  public function addScript($script) {
+    $this->script[] = new TemplateLibraryContent($script);
+  }
+
+  /**
+   * Adds a style blob to this template
+   *
+   * @param unknown_type $style
+   */
+  public function addStyle($style) {
+    $this->style[] = new TemplateLibraryContent($style);
+  }
+
+  /**
+   * Returns the (combined, in inclusion  order) script text blob, or
+   * false if there's no javascript for this template
+   *
+   * @return javascript string or false
+   */
+  public function getScript() {
+    $ret = '';
+    foreach ($this->script as $script) {
+      if (! $script->included) {
+        $ret .= $script->content . "\n";
+      }
+    }
+    return ! empty($ret) ? $ret : false;
+  }
+
+  /**
+   * Returns the (combined, in inclusion  order) stylesheet text blob, or
+   * false if there's no style sheet associated with this template
+   *
+   * @return javascript string or false
+   */
+  public function getStyle() {
+    $ret = '';
+    foreach ($this->style as $style) {
+      if (! $style->included) {
+        $ret .= $style->content . "\n";
+      }
+    }
+    return ! empty($ret) ? $ret : false;
+  }
+}
+
+/**
+ * Scripts can be global per library set, so we assign the global script to 
each actual template
+ * and on calling it, the TemplateLibraryEntry will check to see if the 
content was already output
+ */
+class TemplateLibraryContent {
+  public $content;
+  public $included;
+
+  public function __construct($content) {
+    $this->content = $content;
+    $this->included = false;
+  }
+}

Modified: 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=796908&r1=796907&r2=796908&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php 
(original)
+++ incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php Wed 
Jul 22 23:11:50 2009
@@ -18,23 +18,27 @@
  * under the License.
  */
 
+//TODO os:repeat (and <foo repeat="" var="">) has a var="foo" param that 
hasn't been implmemented yet
 //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 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;
+  private $templateLibrary;
 
   /**
    * Processes an os-template
    *
    * @param string $template
    */
-  public function process(DOMnode &$osTemplate, $dataContext) {
+  public function process(DOMnode &$osTemplate, $dataContext, 
$templateLibrary) {
     $this->setDataContext($dataContext);
+    $this->templateLibrary = $templateLibrary;
     if ($osTemplate instanceof DOMElement) {
       $this->parseNode($osTemplate);
     }
@@ -53,21 +57,63 @@
     $this->dataContext['Context'] = array('UniqueId' => uniqid());
   }
 
-  private function parseNode(DOMNode &$node) {
+  public function parseNode(DOMNode &$node) {
     if ($node instanceof DOMText) {
       if (! $node->isWhitespaceInElementContent() && ! 
empty($node->wholeText)) {
         $this->parseNodeText($node);
       }
     } else {
-      $tagName = $node->tagName;
+      $tagName = isset($node->tagName) ? $node->tagName : '';
       if (substr($tagName, 0, 3) == 'os:' || substr($tagName, 0, 4) == 'osx:') 
{
         $this->parseOsmlNode($node);
+      } elseif ($this->templateLibrary->hasTemplate($tagName)) {
+        // the tag name refers to an existing template (myapp:EmployeeCard 
type naming)
+        // the extra check on the : character is to make sure this is a name 
spaced custom tag and not some one trying to override basic html tags (br, img, 
etc)
+        $this->parseLibrary($tagName, $node);
       } else {
         $this->parseNodeAttributes($node);
       }
     }
   }
 
+  private function parseLibrary($tagName, DOMNode &$node) {
+    // loop through attributes and assign vars to the context
+    $myContext = array();
+    if ($node->hasAttributes()) {
+      foreach ($node->attributes as $attr) {
+        if (strpos($attr->value, '${') !== false) {
+          // attribute value contains an expression
+          $expressions = array();
+          preg_match_all('/(\$\{)(.*)(\})/imsxU', $attr->value, $expressions);
+          for ($i = 0; $i < count($expressions[0]); $i ++) {
+            $expression = $expressions[2][$i];
+            $myContext[$attr->name] = ExpressionParser::evaluate($expression, 
$this->dataContext);
+          }
+        } else {
+          // plain old string
+          $myContext[$attr->name] = trim($attr->value);
+        }
+      }
+    }
+
+    // Parse the template library
+    $this->dataContext['My'] = $myContext;
+    $ret = $this->templateLibrary->parseTemplate($tagName, $this);
+    $this->dataContext['My'] = array();
+
+    if ($ret) {
+           // And replace the node with the parsed output
+           $ownerDocument = $node->ownerDocument;
+           foreach ($ret->childNodes as $childNode) {
+               if ($childNode instanceOf DOMElement) {
+               $importedNode = $ownerDocument->importNode($childNode, true);
+               $node->parentNode->insertBefore($importedNode, $node);
+               }
+           }
+      $node->parentNode->removeChild($node);
+    }
+  }
+
   private function parseNodeText(DOMText &$node) {
     if (strpos($node->wholeText, '${') !== false) {
       $expressions = array();
@@ -93,6 +139,7 @@
             $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)) {
@@ -154,7 +201,8 @@
                 break;
 
               case 'disabled':
-                $disabledTags = array('input', 'button', 'select', 'textarea');
+                $disabledTags = array('input', 'button',
+                    'select', 'textarea');
                 if (in_array($node->tagName, $disabledTags)) {
                   if ($expressionResult) {
                     $node->setAttribute('disabled', 'disabled');
@@ -198,7 +246,7 @@
       // Control statements
 
       case 'os:repeat':
-        if (!$node->getAttribute('expression')) {
+        if (! $node->getAttribute('expression')) {
           throw new ExpressionException("Invalid os:Repeat tag, missing 
expression attribute");
         }
         $expressions = array();
@@ -227,7 +275,7 @@
 
       case 'os:if':
         $expressions = array();
-        if (!$node->getAttribute('condition')) {
+        if (! $node->getAttribute('condition')) {
           throw new ExpressionException("Invalid os:If tag, missing condition 
attribute");
         }
         preg_match_all('/(\$\{)(.*)(\})/imsxU', 
$node->getAttribute('condition'), $expressions);
@@ -255,7 +303,7 @@
         break;
 
       case 'os:html':
-         if (!$node->getAttribute('code')) {
+        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


Reply via email to