Fixes Issue #733
Replaces the toString implementation in the XML library to use native
XMLSerializers instead of the DIY job we currently have. I also had
to change XMLTest quite a bit because it leaned very heavily on the
broken implementation of toString in assertions.

Review by: jgw



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2650 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/xml/client/XMLParser.java b/user/src/com/google/gwt/xml/client/XMLParser.java
index 172c8c3..eceb018 100644
--- a/user/src/com/google/gwt/xml/client/XMLParser.java
+++ b/user/src/com/google/gwt/xml/client/XMLParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -101,5 +101,4 @@
    */
   private XMLParser() {
   }
-
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/AttrImpl.java b/user/src/com/google/gwt/xml/client/impl/AttrImpl.java
index 847463c..75254f1 100644
--- a/user/src/com/google/gwt/xml/client/impl/AttrImpl.java
+++ b/user/src/com/google/gwt/xml/client/impl/AttrImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -49,19 +49,4 @@
   public String getValue() {
     return XMLParserImpl.getValue(this.getJsObject());
   }
-
-  /**
-   * This method returns the string representation of this <code>Attr</code>.
-   * @return the string representation of this <code>Attr</code>.
-   * @see java.lang.Object#toString()
-   */
-  @Override
-  public String toString() {
-    final StringBuffer b = new StringBuffer();
-    b.append(" " + getName());
-    b.append("=\"");
-    b.append(getValue());
-    b.append("\"");
-    return b.toString();
-  }
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/CharacterDataImpl.java b/user/src/com/google/gwt/xml/client/impl/CharacterDataImpl.java
index 3da1ff3..4d0a564 100644
--- a/user/src/com/google/gwt/xml/client/impl/CharacterDataImpl.java
+++ b/user/src/com/google/gwt/xml/client/impl/CharacterDataImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -117,7 +117,4 @@
       throw new DOMNodeException(DOMException.INVALID_ACCESS_ERR, e, this);
     }
   }
-
-  @Override
-  public abstract String toString();
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/DocumentFragmentImpl.java b/user/src/com/google/gwt/xml/client/impl/DocumentFragmentImpl.java
index 2969924..b827efd 100644
--- a/user/src/com/google/gwt/xml/client/impl/DocumentFragmentImpl.java
+++ b/user/src/com/google/gwt/xml/client/impl/DocumentFragmentImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -26,20 +26,4 @@
   protected DocumentFragmentImpl(JavaScriptObject o) {
     super(o);
   }
-
-  /**
-   * This method returns the string representation of this 
-   * <code>DocumentFragmentImpl</code>.
-   * @return the string representation of this 
-   * <code>DocumentFragmentImpl</code>.
-   * @see java.lang.Object#toString()
-   */
-  @Override
-  public String toString() {
-    StringBuffer b = new StringBuffer();
-    for (int i = 0; i < getChildNodes().getLength(); i++) {
-      b.append(getChildNodes().item(i));
-    }
-    return b.toString();
-  }
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/DocumentImpl.java b/user/src/com/google/gwt/xml/client/impl/DocumentImpl.java
index 9db890b..ecfa9bb 100644
--- a/user/src/com/google/gwt/xml/client/impl/DocumentImpl.java
+++ b/user/src/com/google/gwt/xml/client/impl/DocumentImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -153,21 +153,4 @@
       throw new DOMNodeException(DOMException.INVALID_STATE_ERR, e, this);
     }
   }
-
-  /**
-   * This method returns the string representation of this
-   * <code>DocumentImpl</code>.
-   * 
-   * @return the string representation of this <code>DocumentImpl</code>.
-   * @see java.lang.Object#toString()
-   */
-  @Override
-  public String toString() {
-    StringBuffer b = new StringBuffer();
-    NodeList children = getChildNodes();
-    for (int i = 0; i < children.getLength(); i++) {
-      b.append(children.item(i).toString());
-    }
-    return b.toString();
-  }
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/ElementImpl.java b/user/src/com/google/gwt/xml/client/impl/ElementImpl.java
index 799e266..895c874 100644
--- a/user/src/com/google/gwt/xml/client/impl/ElementImpl.java
+++ b/user/src/com/google/gwt/xml/client/impl/ElementImpl.java
@@ -96,30 +96,4 @@
       throw new DOMNodeException(DOMException.INVALID_MODIFICATION_ERR, e, this);
     }
   }
