1
2
3
4
5
6
7
8
9 """
10 Functions to produce colorized HTML code for various objects.
11 Currently, C{html_colorize} defines functions to colorize
12 Python source code.
13 """
14 __docformat__ = 'epytext en'
15
16 import re, codecs
17 from epydoc import log
18 from epydoc.util import py_src_filename
19 from epydoc.apidoc import *
20 import tokenize, token, cgi, keyword
21 try: from cStringIO import StringIO
22 except: from StringIO import StringIO
23
24
25
26
27 """
28 Goals:
29 - colorize tokens appropriately (using css)
30 - optionally add line numbers
31 -
32 """
33
34
35 PYSRC_JAVASCRIPTS = '''\
36 function expand(id) {
37 var elt = document.getElementById(id+"-expanded");
38 if (elt) elt.style.display = "block";
39 var elt = document.getElementById(id+"-expanded-linenums");
40 if (elt) elt.style.display = "block";
41 var elt = document.getElementById(id+"-collapsed");
42 if (elt) { elt.innerHTML = ""; elt.style.display = "none"; }
43 var elt = document.getElementById(id+"-collapsed-linenums");
44 if (elt) { elt.innerHTML = ""; elt.style.display = "none"; }
45 var elt = document.getElementById(id+"-toggle");
46 if (elt) { elt.innerHTML = "-"; }
47 }
48
49 function collapse(id) {
50 var elt = document.getElementById(id+"-expanded");
51 if (elt) elt.style.display = "none";
52 var elt = document.getElementById(id+"-expanded-linenums");
53 if (elt) elt.style.display = "none";
54 var elt = document.getElementById(id+"-collapsed-linenums");
55 if (elt) { elt.innerHTML = "<br/>"; elt.style.display="block"; }
56 var elt = document.getElementById(id+"-toggle");
57 if (elt) { elt.innerHTML = "+"; }
58 var elt = document.getElementById(id+"-collapsed");
59 if (elt) {
60 elt.style.display = "block";
61
62 var indent = elt.getAttribute("indent");
63 var pad = elt.getAttribute("pad");
64 var s = "<tt class=\'py-lineno\'>";
65 for (var i=0; i<pad.length; i++) { s += " " }
66 s += "</tt>";
67 s += " <tt class=\'py-line\'>";
68 for (var i=0; i<indent.length; i++) { s += " " }
69 s += "<a href=\'#\' onclick=\'expand(\\"" + id;
70 s += "\\");return false\'>...</a></tt><br />";
71 elt.innerHTML = s;
72 }
73 }
74
75 function toggle(id) {
76 elt = document.getElementById(id+"-toggle");
77 if (elt.innerHTML == "-")
78 collapse(id);
79 else
80 expand(id);
81 return false;
82 }
83
84 function highlight(id) {
85 var elt = document.getElementById(id+"-def");
86 if (elt) elt.className = "py-highlight-hdr";
87 var elt = document.getElementById(id+"-expanded");
88 if (elt) elt.className = "py-highlight";
89 var elt = document.getElementById(id+"-collapsed");
90 if (elt) elt.className = "py-highlight";
91 }
92
93 function num_lines(s) {
94 var n = 1;
95 var pos = s.indexOf("\\n");
96 while ( pos > 0) {
97 n += 1;
98 pos = s.indexOf("\\n", pos+1);
99 }
100 return n;
101 }
102
103 // Collapse all blocks that mave more than `min_lines` lines.
104 function collapse_all(min_lines) {
105 var elts = document.getElementsByTagName("div");
106 for (var i=0; i<elts.length; i++) {
107 var elt = elts[i];
108 var split = elt.id.indexOf("-");
109 if (split > 0)
110 if (elt.id.substring(split, elt.id.length) == "-expanded")
111 if (num_lines(elt.innerHTML) > min_lines)
112 collapse(elt.id.substring(0, split));
113 }
114 }
115
116 function expandto(href) {
117 var start = href.indexOf("#")+1;
118 if (start != 0 && start != href.length) {
119 if (href.substring(start, href.length) != "-") {
120 collapse_all(4);
121 pos = href.indexOf(".", start);
122 while (pos != -1) {
123 var id = href.substring(start, pos);
124 expand(id);
125 pos = href.indexOf(".", pos+1);
126 }
127 var id = href.substring(start, href.length);
128 expand(id);
129 highlight(id);
130 }
131 }
132 }
133
134 function kill_doclink(id) {
135 var parent = document.getElementById(id);
136 parent.removeChild(parent.childNodes.item(0));
137 }
138 function auto_kill_doclink(ev) {
139 if (!ev) var ev = window.event;
140 if (!this.contains(ev.toElement)) {
141 var parent = document.getElementById(this.parentID);
142 parent.removeChild(parent.childNodes.item(0));
143 }
144 }
145
146 function doclink(id, name, targets_id) {
147 var elt = document.getElementById(id);
148
149 // If we already opened the box, then destroy it.
150 // (This case should never occur, but leave it in just in case.)
151 if (elt.childNodes.length > 1) {
152 elt.removeChild(elt.childNodes.item(0));
153 }
154 else {
155 // The outer box: relative + inline positioning.
156 var box1 = document.createElement("div");
157 box1.style.position = "relative";
158 box1.style.display = "inline";
159 box1.style.top = 0;
160 box1.style.left = 0;
161
162 // A shadow for fun
163 var shadow = document.createElement("div");
164 shadow.style.position = "absolute";
165 shadow.style.left = "-1.3em";
166 shadow.style.top = "-1.3em";
167 shadow.style.background = "#404040";
168
169 // The inner box: absolute positioning.
170 var box2 = document.createElement("div");
171 box2.style.position = "relative";
172 box2.style.border = "1px solid #a0a0a0";
173 box2.style.left = "-.2em";
174 box2.style.top = "-.2em";
175 box2.style.background = "white";
176 box2.style.padding = ".3em .4em .3em .4em";
177 box2.style.fontStyle = "normal";
178 box2.onmouseout=auto_kill_doclink;
179 box2.parentID = id;
180
181 // Get the targets
182 var targets_elt = document.getElementById(targets_id);
183 var targets = targets_elt.getAttribute("targets");
184 var links = "";
185 target_list = targets.split(",");
186 for (var i=0; i<target_list.length; i++) {
187 var target = target_list[i].split("=");
188 links += "<li><a href=\'" + target[1] +
189 "\' style=\'text-decoration:none\'>" +
190 target[0] + "</a></li>";
191 }
192
193 // Put it all together.
194 elt.insertBefore(box1, elt.childNodes.item(0));
195 //box1.appendChild(box2);
196 box1.appendChild(shadow);
197 shadow.appendChild(box2);
198 box2.innerHTML =
199 "Which <b>"+name+"</b> do you want to see documentation for?" +
200 "<ul style=\'margin-bottom: 0;\'>" +
201 links +
202 "<li><a href=\'#\' style=\'text-decoration:none\' " +
203 "onclick=\'kill_doclink(\\""+id+"\\");return false;\'>"+
204 "<i>None of the above</i></a></li></ul>";
205 }
206 return false;
207 }
208 '''
209
210 PYSRC_EXPANDTO_JAVASCRIPT = '''\
211 <script type="text/javascript">
212 <!--
213 expandto(location.href);
214 // -->
215 </script>
216 '''
217
219 """
220 A class that renders a python module's source code into HTML
221 pages. These HTML pages are intended to be provided along with
222 the API documentation for a module, in case a user wants to learn
223 more about a particular object by examining its source code.
224 Links are therefore generated from the API documentation to the
225 source code pages, and from the source code pages back into the
226 API documentation.
227
228 The HTML generated by C{PythonSourceColorizer} has several notable
229 features:
230
231 - CSS styles are used to color tokens according to their type.
232 (See L{CSS_CLASSES} for a list of the different token types
233 that are identified).
234
235 - Line numbers are included to the left of each line.
236
237 - The first line of each class and function definition includes
238 a link to the API source documentation for that object.
239
240 - The first line of each class and function definition includes
241 an anchor that can be used to link directly to that class or
242 function.
243
244 - If javascript is enabled, and the page is loaded using the
245 anchor for a class or function (i.e., if the url ends in
246 C{'#I{<name>}'}), then that class or function will automatically
247 be highlighted; and all other classes and function definition
248 blocks will be 'collapsed'. These collapsed blocks can be
249 expanded by clicking on them.
250
251 - Unicode input is supported (including automatic detection
252 of C{'coding:'} declarations).
253
254 """
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270 CSS_CLASSES = {
271 'NUMBER': 'py-number',
272 'STRING': 'py-string',
273 'COMMENT': 'py-comment',
274 'NAME': 'py-name',
275 'KEYWORD': 'py-keyword',
276 'DEFNAME': 'py-def-name',
277 'BASECLASS': 'py-base-class',
278 'PARAM': 'py-param',
279 'DOCSTRING': 'py-docstring',
280 'DECORATOR': 'py-decorator',
281 'OP': 'py-op',
282 '@': 'py-decorator',
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296 START_DEF_BLOCK = (
297 '<div id="%s-collapsed" style="display:none;" '
298 'pad="%s" indent="%s"></div>'
299 '<div id="%s-expanded">')
300
301
302
303 END_DEF_BLOCK = '</div>'
304
305
306
307 UNICODE_CODING_RE = re.compile(r'.*?\n?.*?coding[:=]\s*([-\w.]+)')
308
309
310
311 ADD_DEF_BLOCKS = True
312
313
314
315 ADD_LINE_NUMBERS = True
316
317
318
319 ADD_TOOLTIPS = True
320
321
322
323
324 GUESS_LINK_TARGETS = False
325
326 - def __init__(self, module_filename, module_name,
327 docindex=None, url_func=None, name_to_docs=None):
328 """
329 Create a new HTML colorizer for the specified module.
330
331 @param module_filename: The name of the file containing the
332 module; its text will be loaded from this file.
333 @param module_name: The dotted name of the module; this will
334 be used to create links back into the API source
335 documentation.
336 """
337
338 try: module_filename = py_src_filename(module_filename)
339 except: pass
340
341
342 self.module_filename = module_filename
343
344
345 self.module_name = module_name
346
347
348
349 self.docindex = docindex
350
351
352
353
354 self.name_to_docs = name_to_docs
355
356
357
358
359 self.url_func = url_func
360
361
362
363 self.pos = 0
364
365
366
367
368
369 self.line_offsets = []
370
371
372
373
374
375 self.cur_line = []
376
377
378
379
380
381
382
383 self.context = []
384
385
386
387
388 self.context_types = []
389
390
391
392
393 self.indents = []
394
395
396 self.lineno = 0
397
398
399
400
401 self.def_name = None
402
403
404
405
406
407 self.def_type = None
408
409
411 """
412 Construct the L{line_offsets} table from C{self.text}.
413 """
414
415 self.line_offsets = [None, 0]
416
417
418 pos = self.text.find('\n')
419 while pos != -1:
420 self.line_offsets.append(pos+1)
421 pos = self.text.find('\n', pos+1)
422
423 self.line_offsets.append(len(self.text))
424
426 template = '%%%ds' % self.linenum_size
427 n = template % self.lineno
428 return '<a name="L%s"></a><tt class="py-lineno">%s</tt>' \
429 % (self.lineno, n)
430
432 """
433 Return an HTML string that renders the source code for the
434 module that was specified in the constructor.
435 """
436
437 self.pos = 0
438 self.cur_line = []
439 self.context = []
440 self.context_types = []
441 self.indents = []
442 self.lineno = 1
443 self.def_name = None
444 self.def_type = None
445
446
447
448 self.doclink_targets_cache = {}
449
450
451 self.text = open(self.module_filename).read()
452 self.text = self.text.expandtabs().rstrip()+'\n'
453
454
455 self.find_line_offsets()
456
457 num_lines = self.text.count('\n')+1
458 self.linenum_size = len(`num_lines+1`)
459
460
461
462
463 try:
464 output = StringIO()
465 self.out = output.write
466 tokenize.tokenize(StringIO(self.text).readline, self.tokeneater)
467 html = output.getvalue()
468 except tokenize.TokenError, ex:
469 html = self.text
470
471
472 m = self.UNICODE_CODING_RE.match(self.text)
473 if m: coding = m.group(1)
474 else: coding = 'iso-8859-1'
475
476
477
478
479 try:
480 html = html.decode(coding).encode('ascii', 'xmlcharrefreplace')
481 except LookupError:
482 coding = 'iso-8859-1'
483 html = html.decode(coding).encode('ascii', 'xmlcharrefreplace')
484
485
486 html += PYSRC_EXPANDTO_JAVASCRIPT
487
488 return html
489
490 - def tokeneater(self, toktype, toktext, (srow,scol), (erow,ecol), line):
491 """
492 A callback function used by C{tokenize.tokenize} to handle
493 each token in the module. C{tokeneater} collects tokens into
494 the C{self.cur_line} list until a complete logical line has
495 been formed; and then calls L{handle_line} to process that line.
496 """
497
498 if toktype == token.ERRORTOKEN:
499 raise tokenize.TokenError, toktype
500
501
502
503
504
505 startpos = self.line_offsets[srow] + scol
506 if startpos > self.pos:
507 skipped = self.text[self.pos:startpos]
508 self.cur_line.append( (None, skipped) )
509
510
511 self.pos = startpos + len(toktext)
512
513
514 self.cur_line.append( (toktype, toktext) )
515
516
517 if toktype == token.NEWLINE or toktype == token.ENDMARKER:
518 self.handle_line(self.cur_line)
519 self.cur_line = []
520
521 _next_uid = 0
522
523
524
525
527 """
528 Render a single logical line from the module, and write the
529 generated HTML to C{self.out}.
530
531 @param line: A single logical line, encoded as a list of
532 C{(toktype,tokttext)} pairs corresponding to the tokens in
533 the line.
534 """
535
536
537 def_name = None
538
539
540
541 def_type = None
542
543
544 starting_def_block = False
545
546 in_base_list = False
547 in_param_list = False
548 in_param_default = 0
549 at_module_top = (self.lineno == 1)
550
551 ended_def_blocks = 0
552
553
554 if self.ADD_LINE_NUMBERS:
555 s = self.lineno_to_html()
556 self.lineno += 1
557 else:
558 s = ''
559 s += ' <tt class="py-line">'
560
561
562 for i, (toktype, toktext) in enumerate(line):
563 if type(s) is not str:
564 if type(s) is unicode:
565 log.error('While colorizing %s -- got unexpected '
566 'unicode string' % self.module_name)
567 s = s.encode('ascii', 'xmlcharrefreplace')
568 else:
569 raise ValueError('Unexpected value for s -- %s' %
570 type(s).__name__)
571
572
573
574 css_class = None
575 url = None
576 tooltip = None
577 onclick = uid = targets = None
578
579
580
581 if i>=2 and line[i-2][1] == 'class':
582 in_base_list = True
583 css_class = self.CSS_CLASSES['DEFNAME']
584 def_name = toktext
585 def_type = 'class'
586 if 'func' not in self.context_types:
587 cls_name = self.context_name(def_name)
588 url = self.name2url(cls_name)
589 s = self.mark_def(s, cls_name)
590 starting_def_block = True
591
592
593
594 elif i>=2 and line[i-2][1] == 'def':
595 in_param_list = True
596 css_class = self.CSS_CLASSES['DEFNAME']
597 def_name = toktext
598 def_type = 'func'
599 if 'func' not in self.context_types:
600 cls_name = self.context_name()
601 func_name = self.context_name(def_name)
602 url = self.name2url(cls_name, def_name)
603 s = self.mark_def(s, func_name)
604 starting_def_block = True
605
606
607
608
609
610
611 elif toktype == token.INDENT:
612 self.indents.append(toktext)
613 self.context.append(self.def_name)
614 self.context_types.append(self.def_type)
615
616
617
618
619
620 elif toktype == token.DEDENT:
621 self.indents.pop()
622 self.context_types.pop()
623 if self.context.pop():
624 ended_def_blocks += 1
625
626
627
628 elif toktype in (None, tokenize.NL, token.NEWLINE,
629 token.ENDMARKER):
630 css_class = None
631
632
633 elif toktype == token.NAME and keyword.iskeyword(toktext):
634 css_class = self.CSS_CLASSES['KEYWORD']
635
636 elif in_base_list and toktype == token.NAME:
637 css_class = self.CSS_CLASSES['BASECLASS']
638
639 elif (in_param_list and toktype == token.NAME and
640 not in_param_default):
641 css_class = self.CSS_CLASSES['PARAM']
642
643
644 elif (self.def_name and line[i-1][0] == token.INDENT and
645 self.is_docstring(line, i)):
646 css_class = self.CSS_CLASSES['DOCSTRING']
647
648
649 elif at_module_top and self.is_docstring(line, i):
650 css_class = self.CSS_CLASSES['DOCSTRING']
651
652
653 elif (toktype == token.NAME and
654 ((i>0 and line[i-1][1]=='@') or
655 (i>1 and line[i-1][0]==None and line[i-2][1] == '@'))):
656 css_class = self.CSS_CLASSES['DECORATOR']
657
658
659 elif toktype == token.NAME:
660 css_class = self.CSS_CLASSES['NAME']
661
662
663
664
665
666 if (self.GUESS_LINK_TARGETS and self.docindex is not None
667 and self.url_func is not None):
668 context = [n for n in self.context if n is not None]
669 container = self.docindex.get_vardoc(
670 DottedName(self.module_name, *context))
671 if isinstance(container, NamespaceDoc):
672 doc = container.variables.get(toktext)
673 if doc is not None:
674 url = self.url_func(doc)
675 tooltip = str(doc.canonical_name)
676
677
678 if (url is None and self.name_to_docs is not None
679 and self.url_func is not None):
680 docs = self.name_to_docs.get(toktext)
681 if docs:
682 tooltip='\n'.join([str(d.canonical_name)
683 for d in docs])
684 if len(docs) == 1 and self.GUESS_LINK_TARGETS:
685 url = self.url_func(docs[0])
686 else:
687 uid, onclick, targets = self.doclink(toktext, docs)
688
689
690
691 else:
692 if toktype == token.OP and toktext in self.CSS_CLASSES:
693 css_class = self.CSS_CLASSES[toktext]
694 elif token.tok_name[toktype] in self.CSS_CLASSES:
695 css_class = self.CSS_CLASSES[token.tok_name[toktype]]
696 else:
697 css_class = None
698
699
700 if toktext == ':':
701 in_base_list = False
702 in_param_list = False
703 if toktext == '=' and in_param_list:
704 in_param_default = True
705 if in_param_default:
706 if toktext in ('(','[','{'): in_param_default += 1
707 if toktext in (')',']','}'): in_param_default -= 1
708 if toktext == ',' and in_param_default == 1:
709 in_param_default = 0
710
711
712 if tooltip and self.ADD_TOOLTIPS:
713 tooltip_html = ' title="%s"' % tooltip
714 else: tooltip_html = ''
715 if css_class: css_class_html = ' class="%s"' % css_class
716 else: css_class_html = ''
717 if onclick:
718 if targets: targets_html = ' targets="%s"' % targets
719 else: targets_html = ''
720 s += ('<tt id="%s"%s%s><a%s%s href="#" onclick="%s">' %
721 (uid, css_class_html, targets_html, tooltip_html,
722 css_class_html, onclick))
723 elif url:
724 if isinstance(url, unicode):
725 url = url.encode('ascii', 'xmlcharrefreplace')
726 s += ('<a%s%s href="%s">' %
727 (tooltip_html, css_class_html, url))
728 elif css_class_html or tooltip_html:
729 s += '<tt%s%s>' % (tooltip_html, css_class_html)
730 if i == len(line)-1:
731 s += ' </tt>'
732 s += cgi.escape(toktext)
733 else:
734 try:
735 s += self.add_line_numbers(cgi.escape(toktext), css_class)
736 except Exception, e:
737 print (toktext, css_class, toktext.encode('ascii'))
738 raise
739
740 if onclick: s += "</a></tt>"
741 elif url: s += '</a>'
742 elif css_class_html or tooltip_html: s += '</tt>'
743
744 if self.ADD_DEF_BLOCKS:
745 for i in range(ended_def_blocks):
746 self.out(self.END_DEF_BLOCK)
747
748
749 s = re.sub(r'<tt class="[\w+]"></tt>', '', s)
750
751
752 self.out(s)
753
754 if def_name and starting_def_block:
755 self.out('</div>')
756
757
758 if (self.ADD_DEF_BLOCKS and def_name and starting_def_block and
759 (line[-2][1] == ':')):
760 indentation = (''.join(self.indents)+' ').replace(' ', '+')
761 linenum_padding = '+'*self.linenum_size
762 name=self.context_name(def_name)
763 self.out(self.START_DEF_BLOCK % (name, linenum_padding,
764 indentation, name))
765
766 self.def_name = def_name
767 self.def_type = def_type
768
769 - def context_name(self, extra=None):
770 pieces = [n for n in self.context if n is not None]
771 if extra is not None: pieces.append(extra)
772 return '.'.join(pieces)
773
775 uid = 'link-%s' % self._next_uid
776 self._next_uid += 1
777 context = [n for n in self.context if n is not None]
778 container = DottedName(self.module_name, *context)
779
780
781 targets = ','.join(['%s=%s' % (str(self.doc_descr(d,container)),
782 str(self.url_func(d)))
783 for d in docs])
784
785 if targets in self.doclink_targets_cache:
786 onclick = ("return doclink('%s', '%s', '%s');" %
787 (uid, name, self.doclink_targets_cache[targets]))
788 return uid, onclick, None
789 else:
790 self.doclink_targets_cache[targets] = uid
791 onclick = ("return doclink('%s', '%s', '%s');" %
792 (uid, name, uid))
793 return uid, onclick, targets
794
801
802
803
805 if isinstance(doc, ModuleDoc) and doc.is_package == True:
806 return 'Package'
807 elif (isinstance(doc, ModuleDoc) and
808 doc.canonical_name[0].startswith('script')):
809 return 'Script'
810 elif isinstance(doc, ModuleDoc):
811 return 'Module'
812 elif isinstance(doc, ClassDoc):
813 return 'Class'
814 elif isinstance(doc, ClassMethodDoc):
815 return 'Class Method'
816 elif isinstance(doc, StaticMethodDoc):
817 return 'Static Method'
818 elif isinstance(doc, RoutineDoc):
819 if (self.docindex is not None and
820 isinstance(self.docindex.container(doc), ClassDoc)):
821 return 'Method'
822 else:
823 return 'Function'
824 else:
825 return 'Variable'
826
828 replacement = ('<a name="%s"></a><div id="%s-def">\\1'
829 '<a class="py-toggle" href="#" id="%s-toggle" '
830 'onclick="return toggle(\'%s\');">-</a>\\2' %
831 (name, name, name, name))
832 return re.sub('(.*) (<tt class="py-line">.*)\Z', replacement, s)
833
835 if line[i][0] != token.STRING: return False
836 for toktype, toktext in line[i:]:
837 if toktype not in (token.NEWLINE, tokenize.COMMENT,
838 tokenize.NL, token.STRING, None):
839 return False
840 return True
841
843 result = ''
844 start = 0
845 end = s.find('\n')+1
846 while end:
847 result += s[start:end-1]
848 if css_class: result += '</tt>'
849 result += ' </tt>'
850 result += '\n'
851 if self.ADD_LINE_NUMBERS:
852 result += self.lineno_to_html()
853 result += ' <tt class="py-line">'
854 if css_class: result += '<tt class="%s">' % css_class
855 start = end
856 end = s.find('\n', end)+1
857 self.lineno += 1
858 result += s[start:]
859 return result
860
861 - def name2url(self, class_name, func_name=None):
862 if class_name:
863 class_name = '%s.%s' % (self.module_name, class_name)
864 if func_name:
865 return '%s-class.html#%s' % (class_name, func_name)
866 else:
867 return '%s-class.html' % class_name
868 else:
869 return '%s-module.html#%s' % (self.module_name, func_name)
870
871 _HDR = '''\
872 <?xml version="1.0" encoding="ascii"?>
873 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
874 "DTD/xhtml1-transitional.dtd">
875 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
876 <head>
877 <title>$title$</title>
878 <link rel="stylesheet" href="epydoc.css" type="text/css" />
879 <script type="text/javascript" src="epydoc.js"></script>
880 </head>
881
882 <body bgcolor="white" text="black" link="blue" vlink="#204080"
883 alink="#204080">
884 '''
885 _FOOT = '</body></html>'
886 if __name__=='__main__':
887
888 s = PythonSourceColorizer('/tmp/fo.py', 'epydoc.apidoc').colorize()
889
890 import codecs
891 f = codecs.open('/home/edloper/public_html/color3.html', 'w', 'ascii', 'xmlcharrefreplace')
892 f.write(_HDR+'<pre id="py-src-top" class="py-src">'+s+'</pre>'+_FOOT)
893 f.close()
894