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
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
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
107
108
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><a href='#element_id'>element name</a> */
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
178
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><a href='<i>dtd_reference</i>#element_id'>text</a> */
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, '/');
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
348
349
350
351
352
353
354
355
356
357
358
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
379
380
381 int i = context.length() - 1;
382 int currentCardinal = indexBase.indexOf( context.charAt( i));
383
384 while(i > 0) {
385
386
387
388 i = i - 1;
389 int previous = indexBase.indexOf( context.charAt( i));
390
391
392
393 currentCardinal = indexBase.indexOf( xform[previous].charAt( currentCardinal));
394 }
395
396 if( overallCardinal == -1)
397 overallCardinal = currentCardinal;
398 else if( overallCardinal != currentCardinal)
399
400
401
402
403 return "See model";
404 }
405
406
407
408
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
448
449 private SortedSet collectChildrenNames(DTDItem child, String context, ExtendedDTD dtd)
450 throws IOException {
451
452 if (child instanceof DTDContainer) {
453
454
455
456
457
458
459
460
461
462
463
464
465
466
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
500 SortedSet singleName = new TreeSet();
501
502 singleName.add( ni);
503
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
526
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
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
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 = "<" + element.getName() + ">'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
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());
700
701
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 = "<" + element.getName() + ">'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
843
844
845
846
847
848
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
935
936
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
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 (§) 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><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 <PRE>-block. Exactly if
1023 * <code>str.charAt(ndx)</code> is the '<' of <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><PRE></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 <PRE>-block. Exactly if
1041 * <code>str.charAt(ndx)</code> is the '<' of </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 "<". 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 "<";
1065 else if (c == '>')
1066 return ">";
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 <p>/</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
1112
1113
1114
1115
1116 int spaces = 0;
1117
1118
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
1129
1130
1131 if (linefeeds >= 2)
1132
1133
1134 out.print("</p><p>");
1135 else if (linefeeds == 1)
1136
1137
1138 out.println();
1139 else if (spaces > 0)
1140
1141 out.print(' ');
1142
1143
1144
1145
1146 if (ndx < p.length()) {
1147
1148
1149
1150 if (isPre(p, ndx)) {
1151
1152
1153 out.print("</p><pre");
1154 ndx += 4;
1155
1156
1157 while (ndx < p.length()
1158 && !isPreEnd(p, ndx)) {
1159 out.print(p.charAt(ndx));
1160 ndx++;
1161 }
1162
1163
1164
1165
1166 if (ndx < p.length()) {
1167 out.print("</pre><p>");
1168 ndx += 6;
1169 }
1170
1171 } else if( isCharEscaped( p, ndx)) {
1172
1173 out.print( p.charAt(ndx+1));
1174
1175
1176 ndx += 2;
1177
1178 } else if (isCodeFragmentSeparator(p.charAt(ndx))) {
1179
1180 out.print("</p><pre>");
1181 ndx++;
1182
1183
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
1196 out.print("</pre><p>");
1197 ndx++;
1198
1199 } else if (('<' == p.charAt(ndx)) && ! Tools.startsWithHtmlTag(p.substring(ndx))) {
1200 out.print("<");
1201 ndx++;
1202
1203 } else {
1204
1205
1206
1207
1208
1209 out.print(p.charAt(ndx));
1210 ndx++;
1211 }
1212 }
1213 }
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><!DOCTYPE " + doctype + "></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(" / <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
1419
1420
1421
1422
1423
1424
1425
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
1463
1464
1465
1466
1467
1468
1469
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
1491
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
1510
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
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
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
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 = "<" + dtdElement.getName();
1677
1678 if (item instanceof DTDEmpty)
1679 elementPresentation += '/';
1680
1681 elementPresentation += ">";
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
1708
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
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><!DOCTYPE " + dtd.getDoctype() + "></code>");
1877 empty = false;
1878 }
1879 if (empty) {
1880 out.print(" ");
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<ExtendedDTDT>: 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
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
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
2159
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 }