Source code: org/apache/xalan/xslt/extensions/Redirect.java
1 /*
2 * The Apache Software License, Version 1.1
3 *
4 *
5 * Copyright (c) 1999 The Apache Software Foundation. All rights
6 * reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution,
21 * if any, must include the following acknowledgment:
22 * "This product includes software developed by the
23 * Apache Software Foundation (http://www.apache.org/)."
24 * Alternately, this acknowledgment may appear in the software itself,
25 * if and wherever such third-party acknowledgments normally appear.
26 *
27 * 4. The names "Xalan" and "Apache Software Foundation" must
28 * not be used to endorse or promote products derived from this
29 * software without prior written permission. For written
30 * permission, please contact apache@apache.org.
31 *
32 * 5. Products derived from this software may not be called "Apache",
33 * nor may "Apache" appear in their name, without prior written
34 * permission of the Apache Software Foundation.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 * ====================================================================
49 *
50 * This software consists of voluntary contributions made by many
51 * individuals on behalf of the Apache Software Foundation and was
52 * originally based on software copyright (c) 1999, Lotus
53 * Development Corporation., http://www.lotus.com. For more
54 * information on the Apache Software Foundation, please see
55 * <http://www.apache.org/>.
56 */
57 package org.apache.xalan.xslt.extensions;
58
59 import java.util.*;
60 import org.w3c.dom.*;
61 import org.apache.xalan.xslt.XSLProcessorContext;
62 import org.apache.xalan.xslt.XSLTEngineImpl;
63 import org.apache.xalan.xslt.ElemTemplateElement;
64 import org.apache.xalan.xslt.res.XSLTErrorResources;
65 import org.apache.xalan.xpath.XObject;
66 import org.apache.xalan.xslt.StylesheetRoot;
67 import org.apache.xalan.xslt.ElemExtensionCall;
68 import java.io.*;
69 import java.net.URL;
70 import org.apache.xalan.xpath.xml.*;
71 import org.apache.xml.serialize.*;
72 import org.xml.sax.DocumentHandler;
73
74 /**
75 * Implements three extension elements to allow an XSLT transformation to
76 * redirect its output to multiple output files.
77 * You must declare the Xalan namespace (xmlns:lxslt="http://xml.apache.org/xslt"),
78 * a namespace for the extension prefix (such as xmlns:redirect="org.apache.xalan.xslt.extensions.Redirect"),
79 * and declare the extension namespace as an extension (extension-element-prefixes="redirect").
80 * You can either just use redirect:write, in which case the file will be
81 * opened and immediately closed after the write, or you can bracket the
82 * write calls by redirect:open and redirect:close, in which case the
83 * file will be kept open for multiple writes until the close call is
84 * encountered. Calls can be nested. Calls can take a 'file' attribute
85 * and/or a 'select' attribute in order to get the filename. If a select
86 * attribute is encountered, it will evaluate that expression for a string
87 * that indicates the filename. If the string evaluates to empty, it will
88 * attempt to use the 'file' attribute as a default. Filenames can be relative
89 * or absolute. If they are relative, the base directory will be the same as
90 * the base directory for the output document (setOutputFileName(outFileName) must
91 * be called first on the processor when using the API).
92 *
93 * <p>Example:</p>
94 * <PRE>
95 * <?xml version="1.0"?>
96 * <xsl:stylesheet xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
97 * xmlns:lxslt="http://xml.apache.org/xslt"
98 * xmlns:redirect="org.apache.xalan.xslt.extensions.Redirect"
99 * extension-element-prefixes="redirect">
100 *
101 * <xsl:template match="/">
102 * <out>
103 * default output.
104 * </out>
105 * <redirect:open file="doc3.out"/>
106 * <redirect:write file="doc3.out">
107 * <out>
108 * <redirect:write file="doc1.out">
109 * <out>
110 * doc1 output.
111 * <redirect:write file="doc3.out">
112 * Some text to doc3
113 * </redirect:write>
114 * </out>
115 * </redirect:write>
116 * <redirect:write file="doc2.out">
117 * <out>
118 * doc2 output.
119 * <redirect:write file="doc3.out">
120 * Some more text to doc3
121 * <redirect:write select="doc/foo">
122 * text for doc4
123 * </redirect:write>
124 * </redirect:write>
125 * </out>
126 * </redirect:write>
127 * </out>
128 * </redirect:write>
129 * <redirect:close file="doc3.out"/>
130 * </xsl:template>
131 *
132 * </xsl:stylesheet>
133 * </PRE>
134 *
135 * @author Scott Boag
136 * @version 1.0
137 * @see <a href="../../../../../../extensions.html#ex-redirect" target="_top">Example with Redirect extension</a>
138 */
139 public class Redirect
140 {
141 /**
142 * List of formatter listeners indexed by filename.
143 */
144 protected Hashtable m_formatterListeners = new Hashtable ();
145
146 /**
147 * List of output streams indexed by filename.
148 */
149 protected Hashtable m_outputStreams = new Hashtable ();
150
151 /**
152 * Open the given file and put it in the XML, HTML, or Text formatter listener's table.
153 */
154 public void open(XSLProcessorContext context, Element elem)
155 throws java.net.MalformedURLException,
156 java.io.FileNotFoundException,
157 java.io.IOException,
158 org.xml.sax.SAXException
159 {
160 String fileName = getFilename(context, elem);
161 Object flistener = m_formatterListeners.get(fileName);
162 if(null == flistener)
163 {
164 String mkdirsExpr = ((ElemExtensionCall)elem).getAttribute ("mkdirs", context.sourceNode, context.processor);
165 boolean mkdirs = (mkdirsExpr != null)
166 ? (mkdirsExpr.equals("true") || mkdirsExpr.equals("yes")) : true;
167 // DocumentHandler fl =
168 makeFormatterListener(context, fileName, true, mkdirs);
169 // fl.startDocument();
170 }
171 }
172
173 /**
174 * Write the evalutation of the element children to the given file. Then close the file
175 * unless it was opened with the open extension element and is in the formatter listener's table.
176 */
177 public void write(XSLProcessorContext context, Element elem)
178 throws java.net.MalformedURLException,
179 java.io.FileNotFoundException,
180 java.io.IOException,
181 org.xml.sax.SAXException
182 {
183 String fileName = getFilename(context, elem);
184 Object flObject = m_formatterListeners.get(fileName);
185 DocumentHandler formatter;
186 boolean inTable = false;
187 if(null == flObject)
188 {
189 String mkdirsExpr = ((ElemExtensionCall)elem).getAttribute ("mkdirs", context.sourceNode, context.processor);
190 boolean mkdirs = (mkdirsExpr != null)
191 ? (mkdirsExpr.equals("true") || mkdirsExpr.equals("yes")) : true;
192 formatter = makeFormatterListener(context, fileName, true, mkdirs);
193 }
194 else
195 {
196 inTable = true;
197 formatter = (DocumentHandler)flObject;
198 }
199
200 context.processor.writeChildren( formatter, context.stylesheetTree,
201 (ElemTemplateElement)elem,
202 context.sourceTree, context.sourceNode, context.mode);
203 if(!inTable)
204 {
205 OutputStream ostream = (OutputStream)m_outputStreams.get(fileName);
206 if(null != ostream)
207 {
208 formatter.endDocument();
209 ostream.close();
210 m_outputStreams.remove(fileName);
211 m_formatterListeners.remove(fileName);
212 }
213 }
214 }
215
216
217 /**
218 * Close the given file and remove it from the formatter listener's table.
219 */
220 public void close(XSLProcessorContext context, Element elem)
221 throws java.net.MalformedURLException,
222 java.io.FileNotFoundException,
223 java.io.IOException,
224 org.xml.sax.SAXException
225 {
226 String fileName = getFilename(context, elem);
227 Object formatterObj = m_formatterListeners.get(fileName);
228 if(null != formatterObj)
229 {
230 DocumentHandler fl = (DocumentHandler)formatterObj;
231 fl.endDocument();
232 OutputStream ostream = (OutputStream)m_outputStreams.get(fileName);
233 if(null != ostream)
234 {
235 ostream.close();
236 m_outputStreams.remove(fileName);
237 }
238 m_formatterListeners.remove(fileName);
239 }
240 }
241
242 /**
243 * Get the filename from the 'select' or the 'file' attribute.
244 */
245 private String getFilename(XSLProcessorContext context, Element elem)
246 throws java.net.MalformedURLException,
247 java.io.FileNotFoundException,
248 java.io.IOException,
249 org.xml.sax.SAXException
250 {
251 String fileName;
252 String fileNameExpr = ((ElemExtensionCall)elem).getAttribute ("select", context.sourceNode, context.processor);
253 if(null != fileNameExpr)
254 {
255 org.apache.xalan.xpath.XPathSupport execContext = context.processor.getExecContext();
256 XObject xobj = context.processor.getStylesheet().evalXPathStr(execContext, fileNameExpr,
257 context.sourceNode,
258 execContext.getNamespaceContext());
259 fileName = xobj.str();
260 if((null == fileName) || (fileName.length() == 0))
261 {
262 fileName = ((ElemExtensionCall)elem).getAttribute ("file", context.sourceNode, context.processor);
263 }
264 }
265 else
266 {
267 fileName = ((ElemExtensionCall)elem).getAttribute ("file", context.sourceNode, context.processor);
268 }
269 if(null == fileName)
270 {
271 context.processor.error(elem, context.sourceNode, XSLTErrorResources.ER_REDIRECT_COULDNT_GET_FILENAME);
272 //"Redirect extension: Could not get filename - file or select attribute must return vald string.");
273 }
274 return fileName;
275 }
276
277 /**
278 * Create a new DocumentHandler, based on attributes of the current DocumentHandler.
279 */
280 private DocumentHandler makeFormatterListener(XSLProcessorContext context,
281 String fileName,
282 boolean shouldPutInTable,
283 boolean mkdirs)
284 throws java.net.MalformedURLException,
285 java.io.FileNotFoundException,
286 java.io.IOException,
287 org.xml.sax.SAXException
288 {
289 File file = new File(fileName);
290 if(!file.isAbsolute())
291 {
292 if(null != context.processor.getOutputFileName())
293 {
294 File baseFile = new File(context.processor.getOutputFileName());
295 file = new File(baseFile.getParent(), fileName);
296 }
297 }
298
299 if(mkdirs)
300 {
301 String dirStr = file.getParent();
302 if((null != dirStr) && (dirStr.length() > 0))
303 {
304 File dir = new File(dirStr);
305 dir.mkdirs();
306 }
307 }
308
309 StylesheetRoot sr = context.stylesheetTree.m_stylesheetRoot;
310 OutputFormat formatter = sr.getOutputFormat();
311
312 FileOutputStream ostream = new FileOutputStream(file);
313
314 DocumentHandler flistener
315 = sr.makeSAXSerializer(ostream, formatter);
316
317 flistener.startDocument();
318 if(shouldPutInTable)
319 {
320 m_outputStreams.put(fileName, ostream);
321 m_formatterListeners.put(fileName, flistener);
322 }
323 return flistener;
324 }
325 }