blob: 21c8f31e196c5fb7bb6a89224fd9a3e604996176 [file] [log] [blame]
/*
* 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.empire.xml;
// Java
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* This class prints out a XML-DOM-Tree to an output stream.
* <P>
*
*
*/
public class XMLWriter
{
// Logger
protected static final Logger log = LoggerFactory.getLogger(XMLWriter.class);
/** Print writer. */
protected PrintWriter out;
/** Canonical output. */
protected boolean canonical;
/** xmlWriterRoot for debugToFile function */
private static String xmlWriterRoot = null;
/** Default Encoding */
private String charsetEncoding = "utf-8";
/**
* Prints out the DOM-Tree on System.out for debugging purposes.
*
* @param doc The XML-Document to print
*/
public static void debug(Document doc)
{
XMLWriter dbg = new XMLWriter(System.out);
dbg.print(doc);
}
/**
* Prints out the DOM-Tree to a file for debugging purposes.
* The file will be truncated if it exists or created if if does
* not exist.
*
* @param doc The XML-Document to print
* @param filename The name of the file to write the XML-Document to
*/
public static void debugToFile(Document doc, String filename)
{
String styleSheet = "../" + filename.substring(0, filename.length() - 3) + "xslt";
FileOutputStream fileOutputStream = null;
try
{
File file = new File(xmlWriterRoot, filename);
if (file.exists() == true)
{
file.delete();
}
// do wen need this? file.createNewFile();
fileOutputStream = new FileOutputStream(file);
// write xml
XMLWriter dbg = new XMLWriter(fileOutputStream);
dbg.print(doc, styleSheet);
} catch (IOException ioe)
{
log.error("Error: Could not write XML to file: " + filename + " in directory: " + xmlWriterRoot);
} finally
{
try
{
if (fileOutputStream != null)
{
fileOutputStream.close();
}
} catch (IOException ioe)
{
log.error("Cannot write Document file", ioe);
/* Ignore IOExceptions */
}
}
}
/**
* Prints out the DOM-Tree. The file will be truncated if it
* exists or created if if does not exist.
*
* @param doc The XML-Document to print
* @param filename The name of the file to write the XML-Document to
*/
public static void saveAsFile(Document doc, String filename)
{
try
{
File file = new File(filename);
if (file.exists() == true)
{
file.delete();
}
DOMSource domSource = new DOMSource(doc);
StreamResult streamResult = new StreamResult(file);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer trf = transformerFactory.newTransformer();
trf.transform(domSource, streamResult);
} catch (Exception ex)
{
log.error("Error: Could not write XML to file: " + filename);
}
}
public static void setXmlWriterDebugPath(String path)
{
xmlWriterRoot = path;
}
/**
* Creates a XML Writer object.
*
* @param writer a writer to the output stream
* @param charsetEncoding encoding type (i.e. utf-8)
*/
public XMLWriter(Writer writer, String charsetEncoding)
{
this.out = new PrintWriter(writer);
if (charsetEncoding!=null)
this.charsetEncoding = charsetEncoding;
this.canonical = false;
}
/**
* Creates a XML Writer object.
*
* @param outStream the output stream
* @param charsetEncoding The name of a supported
* {@link java.nio.charset.Charset </code>charset<code>}
*
* @throws UnsupportedEncodingException If the named encoding is not supported
*/
public XMLWriter(OutputStream outStream, String charsetEncoding)
throws UnsupportedEncodingException
{ // Set Debug Level
this(new OutputStreamWriter(outStream, charsetEncoding), charsetEncoding);
}
/**
* Constructor
*
* @param outStream the output stream
*/
public XMLWriter(OutputStream outStream)
{
try
{
this.charsetEncoding = "utf-8";
this.out = new PrintWriter(new OutputStreamWriter(outStream, charsetEncoding));
this.canonical = false;
} catch (UnsupportedEncodingException e)
{
log.error("The encoding \"" + this.charsetEncoding + "\" is not supported!", e);
}
}
/**
* Prints the specified node recursively
*
* @param node the current node to print
* @param level the nesting level used for indenting the output
* @return the node type of this node
*/
public int print(Node node, int level)
{
// is there anything to do?
if (node == null)
{
return 0;
}
int type = node.getNodeType();
switch (type)
{
// print document
case Node.DOCUMENT_NODE:
{ // Sound not come here
print(((Document) node).getDocumentElement(), 0);
}
break;
// print element with attributes
case Node.ELEMENT_NODE:
{
// out
out.print('<');
out.print(node.getNodeName());
Attr attrs[] = sortAttributes(node.getAttributes());
for (int i = 0; i < attrs.length; i++)
{
Attr attr = attrs[i];
out.print(' ');
out.print(attr.getNodeName());
out.print("=\"");
out.print(normalize(attr.getNodeValue()));
out.print('"');
}
// children
NodeList children = node.getChildNodes();
if (children != null)
{
// close-tag
int len = children.getLength();
if (len > 0 && children.item(0).getNodeType() != Node.TEXT_NODE)
out.println('>');
else
out.print('>');
// Print all Children
int prevType = 0;
for (int i = 0; i < len; i++)
{
if (i > 0 || children.item(i).getNodeType() != Node.TEXT_NODE)
{ // Indent next Line
for (int s = 0; s < level; s++)
out.print(" ");
}
// Print a child
prevType = print(children.item(i), level + 1);
}
// Endtag
if (len > 0 && prevType != Node.TEXT_NODE)
{ // padding
for (int s = 1; s < level; s++)
out.print(" ");
}
out.print("</");
out.print(node.getNodeName());
out.println('>');
}
else
out.println("/>");
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++)
{
print(children.item(i), level + 1);
}
}
}
else
{
out.print('&');
out.print(node.getNodeName());
out.print(';');
}
break;
}
// print cdata sections
case Node.CDATA_SECTION_NODE:
{
if (canonical == false)
{
out.print("<![CDATA[");
out.print(node.getNodeValue());
out.println("]]>");
}
else
out.print(normalize(node.getNodeValue()));
break;
}
// print text
case Node.TEXT_NODE:
{ // Text
out.print(normalize(node.getNodeValue()));
break;
}
// print processing instruction
case Node.PROCESSING_INSTRUCTION_NODE:
{
out.print("<?");
out.print(node.getNodeName());
String data = node.getNodeValue();
if (data != null && data.length() > 0)
{
out.print(' ');
out.print(data);
}
out.println("?>");
break;
}
}
out.flush();
return type;
} // print(Node)
/**
* Prints the specified document.
*
* @param doc the XML-DOM-Document to print
*/
public void print(Document doc)
{
print(doc, null);
}
/**
* Prints the specified document.
*
* @param doc the XML-DOM-Document to print
* @param styleSheet the XML-DOM-Document to print
*/
public void print(Document doc, String styleSheet)
{
if (!canonical)
{
out.println("<?xml version=\"1.0\" encoding=\"" + charsetEncoding + "\"?>");
}
if (styleSheet != null)
{
//20040427 Marco: xml stylesheet document specification changed from "<?xml:stylesheet name=\"text/xsl\" href=\""
// to
// "<?xml-stylesheet type=\"text/xsl\" href=\""
out.print("<?xml-stylesheet type=\"text/xsl\" href=\"");
out.print(styleSheet);
out.println("\"?>");
}
// Print the Document
print(doc.getDocumentElement(), 0);
out.flush();
}
/**
* Sorts attributes by name.
*
* @param attrs the unsorted list of attributes
* @return the sorted list of attributes
*/
protected 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);
} // sortAttributes(NamedNodeMap):Attr[]
/**
* Converts a string to valid XML-Syntax replacing XML entities.
*
* @param s the string to normalize
*/
protected String normalize(String s)
{
return normalize(s, canonical);
}
static public String normalize(String s, boolean canonical)
{
StringBuilder str = new StringBuilder();
int len = (s != null) ? s.length() : 0;
for (int i = 0; i < len; i++)
{
char ch = s.charAt(i);
switch (ch)
{
case '<':
{
str.append("&lt;");
break;
}
case '>':
{
str.append("&gt;");
break;
}
case '&':
{
str.append("&amp;");
break;
}
case '"':
{
str.append("&quot;");
break;
}
case '\r':
case '\n':
{
if (canonical)
{
str.append("&#");
str.append(Integer.toString(ch));
str.append(';');
break;
}
// else, default append char
}
default:
{
str.append(ch);
}
}
}
return (str.toString());
} // normalize(String):String
}