View Javadoc

1   /***
2    * Copyright 2003-2004 Fabrizio Giustina.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.maven.xhtml;
17  
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.FileNotFoundException;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.UnsupportedEncodingException;
24  import java.net.UnknownHostException;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  
29  import org.apache.tools.ant.BuildException;
30  import org.apache.tools.ant.DirectoryScanner;
31  import org.apache.tools.ant.Project;
32  import org.apache.tools.ant.Task;
33  import org.apache.tools.ant.types.DTDLocation;
34  import org.apache.tools.ant.types.FileSet;
35  import org.apache.tools.ant.types.XMLCatalog;
36  import org.dom4j.Element;
37  import org.dom4j.io.OutputFormat;
38  import org.dom4j.io.XMLWriter;
39  import org.dom4j.tree.DefaultElement;
40  import org.xml.sax.InputSource;
41  import org.xml.sax.SAXException;
42  import org.xml.sax.SAXNotRecognizedException;
43  import org.xml.sax.SAXNotSupportedException;
44  import org.xml.sax.XMLReader;
45  import org.xml.sax.helpers.XMLReaderFactory;
46  
47  
48  /***
49   * Validator task. This task is a modified version of the standard ant validator task, specialized for checking xhtml
50   * files and logging errors (without breaking the build)
51   * @author fgiust
52   * @version $Revision: 1.4 $ ($Author: fgiust $)
53   */
54  public class Validator extends Task
55  {
56  
57      /***
58       * validation feature.
59       */
60      private static final String VALIDATION_FEATURE = "http://xml.org/sax/features/validation";
61  
62      /***
63       * encoding - defaults to ISO-8859-1.
64       */
65      private String encoding = "ISO-8859-1";
66  
67      /***
68       * output file.
69       */
70      private File outputFile;
71  
72      /***
73       * XmlWriter used to write to outputFile.
74       */
75      private XMLWriter out;
76  
77      /***
78       * sets of file to be validated.
79       */
80      private List filesets = new ArrayList();
81  
82      /***
83       * XMLReader used to validation process.
84       */
85      private XMLReader xmlReader;
86  
87      /***
88       * Validator handler: implements both ErrorHandler and DTDHandler.
89       */
90      private ValidatorHandler validatorHandler = new ValidatorHandler();
91  
92      /***
93       * XMLCatalog containing references to local dtds and entities.
94       */
95      private XMLCatalog xmlCatalog = new XMLCatalog();
96  
97      /***
98       * sets the input/output files encoding.
99       * @param value encoding
100      */
101     public void setEncoding(String value)
102     {
103         this.encoding = value;
104     }
105 
106     /***
107      * sets the output file.
108      * @param file output file
109      */
110     public void setOUtput(File file)
111     {
112         this.outputFile = file;
113     }
114 
115     /***
116      * add an XMLCatalog as a nested element.
117      * @param catalog XMLCatalog
118      */
119     public void addConfiguredXMLCatalog(XMLCatalog catalog)
120     {
121         this.xmlCatalog.addConfiguredXMLCatalog(catalog);
122     }
123 
124     /***
125      * specify a set of file to be checked.
126      * @param set Fileset
127      */
128     public void addFileset(FileSet set)
129     {
130         this.filesets.add(set);
131     }
132 
133     /***
134      * initialize task.
135      */
136     public void init()
137     {
138         super.init();
139         this.xmlCatalog.setProject(this.project);
140     }
141 
142     /***
143      * Create a DTD location record. This stores the location of a DTD. The DTD is identified by its public Id
144      * @return DTDLocation
145      */
146     public DTDLocation createDTD()
147     {
148         DTDLocation dtdLocation = new DTDLocation();
149         this.xmlCatalog.addDTD(dtdLocation);
150         return dtdLocation;
151     }
152 
153     /***
154      * Init the parser. Load the parser class and enable validation
155      */
156     private void initValidator()
157     {
158 
159         // turn validation on
160         try
161         {
162             this.xmlReader = XMLReaderFactory.createXMLReader();
163             this.xmlReader.setFeature(VALIDATION_FEATURE, true);
164 
165         }
166         catch (SAXNotRecognizedException e)
167         {
168             throw new BuildException("Could not start validation: " + this.xmlReader + " doesn't provide validation");
169         }
170         catch (SAXNotSupportedException e)
171         {
172             throw new BuildException("Could not start validation: " + this.xmlReader + " doesn't provide validation");
173         }
174         catch (SAXException e)
175         {
176             throw new BuildException("Could not start validation: " + this.xmlReader + " not found");
177         }
178 
179         log(this.xmlReader.getClass().getName(), Project.MSG_DEBUG);
180 
181         // set entity resolver
182         this.validatorHandler.setChainedEntityResolver(this.xmlCatalog);
183         this.xmlReader.setEntityResolver(this.validatorHandler);
184 
185         // set handler for validation messages
186         this.xmlReader.setErrorHandler(this.validatorHandler);
187 
188     }
189 
190     /***
191      * execute the task.
192      * @throws BuildException if unable to build a validating SAX2 parser
193      */
194     public void execute() throws BuildException
195     {
196 
197         // check output dir
198         File dir = this.outputFile.getParentFile();
199 
200         // create output dir if missing
201         if (dir != null)
202         {
203             dir.mkdirs();
204         }
205 
206         // set output format
207         OutputFormat format = OutputFormat.createPrettyPrint();
208         format.setEncoding(this.encoding);
209 
210         // create the XMLWriter
211         try
212         {
213             this.out = new XMLWriter(new FileOutputStream(this.outputFile), format);
214         }
215         catch (UnsupportedEncodingException e)
216         {
217             throw new BuildException("Unsopported encoding: [" + this.encoding + "]");
218         }
219         catch (FileNotFoundException e)
220         {
221             throw new BuildException("Unable to write to [" + this.outputFile.getAbsolutePath() + "]");
222         }
223 
224         // init validator
225         initValidator();
226 
227         // pass out to validator handler
228         this.validatorHandler.setOut(this.out);
229 
230         // create the root element
231         Element validation = new DefaultElement("validation");
232         try
233         {
234             // start xml document and open the root element
235             this.out.startDocument();
236             this.out.writeOpen(validation);
237 
238             // create the root element
239 
240             // iterates on filesets
241             Iterator fileIterator = this.filesets.iterator();
242 
243             while (fileIterator.hasNext())
244             {
245                 // next fileset
246                 FileSet fs = (FileSet) fileIterator.next();
247 
248                 // scan for files
249                 DirectoryScanner ds = fs.getDirectoryScanner(this.project);
250 
251                 // get all the file names
252                 String[] files = ds.getIncludedFiles();
253 
254                 for (int j = 0; j < files.length; j++)
255                 {
256                     // create a new File
257                     File srcFile = new File(fs.getDir(this.project), files[j]);
258 
259                     // new file element
260                     Element xmlfile = new DefaultElement("file");
261 
262                     // add file name to element
263                     xmlfile.addAttribute("name", files[j]);
264 
265                     // open file element
266                     this.out.writeOpen(xmlfile);
267 
268                     // perform validation
269                     doValidate(srcFile);
270 
271                     // close file element
272                     this.out.writeClose(xmlfile);
273                 }
274             }
275 
276             // close the root element
277             this.out.writeClose(validation);
278 
279             // end document
280             this.out.endDocument();
281         }
282 
283         catch (SAXException e)
284         {
285             log(e.getClass() + " " + e.getMessage(), Project.MSG_WARN);
286             e.printStackTrace();
287             throw new BuildException(e + " " + e.getMessage());
288         }
289         catch (Exception e)
290         {
291             log(e + " " + e.getClass() + " " + e.getMessage(), Project.MSG_WARN);
292             e.printStackTrace();
293             throw new BuildException(e + " " + e.getMessage());
294         }
295 
296         finally
297         {
298             try
299             {
300                 // be sure out is closed
301                 if (this.out != null)
302                 {
303                     this.out.close();
304                 }
305             }
306             catch (IOException e)
307             {
308                 log("Caught IOException: " + e.getMessage(), Project.MSG_ERR);
309             }
310         }
311     }
312 
313     /***
314      * parses the file.
315      * @param afile File to validate
316      */
317     private void doValidate(File afile)
318     {
319         log("Validating " + afile.getName() + "... ", Project.MSG_INFO);
320 
321         // init validation handler
322         this.validatorHandler.init(afile);
323 
324         try
325         {
326 
327             InputSource is = new InputSource(new FileInputStream(afile));
328             String uri = "file:" + afile.getAbsolutePath().replace('//', '/');
329             for (int index = uri.indexOf('#'); index != -1; index = uri.indexOf('#'))
330             {
331                 uri = uri.substring(0, index) + "%23" + uri.substring(index + 1);
332             }
333             is.setSystemId(uri);
334             is.setEncoding(this.encoding);
335 
336             this.xmlReader.parse(is);
337         }
338         catch (SAXException e)
339         {
340             logFatalError(e.getMessage());
341         }
342         catch (UnknownHostException e)
343         {
344             logFatalError("Could not validate document "
345                 + afile.getName()
346                 + "; dtd missing from repository and unable to connect ("
347                 + e.getMessage()
348                 + ")");
349         }
350         catch (IOException e)
351         {
352             logFatalError(e.getMessage());
353         }
354 
355         if (this.validatorHandler.getErrors() > 0)
356         {
357             log(afile + ", errors=" + this.validatorHandler.getErrors(), Project.MSG_INFO);
358         }
359 
360     }
361 
362     /***
363      * log event to output.
364      * @param e SAXParseException
365      * @param logLevel int
366      */
367     private void logFatalError(String message)
368     {
369         Element error = new DefaultElement("error");
370         error.addAttribute("line", "" + 0);
371         error.addAttribute("col", "" + 0);
372         error.addAttribute("position", "0:0");
373         error.addAttribute("level", "fatal");
374 
375         // check for invalid sequence ]]> and encode to be sure not to break the build
376         int endCdata;
377         while (message != null && (endCdata = message.indexOf("]]>")) > -1)
378         {
379             message = message.substring(0, endCdata) + "] ] >" + message.substring(endCdata + 3, message.length());
380         }
381 
382         error.addCDATA(message);
383         writeToLog(error);
384     }
385 
386     /***
387      * write an element to the xml log file.
388      * @param element Element to write
389      */
390     private void writeToLog(Element element)
391     {
392         try
393         {
394             this.out.write(element);
395         }
396         catch (IOException e)
397         {
398             log("Caught IOException: " + e.getMessage(), Project.MSG_ERR);
399         }
400     }
401 
402 }