Java tutorial
/* * jcurl java curling software framework http://www.jcurl.org * Copyright (C) 2005-2009 M. Rohrmoser * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program 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 General * Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.jcurl.core.helpers; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.Iterator; import java.util.List; import java.util.Stack; import org.apache.commons.logging.Log; import org.jcurl.core.log.JCLoggerFactory; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; /** * A very leightweight, quick and simple xml serializer implementing a SAX * content-handler to <b>write to a stream </b> or writer. Can handle changing * default namespaces. Checks well-formedness. Does not support smart namespace * prefix handling - which it should. * * <p> * For Namespace support evtl. use * http://www.jcurl.org/api/com/megginson/sax/XMLWriter.html * </p> * * @author <a href="mailto:m@jcurl.org">M. Rohrmoser </a> * @version $Id:XmlSimpleWriter.java 8046 2006-02-27 17:03:05Z rohrmoser $ */ public class XmlSimpleWriter extends DefaultHandler { private static final Log log = JCLoggerFactory.getLogger(XmlSimpleWriter.class); private static void checkAttName(final String qName) throws SAXException { if (qName == null || "".equals(qName)) throw new SAXException("empty element name not allowed!"); } /** * Check if qName contains only valid characters. * * @see <a * href="http://edition-w3c.de/TR/2000/REC-xml-20001006/#NT-Name">XML * Spec </a> * @param qName * @throws SAXException */ private static void checkQName(final CharSequence qName) throws SAXException { if (qName == null || qName.length() == 0) throw new SAXException("empty element name not allowed!"); } public static String getCurrentXPath(final List elemStack) { final StringBuilder buf = new StringBuilder(); for (final Iterator it = elemStack.iterator(); it.hasNext();) buf.append('/').append((String) it.next()); return buf.toString(); } private static final void writeEncoded(final char ch, final Writer target) throws SAXException { switch (ch) { case '&': writePlain("&", target); break; case '<': writePlain("<", target); break; case '>': writePlain(">", target); break; case '"': writePlain(""", target); break; case '\'': writePlain("'", target); break; default: writePlain(ch, target); break; } } public static void writeEncoded(final CharSequence src, final Writer target) throws SAXException { final int len = src.length(); for (int i = 0; i < len; i++) writeEncoded(src.charAt(i), target); } private static final void writePlain(final char s, final Writer target) throws SAXException { try { target.write(s); } catch (final IOException e) { throw new SAXException(e); } } private static final void writePlain(final String src, final Writer target) throws SAXException { try { target.write(src); } catch (final IOException e) { throw new SAXException(e); } } private final Stack elemStack = new Stack(); private final String encoding; // private Locator locator = null; private String pendingPrefixStart = null; private String pendingUriStart = null; private final Writer target; /** * Create an xml serializer to a stream + encoding. * * @param dst * stream to write to * @param encoding * @param indent * simple pretty printing * @throws UnsupportedEncodingException */ public XmlSimpleWriter(final OutputStream dst, final String encoding, final boolean indent) throws UnsupportedEncodingException { target = new OutputStreamWriter(dst, encoding); this.encoding = encoding; } /** * Create an xml serializer to a writer. * * @param target */ public XmlSimpleWriter(final Writer target) { this.target = target; encoding = null; } /** * Write a bunch of characters (encoded) to the stream. * * @see org.xml.sax.ContentHandler#characters(char[], int, int) */ @Override public void characters(final char[] ch, final int start, final int length) throws SAXException { for (int i = start; i < start + length; i++) writeEncoded(ch[i], target); } public void comment(final CharSequence s) throws SAXException { writePlain("<!-- ", target); writeEncoded(s, target); writePlain(" -->\n", target); } /** * flush the stream. * * @see org.xml.sax.ContentHandler#endDocument() */ @Override public void endDocument() throws SAXException { if (elemStack.size() != 0) throw new SAXException("Unclosed elements pending."); try { target.flush(); } catch (final IOException e) { throw new SAXException("Error flushing xml writer.", e); } } /** * Write the element end tag. Checks for a matching qname, but should check * the namespace as well. * * @see org.xml.sax.ContentHandler#endElement(java.lang.String, * java.lang.String, java.lang.String) */ @Override public void endElement(final String namespaceURI, final String localName, String qName) throws SAXException { final String recent = (String) elemStack.pop(); if (qName == null && localName != null) qName = localName; if (!recent.equals(qName)) throw new SAXException("Cannot write non-wellformed stuff."); writePlain("</", target); writePlain(qName, target); writePlain('>', target); } /** * Should remove a prefix mapping for following child nodes. Does nothing * right now. * * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String) */ @Override public void endPrefixMapping(final String prefix) throws SAXException { // TODO keep track of current mappings // TODO allow multiple changes at once ; } /** * Make errors catastrophic and throw the given Exception * * @param e * @throws SAXParseException */ @Override public void error(final SAXParseException e) throws SAXParseException { log.error("parse exception", e); throw e; } /** * Make errors catastrophic and throw the given Exception * * @param e * @throws SAXParseException */ @Override public void fatalError(final SAXParseException e) throws SAXParseException { log.error("parse exception", e); throw e; } public Writer getWriter() { return target; } /** * Write whitespace that could be omitted (and is if in "indent" mode). * * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int) */ @Override public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException { characters(ch, start, length); } /** * Write a processing instruction. * * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, * java.lang.String) */ @Override public void processingInstruction(final String target, final String data) throws SAXException { writePlain("<?", this.target); writePlain(target, this.target); if (data != null) { writePlain(' ', this.target); writePlain(data, this.target); } writePlain("?>", this.target); } /** * Not supported. * * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator) */ @Override public void setDocumentLocator(final Locator locator) { ;// this.locator = locator; } /** * Not supported. * * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String) */ @Override public void skippedEntity(final String name) throws SAXException { throw new UnsupportedOperationException("Not supported."); } /** * Reset the internal state and write the xml processing instruction * (including encoding if known). * * @see org.xml.sax.ContentHandler#startDocument() */ @Override public void startDocument() throws SAXException { elemStack.clear(); String data = "version=\"1.0\""; if (encoding != null) data += " encoding=\"" + encoding + "\""; processingInstruction("xml", data); } /** * Write an element start tag and it's attributes. Correctly handles * changing default namespaces. * * @see org.xml.sax.ContentHandler#startElement(java.lang.String, * java.lang.String, java.lang.String, org.xml.sax.Attributes) */ @Override public void startElement(final String namespaceURI, final String localName, String qName, final Attributes atts) throws SAXException { if (qName == null && localName != null) qName = localName; checkQName(qName); writePlain('<', target); writePlain(qName, target); // handle namespace changes if (pendingPrefixStart != null || pendingUriStart != null) { writePlain(" xmlns", target); if (pendingPrefixStart != null) { writePlain(':', target); writePlain(pendingPrefixStart, target); } writePlain("=\"", target); if (pendingUriStart != null) writeEncoded(pendingUriStart, target); writePlain("\"", target); pendingPrefixStart = pendingUriStart = null; } // TODO keep track of changing namespace prefixes // TODO handle attribute namespaces right if (atts != null) { final int len = atts.getLength(); for (int i = 0; i < len; i++) { final String val = atts.getValue(i); String name = atts.getQName(i); if (name == null && atts.getLocalName(i) != null) name = atts.getLocalName(i); checkAttName(name); if (val == null) log.warn("Attribute [" + getCurrentXPath(elemStack) + "/" + qName + "/@" + name + "] is skipped because it has the value null"); else { writePlain(' ', target); writePlain(name, target); writePlain("=\"", target); writeEncoded(val, target); writePlain("\"", target); } } } writePlain(">", target); elemStack.push(qName); } /** * Begin a prefix mapping. Only emtpy prefixes are supported right now. * * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, * java.lang.String) */ @Override public void startPrefixMapping(final String prefix, final String uri) throws SAXException { if ("xml".equals(prefix) && "http://www.w3.org/XML/1998/namespace".equals(uri)) return; if ("xmlns".equals(prefix) && "http://www.w3.org/2000/xmlns/".equals(uri)) return; if (prefix != null) throw new UnsupportedOperationException("Not implemented yet."); // TODO allow multiple changes at once pendingPrefixStart = prefix; pendingUriStart = uri; } /** * Do nothing. * * @param e */ @Override public void warning(final SAXParseException e) { log.debug("parse exception", e); } }