/*
* Copyright (c) 2008, Ounce Labs, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form 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.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY OUNCE LABS, INC. ``AS IS'' AND ANY
* EXPRESS 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 OUNCE LABS, INC. 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.
*/
package org.codehaus.mojo.ounce.core;
import org.w3c.dom.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
/**
* An XML Writer
*
* @author <a href="mailto:sam.headrick@ouncelabs.com">Sam Headrick</a>
*/
public class XmlWriter
{
public static final String START_ELEMENT = "<";
public static final String START_CLOSE_ELEMENT = "</";
public static final String END_CLOSE_ELEMENT = "/>";
public static final String END_ELEMENT = ">";
public static final String HEADER_TEXT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
public static String RETURN_CHAR = "\n";
public static final String TAB_CHAR = "\t";
public static final String QUOTE = "\"";
public static final String EQUALS = "=";
public static final String SPACE = " ";
public static final String WINDOWS_FAMILY = "windows";
public static final String LINUX_FAMILY = "linux";
public static final String SOLARIS_FAMILY = "sunos";
public static final String UNKNOWN_FAMILY = "other";
public static final String[] OS_FAMILIES = new String[] { WINDOWS_FAMILY, LINUX_FAMILY, SOLARIS_FAMILY };
private FileOutputStream m_outstream;
private OutputStreamWriter m_writer;
private boolean m_formatXml = true;
private HashMap/* <String, Boolean> */m_attributesOnSameLine = new HashMap/* <String, Boolean> */();
private HashMap/* <String, String[]> */m_attributeOrder = new HashMap/* <String, String[]> */();
private String m_tabs = "";
private boolean m_writeEmptyValues = false;
private boolean m_defaultToAttributesOnSameLine = false;
public XmlWriter()
{
this( true );
}
public XmlWriter( boolean formatXml )
{
m_formatXml = formatXml;
if ( getOsFamily().equals( WINDOWS_FAMILY ) )
{
RETURN_CHAR = "\r\n";
}
}
public void setDefaultToAttributesOnSameLine( boolean value )
{
m_defaultToAttributesOnSameLine = value;
}
public void setAttributesOnSameLine( String nodeName, boolean attributesOnSameLine )
{
m_attributesOnSameLine.put( nodeName, new Boolean( attributesOnSameLine ) );
}
public void setAttributeOrder( String nodeName, String[] attributeOrder )
{
m_attributeOrder.put( nodeName, attributeOrder );
}
public void setWriteEmptyValues( boolean writeEmptyValues )
{
m_writeEmptyValues = writeEmptyValues;
}
public void saveXmlFile( String filePath, Document xmlDoc )
throws IOException
{
if ( xmlDoc != null )
{
m_outstream = new FileOutputStream( filePath );
m_writer = new OutputStreamWriter( m_outstream, "UTF-8" );
startXmlDoc();
Element root = xmlDoc.getDocumentElement();
writeElement( root );
endXmlDoc();
}
}
private void writeElement( Node element )
throws IOException
{
if ( element.getNodeType() != Node.ELEMENT_NODE )
{
// skip
return;
}
NamedNodeMap attributeMap = element.getAttributes();
boolean hasChildren = false;
NodeList childNodes = element.getChildNodes();
for ( int i = 0; i < childNodes.getLength(); i++ )
{
Node node = childNodes.item( i );
if ( node.getNodeType() == Node.ELEMENT_NODE )
{
hasChildren = true;
break;
}
}
if ( hasChildren )
{
openElement( element.getNodeName(), attributeMap );
for ( int i = 0; i < childNodes.getLength(); i++ )
{
writeElement( childNodes.item( i ) );
}
closeElement( element.getNodeName() );
}
else
{
// no element children
// check for text child
String value = null;
for ( int i = 0; i < childNodes.getLength(); i++ )
{
Node node = childNodes.item( i );
if ( node.getNodeType() == Node.TEXT_NODE )
{
value = node.getNodeValue();
break;
}
}
if ( value == null )
{
addElementNoValue( element.getNodeName(), attributeMap );
}
else
{
addElementAndValue( element.getNodeName(), value, attributeMap );
}
}
}
private void addElementAndValue( String nodeName, String value, NamedNodeMap attributeMap )
throws IOException
{
startWriteLine();
writeStartElement( nodeName, attributeMap );
m_writer.write( value );
writeCloseElement( nodeName );
endWriteLine();
}
private void addElementNoValue( String nodeName, NamedNodeMap attributeMap )
throws IOException
{
startWriteLine();
writeElementNoValue( nodeName, attributeMap );
endWriteLine();
}
private void writeElementNoValue( String nodeName, NamedNodeMap attributeMap )
throws IOException
{
m_writer.write( START_ELEMENT );
m_writer.write( nodeName );
writeAttributes( nodeName, attributeMap );
m_writer.write( END_CLOSE_ELEMENT );
}
private void openElement( String nodeName, NamedNodeMap attributeMap )
throws IOException
{
startWriteLine();
writeStartElement( nodeName, attributeMap );
endWriteLine();
incrementTabs();
}
private void writeStartElement( String nodeName, NamedNodeMap attributeMap )
throws IOException
{
m_writer.write( START_ELEMENT );
m_writer.write( nodeName );
writeAttributes( nodeName, attributeMap );
m_writer.write( END_ELEMENT );
}
private void writeAttributes( String nodeName, NamedNodeMap attributeMap )
throws IOException
{
if ( attributeMap != null )
{
boolean attributesOnSameLine = false;
if ( m_defaultToAttributesOnSameLine )
{
attributesOnSameLine = true;
}
if ( m_attributesOnSameLine.get( nodeName ) != null )
{
attributesOnSameLine = ( (Boolean) m_attributesOnSameLine.get( nodeName ) ).booleanValue();
}
if ( !attributesOnSameLine )
{
m_writer.write( RETURN_CHAR );
incrementTabs();
}
ArrayList/* <Node> */attributeList = new ArrayList/* <Node> */();
if ( m_attributeOrder.get( nodeName ) != null )
{
// the attributes have a specific order
String[] attributeOrder = (String[]) m_attributeOrder.get( nodeName );
for ( int i = 0; i < attributeOrder.length; i++ )
{
Node node = attributeMap.getNamedItem( attributeOrder[i] );
attributeList.add( node );
}
}
else
{
for ( int i = 0; i < attributeMap.getLength(); i++ )
{
Node node = attributeMap.item( i );
attributeList.add( node );
}
}
for ( int i = 0; i < attributeList.size(); i++ )
{
Node node = (Node) attributeList.get( i );
if ( node != null && node.getNodeValue() != null
&& ( node.getNodeValue().length() > 0 || m_writeEmptyValues ) )
{
if ( !attributesOnSameLine )
{
m_writer.write( m_tabs );
}
m_writer.write( SPACE );
m_writer.write( node.getNodeName() );
m_writer.write( EQUALS );
m_writer.write( QUOTE );
m_writer.write( safeEncode( node.getNodeValue() ) );
m_writer.write( QUOTE );
if ( !attributesOnSameLine )
{
m_writer.write( RETURN_CHAR );
}
}
}
if ( !attributesOnSameLine )
{
decrementTabs();
m_writer.write( m_tabs );
}
}
}
private void closeElement( String nodeName )
throws IOException
{
decrementTabs();
startWriteLine();
writeCloseElement( nodeName );
endWriteLine();
}
private void writeCloseElement( String nodeName )
throws IOException
{
m_writer.write( START_CLOSE_ELEMENT );
m_writer.write( nodeName );
m_writer.write( END_ELEMENT );
}
public void startWriteLine()
throws IOException
{
if ( m_formatXml )
{
m_writer.write( m_tabs );
}
}
public void endWriteLine()
throws IOException
{
if ( m_formatXml )
{
m_writer.write( RETURN_CHAR );
}
}
private void startXmlDoc()
throws IOException
{
m_writer.write( HEADER_TEXT );
m_writer.write( RETURN_CHAR );
}
private void endXmlDoc()
throws IOException
{
m_writer.flush();
m_writer.close();
m_outstream.close();
}
private void incrementTabs()
{
if ( m_formatXml )
{
m_tabs += TAB_CHAR;
}
}
private void decrementTabs()
{
if ( m_formatXml )
{
if ( m_tabs.length() >= TAB_CHAR.length() )
{
m_tabs = m_tabs.substring( TAB_CHAR.length() );
}
}
}
/**
* Code copied from XMLEncoder to java.bean.XMLEncoder, replaces XML special characters with
*
* the XML special encoding. If the output writer already encodes the XML, do not use this method,
*
* as it will be encoded twice.
*
* @param str the string to encode
* @return the encoded string
*/
public static String safeEncode( String str )
{
StringBuffer result = null;
for ( int i = 0, max = str.length(), delta = 0; i < max; i++ )
{
char c = str.charAt( i );
String replacement = null;
if ( c == '&' )
{
replacement = "&";
}
else if ( c == '<' )
{
replacement = "<";
}
else if ( c == '\r' )
{
replacement = "
";
}
else if ( c == '>' )
{
replacement = ">";
}
else if ( c == '"' )
{
replacement = """;
}
else if ( c == '\'' )
{
replacement = "'";
}
if ( replacement != null )
{
if ( result == null )
{
result = new StringBuffer( str );
}
result.replace( i + delta, i + delta + 1, replacement );
delta += ( replacement.length() - 1 );
}
}
if ( result == null )
{
return str;
}
return result.toString();
}
/**
* @return the operating system name
*/
private static String getOsName()
{
return System.getProperty( "os.name" ).toLowerCase();
}
/**
* Get the OS family, this looks in the OS_FAMILIES array for a match
*
* @return the os family
*/
public static String getOsFamily()
{
String osName = getOsName();
String familyName = UNKNOWN_FAMILY;
for ( int i = 0; i < OS_FAMILIES.length; ++i )
{
if ( osName.startsWith( OS_FAMILIES[i] ) )
{
familyName = OS_FAMILIES[i];
break;
}
}
return familyName;
}
}