Author: chabotc
Date: Wed Jul 29 21:01:23 2009
New Revision: 799076

URL: http://svn.apache.org/viewvc?rev=799076&view=rev
Log:
- Add support for <Param 
name="requireLibrary">http://www.example.com/templates.xml</Param> external 
libraries
- Honor the disableAutoProcessing param
- Support IF attributes on OSML nodes (repeat is still todo)
- lots and lots of small fixes


Modified:
    incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php
    incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php
    incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php
    incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php?rev=799076&r1=799075&r2=799076&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php Wed Jul 29 
21:01:23 2009
@@ -235,6 +235,15 @@
           }
         }
       }
+      // Add template libraries to the request queue
+      if ($gadget->gadgetSpec->templatesRequireLibraries) {
+        foreach ($gadget->gadgetSpec->templatesRequireLibraries as 
$libraryUrl) {
+               $request = new RemoteContentRequest($libraryUrl);
+          $request->createRemoteContentRequestWithUri($libraryUrl);
+          $request->getOptions()->ignoreCache = 
$this->context->getIgnoreCache();
+          $unsignedRequests[] = $request;
+        }
+      }
     }
     // Perform the non-signed requests
     $responses = array();
@@ -264,13 +273,24 @@
         $gadget->gadgetSpec->locales[$key]['messageBundle'] = 
$this->parseMessageBundle($responses[$locale['messages']]['body']);
       }
     }
