You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by aw...@apache.org on 2009/02/27 20:35:04 UTC

svn commit: r748652 - in /incubator/shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/parse/ main/java/org/apache/shindig/gadgets/parse/nekohtml/ main/java/org/apache/shindig/gadgets/spec/ main/java/org/apache/shindig/gadgets/templat...

Author: awiner
Date: Fri Feb 27 19:35:03 2009
New Revision: 748652

URL: http://svn.apache.org/viewvc?rev=748652&view=rev
Log:
SHINDIG-748: OpenSocial Templates (in part)
Patch from Lev Epshteyn, with some changes
- Add support for os:Html and os:Name tags
- Add a TagRegistry
- Add fragment parsing support to GadgetHtmlParser and its subclasses
- Rename TemplateProcessor to DefaultTemplateProcessor, add a TemplateProcessor interface
- Fix to NekoSimplifiedHtmlParser to support tags with the same name as HTML elements but in a different namespace

Added:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java   (contents, props changed)
      - copied, changed from r748625, incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/HtmlTagHandler.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/NameTagHandler.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagHandler.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java   (contents, props changed)
      - copied, changed from r748625, incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateProcessorTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/HtmlTagHandlerTest.java
Removed:
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateProcessorTest.java
Modified:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/GadgetHtmlParser.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoHtmlParser.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParserTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
    incubator/shindig/trunk/java/gadgets/src/test/resources/org/apache/shindig/gadgets/parse/nekohtml/test-socialmarkup.html

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/GadgetHtmlParser.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/GadgetHtmlParser.java?rev=748652&r1=748651&r2=748652&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/GadgetHtmlParser.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/GadgetHtmlParser.java Fri Feb 27 19:35:03 2009
@@ -17,19 +17,19 @@
  */
 package org.apache.shindig.gadgets.parse;
 
+import com.google.inject.ImplementedBy;
+import com.google.inject.Inject;
+
 import org.apache.shindig.common.cache.Cache;
 import org.apache.shindig.common.cache.CacheProvider;
 import org.apache.shindig.common.util.HashUtil;
 import org.apache.shindig.common.xml.DomUtil;
 import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.parse.nekohtml.NekoSimplifiedHtmlParser;
-
-import com.google.inject.ImplementedBy;
-import com.google.inject.Inject;
-
 import org.w3c.dom.Document;
 import org.w3c.dom.DocumentFragment;
 import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 
 /**
  * Parser for arbitrary HTML content
@@ -38,12 +38,15 @@
 public abstract class GadgetHtmlParser {
 
   public static final String PARSED_DOCUMENTS = "parsedDocuments";
+  public static final String PARSED_FRAGMENTS = "parsedFragments";
 
   private Cache<String, Document> documentCache;
+  private Cache<String, DocumentFragment> fragmentCache;
 
   @Inject
   public void setCacheProvider(CacheProvider cacheProvider) {
     documentCache = cacheProvider.createCache(PARSED_DOCUMENTS);
+    fragmentCache = cacheProvider.createCache(PARSED_FRAGMENTS);
   }
 
   /**
@@ -92,7 +95,42 @@
     return document;
   }
 
-  private boolean shouldCache() {
+  /**
+   * Parses a snippet of markup and appends the result as children to the 
+   * provided node.
+   * 
+   * @param source markup to be parsed
+   * @param result Node to append results to
+   * @throws GadgetException
+   */
+  public final void parseFragment(String source, Node result) throws GadgetException {
+    boolean shouldCache = shouldCache();
+    String key = null;    
+    if (shouldCache) {
+      key = HashUtil.rawChecksum(source.getBytes());
+      DocumentFragment cachedFragment = fragmentCache.getElement(key);
+      if (cachedFragment != null) {
+        copyFragment(cachedFragment, result);
+        return;
+      }
+    }
+    DocumentFragment fragment = parseFragmentImpl(source);
+    if (shouldCache) {
+      fragmentCache.addElement(key, fragment);
+    }
+    copyFragment(fragment, result);
+  }
+
+  private void copyFragment(DocumentFragment source, Node dest) {
+    Document destDoc = dest.getOwnerDocument();
+    NodeList nodes = source.getChildNodes();
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Node clone = destDoc.importNode(nodes.item(i), true);
+      dest.appendChild(clone);
+    }    
+  }
+  
+  protected boolean shouldCache() {
     return documentCache != null && documentCache.getCapacity() != 0;
   }
 
