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('&', '&', $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