001: package net.sf.saxon.event;
002:
003: import net.sf.saxon.Configuration;
004: import net.sf.saxon.Controller;
005: import net.sf.saxon.charcode.CharacterSet;
006: import net.sf.saxon.charcode.CharacterSetFactory;
007: import net.sf.saxon.charcode.PluggableCharacterSet;
008: import net.sf.saxon.charcode.UnicodeCharacterSet;
009: import net.sf.saxon.om.NamePool;
010: import net.sf.saxon.trans.DynamicError;
011: import net.sf.saxon.trans.XPathException;
012: import org.xml.sax.ContentHandler;
013:
014: import javax.xml.transform.OutputKeys;
015: import javax.xml.transform.Result;
016: import javax.xml.transform.TransformerException;
017: import javax.xml.transform.stream.StreamResult;
018: import java.io.*;
019: import java.net.URI;
020: import java.net.URISyntaxException;
021: import java.util.Properties;
022:
023: /**
024: * Emitter: This abstract class defines methods that must be implemented by
025: * components that format SAXON output. There is one emitter for XML,
026: * one for HTML, and so on. Additional methods are concerned with
027: * setting options and providing a Writer.<p>
028: *
029: * The interface is deliberately designed to be as close as possible to the
030: * standard SAX2 ContentHandler interface, however, it allows additional
031: * information to be made available.
032: *
033: * An Emitter is a Receiver, specifically it is a Receiver that can direct output
034: * to a Writer or OutputStream, using serialization properties defined in a Properties
035: * object.
036: */
037:
038: public abstract class Emitter implements Result, Receiver {
039: protected PipelineConfiguration pipelineConfig;
040: protected NamePool namePool;
041: protected String systemId;
042: protected StreamResult streamResult;
043: protected Writer writer;
044: protected OutputStream outputStream;
045: protected Properties outputProperties;
046: protected CharacterSet characterSet = null;
047:
048: /**
049: * Set the namePool in which all name codes can be found
050: */
051:
052: public void setPipelineConfiguration(PipelineConfiguration pipe) {
053: this .pipelineConfig = pipe;
054: this .namePool = pipe.getConfiguration().getNamePool();
055: }
056:
057: /**
058: * Get the pipeline configuration used for this document
059: */
060:
061: public PipelineConfiguration getPipelineConfiguration() {
062: return pipelineConfig;
063: }
064:
065: /**
066: * Get the configuration used for this document
067: */
068:
069: public Configuration getConfiguration() {
070: return pipelineConfig.getConfiguration();
071: }
072:
073: /**
074: * Set the System ID
075: */
076:
077: public void setSystemId(String systemId) {
078: this .systemId = systemId;
079: }
080:
081: /**
082: * Get the System ID
083: */
084:
085: public String getSystemId() {
086: return systemId;
087: }
088:
089: /**
090: * Set output properties
091: */
092:
093: public void setOutputProperties(Properties details)
094: throws XPathException {
095: if (characterSet == null) {
096: characterSet = CharacterSetFactory.getCharacterSet(details,
097: getPipelineConfiguration().getController());
098: }
099: outputProperties = details;
100: }
101:
102: /**
103: * Get the output properties
104: */
105:
106: public Properties getOutputProperties() {
107: return outputProperties;
108: }
109:
110: /**
111: * Set the StreamResult acting as the output destination of the Emitter
112: */
113:
114: public void setStreamResult(StreamResult result)
115: throws XPathException {
116: this .streamResult = result;
117: }
118:
119: /**
120: * Make a Writer for this Emitter to use, given a StreamResult
121: */
122:
123: protected void makeWriter() throws XPathException {
124: if (writer != null) {
125: return;
126: }
127: if (streamResult == null) {
128: throw new IllegalStateException(
129: "Emitter must have either a Writer or a StreamResult to write to");
130: }
131: writer = streamResult.getWriter();
132: if (writer == null) {
133: OutputStream os = streamResult.getOutputStream();
134: if (os != null) {
135: setOutputStream(os);
136: }
137: }
138: if (writer == null) {
139: String uri = streamResult.getSystemId();
140: try {
141: File file = new File(new URI(uri));
142: setOutputStream(new FileOutputStream(file));
143: // Set the outputstream in the StreamResult object so that the
144: // call on OutputURIResolver.close() can close it
145: streamResult.setOutputStream(outputStream);
146: } catch (FileNotFoundException fnf) {
147: throw new DynamicError(fnf);
148: } catch (URISyntaxException use) {
149: throw new DynamicError(use);
150: }
151: }
152: }
153:
154: /**
155: * Determine whether the Emitter wants a Writer for character output or
156: * an OutputStream for binary output. The standard Emitters all use a Writer, so
157: * this returns true; but a subclass can override this if it wants to use an OutputStream
158: */
159:
160: public boolean usesWriter() {
161: return true;
162: }
163:
164: /**
165: * Set the output destination as a character stream
166: */
167:
168: public void setWriter(Writer writer) {
169: this .writer = writer;
170:
171: // If the writer uses a known encoding, change the encoding in the XML declaration
172: // to match. Any encoding actually specified in xsl:output is ignored, because encoding
173: // is being done by the user-supplied Writer, and not by Saxon itself.
174:
175: if (writer instanceof OutputStreamWriter
176: && outputProperties != null) {
177: String enc = ((OutputStreamWriter) writer).getEncoding();
178: outputProperties.put(OutputKeys.ENCODING, enc);
179: }
180: }
181:
182: /**
183: * Get the output writer
184: */
185:
186: public Writer getWriter() {
187: return writer;
188: }
189:
190: /**
191: * Set the output destination as a byte stream
192: */
193:
194: public void setOutputStream(OutputStream stream)
195: throws XPathException {
196: this .outputStream = stream;
197:
198: // If the user supplied an OutputStream, but the Emitter is written to
199: // use a Writer (this is the most common case), then we create a Writer
200: // to wrap the supplied OutputStream; the complications are to ensure that
201: // the character encoding is correct.
202:
203: if (usesWriter()) {
204:
205: //CharacterSet charSet = CharacterSetFactory.getCharacterSet(outputProperties);
206:
207: String encoding = outputProperties
208: .getProperty(OutputKeys.ENCODING);
209: if (encoding == null)
210: encoding = "UTF8";
211: if (encoding.equalsIgnoreCase("UTF-8"))
212: encoding = "UTF8";
213: // needed for Microsoft Java VM
214:
215: if (characterSet instanceof PluggableCharacterSet) {
216: encoding = ((PluggableCharacterSet) characterSet)
217: .getEncodingName();
218: }
219:
220: while (true) {
221: try {
222: String javaEncoding = encoding;
223: if (encoding.equalsIgnoreCase("iso-646")
224: || encoding.equalsIgnoreCase("iso646")) {
225: javaEncoding = "US-ASCII";
226: }
227: writer = new BufferedWriter(new OutputStreamWriter(
228: outputStream, javaEncoding));
229: break;
230: } catch (Exception err) {
231: if (encoding.equalsIgnoreCase("UTF8")) {
232: throw new DynamicError(
233: "Failed to create a UTF8 output writer");
234: }
235: DynamicError de = new DynamicError("Encoding "
236: + encoding
237: + " is not supported: using UTF8");
238: de.setErrorCode("SESU0007");
239: try {
240: getPipelineConfiguration().getErrorListener()
241: .error(de);
242: } catch (TransformerException e) {
243: throw DynamicError.makeDynamicError(e);
244: }
245: encoding = "UTF8";
246: characterSet = UnicodeCharacterSet.getInstance();
247: outputProperties.put(OutputKeys.ENCODING, "UTF-8");
248: }
249: }
250: }
251:
252: }
253:
254: /**
255: * Get the output stream
256: */
257:
258: public OutputStream getOutputStream() {
259: return outputStream;
260: }
261:
262: /**
263: * Set unparsed entity URI. Needed to satisfy the Receiver interface, but not used,
264: * because unparsed entities can occur only in input documents, not in output documents.
265: */
266:
267: public void setUnparsedEntity(String name, String uri,
268: String publicId) throws XPathException {
269: }
270:
271: /**
272: * Load a named output emitter or SAX2 ContentHandler and check it is OK.
273: */
274:
275: public static Receiver makeEmitter(String className,
276: Controller controller) throws XPathException {
277: Object handler;
278: try {
279: handler = controller.getConfiguration().getInstance(
280: className, controller.getClassLoader());
281: } catch (XPathException e) {
282: throw new DynamicError(
283: "Cannot load user-supplied output method "
284: + className);
285: }
286:
287: if (handler instanceof Receiver) {
288: return (Receiver) handler;
289: } else if (handler instanceof ContentHandler) {
290: ContentHandlerProxy emitter = new ContentHandlerProxy();
291: emitter
292: .setUnderlyingContentHandler((ContentHandler) handler);
293: return emitter;
294: } else {
295: throw new DynamicError(
296: "Failed to load "
297: + className
298: + ": it is neither a Receiver nor a SAX2 ContentHandler");
299: }
300:
301: }
302:
303: /**
304: * Close output stream or writer. Called by subclasses at endDocument time
305: */
306: /*
307: public void close() {
308: if (closeAfterUse) {
309: try {
310: if (usesWriter) {
311: writer.close();
312: } else {
313: outputStream.close();
314: }
315: } catch (Exception err) {}
316: }
317: }
318: */
319: }
320:
321: //
322: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
323: // you may not use this file except in compliance with the License. You may obtain a copy of the
324: // License at http://www.mozilla.org/MPL/
325: //
326: // Software distributed under the License is distributed on an "AS IS" basis,
327: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
328: // See the License for the specific language governing rights and limitations under the License.
329: //
330: // The Original Code is: all this file.
331: //
332: // The Initial Developer of the Original Code is Michael H. Kay.
333: //
334: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
335: //
336: // Contributor(s): none.
337: //
|