View Javadoc

1   package DTDDoc;
2   
3   import java.io.*;
4   import java.net.URI;
5   import java.net.URISyntaxException;
6   import java.text.Collator;
7   import java.text.DateFormat;
8   import java.util.*;
9   
10  import com.wutka.dtd.*;
11  
12  /** Main class to the DTDDoc program, everything happen here.
13   *  @author Stefan Champailler */
14  
15  /*
16  
17  Given a set of DTD's we want to know in which one a given element is defined.
18  We can restate the problem as:
19  
20  given a set of element, and a set of DTD's where these elements are defined,
21  determine in which set each element is defined.
22  
23  Problem is that for a DTD we can enumerate all elements that are defined
24  in it *AND* in its  children *without* distinction. Therefore, we can
25  only know iff an element is defined in a DTD (and not its children) if and
26  only if this DTD has no children.
27  
28  As DTD have children, they form a tree.
29  
30  Solution is follwoing:
31  
32  1. If the tree t has children:
33         ce = set of all element defined in the children of t.
34         The elements defined in t are the set of all elements
35         defined in t and its children (we know that) minus ce
36         (we have computed)
37  
38  2. if t has no child
39         the elements defined in t are easy to guess.
40  
41  
42  */
43  
44  public class DTDCommenter {
45  
46      private Logger log;
47      private boolean getAroundNetBeanComments;
48      private boolean showHiddenTags;
49      private boolean showFixmeTags;
50  
51  
52      /** Version of DTDDoc running */
53      public final static String VERSION;
54      static {
55          Package pkg = DTDCommenter.class.getPackage();
56          VERSION = (pkg == null) ? "(unknown version)" : pkg.getImplementationVersion();
57      }
58  
59      /**
60       * @deprecated maintained for binary backward compatibility: uses SystemLogger as
61       * default logging system.
62       */
63      public DTDCommenter() {
64          this( new SystemLogger());
65      }
66  
67      public DTDCommenter( Logger log) {
68          this.log = log;
69      }
70  
71      /** Where we'll get the original DTD files. */
72      private File sourceDir = null;
73  
74      /** Where we'll put all the generated documentation. */
75      private File destDir = null;
76  
77      /** Remove the source dir part of a File. The result of this function
78       *  is intended to be used as a relative path to a destination
79       *  directory.
80       *
81       *  @param origin a file located in the source directory
82       *  @return the path of the file relative to the source directory */
83  
84      private String getRelativePath( File origin) throws IOException {
85  
86          String srcDir = sourceDir.getCanonicalPath();
87          String fullPath = origin.getCanonicalPath();
88  
89          if (fullPath.startsWith(srcDir))
90              // +1 for the File.separator char
91              return fullPath.substring(srcDir.length() + 1);
92          else
93              return fullPath;
94      }
95  
96  
97      /** Transform a source directory based path into
98       *  a destination directory based one. Then we create the directory
99       *  if it doesn't exist ('cos we know that if we wan to put
100      *  something in that directory, we certainly want it to exist).
101      *
102      *  @param srcFile The source path */
103 
104     private void mkdestdirsRelativePath( File srcFile) throws IOException {
105 
106         // Basically, we transform a source directory based path into
107         // a destination directory based one. Then we create the directory
108         // if it doesn't exist.
109 
110         String fullRelativePath = getRelativePath( srcFile);
111         int lastSeparator = fullRelativePath.lastIndexOf( File.separatorChar);
112         if( lastSeparator >= 0) {
113             String relativePath = fullRelativePath.substring(0, lastSeparator);
114             File dirToMake = new File( destDir, relativePath);
115             dirToMake.mkdirs();
116         }
117     }
118 
119 
120     /** Check if a node is flat, that is, if it has no children.
121      *  @param node the node to check.
122      *  @return true if the node is flat, false else. If node is null,
123      *               the function returns false as well. */
124 
125     private static boolean isFlat(DTDItem node) {
126 
127         if (node instanceof DTDContainer) {
128             Iterator items = ((DTDContainer) node).getItemsVec().iterator();
129 
130             while (items.hasNext()) {
131                 DTDItem item = (DTDItem) items.next();
132                 if (item instanceof DTDChoice)
133                     return true;
134             }
135         }
136 
137         return false;
138     }
139 
140     /** Builds a coma separated list of HREF to the parents of a given element.
141      *  Most of the time there will be only one parent, but there can be
142      *  several.
143      *
144      *  @param dtd The dtd to which the element belongs.
145      *  @param e The element. This element <em>must</em> have a parent.
146      *  @return A string representing the HREF to the parents of the element. */
147 
148     private String getHREFToParents(ExtendedDTD dtd, DTDElement e) {
149         Iterator i = dtd.getParents(e.getName()).iterator();
150 
151         StringBuffer buffer = new StringBuffer();
152         while (i.hasNext()) {
153             DTDElement p = (DTDElement) i.next();
154             buffer.append(getInternalHREF( dtd, p));
155             if (i.hasNext())
156                 buffer.append(", ");
157         }
158 
159         return buffer.toString();
160     }
161 
162 
163 
164     /** Builds a HTML internal reference to an element inside the documentation
165      *  of a DTD (<code>#element_id</code>).
166      *
167      *  @param dtd the target DTD  (current, since it is an internal reference).
168      *  @param element The element to reference.
169      *  @return A string containing the HTML code of the reference:
170      *  <code>&lt;a href='#element_id'&gt;element name&lt;/a&gt; */
171 
172     public String getInternalHREF( ExtendedDTD dtd, DTDElement element) {
173         if( element != null)
174             return "<a href='#" + ExtendedDTD.getUniqueId( element) + "'>" +
175                 element.getName() + "</a>";
176         else {
177             // FIXME This should throw an exception. But before doing so
178             // we must make sure that this won't break backward compatibility.
179             log.error("Can't build a HREF to a null element");
180             return "";
181         }
182     }
183 
184 
185     /** Builds a HTML external reference to an element from the documentation
186      *  of a DTD (<code>filename#element_id</code>).
187      *
188      *  @param dtd the target DTD.
189      *  @param element The element to reference.
190      *  @param text text that will appear in the reference.
191      *  @return A string containing the HTML code of the reference:
192      *  <code>&lt;a href='<i>dtd_reference</i>#element_id'&gt;text&lt;/a&gt; */
193 
194     public String getExternalHREF( ExtendedDTD dtd, DTDElement element,
195             String text) throws IOException {
196         return "<a href='" + newURITo( dtd, element).toString() + "'>"
197                 + text + "</a>";
198     }
199 
200     public String getExternalHREF( ExtendedDTD dtd, String elementName,
201             String text) throws IOException {
202         return getExternalHREF( dtd, dtd.getElementByName( elementName),
203                 text);
204     }
205 
206     public String getDTDBaseURI( ExtendedDTD dtd) {
207 
208         try {
209             return (getRelativePath( dtd.getSystemPath()) + ".html").
210                     replace( File.separatorChar, '/'); // FIXME There should a constant for that slash.
211         } catch( IOException ex) {
212             log.error( "Unable to make a relative path to DTD: "
213                     + dtd.getSystemPath(), ex);
214             return null;
215         }
216     }
217 
218     private URI toURI( String uri) {
219         try {
220             return new URI( uri.replace( File.separatorChar, '/'));
221         } catch( URISyntaxException ex) {
222             log.error("Unable to create a URI out of the string '" +
223                     uri + "'.",ex);
224             return null;
225         }
226     }
227 
228     public URI newURITo( ExtendedDTD dtd) {
229         return toURI( getDTDBaseURI( dtd));
230     }
231 
232     public URI newURITo( ExtendedDTD dtd, DTDElement element) {
233 
234         return toURI( getDTDBaseURI( dtd) + '#' +
235                 ExtendedDTD.getUniqueId( element) );
236     }
237 
238     public URI newURITo( ExtendedDTD dtd, DTDAttlist list,
239             DTDAttribute attribute) {
240 
241         return toURI( getDTDBaseURI( dtd) + '#' +
242                     ExtendedDTD.getUniqueId( list, attribute));
243     }
244 
245     public URI newURITo( ExtendedDTD dtd, DTDElement element,
246             DTDAttribute attribute) {
247 
248         return toURI( getDTDBaseURI( dtd) + '#' +
249                 ExtendedDTD.getUniqueId( element, attribute));
250     }
251 
252 
253 
254 
255     /** <p>This class encapsulates the context related to a
256      *  DTDName. In this particular case, the DTDName is expected to
257      *  represent a child of an element in the element model.</p>
258      *
259      *  <p>A context describes the cardinality modifiers that are
260      *  applying to the name. For example if an element model
261      *  is (alpha | beta)* then the context is described by
262      *  <i>both</i> the star and the vertical bar. The star
263      *  states that the cardinality is "zero or many" and the
264      *  vertical bar states that alpha or beta might appear, therefore
265      *  the cardinalities of alpha and beta are the same, that
266      *  is "one at most".</p>
267      */
268 
269     class NameInfo implements Comparable {
270         public final DTDName name;
271         public final Set contexts = new HashSet();
272 
273 
274         public NameInfo( DTDName name, String context) {
275             this.name = name;
276             contexts.add( context);
277         }
278 
279         public String getName() {
280             return name.getValue();
281         }
282 
283         public void addContext( String context) {
284             contexts.add( context);
285         }
286 
287 
288         /** <p>This method establishes the overall cardinality of a child
289          *  of an element. The necessary information is taken from the
290          *  context field.</p>
291          *
292          *  <p>The basic principle is to "reduce" the context element by
293          *  element, starting at the end of the context. After each
294          *  reduction we have the overall cardinality of the child
295          *  in respect to the part of context that was processed so far.</p>
296          *
297          *  <p>The reduction opeartion consists in comparing the current
298          *  cardinality with the "current" one in the context. Those
299          *  two cardinalities are used as selector in a two dimensional
300          *  table that will give the new overall cardinality for the
301          *  context explored so far (thus the previous one extended by the
302          *  "current" cardinality). The table is like this:</p>
303          *
304          * 	<table border="1" align="center">
305          * 	  <tr>
306          *      <td></td><td><b>1</b></td><td><b>?</b></td><td><b>+</b></td><td><b>*</b></td><td><b>|</b></td><td><b>,</b></td>
307          * 	  </tr>
308          * 	  <tr>
309          *      <td><b>1</b></td><td>1</td><td>?</td><td>+</td><td>*</td><td>?</td><td>1</td>
310          * 	  </tr>
311           * 	  <tr>
312          *      <td><b>?</b></td><td>?</td><td>?</td><td>*</td><td>*</td><td>?</td><td>?</td>
313          * 	  </tr>
314          * 	  <tr>
315          *      <td><b>+</b></td><td>+</td><td>*</td><td>+</td><td>*</td><td>*</td><td>+</td>
316          * 	  </tr>
317          * 	  <tr>
318          *      <td><b>*</b></td><td>*</td><td>*</td><td>*</td><td>*</td><td>*</td><td>*</td>
319          * 	  </tr>
320          * 	  <tr>
321          *      <td><b>|</b></td><td>?</td><td>?</td><td>*</td><td>*</td><td>|</td><td>|</td>
322          * 	  </tr>
323          * 	  <tr>
324          *      <td><b>,</b></td><td>1</td><td>?</td><td>+</td><td>*</td><td>|</td><td>,</td>
325          * 	  </tr>
326          * </table>
327          *
328          * <p>Some side notes here:
329          * <ul>
330          *     <li>Surprinsigly, this table is symetric !</li>
331          *     <li>"*" leads to "*", so one could stop the processing early.</li>
332          *     <li>"," is transparent.</li>
333          * </ul>
334          * </p>
335          *
336          *  <p>This method for finding cardinality
337          *  gives good results in most cases and gives correct
338          *  but hardly usable results for complicated cases (those where a
339          *  children is deeply nested in cardinality modifiers).</p>
340          *
341          * @return A string stating the cardinality, ready to be displayed.
342          */
343 
344         public String getCardinality() {
345 
346             /*
347              *     ! 1 ? + *   |   is covered by:
348              *   --+------------
349              *   1 ! 1 ? + *   ?
350              *   ? ! ? ? * *   ?
351              *   + ! + * + *   *
352              *   * ! * * * *   *
353              *     !
354              *   | ! ? ? * *   |
355              *
356              *  - When we fall on *, we stop there.
357              *  - "," leaves things untouched (it's "transparent").
358              *  - Strangely, the array is symetric.
359              */
360 
361             final String indexBase = "1?+*|,";
362 
363             final String xform[] = {
364                 "1?+*?1",
365                 "??**??",
366                 "+*+**+",
367                 "******",
368                 "??**||",
369                 "1?+*|,"   };
370 
371             int overallCardinal = -1;
372 
373 
374             for( Iterator ic = contexts.iterator(); ic.hasNext(); ) {
375 
376                 String context = (String) ic.next();
377 
378                 // Now we proceed to the reduction of the context to
379                 // a simple "cardinal".
380 
381                 int i = context.length() - 1;
382                 int currentCardinal = indexBase.indexOf( context.charAt( i));
383 
384                 while(i > 0) {
385 
386                     // find the number of the cardinality symbol (i.e. index in
387                     // indexBase) preceeding the i-th one
388                     i = i - 1;
389                     int previous = indexBase.indexOf( context.charAt( i));
390 
391                     // Based on that previous cardinality symbol, we compute
392                     // the next current cardinality
393                     currentCardinal = indexBase.indexOf( xform[previous].charAt( currentCardinal));
394                 }
395 
396                 if( overallCardinal == -1)
397                     overallCardinal = currentCardinal;
398                 else if( overallCardinal != currentCardinal)
399                     // That's our heuristic to sya: our definition of
400                     // cardinality is too complex to make sense. And if it's
401                     // too complex, we'd better redirect the user to the real
402                     // element's definition / model.
403                     return "See model";
404             }
405 
406 
407             // Finally we convert the cardinality into something
408             // usable.
409 
410             switch( overallCardinal) {
411                 case 0: return "Only one";
412                 case 1: return "One or none";
413                 case 2: return "At least one";
414                 case 3: return "Any number";
415                 default:
416                     log.error("Something wrong happened while establishing cardinality");
417                     return null;
418             }
419         }
420 
421         public int compareTo( Object o) {
422             Collator collator = Collator.getInstance();
423 
424             String oName = ((NameInfo) o).getName();
425 
426             return collator.compare( getName(), oName);
427         }
428 
429     }
430 
431     public char cardinalToChar( DTDCardinal cardinal) {
432 
433         if( cardinal == DTDCardinal.NONE)
434             return '1';
435         else if( cardinal == DTDCardinal.OPTIONAL)
436             return '?';
437         else if( cardinal == DTDCardinal.ZEROMANY)
438             return '*';
439         else if( cardinal == DTDCardinal.ONEMANY)
440             return '+';
441         else {
442             log.error("Unrecognized cardinality! "+cardinal);
443             return 'x';
444         }
445     }
446 
447     // FIXME Decouple the children guess from the cardinality guess
448 
449     private SortedSet collectChildrenNames(DTDItem child, String context, ExtendedDTD dtd)
450         throws IOException {
451 
452         if (child instanceof DTDContainer) {
453 
454             // We flatten the father's structure.
455             // By doing so, we unfortunately loose track of the cardinality of
456             // the children.
457             // To remind this cardinality, we can store it along with the item.
458             // But if the item appears several times in the model, like
459             // ( (alpha, beta+, gamma) | (teta, beta?) ) then what does the cardinality
460             // of beta mean if it has to be expressed on one line in the children
461             // table ? Indeed we should have two lines, one saying that
462             // beta can appear 0 or more times and another line saying
463             // that beta can appear at least one time. In that kind of
464             // situation, displaying the content model look the best way to
465             // go. Unfortunately, it's very difficult to understand for
466             // non-IT people.
467 
468             Iterator items = ((DTDContainer) child).getItemsVec().iterator();
469 
470             context = context + cardinalToChar(((DTDContainer) child).getCardinal());
471 
472             if( child instanceof DTDChoice)
473                 context = context + '|';
474             else if( child instanceof DTDSequence)
475                 context = context + ',';
476             else if( child instanceof DTDMixed)
477                 context = context + '|';
478 
479             SortedSet names = null;
480 
481             while (items.hasNext()) {
482                 DTDItem item = (DTDItem) items.next();
483 
484                 SortedSet s = collectChildrenNames(item, context, dtd);
485                 if (names != null && s != null)
486                     names.addAll(s);
487                 else
488                     names = s;
489             }
490 
491             return names;
492 
493         } else if (child instanceof DTDName) {
494 
495             NameInfo ni = new NameInfo(
496                 (DTDName) child,
497                 context + cardinalToChar(((DTDName) child).getCardinal()));
498 
499             //log.debug("Adding "+ c.getValue());
500             SortedSet singleName = new TreeSet();
501 
502             singleName.add( ni);
503             //log.debug("Done Adding "+c.getValue());
504             return singleName;
505 
506         } else if (!(child instanceof DTDPCData)) {
507             log.error(
508                 "showChildrenHelper(): Unsupported child "
509                     + child.getClass().getName());
510             return null;
511         } else
512             return null;
513     }
514 
515     private void showChildrenHelper_print( NameInfo elementName,
516             PrintWriter out, ExtendedDTD dtd) {
517         DTDElement element =
518                 (DTDElement) dtd.getElementByName( elementName.getName());
519 
520             out.print("<tr>");
521             out.print("<td>");
522 
523             if (element == null) {
524 
525                 // Since element is null, we have to figure out another way to
526                 // get its name...
527 
528                 log.warn( "'" + elementName.getName() + "' element is " +
529                             "undefined in "+dtd.getSystemPath());
530 
531                 out.print(elementName.getName());
532 
533             } else
534                 out.print( getInternalHREF( dtd, element));
535 
536             out.print("</td>");
537 
538             if( element != null) {
539                 // The cardinality is something quite special here. Indeed,
540                 // because we are presenting a list of the children elements
541                 // of a given element in a separate list, then the cardinality
542                 // looses some of its meaning. It's because cardinality is
543                 // locally defined (for example: alpha*, alpha can appear several times) and/or contextually
544                 // interpreted (for example (x | alpha | y)+ alpha can appear several times as well).
545                 // The cardinality is not defined clearly in the XML specification
546                 // therefore there's some room for us.
547                 //   I'll try this definition: cardinality is the number of times
548                 //   a given child element may appear under its father element.
549                 // Does this definition make any practical sense ?
550 
551                 // First of all, we know we display a list of children out of the
552                 // element model. We do this because it helps the readability. That
553                 // is, someone may want to know quickly if an element appears under
554                 // a given element. Displaying a table of the element can solve that
555                 // question.
556 
557                 // However it doesn't solve two questions:
558                 //   - where the element may appear
559                 //   - how many times
560 
561                 // The first answer can only be stated clearly by presenting the
562                 // element definition to the user.
563 
564                 // The second answer can depend on the first one. That
565                 // is, given the use of an element, the number of times it can appear
566                 // may change. For example ( (b,a,c) | (e,a+,d) ).
567 
568                 // Finding the cardinality out of the element model is
569                 // answering the second question without explicitely refering
570                 // to the first answer.
571 
572                 // In my exdperience, the case described in the second answer
573                 // doesn't appear very often. I usually encounter simpler
574                 // structure where the need for context is very limited.
575                 // example: figure (height,width,name) (height appears once)
576                 //           paragraph (text | bold | italic | figure)*   (figure can appear several times)
577 
578                 // As a consequence, we're left with a pretty subjective
579                 // notion of the usefulness of our cardinality definition.
580                 // In a tremendous shortcut, I'll state that sometimes
581                 // providing our cardinality makes sense, sometimes it doesn't.
582                 // The good times are when the element definition is quite
583                 // simple. So if it's simple (we still have to define that :)),
584                 // then why do we need to copy that information from the
585                 // (shown) element definition into the separate children table.
586                 // Answer is: not everyone understand "*", "?", and so on.
587                 // So the separate table gives us an opportunity to simplify
588                 // the presentation some more.
589 
590                 // *,|,+ :  - + => +
591                 //          - |,+ => *
592                 //          - * => end
593 
594                 // |,1 :  - 1 => 1
595                 //        - |,1 => ? => end
596 
597                 // +,+,+ :  - + => +
598                 //          - +,+ => +
599                 //          - +,+,+ => + and end
600 
601                 /*
602                  *     ! 1 ? + *   |   is covered by :
603                  *   --+------------
604                  *   1 ! 1 ? + *   ?
605                  *   ? ! ? ? * *   ?
606                  *   + ! + * + *   *
607                  *   * ! * * * *   *
608                  *     !
609                  *   | ! ? ? * *   |
610                  *
611                  *  - When we fall on *, we stop there.
612                  *  - "," leaves things untouched (it's "transparent").
613                  *  - Strangely, the array is symetric.
614                  */
615 
616                 out.print("<td>");
617                 out.print( elementName.getCardinality());
618                 out.print("</td>");
619                 out.println("</tr>");
620             }
621     }
622 
623     /** Writes a HTML formatted table describing all the children of a given
624      *  element. To ease the presentation, the children tree is
625      *  flattened.
626      *  This brings a problem: ELEMENT e (a,(b|(c*,d))). This element e
627      *  will have d described as a mandatory child. However, it is mandatory
628      *  only in the context of (c*,d) but not in the context of b|(c*,d).
629      *  The question is therefore, what should we show about it or,
630      *  is it a good idea to flatten the children struture ?
631      *
632      *  @param father The element to display for which the children must be
633      *      described.
634      *  @param out Stream where to write the formatted children description.
635      *  @param DTD The DTD in which the element is defined. */
636 
637 
638     /** Show all the children of a given DTD element. That's where all
639      *  the formatting is done. The actual job of gathering children's
640      *  information is actually done by the showChildrenHelper() function.
641      *
642      *  @param element of which the children will be displayed.
643      *  @param out the stream where all will be written.
644      *  @param dtd The DTD from which the element comes from. */
645 
646     private void showChildren(
647         DTDElement element,
648         PrintWriter out,
649         ExtendedDTD dtd)
650         throws IOException {
651 
652         String summary = "&lt;" + element.getName() + "&gt;'s children";
653 
654         out.println("<table summary=\"" + summary + "\">");
655 
656         out.println("<thead>");
657         out.println("<tr><th class='title' colspan='2'>" + summary + "</th></tr>");
658         out.println("<tr><th colspan='2' height='1' class='ruler'></th></tr>");
659         out.print("<tr>");
660         out.print("<th class='subtitle'>Name</th>");
661         out.print("<th class='subtitle'>Cardinality</th>");
662         out.println("</tr>");
663         out.println("<tr><th colspan='2' height='1' class='ruler'></th></tr>");
664         out.println("</thead>");
665 
666         // showChildrenHelper(element.getContent(), out, dtd);
667         out.print("<tbody>");
668         SortedSet names = collectChildrenNames(element.getContent(), "", dtd);
669         for( Iterator i = names.iterator(); i.hasNext(); )
670             showChildrenHelper_print( (NameInfo) i.next(), out, dtd);
671         out.print("</tbody>");
672 
673         out.print("</table>");
674     }
675 
676     /** Gives the values of of an attribute as a coma separated list.
677      *
678      *  @param attr The attribute to get the value from.
679      *  @return a coma separated list of all the values appearing in
680      *          attr. null if anything goes wrong. */
681 
682     private static String getAttributeValues(DTDAttribute attr) {
683         if (attr.getType() instanceof DTDNotationList) {
684             DTDNotationList e = (DTDNotationList) attr.getType();
685             return Tools.join(e.getItems(), ", ");
686 
687         } else if (attr.getType() instanceof DTDEnumeration) {
688             DTDEnumeration e = (DTDEnumeration) attr.getType();
689             return Tools.join(e.getItems(), ", ");
690 
691         } else if (attr.getType() instanceof String) {
692             return "<i>Match the " + attr.getType() + " rules.</i>";
693         }
694 
695         return "unknown attribute type " + attr.getType();
696     }
697 
698     private static boolean isCDATA(DTDAttribute attr) {
699         return "CDATA".equals(attr.getType()); // FIXME There should be a
700                                                // constant for that CDATA
701                                                // string.
702     }
703 
704 
705     /** Show all the attributes of a given DTD element. That's where all
706      *  the formatting is done. The display done here is assumed to be
707      *  a short form that comes along the element's full explanation.
708      *
709      *  @param element of which the attributes will be displayed.
710      *  @param out the stream where all will be written.
711      *  @param dtd The DTD from which the element comes from.*/
712 
713     private void showAttributes(
714         DTDElement element,
715         PrintWriter out,
716         ExtendedDTD dtd) {
717 
718         String summary = "&lt;" + element.getName() + "&gt;'s attributes";
719 
720         out.print("<table  summary=\"" + summary + "\">");
721 
722         out.println("<tr>");
723         out.println("<th class='title' colspan='3'>" + summary + "</th>");
724         out.println("</tr>");
725         out.println("<tr><th colspan='3' height='1' class='ruler'></th></tr>");
726         out.println("<tr>");
727         out.print("<th class='subtitle'>Name</th>");
728         out.print("<th class='subtitle'>Values</th>");
729         out.print("<th class='subtitle'>Default</th>");
730         out.println("</tr>");
731         out.println("<tr><th colspan='3' height='1' class='ruler'></th></tr>");
732 
733         List attributes = Tools.enumerationToList( element.attributes.keys());
734         Collections.sort( attributes, Collator.getInstance());
735 
736         Iterator iter = attributes.iterator();
737         while(iter.hasNext()) {
738             String name = (String) iter.next();
739 
740             DTDAttribute attr = (DTDAttribute) element.attributes.get(name);
741 
742             out.print("<tr>");
743             out.print("<td>");
744             out.print(
745                 "<a href='#"
746                     + ExtendedDTD.getUniqueId(dtd.locateAttributesList(attr), attr)
747                     + "'>");
748             out.print(name + "</a></td>");
749 
750             out.print("<td>");
751             if( !isCDATA( attr))
752                 out.print(getAttributeValues(attr));
753             out.print("</td>");
754 
755             out.print("<td>");
756 
757             if (attr.defaultValue != null)
758                 out.print( attr.defaultValue);
759 
760             out.print("</td>");
761             out.print("</tr>");
762         }
763 
764         out.print("</table>");
765     }
766 
767     /** Show an HTML title head for a DTD's element.
768      * @see #showTitle
769      */
770 
771     private static void showElementTitle( String left, String right,
772             PrintWriter out, String titleSummary) {
773         showTitle( false, left, right, out, titleSummary);
774     }
775 
776     /** Show an HTML title head for a DTD's attribute.
777      * @see #showTitle
778      */
779 
780     private static void showAttributeTitle( String left, String right,
781             PrintWriter out, String titleSummary) {
782         showTitle( true, left, right, out, titleSummary);
783     }
784 
785     /** <p>This is used mostly for the presentation of a title of an an element
786      *  or an attribute. The title text is made of a right part and a left part.
787      *  See an example to get a proper idea of what's going on here because
788      *  it's very graphical.</p>
789      *
790      * <p>One shouldn't use this function as it is very generic. Use the
791      * specialisations instead. For example {@link #showElementTitle} or
792      * {@link #showAttributeTitle}.</p>
793      *
794      *  @param forAttribute Displays a title for an attribute. If
795      *      <code>true</code> "attribute" CSS classes are used instead of
796      *      "element" ones.
797      *  @param left Text to display on the left of the title (can be any
798      *      HTML text). Ignored if null.
799      *  @param right Text to display on the right of the title (can be any
800      *      HTML text). Ignored if null.
801      *  @param out where the title will be displayed.
802      *  @param titleSummary The summary used for the title. We need that
803      *     because the title is shown as a table. This parameter will be
804      *     used as the summary of that table. It's been added to have a
805      *     nice HTML table code. As such, this string must be pure text.*/
806 
807     private static void showTitle(
808         boolean forAttribute,
809         String left,
810         String right,
811         PrintWriter out, String titleSummary) {
812         out.print("<br />");
813         out.println(
814             "<table class='"
815         + (forAttribute ? "attributeTitle" : "elementTitle")
816         + "' summary=\""
817                 + titleSummary
818                 + "\"><tr><td class='"
819         + (forAttribute ? "leftAttributeTitle" : "leftElementTitle")
820         + "'>");
821         out.print(left);
822         out.println("</td><td class='"
823         + (forAttribute ? "rightAttributeTitle" : "rightElementTitle")
824         + "'>");
825         if (right != null)
826             out.println(right);
827         out.println("</td></tr></table>");
828     }
829 
830     /** Formats and renders into a stream a list of attribute. The format is
831      *  a table, it's coded in HTML.
832      *
833      *  @param dtdAttList the attributes list.
834      *  @param out the stream where to render the attributes.
835      *  @param dtd The DTD in which the attributes list is defined. */
836 
837     private void showAttributesList(
838         DTDAttlist dtdAttList,
839         PrintWriter out,
840         ExtendedDTD dtd) {
841 
842         // Attributes must be written to doc only if they are
843         // defined in the current DTD file.
844 
845         // It is assumed that the attributes are _always_ shown on the same
846         // page (therefore in the same file) as the element they charaterize.
847 
848         //out.println("<a name=\"" + dtd.makeAttListId(dtdAttList) + "\"></a>");
849 
850         StringBuffer buffer = new StringBuffer();
851 
852         buffer.append(dtdAttList.getAttribute(0).getName());
853         for (int i = 1; i < dtdAttList.getAttribute().length; i++) {
854             buffer.append(", ");
855             buffer.append(dtdAttList.getAttribute(i).getName());
856         }
857         String s = buffer.toString();
858 
859         String a = "Attribute";
860         if (dtdAttList.getAttribute().length > 1)
861             a += "s";
862 
863         a += " of " +
864             getInternalHREF( dtd, dtd.getElementByName( dtdAttList.getName()));
865 
866         showAttributeTitle( s, a, out, s);
867     }
868 
869     /** This function parses a set of DTD files.
870      *  @param filePaths A set containing all the paths to the DTD files
871      *         (relative to the context of execution.
872      *  @return A list with all the parsed DTD (ExtendedDTD). */
873 
874     private Set parseDTDFiles(Set filePaths) throws IOException {
875 
876         Set dtds = new HashSet();
877 
878         Iterator it = filePaths.iterator();
879         while (it.hasNext()) {
880             File f = (File) it.next();
881             log.info("Parsing '" + f +"'...");
882             dtds.add( new ExtendedDTD(f, log, getAroundNetBeanComments));
883         }
884 
885         return dtds;
886     }
887 
888     /** Character used to begin a tag. Usually @. */
889     public static final char BEGIN_TAG = '@';
890 
891     /** Tag starting a comment (@comment) */
892     public static final String COMMENT_TAG = BEGIN_TAG + "comment";
893 
894     /** Tag starting an attribute comment (@attr attributeName comment) */
895     public static final String ATTR_COMMENT_TAG = BEGIN_TAG + "attr";
896 
897     /** Tag starting a "fix me" comment (@fixme) */
898     public static final String FIXME_TAG = BEGIN_TAG + "fixme";
899 
900     /** Tag starting an "example" comment (@example) */
901     public static final String EXAMPLE_TAG = BEGIN_TAG + "example";
902 
903     /** Tag starting a "hidden" comment (@hidden) */
904     public static final String HIDDEN_TAG = BEGIN_TAG + "hidden";
905 
906     /** Tag starting a root element defintion (@root) */
907     public static final String ROOT_TAG = BEGIN_TAG + "root";
908 
909     /** Tag starting a DTD title defintion (@title) */
910     public static final String TITLE_TAG = BEGIN_TAG + "title";
911 
912     /** Tag starting a DTD doctype defintion (@doctype) */
913     public static final String DOCTYPE_TAG = BEGIN_TAG + "doctype";
914 
915     /** This function will create a table of content for the parsed DTDs
916      * documentation, and copy necessary images (<code>img</code> sub-directory) and
917      * JavaScript library.
918      * <p>The TOC will be created in a separate HTML file (<code>toc.html</code>) so that it
919      * can be used in a frame-structured page.</p>
920      *
921      * @param docTitle the title to put at the top of the table.
922      * @param dtds the DTD's to make the table of content for. */
923 
924     public void makeTOC(Set dtds, String docTitle) throws IOException {
925 
926         File tocPath = new File(destDir, "toc.html");
927         log.info("Making the table of content in '" + tocPath.getName() + "'...");
928 
929         OutputStreamWriter htmlFile =
930             new OutputStreamWriter(new FileOutputStream(tocPath));
931 
932         PrintWriter out = new PrintWriter(htmlFile);
933 
934         // Make sure we stick to the locale for the date format
935         // SC 28/8/2006 Don't forget to properly escape if the locale and
936         // encoding happen to differ
937 
938         DateFormat dateFormatter =
939             DateFormat.getDateInstance( DateFormat.MEDIUM);
940         String today = Tools.escapeHTMLUnicode( dateFormatter.format(
941             new Date()), false);
942 
943         out.println(
944             "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
945         out.println("<html><head>");
946         out.println("<meta http-equiv='CONTENT-TYPE' content='text/html' />");
947         out.println("<link rel='StyleSheet' href='"+CSS_FILENAME+"' type='text/css' media='screen' />");
948         out.println("<link rel='StyleSheet' href='"+DTREE_CSS_FILENAME+"' type='text/css' media='screen' />");
949         ElementTreeBuilder.generateJavascriptSetup(out);
950         out.println("<title>" + docTitle + ", " + today + "</title>");
951         out.println("</head><body>");
952         out.println("<h1 class='TOCTitle'>" + docTitle + "</h1>");
953         out.println("<h2 class='TOCTitle'>" + today + "</h2>");
954 
955         out.println("<a href='elementsIndex.html' target='detail'>Elements' index</a><hr />");
956 
957         ElementTreeBuilder builder = new ElementTreeBuilder( out, this, dtds, log);
958         builder.generateTree();
959 
960         out.println("</body></html>");
961         out.close();
962 
963         // Copy the images for the tree into the output directory:
964         String[] imageNames = { "empty.gif", "join.gif", "joinbottom.gif",
965                 "line.gif", "minus.gif", "minusbottom.gif", "plus.gif",
966                 "plusbottom.gif" };
967 
968         File imgPath = new File(destDir,"img");
969         imgPath.mkdirs();
970 
971         for (int i=0;i<imageNames.length;i++)
972             Tools.copyFromResource( "/resources/img/"+ imageNames[i],
973                     new File(imgPath,imageNames[i]));
974 
975         Tools.copyFromResource( "/resources/cctree.js",
976                 new File(destDir,"cctree.js"));
977 
978     }
979 
980     /** Character used to mark the beginning and the end of a part of
981      *  code fragment. This one (&#167;) is deprecated because it is not an
982      *  ASCII one (it's ISO-8859-1). Therefore it's not compatible
983      *  between different charsets (only the first 128 are). It was a
984      *  mistake...
985      */
986 
987     public final static char CODE_SEPARATOR_DEPRECATED = '\u00a7';
988 
989     /** Character used to mark the beginning and the end of a part of
990      *  a code fragment. This one is ASCII and is to be preferred.
991      */
992 
993     public final static char CODE_SEPARATOR = '%';
994 
995     /** Tests if a given character is a code fragment separator.
996      *  A deprecation message is displayed if the old code fragment separator
997      *  is used.
998      *
999      * @param c the character to check.
1000      * @return true if the character is a code fragment separator. False else.
1001      */
1002 
1003     private boolean isCodeFragmentSeparator(char c) {
1004 
1005         if (c == CODE_SEPARATOR_DEPRECATED) {
1006             log.warn(
1007                 "Warning ! Usage of '"
1008                     + CODE_SEPARATOR_DEPRECATED
1009                     + "' as the code fragment delimiter is deprecated."
1010                     + " Please use '" + CODE_SEPARATOR + "' instead.");
1011             return true;
1012         } else
1013             return c == CODE_SEPARATOR;
1014     }
1015 
1016     /**
1017      * Tests if the given string contains an preformated block
1018      * (<code>&lt;PRE</code>) beginning at the given index.
1019      *
1020      * @param str the string to check
1021      * @param ndx index to start checking at
1022      * @return true if this is the start of an &lt;PRE&gt;-block. Exactly if
1023      *    <code>str.charAt(ndx)</code> is the '&lt;' of &lt;PRE
1024      *    (case insensitive). False else.
1025      */
1026 
1027     private static boolean isPre(String str, int ndx) {
1028         if ('<' != str.charAt(ndx) || ndx + "<PRE".length() >= str.length()) {
1029             return false;
1030         }
1031         String pre = str.substring(ndx, ndx + "<PRE".length());
1032         return pre.equalsIgnoreCase("<PRE");
1033     }
1034 
1035     /** Tests if the given string contains a preformated block
1036      * (<code>&lt;PRE&gt;</code>) ending at the given index.
1037      *
1038      * @param str the string to check
1039      * @param ndx index to start checking at
1040      * @return true if this is the end of an &lt;PRE&gt;-block. Exactly if
1041      *    <code>str.charAt(ndx)</code> is the '&lt;' of &lt;/PRE
1042      *    (case insensitive). False else.
1043      */
1044 
1045     private static boolean isPreEnd(String str, int ndx) {
1046         int end = ndx + "</PRE>".length();
1047         if ( end <= str.length()) {
1048             String pre = str.substring(ndx, end);
1049             return pre.equalsIgnoreCase("</PRE>");
1050         }
1051         return false;
1052     }
1053 
1054     /** Encodes a given character so that basic entities are used when
1055      *  necessary. For example, "<" will be encoded as "&lt;". No other
1056      *  form of encoding (such as character set encoding) is done here.
1057      *
1058      * @param c character to encode.
1059      * @return An HTML string representing the character.
1060      */
1061 
1062     private static String guardHtmlCharacter(char c) {
1063         if (c == '<')
1064             return "&lt;";
1065         else if (c == '>')
1066             return "&gt;";
1067         else
1068             return String.valueOf(c);
1069     }
1070 
1071     private boolean isCharEscaped( String p, int ndx) {
1072 
1073         return p.charAt( ndx) == '\\' && ndx+1 < p.length()
1074             && isCodeFragmentSeparator(p.charAt(ndx+1));
1075     }
1076 
1077     /** Formats and writes some text in a HTML output stream.
1078      *
1079      *  <p>First the text is divided into paragraph. The separation between two
1080      *  paragraphs is marked by a blank line.
1081      *  Paragraph enclosed between the XML paragraph separator (?) are
1082      *  written "quoted". The other are written as HTML paragraph,
1083      *  surrounded by the usual P tags.</p>
1084      *
1085      *  <p>Usage of a special
1086      *  character allows for easy XML code integration.
1087      *  This will alleviate the work of the documentation author and shield
1088      *  him from portability issues.</p>
1089      *
1090      *  @param p the paragraph
1091      *  @param titleCode HTML code to be put at the beginning of the
1092      *     paragraph. This might be helpful for some presentation
1093      *     issues. This code will be included inside the &lt;p>/&lt;/p>
1094      *     pair.
1095      *  @param out the output stream where to write the paragraph. */
1096 
1097     private void forcePrintParagraph(
1098         String p,
1099         PrintWriter out,
1100         String titleCode) {
1101 
1102         if (!p.startsWith("<p>"))
1103             out.print("<p>");
1104 
1105         if (titleCode != null)
1106             out.print("<span class='inTextTitle'>"+titleCode+"</span> ");
1107 
1108         int ndx = 0;
1109         while (ndx < p.length()) {
1110 
1111             // We split the paragraph on spaces.
1112             // In fact, we skip the space, but we also "summarise" it (a space
1113             // is a sequence/string of one or more space characters):
1114 
1115             // Number of "white spaces" in the space
1116             int spaces = 0;
1117 
1118             // Number of linefeeds in the space.
1119             int linefeeds = 0;
1120 
1121             while (ndx < p.length() && Character.isWhitespace(p.charAt(ndx))) {
1122                 spaces++;
1123                 if (p.charAt(ndx) == 0x000A)
1124                     linefeeds++;
1125                 ndx++;
1126             }
1127 
1128             // Depending on the nature of the splitting spaces, we decide
1129             // to replace them by a shorter, equivalent form.
1130 
1131             if (linefeeds >= 2)
1132                 // Two or more consecutives line feeds count for a paragraph
1133                 // break.
1134                 out.print("</p><p>");
1135             else if (linefeeds == 1)
1136                 // Only one count for nothing. However, we put one linefeed
1137                 // in the HTML to make it more readable.
1138                 out.println();
1139             else if (spaces > 0)
1140                 // Spaces are collapsed into a single space.
1141                 out.print(' ');
1142 
1143             // Now we've skipped a chunk of spaces, we analyse the following
1144             // chunk of text.
1145 
1146             if (ndx < p.length()) {
1147 
1148                 // Here we handle the <pre> tag.
1149 
1150                 if (isPre(p, ndx)) {
1151                     // Preformated HTML-Text we don't change anything - not even
1152                     // whitespace skipping - until we've seen the </PRE>-end.
1153                     out.print("</p><pre");
1154                     ndx += 4;
1155 
1156                     // Content of the <pre>-Section
1157                     while (ndx < p.length()
1158                             && !isPreEnd(p, ndx)) {
1159                         out.print(p.charAt(ndx));
1160                         ndx++;
1161                     }
1162 
1163                     // We make sure that we have actually encountered </pre> to
1164                     // issue the HTML code.
1165 
1166                     if (ndx < p.length()) {
1167                         out.print("</pre><p>");
1168                         ndx += 6;
1169                     }
1170 
1171                 } else if( isCharEscaped( p, ndx)) {
1172                     // So we print the escaped char...
1173                     out.print( p.charAt(ndx+1));
1174                     // and skip the escape char as well as the escaped
1175                     // char
1176                     ndx += 2;
1177 
1178                 } else if (isCodeFragmentSeparator(p.charAt(ndx))) {
1179                     // Skip code fragment beginning separator
1180                     out.print("</p><pre>");
1181                     ndx++;
1182 
1183                     // Content of the code fragment.
1184                     while (ndx < p.length()) {
1185                         if( isCharEscaped(p,ndx)) {
1186                             out.print( p.charAt(ndx+1));
1187                             ndx += 2;
1188                         } else if( !isCodeFragmentSeparator(p.charAt(ndx))) {
1189                             out.print(guardHtmlCharacter(p.charAt(ndx)));
1190                             ndx++;
1191                         } else
1192                             break;
1193                     }
1194 
1195                     // Skip code fragment end separator
1196                     out.print("</pre><p>");
1197                     ndx++;
1198 
1199                 } else if (('<' == p.charAt(ndx)) && ! Tools.startsWithHtmlTag(p.substring(ndx))) {
1200                     out.print("&lt;");
1201                     ndx++;
1202 
1203                 } else {
1204                     // Please note that we don't transform characters like
1205                     // > and < to entities. This is done like that to ensure
1206                     // that the commenter has full freedom for writing HTML
1207                     // stuff.
1208 
1209                     out.print(p.charAt(ndx));
1210                     ndx++;
1211                 }
1212             } // if ndx < p.length()
1213         } // while ndx < p.length()
1214 
1215         if (!p.endsWith("</p>"))
1216             out.print("</p>");
1217     }
1218 
1219 
1220     /** Displays the basic comment as parsed by a CommentParser.
1221      *
1222      *  @param out Where to write the comments.
1223      *  @param cp The comment parser holding the comment to write.
1224      *  @param headComment True if we're writing the topmost comment (that is, on the
1225      *                     documentation sheet).
1226      *  @return true if anything was written out. */
1227 
1228     private boolean printBaseCommentTags( PrintWriter out, CommentParser cp, boolean headComment) {
1229 
1230         if( cp == null)
1231             return false;
1232 
1233         boolean commentShown = false;
1234 
1235         if (headComment) {
1236             String doctype = cp.getUniqueTagValue( DOCTYPE_TAG);
1237             if (doctype != null) {
1238                 out.println("<p><span class='inTextTitle'>Usage</span>: <code>&lt;!DOCTYPE " + doctype + "&gt;</code></p>");
1239                 commentShown = true;
1240             }
1241         }
1242 
1243         String comment = cp.getUniqueTagValue( COMMENT_TAG);
1244         if (comment != null) {
1245             forcePrintParagraph(comment, out, null);
1246             commentShown = true;
1247         }
1248 
1249         String hidden = cp.getUniqueTagValue( HIDDEN_TAG);
1250         if (hidden != null && showHiddenTags) {
1251             forcePrintParagraph(hidden, out, null);
1252             commentShown = true;
1253         }
1254 
1255         String fixme = cp.getUniqueTagValue(FIXME_TAG);
1256         if (fixme != null && showFixmeTags) {
1257             forcePrintParagraph(fixme, out, "To fix:");
1258             commentShown = true;
1259         }
1260 
1261         String example = cp.getUniqueTagValue(EXAMPLE_TAG);
1262         if (example != null) {
1263             forcePrintParagraph(example, out, "Example:");
1264             commentShown = true;
1265         }
1266 
1267         return commentShown;
1268     }
1269 
1270     /** Format and displays a full comment on a HTML stream. The format
1271      *  is defined following the usage of several tags. Some part of the
1272      *  comment may be emphasized, hidden, etc.
1273      *
1274      *  @param pComment the comment
1275      *  @param out the stream to write the formatted comment to.
1276      *  @param headComment this comment is at the header of the DTD documentation */
1277 
1278     private void printComment( DTDComment pComment, PrintWriter out, boolean headComment) {
1279 
1280         if (pComment != null) {
1281             CommentParser cp = new CommentParser( pComment, log,
1282                 getAroundNetBeanComments);
1283 
1284             if( printBaseCommentTags( out, cp, headComment))
1285                 return;
1286         }
1287 
1288         out.print("<p>Sorry, no documentation.</p>");
1289     }
1290 
1291     /**
1292      * Copy style sheets (D-tree and DTDDoc) to destination directory.
1293      * @param styleSheet <code>null</code> to use default DTDDoc style sheet
1294      * @throws FileNotFoundException
1295      * @throws IOException
1296      */
1297     private void copyStyleSheets( File styleSheet) throws FileNotFoundException, IOException {
1298 
1299         Tools.copyFromResource( "/resources/"+DTREE_CSS_FILENAME,
1300                 new File( destDir, DTREE_CSS_FILENAME));
1301 
1302         if( styleSheet == null) {
1303             Tools.copyFromResource( "/resources/"+CSS_FILENAME,
1304                     new File( destDir, CSS_FILENAME));
1305         } else {
1306             Tools.copy( styleSheet, new File( destDir, CSS_FILENAME));
1307         }
1308     }
1309 
1310     private static final String CSS_FILENAME ="DTDDocStyle.css";
1311     private static final String DTREE_CSS_FILENAME ="dtreeStyle.css";
1312 
1313     private String getRelativePathTo( ExtendedDTD fromDtd, String to) throws IOException {
1314         if (fromDtd == null) {
1315             return to;
1316         }
1317 
1318         StringBuffer path = new StringBuffer();
1319         String relPath = getRelativePath( fromDtd.getSystemPath());
1320         for( int i=0; i<relPath.length(); i++)
1321             if( relPath.charAt(i) == File.separatorChar)
1322                 path.append("../");
1323         path.append(to);
1324         return path.toString();
1325     }
1326 
1327     /** Send the html code to link to the stylesheet used in to format the
1328      * documentation.
1329      *
1330      * @param dtd The dtd being documented (we need this info to build the path
1331      *        to the stylesheet (i.e. the number of /.. to use).
1332      * @param out Where to send the HTML colde. */
1333 
1334     private void linkToStyleSheet( ExtendedDTD dtd, PrintWriter out)
1335         throws IOException {
1336 
1337         String cssPath = getRelativePathTo(dtd, CSS_FILENAME);
1338         out.println("<link rel='StyleSheet' href='" + cssPath
1339             + "' type='text/css' media='screen' />");
1340     }
1341 
1342 
1343     /** Builds the HTML code that shows information about a DTD at the top of
1344      * its documentation.
1345      *
1346      * @param dtd The documented DTD.
1347      * @param out Where to write the code. */
1348 
1349     private void showPageStart(
1350         ExtendedDTD dtd,
1351         PrintWriter out, String currentFilename) throws IOException {
1352 
1353         out.print( "<p class='DTDSource'>");
1354 
1355         if (dtd == null) {
1356             out.print("Elements - Entities - Source");
1357         } else {
1358             String filename = dtd.getSystemPath().getName();
1359             out.print("<b><code>" + filename + "</code></b>: ");
1360 
1361             out.print( "<a href='" + filename + ".html'>Elements</a> - ");
1362 
1363             if( dtd.getEntities().size() > 0) {
1364                 out.print( "<a href='" + filename + ".entities.html'>Entities</a>");
1365             } else {
1366                 out.print("Entities");
1367             }
1368 
1369             out.print(" - <a href='" + filename + ".org.html'>Source</a>");
1370         }
1371 
1372         out.print(" | <a href='" + getRelativePathTo(dtd, "intro.html") + "'>Intro</a>");
1373         out.print(" - <a href='" + getRelativePathTo(dtd, "elementsIndex.html") + "'>Index</a>");
1374 
1375         out.print("<br /><a href='" + getRelativePathTo(dtd, "index.html") + "' target='_top'>FRAMES</a>");
1376         out.print("&nbsp;/&nbsp;<a href='" + currentFilename + "' target='_top'>NO FRAMES</a>");
1377 
1378         out.print("</p>");
1379 
1380     }
1381 
1382     /** Formats and displays the introduction text and HTML header to
1383      *  the documentation of a DTD.
1384      *
1385      *  @param dtd The DTD for which the introduction must be created.
1386      *  @param out Where the introduction HTML will be written. */
1387 
1388     private void showIntroduction(
1389         ExtendedDTD dtd,
1390         PrintWriter out) throws IOException {
1391 
1392         out.println(
1393             "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
1394         out.println("<html><head>");
1395 
1396         out.println(
1397             "<meta http-equiv='CONTENT-TYPE' " +
1398             "content='text/html; charset="	+ dtd.getEffectiveEncoding() + "' />");
1399 
1400         linkToStyleSheet( dtd, out);
1401 
1402         if (dtd.getTitle() != null) {
1403             out.print("<title>");
1404             out.print(dtd.getTitle());
1405             out.println("</title>");
1406         }
1407 
1408         out.println("</head><body>");
1409 
1410         showPageStart(dtd, out, dtd.getSystemPath().getName() + ".html");
1411 
1412         if (dtd.getTitle() != null) {
1413             out.print("<h1>");
1414             out.print(dtd.getTitle());
1415             out.println("</h1>");
1416         }
1417 
1418         // Removed, not suitable anymore because we can now guess the root
1419         // FIXME SC: how's that ?
1420 
1421         /* if (dtd.getRootElement() == null) {
1422             out.print("<p><EM>Warning !</EM> ");
1423             out.println("This DTD doesn't have a root tag ! Therefore " +
1424                 "it is used as a repository for tags");
1425             out.println("to be used in other DTDs</p>");
1426         }*/
1427     }
1428 
1429     private void showAttributeUsage( DTDAttribute attr, PrintWriter out) {
1430         String possibleValues = null;
1431         if (getAttributeValues(attr) != null
1432             && !isCDATA(attr))
1433             possibleValues =
1434                 "<span class='inTextTitle'>Possible values</span>: "
1435                     + getAttributeValues(attr);
1436 
1437         String usage = null;
1438 
1439         if (attr.decl == DTDDecl.REQUIRED)
1440             usage = "<span class='inTextTitle'>Required</span>";
1441         else if (attr.decl == DTDDecl.FIXED)
1442             usage = "<span class='inTextTitle'>Fixed value:</span> " + attr.defaultValue;
1443         else if (
1444             attr.defaultValue != null
1445                 && attr.defaultValue.length() > 0)
1446             usage = "<span class='inTextTitle'>Default value</span>: " + attr.defaultValue;
1447 
1448         if (possibleValues != null || usage != null) {
1449             out.print("<p>");
1450             if (possibleValues != null)
1451                 out.print(possibleValues);
1452             if (possibleValues != null && usage != null)
1453                 out.print(" - ");
1454             if (usage != null)
1455                 out.print(usage);
1456             out.print("</p>");
1457         }
1458     }
1459 
1460     private void documentAttributesList( ExtendedDTD dtd, DTDAttlist list, PrintWriter out, DTDComment comment) {
1461 
1462         /* If the attribute list has only one memeber
1463          *    @attr tag is useless, use simple, anonymous comment and example
1464          * If the attribute list has more than one memeber
1465          *    @attr tags are mandatory
1466          *    @example tag must be named like @attr tags.
1467          */
1468 
1469         //log.debug("documentAttributesList-1");
1470 
1471         if( list == null || out == null) {
1472             String err = "documentAttributesList: wrong argument: ";
1473             if( list == null) err += "list ";
1474             if( out == null) err += "out ";
1475             throw new IllegalArgumentException( err);
1476         }
1477 
1478         CommentParser cp = null;
1479         Map attrComments = null;
1480 
1481         if( comment != null) {
1482             cp = new CommentParser( comment, log, getAroundNetBeanComments);
1483 
1484             if( list.getAttribute().length > 1) {
1485                 if( cp.getUniqueTagValue( COMMENT_TAG) != null)
1486                     log.warn("When commenting a list of" +
1487                         " attributes, use @attr instead of a regular comment.");
1488             }
1489 
1490             // if there is only one attribute, one can use @attr
1491             // _or_ a regular comment.
1492             attrComments = cp.getMultipleTagValue( ATTR_COMMENT_TAG);
1493         }
1494 
1495 
1496         for (int i = 0; i < list.getAttribute().length; i++) {
1497             String a = "Attribute of "
1498                 + getInternalHREF( dtd, dtd.getElementByName( list.getName()));
1499 
1500             DTDAttribute attr = list.getAttribute(i);
1501             out.println("<a name='" + ExtendedDTD.getUniqueId( list, attr) + "'></a>");
1502             showAttributeTitle( "@"+attr.getName(), a, out, attr.getName());
1503 
1504             String attrComment = null;
1505             if( (attrComments != null) && ((attrComment = (String) attrComments.get( attr.getName())) != null)) {
1506                 forcePrintParagraph( attrComment, out, null);
1507 
1508             } else if( !printBaseCommentTags( out, cp, false)) {
1509                 // heuristics: id and xmlns are so classical that it is never documented
1510                 // Therefore, we provide some documentation for them.
1511 
1512                 if ( "id".equals(attr.getName())) {
1513                     out.print("<p>Element identifier.</p>");
1514                 } else if ( "xmlns".equals(attr.getName())) {
1515                     out.print("<p>XML namespace of the element.</p>");
1516                 } else {
1517                     out.print("<p>Sorry, no documentation.</p>");
1518                     log.warn( "UNDOCUMENTED attribute '" +
1519                             attr.getName() + "' in element '" + list.getName()
1520                             + "'");
1521                 }
1522             }
1523 
1524             showAttributeUsage( attr, out);
1525         }
1526 
1527         //log.debug("documentAttributesList-2");
1528     }
1529 
1530     void showContainerModel( ExtendedDTD dtd, PrintWriter out, DTDContainer container,
1531             String sep) {
1532 
1533         out.print('(');
1534 
1535         Iterator i = container.getItemsVec().iterator();
1536 
1537         boolean printSep = false;
1538         while( i.hasNext()) {
1539             if( printSep)
1540                 out.print( sep);
1541             else
1542                 printSep = true;
1543 
1544             showElementModel( dtd, out, (DTDItem) i.next());
1545         }
1546         out.print(')');
1547     }
1548 
1549 
1550      void showElementModel( ExtendedDTD dtd, PrintWriter out, DTDItem item) {
1551         if( item instanceof DTDAny )
1552             out.print("ANY");
1553         else if( item instanceof DTDEmpty )
1554             out.print("EMPTY");
1555         else if( item instanceof DTDPCData )
1556             out.print("#PCDATA");
1557         else {
1558             if( item instanceof DTDName) {
1559                 out.print( getInternalHREF( dtd,
1560                         dtd.getElementByName((DTDName) item)));
1561             } else if( item instanceof DTDChoice)
1562                 showContainerModel( dtd, out, (DTDContainer) item, " | ");
1563             else if( item instanceof DTDSequence)
1564                 showContainerModel( dtd, out, (DTDContainer) item, ", ");
1565             else if( item instanceof DTDMixed)
1566                 showContainerModel( dtd, out, (DTDContainer) item, " | ");
1567 
1568             if( item.getCardinal() == DTDCardinal.OPTIONAL)
1569                 out.print('?');
1570             else if( item.getCardinal() == DTDCardinal.ZEROMANY)
1571                 out.print('*');
1572             else if( item.getCardinal() == DTDCardinal.ONEMANY)
1573                 out.print('+');
1574         }
1575     }
1576 
1577     /**
1578      * Creates a PrintWriter to a file in destDir corresponding to the DTD, with a suffix
1579      * added to the file name. If the DTD declares an encoding, it is taken into account,
1580      * else default encoding UTF-8 is used.
1581      * @param dtd the source DTD
1582      * @param suffix the suffix that will be added in the destination file
1583      * @return a PrintWriter with appropriate encoding
1584      * @throws IOException
1585      */
1586     private PrintWriter newPrintWriter( ExtendedDTD dtd, String suffix)
1587         throws IOException {
1588 
1589         File outputName = new File(	destDir,
1590                 getRelativePath(dtd.getSystemPath()) + suffix);
1591 
1592         Tools.makeFileDir( outputName);
1593 
1594         FileOutputStream outFile = new FileOutputStream( outputName);
1595 
1596         Writer out =  new OutputStreamWriter( outFile, dtd.getEffectiveEncoding());
1597 
1598         return new PrintWriter(new BufferedWriter(out));
1599     }
1600 
1601 
1602     /** Main function for building a Extended DTD documentation. Basically,
1603      *  this function will run over all the items of a DTD (in their natural
1604      *  order) and build the related pieces of documentation.
1605      *  @param dtd The DTD to document*/
1606 
1607     private void makeDocumentation(ExtendedDTD dtd) throws IOException {
1608         log.info(
1609                 "Making doc for '" + getRelativePath(dtd.getSystemPath())
1610                     + "' [" + dtd.getEffectiveEncoding() + "]");
1611 
1612         PrintWriter out = newPrintWriter( dtd, ".html");
1613 
1614         showIntroduction(dtd, out);
1615 
1616         DTDComment currentComment = null;
1617         boolean firstCommentImpossible = false;
1618 
1619         for (int i = 0; i < dtd.getItems().size(); i++) {
1620 
1621             Object e = dtd.getItems().get(i);
1622 
1623             if (e instanceof DTDComment) {
1624 
1625                 currentComment = (DTDComment) e;
1626 
1627                 if (i + 1 < dtd.getItems().size()
1628                     && dtd.getItems().get(i + 1) instanceof DTDComment
1629                     && !firstCommentImpossible) {
1630                     //log.debug("Writing first comment !"+currentComment);
1631                     printComment(currentComment, out, true);
1632                     out.println("<br />");
1633                 }
1634 
1635                 firstCommentImpossible = true;
1636 
1637             } else if (e instanceof DTDAttlist) {
1638 
1639                 documentAttributesList( dtd, (DTDAttlist) e, out, currentComment);
1640                 firstCommentImpossible = true;
1641                 currentComment = null;
1642 
1643             } else if (e instanceof DTDElement) {
1644 
1645                 firstCommentImpossible = true;
1646                 DTDElement dtdElement = (DTDElement) e;
1647 
1648                 // add the element to elementsIndex
1649                 Set declaredInDtds = (Set)elementsIndex.get(dtdElement.getName());
1650                 if (declaredInDtds == null) {
1651                     declaredInDtds = new HashSet();
1652                     elementsIndex.put(dtdElement.getName(), declaredInDtds);
1653                 }
1654                 declaredInDtds.add(dtd);
1655 
1656                 if (currentComment == null) {
1657                     log.warn("UNDOCUMENTED element: " + dtdElement.getName());
1658                 }
1659 
1660                 out.println("<a name='" + ExtendedDTD.getUniqueId( dtdElement) + "'></a>");
1661 
1662                 String linkToParent = null;
1663                 if (dtd.getParents(dtdElement.getName()) != null) {
1664                     if (dtdElement == dtd.getRootElement()) {
1665                         linkToParent = "Root element, " +
1666                             "child of " + getHREFToParents(dtd, dtdElement);
1667                     } else {
1668                         linkToParent =
1669                             "Child of " + getHREFToParents(dtd, dtdElement);
1670                     }
1671                 } else
1672                     linkToParent = "Root element";
1673 
1674                 DTDItem item = dtdElement.getContent();
1675 
1676                 String elementPresentation = "&lt;" + dtdElement.getName();
1677 
1678                 if (item instanceof DTDEmpty)
1679                     elementPresentation += '/';
1680 
1681                 elementPresentation += "&gt;";
1682 
1683                 if (linkToParent == null)
1684                     showElementTitle( elementPresentation, null, out,
1685                             dtdElement.getName());
1686                 else
1687                     showElementTitle( elementPresentation, linkToParent,
1688                         out, dtdElement.getName());
1689 
1690                 printComment(currentComment, out, false);
1691 
1692                 if (item instanceof DTDContainer) {
1693 
1694                     boolean attributesToShow =
1695                         dtdElement.attributes != null
1696                             && dtdElement.attributes.size() > 0;
1697 
1698                     boolean childrenToShow =
1699                         !(((DTDContainer) item).getItem(0) instanceof DTDPCData
1700                             && ((DTDContainer) item).getItems().length == 1);
1701 
1702                     if (childrenToShow || attributesToShow) {
1703                         out.println(
1704                             "<blockquote><table summary='element info'><tr>");
1705                     }
1706 
1707                     // If the element is "just" a PCDATA, it is better to not
1708                     // display its children !
1709 
1710                     if (childrenToShow) {
1711                         out.print("<td class='construct'>");
1712                         showChildren(dtdElement, out, dtd);
1713                         out.print("</td>");
1714                     }
1715 
1716                     if (attributesToShow) {
1717                         out.print("<td class='construct'>");
1718                         showAttributes(dtdElement, out, dtd);
1719                         out.print("</td>");
1720                     }
1721 
1722                     if (childrenToShow || attributesToShow) {
1723                         out.println("</tr></table></blockquote>");
1724                     }
1725 
1726                     if (childrenToShow) {
1727                         out.print("<span class='inTextTitle'>Element's model:</span>");
1728                         out.print("<p class='model'>");
1729                         //item.write(out);
1730                         showElementModel( dtd, out, item);
1731                         out.print("</p>");
1732                     }
1733 
1734                 } else if (item instanceof DTDEmpty) {
1735 
1736                     firstCommentImpossible = true;
1737 
1738                     boolean attributesToShow =
1739                         dtdElement.attributes != null
1740                             && dtdElement.attributes.size() > 0;
1741 
1742                     if (attributesToShow) {
1743                         out.println("<blockquote>");
1744                         showAttributes(dtdElement, out, dtd);
1745                         out.println("</blockquote>");
1746                     }
1747 
1748                     out.print("<p class='emptyTagNote'>This element is always empty.</p>");
1749 
1750                 } else if (item instanceof DTDAny) {
1751 
1752                     firstCommentImpossible = true;
1753 
1754                     boolean attributesToShow =
1755                         dtdElement.attributes != null
1756                             && dtdElement.attributes.size() > 0;
1757 
1758                     if (attributesToShow) {
1759                         out.println("<blockquote>");
1760                         showAttributes(dtdElement, out, dtd);
1761                         out.println("</blockquote>");
1762                     }
1763 
1764                     out.print("<p class='emptyTagNote'>This element accepts any declared element as its children.</p>");
1765 
1766                 } else
1767                     out.println(
1768                         "type= "
1769                             + item.getClass().getName()
1770                             + " is unsupported !");
1771 
1772                 currentComment = null;
1773 
1774             } else if (e instanceof DTDNotation) {
1775                 DTDNotation notation = (DTDNotation)e;
1776                 log.warn("NOTATION declaration is still not supported: ignoring <!NOTATION " + notation.getName() + " ...>");
1777 
1778             } else if (
1779                 !(e instanceof DTDEntity)
1780                     && !(e instanceof DTDElement)
1781                     && !(e instanceof DTDProcessingInstruction))
1782                 log.error(
1783                     "makeHTMLDoc(): "
1784                         + e.getClass().getName()
1785                         + " not supported");
1786         }
1787 
1788         out.println("</body></html>");
1789         out.close();
1790 
1791         if( dtd.getEntities().size() > 0)
1792             makeEntitiesPage( dtd);
1793         makeDTDtoHtml(dtd);
1794     }
1795 
1796     /** Build the complete HTML documentation for a set of DTDs.
1797      *  @param dtds a set containing all the (Extended) DTD's to parse. */
1798 
1799     private void makeDocumentation(Set dtds) throws IOException {
1800 
1801         if( dtds == null) {
1802             log.warn("No DTD to make documentation for");
1803             return;
1804         }
1805 
1806         for (Iterator iter = dtds.iterator(); iter.hasNext(); ) {
1807             ExtendedDTD dtd = (ExtendedDTD) iter.next();
1808             mkdestdirsRelativePath( dtd.getSystemPath());
1809             makeDocumentation(dtd);
1810         }
1811     }
1812 
1813     /** Transforms a DTD file into an HTML file. There's no syntax highlighting
1814      * nor any eye-candy.
1815      *
1816      * @param dtd The extended DTD that represents the DTD file (in particular,
1817      * its "getSystemPath()" property. */
1818 
1819     private void makeDTDtoHtml( ExtendedDTD dtd)
1820         throws IOException {
1821 
1822         PrintWriter pw = newPrintWriter( dtd, ".org.html");
1823 
1824         pw.println(
1825             "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
1826         pw.println("<html> <head>");
1827         pw.println(
1828             "<meta http-equiv='CONTENT-TYPE' content='text/html; charset="
1829                 + dtd.getEffectiveEncoding() + "' />");
1830 
1831         linkToStyleSheet( dtd, pw);
1832 
1833         pw.println("<title>" + dtd.getTitle() + "</title>");
1834         pw.println("</head><body>");
1835 
1836         showPageStart(dtd, pw, dtd.getSystemPath().getName() + ".org.html");
1837 
1838         DtdXhtmlRenderer renderer = new DtdXhtmlRenderer();
1839         pw.print("<pre id='dtd_source'>");
1840         renderer.highlight(new InputStreamReader(new FileInputStream(dtd.getSystemPath()), dtd.getEffectiveEncoding()), pw);
1841         pw.println("</pre>");
1842 
1843         pw.println("</body></html>");
1844 
1845         pw.close();
1846     }
1847 
1848     private void showIntro( Set dtds, String docTitle, PrintWriter out) throws IOException {
1849         showPageStart(null, out, "intro.html");
1850         out.println("<h1>" + docTitle + "</h1>");
1851 
1852         SortedMap sortedDtds = new TreeMap(Collator.getInstance());
1853         for (Iterator iter = dtds.iterator(); iter.hasNext(); ) {
1854             ExtendedDTD dtd = (ExtendedDTD) iter.next();
1855             sortedDtds.put(dtd.getSystemPath().getName(), dtd);
1856         }
1857         Set keys = sortedDtds.keySet();
1858 
1859         out.println("<table border='1' cellspacing='0'>");
1860         Iterator iter = keys.iterator();
1861         while (iter.hasNext()) {
1862             String filename = (String)iter.next();
1863             ExtendedDTD dtd = (ExtendedDTD)sortedDtds.get(filename);
1864             String title = dtd.getTitle();
1865             out.print("<tr><td><code><a href='" + getRelativePath(dtd.getSystemPath())+ ".html'>" + filename + "</a></code></td>");
1866             out.print("<td>");
1867             boolean empty = true;
1868             if (! filename.equals(title)) {
1869                 out.print(Tools.escapeHTMLUnicode(title, false));
1870                 empty = false;
1871             }
1872             if (dtd.getDoctype() != null) {
1873                 if (! empty) {
1874                     out.print("<br />");
1875                 }
1876                 out.print("<code>&lt;!DOCTYPE " + dtd.getDoctype() + "&gt;</code>");
1877                 empty = false;
1878             }
1879             if (empty) {
1880                 out.print("&nbsp;");
1881             }
1882             out.print("</td>");
1883             out.println("</tr>");
1884         }
1885         out.println("</table>");
1886 
1887         out.println(
1888             "<p>This documentation was generated by <a href='http://dtddoc.sourceforge.net'>DTDDoc</a> " +
1889             VERSION + " !</p>");
1890     }
1891 
1892     /** Builds the introduction page to the documentation.
1893      *  A small advertisement message for DTDDoc is
1894      *  displayed as well ("? la guerre comme ? la guerre" :)). */
1895 
1896     private void makeIntroPageHtml( Set dtds, String docTitle) throws IOException {
1897 
1898         File introPath = new File(destDir, "intro.html");
1899         log.info("Making the introduction page in '"+introPath.getName()+"'...");
1900         Writer htmlFile = new OutputStreamWriter( new FileOutputStream( introPath), "US-ASCII");
1901         PrintWriter out = new PrintWriter( new BufferedWriter( htmlFile));
1902 
1903         out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
1904         out.println("<html><head><title>DTDDoc for " + docTitle + "</title>");
1905         out.println("<link rel='StyleSheet' href='"+CSS_FILENAME+"' type='text/css' media='screen' />");
1906         out.println("</head><body>");
1907 
1908         showIntro(dtds, docTitle, out);
1909 
1910         out.println("<p>Use the left menu to navigate through the DTDs !</p>");
1911         out.println("</body></html>");
1912 
1913         out.close();
1914     }
1915 
1916     /** Make the index.jhtml, that is the main entry point into the
1917      *  documentation. Basically it's some frames. */
1918 
1919     private void makeIndexHtml( Set dtds, String docTitle) throws IOException {
1920 
1921         File indexPath = new File(destDir, "index.html");
1922         log.info("Making '"+indexPath.getName()+"'...");
1923         Writer htmlFile = new OutputStreamWriter( new FileOutputStream( indexPath), "US-ASCII");
1924         PrintWriter out = new PrintWriter( new BufferedWriter( htmlFile));
1925 
1926         out.println(
1927             "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" " +
1928             "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">\n" +
1929             "<html><head>\n" +
1930             "<title>DTDDoc for " + docTitle + "</title>\n"+
1931             "<link rel='StyleSheet' href='"+CSS_FILENAME+"' type='text/css' media='screen' />" +
1932             "</head>\n" +
1933             "<frameset cols='20%, 80%'>\n" +
1934             "   <frame src='toc.html' />\n" +
1935             "   <frame src='intro.html' name='detail' />\n" +
1936             "   <noframes><body>");
1937 
1938         showIntro(dtds, docTitle, out);
1939 
1940         out.println("</body></noframes>\n" +
1941             "</frameset>" +
1942             "</html>");
1943 
1944         out.close();
1945     }
1946 
1947     /**
1948      * A map of every element name, with declaring DTDs.
1949      * <ul>
1950      * <li>key = String: element name</li>
1951      * <li>value = Set&lt;ExtendedDTDT&gt;: the DTDs containing an element with this name</li>
1952      * </ul>
1953      */
1954     private SortedMap elementsIndex = new TreeMap(Collator.getInstance());
1955 
1956     /** Make an HTML file containing an index to all the elements
1957       * 	that are in the parsed DTDs.
1958       *
1959      *  Please note that it is a little more difficult to make
1960      *  an index for attributes. That is, a single attribute is often
1961      *  used in several elements of a DTD. For the index to be helpful,
1962      *  we have to make a direct link to each instances of that
1963      *  attribute. This may clutter the index. Therefore, I
1964      *  decide to not put them right now.
1965      *
1966        * @throws IOException As usual.
1967        */
1968 
1969     private void makeElementsIndex() throws IOException {
1970 
1971         File elIndex = new File(destDir, "elementsIndex.html");
1972         log.info("Making the elements' index '" + elIndex.getName() + "'...");
1973 
1974         Writer htmlFile = new OutputStreamWriter( new FileOutputStream(elIndex), "US-ASCII");
1975         PrintWriter out = new PrintWriter( new BufferedWriter( htmlFile));
1976 
1977         out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
1978         out.println("<html><head><title>elements' index</title>");
1979         out.println("<meta http-equiv='CONTENT-TYPE' content='text/html' />");
1980         out.println("<link rel='StyleSheet' href='"+CSS_FILENAME+"' type='text/css' media='screen' />");
1981         out.println("</head><body>");
1982 
1983         showPageStart(null, out, elIndex.getName());
1984 
1985         // generate the index of the first letter used
1986         char lastLetter = ' ';
1987         for (Iterator iter = elementsIndex.keySet().iterator(); iter.hasNext(); ) {
1988             String name = (String)iter.next();
1989             char newLetter = Character.toUpperCase( name.charAt(0));
1990             if( newLetter != lastLetter) {
1991                 lastLetter = newLetter;
1992                 out.print("<a href='#"+lastLetter+"'>" +lastLetter+ "</a> ");
1993             }
1994         }
1995 
1996         out.println( "<br />");
1997 
1998         lastLetter = ' ';
1999 
2000         for (Iterator iter = elementsIndex.entrySet().iterator(); iter.hasNext(); ) {
2001             Map.Entry entry = (Map.Entry)iter.next();
2002             String name = (String) entry.getKey();
2003             Set dtds = (Set) entry.getValue();
2004 
2005             char newLetter = Character.toUpperCase( name.charAt(0));
2006             if( newLetter != lastLetter) {
2007                 lastLetter = newLetter;
2008                 out.println("<a name='"+lastLetter+"'></a>");
2009                 out.println("<h2>"+lastLetter+"</h2>");
2010             }
2011 
2012             out.print( name + " - ");
2013 
2014             // print DTDs list for this element
2015             SortedMap sortedDtds = new TreeMap(Collator.getInstance());
2016             for (Iterator dtdIter = dtds.iterator(); dtdIter.hasNext(); ) {
2017                 ExtendedDTD dtd = (ExtendedDTD)dtdIter.next();
2018                 sortedDtds.put(dtd.getTitle(), dtd);
2019             }
2020             for (Iterator sortedDtdIter = sortedDtds.entrySet().iterator(); sortedDtdIter.hasNext(); ) {
2021                 Map.Entry dtdEntry = (Map.Entry)sortedDtdIter.next();
2022                 ExtendedDTD dtd = (ExtendedDTD)dtdEntry.getValue();
2023 
2024                 String url = getExternalHREF( dtd, name,
2025                         Tools.escapeHTMLUnicode( dtd.getTitle(), false));
2026                 out.print(url);
2027                 if (sortedDtdIter.hasNext()) {
2028                     out.print(", ");
2029                 }
2030             }
2031 
2032             if (iter.hasNext()) {
2033                 out.println("<br />");
2034             }
2035         }
2036 
2037         out.println("</body></html>");
2038         out.close();
2039     }
2040 
2041     private void makeEntitiesPage( ExtendedDTD dtd) throws IOException {
2042 
2043         log.info("Making the entities page for " + dtd.getSystemPath().getName() + "...");
2044 
2045         PrintWriter out = newPrintWriter( dtd, ".entities.html");
2046 
2047         out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
2048         out.println("<html><head><title>" + dtd.getSystemPath().getName() + "'s entities</title>");
2049         out.println("<meta http-equiv='CONTENT-TYPE' content='text/html; charset="
2050                     + dtd.getEffectiveEncoding() + "' />");
2051         linkToStyleSheet( dtd, out);
2052         out.println("</head><body>");
2053 
2054         showPageStart(dtd, out, dtd.getSystemPath().getName() + ".entities.html");
2055 
2056         out.println("<h1>Entities for "+dtd.getTitle()+"</h1>");
2057         out.println("<table summary='Entities'>");
2058         out.println("<thead><tr><th>Name</th><th>Value</th></tr></thead>");
2059 
2060         out.println("<tbody>");
2061         out.println("<tr><th colspan='2' height='1' class='ruler'></th></tr>");
2062 
2063         Iterator i = dtd.getEntities().values().iterator();
2064         while( i.hasNext()) {
2065 
2066             out.println("<tr>");
2067 
2068             DTDEntity entity = (DTDEntity) i.next();
2069 
2070             out.println("<td>"+entity.name+"</td>");
2071 
2072             out.println("<td>");
2073 
2074             if( entity.value != null)
2075                 out.println( entity.value);
2076 
2077             if( entity.ndata != null)
2078                 out.println( entity.ndata);
2079 
2080             if( entity.externalID != null)
2081                 out.println( entity.externalID.system+" <i>(system)</i>" );
2082 
2083             out.println("</td>");
2084 
2085             out.println("</tr>");
2086         }
2087 
2088         out.println("</tbody>");
2089         out.println("</table>");
2090         out.println("</body></html>");
2091         out.close();
2092     }
2093 
2094     /**
2095      * @deprecated signature maintained for binary backward compatibility with versions <= 0.0.11, but
2096      * equivalent to the other more generic commentDTDs() method
2097      */
2098     public void commentDTDs(
2099             HashSet scan,
2100             File sourceDir,
2101             File destDir,
2102             boolean showHiddenTags,
2103             boolean showFixmeTags,
2104             String docTitle,
2105             File styleSheet)
2106             throws IOException, Exception {
2107         commentDTDs((Set)scan, sourceDir, destDir, showHiddenTags, showFixmeTags,
2108             true, docTitle, styleSheet);
2109     }
2110 
2111     /**
2112      * This is the main method. It chains the following tasks: <ol>
2113      * <li>parse the DTDs</li>
2114      * <li>make the documentation for each one</li>
2115      * <li>make the index page (the main frameset, or main page if frames are not supported)</li>
2116      * <li>make the introduction page (in the frame on the right)</li>
2117      * <li>make the TOC (frame on the left)</li>
2118      * <li>copy the stylesheet and other resources (images and JavaScript)</li>
2119      * <li>make the element index</li>
2120      * </ol>
2121      * @param scan the DTD files to analyze
2122      * @param sourceDir the base directory for relative path computations
2123      * @param destDir the destination directory to store the generated documentation
2124      * @param showHiddenTags tells if the content of the <code>@hidden</code> tags must be part of the documentation
2125      * @param showFixmeTags tells if the content of the <code>@fixme</code> tags must be part of the documentation
2126      * @param getAroundNetBeanComments in case you have comments that start with three hyphens, this option will
2127      *  tell DTDDoc to interpret them as regular double hyphens. This was introduced to support NetBeans comments.
2128      * @param docTitle name of the documentation: it will appear at the top of the index.
2129      * @param styleSheet the stylesheet to use to render the documentation: it will be copied into the documentation.
2130      *  If <code>null</code>, a default stylesheet is used.
2131      * @throws IOException in case of an error while reading the DTDs or writing the documentation
2132      */
2133     public void commentDTDs(
2134         Set scan,
2135         File sourceDir,
2136         File destDir,
2137         boolean showHiddenTags,
2138         boolean showFixmeTags,
2139         boolean getAroundNetBeanComments,
2140         String docTitle,
2141         File styleSheet)
2142         throws IOException {
2143 
2144         this.sourceDir = sourceDir;
2145         this.destDir = destDir;
2146         this.showHiddenTags = showHiddenTags;
2147         this.showFixmeTags = showFixmeTags;
2148         this.getAroundNetBeanComments = getAroundNetBeanComments;
2149 
2150         if (scan == null || scan.isEmpty()) {
2151             log.error( "No files to process ! Please note that you" +
2152                 " have to specify which files to include with the <include>" +
2153                 " tags. There's no" +
2154                 " automatic selection of all DTD's anymore (as of 0.0.7).");
2155         } else {
2156             destDir.mkdir();
2157 
2158             // FIXME: architecturally, this realllllly sux. Load all the
2159             // DTD's at once in memory... pfff... a shame :)
2160 
2161             Set parsedDTDs = parseDTDFiles( scan);
2162 
2163             makeDocumentation( parsedDTDs);
2164             makeIndexHtml( parsedDTDs, docTitle);
2165             makeIntroPageHtml( parsedDTDs, docTitle);
2166             makeTOC( parsedDTDs, docTitle);
2167             copyStyleSheets(styleSheet);
2168             makeElementsIndex();
2169         }
2170     }
2171 }