xoxo-sample-code-java

From Microformats Wiki
Jump to navigation Jump to search

XOXO Sample Code - Java

this is sub-page of xoxo-sample-code

XOXOWriter.java

/*
 * Copyright 2005 Robert Sayre
 *
 * Licensed 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.
 *
 * Portions of this code are derived from the Apache-licensed Python XOXO
 * module by Kevin Marks. <http://microformats.org/wiki/xoxo-sample-code>
 */

package org.atompub.draft.xoxo;

import java.util.*;

public class XOXOWriter {

  public String[] attrs = {"title","rel","type"};

  public String makeXOXO(List struct, String className){
    return makeXOXO(struct, className, 0, true);
  }

  public String makeXOXO(List struct, String className,
                         boolean doNSDeclaration){
    return makeXOXO(struct, className, 0, doNSDeclaration);
  }

  public String makeXOXO(List struct){
    return makeXOXO(struct, "xoxo", 0, true);
  }

  public String makeXOXO(Object struct, int depth){
    return makeXOXO(struct, null, 0, false);
  }

  public String makeXOXO(Object struct, String className,
                         int depth, boolean doNSDeclaration){
    if(struct == null) return "";
    StringBuffer sb = new StringBuffer();
    if(struct instanceof Object[]){
      struct = Arrays.asList((Object[]) struct);
    }
    if(struct instanceof List){
      sb.append("<ol");
      if(doNSDeclaration)
        sb.append(" xmlns=\"http://www.w3.org/1999/xhtml\"");
      if(className != null){
        sb.append(" class=\"");
        sb.append(className);
        sb.append("\"");
      }
      sb.append(">");
    }
    if(struct instanceof Map){
      Map d = new LinkedHashMap((Map) struct);
      if(d.containsKey("url")){
        sb.append("<a href=\"" + d.get("url") + "\" ");
        Object text;
        if(d.containsKey("text")){
          text = d.get("text");
        }else if(d.containsKey("title")){
          text = d.get("title");
        }else{
          text = d.get("url");
        }
        for(int i=0; i<attrs.length; i++){
          String xVal = makeXOXO(d.get(attrs[i]),depth+1);
          if(xVal != null && !xVal.equals("")){
            sb.append(attrs[i] + "=\"" + xVal + "\" ");
          }
          d.remove(attrs[i]);
        }
        sb.append(">" + makeXOXO(text, depth+1) + "</a>");
        d.remove("text");
        d.remove("url");
      }
      if(!d.isEmpty()){
        sb.append("<dl>");
        for(Iterator i = d.entrySet().iterator(); i.hasNext();){
          Map.Entry entry = (Map.Entry)i.next();
          String ddVal = makeXOXO(entry.getValue(),depth+1);
          sb.append("<dt>" + entry.getKey() + "</dt>");
          sb.append("<dd>" + ddVal + "</dd>");
        }
        sb.append("</dl>");
      }
    }else if(struct instanceof List){
      List l = (List) struct;
      for(Iterator i = l.iterator(); i.hasNext();){
        Object item = i.next();
        sb.append("<li>" + makeXOXO(item,depth+1) + "</li>");
      }
      sb.append("</ol>");
    }else{
      sb.append(struct);
    }
    return sb.toString();
  }

  public String toXOXO(List struct){
    return toXOXO(struct, false, null);
  }

  public String toXOXO(Object struct){
    List alist = new ArrayList();
    alist.add(struct);
    return toXOXO(alist);
  }

  public String toXOXO(Object struct,
                       boolean addHTMLWrapper,
                       String cssUrl){
    List alist = new ArrayList();
    alist.add(struct);
    return toXOXO(alist, addHTMLWrapper, cssUrl);
  }

  public String toXOXO(List struct,
                       boolean addHTMLWrapper,
                       String cssUrl){
    String startHTML = "<!DOCTYPE html PUBLIC \"-//W3C//DTD"
        + "XHTML 1.0 Transitional//EN\n"
        + "http://www.w3.org/TR/xhtml1/DTD/"
        + "xhtml1-transitional.dtd\">"
        + "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
        + "<head>";
    if(addHTMLWrapper){
      String s = startHTML;
      if(cssUrl != null){
        s += "<style type=\"text/css\">@import \""
            + cssUrl + "\";</style>";
      }
      s += "</head><body>" + makeXOXO(struct, "xoxo", false)
          + "</body></html>";
      return s;
    }else{
      return makeXOXO(struct, "xoxo");
    }
  }
}

XOXOParser.java

This needs some small additions to handle the XHTML DTD and named character entities.

