0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.tools.ant.taskdefs;
0020:
0021: import java.io.File;
0022: import java.util.Enumeration;
0023: import java.util.Iterator;
0024: import java.util.Vector;
0025: import org.apache.tools.ant.AntClassLoader;
0026: import org.apache.tools.ant.BuildException;
0027: import org.apache.tools.ant.DirectoryScanner;
0028: import org.apache.tools.ant.DynamicConfigurator;
0029: import org.apache.tools.ant.Project;
0030: import org.apache.tools.ant.types.Mapper;
0031: import org.apache.tools.ant.types.Path;
0032: import org.apache.tools.ant.types.Reference;
0033: import org.apache.tools.ant.types.Resource;
0034: import org.apache.tools.ant.types.ResourceCollection;
0035: import org.apache.tools.ant.types.XMLCatalog;
0036: import org.apache.tools.ant.types.resources.FileResource;
0037: import org.apache.tools.ant.types.resources.Resources;
0038: import org.apache.tools.ant.types.resources.Union;
0039: import org.apache.tools.ant.util.FileNameMapper;
0040: import org.apache.tools.ant.util.FileUtils;
0041:
0042: /**
0043: * Processes a set of XML documents via XSLT. This is
0044: * useful for building views of XML based documentation.
0045: *
0046: *
0047: * @since Ant 1.1
0048: *
0049: * @ant.task name="xslt" category="xml"
0050: */
0051:
0052: public class XSLTProcess extends MatchingTask implements XSLTLogger {
0053: /** destination directory */
0054: private File destDir = null;
0055:
0056: /** where to find the source XML file, default is the project's basedir */
0057: private File baseDir = null;
0058:
0059: /** XSL stylesheet as a filename */
0060: private String xslFile = null;
0061:
0062: /** XSL stylesheet as a {@link org.apache.tools.ant.types.Resource} */
0063: private Resource xslResource = null;
0064:
0065: /** extension of the files produced by XSL processing */
0066: private String targetExtension = ".html";
0067:
0068: /** name for XSL parameter containing the filename */
0069: private String fileNameParameter = null;
0070:
0071: /** name for XSL parameter containing the file directory */
0072: private String fileDirParameter = null;
0073:
0074: /** additional parameters to be passed to the stylesheets */
0075: private Vector params = new Vector();
0076:
0077: /** Input XML document to be used */
0078: private File inFile = null;
0079:
0080: /** Output file */
0081: private File outFile = null;
0082:
0083: /** The name of the XSL processor to use */
0084: private String processor;
0085:
0086: /** Classpath to use when trying to load the XSL processor */
0087: private Path classpath = null;
0088:
0089: /** The Liason implementation to use to communicate with the XSL
0090: * processor */
0091: private XSLTLiaison liaison;
0092:
0093: /** Flag which indicates if the stylesheet has been loaded into
0094: * the processor */
0095: private boolean stylesheetLoaded = false;
0096:
0097: /** force output of target files even if they already exist */
0098: private boolean force = false;
0099:
0100: /** XSL output properties to be used */
0101: private Vector outputProperties = new Vector();
0102:
0103: /** for resolving entities such as dtds */
0104: private XMLCatalog xmlCatalog = new XMLCatalog();
0105:
0106: /** Name of the TRAX Liaison class */
0107: private static final String TRAX_LIAISON_CLASS = "org.apache.tools.ant.taskdefs.optional.TraXLiaison";
0108:
0109: /** Utilities used for file operations */
0110: private static final FileUtils FILE_UTILS = FileUtils
0111: .getFileUtils();
0112:
0113: /**
0114: * Whether to style all files in the included directories as well.
0115: *
0116: * @since Ant 1.5
0117: */
0118: private boolean performDirectoryScan = true;
0119:
0120: /**
0121: * factory element for TraX processors only
0122: * @since Ant 1.6
0123: */
0124: private Factory factory = null;
0125:
0126: /**
0127: * whether to reuse Transformer if transforming multiple files.
0128: * @since 1.5.2
0129: */
0130: private boolean reuseLoadedStylesheet = true;
0131:
0132: /**
0133: * AntClassLoader for the nested <classpath> - if set.
0134: *
0135: * <p>We keep this here in order to reset the context classloader
0136: * in execute. We can't use liaison.getClass().getClassLoader()
0137: * since the actual liaison class may have been loaded by a loader
0138: * higher up (system classloader, for example).</p>
0139: *
0140: * @since Ant 1.6.2
0141: */
0142: private AntClassLoader loader = null;
0143:
0144: /**
0145: * Mapper to use when a set of files gets processed.
0146: *
0147: * @since Ant 1.6.2
0148: */
0149: private Mapper mapperElement = null;
0150:
0151: /**
0152: * Additional resource collections to process.
0153: *
0154: * @since Ant 1.7
0155: */
0156: private Union resources = new Union();
0157:
0158: /**
0159: * Whether to use the implicit fileset.
0160: *
0161: * @since Ant 1.7
0162: */
0163: private boolean useImplicitFileset = true;
0164:
0165: /**
0166: * The default processor is trax
0167: * @since Ant 1.7
0168: */
0169: public static final String PROCESSOR_TRAX = "trax";
0170:
0171: /**
0172: * Creates a new XSLTProcess Task.
0173: */
0174: public XSLTProcess() {
0175: } //-- XSLTProcess
0176:
0177: /**
0178: * Whether to style all files in the included directories as well;
0179: * optional, default is true.
0180: *
0181: * @param b true if files in included directories are processed.
0182: * @since Ant 1.5
0183: */
0184: public void setScanIncludedDirectories(boolean b) {
0185: performDirectoryScan = b;
0186: }
0187:
0188: /**
0189: * Controls whether the stylesheet is reloaded for every transform.
0190: *
0191: * <p>Setting this to true may get around a bug in certain
0192: * Xalan-J versions, default is false.</p>
0193: * @param b a <code>boolean</code> value
0194: * @since Ant 1.5.2
0195: */
0196: public void setReloadStylesheet(boolean b) {
0197: reuseLoadedStylesheet = !b;
0198: }
0199:
0200: /**
0201: * Defines the mapper to map source to destination files.
0202: * @param mapper the mapper to use
0203: * @exception BuildException if more than one mapper is defined
0204: * @since Ant 1.6.2
0205: */
0206: public void addMapper(Mapper mapper) {
0207: if (mapperElement != null) {
0208: throw new BuildException(
0209: "Cannot define more than one mapper", getLocation());
0210: }
0211: mapperElement = mapper;
0212: }
0213:
0214: /**
0215: * Adds a collection of resources to style in addition to the
0216: * given file or the implicit fileset.
0217: *
0218: * @param rc the collection of resources to style
0219: * @since Ant 1.7
0220: */
0221: public void add(ResourceCollection rc) {
0222: resources.add(rc);
0223: }
0224:
0225: /**
0226: * Add a nested <style> element.
0227: * @param rc the configured Resources object represented as <style>.
0228: * @since Ant 1.7
0229: */
0230: public void addConfiguredStyle(Resources rc) {
0231: if (rc.size() != 1) {
0232: throw new BuildException(
0233: "The style element must be specified"
0234: + " with exactly one nested resource.");
0235: }
0236: setXslResource((Resource) rc.iterator().next());
0237: }
0238:
0239: /**
0240: * API method to set the XSL Resource.
0241: * @param xslResource Resource to set as the stylesheet.
0242: * @since Ant 1.7
0243: */
0244: public void setXslResource(Resource xslResource) {
0245: this .xslResource = xslResource;
0246: }
0247:
0248: /**
0249: * Adds a nested filenamemapper.
0250: * @param fileNameMapper the mapper to add
0251: * @exception BuildException if more than one mapper is defined
0252: * @since Ant 1.7.0
0253: */
0254: public void add(FileNameMapper fileNameMapper)
0255: throws BuildException {
0256: Mapper mapper = new Mapper(getProject());
0257: mapper.add(fileNameMapper);
0258: addMapper(mapper);
0259: }
0260:
0261: /**
0262: * Executes the task.
0263: *
0264: * @exception BuildException if there is an execution problem.
0265: * @todo validate that if either in or our is defined, then both are
0266: */
0267: public void execute() throws BuildException {
0268: if ("style".equals(getTaskType())) {
0269: log(
0270: "Warning: the task name <style> is deprecated. Use <xslt> instead.",
0271: Project.MSG_WARN);
0272: }
0273:
0274: File savedBaseDir = baseDir;
0275:
0276: DirectoryScanner scanner;
0277: String[] list;
0278: String[] dirs;
0279:
0280: if (xslResource == null && xslFile == null) {
0281: throw new BuildException("specify the "
0282: + "stylesheet either as a filename in style "
0283: + "attribute or as a nested resource",
0284: getLocation());
0285:
0286: }
0287: if (xslResource != null && xslFile != null) {
0288: throw new BuildException("specify the "
0289: + "stylesheet either as a filename in style "
0290: + "attribute or as a nested resource but not "
0291: + "as both", getLocation());
0292: }
0293:
0294: if (inFile != null && !inFile.exists()) {
0295: throw new BuildException("input file " + inFile.toString()
0296: + " does not exist", getLocation());
0297: }
0298:
0299: try {
0300: if (baseDir == null) {
0301: baseDir = getProject().resolveFile(".");
0302: }
0303:
0304: liaison = getLiaison();
0305:
0306: // check if liaison wants to log errors using us as logger
0307: if (liaison instanceof XSLTLoggerAware) {
0308: ((XSLTLoggerAware) liaison).setLogger(this );
0309: }
0310:
0311: log("Using " + liaison.getClass().toString(),
0312: Project.MSG_VERBOSE);
0313:
0314: if (xslFile != null) {
0315: // If we enter here, it means that the stylesheet is supplied
0316: // via style attribute
0317: File stylesheet = getProject().resolveFile(xslFile);
0318: if (!stylesheet.exists()) {
0319: stylesheet = FILE_UTILS.resolveFile(baseDir,
0320: xslFile);
0321: /*
0322: * shouldn't throw out deprecation warnings before we know,
0323: * the wrong version has been used.
0324: */
0325: if (stylesheet.exists()) {
0326: log("DEPRECATED - the 'style' attribute should be relative "
0327: + "to the project's");
0328: log(" basedir, not the tasks's basedir.");
0329: }
0330: }
0331: FileResource fr = new FileResource();
0332: fr.setProject(getProject());
0333: fr.setFile(stylesheet);
0334: xslResource = fr;
0335: }
0336:
0337: // if we have an in file and out then process them
0338: if (inFile != null && outFile != null) {
0339: process(inFile, outFile, xslResource);
0340: return;
0341: }
0342:
0343: /*
0344: * if we get here, in and out have not been specified, we are
0345: * in batch processing mode.
0346: */
0347:
0348: //-- make sure destination directory exists...
0349: checkDest();
0350:
0351: if (useImplicitFileset) {
0352: scanner = getDirectoryScanner(baseDir);
0353: log("Transforming into " + destDir, Project.MSG_INFO);
0354:
0355: // Process all the files marked for styling
0356: list = scanner.getIncludedFiles();
0357: for (int i = 0; i < list.length; ++i) {
0358: process(baseDir, list[i], destDir, xslResource);
0359: }
0360: if (performDirectoryScan) {
0361: // Process all the directories marked for styling
0362: dirs = scanner.getIncludedDirectories();
0363: for (int j = 0; j < dirs.length; ++j) {
0364: list = new File(baseDir, dirs[j]).list();
0365: for (int i = 0; i < list.length; ++i) {
0366: process(baseDir, dirs[j] + File.separator
0367: + list[i], destDir, xslResource);
0368: }
0369: }
0370: }
0371: } else { // only resource collections, there better be some
0372: if (resources.size() == 0) {
0373: throw new BuildException("no resources specified");
0374: }
0375: }
0376: processResources(xslResource);
0377: } finally {
0378: if (loader != null) {
0379: loader.resetThreadContextLoader();
0380: loader.cleanup();
0381: loader = null;
0382: }
0383: liaison = null;
0384: stylesheetLoaded = false;
0385: baseDir = savedBaseDir;
0386: }
0387: }
0388:
0389: /**
0390: * Set whether to check dependencies, or always generate;
0391: * optional, default is false.
0392: *
0393: * @param force true if always generate.
0394: */
0395: public void setForce(boolean force) {
0396: this .force = force;
0397: }
0398:
0399: /**
0400: * Set the base directory;
0401: * optional, default is the project's basedir.
0402: *
0403: * @param dir the base directory
0404: **/
0405: public void setBasedir(File dir) {
0406: baseDir = dir;
0407: }
0408:
0409: /**
0410: * Set the destination directory into which the XSL result
0411: * files should be copied to;
0412: * required, unless <tt>in</tt> and <tt>out</tt> are
0413: * specified.
0414: * @param dir the name of the destination directory
0415: **/
0416: public void setDestdir(File dir) {
0417: destDir = dir;
0418: }
0419:
0420: /**
0421: * Set the desired file extension to be used for the target;
0422: * optional, default is html.
0423: * @param name the extension to use
0424: **/
0425: public void setExtension(String name) {
0426: targetExtension = name;
0427: }
0428:
0429: /**
0430: * Name of the stylesheet to use - given either relative
0431: * to the project's basedir or as an absolute path; required.
0432: *
0433: * @param xslFile the stylesheet to use
0434: */
0435: public void setStyle(String xslFile) {
0436: this .xslFile = xslFile;
0437: }
0438:
0439: /**
0440: * Set the optional classpath to the XSL processor
0441: *
0442: * @param classpath the classpath to use when loading the XSL processor
0443: */
0444: public void setClasspath(Path classpath) {
0445: createClasspath().append(classpath);
0446: }
0447:
0448: /**
0449: * Set the optional classpath to the XSL processor
0450: *
0451: * @return a path instance to be configured by the Ant core.
0452: */
0453: public Path createClasspath() {
0454: if (classpath == null) {
0455: classpath = new Path(getProject());
0456: }
0457: return classpath.createPath();
0458: }
0459:
0460: /**
0461: * Set the reference to an optional classpath to the XSL processor
0462: *
0463: * @param r the id of the Ant path instance to act as the classpath
0464: * for loading the XSL processor
0465: */
0466: public void setClasspathRef(Reference r) {
0467: createClasspath().setRefid(r);
0468: }
0469:
0470: /**
0471: * Set the name of the XSL processor to use; optional, default trax.
0472: * Other values are "xalan" for Xalan1
0473: *
0474: * @param processor the name of the XSL processor
0475: */
0476: public void setProcessor(String processor) {
0477: this .processor = processor;
0478: }
0479:
0480: /**
0481: * Whether to use the implicit fileset.
0482: *
0483: * <p>Set this to false if you want explicit control with nested
0484: * resource collections.</p>
0485: * @param useimplicitfileset set to true if you want to use implicit fileset
0486: * @since Ant 1.7
0487: */
0488: public void setUseImplicitFileset(boolean useimplicitfileset) {
0489: useImplicitFileset = useimplicitfileset;
0490: }
0491:
0492: /**
0493: * Add the catalog to our internal catalog
0494: *
0495: * @param xmlCatalog the XMLCatalog instance to use to look up DTDs
0496: */
0497: public void addConfiguredXMLCatalog(XMLCatalog xmlCatalog) {
0498: this .xmlCatalog.addConfiguredXMLCatalog(xmlCatalog);
0499: }
0500:
0501: /**
0502: * Pass the filename of the current processed file as a xsl parameter
0503: * to the transformation. This value sets the name of that xsl parameter.
0504: *
0505: * @param fileNameParameter name of the xsl parameter retrieving the
0506: * current file name
0507: */
0508: public void setFileNameParameter(String fileNameParameter) {
0509: this .fileNameParameter = fileNameParameter;
0510: }
0511:
0512: /**
0513: * Pass the directory name of the current processed file as a xsl parameter
0514: * to the transformation. This value sets the name of that xsl parameter.
0515: *
0516: * @param fileDirParameter name of the xsl parameter retrieving the
0517: * current file directory
0518: */
0519: public void setFileDirParameter(String fileDirParameter) {
0520: this .fileDirParameter = fileDirParameter;
0521: }
0522:
0523: /**
0524: * Load processor here instead of in setProcessor - this will be
0525: * called from within execute, so we have access to the latest
0526: * classpath.
0527: *
0528: * @param proc the name of the processor to load.
0529: * @exception Exception if the processor cannot be loaded.
0530: */
0531: private void resolveProcessor(String proc) throws Exception {
0532: String classname;
0533: if (proc.equals(PROCESSOR_TRAX)) {
0534: classname = TRAX_LIAISON_CLASS;
0535: } else {
0536: //anything else is a classname
0537: classname = proc;
0538: }
0539: Class clazz = loadClass(classname);
0540: liaison = (XSLTLiaison) clazz.newInstance();
0541: }
0542:
0543: /**
0544: * Load named class either via the system classloader or a given
0545: * custom classloader.
0546: *
0547: * As a side effect, the loader is set as the thread context classloader
0548: * @param classname the name of the class to load.
0549: * @return the requested class.
0550: * @exception Exception if the class could not be loaded.
0551: */
0552: private Class loadClass(String classname) throws Exception {
0553: if (classpath == null) {
0554: return Class.forName(classname);
0555: } else {
0556: loader = getProject().createClassLoader(classpath);
0557: loader.setThreadContextLoader();
0558: Class c = Class.forName(classname, true, loader);
0559: return c;
0560: }
0561: }
0562:
0563: /**
0564: * Specifies the output name for the styled result from the
0565: * <tt>in</tt> attribute; required if <tt>in</tt> is set
0566: *
0567: * @param outFile the output File instance.
0568: */
0569: public void setOut(File outFile) {
0570: this .outFile = outFile;
0571: }
0572:
0573: /**
0574: * specifies a single XML document to be styled. Should be used
0575: * with the <tt>out</tt> attribute; ; required if <tt>out</tt> is set
0576: *
0577: * @param inFile the input file
0578: */
0579: public void setIn(File inFile) {
0580: this .inFile = inFile;
0581: }
0582:
0583: /**
0584: * Throws a BuildException if the destination directory hasn't
0585: * been specified.
0586: * @since Ant 1.7
0587: */
0588: private void checkDest() {
0589: if (destDir == null) {
0590: String msg = "destdir attributes must be set!";
0591: throw new BuildException(msg);
0592: }
0593: }
0594:
0595: /**
0596: * Styles all existing resources.
0597: *
0598: * @since Ant 1.7
0599: */
0600: private void processResources(Resource stylesheet) {
0601: Iterator iter = resources.iterator();
0602: while (iter.hasNext()) {
0603: Resource r = (Resource) iter.next();
0604: if (!r.isExists()) {
0605: continue;
0606: }
0607: File base = baseDir;
0608: String name = r.getName();
0609: if (r instanceof FileResource) {
0610: FileResource f = (FileResource) r;
0611: base = f.getBaseDir();
0612: if (base == null) {
0613: name = f.getFile().getAbsolutePath();
0614: }
0615: }
0616: process(base, name, destDir, stylesheet);
0617: }
0618: }
0619:
0620: /**
0621: * Processes the given input XML file and stores the result
0622: * in the given resultFile.
0623: *
0624: * @param baseDir the base directory for resolving files.
0625: * @param xmlFile the input file
0626: * @param destDir the destination directory
0627: * @param stylesheet the stylesheet to use.
0628: * @exception BuildException if the processing fails.
0629: */
0630: private void process(File baseDir, String xmlFile, File destDir,
0631: Resource stylesheet) throws BuildException {
0632:
0633: File outF = null;
0634: File inF = null;
0635:
0636: try {
0637: long styleSheetLastModified = stylesheet.getLastModified();
0638: inF = new File(baseDir, xmlFile);
0639:
0640: if (inF.isDirectory()) {
0641: log("Skipping " + inF + " it is a directory.",
0642: Project.MSG_VERBOSE);
0643: return;
0644: }
0645:
0646: FileNameMapper mapper = null;
0647: if (mapperElement != null) {
0648: mapper = mapperElement.getImplementation();
0649: } else {
0650: mapper = new StyleMapper();
0651: }
0652:
0653: String[] outFileName = mapper.mapFileName(xmlFile);
0654: if (outFileName == null || outFileName.length == 0) {
0655: log("Skipping " + inFile
0656: + " it cannot get mapped to output.",
0657: Project.MSG_VERBOSE);
0658: return;
0659: } else if (outFileName == null || outFileName.length > 1) {
0660: log("Skipping " + inFile + " its mapping is ambiguos.",
0661: Project.MSG_VERBOSE);
0662: return;
0663: }
0664:
0665: outF = new File(destDir, outFileName[0]);
0666:
0667: if (force || inF.lastModified() > outF.lastModified()
0668: || styleSheetLastModified > outF.lastModified()) {
0669: ensureDirectoryFor(outF);
0670: log("Processing " + inF + " to " + outF);
0671:
0672: configureLiaison(stylesheet);
0673: setLiaisonDynamicFileParameters(liaison, inF);
0674: liaison.transform(inF, outF);
0675: }
0676: } catch (Exception ex) {
0677: // If failed to process document, must delete target document,
0678: // or it will not attempt to process it the second time
0679: log("Failed to process " + inFile, Project.MSG_INFO);
0680: if (outF != null) {
0681: outF.delete();
0682: }
0683:
0684: throw new BuildException(ex);
0685: }
0686:
0687: } //-- processXML
0688:
0689: /**
0690: * Process the input file to the output file with the given stylesheet.
0691: *
0692: * @param inFile the input file to process.
0693: * @param outFile the destination file.
0694: * @param stylesheet the stylesheet to use.
0695: * @exception BuildException if the processing fails.
0696: */
0697: private void process(File inFile, File outFile, Resource stylesheet)
0698: throws BuildException {
0699: try {
0700: long styleSheetLastModified = stylesheet.getLastModified();
0701: log(
0702: "In file " + inFile + " time: "
0703: + inFile.lastModified(), Project.MSG_DEBUG);
0704: log("Out file " + outFile + " time: "
0705: + outFile.lastModified(), Project.MSG_DEBUG);
0706: log("Style file " + xslFile + " time: "
0707: + styleSheetLastModified, Project.MSG_DEBUG);
0708: if (force
0709: || inFile.lastModified() >= outFile.lastModified()
0710: || styleSheetLastModified >= outFile.lastModified()) {
0711: ensureDirectoryFor(outFile);
0712: log("Processing " + inFile + " to " + outFile,
0713: Project.MSG_INFO);
0714: configureLiaison(stylesheet);
0715: setLiaisonDynamicFileParameters(liaison, inFile);
0716: liaison.transform(inFile, outFile);
0717: } else {
0718: log("Skipping input file " + inFile
0719: + " because it is older than output file "
0720: + outFile + " and so is the stylesheet "
0721: + stylesheet, Project.MSG_DEBUG);
0722: }
0723: } catch (Exception ex) {
0724: log("Failed to process " + inFile, Project.MSG_INFO);
0725: if (outFile != null) {
0726: outFile.delete();
0727: }
0728: throw new BuildException(ex);
0729: }
0730: }
0731:
0732: /**
0733: * Ensure the directory exists for a given file
0734: *
0735: * @param targetFile the file for which the directories are required.
0736: * @exception BuildException if the directories cannot be created.
0737: */
0738: private void ensureDirectoryFor(File targetFile)
0739: throws BuildException {
0740: File directory = targetFile.getParentFile();
0741: if (!directory.exists()) {
0742: if (!directory.mkdirs()) {
0743: throw new BuildException("Unable to create directory: "
0744: + directory.getAbsolutePath());
0745: }
0746: }
0747: }
0748:
0749: /**
0750: * Get the factory instance configured for this processor
0751: *
0752: * @return the factory instance in use
0753: */
0754: public Factory getFactory() {
0755: return factory;
0756: }
0757:
0758: /**
0759: * Get the XML catalog containing entity definitions
0760: *
0761: * @return the XML catalog for the task.
0762: */
0763: public XMLCatalog getXMLCatalog() {
0764: xmlCatalog.setProject(getProject());
0765: return xmlCatalog;
0766: }
0767:
0768: /**
0769: * Get an enumeration on the outputproperties.
0770: * @return the outputproperties
0771: */
0772: public Enumeration getOutputProperties() {
0773: return outputProperties.elements();
0774: }
0775:
0776: /**
0777: * Get the Liason implementation to use in processing.
0778: *
0779: * @return an instance of the XSLTLiason interface.
0780: */
0781: protected XSLTLiaison getLiaison() {
0782: // if processor wasn't specified, see if TraX is available. If not,
0783: // default it to xalan, depending on which is in the classpath
0784: if (liaison == null) {
0785: if (processor != null) {
0786: try {
0787: resolveProcessor(processor);
0788: } catch (Exception e) {
0789: throw new BuildException(e);
0790: }
0791: } else {
0792: try {
0793: resolveProcessor(PROCESSOR_TRAX);
0794: } catch (Throwable e1) {
0795: e1.printStackTrace();
0796: throw new BuildException(e1);
0797: }
0798: }
0799: }
0800: return liaison;
0801: }
0802:
0803: /**
0804: * Create an instance of an XSL parameter for configuration by Ant.
0805: *
0806: * @return an instance of the Param class to be configured.
0807: */
0808: public Param createParam() {
0809: Param p = new Param();
0810: params.addElement(p);
0811: return p;
0812: }
0813:
0814: /**
0815: * The Param inner class used to store XSL parameters
0816: */
0817: public static class Param {
0818: /** The parameter name */
0819: private String name = null;
0820:
0821: /** The parameter's value */
0822: private String expression = null;
0823:
0824: private String ifProperty;
0825: private String unlessProperty;
0826: private Project project;
0827:
0828: /**
0829: * Set the current project
0830: *
0831: * @param project the current project
0832: */
0833: public void setProject(Project project) {
0834: this .project = project;
0835: }
0836:
0837: /**
0838: * Set the parameter name.
0839: *
0840: * @param name the name of the parameter.
0841: */
0842: public void setName(String name) {
0843: this .name = name;
0844: }
0845:
0846: /**
0847: * The parameter value
0848: * NOTE : was intended to be an XSL expression.
0849: * @param expression the parameter's value.
0850: */
0851: public void setExpression(String expression) {
0852: this .expression = expression;
0853: }
0854:
0855: /**
0856: * Get the parameter name
0857: *
0858: * @return the parameter name
0859: * @exception BuildException if the name is not set.
0860: */
0861: public String getName() throws BuildException {
0862: if (name == null) {
0863: throw new BuildException("Name attribute is missing.");
0864: }
0865: return name;
0866: }
0867:
0868: /**
0869: * Get the parameter's value
0870: *
0871: * @return the parameter value
0872: * @exception BuildException if the value is not set.
0873: */
0874: public String getExpression() throws BuildException {
0875: if (expression == null) {
0876: throw new BuildException(
0877: "Expression attribute is missing.");
0878: }
0879: return expression;
0880: }
0881:
0882: /**
0883: * Set whether this param should be used. It will be
0884: * used if the property has been set, otherwise it won't.
0885: * @param ifProperty name of property
0886: */
0887: public void setIf(String ifProperty) {
0888: this .ifProperty = ifProperty;
0889: }
0890:
0891: /**
0892: * Set whether this param should NOT be used. It
0893: * will not be used if the property has been set, otherwise it
0894: * will be used.
0895: * @param unlessProperty name of property
0896: */
0897: public void setUnless(String unlessProperty) {
0898: this .unlessProperty = unlessProperty;
0899: }
0900:
0901: /**
0902: * Ensures that the param passes the conditions placed
0903: * on it with <code>if</code> and <code>unless</code> properties.
0904: * @return true if the task passes the "if" and "unless" parameters
0905: */
0906: public boolean shouldUse() {
0907: if (ifProperty != null
0908: && project.getProperty(ifProperty) == null) {
0909: return false;
0910: } else if (unlessProperty != null
0911: && project.getProperty(unlessProperty) != null) {
0912: return false;
0913: }
0914:
0915: return true;
0916: }
0917: } // Param
0918:
0919: /**
0920: * Create an instance of an output property to be configured.
0921: * @return the newly created output property.
0922: * @since Ant 1.5
0923: */
0924: public OutputProperty createOutputProperty() {
0925: OutputProperty p = new OutputProperty();
0926: outputProperties.addElement(p);
0927: return p;
0928: }
0929:
0930: /**
0931: * Specify how the result tree should be output as specified
0932: * in the <a href="http://www.w3.org/TR/xslt#output">
0933: * specification</a>.
0934: * @since Ant 1.5
0935: */
0936: public static class OutputProperty {
0937: /** output property name */
0938: private String name;
0939:
0940: /** output property value */
0941: private String value;
0942:
0943: /**
0944: * @return the output property name.
0945: */
0946: public String getName() {
0947: return name;
0948: }
0949:
0950: /**
0951: * set the name for this property
0952: * @param name A non-null String that specifies an
0953: * output property name, which may be namespace qualified.
0954: */
0955: public void setName(String name) {
0956: this .name = name;
0957: }
0958:
0959: /**
0960: * @return the output property value.
0961: */
0962: public String getValue() {
0963: return value;
0964: }
0965:
0966: /**
0967: * set the value for this property
0968: * @param value The non-null string value of the output property.
0969: */
0970: public void setValue(String value) {
0971: this .value = value;
0972: }
0973: }
0974:
0975: /**
0976: * Initialize internal instance of XMLCatalog
0977: * @throws BuildException on error
0978: */
0979: public void init() throws BuildException {
0980: super .init();
0981: xmlCatalog.setProject(getProject());
0982: }
0983:
0984: /**
0985: * Loads the stylesheet and set xsl:param parameters.
0986: *
0987: * @param stylesheet the file from which to load the stylesheet.
0988: * @exception BuildException if the stylesheet cannot be loaded.
0989: * @deprecated since Ant 1.7
0990: */
0991: protected void configureLiaison(File stylesheet)
0992: throws BuildException {
0993: FileResource fr = new FileResource();
0994: fr.setProject(getProject());
0995: fr.setFile(stylesheet);
0996: configureLiaison(fr);
0997: }
0998:
0999: /**
1000: * Loads the stylesheet and set xsl:param parameters.
1001: *
1002: * @param stylesheet the resource from which to load the stylesheet.
1003: * @exception BuildException if the stylesheet cannot be loaded.
1004: * @since Ant 1.7
1005: */
1006: protected void configureLiaison(Resource stylesheet)
1007: throws BuildException {
1008: if (stylesheetLoaded && reuseLoadedStylesheet) {
1009: return;
1010: }
1011: stylesheetLoaded = true;
1012:
1013: try {
1014: log("Loading stylesheet " + stylesheet, Project.MSG_INFO);
1015: // We call liason.configure() and then liaison.setStylesheet()
1016: // so that the internal variables of liaison can be set up
1017: if (liaison instanceof XSLTLiaison2) {
1018: ((XSLTLiaison2) liaison).configure(this );
1019: }
1020:
1021: if (liaison instanceof XSLTLiaison3) {
1022: // If we are here we can set the stylesheet as a
1023: // resource
1024: ((XSLTLiaison3) liaison).setStylesheet(stylesheet);
1025: } else {
1026: // If we are here we cannot set the stylesheet as
1027: // a resource, but we can set it as a file. So,
1028: // we make an attempt to get it as a file
1029: if (stylesheet instanceof FileResource) {
1030: liaison.setStylesheet(((FileResource) stylesheet)
1031: .getFile());
1032: } else {
1033: throw new BuildException(liaison.getClass()
1034: .toString()
1035: + " accepts the stylesheet only as a file",
1036: getLocation());
1037: }
1038: }
1039: for (Enumeration e = params.elements(); e.hasMoreElements();) {
1040: Param p = (Param) e.nextElement();
1041: if (p.shouldUse()) {
1042: liaison.addParam(p.getName(), p.getExpression());
1043: }
1044: }
1045: } catch (Exception ex) {
1046: log("Failed to transform using stylesheet " + stylesheet,
1047: Project.MSG_INFO);
1048: throw new BuildException(ex);
1049: }
1050: }
1051:
1052: /**
1053: * Sets file parameter(s) for directory and filename if the attribute
1054: * 'filenameparameter' or 'filedirparameter' are set in the task.
1055: *
1056: * @param liaison to change parameters for
1057: * @param inFile to get the additional file information from
1058: * @throws Exception if an exception occurs on filename lookup
1059: *
1060: * @since Ant 1.7
1061: */
1062: private void setLiaisonDynamicFileParameters(XSLTLiaison liaison,
1063: File inFile) throws Exception {
1064: if (fileNameParameter != null) {
1065: liaison.addParam(fileNameParameter, inFile.getName());
1066: }
1067: if (fileDirParameter != null) {
1068: String fileName = FileUtils
1069: .getRelativePath(baseDir, inFile);
1070: File file = new File(fileName);
1071: // Give always a slash as file separator, so the stylesheet could be sure about that
1072: // Use '.' so a dir+"/"+name would not result in an absolute path
1073: liaison.addParam(fileDirParameter,
1074: (file.getParent() != null) ? file.getParent()
1075: .replace('\\', '/') : ".");
1076: }
1077: }
1078:
1079: /**
1080: * Create the factory element to configure a trax liaison.
1081: * @return the newly created factory element.
1082: * @throws BuildException if the element is created more than one time.
1083: */
1084: public Factory createFactory() throws BuildException {
1085: if (factory != null) {
1086: throw new BuildException("'factory' element must be unique");
1087: }
1088: factory = new Factory();
1089: return factory;
1090: }
1091:
1092: /**
1093: * The factory element to configure a transformer factory
1094: * @since Ant 1.6
1095: */
1096: public static class Factory {
1097:
1098: /** the factory class name to use for TraXLiaison */
1099: private String name;
1100:
1101: /**
1102: * the list of factory attributes to use for TraXLiaison
1103: */
1104: private Vector attributes = new Vector();
1105:
1106: /**
1107: * @return the name of the factory.
1108: */
1109: public String getName() {
1110: return name;
1111: }
1112:
1113: /**
1114: * Set the name of the factory
1115: * @param name the name of the factory.
1116: */
1117: public void setName(String name) {
1118: this .name = name;
1119: }
1120:
1121: /**
1122: * Create an instance of a factory attribute.
1123: * @param attr the newly created factory attribute
1124: */
1125: public void addAttribute(Attribute attr) {
1126: attributes.addElement(attr);
1127: }
1128:
1129: /**
1130: * return the attribute elements.
1131: * @return the enumeration of attributes
1132: */
1133: public Enumeration getAttributes() {
1134: return attributes.elements();
1135: }
1136:
1137: /**
1138: * A JAXP factory attribute. This is mostly processor specific, for
1139: * example for Xalan 2.3+, the following attributes could be set:
1140: * <ul>
1141: * <li>http://xml.apache.org/xalan/features/optimize (true|false) </li>
1142: * <li>http://xml.apache.org/xalan/features/incremental (true|false) </li>
1143: * </ul>
1144: */
1145: public static class Attribute implements DynamicConfigurator {
1146:
1147: /** attribute name, mostly processor specific */
1148: private String name;
1149:
1150: /** attribute value, often a boolean string */
1151: private Object value;
1152:
1153: /**
1154: * @return the attribute name.
1155: */
1156: public String getName() {
1157: return name;
1158: }
1159:
1160: /**
1161: * @return the output property value.
1162: */
1163: public Object getValue() {
1164: return value;
1165: }
1166:
1167: /**
1168: * Not used.
1169: * @param name not used
1170: * @return null
1171: * @throws BuildException never
1172: */
1173: public Object createDynamicElement(String name)
1174: throws BuildException {
1175: return null;
1176: }
1177:
1178: /**
1179: * Set an attribute.
1180: * Only "name" and "value" are supported as names.
1181: * @param name the name of the attribute
1182: * @param value the value of the attribute
1183: * @throws BuildException on error
1184: */
1185: public void setDynamicAttribute(String name, String value)
1186: throws BuildException {
1187: // only 'name' and 'value' exist.
1188: if ("name".equalsIgnoreCase(name)) {
1189: this .name = value;
1190: } else if ("value".equalsIgnoreCase(name)) {
1191: // a value must be of a given type
1192: // say boolean|integer|string that are mostly used.
1193: if ("true".equalsIgnoreCase(value)) {
1194: this .value = Boolean.TRUE;
1195: } else if ("false".equalsIgnoreCase(value)) {
1196: this .value = Boolean.FALSE;
1197: } else {
1198: try {
1199: this .value = new Integer(value);
1200: } catch (NumberFormatException e) {
1201: this .value = value;
1202: }
1203: }
1204: } else {
1205: throw new BuildException("Unsupported attribute: "
1206: + name);
1207: }
1208: }
1209: } // -- class Attribute
1210:
1211: } // -- class Factory
1212:
1213: /**
1214: * Mapper implementation of the "traditional" way <xslt>
1215: * mapped filenames.
1216: *
1217: * <p>If the file has an extension, chop it off. Append whatever
1218: * the user has specified as extension or ".html".</p>
1219: *
1220: * @since Ant 1.6.2
1221: */
1222: private class StyleMapper implements FileNameMapper {
1223: public void setFrom(String from) {
1224: }
1225:
1226: public void setTo(String to) {
1227: }
1228:
1229: public String[] mapFileName(String xmlFile) {
1230: int dotPos = xmlFile.lastIndexOf('.');
1231: if (dotPos > 0) {
1232: xmlFile = xmlFile.substring(0, dotPos);
1233: }
1234: return new String[] { xmlFile + targetExtension };
1235: }
1236: }
1237:
1238: }
|