- /*
- * The Apache Software License, Version 1.1
- *
- *
- * Copyright (c) 1999 The Apache Software Foundation. All rights
- * reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * 3. The end-user documentation included with the redistribution,
- * if any, must include the following acknowledgment:
- * "This product includes software developed by the
- * Apache Software Foundation (http://www.apache.org/)."
- * Alternately, this acknowledgment may appear in the software itself,
- * if and wherever such third-party acknowledgments normally appear.
- *
- * 4. The names "Xalan" and "Apache Software Foundation" must
- * not be used to endorse or promote products derived from this
- * software without prior written permission. For written
- * permission, please contact apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache",
- * nor may "Apache" appear in their name, without prior written
- * permission of the Apache Software Foundation.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation and was
- * originally based on software copyright (c) 1999, Lotus
- * Development Corporation., http://www.lotus.com. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- */
- package org.apache.xalan.lib;
-
- import java.util.*;
- import java.io.*;
- import java.net.URL;
-
- import org.xml.sax.ContentHandler;
-
- import org.apache.xalan.extensions.XSLProcessorContext;
- import org.apache.xalan.transformer.TransformerImpl;
- import org.apache.xalan.templates.StylesheetRoot;
- import org.apache.xalan.templates.ElemExtensionCall;
- import org.apache.xalan.templates.OutputProperties;
- import org.apache.xalan.res.XSLTErrorResources;
- import org.apache.xpath.objects.XObject;
- import org.apache.xpath.XPath;
-
- import javax.xml.transform.stream.StreamResult;
- import javax.xml.transform.Result;
- import javax.xml.transform.TransformerException;
-
- import org.w3c.dom.*;
-
- /**
- * Implements three extension elements to allow an XSLT transformation to
- * redirect its output to multiple output files.
- *
- * It is accessed by specifying a namespace URI as follows:
- * <pre>
- * xmlns:redirect="http://xml.apache.org/xalan/redirect"
- * </pre>
- *
- * <p>You can either just use redirect:write, in which case the file will be
- * opened and immediately closed after the write, or you can bracket the
- * write calls by redirect:open and redirect:close, in which case the
- * file will be kept open for multiple writes until the close call is
- * encountered. Calls can be nested.
- *
- * <p>Calls can take a 'file' attribute
- * and/or a 'select' attribute in order to get the filename. If a select
- * attribute is encountered, it will evaluate that expression for a string
- * that indicates the filename. If the string evaluates to empty, it will
- * attempt to use the 'file' attribute as a default. Filenames can be relative
- * or absolute. If they are relative, the base directory will be the same as
- * the base directory for the output document. This is obtained by calling
- * getOutputTarget() on the TransformerImpl. You can set this base directory
- * by calling TransformerImpl.setOutputTarget() or it is automatically set
- * when using the two argument form of transform() or transformNode().
- *
- * <p>Calls to redirect:write and redirect:open also take an optional
- * attribute append="true|yes", which will attempt to simply append
- * to an existing file instead of always opening a new file. The
- * default behavior of always overwriting the file still happens
- * if you do not specify append.
- * <p><b>Note:</b> this may give unexpected results when using xml
- * or html output methods, since this is <b>not</b> coordinated
- * with the serializers - hence, you may get extra xml decls in
- * the middle of your file after appending to it.
- *
- * <p>Example:</p>
- * <PRE>
- * <?xml version="1.0"?>
- * <xsl:stylesheet xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
- * xmlns:redirect="http://xml.apache.org/xalan/redirect"
- * extension-element-prefixes="redirect">
- *
- * <xsl:template match="/">
- * <out>
- * default output.
- * </out>
- * <redirect:open file="doc3.out"/>
- * <redirect:write file="doc3.out">
- * <out>
- * <redirect:write file="doc1.out">
- * <out>
- * doc1 output.
- * <redirect:write file="doc3.out">
- * Some text to doc3
- * </redirect:write>
- * </out>
- * </redirect:write>
- * <redirect:write file="doc2.out">
- * <out>
- * doc2 output.
- * <redirect:write file="doc3.out">
- * Some more text to doc3
- * <redirect:write select="doc/foo">
- * text for doc4
- * </redirect:write>
- * </redirect:write>
- * </out>
- * </redirect:write>
- * </out>
- * </redirect:write>
- * <redirect:close file="doc3.out"/>
- * </xsl:template>
- *
- * </xsl:stylesheet>
- * </PRE>
- *
- * @author Scott Boag
- * @version 1.0
- * @see <a href="../../../../../../extensions.html#ex-redirect" target="_top">Example with Redirect extension</a>
- */
- public class Redirect
- {
- /**
- * List of formatter listeners indexed by filename.
- */
- protected Hashtable m_formatterListeners = new Hashtable ();
-
- /**
- * List of output streams indexed by filename.
- */
- protected Hashtable m_outputStreams = new Hashtable ();
-
- /**
- * Default append mode for bare open calls.
- * False for backwards compatibility (I think).
- */
- public static final boolean DEFAULT_APPEND_OPEN = false;
-
- /**
- * Default append mode for bare write calls.
- * False for backwards compatibility.
- */
- public static final boolean DEFAULT_APPEND_WRITE = false;
-
- /**
- * Open the given file and put it in the XML, HTML, or Text formatter listener's table.
- */
- public void open(XSLProcessorContext context, ElemExtensionCall elem)
- throws java.net.MalformedURLException,
- java.io.FileNotFoundException,
- java.io.IOException,
- javax.xml.transform.TransformerException
- {
- String fileName = getFilename(context, elem);
- Object flistener = m_formatterListeners.get(fileName);
- if(null == flistener)
- {
- String mkdirsExpr
- = elem.getAttribute ("mkdirs", context.getContextNode(),
- context.getTransformer());
- boolean mkdirs = (mkdirsExpr != null)
- ? (mkdirsExpr.equals("true") || mkdirsExpr.equals("yes")) : true;
-
- // Whether to append to existing files or not, <jpvdm@iafrica.com>
- String appendExpr = elem.getAttribute("append", context.getContextNode(), context.getTransformer());
- boolean append = (appendExpr != null)
- ? (appendExpr.equals("true") || appendExpr.equals("yes")) : DEFAULT_APPEND_OPEN;
-
- Object ignored = makeFormatterListener(context, elem, fileName, true, mkdirs, append);
- }
- }
-
- /**
- * Write the evalutation of the element children to the given file. Then close the file
- * unless it was opened with the open extension element and is in the formatter listener's table.
- */
- public void write(XSLProcessorContext context, ElemExtensionCall elem)
- throws java.net.MalformedURLException,
- java.io.FileNotFoundException,
- java.io.IOException,
- javax.xml.transform.TransformerException
- {
- String fileName = getFilename(context, elem);
- Object flObject = m_formatterListeners.get(fileName);
- ContentHandler formatter;
- boolean inTable = false;
- if(null == flObject)
- {
- String mkdirsExpr
- = ((ElemExtensionCall)elem).getAttribute ("mkdirs",
- context.getContextNode(),
- context.getTransformer());
- boolean mkdirs = (mkdirsExpr != null)
- ? (mkdirsExpr.equals("true") || mkdirsExpr.equals("yes")) : true;
-
- // Whether to append to existing files or not, <jpvdm@iafrica.com>
- String appendExpr = elem.getAttribute("append", context.getContextNode(), context.getTransformer());
- boolean append = (appendExpr != null)
- ? (appendExpr.equals("true") || appendExpr.equals("yes")) : DEFAULT_APPEND_WRITE;
-
- formatter = makeFormatterListener(context, elem, fileName, true, mkdirs, append);
- }
- else
- {
- inTable = true;
- formatter = (ContentHandler)flObject;
- }
-
- TransformerImpl transf = context.getTransformer();
-
- transf.executeChildTemplates(elem,
- context.getContextNode(),
- context.getMode(), formatter);
-
- if(!inTable)
- {
- OutputStream ostream = (OutputStream)m_outputStreams.get(fileName);
- if(null != ostream)
- {
- try
- {
- formatter.endDocument();
- }
- catch(org.xml.sax.SAXException se)
- {
- throw new TransformerException(se);
- }
- ostream.close();
- m_outputStreams.remove(fileName);
- m_formatterListeners.remove(fileName);
- }
- }
- }
-
-
- /**
- * Close the given file and remove it from the formatter listener's table.
- */
- public void close(XSLProcessorContext context, ElemExtensionCall elem)
- throws java.net.MalformedURLException,
- java.io.FileNotFoundException,
- java.io.IOException,
- javax.xml.transform.TransformerException
- {
- String fileName = getFilename(context, elem);
- Object formatterObj = m_formatterListeners.get(fileName);
- if(null != formatterObj)
- {
- ContentHandler fl = (ContentHandler)formatterObj;
- try
- {
- fl.endDocument();
- }
- catch(org.xml.sax.SAXException se)
- {
- throw new TransformerException(se);
- }
- OutputStream ostream = (OutputStream)m_outputStreams.get(fileName);
- if(null != ostream)
- {
- ostream.close();
- m_outputStreams.remove(fileName);
- }
- m_formatterListeners.remove(fileName);
- }
- }
-
- /**
- * Get the filename from the 'select' or the 'file' attribute.
- */
- private String getFilename(XSLProcessorContext context, ElemExtensionCall elem)
- throws java.net.MalformedURLException,
- java.io.FileNotFoundException,
- java.io.IOException,
- javax.xml.transform.TransformerException
- {
- String fileName;
- String fileNameExpr
- = ((ElemExtensionCall)elem).getAttribute ("select",
- context.getContextNode(),
- context.getTransformer());
- if(null != fileNameExpr)
- {
- org.apache.xpath.XPathContext xctxt
- = context.getTransformer().getXPathContext();
- XPath myxpath = new XPath(fileNameExpr, elem, xctxt.getNamespaceContext(), XPath.SELECT);
- XObject xobj = myxpath.execute(xctxt, context.getContextNode(), elem);
- fileName = xobj.str();
- if((null == fileName) || (fileName.length() == 0))
- {
- fileName = elem.getAttribute ("file",
- context.getContextNode(),
- context.getTransformer());
- }
- }
- else
- {
- fileName = elem.getAttribute ("file", context.getContextNode(),
- context.getTransformer());
- }
- if(null == fileName)
- {
- context.getTransformer().getMsgMgr().error(elem, elem,
- context.getContextNode(),
- XSLTErrorResources.ER_REDIRECT_COULDNT_GET_FILENAME);
- //"Redirect extension: Could not get filename - file or select attribute must return vald string.");
- }
- return fileName;
- }
-
- // yuck.
- // Note: this is not the best way to do this, and may not even
- // be fully correct! Patches (with test cases) welcomed. -sc
- private String urlToFileName(String base)
- {
- if(null != base)
- {
- if(base.startsWith("file:////"))
- {
- base = base.substring(7);
- }
- else if(base.startsWith("file:///"))
- {
- base = base.substring(6);
- }
- else if(base.startsWith("file://"))
- {
- base = base.substring(5); // absolute?
- }
- else if(base.startsWith("file:/"))
- {
- base = base.substring(5);
- }
- else if(base.startsWith("file:"))
- {
- base = base.substring(4);
- }
- }
- return base;
- }
-
- /**
- * Create a new ContentHandler, based on attributes of the current ContentHandler.
- */
- private ContentHandler makeFormatterListener(XSLProcessorContext context,
- ElemExtensionCall elem,
- String fileName,
- boolean shouldPutInTable,
- boolean mkdirs,
- boolean append)
- throws java.net.MalformedURLException,
- java.io.FileNotFoundException,
- java.io.IOException,
- javax.xml.transform.TransformerException
- {
- File file = new File(fileName);
- TransformerImpl transformer = context.getTransformer();
- String base; // Base URI to use for relative paths
-
- if(!file.isAbsolute())
- {
- // This code is attributed to Jon Grov <jon@linpro.no>. A relative file name
- // is relative to the Result used to kick off the transform. If no such
- // Result was supplied, the filename is relative to the source document.
- // When transforming with a SAXResult or DOMResult, call
- // TransformerImpl.setOutputTarget() to set the desired Result base.
- // String base = urlToFileName(elem.getStylesheet().getSystemId());
-
- Result outputTarget = transformer.getOutputTarget();
- if ( (null != outputTarget) && ((base = outputTarget.getSystemId()) != null) ) {
- base = urlToFileName(base);
- }
- else
- {
- base = urlToFileName(transformer.getBaseURLOfSource());
- }
-
- if(null != base)
- {
- File baseFile = new File(base);
- file = new File(baseFile.getParent(), fileName);
- }
- // System.out.println("file is: "+file.toString());
- }
-
- if(mkdirs)
- {
- String dirStr = file.getParent();
- if((null != dirStr) && (dirStr.length() > 0))
- {
- File dir = new File(dirStr);
- dir.mkdirs();
- }
- }
-
- // This should be worked on so that the output format can be
- // defined by a first child of the redirect element.
- OutputProperties format = transformer.getOutputFormat();
-
- // FileOutputStream ostream = new FileOutputStream(file);
- // Patch from above line to below by <jpvdm@iafrica.com>
- // Note that in JDK 1.2.2 at least, FileOutputStream(File)
- // is implemented as a call to
- // FileOutputStream(File.getPath, append), thus this should be
- // the equivalent instead of getAbsolutePath()
- FileOutputStream ostream = new FileOutputStream(file.getPath(), append);
-
- try
- {
- ContentHandler flistener
- = transformer.createResultContentHandler(new StreamResult(ostream), format);
- try
- {
- flistener.startDocument();
- }
- catch(org.xml.sax.SAXException se)
- {
- throw new TransformerException(se);
- }
- if(shouldPutInTable)
- {
- m_outputStreams.put(fileName, ostream);
- m_formatterListeners.put(fileName, flistener);
- }
- return flistener;
- }
- catch(TransformerException te)
- {
- throw new javax.xml.transform.TransformerException(te);
- }
-
- }
- }