/*
 * Copyright 2005 Robert Sayre
 *
 * Licensed 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.
 *
 * Portions of this code are derived from the Apache-licensed Python XOXO
 * module by Kevin Marks. <http://microformats.org/wiki/xoxo-sample-code>
 */

package org.atompub.draft.xoxo;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;

import java.util.*;
import java.io.InputStream;
import java.io.StringReader;
import java.io.IOException;

public class XOXOParser extends DefaultHandler {

  protected String XHTML_NS = "http://www.w3.org/1999/xhtml";
  protected List elStack;
  protected Map listEls;
  public List structs;
  public List xoStack;
  public List textStack;

  public XOXOParser() {
    reset();
  }

  protected void pushStruct(Object struct){
    if(struct instanceof Map && !((Map) struct).isEmpty()
        && structs.get(structs.size()-1) instanceof Map
        && ((Map) struct).containsKey("url")){
      // put back the <a>-made one for extra defs
      xoStack.add(structs.get(structs.size()-1));
    }else{
      structs.add(struct);
      xoStack.add(struct);
    }
  }

  public void startElement(String nsUri, String localName,
                           String qName, Attributes atts){
    // bounce non-XHTML elements
    if(nsUri.equals(XHTML_NS)){
      elStack.add(localName);
    }else{
      elStack.add("foo");
      return;
    }

    if(localName.equals("a")){
      Map attmap = new LinkedHashMap();
      int len = atts.getLength();
      for(int i=0; i<len; i++){
        attmap.put(atts.getQName(i),atts.getValue(i));
      }
      if(attmap.containsKey("href")){
        attmap.put("url",attmap.get("href"));
        attmap.remove("href");
      }
      pushStruct(attmap);
      textStack.add("");
    }else if(localName.equals("dl")){
      pushStruct(new LinkedHashMap());
    }else if(localName.equals("ol")){
      pushStruct(new ArrayList());
    }else if(localName.equals("ul")){
      pushStruct(new ArrayList());
    }else if(localName.equals("li")){
      textStack.add("");
    }else if(localName.equals("dt")){
      textStack.add("");
    }else if(localName.equals("dd")){
      textStack.add("");
    }
  }

  public void endElement(String nsUri, String localName,
                         String qName){
    elStack.remove(elStack.size()-1);
    // bounce non-XHTML elements
    if(nsUri != XHTML_NS){
      return;
    }

    if(localName.equals("a")){
      String val = (String) textStack.remove(textStack.size()-1);
      if (val.length() > 0){
        Map defs = (Map) xoStack.get(xoStack.size()-1);
        String defVal = (String) defs.get("title");
        if(defVal != null && val.equals(defVal)){
          val = "";
        }
        defVal = (String) defs.get("url");
        if(defVal != null && val.equals(defVal)){
          val = "";
        }
        if(val.length() > 0){
          defs.put("text",val);
        }
      }
      xoStack.remove(xoStack.size()-1);
    }else if(localName.equals("dl")){
      xoStack.remove(xoStack.size()-1);
    }else if(localName.equals("ol")){
      xoStack.remove(xoStack.size()-1);
    }else if(localName.equals("ul")){
      xoStack.remove(xoStack.size()-1);
    }else if(localName.equals("li")){
      Object val = textStack.remove(textStack.size()-1);
      List last = (List) xoStack.get(xoStack.size()-1);
      if(structs.get(structs.size()-1) != last){
        val = structs.remove(structs.size()-1);
      }
      last.add(val);
    }else if(localName.equals("dd")){
      Object val = textStack.remove(textStack.size()-1);
      Object key = textStack.remove(textStack.size()-1);
      Map last = (Map) xoStack.get(xoStack.size()-1);
      if(structs.get(structs.size()-1) != last){
        val = structs.remove(structs.size()-1);
      }
      last.put(key,val);
    }
  }

  public void characters(char[] ch, int start, int length){
    if(!xoStack.isEmpty()
        && !listEls.containsKey(elStack.get(elStack.size()-1))){
      String text = (String) textStack.get(textStack.size()-1);
      String test = new String(ch,start,length);
      textStack.set(textStack.size()-1,text+test);
    }
  }

  public Object parse(String s) throws SAXException, IOException{
    return parse(new InputSource(new StringReader(s)));
  }

  public Object parse(InputStream is) throws SAXException, IOException {
    return parse(new InputSource(is));
  }

