001: package org.apache.velocity.anakia;
002:
003: /*
004: * Copyright 2001,2004 The Apache Software Foundation.
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: import java.io.BufferedWriter;
020: import java.io.File;
021: import java.io.FileOutputStream;
022: import java.io.OutputStreamWriter;
023: import java.io.Writer;
024:
025: import java.util.StringTokenizer;
026:
027: import org.apache.tools.ant.BuildException;
028: import org.apache.tools.ant.DirectoryScanner;
029: import org.apache.tools.ant.Project;
030: import org.apache.tools.ant.taskdefs.MatchingTask;
031:
032: import org.xml.sax.SAXParseException;
033:
034: import org.jdom.Document;
035: import org.jdom.JDOMException;
036: import org.jdom.input.SAXBuilder;
037:
038: import org.apache.velocity.Template;
039: import org.apache.velocity.app.VelocityEngine;
040: import org.apache.velocity.runtime.RuntimeConstants;
041: import org.apache.velocity.util.StringUtils;
042:
043: import org.apache.velocity.VelocityContext;
044:
045: /**
046: * The purpose of this Ant Task is to allow you to use
047: * Velocity as an XML transformation tool like XSLT is.
048: * So, instead of using XSLT, you will be able to use this
049: * class instead to do your transformations. It works very
050: * similar in concept to Ant's <style> task.
051: * <p>
052: * You can find more documentation about this class on the
053: * Velocity
054: * <a href="http://jakarta.apache.org/velocity/anakia.html">Website</a>.
055: *
056: * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
057: * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
058: * @version $Id: AnakiaTask.java,v 1.2 2005/11/18 09:24:36 klaas Exp $
059: */
060: public class AnakiaTask extends MatchingTask {
061: /** <code>{@link SAXBuilder}</code> instance to use */
062: private SAXBuilder builder;
063:
064: /** the destination directory */
065: private File destDir = null;
066:
067: /** the base directory */
068: private File baseDir = null;
069:
070: /** the style= attribute */
071: private String style = null;
072:
073: /** the File to the style file */
074: private File styleFile = null;
075:
076: /** last modified of the style sheet */
077: private long styleSheetLastModified = 0;
078:
079: /** the projectFile= attribute */
080: private String projectAttribute = null;
081:
082: /** the File for the project.xml file */
083: private File projectFile = null;
084:
085: /** last modified of the project file if it exists */
086: private long projectFileLastModified = 0;
087:
088: /** check the last modified date on files. defaults to true */
089: private boolean lastModifiedCheck = true;
090:
091: /** the default output extension is .html */
092: private String extension = ".html";
093:
094: /** the template path */
095: private String templatePath = null;
096:
097: /** the file to get the velocity properties file */
098: private File velocityPropertiesFile = null;
099:
100: /** the VelocityEngine instance to use */
101: private VelocityEngine ve = new VelocityEngine();
102:
103: /**
104: * Constructor creates the SAXBuilder.
105: */
106: public AnakiaTask() {
107: builder = new SAXBuilder();
108: builder.setFactory(new AnakiaJDOMFactory());
109: }
110:
111: /**
112: * Set the base directory.
113: */
114: public void setBasedir(File dir) {
115: baseDir = dir;
116: }
117:
118: /**
119: * Set the destination directory into which the VSL result
120: * files should be copied to
121: * @param dirName the name of the destination directory
122: */
123: public void setDestdir(File dir) {
124: destDir = dir;
125: }
126:
127: /**
128: * Allow people to set the default output file extension
129: */
130: public void setExtension(String extension) {
131: this .extension = extension;
132: }
133:
134: /**
135: * Allow people to set the path to the .vsl file
136: */
137: public void setStyle(String style) {
138: this .style = style;
139: }
140:
141: /**
142: * Allow people to set the path to the project.xml file
143: */
144: public void setProjectFile(String projectAttribute) {
145: this .projectAttribute = projectAttribute;
146: }
147:
148: /**
149: * Set the path to the templates.
150: * The way it works is this:
151: * If you have a Velocity.properties file defined, this method
152: * will <strong>override</strong> whatever is set in the
153: * Velocity.properties file. This allows one to not have to define
154: * a Velocity.properties file, therefore using Velocity's defaults
155: * only.
156: */
157:
158: public void setTemplatePath(File templatePath) {
159: try {
160: this .templatePath = templatePath.getCanonicalPath();
161: } catch (java.io.IOException ioe) {
162: throw new BuildException(ioe);
163: }
164: }
165:
166: /**
167: * Allow people to set the path to the velocity.properties file
168: * This file is found relative to the path where the JVM was run.
169: * For example, if build.sh was executed in the ./build directory,
170: * then the path would be relative to this directory.
171: * This is optional based on the setting of setTemplatePath().
172: */
173: public void setVelocityPropertiesFile(File velocityPropertiesFile) {
174: this .velocityPropertiesFile = velocityPropertiesFile;
175: }
176:
177: /**
178: * Turn on/off last modified checking. by default, it is on.
179: */
180: public void setLastModifiedCheck(String lastmod) {
181: if (lastmod.equalsIgnoreCase("false")
182: || lastmod.equalsIgnoreCase("no")
183: || lastmod.equalsIgnoreCase("off")) {
184: this .lastModifiedCheck = false;
185: }
186: }
187:
188: /**
189: * Main body of the application
190: */
191: public void execute() throws BuildException {
192: DirectoryScanner scanner;
193: String[] list;
194: String[] dirs;
195:
196: if (baseDir == null) {
197: baseDir = project.resolveFile(".");
198: }
199: if (destDir == null) {
200: String msg = "destdir attribute must be set!";
201: throw new BuildException(msg);
202: }
203: if (style == null) {
204: throw new BuildException("style attribute must be set!");
205: }
206:
207: if (velocityPropertiesFile == null) {
208: velocityPropertiesFile = new File("velocity.properties");
209: }
210:
211: /*
212: * If the props file doesn't exist AND a templatePath hasn't
213: * been defined, then throw the exception.
214: */
215: if (!velocityPropertiesFile.exists() && templatePath == null) {
216: throw new BuildException("No template path and could not "
217: + "locate velocity.properties file: "
218: + velocityPropertiesFile.getAbsolutePath());
219: }
220:
221: log("Transforming into: " + destDir.getAbsolutePath(),
222: Project.MSG_INFO);
223:
224: // projectFile relative to baseDir
225: if (projectAttribute != null && projectAttribute.length() > 0) {
226: projectFile = new File(baseDir, projectAttribute);
227: if (projectFile.exists()) {
228: projectFileLastModified = projectFile.lastModified();
229: } else {
230: log(
231: "Project file is defined, but could not be located: "
232: + projectFile.getAbsolutePath(),
233: Project.MSG_INFO);
234: projectFile = null;
235: }
236: }
237:
238: Document projectDocument = null;
239: try {
240: if (velocityPropertiesFile.exists()) {
241: ve.init(velocityPropertiesFile.getAbsolutePath());
242: } else if (templatePath != null
243: && templatePath.length() > 0) {
244: ve.setProperty(
245: RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
246: templatePath);
247: ve.init();
248: }
249:
250: // get the last modification of the VSL stylesheet
251: styleSheetLastModified = ve.getTemplate(style)
252: .getLastModified();
253:
254: // Build the Project file document
255: if (projectFile != null) {
256: projectDocument = builder.build(projectFile);
257: }
258: } catch (Exception e) {
259: log("Error: " + e.toString(), Project.MSG_INFO);
260: throw new BuildException(e);
261: }
262:
263: // find the files/directories
264: scanner = getDirectoryScanner(baseDir);
265:
266: // get a list of files to work on
267: list = scanner.getIncludedFiles();
268: for (int i = 0; i < list.length; ++i) {
269: process(baseDir, list[i], destDir, projectDocument);
270: }
271: }
272:
273: /**
274: * Process an XML file using Velocity
275: */
276: private void process(File baseDir, String xmlFile, File destDir,
277: Document projectDocument) throws BuildException {
278: File outFile = null;
279: File inFile = null;
280: Writer writer = null;
281: try {
282: // the current input file relative to the baseDir
283: inFile = new File(baseDir, xmlFile);
284: // the output file relative to basedir
285: outFile = new File(destDir, xmlFile.substring(0, xmlFile
286: .lastIndexOf('.'))
287: + extension);
288:
289: // only process files that have changed
290: if (lastModifiedCheck == false
291: || (inFile.lastModified() > outFile.lastModified()
292: || styleSheetLastModified > outFile
293: .lastModified() || projectFileLastModified > outFile
294: .lastModified())) {
295: ensureDirectoryFor(outFile);
296:
297: //-- command line status
298: log("Input: " + xmlFile, Project.MSG_INFO);
299:
300: // Build the JDOM Document
301: Document root = builder.build(inFile);
302:
303: // Shove things into the Context
304: VelocityContext context = new VelocityContext();
305:
306: /*
307: * get the property TEMPLATE_ENCODING
308: * we know it's a string...
309: */
310: String encoding = (String) ve
311: .getProperty(RuntimeConstants.OUTPUT_ENCODING);
312: if (encoding == null || encoding.length() == 0
313: || encoding.equals("8859-1")
314: || encoding.equals("8859_1")) {
315: encoding = "ISO-8859-1";
316: }
317:
318: OutputWrapper ow = new OutputWrapper();
319: ow.setEncoding(encoding);
320:
321: context.put("inFile", inFile);
322: context.put("outFile", outFile);
323: context.put("outFilePath", xmlFile.substring(0,
324: xmlFile.lastIndexOf('.')).replace('\\', '/')
325: + extension);
326: context.put("root", root.getRootElement());
327: context.put("xmlout", ow);
328: context.put("relativePath", getRelativePath(xmlFile));
329: context.put("treeWalk", new TreeWalker());
330: context.put("xpath", new XPathTool());
331: context.put("escape", new Escape());
332: context.put("date", new java.util.Date());
333: context
334: .put(
335: "datetool",
336: new org.apache.velocity.tools.generic.DateTool());
337:
338: // only put this into the context if it exists.
339: if (projectDocument != null) {
340: context.put("project", projectDocument
341: .getRootElement());
342: }
343:
344: // Process the VSL template with the context and write out
345: // the result as the outFile.
346: writer = new BufferedWriter(new OutputStreamWriter(
347: new FileOutputStream(outFile), encoding));
348: // get the template to process
349: Template template = ve.getTemplate(style);
350: template.merge(context, writer);
351:
352: log("Output: " + outFile, Project.MSG_INFO);
353: }
354: } catch (JDOMException e) {
355: if (outFile != null)
356: outFile.delete();
357: if (e.getCause() != null) {
358: Throwable rootCause = e.getCause();
359: if (rootCause instanceof SAXParseException) {
360: System.out.println("");
361: System.out.println("Error: "
362: + rootCause.getMessage());
363: System.out.println(" Line: "
364: + ((SAXParseException) rootCause)
365: .getLineNumber()
366: + " Column: "
367: + ((SAXParseException) rootCause)
368: .getColumnNumber());
369: System.out.println("");
370: } else {
371: rootCause.printStackTrace();
372: }
373: } else {
374: e.printStackTrace();
375: }
376: // log("Failed to process " + inFile, Project.MSG_INFO);
377: } catch (Throwable e) {
378: // log("Failed to process " + inFile, Project.MSG_INFO);
379: if (outFile != null) {
380: outFile.delete();
381: }
382: e.printStackTrace();
383: } finally {
384: if (writer != null) {
385: try {
386: writer.flush();
387: writer.close();
388: } catch (Exception e) {
389: }
390: }
391: }
392: }
393:
394: /**
395: * Hacky method to figure out the relative path
396: * that we are currently in. This is good for getting
397: * the relative path for images and anchor's.
398: */
399: private String getRelativePath(String file) {
400: if (file == null || file.length() == 0)
401: return "";
402: StringTokenizer st = new StringTokenizer(file, "/\\");
403: // needs to be -1 cause ST returns 1 even if there are no matches. huh?
404: int slashCount = st.countTokens() - 1;
405: StringBuffer sb = new StringBuffer();
406: for (int i = 0; i < slashCount; i++) {
407: sb.append("../");
408: }
409:
410: if (sb.toString().length() > 0) {
411: return StringUtils.chop(sb.toString(), 1);
412: } else {
413: return ".";
414: }
415: }
416:
417: /**
418: * create directories as needed
419: */
420: private void ensureDirectoryFor(File targetFile)
421: throws BuildException {
422: File directory = new File(targetFile.getParent());
423: if (!directory.exists()) {
424: if (!directory.mkdirs()) {
425: throw new BuildException("Unable to create directory: "
426: + directory.getAbsolutePath());
427: }
428: }
429: }
430: }
|