-    $preloads = array();
-    foreach ($gadget->gadgetSpec->preloads as $key => $preload) {
-      if (! empty($preload['href']) && isset($responses[$preload['href']]) && 
$responses[$preload['href']]['rc'] == 200) {
-        $preloads[] = array_merge(array('id' => $preload['href']), 
$responses[$preload['href']]);
-      }
+    if (! $gadget->gadgetContext instanceof MetadataGadgetContext) {
+           $preloads = array();
+           foreach ($gadget->gadgetSpec->preloads as $key => $preload) {
+             if (! empty($preload['href']) && 
isset($responses[$preload['href']]) && $responses[$preload['href']]['rc'] == 
200) {
+               $preloads[] = array_merge(array('id' => $preload['href']), 
$responses[$preload['href']]);
+             }
+           }
+           $gadget->gadgetSpec->preloads = $preloads;
+           if ($gadget->gadgetSpec->templatesRequireLibraries) {
+                $requiredLibraries = array();
+                   foreach ($gadget->gadgetSpec->templatesRequireLibraries as 
$key => $libraryUrl) {
+                       if (isset($responses[$libraryUrl]) && 
$responses[$libraryUrl]['rc'] == 200) {
+                               $requiredLibraries[$libraryUrl] = 
$responses[$libraryUrl]['body'];
+                       }
+                   }
+                   $gadget->gadgetSpec->templatesRequireLibraries = 
$requiredLibraries;
+           }
     }
-    $gadget->gadgetSpec->preloads = $preloads;
   }
 
   /**

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php?rev=799076&r1=799075&r2=799076&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php Wed Jul 29 21:01:23 
2009
@@ -64,4 +64,8 @@
   public $userPrefs;
   public $rewrite = null;
   public $oauth = null;
+
+  // used to track os-templating
+  public $templatesRequireLibraries = false;
+  public $templatesDisableAutoProcessing = false;
 }

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php?rev=799076&r1=799075&r2=799076&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php Wed Jul 29 
21:01:23 2009
@@ -1,4 +1,5 @@
 <?php
+
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -35,7 +36,7 @@
     libxml_use_internal_errors(true);
     $doc = new DOMDocument();
     if (! $doc->loadXML($xmlContent, LIBXML_NOCDATA)) {
-      throw new GadgetSpecException("Error parsing gadget 
xml:\n".XmlError::getErrors($xmlContent));
+      throw new GadgetSpecException("Error parsing gadget xml:\n" . 
XmlError::getErrors($xmlContent));
     }
     //TODO: we could do a XSD schema validation here, but both the schema and 
most of the gadgets seem to have some form of schema
     // violatons, so it's not really practical yet (and slow)
@@ -79,10 +80,21 @@
         if (isset($gadget->views[$view])) {
           $gadget->views[$view]['content'] .= $viewNode->nodeValue;
         } else {
-          $gadget->views[$view] = array('view' => $view, 'type' => $type, 
'href' => $href, 'preferedHeight' => 
$viewNode->getAttribute('prefered_height'), 'preferedWidth' => 
$viewNode->getAttribute('prefered_width'),
-              'quirks' => $viewNode->getAttribute('quirks'), 'content' => 
$viewNode->nodeValue, 'authz' => $viewNode->getAttribute('authz'), 
'oauthServiceName' => $viewNode->getAttribute('oauth_service_name'),
-              'oauthTokenName' => $viewNode->getAttribute('oauth_token_name'), 
'oauthRequestToken' => $viewNode->getAttribute('oauth_request_token'), 
'oauthRequestTokenSecret' => 
$viewNode->getAttribute('oauth_request_token_secret'),
-              'signOwner' => $viewNode->getAttribute('sign_owner'), 
'signViewer' => $viewNode->getAttribute('sign_viewer'), 'refreshInterval' => 
$viewNode->getAttribute('refresh_interval'), 'dataPipelining' => 
$dataPipeliningRequests);
+          $gadget->views[$view] = array('view' => $view, 'type' => $type,
+              'href' => $href,
+              'preferedHeight' => $viewNode->getAttribute('prefered_height'),
+              'preferedWidth' => $viewNode->getAttribute('prefered_width'),
+              'quirks' => $viewNode->getAttribute('quirks'),
+              'content' => $viewNode->nodeValue,
+              'authz' => $viewNode->getAttribute('authz'),
+              'oauthServiceName' => 
$viewNode->getAttribute('oauth_service_name'),
+              'oauthTokenName' => $viewNode->getAttribute('oauth_token_name'),
+              'oauthRequestToken' => 
$viewNode->getAttribute('oauth_request_token'),
+              'oauthRequestTokenSecret' => 
$viewNode->getAttribute('oauth_request_token_secret'),
+              'signOwner' => $viewNode->getAttribute('sign_owner'),
+              'signViewer' => $viewNode->getAttribute('sign_viewer'),
+              'refreshInterval' => $viewNode->getAttribute('refresh_interval'),
+              'dataPipelining' => $dataPipeliningRequests);
         }
       }
     }
@@ -98,13 +110,18 @@
     $gadget->userPrefs = array();
     if (($userPrefs = $doc->getElementsByTagName('UserPref')) != null) {
       foreach ($userPrefs as $prefNode) {
-        $pref = array('name' => $prefNode->getAttribute('name'), 'displayName' 
=> $prefNode->getAttribute('display_name'), 'datatype' => 
strtoupper($prefNode->getAttribute('datatype')), 'defaultValue' => 
$prefNode->getAttribute('default_value'),
+        $pref = array('name' => $prefNode->getAttribute('name'),
+            'displayName' => $prefNode->getAttribute('display_name'),
+            'datatype' => strtoupper($prefNode->getAttribute('datatype')),
+            'defaultValue' => $prefNode->getAttribute('default_value'),
             'required' => $prefNode->getAttribute('required'));
         if ($pref['datatype'] == 'ENUM') {
           if (($enumValues = $prefNode->getElementsByTagName('EnumValue')) != 
null) {
             $enumVals = array();
             foreach ($enumValues as $enumNode) {
-              $enumVals[] = array('value' => $enumNode->getAttribute('value'), 
'displayValue' => $enumNode->getAttribute('display_value'));
+              $enumVals[] = array(
+                  'value' => $enumNode->getAttribute('value'),
+                  'displayValue' => $enumNode->getAttribute('display_value'));
             }
           }
           $pref['enumValues'] = $enumVals;
@@ -124,7 +141,9 @@
     $gadget->links = array();
     if (($links = $doc->getElementsByTagName('link')) != null) {
       foreach ($links as $linkNode) {
-        $gadget->links[] = array('rel' => $linkNode->getAttribute('rel'), 
'href' => $linkNode->getAttribute('href'), 'method' => 
strtoupper($linkNode->getAttribute('method')));
+        $gadget->links[] = array('rel' => $linkNode->getAttribute('rel'),
+            'href' => $linkNode->getAttribute('href'),
+            'method' => strtoupper($linkNode->getAttribute('method')));
       }
     }
   }
@@ -146,8 +165,11 @@
     }
     $modulePrefs = $modulePrefs->item(0);
     // parse the ModulePrefs attributes
-    $knownAttributes = array('title', 'author', 'authorEmail', 'description', 
'directoryTitle', 'screenshot', 'thumbnail', 'titleUrl', 'authorAffiliation', 
'authorLocation', 'authorPhoto', 'authorAboutme', 'authorQuote', 'authorLink', 
'showStats',
-        'showInDirectory', 'string', 'width', 'height', 'category', 
'category2', 'singleton', 'renderInline', 'scaling', 'scrolling');
+    $knownAttributes = array('title', 'author', 'authorEmail', 'description',
+        'directoryTitle', 'screenshot', 'thumbnail', 'titleUrl', 
'authorAffiliation',
+        'authorLocation', 'authorPhoto', 'authorAboutme', 'authorQuote', 
'authorLink',
+        'showStats', 'showInDirectory', 'string', 'width', 'height', 
'category',
+        'category2', 'singleton', 'renderInline', 'scaling', 'scrolling');
     foreach ($modulePrefs->attributes as $key => $attribute) {
       $attrValue = trim($attribute->value);
       // var format conversion from directory_title => directoryTitle
@@ -193,6 +215,11 @@
     if (($requiredNodes = $modulePrefs->getElementsByTagName('Require')) != 
null) {
       foreach ($requiredNodes as $requiredFeature) {
         $gadget->requiredFeatures[] = 
$requiredFeature->getAttribute('feature');
+        if ($requiredFeature->getAttribute('feature') == 'content-rewrite') {
+          $this->parseContentRewrite($requiredFeature, $gadget);
+        } elseif ($requiredFeature->getAttribute('feature') == 
'opensocial-templates') {
+          $this->parseOpenSocialTemplates($requiredFeature, $gadget);
+        }
       }
     }
     if (($optionalNodes = $modulePrefs->getElementsByTagName('Optional')) != 
null) {
@@ -201,6 +228,8 @@
         // Content-rewrite is a special case since it has Params as child nodes
         if ($optionalFeature->getAttribute('feature') == 'content-rewrite') {
           $this->parseContentRewrite($optionalFeature, $gadget);
+        } elseif ($optionalFeature->getAttribute('feature') == 
'opensocial-templates') {
+          $this->parseOpenSocialTemplates($optionalFeature, $gadget);
         }
       }
     }
@@ -256,6 +285,33 @@
   }
 
   /**
+   * Parses the opensocial-template params (if any), supported params are:
+   * <Require feature="opensocial-templates">
+   *   <Param 
name="requireLibrary">http://www.example.com/templates.xml</Param>
+   *   <Param name="disableAutoProcessing">false</Param>
+   * </Require>
+   * @param DOMElement $feature
+   * @param GadgetSpec $gadget
+   */
+  private function parseOpenSocialTemplates(DOMElement $feature, GadgetSpec 
&$gadget) {
+    $requireLibraries = array();
+    if (($paramNodes = $feature->getElementsByTagName('Param')) != null) {
+       foreach ($paramNodes as $param) {
+             $paramName = $param->getAttribute('name');
+             $paramValue = trim($param->nodeValue);
+             if ($paramName == 'disableAutoProcessing') {
+               $gadget->templatesDisableAutoProcessing = $paramValue != 
'false';
+             } elseif ($paramName == 'requireLibrary') {
+               $requireLibraries[] = $paramValue;
+             }
+       }
+    }
+    if (count($requireLibraries)) {
+       $gadget->templatesRequireLibraries = $requireLibraries;
+    }
+  }
+
+  /**
    * Parses the content-rewrite feature's params, possible params entries are:
    *   <Param name="expires">86400</Param>
    *   <Param name="include-url">*</Param>
@@ -311,7 +367,10 @@
     $gadget->preloads = array();
     if (($preloadNodes = $modulePrefs->getElementsByTagName('Preload')) != 
null) {
       foreach ($preloadNodes as $node) {
-        $gadget->preloads[] = array('href' => $node->getAttribute('href'), 
'authz' => strtoupper($node->getAttribute('authz')), 'signViewer' => 
$node->getAttribute('sign_viewer'), 'signOwner' => 
$node->getAttribute('sign_owner'));
+        $gadget->preloads[] = array('href' => $node->getAttribute('href'),
+            'authz' => strtoupper($node->getAttribute('authz')),
+            'signViewer' => $node->getAttribute('sign_viewer'),
+            'signOwner' => $node->getAttribute('sign_owner'));
       }
     }
   }
@@ -337,7 +396,10 @@
         }
         $lang = $node->getAttribute('lang') == '' ? 'all' : 
strtolower($node->getAttribute('lang'));
         $country = $node->getAttribute('country') == '' ? 'all' : 
strtoupper($node->getAttribute('country'));
-        $gadget->locales[] = array('lang' => $lang, 'country' => $country, 
'messages' => $node->getAttribute('messages'), 'languageDirection' => 
$node->getAttribute('language_direction'), 'messageBundle' => $messageBundle);
+        $gadget->locales[] = array('lang' => $lang, 'country' => $country,
+            'messages' => $node->getAttribute('messages'),
+            'languageDirection' => $node->getAttribute('language_direction'),
+            'messageBundle' => $messageBundle);
       }
     }
   }

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=799076&r1=799075&r2=799076&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php 
(original)
+++ incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php Wed 
Jul 29 21:01:23 2009
@@ -20,10 +20,6 @@
 
 require_once 'src/gadgets/templates/DataPipelining.php';
 
-
-//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 {
 }
 
@@ -70,6 +66,7 @@
    * so javascript can take care of them
    */
   public function addTemplates($content) {
+       // If $this->gadget->gadgetSpec->templatesDisableAutoProcessing == 
true, unparsedTemplates will be empty, so the setting is ignored here
     if (count($this->unparsedTemplates)) {
       foreach ($this->unparsedTemplates as $key => $val) {
         $content = str_replace("<template_$key></template_$key>", $val . "\n", 
$content);
@@ -88,6 +85,10 @@
    * @param string $content html to parse
    */
   public function parseTemplates($content) {
+       if ($this->gadget->gadgetSpec->templatesDisableAutoProcessing) {
+               // The only code path to this location is if content-rewriting 
is enabled but disableAutoProcessing = true
+               return null;
+       }
     $osTemplates = array();
     $osDataRequests = array();
     // First extract all the os-data tags, and execute those in a single 
combined request, saves latency
@@ -105,27 +106,31 @@
     
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';
+      // 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);
-    }
-    foreach ($osTemplates[0] as $match) {
-      if (($renderedTemplate = $this->renderTemplate($match, 
$templateLibrary)) !== false) {
-        // Template was rendered, insert the rendered html into the document
-        $content = str_replace($match, $renderedTemplate, $content);
-      } else {
+      $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 (($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
-         * - disableAutoProcessing param on the opensocial-templates feature 
is 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);
+          $index = count($this->unparsedTemplates);
+          $this->unparsedTemplates[$index] = $match;
+          $content = str_replace($match, 
"<template_$index></template_$index>", $content);
+        }
       }
     }
     return $content;
@@ -221,25 +226,26 @@
       }
       // 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);
+      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;
+        // 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;

Modified: incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php?rev=799076&r1=799075&r2=799076&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php 
(original)
+++ incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php Wed 
Jul 29 21:01:23 2009
@@ -43,7 +43,7 @@
     $domRewrite = false;
     if (isset($gadget->gadgetSpec->rewrite) || 
Config::get('rewrite_by_default')) {
       $domRewrite = true;
-    } elseif (strpos($view['content'], 'text/os-data') !== false || 
strpos($view['content'], 'text/os-template') !== false) {
+    } elseif ((strpos($view['content'], 'text/os-data') !== false || 
strpos($view['content'], 'text/os-template') !== false) && 
($gadget->gadgetSpec->templatesDisableAutoProcessing == false)) {
       $domRewrite = true;
     }
     if (!$domRewrite) {

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=799076&r1=799075&r2=799076&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php 
(original)
+++ incubator/shindig/trunk/php/src/gadgets/templates/TemplateParser.php Wed 
Jul 29 21:01:23 2009
@@ -18,11 +18,9 @@
  * 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 support repeat tags on OSML tags, ie this should work: <os:Html 
repeat="${Bar}" />
+//TODO support os:render
 //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 tag functions and extensions (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';
 
@@ -181,9 +179,19 @@
                 }
                 // Make sure the repeat variable doesn't show up in the cloned 
nodes (otherwise it would infinit recurse on this->parseNode())
                 $node->removeAttribute('repeat');
+                // Is a named var requested?
+                $variableName = $node->getAttribute('var') ? 
trim($node->getAttribute('var')) : false;
+                // Store the current 'Cur', index and count state, we might be 
in a nested repeat loop
+                $previousCount = isset($this->dataContext['Context']['Count']) 
? $this->dataContext['Context']['Count'] : null;
+                $previousIndex = isset($this->dataContext['Context']['Index']) 
? $this->dataContext['Context']['Index'] : null;
+                $previousCur = $this->dataContext['Cur'];
                 // 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) {
+                  if ($variableName) {
+                    // this is cheating a little since we're not putting it on 
the top level scope, the variable resolver will check 'Cur' first though so 
myVar.Something will still resolve correctly
+                    $this->dataContext['Cur'][$variableName] = $entry;
+                  }
                   $this->dataContext['Cur'] = $entry;
                   $this->dataContext['Context']['Index'] = $index;
                   // Clone this node and it's children
@@ -193,11 +201,18 @@
                   // And parse it (using the global + loop context)
                   $this->parseNode($newNode, true);
                 }
-                // Remove the original (unparsed) node
-                // And remove the loop data context entries
-                $this->dataContext['Cur'] = array();
-                unset($this->dataContext['Context']['Index']);
-                unset($this->dataContext['Context']['Count']);
+                // Restore our previous data context state
+                $this->dataContext['Cur'] = $previousCur;
+                if ($previousCount) {
+                  $this->dataContext['Context']['Count'] = $previousCount;
+                } else {
+                  unset($this->dataContext['Context']['Count']);
+                }
+                if ($previousIndex) {
+                  $this->dataContext['Context']['Index'] = $previousIndex;
+                } else {
+                  unset($this->dataContext['Context']['Index']);
+                }
                 return $node;
                 break;
 
@@ -285,6 +300,12 @@
    */
   private function parseOsmlNode(DOMNode &$node) {
     $tagName = strtolower($node->tagName);
+    if (!$this->checkIf($node)) {
+       // If the OSML tag contains an if attribute and the expression 
evaluates to false
+       // flag it for removal and don't process it
+       return $node;
+    }
+
     switch ($tagName) {
 
       /****** Control statements ******/
@@ -300,9 +321,19 @@
         if (! is_array($expressionResult)) {
           throw new ExpressionException("Can't repeat on a singular var");
         }
+        // Store the current 'Cur', index and count state, we might be in a 
nested repeat loop
+        $previousCount = isset($this->dataContext['Context']['Count']) ? 
$this->dataContext['Context']['Count'] : null;
+        $previousIndex = isset($this->dataContext['Context']['Index']) ? 
$this->dataContext['Context']['Index'] : null;
+        $previousCur = $this->dataContext['Cur'];
+        // Is a named var requested?
+        $variableName = $node->getAttribute('var') ? 
trim($node->getAttribute('var')) : false;
         // 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) {
+          if ($variableName) {
+            // this is cheating a little since we're not putting it on the top 
level scope, the variable resolver will check 'Cur' first though so 
myVar.Something will still resolve correctly
+            $this->dataContext['Cur'][$variableName] = $entry;
+          }
           $this->dataContext['Cur'] = $entry;
           $this->dataContext['Context']['Index'] = $index;
           foreach ($node->childNodes as $childNode) {
@@ -311,10 +342,19 @@
             $this->parseNode($newNode);
           }
         }
-        $node->parentNode->removeChild($node);
-        $this->dataContext['Cur'] = array();
-        unset($this->dataContext['Context']['Index']);
-        unset($this->dataContext['Context']['Count']);
+        // Restore our previous data context state
+        $this->dataContext['Cur'] = $previousCur;
+        if ($previousCount) {
+          $this->dataContext['Context']['Count'] = $previousCount;
+        } else {
+          unset($this->dataContext['Context']['Count']);
+        }
+        if ($previousIndex) {
+          $this->dataContext['Context']['Index'] = $previousIndex;
+        } else {
+          unset($this->dataContext['Context']['Index']);
+        }
+        return $node;
         break;
 
       case 'os:if':
@@ -350,14 +390,14 @@
 
       case 'os:peopleselector':
         $this->parseLibrary('os:PeopleSelector', $node);
-
         break;
 
       case 'os:html':
         if (! $node->getAttribute('code')) {
           throw new ExpressionException("Invalid os:Html tag, missing code 
attribute");
         }
-        
$node->parentNode->replaceChild($node->ownerDocument->createTextNode($node->getAttribute('code')),
 $node);
+        
$node->parentNode->insertBefore($node->ownerDocument->createTextNode($node->getAttribute('code')),
 $node);
+        return $node;
         break;
 
       case 'os:render':
@@ -390,4 +430,24 @@
     }
     return false;
   }
+
+  /**
+   * Misc function that checks if the OSML tag $node has an if attribute, 
returns
+   * true if the expression is true or no if attribute is set
+   *
+   * @param DOMElement $node
+   */
+  private function checkIf(DOMElement &$node) {
+    if (($if = $node->getAttribute('if'))) {
+      $expressions = array();
+      preg_match_all('/(\$\{)(.*)(\})/imsxU', $if, $expressions);
+      if (! count($expressions[2])) {
+        throw new ExpressionException("Invalid os:If tag, missing condition 
expression");
+      }
+      $expression = $expressions[2][0];
+      $expressionResult = ExpressionParser::evaluate($expression, 
$this->dataContext);
+      return $expressionResult ? true : false;
+    }
+    return true;
+  }
 }


Reply via email to