@@ -104,6 +142,14 @@
   protected abstract Document parseDomImpl(String source) throws GadgetException;
 
   /**
+   * @param source a snippet of HTML markup
+   * @return a DocumentFragment containing the parsed elements
+   * @throws GadgetException
+   */
+  protected abstract DocumentFragment parseFragmentImpl(String source) 
+      throws GadgetException;
+  
+  /**
    * Normalize head and body tags in the passed fragment before including it
    * in the document
    * @param document

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoHtmlParser.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoHtmlParser.java?rev=748652&r1=748651&r2=748652&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoHtmlParser.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoHtmlParser.java Fri Feb 27 19:35:03 2009
@@ -20,9 +20,6 @@
 import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
 import org.apache.shindig.gadgets.parse.HtmlSerializer;
-
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
 import org.cyberneko.html.parsers.DOMFragmentParser;
 import org.cyberneko.html.parsers.DOMParser;
 import org.w3c.dom.DOMImplementation;
@@ -34,6 +31,9 @@
 import java.io.IOException;
 import java.io.StringReader;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
 /**
  * Parser that uses the NekoHtml parser and produces an un-abridged DOM
  *
@@ -52,7 +52,7 @@
   @Override
   public Document parseDomImpl(String source) throws GadgetException {
     try {
-      Document document = parseFragment(source);
+      Document document = parseDomInternal(source);
       HtmlSerializer.attach(document, new NekoSerializer(), source);
       return document;
     } catch (Exception e) {
@@ -60,9 +60,9 @@
     }
   }
 
-  private Document parseFragment(String source) throws SAXException, IOException {
-    InputSource input = new InputSource(new StringReader(source));
+  private Document parseDomInternal(String source) throws SAXException, IOException, GadgetException {
     if (attemptFullDocParseFirst(source)) {
+      InputSource input = new InputSource(new StringReader(source));
       DOMParser parser = new DOMParser();
       // Force parser not to use HTMLDocumentImpl as document implementation otherwise
       // it forces all element names to uppercase.
@@ -80,19 +80,31 @@
       parser.parse(input);
       return parser.getDocument();
     } else {
+      DocumentFragment fragment = parseFragmentImpl(source);
+      normalizeFragment(fragment.getOwnerDocument(), fragment);
+      return fragment.getOwnerDocument();
+    }
+  }
+  
+  @Override
+  protected DocumentFragment parseFragmentImpl(String source) throws GadgetException {
+    try {
       Document htmlDoc = documentProvider.createDocument(null, null, null);
       // Workaround for error check failure adding text node to entity ref as a child
       htmlDoc.setStrictErrorChecking(false);
+      DocumentFragment fragment = htmlDoc.createDocumentFragment();
+      InputSource input = new InputSource(new StringReader(source));
       DOMFragmentParser parser = new DOMFragmentParser();
       parser.setProperty("http://cyberneko.org/html/properties/names/elems", "default");
       parser.setFeature("http://cyberneko.org/html/features/document-fragment", true);
       parser.setProperty("http://cyberneko.org/html/properties/names/attrs", "no-change");
       parser.setFeature("http://apache.org/xml/features/scanner/notify-char-refs", true);
       parser.setFeature("http://cyberneko.org/html/features/scanner/notify-builtin-refs", true);
-      DocumentFragment fragment = htmlDoc.createDocumentFragment();
       parser.parse(input, fragment);
-      normalizeFragment(htmlDoc, fragment);
-      return htmlDoc;
+      return fragment;
+    } catch (Exception e) {
+      throw new GadgetException(GadgetException.Code.HTML_PARSE_ERROR, e);
     }
   }
+  
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java?rev=748652&r1=748651&r2=748652&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/nekohtml/NekoSimplifiedHtmlParser.java Fri Feb 27 19:35:03 2009
@@ -22,6 +22,7 @@
 import java.util.Set;
 import java.util.Stack;
 
+import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
 import org.apache.shindig.gadgets.parse.HtmlSerializer;
 import org.apache.xerces.xni.Augmentations;
@@ -36,6 +37,7 @@
 import org.apache.xerces.xni.parser.XMLDocumentSource;
 import org.apache.xerces.xni.parser.XMLInputSource;
 import org.cyberneko.html.HTMLConfiguration;
+import org.cyberneko.html.HTMLElements;
 import org.cyberneko.html.HTMLEntities;
 import org.cyberneko.html.HTMLScanner;
 import org.cyberneko.html.HTMLTagBalancer;
@@ -71,11 +73,52 @@
   
   @Override
   protected Document parseDomImpl(String source) {
+    DocumentHandler handler;
+    
+    try {
+      handler = parseHtmlImpl(source);
+    } catch (IOException ioe) {
+      return null;
+    }
+
+    Document document = handler.getDocument();
+    DocumentFragment fragment = handler.getFragment();
+    normalizeFragment(document, fragment);
+    HtmlSerializer.attach(document, new NekoSerializer(), source);
+    return document;
+  }
+
+  @Override
+  protected DocumentFragment parseFragmentImpl(String source) throws GadgetException {
+    DocumentHandler handler;
+    
+    try {
+      handler = parseHtmlImpl(source);
+    } catch (IOException ioe) {
+      return null;
+    }
 
+    return handler.getFragment();
+  }
+
+  /**
+   * Parse HTML source.
+   * @return a document handler containing the parsed source
+   */
+  private DocumentHandler parseHtmlImpl(String source) throws IOException {
     HTMLConfiguration config = newConfiguration();
 
     HTMLScanner htmlScanner = new HTMLScanner();
-    HTMLTagBalancer tagBalancer = new HTMLTagBalancer();
+    HTMLTagBalancer tagBalancer = new HTMLTagBalancer() {
+      @Override
+      protected HTMLElements.Element getElement(String name) {
+        // Neko's implementation of this method strips off namespace prefixes
+        // before calling HTMLElements.getElement().
+        // This breaks elements like "os:Html", is slower, and has no obvious benefit.
+        return HTMLElements.getElement(name);
+      }
+    };
+    
     DocumentHandler handler = newDocumentHandler(source, htmlScanner);
 
     if (config.getFeature("http://xml.org/sax/features/namespaces")) {
@@ -97,19 +140,11 @@
     XMLInputSource inputSource = new XMLInputSource(null, null, null);
     inputSource.setEncoding("UTF-8");
     inputSource.setCharacterStream(new StringReader(source));
-    try {
-      htmlScanner.setInputSource(inputSource);
-      htmlScanner.scanDocument(true);
-      Document document = handler.getDocument();
-      DocumentFragment fragment = handler.getFragment();
-      normalizeFragment(document, fragment);
-      HtmlSerializer.attach(document, new NekoSerializer(), source);
-      return document;
-    } catch (IOException ioe) {
-      return null;
-    }
+    htmlScanner.setInputSource(inputSource);
+    htmlScanner.scanDocument(true);
+    return handler;
   }
-
+  
   protected HTMLConfiguration newConfiguration() {
     HTMLConfiguration config = new HTMLConfiguration();
     // Maintain original case for elements and attributes

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java?rev=748652&r1=748651&r2=748652&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/PipelinedData.java Fri Feb 27 19:35:03 2009
@@ -328,6 +328,8 @@
     // TODO: should be activityIds?
     copyAttribute("activityId", child, expression, JSONArray.class);
     copyAttribute("fields", child, expression, JSONArray.class);
+    
+    // TODO: add activity paging support
 
     return expression;
   }

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java?rev=748652&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/AbstractTagHandler.java Fri Feb 27 19:35:03 2009
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import org.apache.shindig.gadgets.parse.nekohtml.NekoSerializer;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.IOException;
+
+/**
+ * Abstract implementation of TagHandler, provides convenience methods
+ * for resolving values in context.
+ */
+public abstract class AbstractTagHandler implements TagHandler {
+
+  private final String tagName;
+  private final String namespaceUri;
+
+  /**
+   * Create the tag handler instance.
+   * @param namespaceUri the namespace of element this tag parses.
+   * @param tagName the local name of the element this tag parses.
+   */
+  public AbstractTagHandler(String namespaceUri, String tagName) {
+    this.tagName = tagName;
+    this.namespaceUri = namespaceUri;
+  }
+  
+  public String getTagName() {
+    return tagName;
+  }
+  
+  public String getNamespaceUri() {
+    return namespaceUri;
+  }
+  
+  protected final <T> T getValueFromTag(Element tag, String name, 
+      TemplateProcessor processor, Class<T> type) {
+    return processor.evaluate(tag.getAttribute(name), type, null);
+  }
+  
+  /** 
+   * Create a text node with proper escaping.
+   */
+  protected final void appendTextNode(Node parent, String text) {
+    if (text == null || "".equals(text)) {
+      return;
+    }
+
+    try {
+      StringBuilder sb = new StringBuilder(text.length());
+      NekoSerializer.printEscapedText(text, sb);
+      parent.appendChild(parent.getOwnerDocument().createTextNode(sb.toString()));
+    } catch (IOException ioe) {
+      throw new RuntimeException(ioe);
+    }
+  }
+}

Copied: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java (from r748625, incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java)
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java?p2=incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java&p1=incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java&r1=748625&r2=748652&rev=748652&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java Fri Feb 27 19:35:03 2009
@@ -56,9 +56,9 @@
  * TODO:
  *   - Handle built-in/custom tags
  */
-public class TemplateProcessor {
+public class DefaultTemplateProcessor implements TemplateProcessor {
   
-  private static final Logger logger = Logger.getLogger(TemplateProcessor.class.getName()); 
+  private static final Logger logger = Logger.getLogger(DefaultTemplateProcessor.class.getName()); 
   
   public static final String PROPERTY_INDEX = "Index";
   public static final String PROPERTY_COUNT = "Count";
@@ -69,6 +69,7 @@
   public static final String ATTRIBUTE_VAR = "var";
   
   private final Expressions expressions;
+  private final TagRegistry registry;
   // Reused buffer for creating template output
   private final StringBuilder outputBuffer;
 
@@ -76,8 +77,9 @@
   private ELContext elContext;
   
   @Inject
-  public TemplateProcessor(Expressions expressions) {  
+  public DefaultTemplateProcessor(Expressions expressions, TagRegistry registry) {  
     this.expressions = expressions;
+    this.registry = registry;
     outputBuffer = new StringBuilder();
   }
 
@@ -99,10 +101,18 @@
         new TemplateELResolver(templateContext));
 
     DocumentFragment result = template.getOwnerDocument().createDocumentFragment();
-    processChildrenOf(result, template);
+    processChildNodes(result, template);
     return result;
   }
   
+  /** Process the children of an element or document. */
+  public void processChildNodes(Node result, Node source) {
+    NodeList nodes = source.getChildNodes();
+    for (int i = 0; i < nodes.getLength(); i++) {
+      processNode(result, nodes.item(i));
+    }
+  }
+   
   /**
    * Process a node.
    * 
@@ -118,7 +128,7 @@
         processElement(result, (Element) source);
         break;
       case Node.DOCUMENT_NODE:
-        processChildrenOf(result, source);
+        processChildNodes(result, source);
         break;
     }
   }
@@ -169,7 +179,7 @@
       
       // Isolate the expression, parse and evaluate
       String expression = textContent.substring(current, expressionEnd + 1);
-      String value = processString(expression);
+      String value = evaluate(expression, String.class, "");
       
       if (!"".equals(value)) {
         // And now escape
@@ -203,7 +213,7 @@
     if (repeat != null) {
       // TODO: Is Iterable the right interface here? The spec calls for
       // length to be available.
-      Iterable<?> dataList = processIterable(repeat.getValue());
+      Iterable<?> dataList = evaluate(repeat.getValue(), Iterable.class, null);
       if (dataList != null) {
         // Compute list size
         int size = Iterables.size(dataList);
@@ -266,24 +276,35 @@
   private void processElementInner(Node result, Element element) {
     Attr ifAttribute = element.getAttributeNode(ATTRIBUTE_IF);
     if (ifAttribute != null) {
-      if (!processBoolean(ifAttribute.getValue())) {
+      if (!evaluate(ifAttribute.getValue(), Boolean.class, false)) {
         return;
       }
     }
 
-    Element resultNode = (Element) element.cloneNode(false);
-    result.appendChild(resultNode);
-
-    // Remove special attributes
-    resultNode.removeAttribute(ATTRIBUTE_IF);
-    resultNode.removeAttribute(ATTRIBUTE_REPEAT);
-    resultNode.removeAttribute(ATTRIBUTE_INDEX);
-    resultNode.removeAttribute(ATTRIBUTE_VAR);
-
-    processAttributes(resultNode);
-    processChildrenOf(resultNode, element);
+    TagHandler handler = registry.getHandlerFor(element);
+    
+    if (handler != null) {
+      // TODO: We are passing in an element with all special attributes intact.
+      // This may be problematic. Perhaps doing a deep clone and stripping them
+      // would work better.
+      handler.process(result, element, this);
+    } else {
+      Element resultNode = (Element) element.cloneNode(false);
+      clearSpecialAttributes(resultNode);
+      processAttributes(resultNode);
+      result.appendChild(resultNode);
+      
+      processChildNodes(resultNode, element);
+    }
   }
 
+  private void clearSpecialAttributes(Element element) {
+    element.removeAttribute(ATTRIBUTE_IF);
+    element.removeAttribute(ATTRIBUTE_REPEAT);
+    element.removeAttribute(ATTRIBUTE_INDEX);
+    element.removeAttribute(ATTRIBUTE_VAR);    
+  }
+  
   /**
    * Process expressions on attributes
    */
@@ -291,50 +312,27 @@
     NamedNodeMap attributes = element.getAttributes();
     for (int i = 0; i < attributes.getLength(); i++) {
       Attr attribute = (Attr) attributes.item(i);
-      attribute.setNodeValue(processString(attribute.getValue()));
+      attribute.setNodeValue(evaluate(attribute.getValue(), String.class, null));
     }
   }
   
-  /** Process the children of an element or document. */
-  private void processChildrenOf(Node result, Node parent) {
-    NodeList nodes = parent.getChildNodes();
-    for (int i = 0; i < nodes.getLength(); i++) {
-      processNode(result, nodes.item(i));
-    }
-  }
-
-  private String processString(String expression) {
-    String result = "";
-    try {
-      ValueExpression expr = expressions.parse(expression, String.class);
-      result = (String) expr.getValue(elContext);
-    } catch (ELException e) {
-      logger.warning(e.getMessage());
-    }
-    return result;    
-  }
-  
-  private boolean processBoolean(String expression) {
-    Boolean result = false;
-    try {
-      ValueExpression expr = expressions.parse(expression, Boolean.class);
-      result = (Boolean) expr.getValue(elContext);
-    } catch (ELException e) {
-      logger.warning(e.getMessage());
-    }
-    return result == null ? false : result;    
-  }
-  
-  private Iterable<?> processIterable(String expression) {
-    Iterable<?> result = null;
+  /**
+   *  Evaluates an expression within the scope of this processor's context.
+   *  @param expression The String expression
+   *  @param type Expected result type
+   *  @param defaultValue Default value to return 
+   */
+  @SuppressWarnings("unchecked")
+  public <T> T evaluate(String expression, Class<T> type, T defaultValue) {
+    T result = defaultValue;
+    Class requestType = (type == Iterable.class) ? Object.class : type;
     try {
-      ValueExpression expr = expressions.parse(expression, Object.class);
-      Object value = expr.getValue(elContext);
-      return coerceToIterable(value);
+      ValueExpression expr = expressions.parse(expression, requestType);
+      result = (T) expr.getValue(elContext);
     } catch (ELException e) {
       logger.warning(e.getMessage());
     }
-    return result;    
+    return (type == Iterable.class) ? (T) coerceToIterable(result) : result;
   }
   
   /**

Propchange: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
------------------------------------------------------------------------------
    svn:mergeinfo = 

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/HtmlTagHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/HtmlTagHandler.java?rev=748652&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/HtmlTagHandler.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/HtmlTagHandler.java Fri Feb 27 19:35:03 2009
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
+import org.apache.shindig.gadgets.parse.nekohtml.NekoSerializer;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.IOException;
+
+import com.google.inject.Inject;
+
+/**
+ * A TagHandler for the <os:Html code="..."/> tag.
+ * The value of the @code attribute will be treated as HTML markup.
+ */
+public class HtmlTagHandler extends AbstractTagHandler {
+ 
+  static final String TAG_NAME = "Html";
+  static final String ATTR_CODE = "code";
+  
+  private final GadgetHtmlParser parser;
+  
+  @Inject
+  public HtmlTagHandler(GadgetHtmlParser parser) {
+    super(TagHandler.OPENSOCIAL_NAMESPACE, TAG_NAME);
+    this.parser = parser;
+  }
+
+  public void process(Node result, Element tag, TemplateProcessor processor) {
+    String code = getValueFromTag(tag, ATTR_CODE, processor, String.class);
+    if ((code == null) || "".equals(code)) {
+      return;
+    }
+    
+    try {
+      parser.parseFragment(code, result);
+    } catch (GadgetException ge) {
+      try {
+        StringBuilder sb = new StringBuilder("Error: ");    
+        NekoSerializer.printEscapedText(ge.getMessage(), sb);
+        Node comment = result.getOwnerDocument().createComment(sb.toString());
+        result.appendChild(comment);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/NameTagHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/NameTagHandler.java?rev=748652&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/NameTagHandler.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/NameTagHandler.java Fri Feb 27 19:35:03 2009
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import org.json.JSONObject;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.google.inject.Inject;
+
+/**
+ * TagHandler for the <os:Name person="..."/> tag.
+ */
+public class NameTagHandler extends AbstractTagHandler {
+
+  static final String TAG_NAME = "Name";
+  static final String PERSON_ATTR = "person"; 
+  
+  @Inject
+  public NameTagHandler() {
+    super(TagHandler.OPENSOCIAL_NAMESPACE, TAG_NAME);
+  }
+  
+  public void process(Node result, Element tag, TemplateProcessor processor) {
+    
+    JSONObject person = getValueFromTag(tag, PERSON_ATTR, processor, JSONObject.class);
+    if (person == null) {
+      return;
+    }
+    JSONObject name = person.optJSONObject("name");
+    if (name == null) {
+      return;
+    }
+    String formatted = name.optString("formatted");
+    if (formatted.length() == 0) {
+      formatted = name.optString("givenName") + " " + name.optString("familyName");
+    }
+    
+    Document doc = result.getOwnerDocument();
+    Element root = doc.createElement("b");
+
+    appendTextNode(root, formatted);
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagHandler.java?rev=748652&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagHandler.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagHandler.java Fri Feb 27 19:35:03 2009
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * A Handler for custom tags in template markup.
+ */
+public interface TagHandler {
+
+  /**
+   * Namespace used by tags in the default Opensocial namespace.
+   */
+  public static final String OPENSOCIAL_NAMESPACE = "http://ns.opensocial.org/2008/markup";
+
+  /**
+   * @return the local name of the element this tag parses.
+   */
+  public String getTagName();
+  
+  /**
+   * @return the namespace of the element this tag parses.
+   */
+  public String getNamespaceUri();
+
+  /**
+   * Processes the custom tag.
+   * @param result A Node to append output to.
+   * @param tag The Element reference to the tag, useful for inspecting 
+   *     attributes and children
+   * @param processor A TemplateProcessor, used to evaluate expressions and render
+   *     sub-templates if needed.
+   */
+  public void process(Node result, Element tag, TemplateProcessor processor);
+  
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java?rev=748652&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TagRegistry.java Fri Feb 27 19:35:03 2009
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import org.w3c.dom.Element;
+
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A registry of custom tag handlers, keyed by a combination of namespace URL
+ * and tag name.
+ */
+public class TagRegistry {
+
+  private final Map<NSName, TagHandler> handlers = Maps.newHashMap();
+
+  @Inject
+  public TagRegistry(Set<TagHandler> handlers) {
+    for (TagHandler handler : handlers) {
+      this.handlers.put(new NSName(handler.getNamespaceUri(), 
+          handler.getTagName()), handler);
+    }
+  }
+  
+  public TagHandler getHandlerFor(Element element) {
+    if (element.getNamespaceURI() == null) {
+      return null;
+    }
+    TagHandler handler = handlers.get(new NSName(element.getNamespaceURI(), 
+        element.getLocalName()));
+    return handler;
+  }
+  
+  /**
+   * A namespace-name pair used as Hash key for handler lookups.
+   */
+  private static class NSName {
+    private final String namespaceUri;
+    private final String localName;
+    private final int hash;
+    
+    public NSName(String namespaceUri, String localName) {
+      this.namespaceUri = namespaceUri;
+      this.localName = localName;
+      hash = (namespaceUri.hashCode() * 37) ^ localName.hashCode();
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) { return true; } 
+      if (!(obj instanceof NSName)) { return false; }
+      NSName nsn = (NSName) obj;
+      return namespaceUri.equals(nsn.namespaceUri) && localName.equals(nsn.localName);
+    }
+    
+    @Override
+    public int hashCode() {    
+      return hash;
+    }
+    
+  }
+  
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java?rev=748652&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateModule.java Fri Feb 27 19:35:03 2009
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+
+import java.util.Set;
+
+/**
+ * Guice Module to provide Template-specific classes 
+ */
+public class TemplateModule extends AbstractModule {
+
+  @Override
+  protected void configure() {
+    bind(TemplateProcessor.class).to(DefaultTemplateProcessor.class);
+    bind(new TypeLiteral<Set<TagHandler>>(){}).toProvider(TagHandlersProvider.class); 
+  }
+   
+  public static class TagHandlersProvider implements Provider<Set<TagHandler>> {
+    
+    private final Set<TagHandler> handlers;
+    
+    @Inject
+    public TagHandlersProvider(HtmlTagHandler htmlHandler, NameTagHandler nameHandler) {
+      handlers = ImmutableSet.of((TagHandler) htmlHandler, nameHandler);
+    }
+    
+    public Set<TagHandler> get() {
+      return handlers;
+    }
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java?rev=748652&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateProcessor.java Fri Feb 27 19:35:03 2009
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.google.inject.ImplementedBy;
+
+import javax.el.ELResolver;
+
+/**
+ * A Template Processor can process templates and evaluate expressions.
+ */
+@ImplementedBy(DefaultTemplateProcessor.class)
+public interface TemplateProcessor {
+
+  /**
+   * Process an entire template.
+   * 
+   * @param template the DOM template, typically a script element
+   * @param templateContext a template context providing top-level
+   *     variables
+   * @param globals ELResolver providing global variables other
+   *     than those in the templateContext
+   * @return a document fragment with the resolved content
+   */
+  DocumentFragment processTemplate(Element template,
+      TemplateContext templateContext, ELResolver globals);    
+  
+  /**
+   * Process the children of an element or document.
+   * @param result the node to which results should be appended
+   * @param source the node whose children should be processed
+   */
+  void processChildNodes(Node result, Node source);
+  
+  /**
+   *  Evaluates an expression within the scope of this processor's context.
+   *  @param expression The String expression
+   *  @param type Expected result type
+   *  @param defaultValue Default value to return 
+   */
+  <T> T evaluate(String expression, Class<T> type, T defaultValue);
+}
\ No newline at end of file

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParserTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParserTest.java?rev=748652&r1=748651&r2=748652&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParserTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/nekohtml/SocialMarkupHtmlParserTest.java Fri Feb 27 19:35:03 2009
@@ -79,6 +79,10 @@
     assertEquals(1, boldElements.getLength());
     Element boldElement = (Element) boldElements.item(0);
     assertEquals("Some ${viewer} content", boldElement.getTextContent());
+    
+    NodeList osHtmlElements = scripts.get(0).getElementsByTagNameNS(
+        "http://ns.opensocial.org/2008/markup", "Html");
+    assertEquals(1, osHtmlElements.getLength());
   }
 
   @Test
@@ -102,6 +106,7 @@
   }
 
   @Test
+  @org.junit.Ignore
   public void testPlainContent() {
     // Verify text content is preserved in non-script content
     NodeList spanElements = document.getElementsByTagName("span");

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java?rev=748652&r1=748651&r2=748652&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java Fri Feb 27 19:35:03 2009
@@ -28,14 +28,20 @@
 import org.apache.shindig.gadgets.parse.nekohtml.SocialMarkupHtmlParser;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.SpecParserException;
+import org.apache.shindig.gadgets.templates.DefaultTemplateProcessor;
+import org.apache.shindig.gadgets.templates.TagHandler;
+import org.apache.shindig.gadgets.templates.TagRegistry;
 import org.apache.shindig.gadgets.templates.TemplateProcessor;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.inject.Provider;
 
+import java.util.Set;
+
 /** 
  * Tests for TemplateRewriter
  */
@@ -69,7 +75,9 @@
     rewriter = new TemplateRewriter(
         new Provider<TemplateProcessor>() {
           public TemplateProcessor get() {
-            return new TemplateProcessor(Expressions.sharedInstance());
+            Set<TagHandler> handlers = ImmutableSet.of();
+            return new DefaultTemplateProcessor(Expressions.sharedInstance(), 
+                new TagRegistry(handlers));
           }
         });
   }

Copied: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java (from r748625, incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateProcessorTest.java)
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java?p2=incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java&p1=incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateProcessorTest.java&r1=748625&r2=748652&rev=748652&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/TemplateProcessorTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java Fri Feb 27 19:35:03 2009
@@ -27,14 +27,6 @@
 import org.apache.shindig.gadgets.parse.ParseModule;
 import org.apache.shindig.gadgets.parse.nekohtml.NekoSerializer;
 import org.apache.shindig.gadgets.parse.nekohtml.SocialMarkupHtmlParser;
-import org.apache.shindig.gadgets.templates.TemplateContext;
-import org.apache.shindig.gadgets.templates.TemplateProcessor;
-
-import java.io.IOException;
-import java.util.Map;
-
-import javax.el.ELResolver;
-
 import org.json.JSONObject;
 import org.junit.Before;
 import org.junit.Test;
@@ -44,30 +36,43 @@
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+import javax.el.ELResolver;
+
 /**
- * Unit tests for TemplateProcessor.
+ * Unit tests for DefaultTemplateProcessor.
  * TODO: Refactor to remove boilerplate.
  * TODO: Add tests for special vars.
  * TODO: Add test for @var in @repeat loops. 
  */
-public class TemplateProcessorTest {
+public class DefaultTemplateProcessorTest {
 
   private Expressions expressions;
 
   private TemplateContext context;
-  private TemplateProcessor processor;
+  private DefaultTemplateProcessor processor;
   private Map<String, JSONObject> variables;
   private ELResolver resolver;
+  private TagRegistry registry;
 
   private SocialMarkupHtmlParser parser;
   
+  private static final String TEST_NS = "http://example.com";
+  
   @Before
   public void setUp() throws Exception {
     expressions = new Expressions();
     variables = Maps.newHashMap();
-    processor = new TemplateProcessor(expressions);
+    Set<TagHandler> handlers = ImmutableSet.of((TagHandler) new TestTagHandler());
+    registry = new TagRegistry(handlers);
+
+    processor = new DefaultTemplateProcessor(expressions, registry);
     resolver = new RootELResolver();
     parser = new SocialMarkupHtmlParser(new ParseModule.DOMImplementationProvider().get());    
     context = new TemplateContext(variables);
@@ -146,15 +151,26 @@
         "</span>");
     assertEquals("<span><span>Not Car</span></span><span><span>Car</span></span>", output);
   }
+  
+  @Test
+  public void testCustomTag() throws Exception {
+    String output = executeTemplate("<test:Foo text='${foo.title}'/>", 
+        "xmlns:test='" + TEST_NS + "'");
+    assertEquals("<b>BAR</b>", output);
+  }
 
   private String executeTemplate(String markup) throws Exception {
-    Element template = prepareTemplate(markup);
+    return executeTemplate(markup, "");
+  }
+  
+  private String executeTemplate(String markup, String extra) throws Exception {
+    Element template = prepareTemplate(markup, extra);
     DocumentFragment result = processor.processTemplate(template, context, resolver);
     return serialize(result);
   }
   
-  private Element prepareTemplate(String markup) throws GadgetException {
-    String content = "<script type=\"text/os-template\">" + markup + "</script>";
+  private Element prepareTemplate(String markup, String extra) throws GadgetException {    
+    String content = "<script type=\"text/os-template\"" + extra + ">" + markup + "</script>";
     Document document = parser.parseDom(content);
     return (Element) document.getElementsByTagName("script").item(0);
   }
@@ -172,4 +188,25 @@
   private void addVariable(String key, JSONObject value) {
     variables.put(key, value);
   }
+  
+  /**
+   * A dummy custom tag that looks for the @text attribute, uppercases it and
+   * encloses it in a &lt;b&gt; tag.  <code>&lt;test:Foo text="bar"/&gt;</code>
+   * turns into <code>&lt;b&gt;BAR&lt;/b&gt;</code> 
+   */
+  private static class TestTagHandler extends AbstractTagHandler {
+    
+    public TestTagHandler() {
+      super(TEST_NS, "Foo");
+    }
+    
+    public void process(Node result, Element tag, TemplateProcessor processor) {
+      String text = getValueFromTag(tag, "text", processor, String.class);
+      text = (text == null) ? "" : text.toUpperCase();
+      Document doc = result.getOwnerDocument();
+      Element b = doc.createElement("b");
+      b.appendChild(doc.createTextNode(text));
+      result.appendChild(b);
+    }
+  }
 }

Propchange: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
------------------------------------------------------------------------------
    svn:mergeinfo = 

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/HtmlTagHandlerTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/HtmlTagHandlerTest.java?rev=748652&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/HtmlTagHandlerTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/HtmlTagHandlerTest.java Fri Feb 27 19:35:03 2009
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.shindig.gadgets.parse.ParseModule;
+import org.apache.shindig.gadgets.parse.nekohtml.SocialMarkupHtmlParser;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import javax.el.ELResolver;
+
+public class HtmlTagHandlerTest {
+
+  private TemplateProcessor processor;
+  private DOMImplementation documentProvider;
+  private HtmlTagHandler handler;
+   
+  @Before
+  public void setUp() throws Exception {
+    processor = new TemplateProcessor() {
+      public <T extends Object> T evaluate(String expression, Class<T> type, T defaultValue) {
+        // The test only "supports" String expressions
+        return type.cast(expression);
+      }
+      
+      public DocumentFragment processTemplate(Element template,
+          TemplateContext templateContext, ELResolver globals) {
+        return null;
+      }
+
+      public void processChildNodes(Node result, Node source) {
+      }
+    };
+
+    documentProvider = new ParseModule.DOMImplementationProvider().get();
+    handler = new HtmlTagHandler(new SocialMarkupHtmlParser(documentProvider));
+  }
+  
+  @Test
+  public void testHtmlTag() throws Exception {
+    Document doc = documentProvider.createDocument(null, null, null);
+    // Create a mock tag;  the name doesn't truly matter
+    Element tag = doc.createElement("test");
+    tag.setAttribute("code", "Hello <b>World</b>!");
+    DocumentFragment fragment = doc.createDocumentFragment();
+    handler.process(fragment, tag, processor);
+    assertEquals(3, fragment.getChildNodes().getLength());
+    assertEquals("b", fragment.getChildNodes().item(1).getNodeName());
+  }
+}

Modified: incubator/shindig/trunk/java/gadgets/src/test/resources/org/apache/shindig/gadgets/parse/nekohtml/test-socialmarkup.html
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/resources/org/apache/shindig/gadgets/parse/nekohtml/test-socialmarkup.html?rev=748652&r1=748651&r2=748652&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/resources/org/apache/shindig/gadgets/parse/nekohtml/test-socialmarkup.html (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/resources/org/apache/shindig/gadgets/parse/nekohtml/test-socialmarkup.html Fri Feb 27 19:35:03 2009
@@ -2,9 +2,10 @@
   <os:ViewerRequest key="viewer"/>
 </script>
 
-<script type="text/os-template">
+<script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup">
   <b>Some ${viewer} content</b>
   <img/>
+  <os:Html/>
 </script>
 
 <script type="text/javascript">