001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.common.xml; 017 018import java.io.IOException; 019import java.io.OutputStream; 020import java.io.OutputStreamWriter; 021import java.io.UnsupportedEncodingException; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.Map; 026import org.modeshape.common.SystemFailureException; 027import org.modeshape.common.text.TextEncoder; 028import org.modeshape.common.text.XmlValueEncoder; 029import org.xml.sax.Attributes; 030import org.xml.sax.ContentHandler; 031import org.xml.sax.SAXException; 032import org.xml.sax.helpers.DefaultHandler; 033 034/** 035 * Class that adapts an arbitrary, open {@link OutputStream} to the {@link ContentHandler} interface. SAX events invoked on this 036 * object will be translated into their corresponding XML text and written to the output stream. 037 */ 038public class StreamingContentHandler extends DefaultHandler { 039 040 public static final String DEFAULT_ENCODING = "UTF-8"; 041 042 /** 043 * Encoder to properly escape XML attribute values 044 * 045 * @see XmlValueEncoder 046 */ 047 private final TextEncoder VALUE_ENCODER = new XmlValueEncoder(); 048 049 /** 050 * The list of XML namespaces that are predefined and should not be exported by the content handler. 051 */ 052 private final Collection<String> unexportableNamespaces; 053 054 /** 055 * The output stream to which the XML will be written 056 */ 057 private final OutputStreamWriter writer; 058 059 /** 060 * The XML namespace prefixes that are currently mapped 061 */ 062 private final Map<String, String> mappedPrefixes; 063 064 /** 065 * The XML declaration information written to the output. 066 */ 067 private final String declaration; 068 069 public StreamingContentHandler( OutputStream os ) { 070 this(os, Collections.<String>emptyList()); 071 } 072 073 public StreamingContentHandler( OutputStream os, 074 Collection<String> unexportableNamespaces ) { 075 try { 076 this.writer = new OutputStreamWriter(os, DEFAULT_ENCODING); 077 } catch (UnsupportedEncodingException e) { 078 // This should never happen ... 079 throw new SystemFailureException(e); 080 } 081 this.unexportableNamespaces = unexportableNamespaces; 082 this.mappedPrefixes = new HashMap<String, String>(); 083 this.declaration = "version=\"1.0\" encoding=\"" + DEFAULT_ENCODING + "\""; 084 } 085 086 public StreamingContentHandler( OutputStream os, 087 Collection<String> unexportableNamespaces, 088 String encoding ) throws UnsupportedEncodingException { 089 this.writer = new OutputStreamWriter(os, encoding); 090 this.unexportableNamespaces = unexportableNamespaces; 091 this.mappedPrefixes = new HashMap<String, String>(); 092 if (encoding == null || encoding.length() == 0) encoding = "UTF-8"; 093 this.declaration = "version=\"1.0\" encoding=\"" + encoding + "\""; 094 } 095 096 @Override 097 public void characters( char[] ch, 098 int start, 099 int length ) throws SAXException { 100 emit(VALUE_ENCODER.encode(new String(ch, start, length))); 101 } 102 103 @Override 104 public void startDocument() throws SAXException { 105 emit("<?xml " + declaration + "?>"); 106 } 107 108 @Override 109 public void endDocument() throws SAXException { 110 try { 111 writer.flush(); 112 } catch (IOException e) { 113 throw new SAXException(e); 114 } 115 } 116 117 @Override 118 public void startElement( String uri, 119 String localName, 120 String name, 121 Attributes attributes ) throws SAXException { 122 emit("<"); 123 emit(name); 124 125 for (Map.Entry<String, String> mapping : mappedPrefixes.entrySet()) { 126 emit(" xmlns:"); 127 emit(mapping.getKey()); 128 emit("=\""); 129 emit(mapping.getValue()); 130 emit("\""); 131 } 132 133 mappedPrefixes.clear(); 134 135 if (attributes != null) { 136 for (int i = 0; i < attributes.getLength(); i++) { 137 emit(" "); 138 emit(attributes.getQName(i)); 139 emit("=\""); 140 emit(VALUE_ENCODER.encode(attributes.getValue(i))); 141 emit("\""); 142 } 143 } 144 145 emit(">"); 146 } 147 148 @Override 149 public void endElement( String uri, 150 String localName, 151 String name ) throws SAXException { 152 emit("</"); 153 emit(name); 154 emit(">"); 155 // System.out.println(); 156 157 } 158 159 @Override 160 public void startPrefixMapping( String prefix, 161 String uri ) { 162 if (!unexportableNamespaces.contains(prefix)) { 163 mappedPrefixes.put(prefix, uri); 164 } 165 } 166 167 /** 168 * Writes the given text to the output stream for this {@link StreamingContentHandler}. 169 * 170 * @param text the text to output 171 * @throws SAXException if there is an error writing to the stream 172 */ 173 private void emit( String text ) throws SAXException { 174 175 try { 176 // System.out.print(text); 177 writer.write(text); 178 } catch (IOException ioe) { 179 throw new SAXException(ioe); 180 } 181 } 182}