/** * Copyright 2007-2010 University Of Southern California * * Licensed 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 edu.isi.pegasus.common.util; import java.io.Writer; import java.io.StringWriter; import java.io.IOException; import java.text.StringCharacterIterator; /** * This abstract class defines a common base for certain classes that * deal with the generation of XML files. Historically, this class also * dealt with text generation, but those methods have been mostly * removed. * * @author Jens-S. Vöckler * @version $Revision$ */ public abstract class XMLOutput { /** * Escapes certain characters inappropriate for textual output. * * Since this method does not hurt, and may be useful in other * regards, it will be retained for now. * * @param original is a string that needs to be quoted * @return a string that is "safe" to print. */ static public String escape( String original ) { if ( original == null ) return null; StringBuilder result = new StringBuilder( 2 * original.length() ); StringCharacterIterator i = new StringCharacterIterator(original); for ( char ch = i.first(); ch != StringCharacterIterator.DONE; ch = i.next() ) { if ( ch == '\r' ) { result.append("\\r"); } else if ( ch == '\n' ) { result.append("\\n"); } else if ( ch == '\t' ) { result.append("\\t"); } else { // DO NOT escape apostrophe. If apostrophe escaping is required, // do it beforehand. if ( ch == '\"' || ch == '\\' ) result.append('\\'); result.append(ch); } } return result.toString(); } /** * Escapes certain characters inappropriate for XML content output. * * According to the * <a href="http://www.w3.org/TR/2008/REC-xml-20081126/#NT-AttValue">XML * 1.0 Specification</a>, an attribute cannot contain ampersand, percent, * nor the character that was used to quote the attribute value. * * @param original is a string that needs to be quoted * @param isAttribute denotes an attributes value, if set to true. * If false, it denotes regular XML content outside of attributes. * @return a string that is "safe" to print as XML. */ static public String quote( String original, boolean isAttribute ) { if ( original == null ) return null; StringBuilder result = new StringBuilder( 2 * original.length() ); StringCharacterIterator i = new StringCharacterIterator(original); for ( char ch = i.first(); ch != StringCharacterIterator.DONE; ch = i.next() ) { switch (ch) { case '<': if ( isAttribute ) result.append("<"); else result.append("<"); break; case '&': if ( isAttribute ) result.append("&"); else result.append("&"); break; case '>': // greater-than does not need to be attribute-escaped, // but standard does not say we must not do it, either. if ( isAttribute ) result.append(">"); else result.append(">"); break; case '\'': if ( isAttribute ) result.append("'"); else result.append("'"); break; case '\"': if ( isAttribute ) result.append("""); else result.append("""); break; default: result.append(ch); break; } } return result.toString(); } /** * XML write helper method writes a quoted attribute onto a stream. * The terminating quote will be appended automatically. Values will * be XML-escaped. No action will be taken, if the value is null. * * @param stream is the stream to append to * @param key is the attribute including initial space, attribute name, * equals sign, and opening quote. The string passed as key must never * be <code>null</code>. * @param value is a string value, which will be put within the quotes * and which will be escaped. If the value is null, no action will be * taken * @throws IOException for stream errors. */ public void writeAttribute( Writer stream, String key, String value ) throws IOException { if ( value != null ) { stream.write( key ); stream.write( quote(value,true) ); stream.write( '"' ); } } /** * Saner XML write helper method writes a quoted attribute onto a * stream. The value will be put properly into quotes. Values will be * XML-escaped. No action will be taken, if the key or value are * <code>null</code>. * * @param stream is the stream to append to * @param key is the attribute identifier, and just that. * @param value is a string value, which will be put within the quotes * and which will be escaped. If the value is null, no action will be * taken. * @throws IOException for stream errors. */ public void writeAttribute2( Writer stream, String key, String value ) throws IOException { if ( key != null && value != null ) { stream.write( key ); stream.write( "=\"" ); stream.write( quote(value,true) ); stream.write( '"' ); } } /** * Dumps the state of the current element as XML output. This function * traverses all sibling classes as necessary, and converts the data * into pretty-printed XML output.<p> * * Sibling classes which represent small leaf objects, and can return * the necessary data more efficiently, are encouraged to overwrite * this method. * * @param indent is a <code>String</code> of spaces used for pretty * printing. The initial amount of spaces should be an empty string. * The parameter is used internally for the recursive traversal. * If null, avoidable whitespaces in the output will be avoided. * @param namespace is the XML schema namespace prefix. If neither * empty nor null, each element will be prefixed with this prefix, * and the root element will map the XML namespace. * @return a String which contains the state of the current class and * its siblings using XML. Note that these strings might become large. * @throws IOException when encountering an error constructing the * string. */ public String toXML( String indent, String namespace ) throws IOException { StringWriter sw = new StringWriter(); this.toXML( sw, indent, namespace ); sw.flush(); return sw.toString(); } /** * Convenience adaptor method invoking the equivalent of: * <pre> * toXML( stream, indent, (String) null ); * </pre> * * @param stream is a stream opened and ready for writing. This can also * be a string stream for efficient output. * @param indent is a <code>String</code> of spaces used for pretty * printing. The initial amount of spaces should be an empty string. * The parameter is used internally for the recursive traversal. * If a <code>null</code> value is specified, no indentation nor * linefeeds will be generated. * @throws IOException if something fishy happens to the stream. * @see #toXML( Writer, String, String ) */ public void toXML( Writer stream, String indent ) throws IOException { toXML( stream, indent, (String) null ); } /** * Dump the state of the current element as XML output. This function * traverses all sibling classes as necessary, and converts the data * into pretty-printed XML output. The stream interface should be able * to handle large output efficiently, if you use a <b>buffered</b> * writer. * * @param stream is a stream opened and ready for writing. This can also * be a string stream for efficient output. * @param indent is a <code>String</code> of spaces used for pretty * printing. The initial amount of spaces should be an empty string. * The parameter is used internally for the recursive traversal. * If a <code>null</code> value is specified, no indentation nor * linefeeds will be generated. * @param namespace is the XML schema namespace prefix. If neither * empty nor null, each element will be prefixed with this prefix, and * the root element will map the XML namespace. Use <code>null</code>, * if you do not need an XML namespace. * @throws IOException if something fishy happens to the stream. * @see java.io.BufferedWriter */ public abstract void toXML( Writer stream, String indent, String namespace ) throws IOException; }