package org.jbpm.pvm.internal.util;
|
|
/*
|
* ====================================================================
|
*
|
* The Apache Software License, Version 1.1
|
*
|
* Copyright (c) 1999 The Apache Software Foundation. All rights
|
* reserved.
|
*
|
* Redistribution and use in source and binary forms, with or without
|
* modification, are permitted provided that the following conditions
|
* are met:
|
*
|
* 1. Redistributions of source code must retain the above copyright
|
* notice, this list of conditions and the following disclaimer.
|
*
|
* 2. Redistributions in binary formResourceName must reproduce the above copyright
|
* notice, this list of conditions and the following disclaimer in
|
* the documentation and/or other materials provided with the
|
* distribution.
|
*
|
* 3. The end-user documentation included with the redistribution, if
|
* any, must include the following acknowlegement:
|
* "This product includes software developed by the
|
* Apache Software Foundation (http://www.apache.org/)."
|
* Alternately, this acknowlegement may appear in the software itself,
|
* if and wherever such third-party acknowlegements normally appear.
|
*
|
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
|
* Foundation" must not be used to endorse or promote products derived
|
* from this software without prior written permission. For written
|
* permission, please contact apache@apache.org.
|
*
|
* 5. Products derived from this software may not be called "Apache"
|
* nor may "Apache" appear in their names without prior written
|
* permission of the Apache Group.
|
*
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
* SUCH DAMAGE.
|
* ====================================================================
|
*
|
* This software consists of voluntary contributions made by many
|
* individuals on behalf of the Apache Software Foundation. For more
|
* information on the Apache Software Foundation, please see
|
* <http://www.apache.org/>.
|
*/
|
|
import java.io.OutputStream;
|
import java.io.OutputStreamWriter;
|
import java.io.PrintWriter;
|
import java.io.StringWriter;
|
import java.io.UnsupportedEncodingException;
|
import java.io.Writer;
|
import java.util.HashMap;
|
import java.util.Iterator;
|
import java.util.Map;
|
|
import org.w3c.dom.Attr;
|
import org.w3c.dom.Element;
|
import org.w3c.dom.NamedNodeMap;
|
import org.w3c.dom.Node;
|
import org.w3c.dom.NodeList;
|
|
/**
|
* Traverse a DOM tree in order to print a document that is parsed.
|
*
|
* @author Andy Clark, IBM
|
* @author Thomas.Diesler@jboss.org
|
*/
|
@SuppressWarnings("unchecked")
|
public class DOMWriter
|
{
|
// Print writer
|
private PrintWriter out;
|
// True, if canonical output
|
private boolean canonical;
|
// True, if pretty printing should be used
|
private boolean prettyprint;
|
// True, if the XML declaration should be written
|
private boolean writeXMLDeclaration;
|
// True, if whitespace should be ignored
|
private boolean ignoreWhitespace;
|
// Explicit character set encoding
|
private String charsetName;
|
// indent for the pretty printer
|
private int prettyIndent;
|
// True, if the XML declaration has been written
|
private boolean wroteXMLDeclaration;
|
// The node that started the write
|
private Node rootNode;
|
// True if we want namespace completion
|
private boolean completeNamespaces = true;
|
// The current default namespace
|
private String currentDefaultNamespace;
|
|
public DOMWriter(Writer w)
|
{
|
this.out = new PrintWriter(w);
|
}
|
|
public DOMWriter(Writer w, String charsetName)
|
{
|
this.out = new PrintWriter(w);
|
this.charsetName = charsetName;
|
this.writeXMLDeclaration = true;
|
}
|
|
public DOMWriter(OutputStream stream)
|
{
|
try
|
{
|
this.out = new PrintWriter(new OutputStreamWriter(stream, "UTF-8"));
|
}
|
catch (UnsupportedEncodingException e)
|
{
|
// ignore, UTF-8 should be available
|
}
|
}
|
|
public DOMWriter(OutputStream stream, String charsetName)
|
{
|
try
|
{
|
this.out = new PrintWriter(new OutputStreamWriter(stream, charsetName));
|
this.charsetName = charsetName;
|
this.writeXMLDeclaration = true;
|
}
|
catch (UnsupportedEncodingException e)
|
{
|
throw new IllegalArgumentException("Unsupported encoding: " + charsetName);
|
}
|
}
|
|
/**
|
* Print a node with explicit prettyprinting.
|
* The defaults for all other DOMWriter properties apply.
|
*
|
*/
|
public static String printNode(Node node, boolean prettyprint)
|
{
|
StringWriter strw = new StringWriter();
|
new DOMWriter(strw).setPrettyprint(prettyprint).print(node);
|
return strw.toString();
|
}
|
|
public boolean isCanonical()
|
{
|
return canonical;
|
}
|
|
/**
|
* Set wheter entities should appear in their canonical formResourceName.
|
* The default is false.
|
*/
|
public DOMWriter setCanonical(boolean canonical)
|
{
|
this.canonical = canonical;
|
return this;
|
}
|
|
public boolean isIgnoreWhitespace()
|
{
|
return ignoreWhitespace;
|
}
|
|
/**
|
* Set whether whitespace should be ignored.
|
* The default is false.
|
*/
|
public DOMWriter setIgnoreWhitespace(boolean ignoreWhitespace)
|
{
|
this.ignoreWhitespace = ignoreWhitespace;
|
return this;
|
}
|
|
/**
|
* Set wheter subelements should have their namespaces completed.
|
* Setting this to false may lead to invalid XML fragments.
|
* The default is true.
|
*/
|
public DOMWriter setCompleteNamespaces(boolean complete)
|
{
|
this.completeNamespaces = complete;
|
return this;
|
}
|
|
public boolean isPrettyprint()
|
{
|
return prettyprint;
|
}
|
|
/**
|
* Set wheter element should be indented.
|
* The default is false.
|
*/
|
public DOMWriter setPrettyprint(boolean prettyprint)
|
{
|
this.prettyprint = prettyprint;
|
return this;
|
}
|
|
public boolean isWriteXMLDeclaration()
|
{
|
return writeXMLDeclaration;
|
}
|
|
/**
|
* Set wheter the XML declaration should be written.
|
* The default is false.
|
*/
|
public DOMWriter setWriteXMLDeclaration(boolean flag)
|
{
|
this.writeXMLDeclaration = flag;
|
return this;
|
}
|
|
public void print(Node node)
|
{
|
if (prettyprint && ignoreWhitespace)
|
throw new IllegalStateException("Cannot pretty print and ignore whitespace");
|
|
rootNode = node;
|
printInternal(node, false);
|
}
|
|
private void printInternal(Node node, boolean indentEndMarker)
|
{
|
// is there anything to do?
|
if (node == null)
|
{
|
return;
|
}
|
|
// JBAS-2117 - Don't skip the DOCUMENT_NODE
|
// if (node instanceof Document) node = ((Document)node).getDocumentElement();
|
|
if (wroteXMLDeclaration == false && writeXMLDeclaration == true && canonical == false)
|
{
|
out.print("<?xml version='1.0'");
|
if (charsetName != null)
|
out.print(" encoding='" + charsetName + "'");
|
|
out.print("?>");
|
if (prettyprint)
|
out.println();
|
|
wroteXMLDeclaration = true;
|
}
|
|
int type = node.getNodeType();
|
boolean hasChildNodes = node.getChildNodes().getLength() > 0;
|
|
String nodeName = node.getNodeName();
|
switch (type)
|
{
|
// print document
|
case Node.DOCUMENT_NODE:
|
{
|
NodeList children = node.getChildNodes();
|
for (int iChild = 0; iChild < children.getLength(); iChild++)
|
{
|
printInternal(children.item(iChild), false);
|
}
|
out.flush();
|
break;
|
}
|
|
// print element with attributes
|
case Node.ELEMENT_NODE:
|
{
|
Element element = (Element)node;
|
if (prettyprint)
|
{
|
for (int i = 0; i < prettyIndent; i++)
|
{
|
out.print(' ');
|
}
|
prettyIndent++;
|
}
|
|
out.print('<');
|
out.print(nodeName);
|
|
Map nsMap = new HashMap();
|
String elPrefix = node.getPrefix();
|
String elNamespaceURI = node.getNamespaceURI();
|
if (elPrefix != null)
|
{
|
String nsURI = getNamespaceURI(elPrefix, element, rootNode);
|
nsMap.put(elPrefix, nsURI);
|
}
|
|
Attr attrs[] = sortAttributes(node.getAttributes());
|
for (int i = 0; i < attrs.length; i++)
|
{
|
Attr attr = attrs[i];
|
String atPrefix = attr.getPrefix();
|
String atName = attr.getNodeName();
|
String atValue = normalize(attr.getNodeValue(), canonical);
|
|
if (atName.equals("xmlns"))
|
currentDefaultNamespace = atValue;
|
|
if (atPrefix != null && !atPrefix.equals("xmlns") && !atPrefix.equals("xml"))
|
{
|
String nsURI = getNamespaceURI(atPrefix, element, rootNode);
|
nsMap.put(atPrefix, nsURI);
|
// xsi:type='ns1:SubType', xsi:type='xsd:string'
|
if (atName.equals(atPrefix + ":type") && atValue.indexOf(":") > 0)
|
{
|
// xsi defined on the envelope
|
if (nsURI == null)
|
nsURI = getNamespaceURI(atPrefix, element, null);
|
|
if ("http://www.w3.org/2001/XMLSchema-instance".equals(nsURI))
|
{
|
String typePrefix = atValue.substring(0, atValue.indexOf(":"));
|
String typeURI = getNamespaceURI(typePrefix, element, rootNode);
|
nsMap.put(typePrefix, typeURI);
|
}
|
}
|
}
|
|
out.print(" " + atName + "='" + atValue + "'");
|
}
|
|
// Add namespace declaration for prefixes
|
// that are defined further up the tree
|
if (completeNamespaces)
|
{
|
Iterator itPrefix = nsMap.keySet().iterator();
|
while (itPrefix.hasNext())
|
{
|
String prefix = (String)itPrefix.next();
|
String nsURI = (String)nsMap.get(prefix);
|
if (nsURI == null)
|
{
|
nsURI = getNamespaceURI(prefix, element, null);
|
out.print(" xmlns:" + prefix + "='" + nsURI + "'");
|
}
|
}
|
}
|
|
// The SAX ContentHandler will by default not add the namespace declaration
|
// <Hello xmlns='http://somens'>World</Hello>
|
if (elPrefix == null && elNamespaceURI != null)
|
{
|
String defaultNamespace = element.getAttribute("xmlns");
|
if (defaultNamespace.length() == 0 && !elNamespaceURI.equals(currentDefaultNamespace))
|
{
|
out.print(" xmlns='" + elNamespaceURI + "'");
|
currentDefaultNamespace = elNamespaceURI;
|
}
|
}
|
|
if (hasChildNodes)
|
{
|
out.print('>');
|
}
|
|
// Find out if the end marker is indented
|
indentEndMarker = isEndMarkerIndented(node);
|
|
if (indentEndMarker)
|
{
|
out.print('\n');
|
}
|
|
NodeList childNodes = node.getChildNodes();
|
int len = childNodes.getLength();
|
for (int i = 0; i < len; i++)
|
{
|
Node childNode = childNodes.item(i);
|
printInternal(childNode, false);
|
}
|
break;
|
}
|
|
// handle entity reference nodes
|
case Node.ENTITY_REFERENCE_NODE:
|
{
|
if (canonical)
|
{
|
NodeList children = node.getChildNodes();
|
if (children != null)
|
{
|
int len = children.getLength();
|
for (int i = 0; i < len; i++)
|
{
|
printInternal(children.item(i), false);
|
}
|
}
|
}
|
else
|
{
|
out.print('&');
|
out.print(nodeName);
|
out.print(';');
|
}
|
break;
|
}
|
|
// print cdata sections
|
case Node.CDATA_SECTION_NODE:
|
{
|
if (canonical)
|
{
|
out.print(normalize(node.getNodeValue(), canonical));
|
}
|
else
|
{
|
out.print("<![CDATA[");
|
out.print(node.getNodeValue());
|
out.print("]]>");
|
}
|
break;
|
}
|
|
// print text
|
case Node.TEXT_NODE:
|
{
|
String text = normalize(node.getNodeValue(), canonical);
|
if (text.trim().length() > 0)
|
{
|
out.print(text);
|
}
|
else if (prettyprint == false && ignoreWhitespace == false)
|
{
|
out.print(text);
|
}
|
break;
|
}
|
|
// print processing instruction
|
case Node.PROCESSING_INSTRUCTION_NODE:
|
{
|
out.print("<?");
|
out.print(nodeName);
|
String data = node.getNodeValue();
|
if (data != null && data.length() > 0)
|
{
|
out.print(' ');
|
out.print(data);
|
}
|
out.print("?>");
|
break;
|
}
|
|
// print comment
|
case Node.COMMENT_NODE:
|
{
|
for (int i = 0; i < prettyIndent; i++)
|
{
|
out.print(' ');
|
}
|
|
out.print("<!--");
|
String data = node.getNodeValue();
|
if (data != null)
|
{
|
out.print(data);
|
}
|
out.print("-->");
|
|
if (prettyprint)
|
{
|
out.print('\n');
|
}
|
|
break;
|
}
|
}
|
|
if (type == Node.ELEMENT_NODE)
|
{
|
if (prettyprint)
|
prettyIndent--;
|
|
if (hasChildNodes == false)
|
{
|
out.print("/>");
|
}
|
else
|
{
|
if (indentEndMarker)
|
{
|
for (int i = 0; i < prettyIndent; i++)
|
{
|
out.print(' ');
|
}
|
}
|
|
out.print("</");
|
out.print(nodeName);
|
out.print('>');
|
}
|
|
if (prettyIndent > 0)
|
{
|
out.print('\n');
|
}
|
}
|
out.flush();
|
}
|
|
private String getNamespaceURI(String prefix, Element element, Node stopNode)
|
{
|
Node parent = element.getParentNode();
|
String nsURI = element.getAttribute("xmlns:" + prefix);
|
if (nsURI.length() == 0 && element != stopNode && parent instanceof Element)
|
return getNamespaceURI(prefix, (Element)parent, stopNode);
|
|
return (nsURI.length() > 0 ? nsURI : null);
|
}
|
|
private boolean isEndMarkerIndented(Node node)
|
{
|
if (prettyprint)
|
{
|
NodeList childNodes = node.getChildNodes();
|
int len = childNodes.getLength();
|
for (int i = 0; i < len; i++)
|
{
|
Node children = childNodes.item(i);
|
if (children.getNodeType() == Node.ELEMENT_NODE)
|
{
|
return true;
|
}
|
}
|
}
|
return false;
|
}
|
|
/** Returns a sorted list of attributes. */
|
private Attr[] sortAttributes(NamedNodeMap attrs)
|
{
|
|
int len = (attrs != null) ? attrs.getLength() : 0;
|
Attr array[] = new Attr[len];
|
for (int i = 0; i < len; i++)
|
{
|
array[i] = (Attr)attrs.item(i);
|
}
|
for (int i = 0; i < len - 1; i++)
|
{
|
String name = array[i].getNodeName();
|
int index = i;
|
for (int j = i + 1; j < len; j++)
|
{
|
String curName = array[j].getNodeName();
|
if (curName.compareTo(name) < 0)
|
{
|
name = curName;
|
index = j;
|
}
|
}
|
if (index != i)
|
{
|
Attr temp = array[i];
|
array[i] = array[index];
|
array[index] = temp;
|
}
|
}
|
return (array);
|
}
|
|
/** Normalizes the given string. */
|
public static String normalize(String s, boolean canonical)
|
{
|
StringBuilder normal = new StringBuilder();
|
|
int len = (s != null) ? s.length() : 0;
|
for (int i = 0; i < len; i++)
|
{
|
char ch = s.charAt(i);
|
switch (ch)
|
{
|
case '<':
|
{
|
normal.append("<");
|
break;
|
}
|
case '>':
|
{
|
normal.append(">");
|
break;
|
}
|
case '&':
|
{
|
normal.append("&");
|
break;
|
}
|
case '"':
|
{
|
normal.append(""");
|
break;
|
}
|
case '\'':
|
{
|
normal.append("'");
|
break;
|
}
|
case '\r':
|
case '\n':
|
{
|
if (canonical)
|
{
|
normal.append("&#");
|
normal.append(Integer.toString(ch));
|
normal.append(';');
|
break;
|
}
|
// else, default append char
|
}
|
default:
|
{
|
normal.append(ch);
|
}
|
}
|
}
|
return (normal.toString());
|
}
|
}
|