  public Object parse(InputSource in) throws SAXException, IOException {
    XMLReader parser = XMLReaderFactory.createXMLReader();
    parser.setContentHandler(this);
    parser.parse(in);
    List returnList = new ArrayList();
    for(Iterator i = this.structs.iterator(); i.hasNext();){
      Object thing = i.next();
      if(thing != null){
        returnList.add(thing);
      }
    }
    while(returnList.size()==1){
      if(returnList.get(0) instanceof List){
        returnList = (List) returnList.get(0);
      }else{
        reset();
        return returnList.get(0);
      }
    }
    reset();
    return returnList;
  }

  protected void reset(){
    elStack = new ArrayList();
    listEls = new HashMap();
    structs = new ArrayList();
    xoStack = new ArrayList();
    textStack = new ArrayList();
    listEls.put("ol","ol");
    listEls.put("ul","ul");
    listEls.put("dl","dl");
  }
}

XOXOTest.java

/*
 * Copyright 2005 Robert Sayre
 *
 * Licensed 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.
 *
 * Portions of this code are derived from the Apache-licensed Python XOXO
 * module by Kevin Marks. <http://microformats.org/wiki/xoxo-sample-code>
 */

package org.atompub.draft.xoxo.tests;

import junit.framework.TestSuite;
import junit.framework.TestCase;
import junit.textui.TestRunner;
import org.atompub.draft.xoxo.XOXOWriter;
import org.atompub.draft.xoxo.XOXOParser;

import java.util.*;

public class XOXOTest extends TestCase {

  public static void main(String[] args) {
    new TestRunner().doRun(new TestSuite(XOXOTest.class));
  }
  String XHTML_DEC = "xmlns=\"http://www.w3.org/1999/xhtml\" ";
  public String simpleListHTML = "<ol "
  + XHTML_DEC
  + "class=\"xoxo\">"
  + "<li>1</li><li>2</li><li>3</li></ol>";

  public void testSimpleList(){
    String [] numbers = {"1","2","3"};
    XOXOWriter xoxo = new XOXOWriter();
    assertEquals(simpleListHTML,
                 xoxo.toXOXO(Arrays.asList(numbers)));
  }

  public void testStringIntegerList(){
    Object[] numbers = {new Integer(1),"2","3"};
    XOXOWriter xoxo = new XOXOWriter();
    assertEquals(simpleListHTML,
                 xoxo.toXOXO(Arrays.asList(numbers)));
  }

  public String nestedListHTML = "<ol "
  + XHTML_DEC
  + "class=\"xoxo\"><li>1</li><li>"
  + "<ol><li>2</li><li>3</li></ol></li></ol>";

  public void testNestedList(){
    Object[] arr = {"2","3"};
    Object[] nested = {"1",Arrays.asList(arr)};
    XOXOWriter xoxo = new XOXOWriter();
    assertEquals(nestedListHTML,
                 xoxo.toXOXO(Arrays.asList(nested)));
  }

  public void testNestedArray(){
    Object[] arr = {"2","3"};
    Object[] nested = {"1",arr};
    XOXOWriter xoxo = new XOXOWriter();
    assertEquals(nestedListHTML,
                 xoxo.toXOXO(Arrays.asList(nested)));
  }

  public String dictHTML = "<ol "
  + XHTML_DEC
  + "class=\"xoxo\">"
  + "<li><dl><dt>test</dt><dd>1</dd><dt>name</dt>"
  + "<dd>Kevin</dd></dl></li></ol>";

  public void testDictionary(){
    Map dict = new LinkedHashMap();
    dict.put("test", new Integer(1));
    dict.put("name", "Kevin");
    XOXOWriter xoxo = new XOXOWriter();
    assertEquals(dictHTML,
                 xoxo.toXOXO(dict));
  }

  public String singleHTML = "<ol "
  + XHTML_DEC
  + "class=\"xoxo\">"
  + "<li>test</li></ol>";

  public void testSingleItem(){
    String item = "test";
    XOXOWriter xoxo = new XOXOWriter();
    assertEquals(singleHTML,
                 xoxo.toXOXO(item));
  }

  public void testWrapDiffers(){
    String item = "test";
    XOXOWriter xoxo = new XOXOWriter();
    String nowrap = xoxo.toXOXO(item);
    Object[] itemArr = {item};
    String wrap = xoxo.toXOXO(Arrays.asList(itemArr),true,null);
    assertFalse(wrap.equals(nowrap));
  }

  String startHTML = "<!DOCTYPE html PUBLIC \"-//W3C//DTD"
        + "XHTML 1.0 Transitional//EN\n"
        + "http://www.w3.org/TR/xhtml1/DTD/"
        + "xhtml1-transitional.dtd\">"
        + "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
        + "<head></head><body>";
  public String singleWrapHTML = "<ol "
  + "class=\"xoxo\">"
  + "<li>test</li></ol>";
  public String endHTML = "</body></html>";

