/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. 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 jCoderZ.org Project 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 REGENTS 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 REGENTS AND 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) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jcoderz.commons.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import javax.xml.transform.stream.StreamResult; import org.xml.sax.Attributes; /** * This class holds utility methods for common xml topics. * * @author Andreas Mandel */ public final class XmlUtil { /** * Immutable and always empty version of a * {@link org.xml.sax.Attributes} object. */ public static final Attributes EMPTY_ATTRIBUTES = new EmptyAttribute(); private static final int INDENT = 2; private static final String SPACES = " "; /** No instances. */ private XmlUtil () { // No instances. } /** * Immutable implementation of a empty attribute list. * @see Attributes */ private static final class EmptyAttribute implements Attributes { /** {@inheritDoc} */ public int getLength () { return 0; } /** {@inheritDoc} */ public String getLocalName (int index) { return null; } /** {@inheritDoc} */ public String getQName (int index) { return null; } /** {@inheritDoc} */ public String getType (int index) { return null; } /** {@inheritDoc} */ public String getURI (int index) { return null; } /** {@inheritDoc} */ public String getValue (int index) { return null; } /** {@inheritDoc} */ public int getIndex (String qName) { return -1; } /** {@inheritDoc} */ public String getType (String qName) { return null; } /** {@inheritDoc} */ public String getValue (String qName) { return null; } /** {@inheritDoc} */ public int getIndex (String uri, String localName) { return -1; } /** {@inheritDoc} */ public String getType (String uri, String localName) { return null; } /** {@inheritDoc} */ public String getValue (String uri, String localName) { return null; } } /** * Encode a string so that it can be safely used as attribute value in * XML output. * @param attribute the attribute value to be encoded. * @return a string representing the attribute value that can be safely * used in XML output. */ public static String attributeEscape (String attribute) { final StringBuffer sb = new StringBuffer(); if (attribute != null) { char c; final int l = attribute.length(); for (int i = 0; i < l; i++) { c = attribute.charAt(i); switch (c) { case '<': sb.append("<"); break; case '>': sb.append(">"); break; case '\'': sb.append("'"); break; case '"': sb.append("""); break; case '&': sb.append("&"); break; default : if (c > Byte.MAX_VALUE || Character.isISOControl(c)) { sb.append("&#x"); sb.append(Integer.toHexString(c)); sb.append(';'); } else { sb.append(c); } } } } return sb.toString(); } /** * Encode a string so that it can be safely used as text in an element * for XML output. * @param text the element text body. * @return a string so that it can be safely used as text in an element * for XML output. */ public static String escape (String text) { final StringBuffer sb = new StringBuffer(); if (text != null) { char c; final int l = text.length(); for (int i = 0; i < l; i++) { c = text.charAt(i); switch (c) { case '<': sb.append("<"); break; case '>': // only needed to avoid ]]> sb.append(">"); break; case '&': sb.append("&"); break; default : if (c > Byte.MAX_VALUE) { sb.append("&#x"); sb.append(Integer.toHexString(c)); sb.append(';'); } else { sb.append(c); } } } } return sb.toString(); } /** * Simple xml formatter. * This code might fail for several input. In this case the * original input is returned. * @param org the input to be formated. * @return the input in xml formated (human readable) form or the * input string. */ public static String formatXml (String org) { String result = org; try { final String in = org.trim(); if (in.charAt(0) == '<') // && sb.charAt(1) == '?') { final StringBuffer sb = new StringBuffer(); boolean nestedTag = false; int indent = 0; for (int t = 0; t < in.length(); t++) { char c = in.charAt(t); switch (c) { case '<': t++; c = in.charAt(t); switch (c) { case '/': if (!nestedTag) { indent -= INDENT; sb.append("</"); } else { sb.append('\n'); indent -= INDENT; indent(indent, sb); sb.append("</"); } nestedTag = true; break; case '?': case '!': if (t != 1) { sb.append('\n'); } sb.append('<'); sb.append(c); break; default: nestedTag = false; if (sb.length() > 0) { sb.append('\n'); } indent(indent, sb); sb.append('<'); sb.append(c); indent += INDENT; break; } break; case '/': sb.append(c); if (in.charAt(t + 1) == '>') { indent -= INDENT; nestedTag = true; } break; case '\n': case '\r': break; case '>': default: sb.append(c); break; } } result = sb.toString(); } } catch (Exception ex) { result = org; // CHECKME: Nicer exception handling no formated output... } return result; } /** * Creae a stream result based on a File. * Other than the default implementation not only the File is used to * initialize the result, but also a Stream is opened and initialized. * This works around an issue with whitespaces in the pathname which * otherwise would lead to a file not found exception * <pre> * Error during transformation: javax.xml.transform.TransformerException: java.io.FileNotFoundException: ...%20... * at org.apache.xalan.transformer.TransformerImpl.createSerializationHandler(TransformerImpl.java:1218) * at org.apache.xalan.transformer.TransformerImpl.createSerializationHandler(TransformerImpl.java:1060) * at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1268) * at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1251) * .... * </pre>. * The caller must ensure that the created outputstream is closed. * @param outFile the file to be used in the stream result. * @return a new StreamResult piontint to the given File. * @throws IOException in case of an issue while creating the stream. */ public static StreamResult createStreamResult(File outFile) throws IOException { final StreamResult result = new StreamResult(outFile); // set the stream directly to avoid issues with blanks in the // filename. result.setOutputStream(new FileOutputStream(outFile)); return result; } private static void indent (final int i, StringBuffer b) { if (i > SPACES.length()) { b.append(SPACES); indent(i - SPACES.length(), b); } else { b.append(SPACES.substring(0, i)); } } }