diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java | 1689 |
1 files changed, 1689 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java new file mode 100644 index 00000000..4fe31260 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java @@ -0,0 +1,1689 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.taskdefs; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import javax.xml.namespace.QName; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathVariableResolver; + +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.DynamicConfigurator; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.PropertyHelper; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.PropertySet; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.XMLCatalog; +import org.apache.tools.ant.types.resources.FileProvider; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.resources.Resources; +import org.apache.tools.ant.types.resources.Union; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.ResourceUtils; + +/** + * Processes a set of XML documents via XSLT. This is + * useful for building views of XML based documentation. + * + * + * @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 as a filename */ + private String xslFile = null; + + /** XSL stylesheet as a {@link org.apache.tools.ant.types.Resource} */ + private Resource xslResource = null; + + /** extension of the files produced by XSL processing */ + private String targetExtension = ".html"; + + /** name for XSL parameter containing the filename */ + private String fileNameParameter = null; + + /** name for XSL parameter containing the file directory */ + private String fileDirParameter = null; + + /** additional parameters to be passed to the stylesheets */ + private final List<Param> params = new ArrayList<Param>(); + + /** 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 Liaison 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; + + /** XSL output properties to be used */ + private final Vector outputProperties = new Vector(); + + /** for resolving entities such as dtds */ + private final XMLCatalog xmlCatalog = new XMLCatalog(); + + /** Utilities used for file operations */ + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** + * Whether to style all files in the included directories as well. + * + * @since Ant 1.5 + */ + private boolean performDirectoryScan = true; + + /** + * factory element for TraX processors only + * @since Ant 1.6 + */ + private Factory factory = null; + + /** + * whether to reuse Transformer if transforming multiple files. + * @since 1.5.2 + */ + private boolean reuseLoadedStylesheet = true; + + /** + * AntClassLoader for the nested <classpath> - if set. + * + * <p>We keep this here in order to reset the context classloader + * in execute. We can't use liaison.getClass().getClassLoader() + * since the actual liaison class may have been loaded by a loader + * higher up (system classloader, for example).</p> + * + * @since Ant 1.6.2 + */ + private AntClassLoader loader = null; + + /** + * Mapper to use when a set of files gets processed. + * + * @since Ant 1.6.2 + */ + private Mapper mapperElement = null; + + /** + * Additional resource collections to process. + * + * @since Ant 1.7 + */ + private final Union resources = new Union(); + + /** + * Whether to use the implicit fileset. + * + * @since Ant 1.7 + */ + private boolean useImplicitFileset = true; + + /** + * The default processor is trax + * @since Ant 1.7 + */ + public static final String PROCESSOR_TRAX = "trax"; + + /** + * whether to suppress warnings. + * + * @since Ant 1.8.0 + */ + private boolean suppressWarnings = false; + + /** + * whether to fail the build if an error occurs during transformation. + * + * @since Ant 1.8.0 + */ + private boolean failOnTransformationError = true; + + /** + * whether to fail the build if an error occurs. + * + * @since Ant 1.8.0 + */ + private boolean failOnError = true; + + /** + * Whether the build should fail if the nested resource collection + * is empty. + * + * @since Ant 1.8.0 + */ + private boolean failOnNoResources = true; + + /** + * For evaluating template params + * + * @since Ant 1.9.3 + */ + private XPathFactory xpathFactory; + /** + * For evaluating template params + * + * @since Ant 1.9.3 + */ + private XPath xpath; + + /** + * System properties to set during transformation. + * + * @since Ant 1.8.0 + */ + private final CommandlineJava.SysProperties sysProperties = + new CommandlineJava.SysProperties(); + + /** + * Trace configuration for Xalan2. + * + * @since Ant 1.8.0 + */ + private TraceConfiguration traceConfiguration; + + /** + * Creates a new XSLTProcess Task. + */ + public XSLTProcess() { + } //-- 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(final boolean b) { + performDirectoryScan = b; + } + + /** + * Controls whether the stylesheet is reloaded for every transform. + * + * <p>Setting this to true may get around a bug in certain + * Xalan-J versions, default is false.</p> + * @param b a <code>boolean</code> value + * @since Ant 1.5.2 + */ + public void setReloadStylesheet(final boolean b) { + reuseLoadedStylesheet = !b; + } + + /** + * Defines the mapper to map source to destination files. + * @param mapper the mapper to use + * @exception BuildException if more than one mapper is defined + * @since Ant 1.6.2 + */ + public void addMapper(final Mapper mapper) { + if (mapperElement != null) { + handleError("Cannot define more than one mapper"); + } else { + mapperElement = mapper; + } + } + + /** + * Adds a collection of resources to style in addition to the + * given file or the implicit fileset. + * + * @param rc the collection of resources to style + * @since Ant 1.7 + */ + public void add(final ResourceCollection rc) { + resources.add(rc); + } + + /** + * Add a nested <style> element. + * @param rc the configured Resources object represented as <style>. + * @since Ant 1.7 + */ + public void addConfiguredStyle(final Resources rc) { + if (rc.size() != 1) { + handleError("The style element must be specified with exactly one" + + " nested resource."); + } else { + setXslResource(rc.iterator().next()); + } + } + + /** + * API method to set the XSL Resource. + * @param xslResource Resource to set as the stylesheet. + * @since Ant 1.7 + */ + public void setXslResource(final Resource xslResource) { + this.xslResource = xslResource; + } + + /** + * Adds a nested filenamemapper. + * @param fileNameMapper the mapper to add + * @exception BuildException if more than one mapper is defined + * @since Ant 1.7.0 + */ + public void add(final FileNameMapper fileNameMapper) throws BuildException { + final Mapper mapper = new Mapper(getProject()); + mapper.add(fileNameMapper); + addMapper(mapper); + } + + /** + * Executes the task. + * + * @exception BuildException if there is an execution problem. + * @todo validate that if either in or out is defined, then both are + */ + @Override + public void execute() throws BuildException { + if ("style".equals(getTaskType())) { + log("Warning: the task name <style> is deprecated. Use <xslt> instead.", + Project.MSG_WARN); + } + final File savedBaseDir = baseDir; + + DirectoryScanner scanner; + String[] list; + String[] dirs; + + final String baseMessage = + "specify the stylesheet either as a filename in style attribute " + + "or as a nested resource"; + + if (xslResource == null && xslFile == null) { + handleError(baseMessage); + return; + } + if (xslResource != null && xslFile != null) { + handleError(baseMessage + " but not as both"); + return; + } + if (inFile != null && !inFile.exists()) { + handleError("input file " + inFile + " does not exist"); + return; + } + try { + setupLoader(); + + if (sysProperties.size() > 0) { + sysProperties.setSystem(); + } + + Resource styleResource; + if (baseDir == null) { + baseDir = getProject().getBaseDir(); + } + 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); + + if (xslFile != null) { + // If we enter here, it means that the stylesheet is supplied + // via style attribute + File stylesheet = getProject().resolveFile(xslFile); + if (!stylesheet.exists()) { + final File alternative = FILE_UTILS.resolveFile(baseDir, xslFile); + /* + * shouldn't throw out deprecation warnings before we know, + * the wrong version has been used. + */ + if (alternative.exists()) { + log("DEPRECATED - the 'style' attribute should be " + + "relative to the project's"); + log(" basedir, not the tasks's basedir."); + stylesheet = alternative; + } + } + final FileResource fr = new FileResource(); + fr.setProject(getProject()); + fr.setFile(stylesheet); + styleResource = fr; + } else { + styleResource = xslResource; + } + + if (!styleResource.isExists()) { + handleError("stylesheet " + styleResource + " doesn't exist."); + return; + } + + // if we have an in file and out then process them + if (inFile != null && outFile != null) { + process(inFile, outFile, styleResource); + return; + } + /* + * if we get here, in and out have not been specified, we are + * in batch processing mode. + */ + + //-- make sure destination directory exists... + checkDest(); + + if (useImplicitFileset) { + 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, styleResource); + } + 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, dirs[j] + File.separator + list[i], destDir, + styleResource); + } + } + } + } else { // only resource collections, there better be some + if (resources.size() == 0) { + if (failOnNoResources) { + handleError("no resources specified"); + } + return; + } + } + processResources(styleResource); + } finally { + if (loader != null) { + loader.resetThreadContextLoader(); + loader.cleanup(); + loader = null; + } + if (sysProperties.size() > 0) { + sysProperties.restoreSystem(); + } + 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(final boolean force) { + this.force = force; + } + + /** + * Set the base directory; + * optional, default is the project's basedir. + * + * @param dir the base directory + **/ + public void setBasedir(final 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(final 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(final 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(final 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(final 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(getProject()); + } + 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(final Reference r) { + createClasspath().setRefid(r); + } + + /** + * Set the name of the XSL processor to use; optional, default trax. + * + * @param processor the name of the XSL processor + */ + public void setProcessor(final String processor) { + this.processor = processor; + } + + /** + * Whether to use the implicit fileset. + * + * <p>Set this to false if you want explicit control with nested + * resource collections.</p> + * @param useimplicitfileset set to true if you want to use implicit fileset + * @since Ant 1.7 + */ + public void setUseImplicitFileset(final boolean useimplicitfileset) { + useImplicitFileset = useimplicitfileset; + } + + /** + * Add the catalog to our internal catalog + * + * @param xmlCatalog the XMLCatalog instance to use to look up DTDs + */ + public void addConfiguredXMLCatalog(final XMLCatalog xmlCatalog) { + this.xmlCatalog.addConfiguredXMLCatalog(xmlCatalog); + } + + /** + * Pass the filename of the current processed file as a xsl parameter + * to the transformation. This value sets the name of that xsl parameter. + * + * @param fileNameParameter name of the xsl parameter retrieving the + * current file name + */ + public void setFileNameParameter(final String fileNameParameter) { + this.fileNameParameter = fileNameParameter; + } + + /** + * Pass the directory name of the current processed file as a xsl parameter + * to the transformation. This value sets the name of that xsl parameter. + * + * @param fileDirParameter name of the xsl parameter retrieving the + * current file directory + */ + public void setFileDirParameter(final String fileDirParameter) { + this.fileDirParameter = fileDirParameter; + } + + /** + * Whether to suppress warning messages of the processor. + * + * @since Ant 1.8.0 + */ + public void setSuppressWarnings(final boolean b) { + suppressWarnings = b; + } + + /** + * Whether to suppress warning messages of the processor. + * + * @since Ant 1.8.0 + */ + public boolean getSuppressWarnings() { + return suppressWarnings; + } + + /** + * Whether transformation errors should make the build fail. + * + * @since Ant 1.8.0 + */ + public void setFailOnTransformationError(final boolean b) { + failOnTransformationError = b; + } + + /** + * Whether any errors should make the build fail. + * + * @since Ant 1.8.0 + */ + public void setFailOnError(final boolean b) { + failOnError = b; + } + + /** + * Whether the build should fail if the nested resource collection is empty. + * + * @since Ant 1.8.0 + */ + public void setFailOnNoResources(final boolean b) { + failOnNoResources = b; + } + + /** + * A system property to set during transformation. + * + * @since Ant 1.8.0 + */ + public void addSysproperty(final Environment.Variable sysp) { + sysProperties.addVariable(sysp); + } + + /** + * A set of system properties to set during transformation. + * + * @since Ant 1.8.0 + */ + public void addSyspropertyset(final PropertySet sysp) { + sysProperties.addSyspropertyset(sysp); + } + + /** + * Enables Xalan2 traces and uses the given configuration. + * + * <p>Note that this element doesn't have any effect with a + * processor other than trax or if the Transformer is not Xalan2's + * transformer implementation.</p> + * + * @since Ant 1.8.0 + */ + public TraceConfiguration createTrace() { + if (traceConfiguration != null) { + throw new BuildException("can't have more than one trace" + + " configuration"); + } + traceConfiguration = new TraceConfiguration(); + return traceConfiguration; + } + + /** + * Configuration for Xalan2 traces. + * + * @since Ant 1.8.0 + */ + public TraceConfiguration getTraceConfiguration() { + return traceConfiguration; + } + + /** + * 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(final String proc) throws Exception { + if (proc.equals(PROCESSOR_TRAX)) { + liaison = new org.apache.tools.ant.taskdefs.optional.TraXLiaison(); + } else { + //anything else is a classname + final Class clazz = loadClass(proc); + liaison = (XSLTLiaison) clazz.newInstance(); + } + } + + /** + * Load named class either via the system classloader or a given + * custom classloader. + * + * As a side effect, the loader is set as the thread context 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(final String classname) throws Exception { + setupLoader(); + if (loader == null) { + return Class.forName(classname); + } + return Class.forName(classname, true, loader); + } + + /** + * If a custom classpath has been defined but no loader created + * yet, create the classloader and set it as the context + * classloader. + */ + private void setupLoader() { + if (classpath != null && loader == null) { + loader = getProject().createClassLoader(classpath); + loader.setThreadContextLoader(); + } + } + + /** + * 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(final 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(final File inFile) { + this.inFile = inFile; + } + + /** + * Throws a BuildException if the destination directory hasn't + * been specified. + * @since Ant 1.7 + */ + private void checkDest() { + if (destDir == null) { + handleError("destdir attributes must be set!"); + } + } + + /** + * Styles all existing resources. + * + * @param stylesheet style sheet to use + * @since Ant 1.7 + */ + private void processResources(final Resource stylesheet) { + for (final Resource r : resources) { + if (!r.isExists()) { + continue; + } + File base = baseDir; + String name = r.getName(); + final FileProvider fp = r.as(FileProvider.class); + if (fp != null) { + final FileResource f = ResourceUtils.asFileResource(fp); + base = f.getBaseDir(); + if (base == null) { + name = f.getFile().getAbsolutePath(); + } + } + process(base, name, destDir, stylesheet); + } + } + + /** + * 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(final File baseDir, final String xmlFile, final File destDir, final Resource stylesheet) + throws BuildException { + + File outF = null; + File inF = null; + + try { + final long styleSheetLastModified = stylesheet.getLastModified(); + inF = new File(baseDir, xmlFile); + + if (inF.isDirectory()) { + log("Skipping " + inF + " it is a directory.", Project.MSG_VERBOSE); + return; + } + FileNameMapper mapper = null; + if (mapperElement != null) { + mapper = mapperElement.getImplementation(); + } else { + mapper = new StyleMapper(); + } + + final String[] outFileName = mapper.mapFileName(xmlFile); + if (outFileName == null || outFileName.length == 0) { + log("Skipping " + inFile + " it cannot get mapped to output.", Project.MSG_VERBOSE); + return; + } else if (outFileName == null || outFileName.length > 1) { + log("Skipping " + inFile + " its mapping is ambiguos.", Project.MSG_VERBOSE); + return; + } + outF = new File(destDir, outFileName[0]); + + if (force || inF.lastModified() > outF.lastModified() + || styleSheetLastModified > outF.lastModified()) { + ensureDirectoryFor(outF); + log("Processing " + inF + " to " + outF); + configureLiaison(stylesheet); + setLiaisonDynamicFileParameters(liaison, inF); + liaison.transform(inF, outF); + } + } catch (final 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 (outF != null) { + outF.delete(); + } + handleTransformationError(ex); + } + + } //-- processXML + + /** + * Process the input file to the output file with the given stylesheet. + * + * @param inFile the input file to process. + * @param outFile the destination file. + * @param stylesheet the stylesheet to use. + * @exception BuildException if the processing fails. + */ + private void process(final File inFile, final File outFile, final Resource stylesheet) throws BuildException { + try { + final long styleSheetLastModified = stylesheet.getLastModified(); + 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); + setLiaisonDynamicFileParameters(liaison, inFile); + liaison.transform(inFile, outFile); + } else { + log("Skipping input file " + inFile + " because it is older than output file " + + outFile + " and so is the stylesheet " + stylesheet, Project.MSG_DEBUG); + } + } catch (final Exception ex) { + log("Failed to process " + inFile, Project.MSG_INFO); + if (outFile != null) { + outFile.delete(); + } + handleTransformationError(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(final File targetFile) throws BuildException { + final File directory = targetFile.getParentFile(); + if (!directory.exists()) { + if (!(directory.mkdirs() || directory.isDirectory())) { + handleError("Unable to create directory: " + + directory.getAbsolutePath()); + } + } + } + + /** + * Get the factory instance configured for this processor + * + * @return the factory instance in use + */ + public Factory getFactory() { + return factory; + } + + /** + * Get the XML catalog containing entity definitions + * + * @return the XML catalog for the task. + */ + public XMLCatalog getXMLCatalog() { + xmlCatalog.setProject(getProject()); + return xmlCatalog; + } + + /** + * Get an enumeration on the outputproperties. + * @return the outputproperties + */ + public Enumeration getOutputProperties() { + return outputProperties.elements(); + } + + /** + * Get the Liaison implementation to use in processing. + * + * @return an instance of the XSLTLiaison interface. + */ + protected XSLTLiaison getLiaison() { + // if processor wasn't specified, use TraX. + if (liaison == null) { + if (processor != null) { + try { + resolveProcessor(processor); + } catch (final Exception e) { + handleError(e); + } + } else { + try { + resolveProcessor(PROCESSOR_TRAX); + } catch (final Throwable e1) { + e1.printStackTrace(); + handleError(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() { + final Param p = new Param(); + params.add(p); + return p; + } + + /** + * The Param inner class used to store XSL parameters + */ + public static class Param { + /** The parameter name */ + private String name = null; + + /** The parameter's value */ + private String expression = null; + + /** + * Type of the expression. + * @see ParamType + */ + private String type; + + private Object ifCond; + private Object unlessCond; + private Project project; + + /** + * Set the current project + * + * @param project the current project + */ + public void setProject(final Project project) { + this.project = project; + } + + /** + * Set the parameter name. + * + * @param name the name of the parameter. + */ + public void setName(final String name) { + this.name = name; + } + + /** + * The parameter value - + * can be a primitive type value or an XPath expression. + * @param expression the parameter's value/expression. + * @see #setType(java.lang.String) + */ + public void setExpression(final String expression) { + this.expression = expression; + } + + /** + * @see ParamType + * @since Ant 1.9.3 + */ + public void setType(final String type) { + this.type = type; + } + + /** + * 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's value + * + * @return the parameter value + * @exception BuildException if the value is not set. + * @see #getType() + */ + public String getExpression() throws BuildException { + if (expression == null) { + throw new BuildException("Expression attribute is missing."); + } + return expression; + } + + /** + * @see ParamType + * @since Ant 1.9.3 + */ + public String getType() { + return type; + } + + /** + * Set whether this param should be used. It will be used if + * the expression evaluates to true or the name of a property + * which has been set, otherwise it won't. + * @param ifCond evaluated expression + * @since Ant 1.8.0 + */ + public void setIf(final Object ifCond) { + this.ifCond = ifCond; + } + + /** + * Set whether this param should be used. It will be used if + * the expression evaluates to true or the name of a property + * which has been set, otherwise it won't. + * @param ifProperty evaluated expression + */ + public void setIf(final String ifProperty) { + setIf((Object) ifProperty); + } + + /** + * Set whether this param should NOT be used. It will not be + * used if the expression evaluates to true or the name of a + * property which has been set, otherwise it will be used. + * @param unlessCond evaluated expression + * @since Ant 1.8.0 + */ + public void setUnless(final Object unlessCond) { + this.unlessCond = unlessCond; + } + + /** + * Set whether this param should NOT be used. It will not be + * used if the expression evaluates to true or the name of a + * property which has been set, otherwise it will be used. + * @param unlessProperty evaluated expression + */ + public void setUnless(final String unlessProperty) { + setUnless((Object) unlessProperty); + } + + /** + * Ensures that the param passes the conditions placed + * on it with <code>if</code> and <code>unless</code> properties. + * @return true if the task passes the "if" and "unless" parameters + */ + public boolean shouldUse() { + final PropertyHelper ph = PropertyHelper.getPropertyHelper(project); + return ph.testIfCondition(ifCond) + && ph.testUnlessCondition(unlessCond); + } + } // Param + + /** + * Enum for types of the parameter expression. + * + * <p>The expression can be:</p> + * <ul> + * <li>primitive type that will be parsed from the string value e.g. + * {@linkplain Integer#parseInt(java.lang.String)}</li> + * <li>XPath expression that will be evaluated (outside of the transformed + * document - on empty one) and casted to given type. Inside XPath + * expressions the Ant variables (properties) can be used (as XPath + * variables - e.g. $variable123). n.b. placeholders in form of + * ${variable123} will be substituted with their values before evaluating the + * XPath expression (so it can be used for dynamic XPath function names and + * other hacks).</li> + * </ul> + * <p>The parameter will be then passed to the XSLT template.</p> + * + * <p>Default type (if omitted) is primitive String. So if the expression is e.g + * "true" with no type, in XSLT it will be only a text string, not true + * boolean.</p> + * + * @see Param#setType(java.lang.String) + * @see Param#setExpression(java.lang.String) + * @since Ant 1.9.3 + */ + public enum ParamType { + + STRING, + BOOLEAN, + INT, + LONG, + DOUBLE, + XPATH_STRING, + XPATH_BOOLEAN, + XPATH_NUMBER, + XPATH_NODE, + XPATH_NODESET; + + public static final Map<ParamType, QName> XPATH_TYPES; + + static { + final Map<ParamType, QName> m = new EnumMap<ParamType, QName>(ParamType.class); + m.put(XPATH_STRING, XPathConstants.STRING); + m.put(XPATH_BOOLEAN, XPathConstants.BOOLEAN); + m.put(XPATH_NUMBER, XPathConstants.NUMBER); + m.put(XPATH_NODE, XPathConstants.NODE); + m.put(XPATH_NODESET, XPathConstants.NODESET); + XPATH_TYPES = Collections.unmodifiableMap(m); + } + } + + /** + * Create an instance of an output property to be configured. + * @return the newly created output property. + * @since Ant 1.5 + */ + public OutputProperty createOutputProperty() { + final 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(final 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(final String value) { + this.value = value; + } + } + + /** + * Initialize internal instance of XMLCatalog. + * Initialize XPath for parameter evaluation. + * @throws BuildException on error + */ + @Override + public void init() throws BuildException { + super.init(); + xmlCatalog.setProject(getProject()); + + xpathFactory = XPathFactory.newInstance(); + xpath = xpathFactory.newXPath(); + xpath.setXPathVariableResolver(new XPathVariableResolver() { + public Object resolveVariable(final QName variableName) { + return getProject().getProperty(variableName.toString()); + } + }); + } + + /** + * Loads the stylesheet and set xsl:param parameters. + * + * @param stylesheet the file from which to load the stylesheet. + * @exception BuildException if the stylesheet cannot be loaded. + * @deprecated since Ant 1.7 + */ + @Deprecated + protected void configureLiaison(final File stylesheet) throws BuildException { + final FileResource fr = new FileResource(); + fr.setProject(getProject()); + fr.setFile(stylesheet); + configureLiaison(fr); + } + + /** + * Loads the stylesheet and set xsl:param parameters. + * + * @param stylesheet the resource from which to load the stylesheet. + * @exception BuildException if the stylesheet cannot be loaded. + * @since Ant 1.7 + */ + protected void configureLiaison(final Resource stylesheet) throws BuildException { + if (stylesheetLoaded && reuseLoadedStylesheet) { + return; + } + stylesheetLoaded = true; + + try { + log("Loading stylesheet " + stylesheet, Project.MSG_INFO); + // We call liaison.configure() and then liaison.setStylesheet() + // so that the internal variables of liaison can be set up + if (liaison instanceof XSLTLiaison2) { + ((XSLTLiaison2) liaison).configure(this); + } + if (liaison instanceof XSLTLiaison3) { + // If we are here we can set the stylesheet as a + // resource + ((XSLTLiaison3) liaison).setStylesheet(stylesheet); + } else { + // If we are here we cannot set the stylesheet as + // a resource, but we can set it as a file. So, + // we make an attempt to get it as a file + final FileProvider fp = + stylesheet.as(FileProvider.class); + if (fp != null) { + liaison.setStylesheet(fp.getFile()); + } else { + handleError(liaison.getClass().toString() + + " accepts the stylesheet only as a file"); + return; + } + } + for (final Param p : params) { + if (p.shouldUse()) { + final Object evaluatedParam = evaluateParam(p); + if (liaison instanceof XSLTLiaison4) { + ((XSLTLiaison4)liaison).addParam(p.getName(), evaluatedParam); + } else { + if (evaluatedParam == null || evaluatedParam instanceof String) { + liaison.addParam(p.getName(), (String)evaluatedParam); + } else { + log("XSLTLiaison '" + liaison.getClass().getName() + + "' supports only String parameters. Converting parameter '" + p.getName() + + "' to its String value '" + evaluatedParam, Project.MSG_WARN); + liaison.addParam(p.getName(), String.valueOf(evaluatedParam)); + } + } + } + } + } catch (final Exception ex) { + log("Failed to transform using stylesheet " + stylesheet, Project.MSG_INFO); + handleTransformationError(ex); + } + } + + /** + * Evaluates parameter expression according to its type. + * + * @param param parameter from Ant build file + * @return value to be passed to XSLT as parameter + * @throws IllegalArgumentException if param type is unsupported + * @throws NumberFormatException if expression of numeric type is not + * desired numeric type + * @throws XPathExpressionException if XPath expression can not be compiled + * @since Ant 1.9.3 + */ + private Object evaluateParam(final Param param) throws XPathExpressionException { + final String typeName = param.getType(); + final String expression = param.getExpression(); + + ParamType type; + + if (typeName == null || "".equals(typeName)) { + type = ParamType.STRING; // String is default + } else { + try { + type = ParamType.valueOf(typeName); + } catch (final IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid XSLT parameter type: " + typeName, e); + } + } + + switch (type) { + case STRING: + return expression; + case BOOLEAN: + return Boolean.parseBoolean(expression); + case DOUBLE: + return Double.parseDouble(expression); + case INT: + return Integer.parseInt(expression); + case LONG: + return Long.parseLong(expression); + default: // XPath expression + final QName xpathType = ParamType.XPATH_TYPES.get(type); + if (xpathType == null) { + throw new IllegalArgumentException("Invalid XSLT parameter type: " + typeName); + } else { + final XPathExpression xpe = xpath.compile(expression); + // null = evaluate XPath on empty XML document + return xpe.evaluate((Object) null, xpathType); + } + } + } + + /** + * Sets file parameter(s) for directory and filename if the attribute + * 'filenameparameter' or 'filedirparameter' are set in the task. + * + * @param liaison to change parameters for + * @param inFile to get the additional file information from + * @throws Exception if an exception occurs on filename lookup + * + * @since Ant 1.7 + */ + private void setLiaisonDynamicFileParameters( + final XSLTLiaison liaison, final File inFile) throws Exception { + if (fileNameParameter != null) { + liaison.addParam(fileNameParameter, inFile.getName()); + } + if (fileDirParameter != null) { + final String fileName = FileUtils.getRelativePath(baseDir, inFile); + final File file = new File(fileName); + // Give always a slash as file separator, so the stylesheet could be sure about that + // Use '.' so a dir+"/"+name would not result in an absolute path + liaison.addParam(fileDirParameter, file.getParent() != null ? file.getParent().replace( + '\\', '/') : "."); + } + } + + /** + * Create the factory element to configure a trax liaison. + * @return the newly created factory element. + * @throws BuildException if the element is created more than one time. + */ + public Factory createFactory() throws BuildException { + if (factory != null) { + handleError("'factory' element must be unique"); + } else { + factory = new Factory(); + } + return factory; + } + + /** + * Throws an exception with the given message if failOnError is + * true, otherwise logs the message using the WARN level. + * + * @since Ant 1.8.0 + */ + protected void handleError(final String msg) { + if (failOnError) { + throw new BuildException(msg, getLocation()); + } + log(msg, Project.MSG_WARN); + } + + + /** + * Throws an exception with the given nested exception if + * failOnError is true, otherwise logs the message using the WARN + * level. + * + * @since Ant 1.8.0 + */ + protected void handleError(final Throwable ex) { + if (failOnError) { + throw new BuildException(ex); + } else { + log("Caught an exception: " + ex, Project.MSG_WARN); + } + } + + /** + * Throws an exception with the given nested exception if + * failOnError and failOnTransformationError are true, otherwise + * logs the message using the WARN level. + * + * @since Ant 1.8.0 + */ + protected void handleTransformationError(final Exception ex) { + if (failOnError && failOnTransformationError) { + throw new BuildException(ex); + } else { + log("Caught an error during transformation: " + ex, + Project.MSG_WARN); + } + } + + /** + * The factory element to configure a transformer factory + * @since Ant 1.6 + */ + public static class Factory { + + /** the factory class name to use for TraXLiaison */ + private String name; + + /** + * the list of factory attributes to use for TraXLiaison + */ + private final Vector attributes = new Vector(); + + /** + * @return the name of the factory. + */ + public String getName() { + return name; + } + + /** + * Set the name of the factory + * @param name the name of the factory. + */ + public void setName(final String name) { + this.name = name; + } + + /** + * Create an instance of a factory attribute. + * @param attr the newly created factory attribute + */ + public void addAttribute(final Attribute attr) { + attributes.addElement(attr); + } + + /** + * return the attribute elements. + * @return the enumeration of attributes + */ + public Enumeration getAttributes() { + return attributes.elements(); + } + + /** + * A JAXP factory attribute. This is mostly processor specific, for + * example for Xalan 2.3+, the following attributes could be set: + * <ul> + * <li>http://xml.apache.org/xalan/features/optimize (true|false) </li> + * <li>http://xml.apache.org/xalan/features/incremental (true|false) </li> + * </ul> + */ + public static class Attribute implements DynamicConfigurator { + + /** attribute name, mostly processor specific */ + private String name; + + /** attribute value, often a boolean string */ + private Object value; + + /** + * @return the attribute name. + */ + public String getName() { + return name; + } + + /** + * @return the output property value. + */ + public Object getValue() { + return value; + } + + /** + * Not used. + * @param name not used + * @return null + * @throws BuildException never + */ + public Object createDynamicElement(final String name) throws BuildException { + return null; + } + + /** + * Set an attribute. + * Only "name" and "value" are supported as names. + * @param name the name of the attribute + * @param value the value of the attribute + * @throws BuildException on error + */ + public void setDynamicAttribute(final String name, final String value) throws BuildException { + // only 'name' and 'value' exist. + if ("name".equalsIgnoreCase(name)) { + this.name = value; + } else if ("value".equalsIgnoreCase(name)) { + // a value must be of a given type + // say boolean|integer|string that are mostly used. + if ("true".equalsIgnoreCase(value)) { + this.value = Boolean.TRUE; + } else if ("false".equalsIgnoreCase(value)) { + this.value = Boolean.FALSE; + } else { + try { + this.value = new Integer(value); + } catch (final NumberFormatException e) { + this.value = value; + } + } + } else { + throw new BuildException("Unsupported attribute: " + name); + } + } + } // -- class Attribute + } // -- class Factory + + /** + * Mapper implementation of the "traditional" way <xslt> + * mapped filenames. + * + * <p>If the file has an extension, chop it off. Append whatever + * the user has specified as extension or ".html".</p> + * + * @since Ant 1.6.2 + */ + private class StyleMapper implements FileNameMapper { + public void setFrom(final String from) { + } + public void setTo(final String to) { + } + public String[] mapFileName(String xmlFile) { + final int dotPos = xmlFile.lastIndexOf('.'); + if (dotPos > 0) { + xmlFile = xmlFile.substring(0, dotPos); + } + return new String[] {xmlFile + targetExtension}; + } + } + + /** + * Configuration for Xalan2 traces. + * + * @since Ant 1.8.0 + */ + public final class TraceConfiguration { + private boolean elements, extension, generation, selection, templates; + + /** + * Set to true if the listener is to print events that occur + * as each node is 'executed' in the stylesheet. + */ + public void setElements(final boolean b) { + elements = b; + } + + /** + * True if the listener is to print events that occur as each + * node is 'executed' in the stylesheet. + */ + public boolean getElements() { + return elements; + } + + /** + * Set to true if the listener is to print information after + * each extension event. + */ + public void setExtension(final boolean b) { + extension = b; + } + + /** + * True if the listener is to print information after each + * extension event. + */ + public boolean getExtension() { + return extension; + } + + /** + * Set to true if the listener is to print information after + * each result-tree generation event. + */ + public void setGeneration(final boolean b) { + generation = b; + } + + /** + * True if the listener is to print information after each + * result-tree generation event. + */ + public boolean getGeneration() { + return generation; + } + + /** + * Set to true if the listener is to print information after + * each selection event. + */ + public void setSelection(final boolean b) { + selection = b; + } + + /** + * True if the listener is to print information after each + * selection event. + */ + public boolean getSelection() { + return selection; + } + + /** + * Set to true if the listener is to print an event whenever a + * template is invoked. + */ + public void setTemplates(final boolean b) { + templates = b; + } + + /** + * True if the listener is to print an event whenever a + * template is invoked. + */ + public boolean getTemplates() { + return templates; + } + + /** + * The stream to write traces to. + */ + public java.io.OutputStream getOutputStream() { + return new LogOutputStream(XSLTProcess.this); + } + } + +} |