scide: LookupDialog - redo lookup on classes after partial lookup
[supercollider.git] / SCClassLibrary / SCDoc / SCDocRenderer.sc
blob9f5dff5f43a6d9fafe6ff3aa7e2ec11722c35a2a
1 /*
2 HTML renderer
3 */
4 SCDocHTMLRenderer {
5     classvar currentClass, currentImplClass, currentMethod, currArg;
6     classvar currentNArgs;
7     classvar footNotes;
8     classvar noParBreak;
9     classvar currDoc;
10     classvar minArgs;
11     classvar baseDir;
13     *escapeSpecialChars {|str|
14         var x = "";
15         var beg = -1, end = 0;
16         str.do {|chr, i|
17             switch(chr,
18                 $&, { x = x ++ str.copyRange(beg, i-1) ++ "&"; beg = i+1; },
19                 $<, { x = x ++ str.copyRange(beg, i-1) ++ "&lt;"; beg = i+1; },
20                 $>, { x = x ++ str.copyRange(beg, i-1) ++ "&gt;"; beg = i+1; },
21                 { end = i }
22             );
23         };
24         if(beg<=end) {
25             x = x ++ str[beg..end];
26         };
27         ^x;
28     }
30     *htmlForLink {|link|
31         var n, m, f, c, doc;
32         // FIXME: how slow is this? can we optimize
33         #n, m, f = link.split($#); // link, anchor, label
34         ^if ("^[a-zA-Z]+://.+".matchRegexp(link) or: (link.first==$/)) {
35             if(f.size<1) {f=link};
36             c = if(m.size>0) {n++"#"++m} {n};
37             "<a href=\""++c++"\">"++this.escapeSpecialChars(f)++"</a>";
38         } {
39             if(n.size>0) {
40                 c = baseDir+/+n;
41                 doc = SCDoc.documents[n];
42                 // link to other doc (might not be rendered yet)
43                 if(doc.notNil) {
44                     c = c ++ ".html";
45                 } {
46                     // link to ready-made html (Search, Browse, etc)
47                     if(File.exists(SCDoc.helpTargetDir+/+n++".html")) {
48                         c = c ++ ".html";
49                     } {
50                         // link to other file?
51                         if(File.exists(SCDoc.helpTargetDir+/+n).not) {
52                             "SCDoc: In %\n"
53                             "  Broken link: '%'"
54                             .format(currDoc.fullPath, link).warn;
55                         };
56                     };
57                 };
58             } {
59                 c = ""; // link inside same document
60             };
61             if(m.size>0) { c = c ++ "#" ++ m }; // add #anchor
62             if(f.size<1) { // no label
63                 if(n.size>0) {
64                     f = if(doc.notNil) {doc.title} {n.basename};
65                     if(m.size>0) {
66                         f = f++": "++m;
67                     }
68                 } {
69                     f = if(m.size>0) {m} {"(empty link)"};
70                 };
71             };
72             "<a href=\""++c++"\">"++this.escapeSpecialChars(f)++"</a>";
73         };
74     }
76     *makeArgString {|m, par=true|
77         var res = "";
78         var value;
79         var l = m.argNames;
80         var last = l.size-1;
81         l.do {|a,i|
82             if (i>0) { //skip 'this' (first arg)
83                 if(i==last and: {m.varArgs}) {
84                     res = res ++ " <span class='argstr'>" ++ "... " ++ a;
85                 } {
86                     if (i>1) { res = res ++ ", " };
87                     res = res ++ "<span class='argstr'>" ++ a;
88                     (value = m.prototypeFrame[i]) !? {
89                         value = if(value.class===Float) { value.asString } { value.cs };
90                         res = res ++ ": " ++ value;
91                     };
92                 };
93                 res = res ++ "</span>";
94             };
95         };
96         if (res.notEmpty and: par) {
97             ^("("++res++")");
98         };
99         ^res;
100     }
102     *renderHeader {|stream, doc|
103         var x, cats, m, z;
104         var folder = doc.path.dirname;
105         var undocumented = false;
106         if(folder==".",{folder=""});
107         
108         baseDir = ".";
109         doc.path.occurrencesOf(Platform.pathSeparator).do {
110             baseDir = baseDir ++ "/..";
111         };
113         stream
114         << "<html><head><title>" << doc.title << "</title>\n"
115         << "<link rel='stylesheet' href='" << baseDir << "/scdoc.css' type='text/css' />\n"
116         << "<link rel='stylesheet' href='" << baseDir << "/frontend.css' type='text/css' />\n"
117         << "<link rel='stylesheet' href='" << baseDir << "/custom.css' type='text/css' />\n"
118         << "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n"
119         << "<script src='" << baseDir << "/scdoc.js' type='text/javascript'></script>\n"
120         << "<script src='" << baseDir << "/docmap.js' type='text/javascript'></script>\n" // FIXME: remove?
121         << "<script src='" << baseDir << "/prettify.js' type='text/javascript'></script>\n"
122         << "<script src='" << baseDir << "/lang-sc.js' type='text/javascript'></script>\n"
123         << "<script src='" << baseDir << "/MathJax/MathJax.js?config=TeX-AMS_HTML,scmathjax' type='text/javascript'></script>\n"
124         << "<script type='text/javascript'>var helpRoot='" << baseDir << "';</script>\n"
125         << "</head>\n";
127         stream
128         << "<ul id='menubar'></ul>\n"
129         << "<body onload='fixTOC();prettyPrint()'>\n"
130         << "<div class='contents'>\n"
131         << "<div class='header'>\n"
132         << "<div id='label'>SuperCollider " << folder.asString.toUpper;
133         if(doc.isExtension) {
134             stream << " (extension)";
135         };
136         stream << "</div>\n";
138         doc.categories !? {
139             stream
140             << "<div id='categories'>"
141             << (doc.categories.collect {|r|
142                 "<a href='"++baseDir +/+ "Browse.html#"++r++"'>"++r++"</a>"
143             }.join(", "))
144             << "</div>\n";
145         };
147         stream << "<h1>" << doc.title;
148         if((folder=="") and: {doc.title=="Help"}) {
149             stream << "<span class='headerimage'><img src='" << baseDir << "/images/SC_icon.png'/></span>";
150         };
151         stream
152         << "</h1>\n"
153         << "<div id='summary'>" << this.escapeSpecialChars(doc.summary) << "</div>\n"
154         << "</div>\n"
155         << "<div class='subheader'>\n";
157         if(doc.isClassDoc) {
158             if(currentClass.notNil) {
159                 m = currentClass.filenameSymbol.asString;
160                 stream << "<div id='filename'>Source: "
161                 << m.dirname << "/<a href='file://" << m << "'>" << m.basename << "</a></div>";
162                 if(currentClass != Object) {
163                     stream << "<div id='superclasses'>"
164                     << "Inherits from: "
165                     << (currentClass.superclasses.collect {|c|
166                         "<a href=\"../Classes/"++c.name++".html\">"++c.name++"</a>"
167                     }.join(" : "))
168                     << "</div>\n";
169                 };
170                 if(currentClass.subclasses.notNil) {
171                     z = false;
172                     stream << "<div id='subclasses'>"
173                     << "Subclasses: "
174                     << (currentClass.subclasses.collect(_.name).sort.collect {|c,i|
175                         if(i==12,{z=true;"<span id='hiddensubclasses' style='display:none;'>"},{""})
176                         ++"<a href=\"../Classes/"++c++".html\">"++c++"</a>"
177                     }.join(", "));
178                     if(z) {
179                         stream << "</span><a class='subclass_toggle' href='#' onclick='javascript:showAllSubclasses(this); return false'>&hellip;&nbsp;see&nbsp;all</a>";
180                     };
181                     stream << "</div>\n";
182                 };
183                 if(currentImplClass.notNil) {
184                     stream << "<div class='inheritance'>Implementing class: "
185                     << "<a href=\"../Classes/" << currentImplClass.name << ".html\">"
186                     << currentImplClass.name << "</a></div>\n";
187                 };
188             } {
189                 stream << "<div id='filename'>Location: <b>NOT INSTALLED!</b></div>\n";
190             };
191         };
193         doc.related !? {
194             stream << "<div id='related'>See also: "
195             << (doc.related.collect {|r| this.htmlForLink(r)}.join(", "))
196             << "</div>\n";
197         };
199         // FIXME: Remove this when conversion to new help system is done!
200         if(doc.isUndocumentedClass) {
201             x = Help.findHelpFile(name);
202             x !? {
203                 stream << ("[ <a href='" ++ baseDir ++ "/OldHelpWrapper.html#"
204                 ++x++"?"++SCDoc.helpTargetDir +/+ doc.path ++ ".html"
205                 ++"'>old help</a> ]")
206             };
207         };
209         stream << "</div>\n";
210     }
212     *renderChildren {|stream, node|
213         node.children.do {|child| this.renderSubTree(stream, child) };
214     }
216     *renderMethod {|stream, node, cls, icls, css, pfx|
217         var args = node.text ?? ""; // only outside class/instance methods
218         var names = node.children[0].children.collect(_.text);
219         var mstat, sym, m, m2, mname2;
220         var lastargs, args2;
221         var x, maxargs = -1;
222         var methArgsMismatch = false;
223         minArgs = inf;
224         currentMethod = nil;
225         names.do {|mname|
226             mname2 = this.escapeSpecialChars(mname);
227             if(cls.notNil) {
228                 mstat = 0;
229                 sym = mname.asSymbol;
230                 //check for normal method or getter
231                 m = icls !? {icls.findRespondingMethodFor(sym.asGetter)};
232                 m = m ?? {cls.findRespondingMethodFor(sym.asGetter)};
233                 m !? {
234                     mstat = mstat | 1;
235                     args = this.makeArgString(m);
236                     args2 = m.argNames !? {m.argNames[1..]};
237                 };
238                 //check for setter
239                 m2 = icls !? {icls.findRespondingMethodFor(sym.asSetter)};
240                 m2 = m2 ?? {cls.findRespondingMethodFor(sym.asSetter)};
241                 m2 !? {
242                     mstat = mstat | 2;
243                     args = m2.argNames !? {this.makeArgString(m2,false)} ?? {"value"};
244                     args2 = m2.argNames !? {m2.argNames[1..]};
245                 };
246                 maxargs.do {|i|
247                     var a = args2[i];
248                     var b = lastargs[i];
249                     if(a!=b and: {a!=nil} and: {b!=nil}) {
250                         methArgsMismatch = true;
251                     }
252                 };
253                 lastargs = args2;
254                 case
255                     {args2.size>maxargs} {
256                         maxargs = args2.size;
257                         currentMethod = m2 ?? m;
258                     }
259                     {args2.size<minArgs} {
260                         minArgs = args2.size;
261                     }
262                 ;
263             } {
264                 m = nil;
265                 m2 = nil;
266                 mstat = 1;
267             };
269             x = {
270                 stream << "<h3 class='" << css << "'>"
271                 << "<span class='methprefix'>" << (pfx??"&nbsp;") << "</span>"
272                 << "<a name='" << (pfx??".") << mname << "' href='"
273                 << baseDir << "/Overviews/Methods.html#"
274                 << mname2 << "'>" << mname2 << "</a>"
275             };
277             x.value;
278             switch (mstat,
279                 // getter only
280                 1, { stream << " " << args << "</h3>\n"; },
281                 // getter and setter
282                 3, { stream << "</h3>\n"; },
283                 // method not found
284                 0, {
285                     "SCDoc: In %\n"
286                     "  Method %% not found.".format(currDoc.fullPath,pfx,mname2).warn;
287                     stream << ": METHOD NOT FOUND!</h3>\n";
288                 }
289             );
291             // has setter
292             if(mstat & 2 > 0) {
293                 x.value;
294                 if(args2.size<2) {
295                     stream << " = " << args << "</h3>\n";
296                 } {
297                     stream << "_ (" << args << ")</h3>\n";
298                 }
299             };
301             m = m ?? m2;
302             m !? {
303                 if(m.isExtensionOf(cls) and: {icls.isNil or: {m.isExtensionOf(icls)}}) {
304                     stream << "<div class='extmethod'>From extension in <a href='"
305                     << m.filenameSymbol << "'>" << m.filenameSymbol << "</a></div>\n";
306                 } {
307                     if(m.ownerClass == icls) {
308                         stream << "<div class='supmethod'>From implementing class</div>\n";
309                     } {
310                         if(m.ownerClass != cls) {
311                             m = m.ownerClass.name;
312                             m = if(m.isMetaClassName) {m.asString.drop(5)} {m};
313                             stream << "<div class='supmethod'>From superclass: <a href='"
314                             << baseDir << "/Classes/" << m << ".html'>" << m << "</a></div>\n";
315                         }
316                     }
317                 };
318             };
319         };
320         
321         if(methArgsMismatch) {
322             "SCDoc: In %\n"
323             "  Grouped methods % does not have the same argument signature."
324             .format(currDoc.fullPath, names).warn;
325         };
326         
327         // ignore trailing mul add arguments
328         if(currentMethod.notNil) {
329             currentNArgs = currentMethod.argNames.size;
330             if(currentNArgs > 2
331             and: {currentMethod.argNames[currentNArgs-1] == \add}
332             and: {currentMethod.argNames[currentNArgs-2] == \mul}) {
333                 currentNArgs = currentNArgs - 2;
334             }
335         } {
336             currentNArgs = 0;
337         };    
339         if(node.children.size > 1) {
340             stream << "<div class='method'>";
341             this.renderChildren(stream, node.children[1]);
342             stream << "</div>";
343         };
344         currentMethod = nil;
345     }
347     *renderSubTree {|stream, node|
348         var f, z;
349         switch(node.id,
350             \PROSE, {
351                 if(noParBreak) {
352                     noParBreak = false;
353                 } {
354                     stream << "\n<p>";
355                 };
356                 this.renderChildren(stream, node);
357             },
358             \NL, { }, // these shouldn't be here..
359 // Plain text and modal tags
360             \TEXT, {
361                 stream << this.escapeSpecialChars(node.text);
362             },
363             \LINK, {
364                 stream << this.htmlForLink(node.text);
365             },
366             \CODEBLOCK, {
367                 stream << "<pre class='code prettyprint lang-sc'>"
368                 << this.escapeSpecialChars(node.text)
369                 << "</pre>\n";
370             },
371             \CODE, {
372                 stream << "<code class='code prettyprint lang-sc'>"
373                 << this.escapeSpecialChars(node.text)
374                 << "</code>";
375             },
376             \EMPHASIS, {
377                 stream << "<em>" << this.escapeSpecialChars(node.text) << "</em>";
378             },
379             \TELETYPEBLOCK, {
380                 stream << "<pre>" << this.escapeSpecialChars(node.text) << "</pre>";
381             },
382             \TELETYPE, {
383                 stream << "<code>" << this.escapeSpecialChars(node.text) << "</code>";
384             },
385             \STRONG, {
386                 stream << "<strong>" << this.escapeSpecialChars(node.text) << "</strong>";
387             },
388             \SOFT, {
389                 stream << "<span class='soft'>" << this.escapeSpecialChars(node.text) << "</span>";
390             },
391             \ANCHOR, {
392                 stream << "<a class='anchor' name='" << node.text << "'>&nbsp;</a>";
393             },
394             \KEYWORD, {
395                 node.children.do {|child|
396                     stream << "<a class='anchor' name='kw_" << child.text << "'>&nbsp;</a>";
397                 }
398             },
399             \MATHBLOCK, { // uses MathJax to typeset TeX math
400                 stream << "<span class='math'>\\[\n"
401                 << this.escapeSpecialChars(node.text)
402                 << "\n\\]\n</span>";
403             },
404             \MATH, {
405                 stream << "<span class='math'>\\("
406                 << this.escapeSpecialChars(node.text)
407                 << "\\)</span>";
408             },
409             \IMAGE, {
410                 f = node.text.split($#);
411                 stream << "<div class='image'><img src='" << f[0] << "'/>";
412                 f[1] !? { stream << "<br><b>" << f[1] << "</b>" }; // ugly..
413                 stream << "</div>\n";
414             },
415 // Other stuff
416             \NOTE, {
417                 stream << "<div class='note'><span class='notelabel'>NOTE:</span> ";
418                 noParBreak = true;
419                 this.renderChildren(stream, node);
420                 stream << "</div>";
421             },
422             \WARNING, {
423                 stream << "<div class='warning'><span class='warninglabel'>WARNING:</span> ";
424                 noParBreak = true;
425                 this.renderChildren(stream, node);
426                 stream << "</div>";
427             },
428             \FOOTNOTE, {
429                 footNotes = footNotes.add(node);
430                 stream << "<a class='footnote anchor' name='footnote_org_"
431                 << footNotes.size
432                 << "' href='#footnote_"
433                 << footNotes.size
434                 << "'><sup>"
435                 << footNotes.size
436                 << "</sup></a> ";
437             },
438             \CLASSTREE, {
439                 stream << "<ul class='tree'>";
440                 this.renderClassTree(stream, node.text.asSymbol.asClass);
441                 stream << "</ul>";
442             },
443 // Lists and tree
444             \LIST, {
445                 stream << "<ul>\n";
446                 this.renderChildren(stream, node);
447                 stream << "</ul>\n";
448             },
449             \TREE, {
450                 stream << "<ul class='tree'>\n";
451                 this.renderChildren(stream, node);
452                 stream << "</ul>\n";
453             },
454             \NUMBEREDLIST, {
455                 stream << "<ol>\n";
456                 this.renderChildren(stream, node);
457                 stream << "</ol>\n";
458             },
459             \ITEM, { // for LIST, TREE and NUMBEREDLIST
460                 stream << "<li>";
461                 noParBreak = true;
462                 this.renderChildren(stream, node);
463             },
464 // Definitionlist
465             \DEFINITIONLIST, {
466                 stream << "<dl>\n";
467                 this.renderChildren(stream, node);
468                 stream << "</dl>\n";
469             },
470             \DEFLISTITEM, {
471                 this.renderChildren(stream, node);
472             },
473             \TERM, {
474                 stream << "<dt>";
475                 noParBreak = true;
476                 this.renderChildren(stream, node);
477             },
478             \DEFINITION, {
479                 stream << "<dd>";
480                 noParBreak = true;
481                 this.renderChildren(stream, node);
482             },
483 // Tables
484             \TABLE, {
485                 stream << "<table>\n";
486                 this.renderChildren(stream, node);
487                 stream << "</table>\n";
488             },
489             \TABROW, {
490                 stream << "<tr>";
491                 this.renderChildren(stream, node);
492             },
493             \TABCOL, {
494                 stream << "<td>";
495                 noParBreak = true;
496                 this.renderChildren(stream, node);
497             },
498 // Methods         
499             \CMETHOD, {
500                 this.renderMethod (
501                     stream, node,
502                     currentClass !? {currentClass.class},
503                     currentImplClass !? {currentImplClass.class},
504                     "cmethodname", "*"
505                 );
506             },
507             \IMETHOD, {
508                 this.renderMethod (
509                     stream, node,
510                     currentClass,
511                     currentImplClass,
512                     "imethodname", "-"
513                 );
514             },
515             \METHOD, {
516                 this.renderMethod (
517                     stream, node,
518                     nil, nil,
519                     "imethodname", nil
520                 );
521             },
522             \CPRIVATE, {},
523             \IPRIVATE, {},
524             \COPYMETHOD, {},
525             \CCOPYMETHOD, {},
526             \ICOPYMETHOD, {},
527             \ARGUMENTS, {
528                 stream << "<h4>Arguments:</h4>\n<table class='arguments'>\n";
529                 currArg = 0;
530                 if(currentMethod.notNil and: {node.children.size < (currentNArgs-1)}) {
531                     "SCDoc: In %\n"
532                     "  Method %% has % args, but doc has % argument:: tags.".format(
533                         currDoc.fullPath,
534                         if(currentMethod.ownerClass.isMetaClass) {"*"} {"-"},
535                         currentMethod.name,
536                         currentNArgs-1,
537                         node.children.size,
538                     ).warn;
539                 };
540                 this.renderChildren(stream, node);
541                 stream << "</table>";
542             },
543             \ARGUMENT, {
544                 currArg = currArg + 1;
545                 stream << "<tr><td class='argumentname'>";
546                 if(node.text.isNil) {
547                     currentMethod !? {
548                         if(currentMethod.varArgs and: {currArg==(currentMethod.argNames.size-1)}) {
549                             stream << "... ";
550                         };
551                         stream << if(currArg < currentMethod.argNames.size) {
552                             if(currArg > minArgs) {
553                                 "("++currentMethod.argNames[currArg]++")";
554                             } {
555                                 currentMethod.argNames[currArg];
556                             }
557                         } {
558                             "(arg"++currArg++")" // excessive arg
559                         };
560                     };
561                 } {
562                     stream << if(currentMethod.isNil or: {currArg < currentMethod.argNames.size}) {
563                         currentMethod !? {
564                             f = currentMethod.argNames[currArg].asString;
565                             if(
566                                 (z = if(currentMethod.varArgs and: {currArg==(currentMethod.argNames.size-1)})
567                                         {"... "++f} {f}
568                                 ) != node.text;
569                             ) {
570                                 "SCDoc: In %\n"
571                                 "  Method %% has arg named '%', but doc has 'argument:: %'.".format(
572                                     currDoc.fullPath,
573                                     if(currentMethod.ownerClass.isMetaClass) {"*"} {"-"},
574                                     currentMethod.name,
575                                     z,
576                                     node.text,
577                                 ).warn;
578                             };
579                         };
580                         if(currArg > minArgs) {
581                             "("++node.text++")";
582                         } {
583                             node.text;
584                         };
585                     } {
586                         "("++node.text++")" // excessive arg
587                     };
588                 };
589                 stream << "<td class='argumentdesc'>";
590                 this.renderChildren(stream, node);
591             },
592             \RETURNS, {
593                 stream << "<h4>Returns:</h4>\n<div class='returnvalue'>";
594                 this.renderChildren(stream, node);
595                 stream << "</div>";
597             },
598             \DISCUSSION, {
599                 stream << "<h4>Discussion:</h4>\n";
600                 this.renderChildren(stream, node);
601             },
602 // Sections
603             \CLASSMETHODS, {
604                 if(node.notPrivOnly) {
605                     stream << "<h2><a class='anchor' name='classmethods'>Class Methods</a></h2>\n";
606                 };
607                 this.renderChildren(stream, node);
608             },
609             \INSTANCEMETHODS, {
610                 if(node.notPrivOnly) {
611                     stream << "<h2><a class='anchor' name='instancemethods'>Instance Methods</a></h2>\n";
612                 };
613                 this.renderChildren(stream, node);
614             },
615             \DESCRIPTION, {
616                 stream << "<h2><a class='anchor' name='description'>Description</a></h2>\n";
617                 this.renderChildren(stream, node);
618             },
619             \EXAMPLES, {
620                 stream << "<h2><a class='anchor' name='examples'>Examples</a></h2>\n";
621                 this.renderChildren(stream, node);
622             },
623             \SECTION, {
624                 stream << "<h2><a class='anchor' name='" << node.text
625                 << "'>" << this.escapeSpecialChars(node.text) << "</a></h2>\n";
626                 if(node.makeDiv.isNil) {
627                     this.renderChildren(stream, node);
628                 } {
629                     stream << "<div id='" << node.makeDiv << "'>";
630                     this.renderChildren(stream, node);
631                     stream << "</div>";
632                 };
633             },
634             \SUBSECTION, {
635                 stream << "<h3><a class='anchor' name='" << node.text
636                 << "'>" << this.escapeSpecialChars(node.text) << "</a></h3>\n";
637                 if(node.makeDiv.isNil) {
638                     this.renderChildren(stream, node);
639                 } {
640                     stream << "<div id='" << node.makeDiv << "'>";
641                     this.renderChildren(stream, node);
642                     stream << "</div>";
643                 };
644             },
645             {
646                 "SCDoc: In %\n"
647                 "  Unknown SCDocNode id: %".format(currDoc.fullPath, node.id).warn;
648                 this.renderChildren(stream, node);
649             }
650         );
651     }
653     *renderTOC {|stream, node|
654         node.children !? {
655             stream << "<ul class='toc'>";
656             node.children.do {|n|
657                 switch(n.id,
658                     \DESCRIPTION, {
659                         stream << "<li class='toc1'><a href='#description'>Description</a></li>\n";
660                         this.renderTOC(stream, n);
661                     },
662                     \EXAMPLES, {
663                         stream << "<li class='toc1'><a href='#examples'>Examples</a></li>\n";
664                         this.renderTOC(stream, n);
665                     },
666                     \CLASSMETHODS, {
667                         if(n.notPrivOnly) {
668                             stream << "<li class='toc1'><a href='#classmethods'>Class methods</a></li>\n";
669                             this.renderTOC(stream, n);
670                         };
671                     },
672                     \INSTANCEMETHODS, {
673                         if(n.notPrivOnly) {
674                             stream << "<li class='toc1'><a href='#instancemethods'>Instance methods</a></li>\n";
675                             this.renderTOC(stream, n);
676                         };
677                     },
678                     \CMETHOD, {
679                         stream << "<li class='toc3'>"
680                         << (n.children[0].children.collect{|m|
681                             "<a href='#*"++m.text++"'>"++this.escapeSpecialChars(m.text)++"</a> ";
682                         }.join(" "))
683                         << "</li>\n";
684                     },
685                     \IMETHOD, {
686                         stream << "<li class='toc3'>"
687                         << (n.children[0].children.collect{|m|
688                             "<a href='#-"++m.text++"'>"++this.escapeSpecialChars(m.text)++"</a> ";
689                         }.join(" "))
690                         << "</li>\n";
691                     },
692                     \METHOD, {
693                         stream << "<li class='toc3'>"
694                         << (n.children[0].children.collect{|m|
695                             "<a href='#."++m.text++"'>"++this.escapeSpecialChars(m.text)++"</a> ";
696                         }.join(" "))
697                         << "</li>\n";
698                     },
700                     \SECTION, {
701                         stream << "<li class='toc1'><a href='#" << n.text << "'>"
702                         << this.escapeSpecialChars(n.text) << "</a></li>\n";
703                         this.renderTOC(stream, n);
704                     },
705                     \SUBSECTION, {
706                         stream << "<li class='toc2'><a href='#" << n.text << "'>"
707                         << this.escapeSpecialChars(n.text) << "</a></li>\n";
708                         this.renderTOC(stream, n);
709                     }
710                 );
711             };
712             stream << "</ul>";
713         };
714     }
716     *addUndocumentedMethods {|list, body, id2, id, title|
717         var l;
718         if(list.size>0) {
719             l = list.collectAs(_.asString,Array).sort.collect {|name|
720                 SCDocNode()
721                 .id_(id2)
722                 .children_([
723                     SCDocNode()
724                     .id_(\METHODNAMES)
725                     .children_([
726                         SCDocNode()
727                         .id_(\STRING)
728                         .text_(name.asString)
729                     ])
730                 ]);
731             };
732             body.addDivAfter(id, nil, title, l);
733         }
734     }
735     
736     *renderClassTree {|stream, cls|
737         var name, doc, desc = "";
738         name = cls.name.asString;
739         doc = SCDoc.documents["Classes/"++name];
740         doc !? { desc = " - "++doc.summary };
741         if(cls.name.isMetaClassName, {^this});
742         stream << "<li> <a href='" << baseDir << "/Classes/" << name << ".html'>"
743         << name << "</a>" << desc << "\n";
745         cls.subclasses !? {
746             stream << "<ul class='tree'>\n";
747             cls.subclasses.copy.sort {|a,b| a.name < b.name}.do {|x|
748                 this.renderClassTree(stream, x);
749             };
750             stream << "</ul>\n";
751         };
752     }
754     *renderFootNotes {|stream|
755         if(footNotes.notNil) {
756             stream << "<div class='footnotes'>\n";
757             footNotes.do {|n,i|
758                 stream << "<a class='anchor' name='footnote_" << (i+1) << "'/><div class='footnote'>"
759                 << "[<a href='#footnote_org_" << (i+1) << "'>" << (i+1) << "</a>] - ";
760                 noParBreak = true;
761                 this.renderChildren(stream, n);
762                 stream << "</div>";
763             };
764             stream << "</div>";
765         };
766     }
767     
768     *renderFooter {|stream, doc|
769         stream << "<div class='doclink'>";
770         doc.fullPath !? {
771             stream << "source: <a href='file://"
772             << doc.fullPath << "'>" << doc.fullPath << "</a><br>"
773         };
774         stream << "link::" << doc.path << "::<br>"
775         << "sc version: " << Main.version << "</div>"
776         << "</div></body></html>";
777     }
779     *renderOnStream {|stream, doc, root|
780         var body = root.children[1];
781         var redirect;
782         currDoc = doc;
783         footNotes = nil;
784         noParBreak = false;
785         
786         if(doc.isClassDoc) {
787             currentClass = doc.klass;
788             currentImplClass = doc.implKlass;
789             if(currentClass != Object) {
790                 body.addDivAfter(\CLASSMETHODS,"inheritedclassmets","Inherited class methods");
791                 body.addDivAfter(\INSTANCEMETHODS,"inheritedinstmets","Inherited instance methods");
792             };
793             this.addUndocumentedMethods(doc.undoccmethods, body, \CMETHOD, \CLASSMETHODS, "Undocumented class methods");
794             this.addUndocumentedMethods(doc.undocimethods, body, \IMETHOD, \INSTANCEMETHODS, "Undocumented instance methods");
795             body.sortClassDoc;
796         } {
797             currentClass = nil;
798             currentImplClass = nil;
799         };
800         
801         this.renderHeader(stream, doc);
802         stream << "<div id='toc'>\n";
803         this.renderTOC(stream, body);
804         stream << "</div>";
805         this.renderChildren(stream, body);
806         this.renderFootNotes(stream);
807         this.renderFooter(stream, doc);
808         currDoc = nil;
809     }
810     
811     *renderToFile {|filename, doc, root|
812         var stream;
813         File.mkdir(filename.dirname);
814         stream = File(filename, "w");
815         if(stream.isOpen) {
816             this.renderOnStream(stream, doc, root);
817             stream.close;
818         } {
819             warn("SCDoc: Could not open file % for writing".format(filename));
820         }
821     }