  public void testWrapSingleItem(){
    String item = "test";
    XOXOWriter xoxo = new XOXOWriter();
    assertEquals(startHTML + singleWrapHTML + endHTML,
                 xoxo.toXOXO(item,true,null));
  }

  public void testXOXOParser(){
    XOXOParser parser = new XOXOParser();
    try{
      parser.parse(dictHTML);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public void testDictRoundTrip(){
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    Map dict = new LinkedHashMap();
    dict.put("test", "1");
    dict.put("name", "Kevin");
    String html = xoxo.toXOXO(dict);
     try{
      Object newDict = parser.parse(html);
      assertEquals(dict,newDict);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public void testListRoundTrip(){
    Object[] obj = {"1","2","3"};
    List testList = Arrays.asList(obj);
    XOXOWriter xoxo = new XOXOWriter();
    String html = xoxo.toXOXO(testList);
    XOXOParser parser = new XOXOParser();
    try{
      Object newList = parser.parse(html);
      assertEquals(testList,newList);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public void testListOfDictsRoundTrip(){
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    Map dict = new LinkedHashMap();
    dict.put("test", "1");
    dict.put("name", "Kevin");
    Map dict2 = new LinkedHashMap();
    dict2.put("one", "two");
    dict2.put("three", "four");
    dict2.put("five", "six");
    Object[] obj = {"1",dict,dict2};
    List testList = Arrays.asList(obj);
    String html = xoxo.toXOXO(testList);
    try{
      Object newList = parser.parse(html);
      assertEquals(testList,newList);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public void testListOfListsRoundTrip(){
    Object[] list1 = {"1","2","3"};
    Object[] list2 = {"4","5","6", Arrays.asList(list1)};
    Object[] list3 = {"7", Arrays.asList(list2)};
    Object[] list4 = {"8", Arrays.asList(list3)};
    List testList = Arrays.asList(list4);
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    String html = xoxo.toXOXO(testList);
    try{
      Object newList = parser.parse(html);
      assertEquals(testList,newList);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public void testDictOfListsRoundTrip(){
    Object[] list1 = {"1","2","3"};
    Object[] list2 = {"4","5","6"};
    Object[] list3 = {"7"};
    Object[] list4 = {"8", "9"};
    Map dict = new LinkedHashMap();
    dict.put("foo", Arrays.asList(list1));
    dict.put("bar", Arrays.asList(list2));
    dict.put("baz", Arrays.asList(list3));
    dict.put("qux", Arrays.asList(list4));
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    String html = xoxo.toXOXO(dict);
    try{
      Object newDict = parser.parse(html);
      assertEquals(dict,newDict);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public String junkXOXO = "<ol "
  + XHTML_DEC
  + "class=\"xoxo\">"
  + "bad<li><dl>worse<dt>good</dt><dd>buy</dd> now</dl></li></ol>";

  public void testXOXOJunkInContainers(){
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    Map dict = new LinkedHashMap();
    dict.put("good","buy");
    try{
      Object newDict = parser.parse(junkXOXO);
      assertEquals(dict,newDict);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public String junkElementXOXO = "<ol "
  + XHTML_DEC
  + "><li>bad<dl><dt>good</dt><dd>buy</dd></dl>"
  + "worse</li><li>bag<ol><li>OK</li></ol>fish</li></ol>";

  public void testXOXOjunkInElements(){
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    Map dict = new LinkedHashMap();
    dict.put("good","buy");
    Object[] ok = {"OK"};
    Object[] obj ={dict, Arrays.asList(ok)};
    List testList = Arrays.asList(obj);
    try{
      Object newList = parser.parse(junkElementXOXO);
      assertEquals(testList,newList);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public String xoxoSpacesNewlines = "<ol " +  XHTML_DEC +
      " class='xoxo'> \n" +
      "  <li>\n" +
      "    <dl>\n" +
      "        <dt>text</dt>\n" +
      "        <dd>item 1</dd>\n" +
      "        <dt>description</dt>\n" +
      "        <dd> This item represents the main" +
      " point we're trying to make.</dd>\n" +
      "        <dt>url</dt>\n" +
      "        <dd>http://example.com/more.xoxo</dd>\n" +
      "        <dt>title</dt>\n" +
      "        <dd>title of item 1</dd>\n" +
      "        <dt>type</dt>\n" +
      "        <dd>text/xml</dd>\n" +
      "        <dt>rel</dt>\n" +
      "        <dd>help</dd>\n" +
      "    </dl>\n" +
      "  </li>\n" +
      "</ol>";

  public void testXOXOWithSpacesAndNewlines(){
    XOXOParser parser = new XOXOParser();
    Map dict = new LinkedHashMap();
    dict.put("text","item 1");
    dict.put("description"," This item represents the main" +
        " point we're trying to make.");
    dict.put("url","http://example.com/more.xoxo");
    dict.put("title","title of item 1");
    dict.put("type","text/xml");
    dict.put("rel","help");
    try{
      Object newDict = parser.parse(xoxoSpacesNewlines);
      assertEquals(dict,newDict);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public String xoxoSample = "<ol " +  XHTML_DEC +
      " class='xoxo'> \n" +
      "  <li>\n" +
      "    <dl>\n" +
      "        <dt>text</dt>\n" +
      "        <dd>item 1</dd>\n" +
      "        <dt>url</dt>\n" +
      "        <dd>http://example.com/more.xoxo</dd>\n" +
      "        <dt>title</dt>\n" +
      "        <dd>title of item 1</dd>\n" +
      "        <dt>type</dt>\n" +
      "        <dd>text/xml</dd>\n" +
      "        <dt>rel</dt>\n" +
      "        <dd>help</dd>\n" +
      "    </dl>\n" +
      "  </li>\n" +
      "</ol>";

  public String smartXOXOSample = "<ol " + XHTML_DEC +
      "class=\"xoxo\"> \n" +
      "  <li><a href=\"http://example.com/more.xoxo\"\n" +
      "         title=\"title of item 1\"\n" +
      "         type=\"text/xml\"\n" +
      "         rel=\"help\">item 1</a> \n" +
      "<!-- note how the \"text\" property is simply" +
      " the contents of the <a> element -->\n" +
      "  </li>\n" +
      "</ol>";

  public void testSpecialAttributeDecoding(){
    XOXOParser parser = new XOXOParser();
    try{
      Object xoxoDict = parser.parse(xoxoSample);
      Object xoxoDict2 = parser.parse(smartXOXOSample);
      assertEquals(xoxoDict,xoxoDict2);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public String specialAttrHTML =  "<ol " + XHTML_DEC +
      "class=\"xoxo\">" +
      "<li><a href=\"http://example.com/more.xoxo\" title=\"sample url\" " +
      "rel=\"help\" type=\"text/xml\" >an example</a></li></ol>";

  public void testSpecialAttributeEncode(){
    XOXOWriter xoxo = new XOXOWriter();
    Map dict = new LinkedHashMap();
    dict.put("url","http://example.com/more.xoxo");
    dict.put("title","sample url");
    dict.put("type","text/xml");
    dict.put("rel","help");
    dict.put("text","an example");
    String html = xoxo.toXOXO(dict);
    assertEquals(specialAttrHTML,html);
  }

  public void testSpecialAttributeRoundTripFull(){
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    Map dict = new LinkedHashMap();
    dict.put("url","http://example.com/more.xoxo");
    dict.put("title","sample url");
    dict.put("type","text/xml");
    dict.put("rel","help");
    dict.put("text","an example");
    String html = xoxo.toXOXO(dict);
    try{
      Object newDict = parser.parse(html);
      assertEquals(dict,newDict);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public void testSpecialAttributeRoundTripNoText(){
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    Map dict = new LinkedHashMap();
    dict.put("url","http://example.com/more.xoxo");
    dict.put("title","sample url");
    dict.put("type","text/xml");
    dict.put("rel","help");
    String html = xoxo.toXOXO(dict);
    try{
      Object newDict = parser.parse(html);
      assertEquals(dict,newDict);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public void testSpecialAttributeRoundTripNoTextOrTitle(){
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    Map dict = new LinkedHashMap();
    dict.put("url","http://example.com/more.xoxo");
    dict.put("type","text/xml");
    dict.put("rel","help");
    String html = xoxo.toXOXO(dict);
    try{
      Object newDict = parser.parse(html);
      assertEquals(dict,newDict);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

  public void testUnicodeRoundTrip(){
    String s = "Tantek Çelik and a snowman ?";
    XOXOWriter xoxo = new XOXOWriter();
    XOXOParser parser = new XOXOParser();
    String html = xoxo.toXOXO(s);
    try{
      Object newString = parser.parse(html);
      assertEquals(s,newString);
    }catch (Exception e){
      fail(e.getMessage());
      e.printStackTrace();
    }
  }

}