added Info.plist
[TestXSLT.git] / libsablot / utils / apidoc / parse_apidoc.pl
1 #!/usr/bin/perl
2 #
3 # The contents of this file are subject to the Netscape Public
4 # License Version 1.1 (the "License"); you may not use this file
5 # except in compliance with the License. You may obtain a copy of
6 # the License at http://www.mozilla.org/NPL/
7 #
8 # Software distributed under the License is distributed on an "AS
9 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10 # implied. See the License for the specific language governing
11 # rights and limitations under the License.
12 #
13 # The Original Code is Mozilla WebTools.
14 #
15 # The Initial Developer of the Original Code is Netscape
16 # Communications Corporation.  Portions created by Netscape are
17 # Copyright (C) 1997-1999 Netscape Communications Corporation. All
18 # Rights Reserved.
19 #
20 # Alternatively, the contents of this file may be used under the
21 # terms of the GNU Public License (the "GPL"), in which case the
22 # provisions of the GPL are applicable instead of those above.
23 # If you wish to allow use of your version of this file only
24 # under the terms of the GPL and not to allow others to use your
25 # version of this file under the NPL, indicate your decision by
26 # deleting the provisions above and replace them with the notice
27 # and other provisions required by the GPL.  If you do not delete
28 # the provisions above, a recipient may use your version of this
29 # file under either the NPL or the GPL.
30 #
31 # Contributor(s):
32 #  Robert Ginda <rginda@netscape.com>, Initial development.
33 #  Pavel Hlavnicka <pavel@gingerall.cz>, seperate tocs, param linking.
34 #  Petr Cimprich <petr@gingerall.cz>, nested frameset fix, encoded URLs.
35 #  Petr Cimprich <petr@gingerall.cz>, Mozilla sidebar generated from TOC
36 #
37
38 use strict;
39 use XML::Parser;
40
41 my $file = shift;
42 my $outdir = shift || "apidocs";
43 my $c;
44 my $tagname;
45 my $pending_param;
46 my $pending_text;
47 my %pending_attrs;
48 my %groups;
49 my %externals;
50 my %toc_externals;
51 my $user_foot;
52 my $user_head;
53 my @tag_stack;
54 my @text_stack;
55 my @attr_stack;
56 my $inited = 0;
57 my %entries;
58 my $apiid;
59
60 my $API         = 0;
61 my $ENTRY       = 1;
62 my $TYPE        = 2;
63 my $SUMMARY     = 3;
64 my $SYNTAX      = 4;
65 my $PARAM       = 5;
66 my $RETVAL      = 6;
67 my $DESCRIPTION = 7;
68 my $EXAMPLE     = 8;
69 my $NOTE        = 9;
70 my $SEE_ALSO    = 10;
71 my $DEPRECATED  = 11;
72 my $EXTERNALREF = 12;
73 my $GROUP       = 13;
74 my $C           = 14;
75 my $P           = 15;
76 my $BR          = 16;
77 my $B           = 17;
78 my $I           = 18;
79 my $S           = 19;
80 my $FOOT        = 20;
81 my $HEAD        = 21;
82 my $COMPLETED   = 22;
83 my @TAGS = ("API", "ENTRY", "TYPE", "SUMMARY", "SYNTAX", "PARAM", "RETVAL",
84             "DESCRIPTION", "EXAMPLE", "NOTE", "SEEALSO", "DEPRECATED", 
85             "EXTERNALREF", "GROUP", "C", "P", "BR", "B", "I", "S", "FOOT",
86             "HEAD");
87 my @CDATA_TAGS = ($TAGS[$SUMMARY], $TAGS[$SYNTAX], $TAGS[$DESCRIPTION], 
88                   $TAGS[$EXAMPLE], $TAGS[$NOTE], $TAGS[$PARAM], $TAGS[$C],
89                   $TAGS[$B], $TAGS[$I], $TAGS[$S], $TAGS[$FOOT], $TAGS[$HEAD]);
90 my @FORMATTING_TAGS = ($TAGS[$P], $TAGS[$C], $TAGS[$B], $TAGS[$I], $TAGS[$BR],
91                        $TAGS[$BR], $TAGS[$S]);
92 my @FORMAT_CONTAINERS = ($TAGS[$SUMMARY], $TAGS[$PARAM], $TAGS[$RETVAL],
93                          $TAGS[$DESCRIPTION], $TAGS[$NOTE], $TAGS[$C],
94                          $TAGS[$B], $TAGS[$I], $TAGS[$FOOT], $TAGS[$HEAD]);
95 my @CODE_TAGS = ($TAGS[$SYNTAX], $TAGS[$EXAMPLE]);
96
97 my $URLVAR_ENTRY = "{e}";
98
99 my $footstr = "<center>This page was generated by " .
100   "<a href='http://www.mozilla.org/projects/apidoc' target='other_window'>" .
101   "<b>APIDOC</b></a>" .
102   "</center>\n</body></html>";
103
104 my $WARNING = "<!--\n" .
105   "  -- HEADS UP!  This page was *GENERATED* by APIDOC,\n".
106   "  -- DO NOT EDIT THIS FILE BY HAND!\n" .
107   "  -- See http://www.mozilla.org/projects/apidoc for information on APIDOC\n" .
108   "  -- The original source file was " . $file . "\n" .
109   "  -->\n";
110
111 my $JS_COMPLETE = ("\n<script>\n" .
112                    "function navToEntry(entry) {\n" .
113                    "  window.location.hash=entry;\n" .
114                    "}\n" .
115                    "function navToGroup(group) {\n" .
116                    "  var f = parent.frames['toc-container'];\n" .
117                    "  if (!f)\n".
118                    "    window.open ('complete-toc.html#' + group, " .
119                    "'toc_container');\n" .
120                    "  else {\n" .
121                    "    if (f.location.href.search('abc') != -1)\n" .
122                    "        f.location.href = 'complete-toc.html#' + group;\n" .
123                    "    else\n" .
124                    "        f.location.hash = group;\n" .
125                    "  }\n" .
126                    "}\n" .
127                    "</script>\n");
128
129 my $JS_SPARSE = ("\n<script>\n" .
130                  "function navToEntry(entry) {\n" .
131                  "  window.location.href='api-' + entry + '.html';\n" .
132                  "}\n" .
133                  "function navToGroup(group) {\n" .
134                  "  var f = parent.frames['toc-container'];\n" .
135                  "  if (!f)\n".
136                  "    window.open ('sparse-toc.html#' + group, " .
137                  "'toc_container');\n" .
138                  "  else {\n" .
139                  "    if (f.location.href.search('abc') != -1)\n" .
140                  "        f.location.href = 'sparse-toc.html#' + group;\n" .
141                  "    else\n" .
142                  "        f.location.hash = group;\n" .
143                  "  }\n" .
144                  "}\n" .
145                  "</script>\n");
146
147 open (COMPLETE, ">" . $outdir . "/complete.html") ||
148   die ("Couldn't open $outdir/complete.html.\n");
149 open (COMPLETE_TOC, ">" . $outdir . "/complete-toc.html") ||
150   die ("Couldn't open $outdir/complete-toc.html.\n");
151
152 open (COMPLETE_TOC_ABC, ">" . $outdir . "/complete-toc-abc.html") ||
153   die ("Couldn't open $outdir/complete-toc-abc.html.\n");
154 open (COMPLETE_TOC_GRP, ">" . $outdir . "/complete-toc-grp.html") ||
155   die ("Couldn't open $outdir/complete-toc-grp.html.\n");
156
157 open (SPARSE_TOC, ">" . $outdir . "/sparse-toc.html") ||
158   die ("Couldn't open $outdir/sparse-toc.html.\n");
159
160 open (SPARSE_TOC_ABC, ">" . $outdir . "/sparse-toc-abc.html") ||
161   die ("Couldn't open $outdir/sparse-toc-abc.html.\n");
162 open (SPARSE_TOC_GRP, ">" . $outdir . "/sparse-toc-grp.html") ||
163   die ("Couldn't open $outdir/sparse-toc-grp.html.\n");
164
165 open (SIDEBAR_TOC, ">" . $outdir . "/sidebar-toc.html") ||
166   die ("Couldn't open $outdir/sidebar-toc.html.\n");
167
168
169 &main();
170
171 sub main {
172     my $parser = new XML::Parser(ErrorContext => 2);
173
174     # pass 1, scan all <ENTRY> tags.
175     $parser->setHandlers(Start => \&p1_Start, End => \&p1_End);
176     $parser->parsefile($file);
177
178     # sanity check the tag stack from p1
179     if ($#tag_stack != -1) {
180         die ("OOPS: p1 left the tag stack in a bad state.\n");
181     }
182
183     # pass 2, populate the $entries hash.
184     $parser = new XML::Parser(Style => "Stream", ErrorContext => 2);
185     $parser->parsefile ($file);
186
187     # finally, write it all out.
188     &init_files();
189
190     my $k;
191     my $html;
192
193     for $k (sort (keys(%entries))) {
194         $c = $entries{$k};
195         $html = &get_entry_html();
196         &add_entry_complete($html);
197         &add_entry_sparse($html);
198         &add_toc_complete(*COMPLETE_TOC);
199         &add_toc_complete(*COMPLETE_TOC_ABC);
200         &add_toc_sparse(*SPARSE_TOC);
201         &add_toc_sparse(*SPARSE_TOC_ABC);
202         #&debug_write_entry();
203     }
204
205     &end_abc(*SPARSE_TOC);
206     &end_abc(*SPARSE_TOC_ABC);
207     &end_abc(*COMPLETE_TOC);
208     &end_abc(*COMPLETE_TOC_ABC);
209
210     &write_toc_groups();
211
212     &close_files();
213
214 }
215
216 sub p1_Start {
217     # pass 1, find all <ENTRY/>, <EXTERNALREF/>, and <GROUP/> tags
218     # (as well as groups implied by <TYPE/> and <DEPRECATED/> tags), so we
219     # can do things like auto link <C/> tags that refer to entrys,
220     # and validate <S/> tags in pass 2.
221     my $expat = shift;
222     my $lasttagname = $tagname;
223     my $n;
224     my %pending_attrs;
225
226     push (@tag_stack, $lasttagname);
227     $tagname = shift;
228
229     while ($n = shift) {
230         $pending_attrs{$n} = shift;
231     }
232
233     my $value = $pending_attrs{"value"};
234     my $id = $pending_attrs{"id"};
235
236     if ($tagname eq $TAGS[$ENTRY]) {
237         if ($id) {
238             $c = $entries{$id} = {$TAGS[$ENTRY] => $id};
239         } else {
240             &croak_attr ($expat, $tagname, "id");
241         }
242     } elsif ($tagname eq $TAGS[$TYPE]) {
243         if (!$value) {
244             &croak_attr ($expat, $tagname, "value");
245         }
246         $c->{$tagname} = $value;
247         push (@{$groups{$value}}, $c->{$TAGS[$ENTRY]});
248         push (@{$c->{$TAGS[$GROUP]}}, $value);
249     } elsif ($tagname eq $TAGS[$DEPRECATED]) {
250         $c->{$tagname} = 1;
251         push (@{$groups{"Deprecated"}}, $c->{$TAGS[$ENTRY]});
252         push (@{$c->{$TAGS[$GROUP]}}, "Deprecated");
253     } elsif ($tagname eq $TAGS[$GROUP]) {
254         if (($lasttagname ne $TAGS[$API]) && 
255             ($lasttagname ne $TAGS[$ENTRY])) {
256             $expat->xpcroak ("Tag $tagname can only be contained by ".
257                              "an '" . $TAGS[$API] . "' or '" .
258                              $TAGS[$ENTRY] . "' tag");
259         }
260         my $name = $pending_attrs{"name"};
261         if (!$name) {
262             &croak_attr ($expat, $tagname, "name");
263         }
264         if ($lasttagname ne $TAGS[$ENTRY]) {
265             if (!$value) {
266                 &croak_attr ($expat, $tagname, "value");
267             }
268         } else {
269             $value = $c->{$TAGS[$ENTRY]};
270         }
271         if (!grep (/^$value$/, @{$groups{$name}})) {
272             # if it isn't already there, add it
273             push (@{$groups{$name}}, $value);
274             push (@{$entries{$value}->{$TAGS[$GROUP]}}, $name);
275         }
276     } elsif ($tagname eq $TAGS[$EXTERNALREF]) {
277         my $name = $pending_attrs{"name"};
278         my $value = $pending_attrs{"value"};
279         if (!$name) {
280             &croak_attr ($expat, $tagname, "name");
281         }
282         if (!$value) {
283             &croak_attr ($expat, $tagname, "value");
284         }
285         if ($lasttagname eq $TAGS[$API]) {
286             # if the externalref is a child of the API tag
287             if ($value =~ /$URLVAR_ENTRY/) {
288                 # and it has a placeholder for the entry id,
289                 # then attach it to every entry
290                 $externals{$name} = $value;
291             } else {
292                 # otherwise, just put it in the toc.
293                 $toc_externals{$name} = $value;
294             }
295         } elsif ($lasttagname eq $TAGS[$ENTRY]) {
296             # if the externalref is a child of the ENTRY tag
297             # only attach it to this entry
298             $c->{$TAGS[$EXTERNALREF]}{$name} = $value;
299         } else {
300             $expat->xpcroak ("Tag $tagname can only be contained by ".
301                              "an '" . $TAGS[$API] . "' or '" .
302                              $TAGS[$ENTRY] . "' tag");
303         }
304     }
305 }
306
307 sub p1_End {
308     $tagname = pop (@tag_stack);
309 }
310
311 sub StartTag {
312     # phase 2 open tag handler
313     my ($expat) = @_;
314     $_ =~ /<([^\s>]*)/;
315     my $lasttagname = $tagname;
316     $tagname = $1;
317
318     push (@tag_stack, $lasttagname);
319     push (@text_stack, $pending_text);
320     my $s = $#attr_stack + 1;
321     $attr_stack[$s]{"foo"} = "bar";
322     for (keys (%pending_attrs)) {
323         $attr_stack[$s]{$_} = $pending_attrs{$_};
324     }
325
326     $pending_text = "";
327     %pending_attrs = %_;
328
329     if (!grep(/^$tagname$/, @TAGS)) {
330         $expat->xpcroak ("Unknown tag '$tagname'");
331     }
332
333     #    print ("opening: ");
334     #    &debug_dump_c();
335
336     my $value = $pending_attrs{"value"};
337     my $id = $pending_attrs{"id"};
338
339     if ($tagname eq $TAGS[$API]) {
340         if ($inited) {
341             $expat->xpcroak ("Only one '$tagname' tag allowed");
342         }
343         if (!$id) {
344             &croak_attr ($expat, $tagname, "id");
345         }
346         $apiid = $id;
347         $inited = 1;
348     } elsif ($inited) {
349         if ($tagname eq $TAGS[$ENTRY]) {
350             if (!$id) {
351                 &croak_attr ($expat, $tagname, "id");
352             }
353             $c = $entries{$id};
354         } elsif ($tagname eq $TAGS[$SEE_ALSO]) {
355             if (!$value) {
356                 &croak_attr ($expat, $tagname, "value");
357             } elsif (!$entries{$value}) {
358                 $expat->xpcroak ("Undefined SEEALSO reference, '$value'");
359             }
360             if (!grep (/^$value$/, @{$c->{$TAGS[$SEE_ALSO]}})) {
361                 push (@{$c->{$TAGS[$SEE_ALSO]}}, $value);
362             }
363         } elsif (grep(/^$tagname$/, @FORMATTING_TAGS)) {
364             if (!grep(/^$lasttagname$/, @FORMAT_CONTAINERS)) {
365                 $expat->xpcroak ("Tag $lasttagname cannot contain formatting " .
366                                  "tags");
367             }
368         } elsif (($tagname eq $TAGS[$PARAM]) || ($tagname eq $TAGS[$RETVAL])) {
369             if ($lasttagname ne $TAGS[$SYNTAX]) {
370                 $expat->xpcroak ("Tag $tagname can only be contained by a '" .
371                                  $TAGS[$SYNTAX] . "' tag");
372             }
373             if (!$pending_attrs{"name"}) {
374                 if ($tagname eq $TAGS[$RETVAL]) {
375                     $pending_attrs{"name"} = "Return Value";
376                 } else {
377                     &croak_attr ($expat, $tagname, "name");
378                 }
379             }
380             if (!$pending_attrs{"type"}) {
381                 $pending_attrs{"type"} = "&nbsp;";
382                 # &croak_attr ($expat, $tagname, "type");
383             }
384         } elsif ((($tagname eq $TAGS[$HEAD]) || ($tagname eq $TAGS[$HEAD])) &&
385                  ($lasttagname ne $TAGS[$API])) {
386             $expat->xpcroak ("Tag $tagname can only be contained by ".
387                              "an '" . $TAGS[$API] . "' tag");
388         }
389     } else {
390         $expat->xpcroak ("Tag '$tagname' must be contained in an '" .
391                          $TAGS[$API] . " tag");
392     }
393 }
394
395 sub EndTag {
396     # phase 2 close tag handler
397     my ($expat) = @_;
398     my $iscontainer = 0;
399     $_ =~ /<\/([^\s>]*)/;
400     $tagname = $1;
401
402 #    print ("closing: ");
403 #    &debug_dump_c();
404
405     if (grep(/^$tagname$/, @CDATA_TAGS)) {
406         $iscontainer = 1;
407         if ($pending_text eq "") {
408             print STDERR "WARNING: Empty container '$tagname' at line " .
409               $expat->current_line . " ignored.\n";
410             #$expat->xpcroak ("Empty container '$tagname'.");
411         }
412     }
413
414     if (grep(/^$tagname$/, @FORMATTING_TAGS)) {
415         if ($iscontainer) {
416             if ($tagname eq $TAGS[$C]) {
417                 # code tag
418                 if (($pending_text ne $c->{$TAGS[$ENTRY]}) &&
419                     ($entries{$pending_text})) {
420                     # if the contents are a valid entry
421                     # add it to the SEEALSO, in case it's not already there
422                     if (!grep (/^$pending_text$/, @{$c->{$TAGS[$SEE_ALSO]}})) {
423                         push (@{$c->{$TAGS[$SEE_ALSO]}}, $pending_text);
424                     }
425                     # and make it a link
426                     $pending_text = &get_link($pending_text);
427                 }
428                 $pending_text = "<code>$pending_text</code>";
429             } elsif ($tagname eq $TAGS[$S]) {
430                 # seealso reference
431                 my $value;
432                 if (($value = $entries{$pending_text}) ||
433                     ($value = $toc_externals{$pending_text}) ||
434                     ($value = $c->{$TAGS[$EXTERNALREF]}{$pending_text})) {
435                     # it's a valid external reference
436                     # put it in this entry's external references incase it isn't
437                     # already there.
438                     $c->{$TAGS[$EXTERNALREF]}{$pending_text} = $value;
439                     $_ = $value;
440                     s/$URLVAR_ENTRY/$c->{$TAGS[$ENTRY]}/g;
441                     $pending_text =
442                       "<a href='$_' target='other_window'>" .
443                         "$pending_text</a>";
444                 } elsif ($value = $groups{$pending_text}) {
445                     # it's a valid group
446                     # put it in this entry's group references incase it isn't
447                     # already there.
448                     if ((!$c->{$TAGS[$GROUP]}) ||
449                         (!grep (/^$pending_text$/, @{$c->{$TAGS[$GROUP]}}))) {
450                         push (@{$c->{$TAGS[$GROUP]}}, $pending_text);
451                     }
452                     $pending_text ="<a href='javascript:" .
453                       "navToGroup(\"GROUP_$pending_text\")'>$pending_text</a>";
454                 } else {
455                     # it's just not valid
456                     $expat->xpcroak ("Unknown reference in '" . $TAGS[$S] .
457                                      "' tag");
458                 }
459             } elsif ($tagname eq $TAGS[$B]) {
460                 # bold
461                 $pending_text = "<b>$pending_text</b>";
462             } elsif ($tagname eq $TAGS[$I]) {
463                 # italic
464                 $pending_text = "<i>$pending_text</i>";
465             } else {
466                 expat->xpcroak 
467                   ("OOPS: Unhandled container formatting tag '$tagname'");
468             }
469         } else {
470             if ($tagname eq $TAGS[$P]) {
471                 # paragraph
472                 $pending_text = "<P>";
473             } elsif ($tagname eq $TAGS[$BR]) {
474                 # br
475                 $pending_text = "<BR>";
476             } else {
477                 expat->xpcroak
478                   ("OOPS: Unhandled non-container formatting tag '$tagname'");
479             }
480         }
481         # combine with previous pendingtext
482         $pending_text = pop(@text_stack) . $pending_text;
483     } elsif ($iscontainer) {
484         # not a formatting tag, store the accumulated pendingtext in the
485         # right place, after some whitespace trimming
486         my @lines = split ("\n", $pending_text);
487         my $iscode = grep (/^$tagname$/, @CODE_TAGS);
488         my $i;
489         my $line;
490         my $result_text = "";
491
492         for $i (0 ... $#lines) {
493             $line = $lines[$i];
494             if ((($i != 0) && ($i != $#lines)) || ($line =~ /[\S\n]/)) {
495                 if ($iscode) {
496                     $line = &add_leading_nbsp($line);
497                 } else {
498                     $_ = $line;
499                     s/\.\s\s(.)/\.\&nbsp;\&nbsp;$1/g;
500                     $line = $_;
501                 }
502                 $line =~ /^[\s\n]*(.*)[\s\n]*/;
503                 $result_text .= $1 . "\n";
504             }
505         }
506
507         $_ = $result_text;
508 #        s/\n/<br>/g;
509         $result_text = $_;
510
511         if (($tagname eq $TAGS[$PARAM]) || ($tagname eq $TAGS[$RETVAL])) {
512             # parameter block
513             my $name = $pending_attrs{"name"};
514             my $type = $pending_attrs{"type"};
515             my $html = ("<td class='param-name'><code>$name</code></td>" .
516                         "<td class='param-type'><code>$type</code></td>" .
517                         "<td class='param-desc'>$result_text</td>\n");
518             push (@{$c->{$TAGS[$PARAM]}}, $html);
519         } elsif ($tagname eq $TAGS[$EXAMPLE]) {
520             $c->{$tagname . "_DESC"} = $pending_attrs{"desc"};
521             $c->{$tagname} = $result_text;
522         } elsif ($tagname eq $TAGS[$HEAD]) {
523             $user_head .= $result_text;
524         } elsif ($tagname eq $TAGS[$FOOT]) {
525             $user_foot .= $result_text;
526         } else {
527             $c->{$tagname} .= $result_text;
528         }
529
530         $pending_text = pop (@text_stack);
531     } else {
532         $pending_text = pop (@text_stack);
533     }
534
535     %pending_attrs = %{pop (@attr_stack)};
536     $tagname = pop (@tag_stack);
537
538 #    print ("popped: ");
539 #    &debug_dump_c();
540
541 }
542
543 sub Text {
544     my ($expat) = @_;
545
546     if (/^[\s\n]+$/) {
547         return;
548     }
549     if (!grep(/^$tagname$/, @CDATA_TAGS)) {
550         $expat->xpcroak ("Tag '$tagname' cannot contain text");
551     }
552
553     $pending_text .= $_;
554
555 }
556
557 sub EndDocument {
558 }
559
560 sub get_type_links {
561     my @types = split /\s*,\s*/, shift;
562     foreach my $type (@types) {
563         #_PH_ - fix to better parse C pointer types - a bit unsafe
564         $type =~ m|(&\s*)*([^ *]+)(\s*\*)*|;
565         my ($pre, $realtype, $post) = ($1, $2, $3);
566         if (exists $entries{$realtype}) {
567             if (!grep (/^$realtype$/, @{$c->{$TAGS[$SEE_ALSO]}})) {
568                 push (@{$c->{$TAGS[$SEE_ALSO]}}, $realtype);
569             }
570             $realtype = &get_link($realtype) ;
571             $type = "$pre$realtype$post";
572         }
573     }
574     return join ", ", @types;
575 }
576
577 sub get_param_links {
578     my $html = shift;
579     if ($html =~ m|class='(param-type'><code>)(.*?)(</code>)|g) {
580         my $new_param = &get_type_links($2);
581         $html =~ s|class='(param-type'><code>)(.*?)(</code>)|$1$new_param$3|;
582     }
583     return $html;
584 }
585
586 sub get_entry_html {
587     # get html for the current entry ($c)
588     my $html = "";
589     $c->{$TAGS[$ENTRY]} =~ /\.?(.*)/;
590     my $id = $1;
591     $html = "<center><table class='api-entry' width='100%' cellspacing='0'" .
592       "border='1' cellpadding='10'>\n";
593     $html .= "<tr><td class='entry-heading'>\n";
594
595     $html .= "<table class='entry-heading-table' width='100%' cellpadding='5'" .
596       "cellspacing='0'><tr>\n";
597     $html .= "<td class='entry-title' valign='center'><font size='+5'>" .
598       $id . "</font></td>\n";
599     $html .= "<td class='entry-type' align='center' width='25%'>" .
600       $c->{$TAGS[$TYPE]} . "</td>\n";
601     if ($c->{$TAGS[$DEPRECATED]}) {
602         $html .= "<td class='entry-deprecated' align='center' width='25%'>" .
603           "Deprecated</td>\n";
604     }
605     $html .= "</tr></table>\n";
606
607     $html .= "</td></tr>\n";
608
609     if ($c->{$TAGS[$SUMMARY]}) {
610         $html .= "<tr><td class='entry-summary'>\n";
611         $html .= "<h4 class='entry-subhead'>Summary</h4>\n";
612         $html .= $c->{$TAGS[$SUMMARY]};
613         $html .= "</td></tr>\n";
614     }
615     if ($c->{$TAGS[$SYNTAX]}) {
616         $html .= "<tr><td class='entry-syntax'>\n";
617         $html .= "<h4 class='entry-subhead'>Syntax</h4><pre>\n";
618         $html .= $c->{$TAGS[$SYNTAX]};
619         $html .= "</pre>\n";
620         if ($c->{$TAGS[$PARAM]}) {
621             $html .= "<center><table class='param-list' border='1' " .
622               "cellpadding='3' cellspacing='1'>";
623             $html .= "<tr class='param-list-head'>";
624             $html .= "<th>Name</th><th>Type</th><th>Description</th></tr>\n";
625             my $param = shift (@{$c->{$TAGS[$PARAM]}});
626             my $even = 1;
627             while ($param) {
628                 $_ = $param;
629                 if ($even == 1) {
630                     $html .= "<tr class='param-row-even'>";
631                 } else {
632                     $html .= "<tr class='param-row-odd'>";
633                 }
634                 $param = $_;
635
636                 $param = &get_param_links($param);
637                 $even *= -1;
638                 $html .= $param . "</tr>\n";
639                 $param = shift (@{$c->{$TAGS[$PARAM]}});
640             }
641             $html .= "</table></center>\n"
642         }
643         $html .= "</td></tr>\n";
644     }
645     if ($c->{$TAGS[$DESCRIPTION]}) {
646         $html .= "<tr><td class='entry-description'>\n";
647         $html .= "<h4 class='entry-subhead'>Description</h4>\n";
648         $html .= $c->{$TAGS[$DESCRIPTION]};
649         $html .= "</td></tr>\n";
650     }
651     if ($c->{$TAGS[$EXAMPLE]}) {
652         $html .= "<tr><td class='entry-example'>\n";
653         $html .= "<h4 class='entry-subhead'>Example</h4>\n";
654         if ($c->{$TAGS[$EXAMPLE] . "_DESC"}) {
655             $html .= $c->{$TAGS[$EXAMPLE] . "_DESC"} . "<br>";
656         }
657         $html .= "<pre>" . $c->{$TAGS[$EXAMPLE]};
658         $html .= "</pre></td></tr>\n";
659     }
660     if ($c->{$TAGS[$NOTE]}) {
661         $html .= "<tr><td class='entry-notes'>\n";
662         $html .= "<h4 class='entry-subhead'>Notes</h4>\n";
663         $html .= $c->{$TAGS[$NOTE]};
664         $html .= "</td></tr>\n";
665     }
666
667     my $sa = get_seealso();
668     if ($sa) {
669         $html .= "<tr><td class='entry-seealso'>\n";
670         $html .= "<h4 class='entry-subhead'>See Also</h4>\n";
671         $html .= $sa;
672         $html .= "</td></tr>\n";
673     }
674
675     $html .= "</table></center><br>\n";
676     return $html;
677
678 }
679
680 sub get_seealso {
681     # get the see also section for the current entry ($c);
682     my @links;
683     my $k;
684     my $i;
685     my $html = "";
686
687     for (@{$c->{$TAGS[$GROUP]}}) {
688         push (@links, "<a href='javascript:navToGroup(\"GROUP_$_\")'>$_</a>");
689     }
690
691     if ($#links != -1) {
692         $html .= "<tr class='seealso-groups'><td>Groups</td>\n";
693         $html .= "<td>[ " . join (" | ", sort(@links)) . " ]</td></tr>\n";
694     }
695
696     @links = ();
697
698     # global externals (had a parent tag of <API/>)
699     for $k (keys(%externals)) {
700         $_ = $externals{$k};
701         s/$URLVAR_ENTRY/$c->{$TAGS[$ENTRY]}/g;
702         push (@links, "<a href='$_' target='other_window'>$k</a>");
703     }
704
705     # local externals (parented by <ENTRY/>
706     for $k (keys(%{$c->{$TAGS[$EXTERNALREF]}})) {
707         $_ = $c->{$TAGS[$EXTERNALREF]}{$k};
708         s/$URLVAR_ENTRY/$c->{$TAGS[$ENTRY]}/g;
709         push (@links, "<a href='$_' target='other_window'>$k</a>");
710     }
711
712     if ($#links != -1) {
713         $html .= "<tr class='seealso-externals'><td>Documents</td>\n";
714         $html .= "<td>[ " . join (" | ", sort(@links)) . " ]</td></tr>\n";
715     }
716
717     @links = ();
718
719     for $k (@{$c->{$TAGS[$SEE_ALSO]}}) {
720         push (@links, &get_link($k));
721     }
722
723     if ($#links != -1) {
724         $html .= "<tr class='seealso-internals'><td>Entries</td>\n";
725         $html .= "<td>[ " . join (" | ", sort(@links)) . " ]</td></tr>\n";
726     }
727
728     if ($html) {
729         $html = "<table class='seealso-table'>\n" . $html . "\n</table>\n";
730     }
731
732     return $html;
733
734 }
735
736 sub add_entry_complete {
737     # add html for the current entry to the "complete" page
738     my ($html) = @_;
739
740     print COMPLETE "<a name='" . $c->{$TAGS[$ENTRY]} . "'></a>\n";
741     print COMPLETE $html;
742
743 }
744
745 sub add_entry_sparse {
746     # add html for the current entry to a new "sparse" page
747     my ($html) = @_;
748     my $outfile = $outdir . "/api-" . $c->{$TAGS[$ENTRY]} . ".html";
749
750     open (SPARSE, ">$outfile") ||
751       die ("Couldn't open $outfile.\n");
752
753     my $headstr = "<html><head><link rel=StyleSheet href='api-content.css' " .
754       "TYPE='text/css' MEDIA='screen'>" .
755         "<title>" . $c->{$TAGS[$ENTRY]} . "</title>" . $JS_SPARSE .
756           "</head><body bgcolor='white'>\n$WARNING" .
757             "<h1 class='title'>$apiid Reference</h1>\n" . $user_head;
758
759     print SPARSE $headstr;
760     print SPARSE $html;
761
762     print SPARSE $user_foot;
763     print SPARSE $footstr;
764
765     close SPARSE;
766 }
767
768 sub end_abc {
769     local (*G) = shift;
770     print G "</table></center></td></tr>\n";
771 }
772
773 sub write_toc_groups {
774     # Write the groups section of the toc to both the sparse and complete
775     # toc files
776     my @groups = sort(keys (%groups));
777     my $g;
778     my $even = 1;
779     my $head = "<tr class='toc-title'><th><br><h3>Grouped Listing</h3></th>" .
780         "</tr>\n";
781
782     print COMPLETE_TOC $head;
783     print COMPLETE_TOC_GRP $head;
784     print SPARSE_TOC $head;
785     print SPARSE_TOC_GRP $head;
786     #print SIDEBAR_TOC $head;
787
788     for $g (@groups) {
789         $head = "<tr><td class='";
790         if ($even == 1) {
791             $head .= "toc-group-even";
792         } else {
793             $head .= "toc-group-odd";
794         }
795         $head .= "'><center><table border='0' cellspacing='0' cellpading='0' " .
796           "width='100%'>\n";
797         $head .= "<tr><th><a name='GROUP_$g'>$g</a></th><td>&nbsp;</td></tr>\n";
798         print COMPLETE_TOC $head;
799         print COMPLETE_TOC_GRP $head;
800         print SPARSE_TOC $head;
801         print SPARSE_TOC_GRP $head;
802         print SIDEBAR_TOC $head;
803         my $e;
804         for $e (sort(@{$groups{$g}})) {
805             $c = $entries{$e};
806             &add_toc_complete(*COMPLETE_TOC);
807             &add_toc_complete(*COMPLETE_TOC_GRP);
808             &add_toc_sparse(*SPARSE_TOC);
809             &add_toc_sparse(*SPARSE_TOC_GRP);
810             &add_toc_sparse(*SIDEBAR_TOC, 1);
811         }
812         $head = "</table></center><br></td></tr>\n";
813         print COMPLETE_TOC $head;
814         print COMPLETE_TOC_GRP $head;
815         print SPARSE_TOC $head;
816         print SPARSE_TOC_GRP $head;
817         print SIDEBAR_TOC $head;
818         $even *= -1;
819     }
820
821 }
822
823 sub add_toc_complete {
824     local (*G) = shift;
825     # add the current entry ($c) to the complete toc
826     #print COMPLETE_TOC &add_toc(0);
827     print G &add_toc(0);
828 }
829
830 sub add_toc_sparse {
831     local (*G) = shift;
832     my $sidebar = shift;
833     # add the current entry ($c) to the sparse toc
834     #print SPARSE_TOC &add_toc(1);
835     print G &add_toc(1,$sidebar);
836 }
837
838 sub add_toc {
839     # add the current entry ($c) to the either the complete or sparse toc,
840     # based on the is_sparse parameter.  Should only be called from
841     # add_toc_sparse or add_toc_complete.
842     my ($is_sparse, $sidebar) = @_;
843     my $html;
844     my $classsuffix = $c->{$TAGS[$DEPRECATED]} ? "-deprecated" : "";
845
846     $html = "<tr><td class='toc-row$classsuffix'>";
847
848     $html .= &get_toc_link($c->{$TAGS[$ENTRY]}, $is_sparse,
849                            "toc-entry$classsuffix", $sidebar)
850       . "</td>";
851
852     if ($classsuffix) {
853         $html .= "<td class='toc-ind-deprecated' width='10%' " .
854           "align='center' valign='center'>D</td>";
855     } else {
856         $html .= "<td>&nbsp;</td>";
857     }
858
859     $html .= "</tr>\n";
860
861     return $html;
862 }
863
864 sub get_link {
865     # get a link for use in a content page.  Works in both sparse and complete
866     # pages (because of the navToEntry call.)
867     my ($entry) = @_;
868     my $entryE = _encode($entry); #_PC_
869     return ("<a href='javascript:navToEntry(\"$entryE\");'>$entry</a>");
870 }
871
872 sub get_toc_link {
873     # get a link for use in a toc page.
874     my ($entry, $is_sparse, $class, $sidebar) = @_;
875
876     my $entryE = _encode($entry); #_PC_
877     my $contentTarget = $sidebar ? '_content' : 'content-container';
878
879     if ($is_sparse) {
880         return "<a class='$class' href='api-$entryE.html' " .
881           "target='$contentTarget'>$entry</a>\n";
882     } else {
883         return "<a href='complete.html#$entryE' class='$class' " .
884           "target='$contentTarget'>$entry</a>";
885     }
886 }
887
888 sub get_menu {
889     my $type = shift;
890     my %menu = 
891       ('full-sparse' => [['alphabetical listing' => 'sparse-toc-abc.html'],
892                          ['grouped listing' => 'sparse-toc-grp.html']],
893        'abc-sparse' => [['full listing' => 'sparse-toc.html'],
894                         ['grouped listing' => 'sparse-toc-grp.html']],
895        'grp-sparse' => [['full listing' => 'sparse-toc.html'],
896                         ['alphabetical listing' => 'sparse-toc-abc.html']],
897        'full-compl' => [['alphabetical listing' => 'complete-toc-abc.html'],
898                          ['grouped listing' => 'complete-toc-grp.html']],
899        'abc-compl' => [['full listing' => 'complete-toc.html'],
900                         ['grouped listing' => 'complete-toc-grp.html']],
901        'grp-compl' => [['full listing' => 'complete-toc.html'],
902                         ['alphabetical listing' => 'complete-toc-abc.html']],
903       );
904     my $menu = $menu{$type};
905     my $ret;
906
907     foreach my $item (@$menu) {
908         $ret .= "<a href='" . $$item[1] . "'>" . $$item[0] . "</a><br>\n";
909     }
910
911     return $ret;
912 }
913
914 sub init_files {
915     # initialize the complete content and toc files.
916     my $headstr = "<html><head><link rel=StyleSheet href='api-content.css' " .
917       "TYPE='text/css' MEDIA='screen'><title>$apiid</title></head>" .
918         $JS_COMPLETE . "<body bgcolor='white'>\n$WARNING" .
919           "<h1 class='title'>$apiid Reference</h1>\n" . $user_head;
920
921     print COMPLETE $headstr;
922
923     my $tocstr1 = 
924       ("<html><head><link rel=StyleSheet href='api-toc.css' " .
925        "TYPE='text/css' MEDIA='screen'>" .
926        "<title>$apiid table of contents</title>" .
927        "</head>\n<body bgcolor='white'>\n$WARNING" .
928        "<h1 class='title'>$apiid Reference</h1><h4>Table of Contents</h4>\n");
929     my $tocstr2 = 
930       ("<center><table class='toc-table' border='1' cellpadding='0' " .
931        "cellspacing='0' width='100%'>\n");
932     my $abcstr = 
933       ("<tr class='toc-title'><th><br><h3>Alphabetical Listing</h3></th>" .
934        "</tr>\n<tr><td>\n" .
935        "<center><table class='toc-abc' border='0' cellspacing='0' " .
936        "cellpadding='0' width='100%'>\n");
937
938     my $sidebar1 = $tocstr1;
939     $sidebar1 =~ s/api\-toc\.css/sidebar.css/;
940
941     print COMPLETE_TOC $tocstr1;
942     print COMPLETE_TOC &get_menu("full-compl");
943     print COMPLETE_TOC $tocstr2;
944     print COMPLETE_TOC $abcstr;
945
946     print COMPLETE_TOC_ABC $tocstr1;
947     print COMPLETE_TOC_ABC &get_menu("abc-compl");
948     print COMPLETE_TOC_ABC $tocstr2;
949     print COMPLETE_TOC_ABC $abcstr;
950
951     print COMPLETE_TOC_GRP $tocstr1;
952     print COMPLETE_TOC_GRP &get_menu("grp-compl");
953     print COMPLETE_TOC_GRP $tocstr2;
954
955     print SPARSE_TOC $tocstr1;
956     print SPARSE_TOC &get_menu("full-sparse");
957     print SPARSE_TOC $tocstr2;
958     print SPARSE_TOC $abcstr;
959
960     print SPARSE_TOC_ABC $tocstr1;
961     print SPARSE_TOC_ABC &get_menu("abc-sparse");
962     print SPARSE_TOC_ABC $tocstr2;
963     print SPARSE_TOC_ABC $abcstr;
964
965     print SPARSE_TOC_GRP $tocstr1;
966     print SPARSE_TOC_GRP &get_menu("grp-sparse");
967     print SPARSE_TOC_GRP $tocstr2;
968
969     print SIDEBAR_TOC $sidebar1;
970     #print SIDEBAR_TOC &get_menu("grp-sparse");
971     print SIDEBAR_TOC $tocstr2;
972 }
973
974 sub close_toc {
975     local (*G) = shift;
976     my $menu = shift;
977     my $sidebar = shift;
978
979     print G "</table></center>\n";
980     print G &get_menu($menu) . "<p>\n" unless $sidebar;
981     print G $user_foot . "\n";
982     print G $footstr;
983     close G;
984
985 }
986
987 sub close_files {
988     # finish up the complete content and toc files.
989
990     print COMPLETE $user_foot;
991     print COMPLETE $footstr;
992     close COMPLETE;
993
994     &close_toc(*COMPLETE_TOC, "full-compl");
995     &close_toc(*COMPLETE_TOC_ABC, "abc-compl");
996     &close_toc(*COMPLETE_TOC_GRP, "grp-compl");
997     &close_toc(*SPARSE_TOC, "full-sparse");
998     &close_toc(*SPARSE_TOC_ABC, "abc-sparse");
999     &close_toc(*SPARSE_TOC_GRP, "grp-sparse");
1000     &close_toc(*SIDEBAR_TOC, "grp-sparse", 1);
1001 }
1002
1003 sub add_leading_nbsp {
1004     # replaces leading spaces with &nbsp; entities.  Used for tags which
1005     # contain code.
1006     my ($str) = @_;
1007     my $i;
1008     my $pfx = "";
1009
1010     if (!($str =~ /^(\s+)/)) {
1011         return $str;
1012     }
1013
1014     my $len = length($1);
1015     for $i (0 .. $len) {
1016         $pfx .= "&nbsp;";
1017     }
1018
1019     substr ($str, 0, $len) = $pfx;
1020     return $str;
1021
1022 }
1023
1024 sub debug_dump_c {
1025
1026     print ("tag: $tagname, attrs: " .
1027            join (", ", keys (%pending_attrs)) .
1028            ", stacks: " . $#text_stack . ", " . $#tag_stack . ", " .
1029            $#attr_stack . "\n");
1030
1031     for (0 ... $#attr_stack) {
1032         print ("attribs at $_: " . join (", ", keys (%{$attr_stack[$_]})) .
1033                "\n");
1034     }
1035
1036 }
1037
1038 sub debug_write_entry {
1039     my $i;
1040
1041     for $i (keys(%{$c})) {
1042         my $str = "";
1043         if (($i eq $TAGS[$SEE_ALSO]) || ($i eq ($TAGS[$PARAM]))) {
1044             $str = join (", ", @{$c->{$i}});
1045         } else {
1046             $str = $c->{$i};
1047         }
1048
1049         print ("$i : $str\n");
1050     }
1051     print ("===\n");
1052 }
1053
1054 sub croak_attr {
1055     my ($expat, $tagname, $attr) = @_;
1056
1057     $expat->xpcroak ("Tag $tagname needs an $attr attribute");
1058 }
1059
1060 #_PC_ - subroutine to encode URLs
1061 sub _encode {
1062     my ($entry) = @_;
1063
1064     $entry =~ s/(\W)/sprintf("%%%x", ord($1))/eg;
1065     return $entry;
1066 }