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
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
182 this.validatorHandler.setChainedEntityResolver(this.xmlCatalog);
183 this.xmlReader.setEntityResolver(this.validatorHandler);
184
185
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
198 File dir = this.outputFile.getParentFile();
199
200
201 if (dir != null)
202 {
203 dir.mkdirs();
204 }
205
206
207 OutputFormat format = OutputFormat.createPrettyPrint();
208 format.setEncoding(this.encoding);
209
210
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
225 initValidator();
226
227
228 this.validatorHandler.setOut(this.out);
229
230
231 Element validation = new DefaultElement("validation");
232 try
233 {
234
235 this.out.startDocument();
236 this.out.writeOpen(validation);
237
238
239
240
241 Iterator fileIterator = this.filesets.iterator();
242
243 while (fileIterator.hasNext())
244 {
245
246 FileSet fs = (FileSet) fileIterator.next();
247
248
249 DirectoryScanner ds = fs.getDirectoryScanner(this.project);
250
251
252 String[] files = ds.getIncludedFiles();
253
254 for (int j = 0; j < files.length; j++)
255 {
256
257 File srcFile = new File(fs.getDir(this.project), files[j]);
258
259
260 Element xmlfile = new DefaultElement("file");
261
262
263 xmlfile.addAttribute("name", files[j]);
264
265
266 this.out.writeOpen(xmlfile);
267
268
269 doValidate(srcFile);
270
271
272 this.out.writeClose(xmlfile);
273 }
274 }
275
276
277 this.out.writeClose(validation);
278
279
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
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
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
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 }