View Javadoc

1   /*
2   This file is part of the xframe software package
3   hosted at http://xframe.sourceforge.net
4   
5   Copyright (c) 2003 Kurt Riede.
6   
7   This library is free software; you can redistribute it and/or
8   modify it under the terms of the GNU Lesser General Public
9   License as published by the Free Software Foundation; either
10  version 2.1 of the License, or (at your option) any later version.
11  
12  This library is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Lesser General Public License for more details.
16  
17  You should have received a copy of the GNU Lesser General Public
18  License along with this library; if not, write to the Free Software
19  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20  */
21  package net.sf.xframe.xsddoc;
22  
23  import java.io.File;
24  import java.util.Vector;
25  
26  import javax.xml.parsers.DocumentBuilder;
27  import javax.xml.parsers.DocumentBuilderFactory;
28  import javax.xml.parsers.ParserConfigurationException;
29  
30  import org.apache.tools.ant.BuildException;
31  import org.apache.tools.ant.DirectoryScanner;
32  import org.apache.tools.ant.Project;
33  import org.apache.tools.ant.taskdefs.MatchingTask;
34  import org.apache.tools.ant.types.FileSet;
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Element;
37  
38  /***
39   * Adapter for xsddoc processor to Apache Ant.
40   *
41   * <p>Generates XML Schema documentation using the xsddoc tool.</p>
42   * <h3>Parameters</h3>
43   * <p><table border="1" cellspacing="0" cellpadding="2">
44   * <tr><td><b>Attribute</b></td><td><b>Description</b></td><td><b>Required</b></td></tr>
45   * <tr><td>schemaLoaction</td><td><b>deprecated</b>, see <code>file</code> parameter</td>
46   * <td rowspan="3">Exactly one of the these or nested &lt;fileset></td></tr>
47   * <tr><td>file</td><td>shortcut for specifying a single file fileset</td></tr>
48   * <tr><td>dir</td><td>shortcut for specifying a single folder fileset</td></tr>
49   * <tr><td>out</td><td>destination directory for output files</td><td>Yes</td></tr>
50   * <tr><td>xml</td><td>If output should be XML instead of HTML. (yes | no). Default is no</td><td>No</td></tr>
51   * <tr><td>css</td><td>provide external CSS file</td><td>&amp;</td></tr>
52   * <tr><td>title</td><td><b>deprecated</b>, see <code>doctitle</code> parameter</td><td>No</td></tr>
53   * <tr><td>doctitle</td><td>Include title for the package index(first) page (html-code)</td><td>No</td></tr>
54   * <tr><td>header</td><td>Include header text for each page (html-code)</td><td>No</td></tr>
55   * <tr><td>footer</td><td>Include footer text for each page (html-code)</td><td>No</td></tr>
56   * <tr><td>bottom</td><td>Include bottom text for each page (html-code)</td><td>No</td></tr>
57   * <tr><td>failonerror</td><td>Log a warning message, but do not stop the
58   * build, when the file to copy does not exist or one of the nested filesets
59   * points to a directory that doesn't exist or an error occurs while copying.
60   * (yes | no). Default is no</td><td>No</td></tr>
61   * <tr><td>verbose</td><td>Output messages about what xsddoc is doing. (yes | no). Default is yes</td><td>No</td></tr>
62   * <tr><td>quiet</td><td>Be quiet about what xsddoc is doing. (yes | no). Default is no</td><td>No</td></tr>
63   * <tr><td>debug</td><td>Output internal messages about what xsddoc is doing. (yes | no). Default is no</td><td>No</td></tr>
64   * <tr><td>hideSubTypes</td><td>hide sub types references. (yes | no). Default is no</td><td>No</td></tr>
65   * <tr><td>hideLocalUsage</td><td>hide local usage references. (yes | no). Default is no</td><td>No</td></tr>
66   * <tr><td>hideTypes</td><td>hide types in overview pages. (yes | no). Default is no</td><td>No</td></tr>
67   * <tr><td>hideGroups</td><td>hide groups in overview pages. (yes | no). Default is no</td><td>No</td></tr>
68   * <tr><td>hideAttributes</td><td>hide attributes in overview pages. (yes | no). Default is no</td><td>No</td></tr>
69   * <tr>
70   * </table></p>
71   *
72   * <h3>Parameters specified as nested elements</h3>
73   *
74   * <h4>fileset</h4>
75   * <p>
76   * <a href="http://ant.apache.org/manual/CoreTypes/fileset.html">FileSets</a>
77   * are used to select sets of files to process. To use a fileset, the todir
78   * attribute must be set.</p>
79   *
80   * <h4>doctitle</h4>
81   * <p>Same as the <code>doctitle</code> attribute, but you can nest text
82   * inside the element this way.</p>
83   * <h4>header</h4>
84   * <p>Similar to <code>&lt;doctitle&gt;</code>.</p>
85   * <h4>footer</h4>
86   * <p>Similar to <code>&lt;doctitle&gt;</code>.</p>
87   * <h4>bottom</h4>
88   * <p>Similar to <code>&lt;doctitle&gt;</code>.</p>
89   *
90   * <dl><dt><b>Usage in Apache Ant build files:</b></dt><dd><pre>
91   *&lt;!--
92   *  Define xsddoc task.
93   *-->
94   *&lt;taskdef name="xsddoc" classname="net.sf.xframe.xsddoc.Task"/>
95   *
96   *&lt;!--
97   *  Use xsddoc task.
98   *-->
99   *&lt;xsddoc file="myschema.xsd"
100  *         out="doc/schema/myschema">
101  *  &lt;doctitle>&lt;![CDATA[XML <code>Schema</code> for XML Schema]]>&lt;/doctitle>
102  *&lt;/xsddoc>
103  * </pre></dl>
104  *
105  * @author <a href="mailto:kriede@users.sourceforge.net">Kurt Riede</a>
106  *
107  */
108 public final class Task extends MatchingTask {
109 
110     /*** namespace of XML schema. */
111     private static final String SCHEMA_NS = "http://www.w3.org/2001/XMLSchema";
112 
113     /*** documentation footer. */
114     private Html footer;
115 
116     /*** Documentation Title. */
117     private Html doctitle;
118 
119     /*** Documentation header. */
120     private Html header;
121 
122     /*** Documentation bottom. */
123     private Html bottom;
124 
125     /*** Folder of schema files in case of folder based schema localisation. */
126     private String dir = null;
127 
128     /*** schema location in case of a single schema file.*/
129     private String file = null;
130 
131     /*** Fileset in case of fileset based schema localisation. */
132     private Vector filesets = new Vector();
133 
134     /*** Reference to the xsddoc processor. */
135     private Processor processor = null;
136 
137     /***
138      * Whether to stop the build, when the file to process does not exist or
139      * one of the nested filesets points to a directory that doesn't exist or
140      * an error occurs while copying.
141      */
142     private boolean failonerror = true;
143 
144     /*** if verbose mode or not. */
145     private boolean verbose = false;
146 
147     /*** if debug mode or not. */
148     private boolean debug = false;
149 
150     /*** Reference to a DOM document builder. */
151     private DocumentBuilder builder = null;
152 
153     /***
154      * Default constructor.
155      */
156     public Task() {
157         processor = new Processor();
158     }
159 
160     /***
161      * Execute xsddoc task.
162      *
163      * @throws BuildException if execution of xsddoc task failed
164      */
165     public void execute() throws BuildException {
166         try {
167             process();
168         } catch (Exception e) {
169             if (failonerror) {
170                 if (e instanceof BuildException) {
171                     throw (BuildException) e;
172                 }
173                 throw new BuildException(e);
174             }
175             log(e.getMessage(), Project.MSG_WARN);
176         }
177     }
178 
179     /***
180      * Execute xsddoc task.
181      *
182      * @throws ProcessorException if error occured during execution
183      */
184     private void process() throws ProcessorException {
185         final int isFile = (file == null ? 0 : 1);
186         final int isDir = (dir == null ? 0 : 1);
187         final int isFileset = (filesets.size() == 0 ? 0 : 1);
188         if (isFile + isDir + isFileset != 1) {
189             throw new BuildException("Exactly one of the file or dir attributes, or a fileset element, must be set.");
190         }
191         if (doctitle != null) {
192             processor.setDoctitle(doctitle.getText());
193         }
194         if (header != null) {
195             processor.setHeader(header.getText());
196         }
197         if (footer != null) {
198             processor.setFooter(footer.getText());
199         }
200         if (bottom != null) {
201             processor.setBottom(bottom.getText());
202         }
203         if (file != null) {
204             processor.setSchemaLocation(file);
205             processor.execute();
206         }
207         if (dir != null) {
208             final Document mediatorSchema = createDocument();
209             final File folder = new File(dir);
210             if (!folder.exists()) {
211                 throw new BuildException("not a directory: " + folder.getAbsolutePath());
212             }
213             final String[] files = folder.list();
214             for (int i = 0; i < files.length; i++) {
215                 if (debug) {
216                     System.out.println("found file " + files[i]);
217                 }
218                 final File theFile = new File(files[i]);
219                 if (!theFile.isDirectory()) {
220                     addSchema(mediatorSchema, folder.getAbsolutePath(), files[i]);
221                 } else {
222                     if (debug) {
223                         System.out.println("Seems not to be a file " + files[i]);
224                     }
225                 }
226             }
227             processor.setSchemaLocation(folder.getAbsolutePath() + File.separator + "mediator-schema");
228             processor.setMainSchema(mediatorSchema);
229             processor.execute();
230         }
231         if (filesets.size() > 0) {
232             builder = getDocumentBuilder();
233             final Document mediatorSchema = builder.getDOMImplementation().createDocument(SCHEMA_NS, "schema", null);
234             for (int i = 0; i < filesets.size(); i++) {
235                 final FileSet fileSet = (FileSet) filesets.elementAt(i);
236                 final DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject());
237                 addSchemas(mediatorSchema, scanner.getBasedir().getAbsolutePath(), scanner);
238             }
239             processor.setSchemaLocation(getProject().getBaseDir().getAbsolutePath() + File.separator + "todo");
240             processor.setMainSchema(mediatorSchema);
241             processor.execute();
242         }
243     }
244 
245     /***
246      * Creates a new empty DOM document.
247      *
248      * @return mediator schema as a DOM document
249      */
250     private Document createDocument() {
251         final Document mediatorSchema = getDocumentBuilder().getDOMImplementation().createDocument(SCHEMA_NS, "schema", null);
252         return mediatorSchema;
253     }
254 
255     /***
256      * Returns a DOM document builder.
257      *
258      * @return DOM document builder
259      * @throws BuildException if the DocumentBuilder cannot be created
260      */
261     private DocumentBuilder getDocumentBuilder() throws BuildException {
262         if (builder != null) {
263             return builder;
264         }
265         try {
266             final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
267             builder = factory.newDocumentBuilder();
268             return builder;
269         } catch (ParserConfigurationException e) {
270             throw new BuildException(e);
271         }
272     }
273 
274     /***
275      * Adds a set of schemas defined by the files in a DirectoryScanner to
276      * a mediator schema.
277      *
278      * <p>A mediator schema is a schema that does nothing else than import
279      * a set of other schemas.</p>
280      *
281      * @param mediatorSchema the mediator schema
282      * @param base base file to resolve relative paths
283      * @param scanner DirectoryScanner that defines the schema files to mediate
284      */
285     private void addSchemas(final Document mediatorSchema, final String base, final DirectoryScanner scanner) {
286         final String[] files = scanner.getIncludedFiles();
287         for (int i = 0; i < files.length; i++) {
288             addSchema(mediatorSchema, base, files[i]);
289         }
290     }
291 
292     /***
293      * Adds a schema defined by a file name to a mediator schema.
294      *
295      * @param mediatorSchema the meditator schema
296      * @param base base file to resolve relative paths
297      * @param schemaLocation the schema to add
298      */
299     private void addSchema(final Document mediatorSchema, final String base, final String schemaLocation) {
300         if (verbose) {
301             System.out.println("found schema " + schemaLocation);
302         }
303         String namespace = null;
304         namespace = getNamespace(base, schemaLocation);
305         if (namespace == null) {
306             if (debug) {
307                 System.out.println("Seems not to be a schema, ignoring " + schemaLocation);
308             }
309             return;
310         }
311         final Element importNode = mediatorSchema.createElementNS(SCHEMA_NS, "import");
312         final String projectDir = getProject().getBaseDir().getAbsolutePath() + File.separator;
313         // Since the processor interprets everything as relative, the schemaLocation should
314         // be the relative path from the project base. However the base string may contain
315         // some path info not in the base. That is the base may be obtained from the fileset
316         // attribute.
317         final String schemaLoc;
318         //Does the base have additional information?
319         if (base.length() > projectDir.length() && base.startsWith(projectDir)) {
320             String relativePath = base.substring(projectDir.length());
321             schemaLoc = relativePath + File.separator + schemaLocation;
322         } else {
323             schemaLoc = schemaLocation;
324         }
325         importNode.setAttribute("schemaLocation", schemaLoc);
326         importNode.setAttribute("namespace", namespace);
327         mediatorSchema.getDocumentElement().appendChild(importNode);
328     }
329 
330     /***
331      * Extracts and returns the target namespace from a schema file.
332      *
333      * @param base base file name to resolve relative paths
334      * @param filename file name of schema file
335      * @return target namespace of schema
336      */
337     private String getNamespace(final String base, final String filename) {
338         try {
339             final Document schema = getDocumentBuilder().parse(new File(base, filename));
340             final Element root = schema.getDocumentElement();
341             return root.getAttribute("targetNamespace");
342         } catch (Exception e) {
343             return null;
344         }
345     }
346 
347     ////////////////////////////////////////////////
348     // attribute setters
349     ////////////////////////////////////////////////
350 
351     /***
352      * Setter method for debug property.
353      *
354      * @param isDebug output debug information or not
355      */
356     public void setDebug(final boolean isDebug) {
357         debug = isDebug;
358         processor.setDebug(isDebug);
359     }
360 
361     /***
362      * Setter method for out property.
363      *
364      * @param out output folder to use
365      */
366     public void setOut(final String out) {
367         processor.setOut(out);
368     }
369 
370     /***
371      * Setter method for schemaLocation property.
372      *
373      * @param schemaLocation location of XML schema to use
374      * @deprecated use {@link #setFile(java.lang.String)} instead
375      * @see #setFile(java.lang.String)
376      */
377     public void setSchemalocation(final String schemaLocation) {
378         file = schemaLocation;
379     }
380 
381     /***
382      * Setter method for file property.
383      *
384      * @param theFile location of XML schema
385      */
386     public void setFile(final String theFile) {
387         file = theFile;
388     }
389 
390     /***
391      * Setter method for dir property.
392      *
393      * @param theDir folder to search for schemas
394      */
395     public void setDir(final String theDir) {
396         dir = theDir;
397     }
398 
399     /***
400      * Setter method for title property.
401      *
402      * @param title title to use
403      * @deprecated use {@link #setDoctitle(java.lang.String)} instead
404      * @see #setDoctitle(java.lang.String)
405      */
406     public void setTitle(final String title) {
407         final Html html = new Html();
408         html.addText(title);
409         addDoctitle(html);
410     }
411 
412     /***
413      * Setter method for css property.
414      *
415      * @param css location of XML schema
416      */
417     public void setCss(final String css) {
418         processor.setCss(css);
419     }
420 
421     /***
422      * Setter method for verbose property.
423      *
424      * @param isVerbose be verbose or not
425      */
426     public void setVerbose(final boolean isVerbose) {
427         verbose = isVerbose;
428         processor.setVerbose(isVerbose);
429     }
430 
431     /***
432      * Setter method for quiet property.
433      *
434      * @param isQuiet be quiet or not
435      */
436     public void setQuiet(final boolean isQuiet) {
437         verbose = !isQuiet;
438         processor.setVerbose(!isQuiet);
439     }
440 
441     /***
442      * Setter method for hideLocalUsage property.
443      *
444      * @param hideLocalUsage if local usage should be hidden or not
445      */
446     public void setHidelocalusage(final boolean hideLocalUsage) {
447         processor.setHideLocalUsage(hideLocalUsage);
448     }
449 
450     /***
451      * Setter method for hideSubTypes property.
452      *
453      * @param hideSubTypes if sub types should be hidden or not
454      */
455     public void setHidesubtypes(final boolean hideSubTypes) {
456         processor.setHideSubTypes(hideSubTypes);
457     }
458 
459     /***
460      * Setter method for hideTypes property.
461      *
462      * @param hideTypes if types should be hidden or not
463      */
464     public void setHidetypes(final boolean hideTypes) {
465         processor.setHideTypes(hideTypes);
466     }
467 
468     /***
469      * Setter method for hideAttributes property.
470      *
471      * @param hideAttributes if attributes should be hidden or not
472      */
473     public void setHideattributes(final boolean hideAttributes) {
474         processor.setHideAttributes(hideAttributes);
475     }
476 
477     /***
478      * Setter method for hideGroups property.
479      *
480      * @param hideGroups if groups should be hidden or not
481      */
482     public void setHidegroups(final boolean hideGroups) {
483         processor.setHideGroups(hideGroups);
484     }
485 
486     /***
487      * Setter method for xml attribute.
488      *
489      * @param isXml boolean
490      */
491     public void setXml(final boolean isXml) {
492         processor.setXml(isXml);
493     }
494 
495     /***
496      * Setter method for failonerror attribute.
497      *
498      * @param isFailonerror if should fail on error or not
499      */
500     public void setFailonerror(final boolean isFailonerror) {
501         failonerror = isFailonerror;
502     }
503 
504     ////////////////////////////////////////////////
505     // nested tag setters
506     ////////////////////////////////////////////////
507 
508     /***
509      * Adds a set of files to be deleted.
510      * @param set the set of files to be deleted
511      */
512     public void addFileset(final FileSet set) {
513         filesets.addElement(set);
514     }
515 
516     /***
517      * Add a document title to use for the overview page.
518      *
519      * @param text the HTML element containing the document title.
520      */
521     public void addDoctitle(final Html text) {
522         doctitle = text;
523     }
524 
525     /***
526      * Set the title of the generated overview page.
527      *
528      * @param theDoctitle the Document title.
529      */
530     public void setDoctitle(final String theDoctitle) {
531         final Html html = new Html();
532         html.addText(theDoctitle);
533         addDoctitle(html);
534     }
535 
536     /***
537      * Set the header text to be placed at the top of each output file.
538      *
539      * @param theHeader the header text
540      */
541     public void setHeader(final String theHeader) {
542         final Html html = new Html();
543         html.addText(theHeader);
544         addHeader(html);
545     }
546 
547     /***
548      * Set the header text to be placed at the top of each output file.
549      *
550      * @param theHeader the header text
551      */
552     public void addHeader(final Html theHeader) {
553         header = theHeader;
554     }
555 
556     /***
557      * Set the footer text to be placed at the bottom of each output file.
558      *
559      * @param text the footer text.
560      */
561     public void addFooter(final Html text) {
562         footer = text;
563     }
564 
565     /***
566      * Set the footer text to be placed at the bottom of each output file.
567      *
568      * @param theFooter the footer text.
569      */
570     public void setFooter(final String theFooter) {
571         final Html html = new Html();
572         html.addText(theFooter);
573         addFooter(html);
574     }
575 
576     /***
577      * Set the text to be placed at the bottom of each output file.
578      *
579      * @param theBottom the bottom text.
580      */
581     public void setBottom(final String theBottom) {
582         final Html html = new Html();
583         html.addText(theBottom);
584         addBottom(html);
585     }
586 
587     /***
588      * Set the text to be placed at the bottom of each output file.
589      *
590      * @param theBottom the bottom text.
591      */
592     public void addBottom(final Html theBottom) {
593         bottom = theBottom;
594     }
595 
596     /***
597      * An HTML fragment in a nested element of the xsddoc task.
598      *
599      * This class is used for those nested xsddoc elements which can contain
600      * HTML such as doctitle, footer or header.
601      */
602     public static final class Html {
603 
604         /*** A buffer fot the text of the element. */
605         private StringBuffer text = new StringBuffer();
606 
607         /***
608          * Add text to the element.
609          *
610          * @param theText the text to be added.
611          */
612         public void addText(final String theText) {
613             text.append(theText);
614         }
615 
616         /***
617          * Get the current text for the element.
618          *
619          * @return the current text.
620          */
621         public String getText() {
622             return text.substring(0);
623         }
624     }
625 }