blob: 78c556bb0009d6e29c34b2825063aa13550f1da4 [file] [log] [blame]
/*
* 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
*
* https://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.io.OutputStream;
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 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.ProjectComponent;
import org.apache.tools.ant.PropertyHelper;
import org.apache.tools.ant.taskdefs.optional.TraXLiaison;
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.ClasspathUtils;
import org.apache.tools.ant.util.FileNameMapper;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.ResourceUtils;
import org.apache.tools.ant.util.StringUtils;
/**
* 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 {
/**
* The default processor is trax
* @since Ant 1.7
*/
public static final String PROCESSOR_TRAX = "trax";
/** Utilities used for file operations */
private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
/** 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<>();
/** 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 List<OutputProperty> outputProperties = new Vector<>();
/** for resolving entities such as dtds */
private final XMLCatalog xmlCatalog = new XMLCatalog();
/**
* 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 &lt;classpath&gt; - 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;
/**
* 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;
/**
* 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 &lt;style&gt; element.
* @param rc the configured Resources object represented as &lt;style&gt;.
* @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;
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) {
DirectoryScanner scanner = getDirectoryScanner(baseDir);
log("Transforming into " + destDir, Project.MSG_INFO);
// Process all the files marked for styling
for (String element : scanner.getIncludedFiles()) {
process(baseDir, element, destDir, styleResource);
}
if (performDirectoryScan) {
// Process all the directories marked for styling
for (String dir : scanner.getIncludedDirectories()) {
String[] elements = new File(baseDir, dir).list();
if (elements != null) {
for (String element : new File(baseDir, dir).list()) {
process(baseDir, dir + File.separator + element, destDir,
styleResource);
}
}
}
}
} else if (resources.isEmpty()) {
// only resource collections, there better be some
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 <code>in</code> and <code>out</code> 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.
*
* @param b boolean
* @since Ant 1.8.0
*/
public void setSuppressWarnings(final boolean b) {
suppressWarnings = b;
}
/**
* Whether to suppress warning messages of the processor.
*
* @return boolean
* @since Ant 1.8.0
*/
public boolean getSuppressWarnings() {
return suppressWarnings;
}
/**
* Whether transformation errors should make the build fail.
*
* @param b boolean
* @since Ant 1.8.0
*/
public void setFailOnTransformationError(final boolean b) {
failOnTransformationError = b;
}
/**
* Whether any errors should make the build fail.
*
* @param b boolean
* @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.
*
* @param b boolean
* @since Ant 1.8.0
*/
public void setFailOnNoResources(final boolean b) {
failOnNoResources = b;
}
/**
* A system property to set during transformation.
*
* @param sysp Environment.Variable
* @since Ant 1.8.0
*/
public void addSysproperty(final Environment.Variable sysp) {
sysProperties.addVariable(sysp);
}
/**
* A set of system properties to set during transformation.
*
* @param sysp PropertySet
* @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>
*
* @return TraceConfiguration
* @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.
*
* @return TraceConfiguration
* @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 (PROCESSOR_TRAX.equals(proc)) {
liaison = new TraXLiaison();
} else {
//anything else is a classname
final Class<? extends XSLTLiaison> clazz = loadClass(proc).asSubclass(XSLTLiaison.class);
liaison = clazz.getDeclaredConstructor().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.
*/
private Class<?> loadClass(final String classname) throws ClassNotFoundException {
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
* <code>in</code> attribute; required if <code>in</code> 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 <code>out</code> attribute; required if <code>out</code> 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;
try {
final long styleSheetLastModified = stylesheet.getLastModified();
File inF = new File(baseDir, xmlFile);
if (inF.isDirectory()) {
log("Skipping " + inF + " it is a directory.", Project.MSG_VERBOSE);
return;
}
FileNameMapper mapper = mapperElement == null ? new StyleMapper()
: mapperElement.getImplementation();
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;
}
if (outFileName.length > 1) {
log("Skipping " + inFile + " its mapping is ambiguous.", 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() && !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<OutputProperty> getOutputProperties() {
return Collections.enumeration(outputProperties);
}
/**
* 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) {
log(StringUtils.getStackTrace(e1), Project.MSG_ERR);
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;
}
/**
* @param type String
* @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;
}
/**
* @return String
* @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.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.add(p);
return p;
}
/**
* Specify how the result tree should be output as specified
* in the <a href="https://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(
variableName -> 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.getName(), 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 || typeName.isEmpty()) {
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);
}
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 { //NOSONAR
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.
*
* @param msg String
* @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.
*
* @param ex Throwable
* @since Ant 1.8.0
*/
protected void handleError(final Throwable ex) {
if (failOnError) {
throw new BuildException(ex);
}
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.
*
* @param ex Exception
* @since Ant 1.8.0
*/
protected void handleTransformationError(final Exception ex) {
if (failOnError && failOnTransformationError) {
throw new BuildException(ex);
}
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 List<Attribute> attributes = new ArrayList<>();
/**
* the list of factory features to use for TraXLiaison
*/
private final List<Feature> features = new ArrayList<>();
/**
* @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.add(attr);
}
/**
* return the attribute elements.
* @return the enumeration of attributes
*/
public Enumeration<Attribute> getAttributes() {
return Collections.enumeration(attributes);
}
/**
* Create an instance of a factory feature.
* @param feature the newly created feature
* @since Ant 1.9.8
*/
public void addFeature(final Feature feature) {
features.add(feature);
}
/**
* The configured features.
* @since Ant 1.9.8
*
* @return Iterable&lt;Feature&gt;
*/
public Iterable<Feature> getFeatures() {
return features;
}
/**
* 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 extends ProjectComponent
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 attribute value.
*/
public Object getValue() {
return value;
}
/**
* Not used.
* @param name not used
* @return null
* @throws BuildException never
*/
@Override
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
*/
@Override
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 = Integer.valueOf(value);
} catch (final NumberFormatException e) {
this.value = value;
}
}
} else if ("valueref".equalsIgnoreCase(name)) {
this.value = getProject().getReference(value);
} else if ("classloaderforpath".equalsIgnoreCase(name)) {
this.value =
ClasspathUtils.getClassLoaderForPath(getProject(),
new Reference(getProject(),
value));
} else {
throw new BuildException("Unsupported attribute: %s", name);
}
}
} // -- class Attribute
/**
* A feature for the TraX factory.
* @since Ant 1.9.8
*/
public static class Feature {
private String name;
private boolean value;
public Feature() {
}
public Feature(String name, boolean value) {
this.name = name;
this.value = value;
}
/**
* @param name the feature name.
*/
public void setName(String name) {
this.name = name;
}
/**
* @param value the feature value.
*/
public void setValue(boolean value) {
this.value = value;
}
/**
* @return the feature name.
*/
public String getName() {
return name;
}
/**
* @return the feature value.
*/
public boolean getValue() {
return value;
}
}
} // -- class Factory
/**
* Mapper implementation of the "traditional" way &lt;xslt&gt;
* 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 {
@Override
public void setFrom(final String from) {
}
@Override
public void setTo(final String to) {
}
@Override
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.
*
* @param b boolean
*/
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.
*
* @return boolean
*/
public boolean getElements() {
return elements;
}
/**
* Set to true if the listener is to print information after
* each extension event.
*
* @param b boolean
*/
public void setExtension(final boolean b) {
extension = b;
}
/**
* True if the listener is to print information after each
* extension event.
*
* @return boolean
*/
public boolean getExtension() {
return extension;
}
/**
* Set to true if the listener is to print information after
* each result-tree generation event.
*
* @param b boolean
*/
public void setGeneration(final boolean b) {
generation = b;
}
/**
* True if the listener is to print information after each
* result-tree generation event.
*
* @return boolean
*/
public boolean getGeneration() {
return generation;
}
/**
* Set to true if the listener is to print information after
* each selection event.
*
* @param b boolean
*/
public void setSelection(final boolean b) {
selection = b;
}
/**
* True if the listener is to print information after each
* selection event.
*
* @return boolean
*/
public boolean getSelection() {
return selection;
}
/**
* Set to true if the listener is to print an event whenever a
* template is invoked.
*
* @param b boolean
*/
public void setTemplates(final boolean b) {
templates = b;
}
/**
* True if the listener is to print an event whenever a
* template is invoked.
*
* @return boolean
*/
public boolean getTemplates() {
return templates;
}
/**
* The stream to write traces to.
*
* @return OutputStream
*/
public OutputStream getOutputStream() {
return new LogOutputStream(XSLTProcess.this);
}
}
}