You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by jo...@apache.org on 2008/08/15 03:57:48 UTC

svn commit: r686108 - in /incubator/shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/ main/java/org/apache/shindig/gadgets/parse/caja/ test/java/org/apache/shindig/gadgets/parse/caja/

Author: johnh
Date: Thu Aug 14 18:57:48 2008
New Revision: 686108

URL: http://svn.apache.org/viewvc?rev=686108&view=rev
Log:
Caja-based HTML and CSS parser implementations.

Uses Caja's parse trees for HTML and CSS to satisfy the GadgetHtmlParser and GadgetCssParser
interfaces.

These classes stand alone for the moment ie. don't affect existing functionality.

This CL resolves SHINDIG-502


Added:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaCssParser.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParser.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaCssParserTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParserTest.java
Modified:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java?rev=686108&r1=686107&r2=686108&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java Thu Aug 14 18:57:48 2008
@@ -58,6 +58,11 @@
 
     // Caja error
     MALFORMED_FOR_SAFE_INLINING,
+    
+    // Parsing errors
+    CSS_PARSE_ERROR,
+    HTML_PARSE_ERROR,
+    JS_PARSE_ERROR,
 
     // View errors
     UNKNOWN_VIEW_SPECIFIED,

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaCssParser.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaCssParser.java?rev=686108&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaCssParser.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaCssParser.java Thu Aug 14 18:57:48 2008
@@ -0,0 +1,176 @@
+/**
+ * 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.parse.caja;
+
+import com.google.caja.lexer.CharProducer;
+import com.google.caja.lexer.CssLexer;
+import com.google.caja.lexer.CssTokenType;
+import com.google.caja.lexer.InputSource;
+import com.google.caja.lexer.ParseException;
+import com.google.caja.lexer.Token;
+import com.google.caja.lexer.TokenConsumer;
+import com.google.caja.lexer.TokenQueue;
+import com.google.caja.parser.css.CssParser;
+import com.google.caja.parser.css.CssTree;
+import com.google.caja.reporting.MessageContext;
+import com.google.caja.reporting.RenderContext;
+import com.google.caja.util.Criterion;
+
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.parse.GadgetCssParser;
+import org.apache.shindig.gadgets.parse.ParsedCssDeclaration;
+import org.apache.shindig.gadgets.parse.ParsedCssRule;
+
+import java.io.StringReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CajaCssParser implements GadgetCssParser {
+
+  public List<ParsedCssRule> parse(String css) throws GadgetException {
+    if (css.matches("\\s*")) {
+      return new ArrayList<ParsedCssRule>(0);
+    }
+    
+    CssParser parser = getParser(css);
+    CssTree.StyleSheet stylesheet = null;
+    
+    try {
+      stylesheet = parser.parseStyleSheet();
+    } catch (ParseException e) {
+      throw new GadgetException(GadgetException.Code.CSS_PARSE_ERROR, e);
+    }
+    
+    ArrayList<ParsedCssRule> rules =
+        new ArrayList<ParsedCssRule>(stylesheet.children().size());
+    for (CssTree node : stylesheet.children()) {
+      if (node instanceof CssTree.RuleSet) {
+        rules.add(new CajaParsedCssRule((CssTree.RuleSet)node));
+      }
+    }
+    
+    return rules;
+  }
+
+  public List<ParsedCssDeclaration> parseInline(String style)
+      throws GadgetException {
+    if (style.matches("\\s*")) {
+      return new ArrayList<ParsedCssDeclaration>();
+    }
+    
+    CssParser parser = getParser(style);
+    CssTree.DeclarationGroup declGroup = null;
+    
+    try {
+      declGroup = parser.parseDeclarationGroup();
+    } catch (ParseException e) {
+      throw new GadgetException(GadgetException.Code.CSS_PARSE_ERROR, e);
+    }
+    
+    List<ParsedCssDeclaration> attributes =
+        new ArrayList<ParsedCssDeclaration>(declGroup.children().size());
+    for (CssTree node : declGroup.children()) {
+      if (node instanceof CssTree.Declaration) {
+        CssTree.Declaration decl = (CssTree.Declaration)node;
+        if (decl.getProperty() != null) {
+          attributes.add(new CajaParsedCssDeclaration(decl));
+        }
+      }
+    }
+    
+    return attributes;
+  }
+  
+  private CssParser getParser(String content) {
+    InputSource source = null;
+    try {
+      source = new InputSource(new URI("http://dummy.com/"));
+    } catch (URISyntaxException e) {
+      // Never happens. Dummy URI needed to satisfy API.
+      // We may want to pass in the gadget URI for auditing
+      // purposes at some point.
+    }
+    CharProducer producer = CharProducer.Factory.create(new StringReader(content), source);
+    CssLexer lexer = new CssLexer(producer);
+    return new CssParser(new TokenQueue<CssTokenType>(
+        lexer,
+        source,
+        new Criterion<Token<CssTokenType>>() {  
+          public boolean accept(Token<CssTokenType> tok) {
+            return tok.type != CssTokenType.COMMENT
+                && tok.type != CssTokenType.SPACE;
+          }
+        }));
+  }
+  
+  private static final String renderCssTreeElement(CssTree elem) {
+    StringBuffer selBuffer = new StringBuffer();
+    TokenConsumer tc = elem.makeRenderer(selBuffer, null);
+    elem.render(new RenderContext(new MessageContext(), tc));
+    return selBuffer.toString();
+  }
+  
+  private static class CajaParsedCssRule implements ParsedCssRule {
+    private final List<ParsedCssDeclaration> attributes;
+    private final List<String> selectors;
+    
+    private CajaParsedCssRule(CssTree.RuleSet ruleSet) {
+      attributes = new ArrayList<ParsedCssDeclaration>();
+      selectors = new ArrayList<String>();
+      
+      for (CssTree child : ruleSet.children()) {
+        if (child instanceof CssTree.Selector) {
+          selectors.add(renderCssTreeElement(child));
+        } else if (child instanceof CssTree.Declaration) {
+          CssTree.Declaration decl = (CssTree.Declaration)child;
+          if (decl.getProperty() != null) {
+            attributes.add(new CajaParsedCssDeclaration(decl));
+          }
+        }
+      }
+    }
+
+    public List<ParsedCssDeclaration> getDeclarations() {
+      return attributes;
+    }
+
+    public List<String> getSelectors() {
+      return selectors;
+    }
+  }
+  
+  private static class CajaParsedCssDeclaration implements ParsedCssDeclaration {
+    private final String key;
+    private final String value;
+    
+    private CajaParsedCssDeclaration(CssTree.Declaration declaration) {
+      key = declaration.getProperty().getPropertyName();
+      value = renderCssTreeElement(declaration.getExpr());
+    }
+    
+    public String getName() {
+      return key;
+    }
+
+    public String getValue() {
+      return value;
+    }
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParser.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParser.java?rev=686108&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParser.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParser.java Thu Aug 14 18:57:48 2008
@@ -0,0 +1,163 @@
+/**
+ * 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.parse.caja;
+
+import com.google.caja.lexer.CharProducer;
+import com.google.caja.lexer.HtmlLexer;
+import com.google.caja.lexer.HtmlTokenType;
+import com.google.caja.lexer.InputSource;
+import com.google.caja.lexer.ParseException;
+import com.google.caja.lexer.TokenQueue;
+import com.google.caja.parser.html.DomParser;
+import com.google.caja.parser.html.DomTree;
+import com.google.caja.reporting.MessageQueue;
+import com.google.caja.reporting.SimpleMessageQueue;
+
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
+import org.apache.shindig.gadgets.parse.ParsedHtmlAttribute;
+import org.apache.shindig.gadgets.parse.ParsedHtmlNode;
+
+import java.io.StringReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Caja-based implementation of a {@code GadgetHtmlParser}.
+ */
+public class CajaHtmlParser implements GadgetHtmlParser {
+
+  /** {@inheritDoc */
+  public List<ParsedHtmlNode> parse(String source) throws GadgetException {
+    // Wrap the whole thing in a top-level node to get full contents.
+    DomParser parser = getParser("<html>" + source + "</html>");
+    
+    DomTree domTree = null;
+    try {
+      domTree = parser.parseFragment();
+    } catch (ParseException e) {
+      throw new GadgetException(GadgetException.Code.CSS_PARSE_ERROR, e);
+    }
+    
+    List<ParsedHtmlNode> nodes =
+        new ArrayList<ParsedHtmlNode>(domTree.children().size());
+    for (DomTree child : domTree.children()) {
+      nodes.add(new CajaParsedHtmlNode(child));
+    }
+    return nodes;
+  }
+  
+  public DomParser getParser(String content) {
+    InputSource source = null;
+    try {
+      source = new InputSource(new URI("http://dummy.com/"));
+    } catch (URISyntaxException e) {
+      // Never happens. Dummy URI needed to satisfy API.
+      // We may want to pass in the gadget URI for auditing
+      // purposes at some point.
+    }
+    CharProducer producer = CharProducer.Factory.create(
+        new StringReader(content), source);
+    HtmlLexer lexer = new HtmlLexer(producer);
+    MessageQueue mQueue = new SimpleMessageQueue();
+    return new DomParser(new TokenQueue<HtmlTokenType>(lexer, source), false, mQueue);
+  }
+
+  /**
+   * {@code ParsedHtmlNode} implementation built using Caja parsing primitives.
+   */
+  private static class CajaParsedHtmlNode implements ParsedHtmlNode {
+    private final List<ParsedHtmlAttribute> attributes;
+    private final List<ParsedHtmlNode> children;
+    private final String name;
+    private final String text;
+    
+    private CajaParsedHtmlNode(DomTree elem) {
+      if (elem instanceof DomTree.Tag) {
+        DomTree.Tag tag = (DomTree.Tag)elem;
+        attributes = new ArrayList<ParsedHtmlAttribute>();
+        children = new ArrayList<ParsedHtmlNode>();
+        name = tag.getTagName();
+        text = null;
+        for (DomTree child : elem.children()) {
+          if (child instanceof DomTree.Attrib) {
+            attributes.add(new CajaParsedHtmlAttribute((DomTree.Attrib)child));
+          } else {
+            children.add(new CajaParsedHtmlNode(child));
+          }
+        }
+      } else if (elem instanceof DomTree.Text ||
+                 elem instanceof DomTree.CData) {
+        // DomTree.CData can theoretically occur since it's supported
+        // in HTML5, but the implementation doesn't supply this yet.
+        attributes = null;
+        children = null;
+        name = null;
+        text = ((DomTree.Text)elem).getValue();
+      } else {
+        // This should never happen. The only remaining types are
+        // DomTree.Fragment, which is simply a top-level container
+        // that results from the DomTree.parseFragment() method,
+        // and DomTree.Value, which is always a child of DomTree.Attrib.
+        attributes = null;
+        children = null;
+        name = null;
+        text = null;
+      }
+    }
+    
+    public List<ParsedHtmlAttribute> getAttributes() {
+      return attributes;
+    }
+
+    public List<ParsedHtmlNode> getChildren() {
+      return children;
+    }
+
+    public String getTagName() {
+      return name;
+    }
+
+    public String getText() {
+      return text;
+    }
+  }
+  
+  /**
+   * {@code ParsedHtmlAttribute} built from a Caja DomTree primitive.
+   */
+  private static class CajaParsedHtmlAttribute implements ParsedHtmlAttribute {
+    private final String name;
+    private final String value;
+    
+    private CajaParsedHtmlAttribute(DomTree.Attrib attrib) {
+      name = attrib.getAttribName();
+      value = attrib.getAttribValue();
+    }
+    
+    public String getName() {
+      return name;
+    }
+
+    public String getValue() {
+      return value;
+    }
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaCssParserTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaCssParserTest.java?rev=686108&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaCssParserTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaCssParserTest.java Thu Aug 14 18:57:48 2008
@@ -0,0 +1,229 @@
+/**
+ * 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.parse.caja;
+
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.parse.ParsedCssDeclaration;
+import org.apache.shindig.gadgets.parse.ParsedCssRule;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+public class CajaCssParserTest extends TestCase {
+  private final CajaCssParser csp = new CajaCssParser();
+  
+  public void testParseEveryTypeOfSelector() throws Exception {
+    // List from http://www.w3.org/TR/REC-CSS2/selector.html
+    List<ParsedCssRule> rules =
+        csp.parse("*, E, E F, E > F, E:first-child, E:link, E:active, " +
+                  "E:lang(c), E + F, E[foo], E[foo=\"warning\"], DIV.blah, " +
+                  "E#myId { color: blue; }");
+    assertNotNull(rules);
+    assertEquals(1, rules.size());
+    
+    // Validate single resulting ParsedCssRule
+    ParsedCssRule rule = rules.get(0);
+    assertNotNull(rule.getSelectors());
+    assertEquals(13, rule.getSelectors().size());
+    
+    // Selectors should come back in the order parsed as well
+    // Shouldn't matter if they come out the way they went in
+    // ie. if they're normalized
+    assertEquals("*", rule.getSelectors().get(0));
+    assertEquals("E", rule.getSelectors().get(1));
+    assertTrue(rule.getSelectors().get(2).matches("E\\s+F"));
+    assertTrue(rule.getSelectors().get(3).matches("E\\s+>\\s+F"));
+    assertEquals("E:first-child", rule.getSelectors().get(4));
+    assertEquals("E:link", rule.getSelectors().get(5));
+    assertEquals("E:active", rule.getSelectors().get(6));
+    assertEquals("E:lang(c)", rule.getSelectors().get(7));
+    assertTrue(rule.getSelectors().get(8).matches("E\\s+\\+\\s+F"));
+    assertTrue(rule.getSelectors().get(9).matches("E\\[\\s*foo\\s*\\]"));
+    assertTrue(rule.getSelectors().get(10).matches(
+        "E\\[\\s*foo\\s*=\\s*[\"']warning[\"']\\s*\\]"));
+    assertEquals("DIV.blah", rule.getSelectors().get(11));
+    assertEquals("E#myId", rule.getSelectors().get(12));
+    
+    // Declaration thrown in for good measure
+    assertNotNull(rule.getDeclarations());
+    assertEquals(1, rule.getDeclarations().size());
+    ParsedCssDeclaration decl = rule.getDeclarations().get(0);
+    assertEquals("color", decl.getName());
+    assertEquals("blue", decl.getValue());
+  }
+  
+  public void testParseWithNoDeclarations() throws Exception {
+    List<ParsedCssRule> rules =
+        csp.parse("#id { }");
+    assertNotNull(rules);
+    assertEquals(1, rules.size());
+    
+    ParsedCssRule rule = rules.get(0);
+    assertNotNull(rule);
+    assertNotNull(rule.getSelectors());
+    assertEquals(1, rule.getSelectors().size());
+    assertEquals("#id", rule.getSelectors().get(0));
+    assertNotNull(rule.getDeclarations());
+  }
+
+  public void testParseEmptyContent() throws Exception {
+    List<ParsedCssRule> rules =
+        csp.parse("  \n\t  ");
+    assertNotNull(rules);
+    assertEquals(0, rules.size());
+  }
+  
+  public void testParseMultipleRules() throws Exception {
+    List<ParsedCssRule> rules =
+        csp.parse("#id1 { font-size: 1; } #id2 { font-size: 2; } " +
+                  "#id3 { font-size: 3; }");
+    assertNotNull(rules);
+    assertEquals(3, rules.size());
+    
+    // Scoped to hide each rule test from each other
+    {
+      ParsedCssRule rule = rules.get(0);
+      assertNotNull(rule);
+      assertNotNull(rule.getSelectors());
+      assertEquals("#id1", rule.getSelectors().get(0));
+      assertNotNull(rule.getDeclarations());
+      assertEquals("font-size", rule.getDeclarations().get(0).getName());
+      assertEquals("1", rule.getDeclarations().get(0).getValue());
+    }
+    
+    {
+      ParsedCssRule rule = rules.get(1);
+      assertNotNull(rule);
+      assertNotNull(rule.getSelectors());
+      assertEquals("#id2", rule.getSelectors().get(0));
+      assertNotNull(rule.getDeclarations());
+      assertEquals("font-size", rule.getDeclarations().get(0).getName());
+      assertEquals("2", rule.getDeclarations().get(0).getValue());
+    }
+    
+    {
+      ParsedCssRule rule = rules.get(2);
+      assertNotNull(rule);
+      assertNotNull(rule.getSelectors());
+      assertEquals("#id3", rule.getSelectors().get(0));
+      assertNotNull(rule.getDeclarations());
+      assertEquals("font-size", rule.getDeclarations().get(0).getName());
+      assertEquals("3", rule.getDeclarations().get(0).getValue());
+    }
+  }
+  
+  public void testParseCssNoTrailingSemicolon() throws Exception {
+    List<ParsedCssRule> rules =
+        csp.parse("#id { color:blue; font: verdana }");
+    assertNotNull(rules);
+    assertEquals(1, rules.size());
+    
+    ParsedCssRule rule = rules.get(0);
+    assertNotNull(rule);
+    assertNotNull(rule.getSelectors());
+    assertEquals("#id", rule.getSelectors().get(0));
+    assertNotNull(rule.getDeclarations());
+    assertEquals(2, rule.getDeclarations().size());
+    assertEquals("color", rule.getDeclarations().get(0).getName());
+    assertEquals("blue", rule.getDeclarations().get(0).getValue());
+    assertEquals("font", rule.getDeclarations().get(1).getName());
+    assertEquals("verdana", rule.getDeclarations().get(1).getValue());
+  }
+  
+  public void testParseInvalidCssNoDeclValue() throws Exception {
+    try {
+      String css = "#id { color: ; font-size: 10; }";
+      csp.parse(css);
+      fail("Should have failed to parse invalid CSS: " + css);
+    } catch (GadgetException e) {
+      // Expected condition.
+    }
+  }
+  
+  public void testParseInvalidCssNoClosingBrace() throws Exception {
+    try {
+      String css = "#id { color: blue; ";
+      csp.parse(css);
+      fail("Should have failed to parse invalid CSS: " + css);
+    } catch (GadgetException e) {
+      // Expected condition.
+    }
+  }
+  
+  public void testParseInvalidCssNoSelector() throws Exception {
+    try {
+      String css = "{ color: green; font: verdana; }";
+      csp.parse(css);
+      fail("Should have failed to parse invalid CSS: " + css);
+    } catch (GadgetException e) {
+      // Expected condition.
+    }
+  }
+  
+  public void testParseInvalidCssNoSeparator() throws Exception {
+    try {
+      String css = "#id { color blue; }";
+      csp.parse(css);
+      fail("Should have failed to parse invalid CSS: " + css);
+    } catch (GadgetException e) {
+      // Expected condition.
+    }
+  }
+  
+  public void testParseInlineGeneralDeclarations() throws Exception {
+    List<ParsedCssDeclaration> decls =
+        csp.parseInline("font-size: 10 em; color: green; font-color: #343434;");
+    assertNotNull(decls);
+    assertEquals(3, decls.size());
+    assertEquals("font-size", decls.get(0).getName());
+    assertEquals("10 em", decls.get(0).getValue());
+    assertEquals("color", decls.get(1).getName());
+    assertEquals("green", decls.get(1).getValue());
+    assertEquals("font-color", decls.get(2).getName());
+    assertEquals("#343434", decls.get(2).getValue());
+  }
+  
+  public void testParseInlineNoDeclarations() throws Exception {
+    List<ParsedCssDeclaration> decls =
+        csp.parseInline("");
+    assertNotNull(decls);
+    assertEquals(0, decls.size());
+  }
+  
+  public void testParseInlineNoEndingSemicolon() throws Exception {
+    List<ParsedCssDeclaration> decls =
+        csp.parseInline("color: green; font-size: 10");
+    assertNotNull(decls);
+    assertEquals(2, decls.size());
+    assertEquals("color", decls.get(0).getName());
+    assertEquals("green", decls.get(0).getValue());
+    assertEquals("font-size", decls.get(1).getName());
+    assertEquals("10", decls.get(1).getValue());
+  }
+  
+  public void parseInlineNoSeparator() throws Exception {
+    try {
+      String iCss = "color green; font-size: 10;";
+      csp.parseInline(iCss);
+      fail("Should have failed to parse inline CSS: " + iCss);
+    } catch (GadgetException e) {
+      // Expected condition.
+    }
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParserTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParserTest.java?rev=686108&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParserTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/parse/caja/CajaHtmlParserTest.java Thu Aug 14 18:57:48 2008
@@ -0,0 +1,182 @@
+/**
+ * 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.parse.caja;
+
+import org.apache.shindig.gadgets.parse.ParsedHtmlNode;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+public class CajaHtmlParserTest extends TestCase {
+  private final CajaHtmlParser htmlParser = new CajaHtmlParser();
+  
+  public void testParseSimpleString() throws Exception {
+    List<ParsedHtmlNode> nodes =
+        htmlParser.parse("content");
+    assertNotNull(nodes);
+    assertEquals(1, nodes.size());
+    
+    ParsedHtmlNode node = nodes.get(0);
+    assertNotNull(node);
+    assertEquals("content", node.getText());
+    assertNull(node.getAttributes());
+    assertNull(node.getChildren());
+    assertNull(node.getTagName());
+  }
+  
+  public void testParseTagWithStringContents() throws Exception {
+    List<ParsedHtmlNode> nodes =
+        htmlParser.parse("<span>content</span>");
+    assertNotNull(nodes);
+    assertEquals(1, nodes.size());
+    
+    ParsedHtmlNode node = nodes.get(0);
+    assertNull(node.getText());
+    assertNotNull(node.getAttributes());
+    assertEquals(0, node.getAttributes().size());
+    assertNotNull(node.getChildren());
+    assertEquals(1, node.getChildren().size());
+    assertEquals("content", node.getChildren().get(0).getText());
+    assertEquals("span", node.getTagName().toLowerCase());
+  }
+  
+  public void testParseTagWithAttributes() throws Exception {
+    List<ParsedHtmlNode> nodes =
+        htmlParser.parse("<div id=\"foo\">content</div>");
+    assertNotNull(nodes);
+    assertEquals(1, nodes.size());
+    
+    ParsedHtmlNode node = nodes.get(0);
+    assertNotNull(node);
+    assertNull(node.getText());
+    assertNotNull(node.getAttributes());
+    assertEquals(1, node.getAttributes().size());
+    assertEquals("id", node.getAttributes().get(0).getName());
+    assertEquals("foo", node.getAttributes().get(0).getValue());
+    assertNotNull(node.getChildren());
+    assertEquals(1, node.getChildren().size());
+    assertEquals("content", node.getChildren().get(0).getText());
+  }
+  
+  public void testParseStringUnescapesProperly() throws Exception {
+    List<ParsedHtmlNode> nodes =
+        htmlParser.parse("&lt;content&amp;&apos;chrome&apos;&gt;");
+    assertNotNull(nodes);
+    assertEquals(1, nodes.size());
+    
+    ParsedHtmlNode node = nodes.get(0);
+    assertNotNull(node);
+    assertEquals("<content&'chrome'>", node.getText());
+    assertNull(node.getAttributes());
+    assertNull(node.getChildren());
+    assertNull(node.getTagName());
+  }
+  
+  public void testParseNestedContentWithNoCloseForBrAndHr() throws Exception {
+    List<ParsedHtmlNode> nodes =
+        htmlParser.parse("<div><br>  and  <hr></div>");
+    assertNotNull(nodes);
+    assertEquals(1, nodes.size());
+    
+    ParsedHtmlNode divNode = nodes.get(0);
+    assertNull(divNode.getText());
+    assertEquals("div", divNode.getTagName());
+    assertNotNull(divNode.getAttributes());
+    assertEquals(0, divNode.getAttributes().size());
+    assertNotNull(divNode.getChildren());
+    assertEquals(3, divNode.getChildren().size());
+    
+    {
+      // <br>
+      ParsedHtmlNode divChild = divNode.getChildren().get(0);
+      assertNotNull(divChild);
+      assertEquals("br", divChild.getTagName());
+      assertNull(divChild.getText());
+      assertNotNull(divChild.getAttributes());
+      assertEquals(0, divChild.getAttributes().size());
+      assertNotNull(divChild.getChildren());
+      assertEquals(0, divChild.getChildren().size());
+    }
+    
+    {
+      // text
+      ParsedHtmlNode divChild = divNode.getChildren().get(1);
+      assertEquals("  and  ", divChild.getText());
+      assertNull(divChild.getAttributes());
+      assertNull(divChild.getChildren());
+      assertNull(divChild.getTagName());
+    }
+    
+    {
+      // <hr> should be parsed lieniently
+      ParsedHtmlNode divChild = divNode.getChildren().get(2);
+      assertNotNull(divChild);
+      assertEquals("hr", divChild.getTagName());
+      assertNull(divChild.getText());
+      assertNotNull(divChild.getAttributes());
+      assertEquals(0, divChild.getAttributes().size());
+      assertNotNull(divChild.getChildren());
+      assertEquals(0, divChild.getChildren().size());
+    }
+  }
+  
+  public void testParseMixedSiblings() throws Exception {
+    List<ParsedHtmlNode> nodes =
+        htmlParser.parse("content<span>more</span><div id=\"foo\">yet more</div>");
+    assertNotNull(nodes);
+    assertEquals(3, nodes.size());
+    
+    {
+      ParsedHtmlNode textNode = nodes.get(0);
+      assertEquals("content", textNode.getText());
+    }
+    
+    {
+      ParsedHtmlNode spanNode = nodes.get(1);
+      assertNull(spanNode.getText());
+      assertNotNull(spanNode.getAttributes());
+      assertEquals(0, spanNode.getAttributes().size());
+      assertNotNull(spanNode.getChildren());
+      assertEquals(1, spanNode.getChildren().size());
+      assertEquals("more", spanNode.getChildren().get(0).getText());
+    }
+    
+    {
+      ParsedHtmlNode divNode = nodes.get(2);
+      assertNull(divNode.getText());
+      assertNotNull(divNode.getAttributes());
+      assertEquals(1, divNode.getAttributes().size());
+      assertEquals("id", divNode.getAttributes().get(0).getName());
+      assertEquals("foo", divNode.getAttributes().get(0).getValue());
+      assertNotNull(divNode.getChildren());
+      assertEquals(1, divNode.getChildren().size());
+      assertEquals("yet more", divNode.getChildren().get(0).getText());
+    }
+  }
+  
+  public void testParseEmptyContent() throws Exception {
+    String html = "   \n   \t  ";
+    List<ParsedHtmlNode> nodes = htmlParser.parse(html);
+    assertNotNull(nodes);
+    assertEquals(0, nodes.size());
+  }
+  
+  // TODO: figure out to what extent it makes sense to test "invalid"
+  // HTML, semi-structured HTML, and comment parsing
+}