package org.tigris.juxy.util;
import org.w3c.dom.DocumentType;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.TreeWalker;
import java.util.Arrays;
/**
* @author Pavel Sher
*/
public class DocumentsAssertionError extends AssertionError {
private TreeWalker etw;
private TreeWalker atw;
private String message;
public DocumentsAssertionError(TreeWalker etw, TreeWalker atw) {
this.etw = etw;
this.atw = atw;
init();
}
private void init() {
StringBuffer buf = new StringBuffer(100);
buf.append("Documents differ, expected document:\n");
appendDocument(buf, etw);
buf.append("\nActual document:\n");
appendDocument(buf, atw);
message = buf.toString();
}
public String getMessage() {
return message;
}
private void appendDocument(StringBuffer buf, TreeWalker tw) {
Node startFrom = tw.getCurrentNode();
if (hasParentElement(startFrom))
startFrom = tw.parentNode();
if (startFrom.getOwnerDocument() == null)
startFrom = tw.nextNode();
String delimiter = null;
if (hasParentElement(startFrom))
delimiter = "...";
appendDelimiter(buf, delimiter);
serialize(buf, tw, 0);
appendDelimiter(buf, delimiter);
}
private boolean hasParentElement(Node startFrom) {
if (startFrom == null || startFrom.getNodeType() == Node.DOCUMENT_NODE)
return false;
return startFrom.getParentNode() != null && startFrom.getParentNode().getNodeType() == Node.ELEMENT_NODE;
}
private void appendDelimiter(StringBuffer buf, String delimiter) {
if (delimiter != null) {
appendNewLine(buf);
buf.append(delimiter).append('\n');
}
}
private void serialize(StringBuffer buf, TreeWalker tw, int level) {
char[] prefixChars = new char[level * 2];
Arrays.fill(prefixChars, ' ');
Node currentNode = tw.getCurrentNode();
while (currentNode != null) {
switch (currentNode.getNodeType()) {
case Node.ELEMENT_NODE:
appendNewLine(buf);
appendPrefix(buf, prefixChars).append("<").append(currentNode.getNodeName());
NamedNodeMap attrs = currentNode.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
Node attr = attrs.item(i);
// we will skip here xmlns attribute defining namespace of currentNode element
// and write it later
if (attr.getNodeName().startsWith("xmlns") && attr.getNodeValue().equals(currentNode.getNamespaceURI()))
continue;
buf.append(" ").append(attr.getNodeName()).append("=\"").append(getAttributeValue(attr)).append("\"");
}
if (currentNode.getNamespaceURI() != null) {
buf.append(" xmlns");
if (currentNode.getPrefix() != null)
buf.append(':').append(currentNode.getPrefix());
buf.append("=\"").append(currentNode.getNamespaceURI()).append("\"");
}
Node child = tw.firstChild();
if (child == null) {
buf.append("/>\n");
} else {
buf.append(">");
serialize(buf, tw, level + 1);
appendPrefix(buf, prefixChars).append("</").append(currentNode.getNodeName()).append(">\n");
}
tw.setCurrentNode(currentNode);
break;
case Node.TEXT_NODE:
String val = currentNode.getNodeValue().trim();
if (val.length() > 0)
buf.append(StringUtil.escapeXMLText(val));
break;
case Node.COMMENT_NODE:
appendPrefix(buf, prefixChars).append("<!--").append(currentNode.getNodeValue()).append("-->");
break;
case Node.PROCESSING_INSTRUCTION_NODE:
appendPrefix(buf, prefixChars).append("<?").append(currentNode.getNodeName()).append(' ').append(currentNode.getNodeValue()).append("?>");
break;
case Node.CDATA_SECTION_NODE:
appendPrefix(buf, prefixChars).append("<![CDATA[").append(currentNode.getNodeValue()).append("]]>");
break;
case Node.DOCUMENT_TYPE_NODE:
DocumentType dt = (DocumentType) currentNode;
buf.append("<!DOCTYPE ").append(currentNode.getNodeName());
if (dt.getPublicId() != null) {
buf.append(" PUBLIC \"").append(dt.getPublicId()).append("\"");
buf.append(" \"").append(dt.getSystemId()).append("\"");
} else if (dt.getSystemId() != null) {
buf.append(" SYSTEM \"").append(dt.getSystemId()).append("\"");
}
if (dt.getInternalSubset() != null) {
buf.append(" [\n");
buf.append(dt.getInternalSubset());
buf.append("]");
}
buf.append(">");
break;
}
currentNode = tw.nextSibling();
}
}
private String getAttributeValue(Node attr) {
return StringUtil.escapeQuoteCharacter(StringUtil.escapeXMLText(attr.getNodeValue()));
}
private StringBuffer appendNewLine(StringBuffer buf) {
if (!endsWithNewLine(buf))
buf.append('\n');
return buf;
}
private boolean endsWithNewLine(StringBuffer buf) {
return buf.charAt(buf.length() - 1) == '\n';
}
private StringBuffer appendPrefix(StringBuffer buf, char[] prefix) {
if (endsWithNewLine(buf))
buf.append(prefix);
return buf;
}
}