/*
* ModeShape (http://www.modeshape.org)
*
* 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 org.modeshape.common.xml;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.modeshape.common.SystemFailureException;
import org.modeshape.common.text.TextEncoder;
import org.modeshape.common.text.XmlValueEncoder;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Class that adapts an arbitrary, open {@link OutputStream} to the {@link ContentHandler} interface. SAX events invoked on this
* object will be translated into their corresponding XML text and written to the output stream.
*/
public class StreamingContentHandler extends DefaultHandler {
public static final String DEFAULT_ENCODING = "UTF-8";
/**
* Encoder to properly escape XML attribute values
*
* @see XmlValueEncoder
*/
private final TextEncoder VALUE_ENCODER = new XmlValueEncoder();
/**
* The list of XML namespaces that are predefined and should not be exported by the content handler.
*/
private final Collection<String> unexportableNamespaces;
/**
* The output stream to which the XML will be written
*/
private final OutputStreamWriter writer;
/**
* The XML namespace prefixes that are currently mapped
*/
private final Map<String, String> mappedPrefixes;
/**
* The XML declaration information written to the output.
*/
private final String declaration;
public StreamingContentHandler( OutputStream os ) {
this(os, Collections.<String>emptyList());
}
public StreamingContentHandler( OutputStream os,
Collection<String> unexportableNamespaces ) {
try {
this.writer = new OutputStreamWriter(os, DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
// This should never happen ...
throw new SystemFailureException(e);
}
this.unexportableNamespaces = unexportableNamespaces;
this.mappedPrefixes = new HashMap<String, String>();
this.declaration = "version=\"1.0\" encoding=\"" + DEFAULT_ENCODING + "\"";
}
public StreamingContentHandler( OutputStream os,
Collection<String> unexportableNamespaces,
String encoding ) throws UnsupportedEncodingException {
this.writer = new OutputStreamWriter(os, encoding);
this.unexportableNamespaces = unexportableNamespaces;
this.mappedPrefixes = new HashMap<String, String>();
if (encoding == null || encoding.length() == 0) encoding = "UTF-8";
this.declaration = "version=\"1.0\" encoding=\"" + encoding + "\"";
}
@Override
public void characters( char[] ch,
int start,
int length ) throws SAXException {
emit(VALUE_ENCODER.encode(new String(ch, start, length)));
}
@Override
public void startDocument() throws SAXException {
emit("<?xml " + declaration + "?>");
}
@Override
public void endDocument() throws SAXException {
try {
writer.flush();
} catch (IOException e) {
throw new SAXException(e);
}
}
@Override
public void startElement( String uri,
String localName,
String name,
Attributes attributes ) throws SAXException {
emit("<");
emit(name);
for (Map.Entry<String, String> mapping : mappedPrefixes.entrySet()) {
emit(" xmlns:");
emit(mapping.getKey());
emit("=\"");
emit(mapping.getValue());
emit("\"");
}
mappedPrefixes.clear();
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
emit(" ");
emit(attributes.getQName(i));
emit("=\"");
emit(VALUE_ENCODER.encode(attributes.getValue(i)));
emit("\"");
}
}
emit(">");
}
@Override
public void endElement( String uri,
String localName,
String name ) throws SAXException {
emit("</");
emit(name);
emit(">");
// System.out.println();
}
@Override
public void startPrefixMapping( String prefix,
String uri ) {
if (!unexportableNamespaces.contains(prefix)) {
mappedPrefixes.put(prefix, uri);
}
}
/**
* Writes the given text to the output stream for this {@link StreamingContentHandler}.
*
* @param text the text to output
* @throws SAXException if there is an error writing to the stream
*/
private void emit( String text ) throws SAXException {
try {
// System.out.print(text);
writer.write(text);
} catch (IOException ioe) {
throw new SAXException(ioe);
}
}
}