/*
* Bibliothek - DockingFrames
* Library built on Java/Swing, allows the user to "drag and drop"
* panels containing any Swing-Component the developer likes to add.
*
* Copyright (C) 2007 Benjamin Sigg
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Benjamin Sigg
* benjamin_sigg@gmx.ch
* CH - Switzerland
*/
package bibliothek.util.xml;
import java.io.*;
import java.util.LinkedList;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Class used to read and write xml-files. Clients should use
* {@link #writeUTF(XElement, OutputStream)} and {@link #readUTF(InputStream)} to
* guarantee maximal independence from the underlying file system.
* @author Benjamin Sigg
*/
public class XIO {
/**
* Writes the contents of <code>element</code> into <code>out</code>.
* @param element the element to write
* @param out the stream to write into
* @throws IOException if an I/O-error occurs
*/
public static void write( XElement element, Appendable out ) throws IOException{
out.append( "<?xml version='1.0'?>\n" );
write( element, 0, out );
}
/**
* Writes the contents of <code>element</code> into <code>out</code> using
* the <code>UTF-8</code> encoding.
* @param element the element to write
* @param out the stream to write into
* @throws IOException if an I/O-error occurs
*/
public static void writeUTF( XElement element, OutputStream out ) throws IOException{
write( element, out, "UTF-8" );
}
/**
* Writes the contents of <code>element</code> into <code>out</code>.
* @param element the element to write
* @param out the stream to write into
* @param encoding the encoding for the characters
* @throws IOException if an I/O-error occurs
*/
public static void write( XElement element, OutputStream out, String encoding ) throws IOException{
OutputStreamWriter writer = new OutputStreamWriter( out, encoding ){
@Override
public void close() throws IOException {
// ignore
}
};
write( element, writer );
writer.flush();
}
/**
* Reads a xml file provided from <code>in</code> using <code>UTF-8</code>
* as encoding.
* @param in the stream to read from
* @return the root element
* @throws IOException if an I/O-error occurs
*/
public static XElement readUTF( InputStream in ) throws IOException{
return read( in, "UTF-8" );
}
/**
* Reads a xml file provided from <code>in</code> using <code>encoding</code>
* as encoding.
* @param in the stream to read from
* @param encoding the encoding used to decode characters
* @return the root element
* @throws IOException if an I/O-error occurs
*/
public static XElement read( InputStream in, String encoding ) throws IOException{
InputStreamReader reader = new InputStreamReader( in, encoding ){
@Override
public void close() throws IOException {
// ignore
}
};
return read( new InputSource( reader ));
}
/**
* Writes the contents of <code>element</code> into <code>out</code>.
* @param element the element to write
* @param tabs the number of tabs before the element
* @param out the stream to write into
* @throws IOException if an I/O-error occurs
*/
private static void write( XElement element, int tabs, Appendable out ) throws IOException{
for( int i = 0; i < tabs; i++ )
out.append( "\t" );
out.append( "<" );
out.append( element.getName() );
for( XAttribute attribute : element.attributes() ){
out.append( " " );
out.append( attribute.getName() );
out.append( "=\"" );
encode( attribute.getString(), out );
out.append( "\"" );
}
String value = element.getValue();
XElement[] children = element.children();
if( value.length() == 0 && children.length == 0 ){
out.append( "/>" );
}
else{
out.append( ">" );
if( value.length() > 0 ){
if( children.length > 0 ){
out.append( "\n\t" );
for( int i = 0; i < tabs; i++ )
out.append( "\t" );
}
encode( value, out );
}
if( children.length > 0 ){
out.append( "\n" );
for( XElement child : children ){
write( child, tabs+1, out );
out.append( "\n" );
}
for( int i = 0; i < tabs; i++ )
out.append( "\t" );
}
out.append( "</" );
out.append( element.getName() );
out.append( ">" );
}
}
/**
* Encodes <code>value</code> such that it is a valid string in a xml-file.
* @param value the value to encode
* @param out the stream to write into
*/
private static void encode( String value, Appendable out ) throws IOException{
for( int i = 0, n = value.length(); i<n; i++ ){
char c = value.charAt( i );
switch( c ){
case '<':
out.append( "<" );
break;
case '>':
out.append( ">" );
break;
case '\'':
out.append( "'" );
break;
case '"':
out.append( """ );
break;
case '&':
out.append( "&" );
break;
default:
out.append( c );
break;
}
}
}
/**
* Interprets <code>text</code> as a xml-file and reads it.
* @param text the content to read, in xml format
* @return the root element of <code>text</code>
* @throws IOException if an I/O-error occurs
*/
public static XElement read( CharSequence text ) throws IOException{
InputSource in = new InputSource();
in.setCharacterStream( new StringReader( text.toString() ) );
return read( in );
}
/**
* Reads a xml-file from <code>source</code>.
* @param source the xml-file
* @return the root element of the file.
* @throws IOException if an I/O-error occurs
*/
public static XElement read( InputSource source ) throws IOException{
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
Handler handler = new Handler();
parser.parse( source, handler );
return handler.getElement();
}
catch( ParserConfigurationException e ) {
throw new IOException( e.getMessage() );
}
catch( SAXException e ) {
throw new IOException( e.getMessage() );
}
}
/**
* Reads a xml file provided from <code>reader</code>.
* @param reader the reader from which characters will be read
* @return the root element
* @throws IOException if an I/O-error occurs
*/
public static XElement read( Reader reader ) throws IOException{
return read( new InputSource( reader ));
}
/**
* A handler used to read from a {@link SAXParser}.
* @author Benjamin Sigg
*/
private static class Handler extends DefaultHandler{
/** the first element that was read */
private XElement element;
/** the current stack of active entries */
private LinkedList<XElement> stack = new LinkedList<XElement>();
/**
* Gets the first element that was read.
* @return the first element
*/
public XElement getElement() {
return element;
}
@Override
public void startElement( String uri, String localName, String name,
Attributes attributes ) throws SAXException {
XElement element = new XElement( name );
if( this.element == null ){
this.element = element;
}
else{
stack.getFirst().addElement( element );
}
stack.addFirst( element );
// read the attributes
for( int i = 0, n = attributes.getLength(); i<n; i++ ){
XAttribute attr = new XAttribute( attributes.getQName( i ));
attr.setString( attributes.getValue( i ));
element.addAttribute( attr );
}
}
@Override
public void characters( char[] ch, int start, int length ) throws SAXException {
if( length > 0 ){
String value = new String( ch, start, length );
String old = stack.getFirst().getValue();
if( old != null && old.length() > 0 ){
value = old + value;
}
stack.getFirst().setValue( value );
}
}
@Override
public void endElement( String uri, String localName, String name )
throws SAXException {
XElement element = stack.removeFirst();
element.setValue( element.getString().trim() );
}
}
}