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/07/29 23:01:24 UTC

svn commit: r799076 - in /incubator/shindig/trunk/php/src/gadgets: GadgetFactory.php GadgetSpec.php GadgetSpecParser.php render/GadgetBaseRenderer.php render/GadgetHtmlRenderer.php templates/TemplateParser.php

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