/** * Copyright (c) 2007-2011, JAGaToo Project Group 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 'Xith3D Project Group' 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR 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) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.jagatoo.util.xml; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import com.sun.org.apache.xml.internal.serialize.OutputFormat; import com.sun.org.apache.xml.internal.serialize.XMLSerializer; /** * <p> * Writes XML data. * </p> * * <p> * This class simply wrapps an inverted SAX parser to generate valid XML. * </p> * * @author Marvin Froehlich (aka Qudus) */ public class SimpleXMLWriter { private final XMLPath path = new XMLPath(); private boolean[] lastElemPushed = new boolean[ 16 ]; private final Charset charset; private BufferedWriter bw; private boolean useTabs = false; private String indentString = " "; private boolean addNewLines = true; private String closeElementName = null; @SuppressWarnings( "unused" ) private boolean indentCloseElem = false; private int lastElementIndent = 0; private String lastElementName = null; private Object[] lastAttributes = null; private final AttributesImpl attributes = new AttributesImpl(); private ContentHandler contentHandler = null; public void setIndentation( int indent, boolean useTabs ) { char[] chars = new char[ indent ]; for ( int i = 0; i < indent; i++ ) chars[i] = useTabs ? '\t' : ' '; this.indentString = new String( chars ); this.useTabs = useTabs; } public final void setIndentation( int indent ) { setIndentation( indent, false ); } public final boolean getUseTabsForIndentation() { return ( useTabs ); } public void setAddNewLines( boolean addNewLines ) { this.addNewLines = addNewLines; } public final boolean getAddNewLines() { return ( addNewLines ); } public final XMLPath getPath() { return ( path ); } public final int getLevel() { return ( path.getLevel() ); } protected void newLine() throws IOException { bw.newLine(); } private void initSAX() throws IOException, SAXException { OutputFormat of = new OutputFormat( "XML", charset.name(), indentString.length() > 0 ); of.setIndent( indentString.length() ); of.setLineSeparator( getAddNewLines() ? System.getProperty( "line.separator" ) : "" ); of.setLineWidth( 0 ); //of.setDoctype( null, "blah.dtd" ); XMLSerializer serializer = new XMLSerializer( bw, of ); contentHandler = serializer.asContentHandler(); contentHandler.startDocument(); } private void encapsulateAttributes( Object[] attributes ) { this.attributes.clear(); if ( attributes != null ) { for ( int i = 0; i < attributes.length; i += 2 ) { this.attributes.addAttribute( "", "", String.valueOf( attributes[i + 0] ), "CDATA", String.valueOf( attributes[i + 1] ) ); } } } /** * * @param newLine * @param closeDirectly * @throws SAXException */ private void applyLastElement( boolean newLine, boolean closeDirectly ) throws SAXException { if ( closeElementName != null ) { contentHandler.endElement( "", "", closeElementName ); closeElementName = null; } if ( lastElementName != null ) { encapsulateAttributes( lastAttributes ); contentHandler.startElement( "", "", lastElementName, attributes ); if ( closeDirectly ) contentHandler.endElement( "", "", lastElementName ); lastElementName = null; } } /** * Converts the passed element to a path element. By default the passed element is returned. * * @param level * @param element * * @return the converted path element. */ protected Object getPathObject( int level, String element ) { return ( element ); } /** * Writes an XML element to the file. * * @param push push element hierarchy one level down? * @param name the element's name * @param attributes the attributes (attribName1, attribValue1, attribName2, attribValue2, ...) * * @throws IOException * @throws SAXException */ protected void writeElement( boolean push, String name, Object[] attributes ) throws IOException, SAXException { if ( ( attributes != null ) && ( ( attributes.length % 2 ) != 0 ) ) throw new IllegalArgumentException( "attributes array not of even length" ); if ( contentHandler == null ) initSAX(); if ( lastElemPushed.length <= path.getLevel() + 1 ) { boolean[] tmp = new boolean[ lastElemPushed.length * 150 / 100 + 1 ]; System.arraycopy( lastElemPushed, 0, tmp, 0, lastElemPushed.length ); lastElemPushed = tmp; } if ( ( path.getLevel() > 0 ) && !lastElemPushed[path.getLevel() - 1] ) path.popPath(); applyLastElement( true, !lastElemPushed[Math.max( 0, lastElementIndent )] ); path.pushPath( name, getPathObject( path.getLevel(), name ) ); lastElemPushed[path.getLevel() - 1] = push; closeElementName = null; lastElementIndent = path.getLevel() - 1; lastElementName = name; lastAttributes = attributes; } /** * Writes an XML element to the file. * * @param name the element's name * @param attributes the attributes (attribName1, attribValue1, attribName2, attribValue2, ...) * * @throws IOException * @throws SAXException */ public final void writeElement( String name, Object... attributes ) throws IOException, SAXException { writeElement( false, name, attributes ); } /** * Writes an XML element to the file. * * @param name the element's name * @param data * @param attributes the attributes (attribName1, attribValue1, attribName2, attribValue2, ...) * * @throws IOException * @throws SAXException */ public final void writeElementWithData( String name, String data, Object... attributes ) throws IOException, SAXException { writeElement( false, name, attributes ); writeElementData( data ); } /** * Writes an XML element to the file and pushes one level down, so that succeeding elements become children of this. * * @param name the element's name * @param attributes the attributes (attribName1, attribValue1, attribName2, attribValue2, ...) * * @throws IOException * @throws SAXException */ public final void writeElementAndPush( String name, Object... attributes ) throws IOException, SAXException { writeElement( true, name, attributes ); } /** * Writes element data into the last started element. * * @param data * * @throws IOException * @throws SAXException */ protected void writeElementData( String data ) throws IOException, SAXException { String closeElementName = lastElementName; applyLastElement( false, false ); if ( closeElementName != null ) { this.closeElementName = closeElementName; indentCloseElem = false; } contentHandler.characters( data.toCharArray(), 0, data.length() ); } /** * Pops the level hierarchy one level up. * * @throws IOException * @throws SAXException */ public void popElement() throws IOException, SAXException { if ( path.getLevel() < 1 ) throw new IllegalStateException( "Cannot pop hierarchy" ); applyLastElement( true, true ); path.popPath(); closeElementName = path.getLastPathElement(); indentCloseElem = true; lastElementIndent = path.getLevel() - 1; applyLastElement( true, true ); if ( getLevel() > 0 ) lastElemPushed[getLevel() - 1] = false; } /** * * @throws IOException * @throws SAXException */ public void close() throws IOException, SAXException { if ( bw == null ) return; try { while ( path.getLevel() > 1 ) popElement(); applyLastElement( true, true ); contentHandler.endDocument(); } finally { try { bw.close(); } finally { bw = null; } } } /** * {@inheritDoc} */ @Override protected void finalize() throws Throwable { Throwable tt = null; try { super.finalize(); } catch ( Throwable t ) { tt = t; } close(); if ( tt != null ) throw tt; } /** * * @param out * @param codepage * @param charset * * @throws IOException */ protected SimpleXMLWriter( OutputStream out, String codepage, Charset charset ) throws IOException { if ( codepage != null ) charset = Charset.forName( codepage ); if ( charset == null ) charset = Charset.forName( "UTF-8" ); this.charset = charset; this.bw = new BufferedWriter( new OutputStreamWriter( out, charset ) ); } /** * Parses the given file. * * @param out * @param charset * * @throws IOException */ public SimpleXMLWriter( OutputStream out, String charset ) throws IOException { this( out, charset, null ); } /** * Parses the given file. * * @param out * @param charset * * @throws IOException */ public SimpleXMLWriter( OutputStream out, Charset charset ) throws IOException { this( out, null, charset ); } /** * Parses the given file. * * @param out * * @throws IOException */ public SimpleXMLWriter( OutputStream out ) throws IOException { this( out, null, null ); } /** * Parses the given file. * * @param file * @param charset * * @throws IOException */ public SimpleXMLWriter( File file, String charset ) throws IOException { this( new FileOutputStream( file ), charset, null ); } /** * Parses the given file. * * @param file * @param charset * * @throws IOException */ public SimpleXMLWriter( File file, Charset charset ) throws IOException { this( new FileOutputStream( file ), null, charset ); } /** * Parses the given file. * * @param file * * @throws IOException */ public SimpleXMLWriter( File file ) throws IOException { this( new FileOutputStream( file ), null, null ); } /** * Parses the given file. * * @param filename * @param charset * * @throws IOException */ public SimpleXMLWriter( String filename, String charset ) throws IOException { this( new FileOutputStream( filename ), charset, null ); } /** * Parses the given file. * * @param filename * @param charset * * @throws IOException */ public SimpleXMLWriter( String filename, Charset charset ) throws IOException { this( new FileOutputStream( filename ), null, charset ); } /** * Parses the given file. * * @param filename * * @throws IOException */ public SimpleXMLWriter( String filename ) throws IOException { this( new FileOutputStream( filename ), null, null ); } }