| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2000-2002 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 acknowlegement: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowlegement may appear in the software itself, |
| * if and wherever such third-party acknowlegements normally appear. |
| * |
| * 4. The names "The Jakarta Project", "Ant", 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 names without prior written |
| * permission of the Apache Group. |
| * |
| * 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. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| package org.apache.tools.ant.taskdefs; |
| |
| import java.lang.reflect.Method; |
| import java.io.File; |
| import java.util.Enumeration; |
| import java.util.Vector; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.AntClassLoader; |
| import org.apache.tools.ant.taskdefs.optional.TraXLiaison; |
| import org.apache.tools.ant.types.Path; |
| import org.apache.tools.ant.types.Reference; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.types.XMLCatalog; |
| import org.xml.sax.EntityResolver; |
| |
| /** |
| * Processes a set of XML documents via XSLT. This is |
| * useful for building views of XML based documentation. |
| * |
| * @version $Revision$ |
| * |
| * @author <a href="mailto:kvisco@exoffice.com">Keith Visco</a> |
| * @author <a href="mailto:rubys@us.ibm.com">Sam Ruby</a> |
| * @author <a href="mailto:russgold@acm.org">Russell Gold</a> |
| * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> |
| * |
| * @since Ant 1.1 |
| * |
| * @ant.task name="xslt" category="xml" |
| */ |
| |
| public class XSLTProcess extends MatchingTask implements XSLTLogger { |
| /** destination directory */ |
| private File destDir = null; |
| |
| /** where to find the source XML file, default is the project's basedir */ |
| private File baseDir = null; |
| |
| /** XSL stylesheet */ |
| private String xslFile = null; |
| |
| /** extension of the files produced by XSL processing */ |
| private String targetExtension = ".html"; |
| |
| /** additional parameters to be passed to the stylesheets */ |
| private Vector params = new Vector(); |
| |
| /** Input XML document to be used */ |
| private File inFile = null; |
| |
| /** Output file */ |
| private File outFile = null; |
| |
| /** The name of the XSL processor to use */ |
| private String processor; |
| |
| /** Classpath to use when trying to load the XSL processor */ |
| private Path classpath = null; |
| |
| /** The Liason implementation to use to communicate with the XSL |
| * processor */ |
| private XSLTLiaison liaison; |
| |
| /** Flag which indicates if the stylesheet has been loaded into |
| * the processor */ |
| private boolean stylesheetLoaded = false; |
| |
| /** force output of target files even if they already exist */ |
| private boolean force = false; |
| |
| /** Utilities used for file operations */ |
| private FileUtils fileUtils; |
| |
| /** XSL output method to be used */ |
| private Vector outputProperties = new Vector(); |
| |
| /** for resolving entities such as dtds */ |
| private XMLCatalog xmlCatalog = new XMLCatalog(); |
| |
| /** Name of the TRAX Liason class */ |
| private static final String TRAX_LIAISON_CLASS = |
| "org.apache.tools.ant.taskdefs.optional.TraXLiaison"; |
| |
| /** Name of the now-deprecated XSLP Liason class */ |
| private static final String XSLP_LIAISON_CLASS = |
| "org.apache.tools.ant.taskdefs.optional.XslpLiaison"; |
| |
| /** Name of the Xalan liason class */ |
| private static final String XALAN_LIAISON_CLASS = |
| "org.apache.tools.ant.taskdefs.optional.XalanLiaison"; |
| |
| /** |
| * Whether to style all files in the included directories as well. |
| * |
| * @since Ant 1.5 |
| */ |
| private boolean performDirectoryScan = true; |
| |
| /** |
| * Creates a new XSLTProcess Task. |
| */ |
| public XSLTProcess() { |
| fileUtils = FileUtils.newFileUtils(); |
| } //-- XSLTProcess |
| |
| /** |
| * Whether to style all files in the included directories as well; |
| * optional, default is true. |
| * |
| * @param b true if files in included directories are processed. |
| * @since Ant 1.5 |
| */ |
| public void setScanIncludedDirectories(boolean b) { |
| performDirectoryScan = b; |
| } |
| |
| /** |
| * Executes the task. |
| * |
| * @exception BuildException if there is an execution problem. |
| * @todo validate that if either in or our is defined, then both are |
| */ |
| public void execute() throws BuildException { |
| File savedBaseDir = baseDir; |
| |
| DirectoryScanner scanner; |
| String[] list; |
| String[] dirs; |
| |
| if (xslFile == null) { |
| throw new BuildException("no stylesheet specified", location); |
| } |
| |
| try { |
| if (baseDir == null) { |
| baseDir = project.resolveFile("."); |
| } |
| |
| liaison = getLiaison(); |
| |
| // check if liaison wants to log errors using us as logger |
| if (liaison instanceof XSLTLoggerAware) { |
| ((XSLTLoggerAware) liaison).setLogger(this); |
| } |
| |
| log("Using " + liaison.getClass().toString(), Project.MSG_VERBOSE); |
| |
| File stylesheet = project.resolveFile(xslFile); |
| if (!stylesheet.exists()) { |
| stylesheet = fileUtils.resolveFile(baseDir, xslFile); |
| /* |
| * shouldn't throw out deprecation warnings before we know, |
| * the wrong version has been used. |
| */ |
| if (stylesheet.exists()) { |
| log("DEPRECATED - the style attribute should be relative " |
| + "to the project\'s"); |
| log(" basedir, not the tasks\'s basedir."); |
| } |
| } |
| |
| // if we have an in file and out then process them |
| if (inFile != null && outFile != null) { |
| process(inFile, outFile, stylesheet); |
| return; |
| } |
| |
| /* |
| * if we get here, in and out have not been specified, we are |
| * in batch processing mode. |
| */ |
| |
| //-- make sure Source directory exists... |
| if (destDir == null) { |
| String msg = "destdir attributes must be set!"; |
| throw new BuildException(msg); |
| } |
| scanner = getDirectoryScanner(baseDir); |
| log("Transforming into " + destDir, Project.MSG_INFO); |
| |
| // Process all the files marked for styling |
| list = scanner.getIncludedFiles(); |
| for (int i = 0; i < list.length; ++i) { |
| process(baseDir, list[i], destDir, stylesheet); |
| } |
| if (performDirectoryScan) { |
| // Process all the directories marked for styling |
| dirs = scanner.getIncludedDirectories(); |
| for (int j = 0; j < dirs.length; ++j){ |
| list = new File(baseDir, dirs[j]).list(); |
| for (int i = 0; i < list.length; ++i) { |
| process(baseDir, list[i], destDir, stylesheet); |
| } |
| } |
| } |
| } finally { |
| liaison = null; |
| stylesheetLoaded = false; |
| baseDir = savedBaseDir; |
| } |
| } |
| |
| /** |
| * Set whether to check dependencies, or always generate; |
| * optional, default is false. |
| * |
| * @param force true if always generate. |
| */ |
| public void setForce(boolean force) { |
| this.force = force; |
| } |
| |
| /** |
| * Set the base directory; |
| * optional, default is the project's basedir. |
| * |
| * @param dir the base directory |
| **/ |
| public void setBasedir(File dir) { |
| baseDir = dir; |
| } |
| |
| /** |
| * Set the destination directory into which the XSL result |
| * files should be copied to; |
| * required, unless <tt>in</tt> and <tt>out</tt> are |
| * specified. |
| * @param dir the name of the destination directory |
| **/ |
| public void setDestdir(File dir) { |
| destDir = dir; |
| } |
| |
| /** |
| * Set the desired file extension to be used for the target; |
| * optional, default is html. |
| * @param name the extension to use |
| **/ |
| public void setExtension(String name) { |
| targetExtension = name; |
| } |
| |
| /** |
| * Name of the stylesheet to use - given either relative |
| * to the project's basedir or as an absolute path; required. |
| * |
| * @param xslFile the stylesheet to use |
| */ |
| public void setStyle(String xslFile) { |
| this.xslFile = xslFile; |
| } |
| |
| /** |
| * Set the optional classpath to the XSL processor |
| * |
| * @param classpath the classpath to use when loading the XSL processor |
| */ |
| public void setClasspath(Path classpath) { |
| createClasspath().append(classpath); |
| } |
| |
| /** |
| * Set the optional classpath to the XSL processor |
| * |
| * @return a path instance to be configured by the Ant core. |
| */ |
| public Path createClasspath() { |
| if (classpath == null) { |
| classpath = new Path(project); |
| } |
| return classpath.createPath(); |
| } |
| |
| /** |
| * Set the reference to an optional classpath to the XSL processor |
| * |
| * @param r the id of the Ant path instance to act as the classpath |
| * for loading the XSL processor |
| */ |
| public void setClasspathRef(Reference r) { |
| createClasspath().setRefid(r); |
| } |
| |
| /** |
| * Set the name of the XSL processor to use; optional, default trax. |
| * Other values are "xalan" for Xalan1 and "xslp" for XSL:P, though the |
| * later is strongly deprecated. |
| * |
| * @param processor the name of the XSL processor |
| */ |
| public void setProcessor(String processor) { |
| this.processor = processor; |
| } |
| |
| /** |
| * Add the catalog to our internal catalog |
| * |
| * @param xmlCatalog the XMLCatalog instance to use to look up DTDs |
| */ |
| public void addConfiguredXMLCatalog(XMLCatalog xmlCatalog) { |
| this.xmlCatalog.addConfiguredXMLCatalog(xmlCatalog); |
| } |
| |
| /** |
| * Load processor here instead of in setProcessor - this will be |
| * called from within execute, so we have access to the latest |
| * classpath. |
| * |
| * @param proc the name of the processor to load. |
| * @exception Exception if the processor cannot be loaded. |
| */ |
| private void resolveProcessor(String proc) throws Exception { |
| if (proc.equals("trax")) { |
| final Class clazz = loadClass(TRAX_LIAISON_CLASS); |
| liaison = (XSLTLiaison) clazz.newInstance(); |
| } else if (proc.equals("xslp")) { |
| log("DEPRECATED - xslp processor is deprecated. Use trax or " |
| + "xalan instead."); |
| final Class clazz = loadClass(XSLP_LIAISON_CLASS); |
| liaison = (XSLTLiaison) clazz.newInstance(); |
| } else if (proc.equals("xalan")) { |
| final Class clazz = loadClass(XALAN_LIAISON_CLASS); |
| liaison = (XSLTLiaison) clazz.newInstance(); |
| } else { |
| liaison = (XSLTLiaison) loadClass(proc).newInstance(); |
| } |
| } |
| |
| /** |
| * Load named class either via the system classloader or a given |
| * custom classloader. |
| * |
| * @param classname the name of the class to load. |
| * @return the requested class. |
| * @exception Exception if the class could not be loaded. |
| */ |
| private Class loadClass(String classname) throws Exception { |
| if (classpath == null) { |
| return Class.forName(classname); |
| } else { |
| AntClassLoader al = new AntClassLoader(project, classpath); |
| Class c = al.loadClass(classname); |
| AntClassLoader.initializeClass(c); |
| return c; |
| } |
| } |
| |
| /** |
| * Specifies the output name for the styled result from the |
| * <tt>in</tt> attribute; required if <tt>in</tt> is set |
| * |
| * @param outFile the output File instance. |
| */ |
| public void setOut(File outFile){ |
| this.outFile = outFile; |
| } |
| |
| /** |
| * specifies a single XML document to be styled. Should be used |
| * with the <tt>out</tt> attribute; ; required if <tt>out</tt> is set |
| * |
| * @param inFile the input file |
| */ |
| public void setIn(File inFile){ |
| this.inFile = inFile; |
| } |
| |
| /** |
| * Processes the given input XML file and stores the result |
| * in the given resultFile. |
| * |
| * @param baseDir the base directory for resolving files. |
| * @param xmlFile the input file |
| * @param destDir the destination directory |
| * @param stylesheet the stylesheet to use. |
| * @exception BuildException if the processing fails. |
| */ |
| private void process(File baseDir, String xmlFile, File destDir, |
| File stylesheet) |
| throws BuildException { |
| |
| String fileExt = targetExtension; |
| File outFile = null; |
| File inFile = null; |
| |
| try { |
| long styleSheetLastModified = stylesheet.lastModified(); |
| inFile = new File(baseDir, xmlFile); |
| |
| if (inFile.isDirectory()) { |
| log("Skipping " + inFile + " it is a directory.", |
| Project.MSG_VERBOSE); |
| return; |
| } |
| |
| int dotPos = xmlFile.lastIndexOf('.'); |
| if (dotPos > 0) { |
| outFile = new File(destDir, |
| xmlFile.substring(0, xmlFile.lastIndexOf('.')) + fileExt); |
| } else { |
| outFile = new File(destDir, xmlFile + fileExt); |
| } |
| if (force || |
| inFile.lastModified() > outFile.lastModified() || |
| styleSheetLastModified > outFile.lastModified()) { |
| ensureDirectoryFor(outFile); |
| log("Processing " + inFile + " to " + outFile); |
| |
| configureLiaison(stylesheet); |
| liaison.transform(inFile, outFile); |
| } |
| } catch (Exception ex) { |
| // If failed to process document, must delete target document, |
| // or it will not attempt to process it the second time |
| log("Failed to process " + inFile, Project.MSG_INFO); |
| if (outFile != null) { |
| outFile.delete(); |
| } |
| |
| throw new BuildException(ex); |
| } |
| |
| } //-- processXML |
| |
| /** |
| * Process the input file to the output file with the given stylesheet. |
| * |
| * @param inFile the input file to process. |
| * @param outFile the detination file. |
| * @param stylesheet the stylesheet to use. |
| * @exception BuildException if the processing fails. |
| */ |
| private void process(File inFile, File outFile, File stylesheet) |
| throws BuildException { |
| try { |
| long styleSheetLastModified = stylesheet.lastModified(); |
| log("In file " + inFile + " time: " + inFile.lastModified(), |
| Project.MSG_DEBUG); |
| log("Out file " + outFile + " time: " + outFile.lastModified(), |
| Project.MSG_DEBUG); |
| log("Style file " + xslFile + " time: " + styleSheetLastModified, |
| Project.MSG_DEBUG); |
| if (force || |
| inFile.lastModified() > outFile.lastModified() || |
| styleSheetLastModified > outFile.lastModified()) { |
| ensureDirectoryFor(outFile); |
| log("Processing " + inFile + " to " + outFile, |
| Project.MSG_INFO); |
| configureLiaison(stylesheet); |
| liaison.transform(inFile, outFile); |
| } |
| } catch (Exception ex) { |
| log("Failed to process " + inFile, Project.MSG_INFO); |
| if (outFile != null) { |
| outFile.delete(); |
| } |
| throw new BuildException(ex); |
| } |
| } |
| |
| /** |
| * Ensure the directory exists for a given file |
| * |
| * @param targetFile the file for which the directories are required. |
| * @exception BuildException if the directories cannot be created. |
| */ |
| private void ensureDirectoryFor(File targetFile) |
| throws BuildException { |
| File directory = fileUtils.getParentFile(targetFile); |
| if (!directory.exists()) { |
| if (!directory.mkdirs()) { |
| throw new BuildException("Unable to create directory: " |
| + directory.getAbsolutePath()); |
| } |
| } |
| } |
| |
| /** |
| * Get the Liason implementation to use in processing. |
| * |
| * @return an instance of the XSLTLiason interface. |
| */ |
| protected XSLTLiaison getLiaison() { |
| // if processor wasn't specified, see if TraX is available. If not, |
| // default it to xslp or xalan, depending on which is in the classpath |
| if (liaison == null) { |
| if (processor != null) { |
| try { |
| resolveProcessor(processor); |
| } catch (Exception e) { |
| throw new BuildException(e); |
| } |
| } else { |
| try { |
| resolveProcessor("trax"); |
| } catch (Throwable e1) { |
| try { |
| resolveProcessor("xalan"); |
| } catch (Throwable e2) { |
| try { |
| resolveProcessor("xslp"); |
| } catch (Throwable e3) { |
| e3.printStackTrace(); |
| e2.printStackTrace(); |
| throw new BuildException(e1); |
| } |
| } |
| } |
| } |
| } |
| return liaison; |
| } |
| |
| /** |
| * Create an instance of an XSL parameter for configuration by Ant. |
| * |
| * @return an instance of the Param class to be configured. |
| */ |
| public Param createParam() { |
| Param p = new Param(); |
| params.addElement(p); |
| return p; |
| } |
| |
| /** |
| * The Param inner class used to store XSL parameters |
| */ |
| public class Param { |
| /** The parameter name */ |
| private String name = null; |
| |
| /** The parameter's XSL expression */ |
| private String expression = null; |
| |
| /** |
| * Set the parameter name. |
| * |
| * @param name the name of the parameter. |
| */ |
| public void setName(String name){ |
| this.name = name; |
| } |
| |
| /** |
| * The XSL expression for the parameter value |
| * |
| * @param expression the XSL expression representing the |
| * parameter's value. |
| */ |
| public void setExpression(String expression){ |
| this.expression = expression; |
| } |
| |
| /** |
| * Get the parameter name |
| * |
| * @return the parameter name |
| * @exception BuildException if the name is not set. |
| */ |
| public String getName() throws BuildException{ |
| if (name == null) { |
| throw new BuildException("Name attribute is missing."); |
| } |
| return name; |
| } |
| |
| /** |
| * Get the parameter expression |
| * |
| * @return the parameter expression |
| * @exception BuildException if the expression is not set. |
| */ |
| public String getExpression() throws BuildException{ |
| if (expression == null) { |
| throw new BuildException("Expression attribute is missing."); |
| } |
| return expression; |
| } |
| } // Param |
| |
| |
| /** |
| * Create an instance of an output property to be configured. |
| * @return the newly created output property. |
| * @since Ant 1.5 |
| */ |
| public OutputProperty createOutputProperty() { |
| OutputProperty p = new OutputProperty(); |
| outputProperties.addElement(p); |
| return p; |
| } |
| |
| |
| /** |
| * Specify how the result tree should be output as specified |
| * in the <a href="http://www.w3.org/TR/xslt#output"> |
| * specification</a>. |
| * @since Ant 1.5 |
| */ |
| public static class OutputProperty { |
| /** output property name */ |
| private String name; |
| |
| /** output property value */ |
| private String value; |
| |
| /** |
| * @return the output property name. |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * set the name for this property |
| * @param name A non-null String that specifies an |
| * output property name, which may be namespace qualified. |
| */ |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| /** |
| * @return the output property value. |
| */ |
| public String getValue() { |
| return value; |
| } |
| |
| /** |
| * set the value for this property |
| * @param value The non-null string value of the output property. |
| */ |
| public void setValue(String value) { |
| this.value = value; |
| } |
| } |
| |
| /** |
| * Initialize internal instance of XMLCatalog |
| */ |
| public void init() throws BuildException { |
| super.init(); |
| xmlCatalog.setProject(project); |
| } |
| |
| /** |
| * Loads the stylesheet and set xsl:param parameters. |
| * |
| * @param stylesheet the file form which to load the stylesheet. |
| * @exception BuildException if the stylesheet cannot be loaded. |
| */ |
| protected void configureLiaison(File stylesheet) throws BuildException { |
| if (stylesheetLoaded) { |
| return; |
| } |
| stylesheetLoaded = true; |
| |
| try { |
| log("Loading stylesheet " + stylesheet, Project.MSG_INFO); |
| liaison.setStylesheet(stylesheet); |
| for (Enumeration e = params.elements(); e.hasMoreElements();) { |
| Param p = (Param) e.nextElement(); |
| liaison.addParam(p.getName(), p.getExpression()); |
| } |
| if (liaison instanceof TraXLiaison) { |
| configureTraXLiaison((TraXLiaison)liaison); |
| } |
| } catch (Exception ex) { |
| log("Failed to transform using stylesheet " + stylesheet, Project.MSG_INFO); |
| throw new BuildException(ex); |
| } |
| } |
| |
| /** |
| * Specific configuration for the TRaX liaison... support for |
| * all other has been dropped so this liaison will soon look |
| * like the exact copy of JAXP interface.. |
| * @param liaison the TRaXLiaison to configure. |
| */ |
| protected void configureTraXLiaison(TraXLiaison liaison){ |
| // use XMLCatalog as the entity resolver and URI resolver |
| if (xmlCatalog != null) { |
| liaison.setEntityResolver(xmlCatalog); |
| liaison.setURIResolver(xmlCatalog); |
| } |
| |
| // configure output properties |
| for (Enumeration props = outputProperties.elements(); |
| props.hasMoreElements();) { |
| OutputProperty prop = (OutputProperty)props.nextElement(); |
| liaison.setOutputProperty(prop.getName(), prop.getValue()); |
| } |
| } |
| |
| } //-- XSLTProcess |