/* * 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.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.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 <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; /** * 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 <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; 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()) { 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 <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 (PROCESSOR_TRAX.equals(proc)) { liaison = new org.apache.tools.ant.taskdefs.optional.TraXLiaison(); } else { //anything else is a classname final Class<? extends XSLTLiaison> clazz = loadClass(proc).asSubclass(XSLTLiaison.class); liaison = 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 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 * <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; 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()) { 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<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; } /** * @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.add(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( 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, 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. * * @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); } 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); } 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 */ 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 <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 { @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. */ 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 OutputStream getOutputStream() { return new LogOutputStream(XSLTProcess.this); } } }