-
-  /**
-   * This method returns the string representation of this
-   * <code>ElementImpl</code>.
-   * 
-   * @return the string representation of this <code>ElementImpl</code>.
-   * @see java.lang.Object#toString()
-   */
-  @Override
-  public String toString() {
-    final StringBuffer b = new StringBuffer("<");
-    b.append(getTagName());
-    if (hasAttributes()) {
-      b.append(getAttributes().toString());
-    }
-    if (hasChildNodes()) {
-      b.append(">");
-      b.append(getChildNodes().toString());
-      b.append("</");
-      b.append(getTagName());
-      b.append(">");
-    } else {
-      b.append("/>");
-    }
-    return b.toString();
-  }
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/EntityReferenceImpl.java b/user/src/com/google/gwt/xml/client/impl/EntityReferenceImpl.java
index 0e32e0d..94fe4dc 100644
--- a/user/src/com/google/gwt/xml/client/impl/EntityReferenceImpl.java
+++ b/user/src/com/google/gwt/xml/client/impl/EntityReferenceImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -26,16 +26,4 @@
   protected EntityReferenceImpl(JavaScriptObject o) {
     super(o);
   }
-
-  /**
-   * This method returns the string representation of this 
-   * <code>EntityReferenceImpl</code>.
-   * @return the string representation of this 
-   * <code>EntityReferenceImpl</code>.
-   * @see java.lang.Object#toString()
-   */
-  @Override
-  public String toString() {
-    return "&" + getNodeValue() + ";";
-  }
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/NodeImpl.java b/user/src/com/google/gwt/xml/client/impl/NodeImpl.java
index d966883..2e69e20 100644
--- a/user/src/com/google/gwt/xml/client/impl/NodeImpl.java
+++ b/user/src/com/google/gwt/xml/client/impl/NodeImpl.java
@@ -240,4 +240,9 @@
       throw new DOMNodeException(DOMException.INVALID_MODIFICATION_ERR, e, this);
     }
   }
+ 
+  @Override
+  public String toString() {
+    return XMLParserImpl.getInstance().toStringImpl(this);
+  }
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/ProcessingInstructionImpl.java b/user/src/com/google/gwt/xml/client/impl/ProcessingInstructionImpl.java
index ea50ca1..3984540 100644
--- a/user/src/com/google/gwt/xml/client/impl/ProcessingInstructionImpl.java
+++ b/user/src/com/google/gwt/xml/client/impl/ProcessingInstructionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -57,14 +57,4 @@
       throw new DOMNodeException(DOMException.INVALID_CHARACTER_ERR, e, this);
     }
   }
-
-  @Override
-  public String toString() {
-    StringBuffer b = new StringBuffer("<?");
-    b.append(getNodeName());
-    b.append(" ");
-    b.append(getData());
-    b.append("?>");
-    return b.toString();
-  }
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/XMLParserImpl.java b/user/src/com/google/gwt/xml/client/impl/XMLParserImpl.java
index e9cdc0f..594e914 100644
--- a/user/src/com/google/gwt/xml/client/impl/XMLParserImpl.java
+++ b/user/src/com/google/gwt/xml/client/impl/XMLParserImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -299,5 +299,6 @@
       JavaScriptObject importedNode, boolean deep);
 
   protected abstract JavaScriptObject parseImpl(String contents);
