bloattable.pl 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. #!/usr/bin/perl -w
  2. #
  3. # This Source Code Form is subject to the terms of the Mozilla Public
  4. # License, v. 2.0. If a copy of the MPL was not distributed with this
  5. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. # bloattable [-debug] [-source] [-byte n|-obj n|-ref n] <file1> <file2> ... <filen> > <html-file>
  7. #
  8. # file1, file2, ... filen should be successive BloatView files generated from the same run.
  9. # Summarize them in an HTML table. Output the HTML to the standard output.
  10. #
  11. # If -debug is set, create a slightly larger html file which is more suitable for debugging this script.
  12. # If -source is set, create an html file that prints the html source as the output
  13. # If -byte n, -obj n, or -ref n is given, make the page default to showing byte, object, or reference statistics,
  14. # respectively, and sort by the nth column (n is zero-based, so the first column has n==0).
  15. #
  16. # See http://lxr.mozilla.org/mozilla/source/xpcom/doc/MemoryTools.html
  17. use 5.004;
  18. use strict;
  19. use diagnostics;
  20. use File::Basename;
  21. use Getopt::Long;
  22. # The generated HTML is almost entirely generated by a script. Only the <HTML>, <HEAD>, and <BODY> elements are explicit
  23. # because a <SCRIPT> element cannot officially be a direct descendant of an <HTML> element.
  24. # The script itself is almost all generated by an eval of a large string. This allows the script to reproduce itself
  25. # when making a new page using document.write's. Re-sorting the page causes it to regenerate itself in this way.
  26. # Return the file's modification date.
  27. sub fileModDate($) {
  28. my ($pathName) = @_;
  29. my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) =
  30. stat $pathName or die "Can't stat '$pathName'";
  31. return $mtime;
  32. }
  33. sub fileCoreName($) {
  34. my ($pathName) = @_;
  35. my $fileName = basename($pathName, "");
  36. $fileName =~ s/\..*//;
  37. return $fileName;
  38. }
  39. # Convert a raw string into a single-quoted JavaScript string.
  40. sub singleQuoteString($) {
  41. local ($_) = @_;
  42. s/\\/\\\\/g;
  43. s/'/\\'/g;
  44. s/\n/\\n/g;
  45. s/<\//<\\\//g;
  46. return "'$_'";
  47. }
  48. # Convert a raw string into a double-quoted JavaScript string.
  49. sub doubleQuoteString($) {
  50. local ($_) = @_;
  51. s/\\/\\\\/g;
  52. s/"/\\"/g;
  53. s/\n/\\n/g;
  54. s/<\//<\\\//g;
  55. return "\"$_\"";
  56. }
  57. # Quote special HTML characters in the string.
  58. sub quoteHTML($) {
  59. local ($_) = @_;
  60. s/\&/&amp;/g;
  61. s/</&lt;/g;
  62. s/>/&gt;/g;
  63. s/ /&nbsp;/g;
  64. s/\n/<BR>\n/g;
  65. return $_;
  66. }
  67. # Write the generated page to the standard output.
  68. # The script source code is read from this file past the __END__ marker
  69. # @$scriptData is the JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
  70. # @$persistentScriptData is the same as @scriptData, but persists when the page reloads itself.
  71. # If $debug is true, generate the script directly instead of having it eval itself.
  72. # If $source is true, generate a script that displays the page's source instead of the page itself.
  73. sub generate(\@\@$$$$) {
  74. my ($scriptData, $persistentScriptData, $debug, $source, $showMode, $sortColumn) = @_;
  75. my @scriptSource = <DATA>;
  76. chomp @scriptSource;
  77. print <<'EOS';
  78. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
  79. <HTML>
  80. <HEAD>
  81. <SCRIPT type="text/javascript">
  82. EOS
  83. foreach (@$scriptData) {print "$_\n";}
  84. print "\n";
  85. print "var srcArray = [\n";
  86. my @quotedScriptSource = map {
  87. my $line = $_;
  88. $line =~ s/^\s+//g;
  89. # $line =~ s/^\/\/SOURCE\s+//g if $source;
  90. $line =~ s/^\/\/.*//g;
  91. $line =~ s/\s+$//g;
  92. $line eq "" ? () : $line
  93. } @$persistentScriptData, @scriptSource;
  94. my $lastQuotedLine = pop @quotedScriptSource;
  95. foreach (@quotedScriptSource) {print doubleQuoteString($_), ",\n";}
  96. print doubleQuoteString($lastQuotedLine), "];\n\n";
  97. if ($debug) {
  98. push @quotedScriptSource, $lastQuotedLine;
  99. foreach (@quotedScriptSource) {
  100. s/<\//<\\\//g; # This fails if a regexp ends with a '<'. Oh well....
  101. print "$_\n";
  102. }
  103. print "\n";
  104. } else {
  105. print "eval(srcArray.join(\"\\n\"));\n\n";
  106. }
  107. print "showMode = $showMode;\n";
  108. print "sortColumn = $sortColumn;\n";
  109. if ($source) {
  110. print <<'EOS';
  111. function writeQuotedHTML(s) {
  112. document.write(quoteHTML(s.toString()).replace(/\n/g, '<BR>\n'));
  113. }
  114. var quotingDocument = {
  115. write: function () {
  116. for (var i = 0; i < arguments.length; i++)
  117. writeQuotedHTML(arguments[i]);
  118. },
  119. writeln: function () {
  120. for (var i = 0; i < arguments.length; i++)
  121. writeQuotedHTML(arguments[i]);
  122. document.writeln('<BR>');
  123. }
  124. };
  125. EOS
  126. } else {
  127. print "showHead(document);\n";
  128. }
  129. print "</SCRIPT>\n";
  130. print "</HEAD>\n\n";
  131. print "<BODY>\n";
  132. if ($source) {
  133. print "<P><TT>";
  134. print quoteHTML "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n";
  135. print quoteHTML "<HTML>\n";
  136. print quoteHTML "<HEAD>\n";
  137. print "<SCRIPT type=\"text/javascript\">showHead(quotingDocument);</SCRIPT>\n";
  138. print quoteHTML "</HEAD>\n\n";
  139. print quoteHTML "<BODY>\n";
  140. print "<SCRIPT type=\"text/javascript\">showBody(quotingDocument);</SCRIPT>\n";
  141. print quoteHTML "</BODY>\n";
  142. print quoteHTML "</HTML>\n";
  143. print "</TT></P>\n";
  144. } else {
  145. print "<SCRIPT type=\"text/javascript\">showBody(document);</SCRIPT>\n";
  146. }
  147. print "</BODY>\n";
  148. print "</HTML>\n";
  149. }
  150. # Read the bloat file into hash table $h. The hash table is indexed by class names;
  151. # each entry is a list with the following elements:
  152. # bytesAlloc Total number of bytes allocated
  153. # bytesNet Total number of bytes allocated but not deallocated
  154. # objectsAlloc Total number of objects allocated
  155. # objectsNet Total number of objects allocated but not deallocated
  156. # refsAlloc Total number of references AddRef'd
  157. # refsNet Total number of references AddRef'd but not Released
  158. # Except for TOTAL, all hash table entries refer to mutually exclusive data.
  159. # $sizes is a hash table indexed by class names. Each entry of that table contains the class's instance size.
  160. sub readBloatFile($\%\%) {
  161. my ($file, $h, $sizes) = @_;
  162. local $_; # Needed for 'while (<FILE>)' below.
  163. my $readSomething = 0;
  164. open FILE, $file;
  165. while (<FILE>) {
  166. if (my ($name, $size, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet) =
  167. /^\s*(?:\d+)\s+([\w:]+)\s+(\d+)\s+(-?\d+)\s+(\d+)\s+(-?\d+)\s*\([^()]*\)\s*(\d+)\s+(-?\d+)\s*\([^()]*\)\s*$/) {
  168. my $bytesAlloc;
  169. if ($name eq "TOTAL") {
  170. $size = "undefined";
  171. $bytesAlloc = "undefined";
  172. } else {
  173. $bytesAlloc = $objectsAlloc * $size;
  174. if ($bytesNet != $objectsNet * $size) {
  175. print STDERR "In '$file', class $name bytesNet != objectsNet * size: $bytesNet != $objectsNet * $size\n";
  176. }
  177. }
  178. print STDERR "Duplicate entry $name in '$file'\n" if $$h{$name};
  179. $$h{$name} = [$bytesAlloc, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet];
  180. my $oldSize = $$sizes{$name};
  181. print STDERR "Mismatch of sizes of class $name: $oldSize and $size\n" if defined($oldSize) && $size ne $oldSize;
  182. $$sizes{$name} = $size;
  183. $readSomething = 1;
  184. } elsif (/^\s*(?:\d+)\s+([\w:]+)\s/) {
  185. print STDERR "Unable to parse '$file' line: $_";
  186. }
  187. }
  188. close FILE;
  189. print STDERR "No data in '$file'\n" unless $readSomething;
  190. return $h;
  191. }
  192. my %sizes; # <class-name> => <instance-size>
  193. my %tables; # <file-name> => <bloat-table>; see readBloatFile for format of <bloat-table>
  194. # Generate the JavaScript source code for the row named $c. $l can contain the initial entries of the row.
  195. sub genTableRowSource($$) {
  196. my ($l, $c) = @_;
  197. my $lastE;
  198. foreach (@ARGV) {
  199. my $e = $tables{$_}{$c};
  200. if (defined($lastE) && !defined($e)) {
  201. $e = [0,0,0,0,0,0];
  202. print STDERR "Class $c is defined in an earlier file but not in '$_'\n";
  203. }
  204. if (defined $e) {
  205. if (defined $lastE) {
  206. for (my $i = 0; $i <= $#$e; $i++) {
  207. my $n = $$e[$i];
  208. $l .= ($n eq "undefined" ? "undefined" : $n - $$lastE[$i]) . ",";
  209. }
  210. $l .= " ";
  211. } else {
  212. $l .= join(",", @$e) . ", ";
  213. }
  214. $lastE = $e;
  215. } else {
  216. $l .= "0,0,0,0,0,0, ";
  217. }
  218. }
  219. $l .= join(",", @$lastE);
  220. return "[$l]";
  221. }
  222. my $debug;
  223. my $source;
  224. my $showMode;
  225. my $sortColumn;
  226. my @modeOptions;
  227. GetOptions("debug" => \$debug, "source" => \$source, "byte=i" => \$modeOptions[0], "obj=i" => \$modeOptions[1], "ref=i" => \$modeOptions[2]);
  228. for (my $i = 0; $i != 3; $i++) {
  229. my $modeOption = $modeOptions[$i];
  230. if ($modeOption) {
  231. die "Only one of -byte, -obj, or -ref may be given" if defined $showMode;
  232. my $nFileColumns = scalar(@ARGV) + 1;
  233. die "-byte, -obj, or -ref column number out of range" if $modeOption < 0 || $modeOption >= 2 + 2*$nFileColumns;
  234. $showMode = $i;
  235. if ($modeOption >= 2) {
  236. $modeOption -= 2;
  237. $sortColumn = 2 + $showMode*2;
  238. if ($modeOption >= $nFileColumns) {
  239. $sortColumn++;
  240. $modeOption -= $nFileColumns;
  241. }
  242. $sortColumn += $modeOption*6;
  243. } else {
  244. $sortColumn = $modeOption;
  245. }
  246. }
  247. }
  248. unless (defined $showMode) {
  249. $showMode = 0;
  250. $sortColumn = 0;
  251. }
  252. # Read all of the bloat files.
  253. foreach (@ARGV) {
  254. unless ($tables{$_}) {
  255. my $f = $_;
  256. my %table;
  257. readBloatFile $_, %table, %sizes;
  258. $tables{$_} = \%table;
  259. }
  260. }
  261. die "No input" unless %sizes;
  262. my @scriptData; # JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
  263. my @persistentScriptData; # Same as @scriptData, but persists the page reloads itself.
  264. # Print a list of bloat file names.
  265. push @persistentScriptData, "var nFiles = " . scalar(@ARGV) . ";";
  266. push @persistentScriptData, "var fileTags = [" . join(", ", map {singleQuoteString substr(fileCoreName($_), -10)} @ARGV) . "];";
  267. push @persistentScriptData, "var fileNames = [" . join(", ", map {singleQuoteString $_} @ARGV) . "];";
  268. push @persistentScriptData, "var fileDates = [" . join(", ", map {singleQuoteString localtime fileModDate $_} @ARGV) . "];";
  269. # Print the bloat tables.
  270. push @persistentScriptData, "var totals = " . genTableRowSource('"TOTAL", undefined, ', "TOTAL") . ";";
  271. push @scriptData, "var classTables = [";
  272. delete $sizes{"TOTAL"};
  273. my @classes = sort(keys %sizes);
  274. for (my $i = 0; $i <= $#classes; $i++) {
  275. my $c = $classes[$i];
  276. push @scriptData, genTableRowSource(doubleQuoteString($c).", ".$sizes{$c}.", ", $c) . ($i == $#classes ? "];" : ",");
  277. }
  278. generate(@scriptData, @persistentScriptData, $debug, $source, $showMode, $sortColumn);
  279. 1;
  280. # The source of the eval'd JavaScript follows.
  281. # Comments starting with // that are alone on a line are stripped by the Perl script.
  282. __END__
  283. // showMode: 0=bytes, 1=objects, 2=references
  284. var showMode;
  285. var modeName;
  286. var modeNameUpper;
  287. var sortColumn;
  288. // Sort according to the sortColumn. Column 0 is sorted alphabetically in ascending order.
  289. // All other columns are sorted numerically in descending order, with column 0 used for a secondary sort.
  290. // Undefined is always listed last.
  291. function sortCompare(x, y) {
  292. if (sortColumn) {
  293. var xc = x[sortColumn];
  294. var yc = y[sortColumn];
  295. if (xc < yc || xc === undefined && yc !== undefined) return 1;
  296. if (yc < xc || yc === undefined && xc !== undefined) return -1;
  297. }
  298. var x0 = x[0];
  299. var y0 = y[0];
  300. if (x0 > y0 || x0 === undefined && y0 !== undefined) return 1;
  301. if (y0 > x0 || y0 === undefined && x0 !== undefined) return -1;
  302. return 0;
  303. }
  304. // Quote special HTML characters in the string.
  305. function quoteHTML(s) {
  306. s = s.replace(/&/g, '&amp;');
  307. // Can't use /</g because HTML interprets '</g' as ending the script!
  308. s = s.replace(/\x3C/g, '&lt;');
  309. s = s.replace(/>/g, '&gt;');
  310. s = s.replace(/ /g, '&nbsp;');
  311. return s;
  312. }
  313. function writeFileTable(d) {
  314. d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
  315. d.writeln('<TR>\n<TH>Name</TH>\n<TH>File</TH>\n<TH>Date</TH>\n</TR>');
  316. for (var i = 0; i < nFiles; i++)
  317. d.writeln('<TR>\n<TD>'+quoteHTML(fileTags[i])+'</TD>\n<TD><TT>'+quoteHTML(fileNames[i])+'</TT></TD>\n<TD>'+quoteHTML(fileDates[i])+'</TD>\n</TR>');
  318. d.writeln('</TABLE>');
  319. }
  320. function writeReloadLink(d, column, s, rowspan) {
  321. d.write(rowspan == 1 ? '<TH>' : '<TH rowspan='+rowspan+'>');
  322. if (column != sortColumn)
  323. d.write('<A href="javascript:reloadSelf('+column+','+showMode+')">');
  324. d.write(s);
  325. if (column != sortColumn)
  326. d.write('</A>');
  327. d.writeln('</TH>');
  328. }
  329. function writeClassTableRow(d, row, base, modeName) {
  330. if (modeName) {
  331. d.writeln('<TR>\n<TH>'+modeName+'</TH>');
  332. } else {
  333. d.writeln('<TR>\n<TD><A href="javascript:showRowDetail(\''+row[0]+'\')">'+quoteHTML(row[0])+'</A></TD>');
  334. var v = row[1];
  335. d.writeln('<TD class=num>'+(v === undefined ? '' : v)+'</TD>');
  336. }
  337. for (var i = 0; i != 2; i++) {
  338. var c = base + i;
  339. for (var j = 0; j <= nFiles; j++) {
  340. v = row[c];
  341. var style = 'num';
  342. if (j != nFiles)
  343. if (v > 0) {
  344. style = 'pos';
  345. v = '+'+v;
  346. } else
  347. style = 'neg';
  348. d.writeln('<TD class='+style+'>'+(v === undefined ? '' : v)+'</TD>');
  349. c += 6;
  350. }
  351. }
  352. d.writeln('</TR>');
  353. }
  354. function writeClassTable(d) {
  355. var base = 2 + showMode*2;
  356. // Make a copy because a sort is destructive.
  357. var table = classTables.concat();
  358. table.sort(sortCompare);
  359. d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
  360. d.writeln('<TR>');
  361. writeReloadLink(d, 0, 'Class Name', 2);
  362. writeReloadLink(d, 1, 'Instance<BR>Size', 2);
  363. d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated</TH>');
  364. d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated but not freed</TH>\n</TR>');
  365. d.writeln('<TR>');
  366. for (var i = 0; i != 2; i++) {
  367. var c = base + i;
  368. for (var j = 0; j <= nFiles; j++) {
  369. writeReloadLink(d, c, j == nFiles ? 'Total' : quoteHTML(fileTags[j]), 1);
  370. c += 6;
  371. }
  372. }
  373. d.writeln('</TR>');
  374. writeClassTableRow(d, totals, base, 0);
  375. for (var r = 0; r < table.length; r++)
  376. writeClassTableRow(d, table[r], base, 0);
  377. d.writeln('</TABLE>');
  378. }
  379. var modeNames = ["byte", "object", "reference"];
  380. var modeNamesUpper = ["Byte", "Object", "Reference"];
  381. var styleSheet = '<STYLE type="TEXT/CSS">\n'+
  382. 'BODY {background-color: #FFFFFF; color: #000000}\n'+
  383. '.num {text-align: right}\n'+
  384. '.pos {text-align: right; color: #CC0000}\n'+
  385. '.neg {text-align: right; color: #009900}\n'+
  386. '</STYLE>';
  387. function showHead(d) {
  388. modeName = modeNames[showMode];
  389. modeNameUpper = modeNamesUpper[showMode];
  390. d.writeln('<TITLE>'+modeNameUpper+' Bloats</TITLE>');
  391. d.writeln(styleSheet);
  392. }
  393. function showBody(d) {
  394. d.writeln('<H1>'+modeNameUpper+' Bloats</H1>');
  395. writeFileTable(d);
  396. d.write('<FORM>');
  397. for (var i = 0; i != 3; i++)
  398. if (i != showMode) {
  399. var newSortColumn = sortColumn;
  400. if (sortColumn >= 2)
  401. newSortColumn = sortColumn + (i-showMode)*2;
  402. d.write('<INPUT type="button" value="Show '+modeNamesUpper[i]+'s" onClick="reloadSelf('+newSortColumn+','+i+')">');
  403. }
  404. d.writeln('</FORM>');
  405. d.writeln('<P>The numbers do not include <CODE>malloc</CODE>\'d data such as string contents.</P>');
  406. d.writeln('<P>Click on a column heading to sort by that column. Click on a class name to see details for that class.</P>');
  407. writeClassTable(d);
  408. }
  409. function showRowDetail(rowName) {
  410. var row;
  411. var i;
  412. if (rowName == "TOTAL")
  413. row = totals;
  414. else {
  415. for (i = 0; i < classTables.length; i++)
  416. if (rowName == classTables[i][0]) {
  417. row = classTables[i];
  418. break;
  419. }
  420. }
  421. if (row) {
  422. var w = window.open("", "ClassTableRowDetails");
  423. var d = w.document;
  424. d.open();
  425. d.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
  426. d.writeln('<HTML>\n<HEAD>\n<TITLE>'+quoteHTML(rowName)+' bloat details</TITLE>');
  427. d.writeln(styleSheet);
  428. d.writeln('</HEAD>\n\n<BODY>');
  429. d.writeln('<H2>'+quoteHTML(rowName)+'</H2>');
  430. if (row[1] !== undefined)
  431. d.writeln('<P>Each instance has '+row[1]+' bytes.</P>');
  432. d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
  433. d.writeln('<TR>\n<TH></TH>\n<TH colspan='+(nFiles+1)+'>Allocated</TH>');
  434. d.writeln('<TH colspan='+(nFiles+1)+'>Allocated but not freed</TH>\n</TR>');
  435. d.writeln('<TR>\n<TH></TH>');
  436. for (i = 0; i != 2; i++)
  437. for (var j = 0; j <= nFiles; j++)
  438. d.writeln('<TH>'+(j == nFiles ? 'Total' : quoteHTML(fileTags[j]))+'</TH>');
  439. d.writeln('</TR>');
  440. for (i = 0; i != 3; i++)
  441. writeClassTableRow(d, row, 2+i*2, modeNamesUpper[i]+'s');
  442. d.writeln('</TABLE>\n</BODY>\n</HTML>');
  443. d.close();
  444. }
  445. return undefined;
  446. }
  447. function stringSource(s) {
  448. s = s.replace(/\\/g, '\\\\');
  449. s = s.replace(/"/g, '\\"');
  450. s = s.replace(/<\//g, '<\\/');
  451. return '"'+s+'"';
  452. }
  453. function reloadSelf(n,m) {
  454. // Need to cache these because globals go away on document.open().
  455. var sa = srcArray;
  456. var ss = stringSource;
  457. var ct = classTables;
  458. var i;
  459. document.open();
  460. // Uncomment this and comment the document.open() line above to see the reloaded page's source.
  461. //var w = window.open("", "NewDoc");
  462. //var d = w.document;
  463. //var document = new Object;
  464. //document.write = function () {
  465. // for (var i = 0; i < arguments.length; i++) {
  466. // var s = arguments[i].toString();
  467. // s = s.replace(/&/g, '&amp;');
  468. // s = s.replace(/\x3C/g, '&lt;');
  469. // s = s.replace(/>/g, '&gt;');
  470. // s = s.replace(/ /g, '&nbsp;');
  471. // d.write(s);
  472. // }
  473. //};
  474. //document.writeln = function () {
  475. // for (var i = 0; i < arguments.length; i++) {
  476. // var s = arguments[i].toString();
  477. // s = s.replace(/&/g, '&amp;');
  478. // s = s.replace(/\x3C/g, '&lt;');
  479. // s = s.replace(/>/g, '&gt;');
  480. // s = s.replace(/ /g, '&nbsp;');
  481. // d.write(s);
  482. // }
  483. // d.writeln('<BR>');
  484. //};
  485. document.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
  486. document.writeln('<HTML>\n<HEAD>\n<SCRIPT type="text/javascript">');
  487. // Manually copy non-persistent script data
  488. if (!ct.length)
  489. document.writeln('var classTables = [];');
  490. else {
  491. document.writeln('var classTables = [');
  492. for (i = 0; i < ct.length; i++) {
  493. var row = ct[i];
  494. document.write('[' + ss(row[0]));
  495. for (var j = 1; j < row.length; j++)
  496. document.write(',' + row[j]);
  497. document.writeln(']' + (i == ct.length-1 ? '];' : ','));
  498. }
  499. }
  500. document.writeln('var srcArray = [');
  501. for (i = 0; i < sa.length; i++) {
  502. document.write(ss(sa[i]));
  503. if (i != sa.length-1)
  504. document.writeln(',');
  505. }
  506. document.writeln('];');
  507. document.writeln('eval(srcArray.join("\\n"));');
  508. document.writeln('showMode = '+m+';');
  509. document.writeln('sortColumn = '+n+';');
  510. document.writeln('showHead(document);');
  511. document.writeln('</SCRIPT>\n</HEAD>\n\n<BODY>\n<SCRIPT type="text/javascript">showBody(document);</SCRIPT>\n</BODY>\n</HTML>');
  512. document.close();
  513. return undefined;
  514. }