1 | package org.apache.velocity.anakia; |
2 | |
3 | /* |
4 | * Copyright 2001,2004 The Apache Software Foundation. |
5 | * |
6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
7 | * you may not use this file except in compliance with the License. |
8 | * You may obtain a copy of the License at |
9 | * |
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * |
12 | * Unless required by applicable law or agreed to in writing, software |
13 | * distributed under the License is distributed on an "AS IS" BASIS, |
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15 | * See the License for the specific language governing permissions and |
16 | * limitations under the License. |
17 | */ |
18 | |
19 | import java.io.BufferedWriter; |
20 | import java.io.File; |
21 | import java.io.FileOutputStream; |
22 | import java.io.OutputStreamWriter; |
23 | import java.io.Writer; |
24 | |
25 | import java.util.StringTokenizer; |
26 | |
27 | import org.apache.tools.ant.BuildException; |
28 | import org.apache.tools.ant.DirectoryScanner; |
29 | import org.apache.tools.ant.Project; |
30 | import org.apache.tools.ant.taskdefs.MatchingTask; |
31 | |
32 | import org.xml.sax.SAXParseException; |
33 | |
34 | import org.jdom.Document; |
35 | import org.jdom.JDOMException; |
36 | import org.jdom.input.SAXBuilder; |
37 | |
38 | import org.apache.velocity.Template; |
39 | import org.apache.velocity.app.VelocityEngine; |
40 | import org.apache.velocity.runtime.RuntimeConstants; |
41 | import org.apache.velocity.util.StringUtils; |
42 | |
43 | import org.apache.velocity.VelocityContext; |
44 | |
45 | /** |
46 | * The purpose of this Ant Task is to allow you to use |
47 | * Velocity as an XML transformation tool like XSLT is. |
48 | * So, instead of using XSLT, you will be able to use this |
49 | * class instead to do your transformations. It works very |
50 | * similar in concept to Ant's <style> task. |
51 | * <p> |
52 | * You can find more documentation about this class on the |
53 | * Velocity |
54 | * <a href="http://jakarta.apache.org/velocity/anakia.html">Website</a>. |
55 | * |
56 | * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> |
57 | * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a> |
58 | * @version $Id: AnakiaTask.java,v 1.34.4.1 2004/03/03 23:22:04 geirm Exp $ |
59 | */ |
60 | public class AnakiaTask extends MatchingTask |
61 | { |
62 | /** <code>{@link SAXBuilder}</code> instance to use */ |
63 | private SAXBuilder builder; |
64 | |
65 | /** the destination directory */ |
66 | private File destDir = null; |
67 | |
68 | /** the base directory */ |
69 | private File baseDir = null; |
70 | |
71 | /** the style= attribute */ |
72 | private String style = null; |
73 | |
74 | /** the File to the style file */ |
75 | private File styleFile = null; |
76 | |
77 | /** last modified of the style sheet */ |
78 | private long styleSheetLastModified = 0; |
79 | |
80 | /** the projectFile= attribute */ |
81 | private String projectAttribute = null; |
82 | |
83 | /** the File for the project.xml file */ |
84 | private File projectFile = null; |
85 | |
86 | /** last modified of the project file if it exists */ |
87 | private long projectFileLastModified = 0; |
88 | |
89 | /** check the last modified date on files. defaults to true */ |
90 | private boolean lastModifiedCheck = true; |
91 | |
92 | /** the default output extension is .html */ |
93 | private String extension = ".html"; |
94 | |
95 | /** the template path */ |
96 | private String templatePath = null; |
97 | |
98 | /** the file to get the velocity properties file */ |
99 | private File velocityPropertiesFile = null; |
100 | |
101 | /** the VelocityEngine instance to use */ |
102 | private VelocityEngine ve = new VelocityEngine(); |
103 | |
104 | /** |
105 | * Constructor creates the SAXBuilder. |
106 | */ |
107 | public AnakiaTask() |
108 | { |
109 | builder = new SAXBuilder(); |
110 | builder.setFactory(new AnakiaJDOMFactory()); |
111 | } |
112 | |
113 | /** |
114 | * Set the base directory. |
115 | */ |
116 | public void setBasedir(File dir) |
117 | { |
118 | baseDir = dir; |
119 | } |
120 | |
121 | /** |
122 | * Set the destination directory into which the VSL result |
123 | * files should be copied to |
124 | * @param dirName the name of the destination directory |
125 | */ |
126 | public void setDestdir(File dir) |
127 | { |
128 | destDir = dir; |
129 | } |
130 | |
131 | /** |
132 | * Allow people to set the default output file extension |
133 | */ |
134 | public void setExtension(String extension) |
135 | { |
136 | this.extension = extension; |
137 | } |
138 | |
139 | /** |
140 | * Allow people to set the path to the .vsl file |
141 | */ |
142 | public void setStyle(String style) |
143 | { |
144 | this.style = style; |
145 | } |
146 | |
147 | /** |
148 | * Allow people to set the path to the project.xml file |
149 | */ |
150 | public void setProjectFile(String projectAttribute) |
151 | { |
152 | this.projectAttribute = projectAttribute; |
153 | } |
154 | |
155 | /** |
156 | * Set the path to the templates. |
157 | * The way it works is this: |
158 | * If you have a Velocity.properties file defined, this method |
159 | * will <strong>override</strong> whatever is set in the |
160 | * Velocity.properties file. This allows one to not have to define |
161 | * a Velocity.properties file, therefore using Velocity's defaults |
162 | * only. |
163 | */ |
164 | |
165 | public void setTemplatePath(File templatePath) |
166 | { |
167 | try |
168 | { |
169 | this.templatePath = templatePath.getCanonicalPath(); |
170 | } |
171 | catch (java.io.IOException ioe) |
172 | { |
173 | throw new BuildException(ioe); |
174 | } |
175 | } |
176 | |
177 | /** |
178 | * Allow people to set the path to the velocity.properties file |
179 | * This file is found relative to the path where the JVM was run. |
180 | * For example, if build.sh was executed in the ./build directory, |
181 | * then the path would be relative to this directory. |
182 | * This is optional based on the setting of setTemplatePath(). |
183 | */ |
184 | public void setVelocityPropertiesFile(File velocityPropertiesFile) |
185 | { |
186 | this.velocityPropertiesFile = velocityPropertiesFile; |
187 | } |
188 | |
189 | /** |
190 | * Turn on/off last modified checking. by default, it is on. |
191 | */ |
192 | public void setLastModifiedCheck(String lastmod) |
193 | { |
194 | if (lastmod.equalsIgnoreCase("false") || lastmod.equalsIgnoreCase("no") |
195 | || lastmod.equalsIgnoreCase("off")) |
196 | { |
197 | this.lastModifiedCheck = false; |
198 | } |
199 | } |
200 | |
201 | /** |
202 | * Main body of the application |
203 | */ |
204 | public void execute () throws BuildException |
205 | { |
206 | DirectoryScanner scanner; |
207 | String[] list; |
208 | String[] dirs; |
209 | |
210 | if (baseDir == null) |
211 | { |
212 | baseDir = project.resolveFile("."); |
213 | } |
214 | if (destDir == null ) |
215 | { |
216 | String msg = "destdir attribute must be set!"; |
217 | throw new BuildException(msg); |
218 | } |
219 | if (style == null) |
220 | { |
221 | throw new BuildException("style attribute must be set!"); |
222 | } |
223 | |
224 | if (velocityPropertiesFile == null) |
225 | { |
226 | velocityPropertiesFile = new File("velocity.properties"); |
227 | } |
228 | |
229 | /* |
230 | * If the props file doesn't exist AND a templatePath hasn't |
231 | * been defined, then throw the exception. |
232 | */ |
233 | if ( !velocityPropertiesFile.exists() && templatePath == null ) |
234 | { |
235 | throw new BuildException ("No template path and could not " + |
236 | "locate velocity.properties file: " + |
237 | velocityPropertiesFile.getAbsolutePath()); |
238 | } |
239 | |
240 | log("Transforming into: " + destDir.getAbsolutePath(), Project.MSG_INFO); |
241 | |
242 | // projectFile relative to baseDir |
243 | if (projectAttribute != null && projectAttribute.length() > 0) |
244 | { |
245 | projectFile = new File(baseDir, projectAttribute); |
246 | if (projectFile.exists()) |
247 | { |
248 | projectFileLastModified = projectFile.lastModified(); |
249 | } |
250 | else |
251 | { |
252 | log ("Project file is defined, but could not be located: " + |
253 | projectFile.getAbsolutePath(), Project.MSG_INFO ); |
254 | projectFile = null; |
255 | } |
256 | } |
257 | |
258 | Document projectDocument = null; |
259 | try |
260 | { |
261 | if ( velocityPropertiesFile.exists() ) |
262 | { |
263 | ve.init(velocityPropertiesFile.getAbsolutePath()); |
264 | } |
265 | else if (templatePath != null && templatePath.length() > 0) |
266 | { |
267 | ve.setProperty( RuntimeConstants.FILE_RESOURCE_LOADER_PATH, |
268 | templatePath); |
269 | ve.init(); |
270 | } |
271 | |
272 | // get the last modification of the VSL stylesheet |
273 | styleSheetLastModified = ve.getTemplate( style ).getLastModified(); |
274 | |
275 | // Build the Project file document |
276 | if (projectFile != null) |
277 | { |
278 | projectDocument = builder.build(projectFile); |
279 | } |
280 | } |
281 | catch (Exception e) |
282 | { |
283 | log("Error: " + e.toString(), Project.MSG_INFO); |
284 | throw new BuildException(e); |
285 | } |
286 | |
287 | // find the files/directories |
288 | scanner = getDirectoryScanner(baseDir); |
289 | |
290 | // get a list of files to work on |
291 | list = scanner.getIncludedFiles(); |
292 | for (int i = 0;i < list.length; ++i) |
293 | { |
294 | process( baseDir, list[i], destDir, projectDocument ); |
295 | } |
296 | } |
297 | |
298 | /** |
299 | * Process an XML file using Velocity |
300 | */ |
301 | private void process(File baseDir, String xmlFile, File destDir, |
302 | Document projectDocument) |
303 | throws BuildException |
304 | { |
305 | File outFile=null; |
306 | File inFile=null; |
307 | Writer writer = null; |
308 | try |
309 | { |
310 | // the current input file relative to the baseDir |
311 | inFile = new File(baseDir,xmlFile); |
312 | // the output file relative to basedir |
313 | outFile = new File(destDir, |
314 | xmlFile.substring(0, |
315 | xmlFile.lastIndexOf('.')) + extension); |
316 | |
317 | // only process files that have changed |
318 | if (lastModifiedCheck == false || |
319 | (inFile.lastModified() > outFile.lastModified() || |
320 | styleSheetLastModified > outFile.lastModified() || |
321 | projectFileLastModified > outFile.lastModified())) |
322 | { |
323 | ensureDirectoryFor( outFile ); |
324 | |
325 | //-- command line status |
326 | log("Input: " + xmlFile, Project.MSG_INFO ); |
327 | |
328 | // Build the JDOM Document |
329 | Document root = builder.build(inFile); |
330 | |
331 | // Shove things into the Context |
332 | VelocityContext context = new VelocityContext(); |
333 | |
334 | /* |
335 | * get the property TEMPLATE_ENCODING |
336 | * we know it's a string... |
337 | */ |
338 | String encoding = (String) ve.getProperty( RuntimeConstants.OUTPUT_ENCODING ); |
339 | if (encoding == null || encoding.length() == 0 |
340 | || encoding.equals("8859-1") || encoding.equals("8859_1")) |
341 | { |
342 | encoding = "ISO-8859-1"; |
343 | } |
344 | |
345 | OutputWrapper ow = new OutputWrapper(); |
346 | ow.setEncoding (encoding); |
347 | |
348 | context.put ("root", root.getRootElement()); |
349 | context.put ("xmlout", ow ); |
350 | context.put ("relativePath", getRelativePath(xmlFile)); |
351 | context.put ("treeWalk", new TreeWalker()); |
352 | context.put ("xpath", new XPathTool() ); |
353 | context.put ("escape", new Escape() ); |
354 | context.put ("date", new java.util.Date() ); |
355 | |
356 | // only put this into the context if it exists. |
357 | if (projectDocument != null) |
358 | { |
359 | context.put ("project", projectDocument.getRootElement()); |
360 | } |
361 | |
362 | // Process the VSL template with the context and write out |
363 | // the result as the outFile. |
364 | writer = new BufferedWriter(new OutputStreamWriter( |
365 | new FileOutputStream(outFile), |
366 | encoding)); |
367 | // get the template to process |
368 | Template template = ve.getTemplate(style); |
369 | template.merge(context, writer); |
370 | |
371 | log("Output: " + outFile, Project.MSG_INFO ); |
372 | } |
373 | } |
374 | catch (JDOMException e) |
375 | { |
376 | if (outFile != null ) outFile.delete(); |
377 | if (e.getCause() != null) |
378 | { |
379 | Throwable rootCause = e.getCause(); |
380 | if (rootCause instanceof SAXParseException) |
381 | { |
382 | System.out.println(""); |
383 | System.out.println("Error: " + rootCause.getMessage()); |
384 | System.out.println( |
385 | " Line: " + |
386 | ((SAXParseException)rootCause).getLineNumber() + |
387 | " Column: " + |
388 | ((SAXParseException)rootCause).getColumnNumber()); |
389 | System.out.println(""); |
390 | } |
391 | else |
392 | { |
393 | rootCause.printStackTrace(); |
394 | } |
395 | } |
396 | else |
397 | { |
398 | e.printStackTrace(); |
399 | } |
400 | // log("Failed to process " + inFile, Project.MSG_INFO); |
401 | } |
402 | catch (Throwable e) |
403 | { |
404 | // log("Failed to process " + inFile, Project.MSG_INFO); |
405 | if (outFile != null) |
406 | { |
407 | outFile.delete(); |
408 | } |
409 | e.printStackTrace(); |
410 | } |
411 | finally |
412 | { |
413 | if (writer != null) |
414 | { |
415 | try |
416 | { |
417 | writer.flush(); |
418 | writer.close(); |
419 | } |
420 | catch (Exception e) |
421 | { |
422 | } |
423 | } |
424 | } |
425 | } |
426 | |
427 | /** |
428 | * Hacky method to figure out the relative path |
429 | * that we are currently in. This is good for getting |
430 | * the relative path for images and anchor's. |
431 | */ |
432 | private String getRelativePath(String file) |
433 | { |
434 | if (file == null || file.length()==0) |
435 | return ""; |
436 | StringTokenizer st = new StringTokenizer(file, "/\\"); |
437 | // needs to be -1 cause ST returns 1 even if there are no matches. huh? |
438 | int slashCount = st.countTokens() - 1; |
439 | StringBuffer sb = new StringBuffer(); |
440 | for (int i=0;i<slashCount ;i++ ) |
441 | { |
442 | sb.append ("../"); |
443 | } |
444 | |
445 | if (sb.toString().length() > 0) |
446 | { |
447 | return StringUtils.chop(sb.toString(), 1); |
448 | } |
449 | else |
450 | { |
451 | return "."; |
452 | } |
453 | } |
454 | |
455 | /** |
456 | * create directories as needed |
457 | */ |
458 | private void ensureDirectoryFor( File targetFile ) throws BuildException |
459 | { |
460 | File directory = new File( targetFile.getParent() ); |
461 | if (!directory.exists()) |
462 | { |
463 | if (!directory.mkdirs()) |
464 | { |
465 | throw new BuildException("Unable to create directory: " |
466 | + directory.getAbsolutePath() ); |
467 | } |
468 | } |
469 | } |
470 | } |