-
+  
+  abstract String toStringImpl(NodeImpl node);
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/XMLParserImplIE6.java b/user/src/com/google/gwt/xml/client/impl/XMLParserImplIE6.java
index 6389e68..53ccc7e 100644
--- a/user/src/com/google/gwt/xml/client/impl/XMLParserImplIE6.java
+++ b/user/src/com/google/gwt/xml/client/impl/XMLParserImplIE6.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -41,6 +41,9 @@
   @Override
   protected native JavaScriptObject createDocumentImpl() /*-{
     var doc = @com.google.gwt.xml.client.impl.XMLParserImplIE6::selectDOMDocumentVersion()();
+    // preserveWhiteSpace is set to true here to prevent IE from throwing away
+    // text nodes that consist of only whitespace characters. This makes it
+    // act more like other browsers.
     doc.preserveWhiteSpace = true;
     doc.setProperty("SelectionNamespaces", "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
     doc.setProperty("SelectionLanguage", "XPath");
@@ -84,4 +87,8 @@
     }
   }-*/;
 
+  protected native String toStringImpl(NodeImpl node) /*-{
+    var jsNode = node.@com.google.gwt.xml.client.impl.DOMItem::getJsObject()();
+    return jsNode.xml;
+  }-*/;
 }
diff --git a/user/src/com/google/gwt/xml/client/impl/XMLParserImplSafari.java b/user/src/com/google/gwt/xml/client/impl/XMLParserImplSafari.java
index 784b481..3c1f39a 100644
--- a/user/src/com/google/gwt/xml/client/impl/XMLParserImplSafari.java
+++ b/user/src/com/google/gwt/xml/client/impl/XMLParserImplSafari.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -20,14 +20,36 @@
 /**
  * This class is Safari implementation of the XMLParser interface.
  */
-class XMLParserImplSafari extends XMLParserImplStandard {
+public class XMLParserImplSafari extends XMLParserImplStandard {
 
+  private static boolean safari2LevelWebKit = (getWebKitVersion() <= 420);
+
+  public static boolean isSafari2LevelWebKit() {
+    return safari2LevelWebKit;
+  }
+  
+  private static native int getWebKitVersion() /*-{
+    var result = / AppleWebKit\/([\d]+)/.exec(navigator.userAgent);
+    return ((result) ? parseInt(result[1]) : 0) || 0;
+  }-*/;
+  
   @Override
   protected native JavaScriptObject getElementsByTagNameImpl(JavaScriptObject o,
       String tagName) /*-{
     return o.getElementsByTagName(tagName);
   }-*/;
   
+  @Override
+  protected native JavaScriptObject importNodeImpl(JavaScriptObject jsObject,
+      JavaScriptObject importedNode, boolean deep) /*-{
+    // Works around a Safari2 issue where importing a node will steal attributes
+    // from the original. TODO(knorton): Add a runtime check here.
+    if (@com.google.gwt.xml.client.impl.XMLParserImplSafari::isSafari2LevelWebKit()()) {
+      importedNode = importedNode.cloneNode(deep);
+    }
+    return jsObject.importNode(importedNode, deep);
+  }-*/;
+  
   /**
    * <html><body><parsererror style="white-space: pre; border: 2px solid #c77;
    * padding: 0 1em 0 1em; margin: 1em; background-color: #fdd; color: black" >
diff --git a/user/src/com/google/gwt/xml/client/impl/XMLParserImplStandard.java b/user/src/com/google/gwt/xml/client/impl/XMLParserImplStandard.java
index e5ab10a..855b1c02 100644
--- a/user/src/com/google/gwt/xml/client/impl/XMLParserImplStandard.java
+++ b/user/src/com/google/gwt/xml/client/impl/XMLParserImplStandard.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -73,5 +73,9 @@
     }
     return result;
   }-*/;
-
+  
+  protected native String toStringImpl(NodeImpl node) /*-{
+    var jsNode = node.@com.google.gwt.xml.client.impl.DOMItem::getJsObject()();
+    return new XMLSerializer().serializeToString(jsNode);
+  }-*/;
 }
diff --git a/user/test/com/google/gwt/xml/client/XMLTest.java b/user/test/com/google/gwt/xml/client/XMLTest.java
index d9e983f..a34f366 100644
--- a/user/test/com/google/gwt/xml/client/XMLTest.java
+++ b/user/test/com/google/gwt/xml/client/XMLTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * 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
@@ -17,15 +17,114 @@
 
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.xml.client.impl.DOMParseException;
+import com.google.gwt.xml.client.impl.XMLParserImplSafari;
 
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
 /**
- * This class tests all the methods in the GWT XML parser.
+ * This class poorly tests all the methods in the GWT XML parser.
  */
 public class XMLTest extends GWTTestCase {
+  private static List<Node> asList(Node[] nodes) {
+    return Arrays.asList(nodes);
+  }
+
+  private static void assertAttributeMapEquals(NamedNodeMap listA,
+      NamedNodeMap listB) {
+    // This is only for checking attribute maps.
+    assertEquals(listA.getLength(), listB.getLength());
+    for (int i = 0, n = listA.getLength(); i < n; ++i) {
+      final Node itemA = listA.item(i);
+      final Node itemB = listB.getNamedItem(itemA.getNodeName());
+      assertNotNull(itemB);
+      assertEquals(itemA.getNodeType(), itemB.getNodeType());
+      assertEquals(itemA.getNodeValue(), itemB.getNodeValue());
+    }
+  }
+
+  private static void assertCharacterDataEquals(CharacterData charDataA,
+      CharacterData charDataB) {
+    assertEquals(charDataA.getData(), charDataB.getData());
+  }
+
+  private static void assertDocumentEquals(Document docA, Document docB) {
+    assertNodeListEquals(docA.getChildNodes(), docB.getChildNodes());
+  }
+
+  private static void assertElementEquals(Element elemA, Element elemB) {
+    assertEquals(elemA.getNodeName(), elemB.getNodeName());
+    assertAttributeMapEquals(elemA.getAttributes(), elemB.getAttributes());
+    assertNodeListEquals(elemA.getChildNodes(), elemB.getChildNodes());
+  }
+
+  private static void assertNodeEquals(Node nodeA, Node nodeB) {
+    assertNotNull(nodeA);
+    assertNotNull(nodeB);
+    final int typeA = nodeA.getNodeType();
+    final int typeB = nodeB.getNodeType();
+    assertEquals(typeA, typeB);
+    switch (typeA) {
+      case Node.ELEMENT_NODE:
+        assertElementEquals((Element) nodeA, (Element) nodeB);
+        break;
+      case Node.COMMENT_NODE:
+      case Node.CDATA_SECTION_NODE:
+      case Node.TEXT_NODE:
+        assertCharacterDataEquals((CharacterData) nodeA, (CharacterData) nodeB);
+        break;
+      case Node.PROCESSING_INSTRUCTION_NODE:
+        assertProcessingInstructionEquals((ProcessingInstruction) nodeA,
+            (ProcessingInstruction) nodeB);
+        break;
+      default:
+        fail("Unexpected node type: " + nodeA.toString());
+        break;
+    }
+  }
+
+  private static void assertNodeListEquals(NodeList listA, NodeList listB) {
+    final int sizeA = listA.getLength();
+    final int sizeB = listB.getLength();
+    assertEquals(sizeA, sizeB);
+    for (int i = 0, n = sizeA; i < n; ++i) {
+      assertNodeEquals(listA.item(i), listB.item(i));
+    }
+  }
+
+  private static void assertProcessingInstructionEquals(
+      ProcessingInstruction piA, ProcessingInstruction piB) {
+    assertEquals(piA.getData(), piB.getData());
+  }
+
+  private static Document createTestDocument() {
+    Document d = XMLParser.createDocument();
+    Element top = d.createElement("doc");
+    top.setAttribute("fluffy", "true");
+    top.setAttribute("numAttributes", "2");
+    d.appendChild(top);
+    ProcessingInstruction commentBefore = d.createProcessingInstruction(
+        "target", "some data");
+    d.insertBefore(commentBefore, top);
+    Comment commentAfter = d.createComment("after the element");
+    d.insertBefore(commentAfter, null);
+    for (int i = 0; i < 3; i++) {
+      Element e = d.createElement("e" + i);
+      e.setAttribute("id", "e" + i + "Id");
+      top.appendChild(e);
+    }
+    Element deep = d.createElement("deep");
+    top.getFirstChild().appendChild(deep);
+    deep.setAttribute("depth", "1 foot");
+    Element deep2 = d.createElement("deep");
+    deep2.setAttribute("depth", "2 feet");
+    top.getFirstChild().getFirstChild().appendChild(deep2);
+
+    top.appendChild(d.createTextNode("0123456789"));
+    top.appendChild(d.createCDATASection("abcdefghij"));
+    return d;
+  }
 
   /**
    * Returns the module name for GWT unit test running.
@@ -59,13 +158,9 @@
     ProcessingInstruction createProcessingInstruction = d.createProcessingInstruction(
         "target", "processing instruction data");
     Text createTextNode = d.createTextNode("sample text node");
-    // TODO: what is "all" for?
-    List all = asList(new Node[] {
-        createCDATA, createComment, createDocumentFragment,
-        elementWithChildren, createProcessingInstruction, createTextNode});
-    List canHaveChildren = asList(new Node[] {
+    List<Node> canHaveChildren = asList(new Node[] {
         createDocumentFragment, elementWithChildren});
-    List canBeChildren = asList(new Node[] {
+    List<Node> canBeChildren = asList(new Node[] {
         createCDATA, createComment, elementWithChildren,
         createProcessingInstruction, createTextNode});
 
@@ -88,18 +183,19 @@
       Node deepClonedNode = parent.cloneNode(true);
       assertEquals(parent.toString(), deepClonedNode.toString());
     }
+
+    // Now check the document.
     XMLParser.removeWhitespace(d);
     if (XMLParser.supportsCDATASection()) {
-      assertEquals("<elementWithChildren>" + "<![CDATA[sampl<<< >>e data]]>"
-          + "<!--a sample comment-->" + "<elementWithChildren/>"
-          + "<?target processing instruction data?>" + "sample text node"
-          + "</elementWithChildren>", d.toString());
+      assertDocumentEquals(XMLParser.parse("<elementWithChildren>"
+          + "<![CDATA[sampl<<< >>e data]]>" + "<!--a sample comment-->"
+          + "<elementWithChildren/>" + "<?target processing instruction data?>"
+          + "sample text node" + "</elementWithChildren>"), d);
     } else {
-      // Opera does not support CDATASection nodes
-      assertEquals("<elementWithChildren>" + "sample data"
-          + "<!--a sample comment-->" + "<elementWithChildren/>"
-          + "<?target processing instruction data?>" + "sample text node"
-          + "</elementWithChildren>", d.toString());
+      assertDocumentEquals(XMLParser.parse("<elementWithChildren>"
+          + "sample data" + "<!--a sample comment-->"
+          + "<elementWithChildren/>" + "<?target processing instruction data?>"
+          + "sample text node" + "</elementWithChildren>"), d);
     }
   }
 
@@ -109,9 +205,11 @@
     assertEquals(e1Nodes.getLength(), 1);
     Node e1Node = e1Nodes.item(0);
     assertEquals(((Element) e1Node).getTagName(), "e1");
-    Element e1NodeDirect = d.getElementById("e1Id");
-    assertEquals(e1NodeDirect, null);
+
     // we didn't define a dtd, so no id for us
+    Element e1NodeDirect = d.getElementById("e1Id");
+    assertNull(e1NodeDirect);
+
     Document alienDoc = XMLParser.createDocument();
     Node alienNode11 = alienDoc.importNode(e1Node, true);
     alienDoc.appendChild(alienNode11);
@@ -134,10 +232,32 @@
     Element d2 = (Element) deepNodes.item(1);
     assertTrue(d2.hasAttribute("depth"));
     Attr depthAttr = d2.getAttributeNode("depth");
+    assertNotNull(depthAttr);
     d2.removeAttribute("depth");
     assertFalse(d2.hasAttribute("depth"));
   }
 
+  public void testForIssue733() {
+    // TODO (knorton):
+    // http://code.google.com/p/google-web-toolkit/issues/detail?id=2346
+    // Fixing issue #733 has been deferred for Safari2. See the bug URL for more
+    // details. This should be enabled as soon as that bug is fixed.
+    if (XMLParserImplSafari.isSafari2LevelWebKit()) {
+      return;
+    }
+
+    final Document document = XMLParser.createDocument();
+    final Element element = document.createElement("foo");
+    document.appendChild(element);
+    element.setAttribute("bar", "<");
+    final String xmlAsString = document.toString();
+    try {
+      XMLParser.parse(xmlAsString);
+    } catch (DOMParseException e) {
+      fail(xmlAsString + " is invalid XML.");
+    }
+  }
+
   public void testNamedNodeMap() {
     Document d = createTestDocument();
     NamedNodeMap m = d.getDocumentElement().getAttributes();
@@ -145,19 +265,6 @@
     assertEquals(m.getLength(), 2);
   }
 
-  public void testPrefix() {
-    Document d = XMLParser.parse("<?xml version=\"1.0\"?>\r\n"
-        + "<!-- both namespace prefixes are available throughout -->\r\n"
-        + "<bk:book xmlns:bk=\'urn:loc.gov:books\'\r\n"
-        + "         xmlns:isbn=\'urn:ISBN:0-395-36341-6\'>\r\n"
-        + "    <bk:title>Cheaper by the Dozen</bk:title>\r\n"
-        + "    <isbn:number>1568491379</isbn:number>\r\n" + "</bk:book>");
-    assertEquals(d.getDocumentElement().getNodeName(), "bk:book");
-    assertEquals(d.getDocumentElement().getPrefix(), "bk");
-    assertEquals(d.getElementsByTagName("book").getLength(), 1);
-    assertEquals(d.getElementsByTagName("book").item(0), d.getDocumentElement());
-  }
-
   public void testNavigation() {
     Document d = createTestDocument();
     Element documentElement = d.getDocumentElement();
@@ -206,10 +313,21 @@
   }
 
   public void testParse() {
-    Document d = XMLParser.parse("<!--hello-->   <a spam=\"ham\">\n  <?pi hello ?>dfgdfg  <b/>\t</a>");
-    XMLParser.removeWhitespace(d);
-    assertEquals("<!--hello--><a spam=\"ham\"><?pi hello ?>dfgdfg  <b/></a>",
-        d.toString());
+    Document docA = XMLParser.parse("<!--hello-->   <a spam=\"ham\">\n  <?pi hello ?>dfgdfg  <b/>\t</a>");
+
+    Document docB = XMLParser.createDocument();
+    docB.appendChild(docB.createComment("hello"));
+    final Element eleB = docB.createElement("a");
+    docB.appendChild(eleB);
+    eleB.setAttribute("spam", "ham");
+    eleB.appendChild(docB.createTextNode("\n  "));
+    eleB.appendChild(docB.createProcessingInstruction("pi", "hello "));
+    eleB.appendChild(docB.createTextNode("dfgdfg  "));
+    eleB.appendChild(docB.createElement("b"));
+    eleB.appendChild(docB.createTextNode("\t"));
+
+    assertDocumentEquals(docA, docB);
+
     try {
       XMLParser.parse("<<<");
       fail();
@@ -217,6 +335,19 @@
     }
   }
 
+  public void testPrefix() {
+    Document d = XMLParser.parse("<?xml version=\"1.0\"?>\r\n"
+        + "<!-- both namespace prefixes are available throughout -->\r\n"
+        + "<bk:book xmlns:bk=\'urn:loc.gov:books\'\r\n"
+        + "         xmlns:isbn=\'urn:ISBN:0-395-36341-6\'>\r\n"
+        + "    <bk:title>Cheaper by the Dozen</bk:title>\r\n"
+        + "    <isbn:number>1568491379</isbn:number>\r\n" + "</bk:book>");
+    assertEquals(d.getDocumentElement().getNodeName(), "bk:book");
+    assertEquals(d.getDocumentElement().getPrefix(), "bk");
+    assertEquals(d.getElementsByTagName("book").getLength(), 1);
+    assertEquals(d.getElementsByTagName("book").item(0), d.getDocumentElement());
+  }
+
   public void testProcessingInstruction() {
     Document d = createTestDocument();
     ProcessingInstruction pi = (ProcessingInstruction) d.getChildNodes().item(0);
@@ -228,13 +359,13 @@
 
   public void testText() {
     Document d = createTestDocument();
-    List textLikeNodes = Arrays.asList(new Node[] {
+    List<Node> textLikeNodes = Arrays.asList(new Node[] {
         d.createTextNode(""), d.createCDATASection(""), d.createComment("")});
     StringBuffer b = new StringBuffer();
     for (char i = 32; i < 30000; i++) {
       b.append(i);
     }
-    for (Iterator iter = textLikeNodes.iterator(); iter.hasNext();) {
+    for (Iterator<Node> iter = textLikeNodes.iterator(); iter.hasNext();) {
       CharacterData textLike = (CharacterData) iter.next();
       textLike.setData(b.toString());
       assertEquals("initialLength type:" + textLike.getNodeType(), 30000 - 32,
@@ -242,13 +373,13 @@
       assertEquals("initialEquals", textLike.getData(), b.toString());
     }
     for (int i = 32; i < 29900; i += 100) {
-      for (Iterator iter = textLikeNodes.iterator(); iter.hasNext();) {
+      for (Iterator<Node> iter = textLikeNodes.iterator(); iter.hasNext();) {
         CharacterData textLike = (CharacterData) iter.next();
         assertEquals("substring type:" + textLike.getNodeType() + " count: "
             + i, b.substring(i, i + 100), textLike.substringData(i, 100));
       }
     }
-    for (Iterator iter = textLikeNodes.iterator(); iter.hasNext();) {
+    for (Iterator<Node> iter = textLikeNodes.iterator(); iter.hasNext();) {
       StringBuffer bTemp = new StringBuffer(b.toString());
       CharacterData textLike = (CharacterData) iter.next();
       textLike.deleteData(100, 100);
@@ -259,7 +390,7 @@
           bTemp.toString(), textLike.getData());
       bTemp.setLength(0);
     }
-    for (Iterator iter = textLikeNodes.iterator(); iter.hasNext();) {
+    for (Iterator<Node> iter = textLikeNodes.iterator(); iter.hasNext();) {
       StringBuffer bTemp = new StringBuffer(b.toString());
       CharacterData textLike = (CharacterData) iter.next();
       textLike.setData(bTemp.toString());
@@ -271,7 +402,7 @@
           bTemp.toString(), textLike.getData());
       bTemp.setLength(0);
     }
-    for (Iterator iter = textLikeNodes.iterator(); iter.hasNext();) {
+    for (Iterator<Node> iter = textLikeNodes.iterator(); iter.hasNext();) {
       CharacterData textLike = (CharacterData) iter.next();
       textLike.appendData("!!!");
       assertTrue("endswith!!!", textLike.getData().endsWith("!!!"));
@@ -307,37 +438,4 @@
           3).toString(), "0123456789abcdefghij");
     }
   }
-
-  private List asList(Node[] nodes) {
-    return Arrays.asList(nodes);
-  }
-
-  private Document createTestDocument() {
-    Document d = XMLParser.createDocument();
-    Element top = d.createElement("doc");
-    top.setAttribute("fluffy", "true");
-    top.setAttribute("numAttributes", "2");
-    d.appendChild(top);
-    ProcessingInstruction commentBefore = d.createProcessingInstruction(
-        "target", "some data");
-    d.insertBefore(commentBefore, top);
-    Comment commentAfter = d.createComment("after the element");
-    d.insertBefore(commentAfter, null);
-    for (int i = 0; i < 3; i++) {
-      Element e = d.createElement("e" + i);
-      e.setAttribute("id", "e" + i + "Id");
-      top.appendChild(e);
-    }
-    Element deep = d.createElement("deep");
-    top.getFirstChild().appendChild(deep);
-    deep.setAttribute("depth", "1 foot");
-    Element deep2 = d.createElement("deep");
-    deep2.setAttribute("depth", "2 feet");
-    top.getFirstChild().getFirstChild().appendChild(deep2);
-
-    top.appendChild(d.createTextNode("0123456789"));
-    top.appendChild(d.createCDATASection("abcdefghij"));
-    return d;
-  }
-
 }