xref: /aosp_15_r20/external/autotest/client/tools/html_report.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li#!/usr/bin/python3
2*9c5db199SXin Li"""
3*9c5db199SXin LiModule used to parse the autotest job results and generate an HTML report.
4*9c5db199SXin Li
5*9c5db199SXin Li@copyright: (c)2005-2007 Matt Kruse (javascripttoolbox.com)
6*9c5db199SXin Li@copyright: Red Hat 2008-2009
7*9c5db199SXin Li@author: Dror Russo ([email protected])
8*9c5db199SXin Li"""
9*9c5db199SXin Li
10*9c5db199SXin Lifrom __future__ import absolute_import
11*9c5db199SXin Lifrom __future__ import division
12*9c5db199SXin Lifrom __future__ import print_function
13*9c5db199SXin Liimport os, sys, re, getopt, time, datetime, subprocess
14*9c5db199SXin Liimport common
15*9c5db199SXin Li
16*9c5db199SXin Li
17*9c5db199SXin Liformat_css = """
18*9c5db199SXin Lihtml,body {
19*9c5db199SXin Li    padding:0;
20*9c5db199SXin Li    color:#222;
21*9c5db199SXin Li    background:#FFFFFF;
22*9c5db199SXin Li}
23*9c5db199SXin Li
24*9c5db199SXin Libody {
25*9c5db199SXin Li    padding:0px;
26*9c5db199SXin Li    font:76%/150% "Lucida Grande", "Lucida Sans Unicode", Lucida, Verdana, Geneva, Arial, Helvetica, sans-serif;
27*9c5db199SXin Li}
28*9c5db199SXin Li
29*9c5db199SXin Li#page_title{
30*9c5db199SXin Li    text-decoration:none;
31*9c5db199SXin Li    font:bold 2em/2em Arial, Helvetica, sans-serif;
32*9c5db199SXin Li    text-transform:none;
33*9c5db199SXin Li    text-align: left;
34*9c5db199SXin Li    color:#555555;
35*9c5db199SXin Li    border-bottom: 1px solid #555555;
36*9c5db199SXin Li}
37*9c5db199SXin Li
38*9c5db199SXin Li#page_sub_title{
39*9c5db199SXin Li        text-decoration:none;
40*9c5db199SXin Li        font:bold 16px Arial, Helvetica, sans-serif;
41*9c5db199SXin Li        text-transform:uppercase;
42*9c5db199SXin Li        text-align: left;
43*9c5db199SXin Li        color:#555555;
44*9c5db199SXin Li    margin-bottom:0;
45*9c5db199SXin Li}
46*9c5db199SXin Li
47*9c5db199SXin Li#comment{
48*9c5db199SXin Li        text-decoration:none;
49*9c5db199SXin Li        font:bold 10px Arial, Helvetica, sans-serif;
50*9c5db199SXin Li        text-transform:none;
51*9c5db199SXin Li        text-align: left;
52*9c5db199SXin Li        color:#999999;
53*9c5db199SXin Li    margin-top:0;
54*9c5db199SXin Li}
55*9c5db199SXin Li
56*9c5db199SXin Li
57*9c5db199SXin Li#meta_headline{
58*9c5db199SXin Li                text-decoration:none;
59*9c5db199SXin Li                font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
60*9c5db199SXin Li                text-align: left;
61*9c5db199SXin Li                color:black;
62*9c5db199SXin Li                font-weight: bold;
63*9c5db199SXin Li                font-size: 14px;
64*9c5db199SXin Li        }
65*9c5db199SXin Li
66*9c5db199SXin Li
67*9c5db199SXin Litable.meta_table
68*9c5db199SXin Li{text-align: center;
69*9c5db199SXin Lifont-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
70*9c5db199SXin Liwidth: 90%;
71*9c5db199SXin Libackground-color: #FFFFFF;
72*9c5db199SXin Liborder: 0px;
73*9c5db199SXin Liborder-top: 1px #003377 solid;
74*9c5db199SXin Liborder-bottom: 1px #003377 solid;
75*9c5db199SXin Liborder-right: 1px #003377 solid;
76*9c5db199SXin Liborder-left: 1px #003377 solid;
77*9c5db199SXin Liborder-collapse: collapse;
78*9c5db199SXin Liborder-spacing: 0px;}
79*9c5db199SXin Li
80*9c5db199SXin Litable.meta_table td
81*9c5db199SXin Li{background-color: #FFFFFF;
82*9c5db199SXin Licolor: #000;
83*9c5db199SXin Lipadding: 4px;
84*9c5db199SXin Liborder-top: 1px #BBBBBB solid;
85*9c5db199SXin Liborder-bottom: 1px #BBBBBB solid;
86*9c5db199SXin Lifont-weight: normal;
87*9c5db199SXin Lifont-size: 13px;}
88*9c5db199SXin Li
89*9c5db199SXin Li
90*9c5db199SXin Litable.stats
91*9c5db199SXin Li{text-align: center;
92*9c5db199SXin Lifont-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
93*9c5db199SXin Liwidth: 100%;
94*9c5db199SXin Libackground-color: #FFFFFF;
95*9c5db199SXin Liborder: 0px;
96*9c5db199SXin Liborder-top: 1px #003377 solid;
97*9c5db199SXin Liborder-bottom: 1px #003377 solid;
98*9c5db199SXin Liborder-right: 1px #003377 solid;
99*9c5db199SXin Liborder-left: 1px #003377 solid;
100*9c5db199SXin Liborder-collapse: collapse;
101*9c5db199SXin Liborder-spacing: 0px;}
102*9c5db199SXin Li
103*9c5db199SXin Litable.stats td{
104*9c5db199SXin Libackground-color: #FFFFFF;
105*9c5db199SXin Licolor: #000;
106*9c5db199SXin Lipadding: 4px;
107*9c5db199SXin Liborder-top: 1px #BBBBBB solid;
108*9c5db199SXin Liborder-bottom: 1px #BBBBBB solid;
109*9c5db199SXin Lifont-weight: normal;
110*9c5db199SXin Lifont-size: 11px;}
111*9c5db199SXin Li
112*9c5db199SXin Litable.stats th{
113*9c5db199SXin Libackground: #dcdcdc;
114*9c5db199SXin Licolor: #000;
115*9c5db199SXin Lipadding: 6px;
116*9c5db199SXin Lifont-size: 12px;
117*9c5db199SXin Liborder-bottom: 1px #003377 solid;
118*9c5db199SXin Lifont-weight: bold;}
119*9c5db199SXin Li
120*9c5db199SXin Litable.stats td.top{
121*9c5db199SXin Libackground-color: #dcdcdc;
122*9c5db199SXin Licolor: #000;
123*9c5db199SXin Lipadding: 6px;
124*9c5db199SXin Litext-align: center;
125*9c5db199SXin Liborder: 0px;
126*9c5db199SXin Liborder-bottom: 1px #003377 solid;
127*9c5db199SXin Lifont-size: 10px;
128*9c5db199SXin Lifont-weight: bold;}
129*9c5db199SXin Li
130*9c5db199SXin Litable.stats th.table-sorted-asc{
131*9c5db199SXin Li        background-image: url(ascending.gif);
132*9c5db199SXin Li        background-position: top left  ;
133*9c5db199SXin Li        background-repeat: no-repeat;
134*9c5db199SXin Li}
135*9c5db199SXin Li
136*9c5db199SXin Litable.stats th.table-sorted-desc{
137*9c5db199SXin Li        background-image: url(descending.gif);
138*9c5db199SXin Li        background-position: top left;
139*9c5db199SXin Li        background-repeat: no-repeat;
140*9c5db199SXin Li}
141*9c5db199SXin Li
142*9c5db199SXin Litable.stats2
143*9c5db199SXin Li{text-align: left;
144*9c5db199SXin Lifont-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
145*9c5db199SXin Liwidth: 100%;
146*9c5db199SXin Libackground-color: #FFFFFF;
147*9c5db199SXin Liborder: 0px;
148*9c5db199SXin Li}
149*9c5db199SXin Li
150*9c5db199SXin Litable.stats2 td{
151*9c5db199SXin Libackground-color: #FFFFFF;
152*9c5db199SXin Licolor: #000;
153*9c5db199SXin Lipadding: 0px;
154*9c5db199SXin Lifont-weight: bold;
155*9c5db199SXin Lifont-size: 13px;}
156*9c5db199SXin Li
157*9c5db199SXin Li
158*9c5db199SXin Li
159*9c5db199SXin Li/* Put this inside a @media qualifier so Netscape 4 ignores it */
160*9c5db199SXin Li@media screen, print {
161*9c5db199SXin Li        /* Turn off list bullets */
162*9c5db199SXin Li        ul.mktree  li { list-style: none; }
163*9c5db199SXin Li        /* Control how "spaced out" the tree is */
164*9c5db199SXin Li        ul.mktree, ul.mktree ul , ul.mktree li { margin-left:10px; padding:0px; }
165*9c5db199SXin Li        /* Provide space for our own "bullet" inside the LI */
166*9c5db199SXin Li        ul.mktree  li           .bullet { padding-left: 15px; }
167*9c5db199SXin Li        /* Show "bullets" in the links, depending on the class of the LI that the link's in */
168*9c5db199SXin Li        ul.mktree  li.liOpen    .bullet { cursor: pointer; }
169*9c5db199SXin Li        ul.mktree  li.liClosed  .bullet { cursor: pointer;  }
170*9c5db199SXin Li        ul.mktree  li.liBullet  .bullet { cursor: default; }
171*9c5db199SXin Li        /* Sublists are visible or not based on class of parent LI */
172*9c5db199SXin Li        ul.mktree  li.liOpen    ul { display: block; }
173*9c5db199SXin Li        ul.mktree  li.liClosed  ul { display: none; }
174*9c5db199SXin Li
175*9c5db199SXin Li        /* Format menu items differently depending on what level of the tree they are in */
176*9c5db199SXin Li        /* Uncomment this if you want your fonts to decrease in size the deeper they are in the tree */
177*9c5db199SXin Li/*
178*9c5db199SXin Li        ul.mktree  li ul li { font-size: 90% }
179*9c5db199SXin Li*/
180*9c5db199SXin Li}
181*9c5db199SXin Li"""
182*9c5db199SXin Li
183*9c5db199SXin Li
184*9c5db199SXin Litable_js = """
185*9c5db199SXin Li/**
186*9c5db199SXin Li * Copyright (c)2005-2007 Matt Kruse (javascripttoolbox.com)
187*9c5db199SXin Li *
188*9c5db199SXin Li * Dual licensed under the MIT and GPL licenses.
189*9c5db199SXin Li * This basically means you can use this code however you want for
190*9c5db199SXin Li * free, but don't claim to have written it yourself!
191*9c5db199SXin Li * Donations always accepted: http://www.JavascriptToolbox.com/donate/
192*9c5db199SXin Li *
193*9c5db199SXin Li * Please do not link to the .js files on javascripttoolbox.com from
194*9c5db199SXin Li * your site. Copy the files locally to your server instead.
195*9c5db199SXin Li *
196*9c5db199SXin Li */
197*9c5db199SXin Li/**
198*9c5db199SXin Li * Table.js
199*9c5db199SXin Li * Functions for interactive Tables
200*9c5db199SXin Li *
201*9c5db199SXin Li * Copyright (c) 2007 Matt Kruse (javascripttoolbox.com)
202*9c5db199SXin Li * Dual licensed under the MIT and GPL licenses.
203*9c5db199SXin Li *
204*9c5db199SXin Li * @version 0.981
205*9c5db199SXin Li *
206*9c5db199SXin Li * @history 0.981 2007-03-19 Added Sort.numeric_comma, additional date parsing formats
207*9c5db199SXin Li * @history 0.980 2007-03-18 Release new BETA release pending some testing. Todo: Additional docs, examples, plus jQuery plugin.
208*9c5db199SXin Li * @history 0.959 2007-03-05 Added more "auto" functionality, couple bug fixes
209*9c5db199SXin Li * @history 0.958 2007-02-28 Added auto functionality based on class names
210*9c5db199SXin Li * @history 0.957 2007-02-21 Speed increases, more code cleanup, added Auto Sort functionality
211*9c5db199SXin Li * @history 0.956 2007-02-16 Cleaned up the code and added Auto Filter functionality.
212*9c5db199SXin Li * @history 0.950 2006-11-15 First BETA release.
213*9c5db199SXin Li *
214*9c5db199SXin Li * @todo Add more date format parsers
215*9c5db199SXin Li * @todo Add style classes to colgroup tags after sorting/filtering in case the user wants to highlight the whole column
216*9c5db199SXin Li * @todo Correct for colspans in data rows (this may slow it down)
217*9c5db199SXin Li * @todo Fix for IE losing form control values after sort?
218*9c5db199SXin Li */
219*9c5db199SXin Li
220*9c5db199SXin Li/**
221*9c5db199SXin Li * Sort Functions
222*9c5db199SXin Li */
223*9c5db199SXin Livar Sort = (function(){
224*9c5db199SXin Li        var sort = {};
225*9c5db199SXin Li        // Default alpha-numeric sort
226*9c5db199SXin Li        // --------------------------
227*9c5db199SXin Li        sort.alphanumeric = function(a,b) {
228*9c5db199SXin Li                return (a==b)?0:(a<b)?-1:1;
229*9c5db199SXin Li        };
230*9c5db199SXin Li        sort.alphanumeric_rev = function(a,b) {
231*9c5db199SXin Li                return (a==b)?0:(a<b)?1:-1;
232*9c5db199SXin Li        };
233*9c5db199SXin Li        sort['default'] = sort.alphanumeric; // IE chokes on sort.default
234*9c5db199SXin Li
235*9c5db199SXin Li        // This conversion is generalized to work for either a decimal separator of , or .
236*9c5db199SXin Li        sort.numeric_converter = function(separator) {
237*9c5db199SXin Li                return function(val) {
238*9c5db199SXin Li                        if (typeof(val)=="string") {
239*9c5db199SXin Li                                val = parseFloat(val.replace(/^[^\d\.]*([\d., ]+).*/g,"$1").replace(new RegExp("[^\\\d"+separator+"]","g"),'').replace(/,/,'.')) || 0;
240*9c5db199SXin Li                        }
241*9c5db199SXin Li                        return val || 0;
242*9c5db199SXin Li                };
243*9c5db199SXin Li        };
244*9c5db199SXin Li
245*9c5db199SXin Li        // Numeric Reversed Sort
246*9c5db199SXin Li        // ------------
247*9c5db199SXin Li        sort.numeric_rev = function(a,b) {
248*9c5db199SXin Li                if (sort.numeric.convert(a)>sort.numeric.convert(b)) {
249*9c5db199SXin Li                        return (-1);
250*9c5db199SXin Li                }
251*9c5db199SXin Li                if (sort.numeric.convert(a)==sort.numeric.convert(b)) {
252*9c5db199SXin Li                        return 0;
253*9c5db199SXin Li                }
254*9c5db199SXin Li                if (sort.numeric.convert(a)<sort.numeric.convert(b)) {
255*9c5db199SXin Li                        return 1;
256*9c5db199SXin Li                }
257*9c5db199SXin Li        };
258*9c5db199SXin Li
259*9c5db199SXin Li
260*9c5db199SXin Li        // Numeric Sort
261*9c5db199SXin Li        // ------------
262*9c5db199SXin Li        sort.numeric = function(a,b) {
263*9c5db199SXin Li                return sort.numeric.convert(a)-sort.numeric.convert(b);
264*9c5db199SXin Li        };
265*9c5db199SXin Li        sort.numeric.convert = sort.numeric_converter(".");
266*9c5db199SXin Li
267*9c5db199SXin Li        // Numeric Sort - comma decimal separator
268*9c5db199SXin Li        // --------------------------------------
269*9c5db199SXin Li        sort.numeric_comma = function(a,b) {
270*9c5db199SXin Li                return sort.numeric_comma.convert(a)-sort.numeric_comma.convert(b);
271*9c5db199SXin Li        };
272*9c5db199SXin Li        sort.numeric_comma.convert = sort.numeric_converter(",");
273*9c5db199SXin Li
274*9c5db199SXin Li        // Case-insensitive Sort
275*9c5db199SXin Li        // ---------------------
276*9c5db199SXin Li        sort.ignorecase = function(a,b) {
277*9c5db199SXin Li                return sort.alphanumeric(sort.ignorecase.convert(a),sort.ignorecase.convert(b));
278*9c5db199SXin Li        };
279*9c5db199SXin Li        sort.ignorecase.convert = function(val) {
280*9c5db199SXin Li                if (val==null) { return ""; }
281*9c5db199SXin Li                return (""+val).toLowerCase();
282*9c5db199SXin Li        };
283*9c5db199SXin Li
284*9c5db199SXin Li        // Currency Sort
285*9c5db199SXin Li        // -------------
286*9c5db199SXin Li        sort.currency = sort.numeric; // Just treat it as numeric!
287*9c5db199SXin Li        sort.currency_comma = sort.numeric_comma;
288*9c5db199SXin Li
289*9c5db199SXin Li        // Date sort
290*9c5db199SXin Li        // ---------
291*9c5db199SXin Li        sort.date = function(a,b) {
292*9c5db199SXin Li                return sort.numeric(sort.date.convert(a),sort.date.convert(b));
293*9c5db199SXin Li        };
294*9c5db199SXin Li        // Convert 2-digit years to 4
295*9c5db199SXin Li        sort.date.fixYear=function(yr) {
296*9c5db199SXin Li                yr = +yr;
297*9c5db199SXin Li                if (yr<50) { yr += 2000; }
298*9c5db199SXin Li                else if (yr<100) { yr += 1900; }
299*9c5db199SXin Li                return yr;
300*9c5db199SXin Li        };
301*9c5db199SXin Li        sort.date.formats = [
302*9c5db199SXin Li                // YY[YY]-MM-DD
303*9c5db199SXin Li                { re:/(\d{2,4})-(\d{1,2})-(\d{1,2})/ , f:function(x){ return (new Date(sort.date.fixYear(x[1]),+x[2],+x[3])).getTime(); } }
304*9c5db199SXin Li                // MM/DD/YY[YY] or MM-DD-YY[YY]
305*9c5db199SXin Li                ,{ re:/(\d{1,2})[\/-](\d{1,2})[\/-](\d{2,4})/ , f:function(x){ return (new Date(sort.date.fixYear(x[3]),+x[1],+x[2])).getTime(); } }
306*9c5db199SXin Li                // Any catch-all format that new Date() can handle. This is not reliable except for long formats, for example: 31 Jan 2000 01:23:45 GMT
307*9c5db199SXin Li                ,{ re:/(.*\d{4}.*\d+:\d+\d+.*)/, f:function(x){ var d=new Date(x[1]); if(d){return d.getTime();} } }
308*9c5db199SXin Li        ];
309*9c5db199SXin Li        sort.date.convert = function(val) {
310*9c5db199SXin Li                var m,v, f = sort.date.formats;
311*9c5db199SXin Li                for (var i=0,L=f.length; i<L; i++) {
312*9c5db199SXin Li                        if (m=val.match(f[i].re)) {
313*9c5db199SXin Li                                v=f[i].f(m);
314*9c5db199SXin Li                                if (typeof(v)!="undefined") { return v; }
315*9c5db199SXin Li                        }
316*9c5db199SXin Li                }
317*9c5db199SXin Li                return 9999999999999; // So non-parsed dates will be last, not first
318*9c5db199SXin Li        };
319*9c5db199SXin Li
320*9c5db199SXin Li        return sort;
321*9c5db199SXin Li})();
322*9c5db199SXin Li
323*9c5db199SXin Li/**
324*9c5db199SXin Li * The main Table namespace
325*9c5db199SXin Li */
326*9c5db199SXin Livar Table = (function(){
327*9c5db199SXin Li
328*9c5db199SXin Li        /**
329*9c5db199SXin Li         * Determine if a reference is defined
330*9c5db199SXin Li         */
331*9c5db199SXin Li        function def(o) {return (typeof o!="undefined");};
332*9c5db199SXin Li
333*9c5db199SXin Li        /**
334*9c5db199SXin Li         * Determine if an object or class string contains a given class.
335*9c5db199SXin Li         */
336*9c5db199SXin Li        function hasClass(o,name) {
337*9c5db199SXin Li                return new RegExp("(^|\\\s)"+name+"(\\\s|$)").test(o.className);
338*9c5db199SXin Li        };
339*9c5db199SXin Li
340*9c5db199SXin Li        /**
341*9c5db199SXin Li         * Add a class to an object
342*9c5db199SXin Li         */
343*9c5db199SXin Li        function addClass(o,name) {
344*9c5db199SXin Li                var c = o.className || "";
345*9c5db199SXin Li                if (def(c) && !hasClass(o,name)) {
346*9c5db199SXin Li                        o.className += (c?" ":"") + name;
347*9c5db199SXin Li                }
348*9c5db199SXin Li        };
349*9c5db199SXin Li
350*9c5db199SXin Li        /**
351*9c5db199SXin Li         * Remove a class from an object
352*9c5db199SXin Li         */
353*9c5db199SXin Li        function removeClass(o,name) {
354*9c5db199SXin Li                var c = o.className || "";
355*9c5db199SXin Li                o.className = c.replace(new RegExp("(^|\\\s)"+name+"(\\\s|$)"),"$1");
356*9c5db199SXin Li        };
357*9c5db199SXin Li
358*9c5db199SXin Li        /**
359*9c5db199SXin Li         * For classes that match a given substring, return the rest
360*9c5db199SXin Li         */
361*9c5db199SXin Li        function classValue(o,prefix) {
362*9c5db199SXin Li                var c = o.className;
363*9c5db199SXin Li                if (c.match(new RegExp("(^|\\\s)"+prefix+"([^ ]+)"))) {
364*9c5db199SXin Li                        return RegExp.$2;
365*9c5db199SXin Li                }
366*9c5db199SXin Li                return null;
367*9c5db199SXin Li        };
368*9c5db199SXin Li
369*9c5db199SXin Li        /**
370*9c5db199SXin Li         * Return true if an object is hidden.
371*9c5db199SXin Li         * This uses the "russian doll" technique to unwrap itself to the most efficient
372*9c5db199SXin Li         * function after the first pass. This avoids repeated feature detection that
373*9c5db199SXin Li         * would always fall into the same block of code.
374*9c5db199SXin Li         */
375*9c5db199SXin Li         function isHidden(o) {
376*9c5db199SXin Li                if (window.getComputedStyle) {
377*9c5db199SXin Li                        var cs = window.getComputedStyle;
378*9c5db199SXin Li                        return (isHidden = function(o) {
379*9c5db199SXin Li                                return 'none'==cs(o,null).getPropertyValue('display');
380*9c5db199SXin Li                        })(o);
381*9c5db199SXin Li                }
382*9c5db199SXin Li                else if (window.currentStyle) {
383*9c5db199SXin Li                        return(isHidden = function(o) {
384*9c5db199SXin Li                                return 'none'==o.currentStyle['display'];
385*9c5db199SXin Li                        })(o);
386*9c5db199SXin Li                }
387*9c5db199SXin Li                return (isHidden = function(o) {
388*9c5db199SXin Li                        return 'none'==o.style['display'];
389*9c5db199SXin Li                })(o);
390*9c5db199SXin Li        };
391*9c5db199SXin Li
392*9c5db199SXin Li        /**
393*9c5db199SXin Li         * Get a parent element by tag name, or the original element if it is of the tag type
394*9c5db199SXin Li         */
395*9c5db199SXin Li        function getParent(o,a,b) {
396*9c5db199SXin Li                if (o!=null && o.nodeName) {
397*9c5db199SXin Li                        if (o.nodeName==a || (b && o.nodeName==b)) {
398*9c5db199SXin Li                                return o;
399*9c5db199SXin Li                        }
400*9c5db199SXin Li                        while (o=o.parentNode) {
401*9c5db199SXin Li                                if (o.nodeName && (o.nodeName==a || (b && o.nodeName==b))) {
402*9c5db199SXin Li                                        return o;
403*9c5db199SXin Li                                }
404*9c5db199SXin Li                        }
405*9c5db199SXin Li                }
406*9c5db199SXin Li                return null;
407*9c5db199SXin Li        };
408*9c5db199SXin Li
409*9c5db199SXin Li        /**
410*9c5db199SXin Li         * Utility function to copy properties from one object to another
411*9c5db199SXin Li         */
412*9c5db199SXin Li        function copy(o1,o2) {
413*9c5db199SXin Li                for (var i=2;i<arguments.length; i++) {
414*9c5db199SXin Li                        var a = arguments[i];
415*9c5db199SXin Li                        if (def(o1[a])) {
416*9c5db199SXin Li                                o2[a] = o1[a];
417*9c5db199SXin Li                        }
418*9c5db199SXin Li                }
419*9c5db199SXin Li        }
420*9c5db199SXin Li
421*9c5db199SXin Li        // The table object itself
422*9c5db199SXin Li        var table = {
423*9c5db199SXin Li                //Class names used in the code
424*9c5db199SXin Li                AutoStripeClassName:"table-autostripe",
425*9c5db199SXin Li                StripeClassNamePrefix:"table-stripeclass:",
426*9c5db199SXin Li
427*9c5db199SXin Li                AutoSortClassName:"table-autosort",
428*9c5db199SXin Li                AutoSortColumnPrefix:"table-autosort:",
429*9c5db199SXin Li                AutoSortTitle:"Click to sort",
430*9c5db199SXin Li                SortedAscendingClassName:"table-sorted-asc",
431*9c5db199SXin Li                SortedDescendingClassName:"table-sorted-desc",
432*9c5db199SXin Li                SortableClassName:"table-sortable",
433*9c5db199SXin Li                SortableColumnPrefix:"table-sortable:",
434*9c5db199SXin Li                NoSortClassName:"table-nosort",
435*9c5db199SXin Li
436*9c5db199SXin Li                AutoFilterClassName:"table-autofilter",
437*9c5db199SXin Li                FilteredClassName:"table-filtered",
438*9c5db199SXin Li                FilterableClassName:"table-filterable",
439*9c5db199SXin Li                FilteredRowcountPrefix:"table-filtered-rowcount:",
440*9c5db199SXin Li                RowcountPrefix:"table-rowcount:",
441*9c5db199SXin Li                FilterAllLabel:"Filter: All",
442*9c5db199SXin Li
443*9c5db199SXin Li                AutoPageSizePrefix:"table-autopage:",
444*9c5db199SXin Li                AutoPageJumpPrefix:"table-page:",
445*9c5db199SXin Li                PageNumberPrefix:"table-page-number:",
446*9c5db199SXin Li                PageCountPrefix:"table-page-count:"
447*9c5db199SXin Li        };
448*9c5db199SXin Li
449*9c5db199SXin Li        /**
450*9c5db199SXin Li         * A place to store misc table information, rather than in the table objects themselves
451*9c5db199SXin Li         */
452*9c5db199SXin Li        table.tabledata = {};
453*9c5db199SXin Li
454*9c5db199SXin Li        /**
455*9c5db199SXin Li         * Resolve a table given an element reference, and make sure it has a unique ID
456*9c5db199SXin Li         */
457*9c5db199SXin Li        table.uniqueId=1;
458*9c5db199SXin Li        table.resolve = function(o,args) {
459*9c5db199SXin Li                if (o!=null && o.nodeName && o.nodeName!="TABLE") {
460*9c5db199SXin Li                        o = getParent(o,"TABLE");
461*9c5db199SXin Li                }
462*9c5db199SXin Li                if (o==null) { return null; }
463*9c5db199SXin Li                if (!o.id) {
464*9c5db199SXin Li                        var id = null;
465*9c5db199SXin Li                        do { var id = "TABLE_"+(table.uniqueId++); }
466*9c5db199SXin Li                                while (document.getElementById(id)!=null);
467*9c5db199SXin Li                        o.id = id;
468*9c5db199SXin Li                }
469*9c5db199SXin Li                this.tabledata[o.id] = this.tabledata[o.id] || {};
470*9c5db199SXin Li                if (args) {
471*9c5db199SXin Li                        copy(args,this.tabledata[o.id],"stripeclass","ignorehiddenrows","useinnertext","sorttype","col","desc","page","pagesize");
472*9c5db199SXin Li                }
473*9c5db199SXin Li                return o;
474*9c5db199SXin Li        };
475*9c5db199SXin Li
476*9c5db199SXin Li
477*9c5db199SXin Li        /**
478*9c5db199SXin Li         * Run a function against each cell in a table header or footer, usually
479*9c5db199SXin Li         * to add or remove css classes based on sorting, filtering, etc.
480*9c5db199SXin Li         */
481*9c5db199SXin Li        table.processTableCells = function(t, type, func, arg) {
482*9c5db199SXin Li                t = this.resolve(t);
483*9c5db199SXin Li                if (t==null) { return; }
484*9c5db199SXin Li                if (type!="TFOOT") {
485*9c5db199SXin Li                        this.processCells(t.tHead, func, arg);
486*9c5db199SXin Li                }
487*9c5db199SXin Li                if (type!="THEAD") {
488*9c5db199SXin Li                        this.processCells(t.tFoot, func, arg);
489*9c5db199SXin Li                }
490*9c5db199SXin Li        };
491*9c5db199SXin Li
492*9c5db199SXin Li        /**
493*9c5db199SXin Li         * Internal method used to process an arbitrary collection of cells.
494*9c5db199SXin Li         * Referenced by processTableCells.
495*9c5db199SXin Li         * It's done this way to avoid getElementsByTagName() which would also return nested table cells.
496*9c5db199SXin Li         */
497*9c5db199SXin Li        table.processCells = function(section,func,arg) {
498*9c5db199SXin Li                if (section!=null) {
499*9c5db199SXin Li                        if (section.rows && section.rows.length && section.rows.length>0) {
500*9c5db199SXin Li                                var rows = section.rows;
501*9c5db199SXin Li                                for (var j=0,L2=rows.length; j<L2; j++) {
502*9c5db199SXin Li                                        var row = rows[j];
503*9c5db199SXin Li                                        if (row.cells && row.cells.length && row.cells.length>0) {
504*9c5db199SXin Li                                                var cells = row.cells;
505*9c5db199SXin Li                                                for (var k=0,L3=cells.length; k<L3; k++) {
506*9c5db199SXin Li                                                        var cellsK = cells[k];
507*9c5db199SXin Li                                                        func.call(this,cellsK,arg);
508*9c5db199SXin Li                                                }
509*9c5db199SXin Li                                        }
510*9c5db199SXin Li                                }
511*9c5db199SXin Li                        }
512*9c5db199SXin Li                }
513*9c5db199SXin Li        };
514*9c5db199SXin Li
515*9c5db199SXin Li        /**
516*9c5db199SXin Li         * Get the cellIndex value for a cell. This is only needed because of a Safari
517*9c5db199SXin Li         * bug that causes cellIndex to exist but always be 0.
518*9c5db199SXin Li         * Rather than feature-detecting each time it is called, the function will
519*9c5db199SXin Li         * re-write itself the first time it is called.
520*9c5db199SXin Li         */
521*9c5db199SXin Li        table.getCellIndex = function(td) {
522*9c5db199SXin Li                var tr = td.parentNode;
523*9c5db199SXin Li                var cells = tr.cells;
524*9c5db199SXin Li                if (cells && cells.length) {
525*9c5db199SXin Li                        if (cells.length>1 && cells[cells.length-1].cellIndex>0) {
526*9c5db199SXin Li                                // Define the new function, overwrite the one we're running now, and then run the new one
527*9c5db199SXin Li                                (this.getCellIndex = function(td) {
528*9c5db199SXin Li                                        return td.cellIndex;
529*9c5db199SXin Li                                })(td);
530*9c5db199SXin Li                        }
531*9c5db199SXin Li                        // Safari will always go through this slower block every time. Oh well.
532*9c5db199SXin Li                        for (var i=0,L=cells.length; i<L; i++) {
533*9c5db199SXin Li                                if (tr.cells[i]==td) {
534*9c5db199SXin Li                                        return i;
535*9c5db199SXin Li                                }
536*9c5db199SXin Li                        }
537*9c5db199SXin Li                }
538*9c5db199SXin Li                return 0;
539*9c5db199SXin Li        };
540*9c5db199SXin Li
541*9c5db199SXin Li        /**
542*9c5db199SXin Li         * A map of node names and how to convert them into their "value" for sorting, filtering, etc.
543*9c5db199SXin Li         * These are put here so it is extensible.
544*9c5db199SXin Li         */
545*9c5db199SXin Li        table.nodeValue = {
546*9c5db199SXin Li                'INPUT':function(node) {
547*9c5db199SXin Li                        if (def(node.value) && node.type && ((node.type!="checkbox" && node.type!="radio") || node.checked)) {
548*9c5db199SXin Li                                return node.value;
549*9c5db199SXin Li                        }
550*9c5db199SXin Li                        return "";
551*9c5db199SXin Li                },
552*9c5db199SXin Li                'SELECT':function(node) {
553*9c5db199SXin Li                        if (node.selectedIndex>=0 && node.options) {
554*9c5db199SXin Li                                // Sort select elements by the visible text
555*9c5db199SXin Li                                return node.options[node.selectedIndex].text;
556*9c5db199SXin Li                        }
557*9c5db199SXin Li                        return "";
558*9c5db199SXin Li                },
559*9c5db199SXin Li                'IMG':function(node) {
560*9c5db199SXin Li                        return node.name || "";
561*9c5db199SXin Li                }
562*9c5db199SXin Li        };
563*9c5db199SXin Li
564*9c5db199SXin Li        /**
565*9c5db199SXin Li         * Get the text value of a cell. Only use innerText if explicitly told to, because
566*9c5db199SXin Li         * otherwise we want to be able to handle sorting on inputs and other types
567*9c5db199SXin Li         */
568*9c5db199SXin Li        table.getCellValue = function(td,useInnerText) {
569*9c5db199SXin Li                if (useInnerText && def(td.innerText)) {
570*9c5db199SXin Li                        return td.innerText;
571*9c5db199SXin Li                }
572*9c5db199SXin Li                if (!td.childNodes) {
573*9c5db199SXin Li                        return "";
574*9c5db199SXin Li                }
575*9c5db199SXin Li                var childNodes=td.childNodes;
576*9c5db199SXin Li                var ret = "";
577*9c5db199SXin Li                for (var i=0,L=childNodes.length; i<L; i++) {
578*9c5db199SXin Li                        var node = childNodes[i];
579*9c5db199SXin Li                        var type = node.nodeType;
580*9c5db199SXin Li                        // In order to get realistic sort results, we need to treat some elements in a special way.
581*9c5db199SXin Li                        // These behaviors are defined in the nodeValue() object, keyed by node name
582*9c5db199SXin Li                        if (type==1) {
583*9c5db199SXin Li                                var nname = node.nodeName;
584*9c5db199SXin Li                                if (this.nodeValue[nname]) {
585*9c5db199SXin Li                                        ret += this.nodeValue[nname](node);
586*9c5db199SXin Li                                }
587*9c5db199SXin Li                                else {
588*9c5db199SXin Li                                        ret += this.getCellValue(node);
589*9c5db199SXin Li                                }
590*9c5db199SXin Li                        }
591*9c5db199SXin Li                        else if (type==3) {
592*9c5db199SXin Li                                if (def(node.innerText)) {
593*9c5db199SXin Li                                        ret += node.innerText;
594*9c5db199SXin Li                                }
595*9c5db199SXin Li                                else if (def(node.nodeValue)) {
596*9c5db199SXin Li                                        ret += node.nodeValue;
597*9c5db199SXin Li                                }
598*9c5db199SXin Li                        }
599*9c5db199SXin Li                }
600*9c5db199SXin Li                return ret;
601*9c5db199SXin Li        };
602*9c5db199SXin Li
603*9c5db199SXin Li        /**
604*9c5db199SXin Li         * Consider colspan and rowspan values in table header cells to calculate the actual cellIndex
605*9c5db199SXin Li         * of a given cell. This is necessary because if the first cell in row 0 has a rowspan of 2,
606*9c5db199SXin Li         * then the first cell in row 1 will have a cellIndex of 0 rather than 1, even though it really
607*9c5db199SXin Li         * starts in the second column rather than the first.
608*9c5db199SXin Li         * See: http://www.javascripttoolbox.com/temp/table_cellindex.html
609*9c5db199SXin Li         */
610*9c5db199SXin Li        table.tableHeaderIndexes = {};
611*9c5db199SXin Li        table.getActualCellIndex = function(tableCellObj) {
612*9c5db199SXin Li                if (!def(tableCellObj.cellIndex)) { return null; }
613*9c5db199SXin Li                var tableObj = getParent(tableCellObj,"TABLE");
614*9c5db199SXin Li                var cellCoordinates = tableCellObj.parentNode.rowIndex+"-"+this.getCellIndex(tableCellObj);
615*9c5db199SXin Li
616*9c5db199SXin Li                // If it has already been computed, return the answer from the lookup table
617*9c5db199SXin Li                if (def(this.tableHeaderIndexes[tableObj.id])) {
618*9c5db199SXin Li                        return this.tableHeaderIndexes[tableObj.id][cellCoordinates];
619*9c5db199SXin Li                }
620*9c5db199SXin Li
621*9c5db199SXin Li                var matrix = [];
622*9c5db199SXin Li                this.tableHeaderIndexes[tableObj.id] = {};
623*9c5db199SXin Li                var thead = getParent(tableCellObj,"THEAD");
624*9c5db199SXin Li                var trs = thead.getElementsByTagName('TR');
625*9c5db199SXin Li
626*9c5db199SXin Li                // Loop thru every tr and every cell in the tr, building up a 2-d array "grid" that gets
627*9c5db199SXin Li                // populated with an "x" for each space that a cell takes up. If the first cell is colspan
628*9c5db199SXin Li                // 2, it will fill in values [0] and [1] in the first array, so that the second cell will
629*9c5db199SXin Li                // find the first empty cell in the first row (which will be [2]) and know that this is
630*9c5db199SXin Li                // where it sits, rather than its internal .cellIndex value of [1].
631*9c5db199SXin Li                for (var i=0; i<trs.length; i++) {
632*9c5db199SXin Li                        var cells = trs[i].cells;
633*9c5db199SXin Li                        for (var j=0; j<cells.length; j++) {
634*9c5db199SXin Li                                var c = cells[j];
635*9c5db199SXin Li                                var rowIndex = c.parentNode.rowIndex;
636*9c5db199SXin Li                                var cellId = rowIndex+"-"+this.getCellIndex(c);
637*9c5db199SXin Li                                var rowSpan = c.rowSpan || 1;
638*9c5db199SXin Li                                var colSpan = c.colSpan || 1;
639*9c5db199SXin Li                                var firstAvailCol;
640*9c5db199SXin Li                                if(!def(matrix[rowIndex])) {
641*9c5db199SXin Li                                        matrix[rowIndex] = [];
642*9c5db199SXin Li                                }
643*9c5db199SXin Li                                var m = matrix[rowIndex];
644*9c5db199SXin Li                                // Find first available column in the first row
645*9c5db199SXin Li                                for (var k=0; k<m.length+1; k++) {
646*9c5db199SXin Li                                        if (!def(m[k])) {
647*9c5db199SXin Li                                                firstAvailCol = k;
648*9c5db199SXin Li                                                break;
649*9c5db199SXin Li                                        }
650*9c5db199SXin Li                                }
651*9c5db199SXin Li                                this.tableHeaderIndexes[tableObj.id][cellId] = firstAvailCol;
652*9c5db199SXin Li                                for (var k=rowIndex; k<rowIndex+rowSpan; k++) {
653*9c5db199SXin Li                                        if(!def(matrix[k])) {
654*9c5db199SXin Li                                                matrix[k] = [];
655*9c5db199SXin Li                                        }
656*9c5db199SXin Li                                        var matrixrow = matrix[k];
657*9c5db199SXin Li                                        for (var l=firstAvailCol; l<firstAvailCol+colSpan; l++) {
658*9c5db199SXin Li                                                matrixrow[l] = "x";
659*9c5db199SXin Li                                        }
660*9c5db199SXin Li                                }
661*9c5db199SXin Li                        }
662*9c5db199SXin Li                }
663*9c5db199SXin Li                // Store the map so future lookups are fast.
664*9c5db199SXin Li                return this.tableHeaderIndexes[tableObj.id][cellCoordinates];
665*9c5db199SXin Li        };
666*9c5db199SXin Li
667*9c5db199SXin Li        /**
668*9c5db199SXin Li         * Sort all rows in each TBODY (tbodies are sorted independent of each other)
669*9c5db199SXin Li         */
670*9c5db199SXin Li        table.sort = function(o,args) {
671*9c5db199SXin Li                var t, tdata, sortconvert=null;
672*9c5db199SXin Li                // Allow for a simple passing of sort type as second parameter
673*9c5db199SXin Li                if (typeof(args)=="function") {
674*9c5db199SXin Li                        args={sorttype:args};
675*9c5db199SXin Li                }
676*9c5db199SXin Li                args = args || {};
677*9c5db199SXin Li
678*9c5db199SXin Li                // If no col is specified, deduce it from the object sent in
679*9c5db199SXin Li                if (!def(args.col)) {
680*9c5db199SXin Li                        args.col = this.getActualCellIndex(o) || 0;
681*9c5db199SXin Li                }
682*9c5db199SXin Li                // If no sort type is specified, default to the default sort
683*9c5db199SXin Li                args.sorttype = args.sorttype || Sort['default'];
684*9c5db199SXin Li
685*9c5db199SXin Li                // Resolve the table
686*9c5db199SXin Li                t = this.resolve(o,args);
687*9c5db199SXin Li                tdata = this.tabledata[t.id];
688*9c5db199SXin Li
689*9c5db199SXin Li                // If we are sorting on the same column as last time, flip the sort direction
690*9c5db199SXin Li                if (def(tdata.lastcol) && tdata.lastcol==tdata.col && def(tdata.lastdesc)) {
691*9c5db199SXin Li                        tdata.desc = !tdata.lastdesc;
692*9c5db199SXin Li                }
693*9c5db199SXin Li                else {
694*9c5db199SXin Li                        tdata.desc = !!args.desc;
695*9c5db199SXin Li                }
696*9c5db199SXin Li
697*9c5db199SXin Li                // Store the last sorted column so clicking again will reverse the sort order
698*9c5db199SXin Li                tdata.lastcol=tdata.col;
699*9c5db199SXin Li                tdata.lastdesc=!!tdata.desc;
700*9c5db199SXin Li
701*9c5db199SXin Li                // If a sort conversion function exists, pre-convert cell values and then use a plain alphanumeric sort
702*9c5db199SXin Li                var sorttype = tdata.sorttype;
703*9c5db199SXin Li                if (typeof(sorttype.convert)=="function") {
704*9c5db199SXin Li                        sortconvert=tdata.sorttype.convert;
705*9c5db199SXin Li                        sorttype=Sort.alphanumeric;
706*9c5db199SXin Li                }
707*9c5db199SXin Li
708*9c5db199SXin Li                // Loop through all THEADs and remove sorted class names, then re-add them for the col
709*9c5db199SXin Li                // that is being sorted
710*9c5db199SXin Li                this.processTableCells(t,"THEAD",
711*9c5db199SXin Li                        function(cell) {
712*9c5db199SXin Li                                if (hasClass(cell,this.SortableClassName)) {
713*9c5db199SXin Li                                        removeClass(cell,this.SortedAscendingClassName);
714*9c5db199SXin Li                                        removeClass(cell,this.SortedDescendingClassName);
715*9c5db199SXin Li                                        // If the computed colIndex of the cell equals the sorted colIndex, flag it as sorted
716*9c5db199SXin Li                                        if (tdata.col==table.getActualCellIndex(cell) && (classValue(cell,table.SortableClassName))) {
717*9c5db199SXin Li                                                addClass(cell,tdata.desc?this.SortedAscendingClassName:this.SortedDescendingClassName);
718*9c5db199SXin Li                                        }
719*9c5db199SXin Li                                }
720*9c5db199SXin Li                        }
721*9c5db199SXin Li                );
722*9c5db199SXin Li
723*9c5db199SXin Li                // Sort each tbody independently
724*9c5db199SXin Li                var bodies = t.tBodies;
725*9c5db199SXin Li                if (bodies==null || bodies.length==0) { return; }
726*9c5db199SXin Li
727*9c5db199SXin Li                // Define a new sort function to be called to consider descending or not
728*9c5db199SXin Li                var newSortFunc = (tdata.desc)?
729*9c5db199SXin Li                        function(a,b){return sorttype(b[0],a[0]);}
730*9c5db199SXin Li                        :function(a,b){return sorttype(a[0],b[0]);};
731*9c5db199SXin Li
732*9c5db199SXin Li                var useinnertext=!!tdata.useinnertext;
733*9c5db199SXin Li                var col = tdata.col;
734*9c5db199SXin Li
735*9c5db199SXin Li                for (var i=0,L=bodies.length; i<L; i++) {
736*9c5db199SXin Li                        var tb = bodies[i], tbrows = tb.rows, rows = [];
737*9c5db199SXin Li
738*9c5db199SXin Li                        // Allow tbodies to request that they not be sorted
739*9c5db199SXin Li                        if(!hasClass(tb,table.NoSortClassName)) {
740*9c5db199SXin Li                                // Create a separate array which will store the converted values and refs to the
741*9c5db199SXin Li                                // actual rows. This is the array that will be sorted.
742*9c5db199SXin Li                                var cRow, cRowIndex=0;
743*9c5db199SXin Li                                if (cRow=tbrows[cRowIndex]){
744*9c5db199SXin Li                                        // Funky loop style because it's considerably faster in IE
745*9c5db199SXin Li                                        do {
746*9c5db199SXin Li                                                if (rowCells = cRow.cells) {
747*9c5db199SXin Li                                                        var cellValue = (col<rowCells.length)?this.getCellValue(rowCells[col],useinnertext):null;
748*9c5db199SXin Li                                                        if (sortconvert) cellValue = sortconvert(cellValue);
749*9c5db199SXin Li                                                        rows[cRowIndex] = [cellValue,tbrows[cRowIndex]];
750*9c5db199SXin Li                                                }
751*9c5db199SXin Li                                        } while (cRow=tbrows[++cRowIndex])
752*9c5db199SXin Li                                }
753*9c5db199SXin Li
754*9c5db199SXin Li                                // Do the actual sorting
755*9c5db199SXin Li                                rows.sort(newSortFunc);
756*9c5db199SXin Li
757*9c5db199SXin Li                                // Move the rows to the correctly sorted order. Appending an existing DOM object just moves it!
758*9c5db199SXin Li                                cRowIndex=0;
759*9c5db199SXin Li                                var displayedCount=0;
760*9c5db199SXin Li                                var f=[removeClass,addClass];
761*9c5db199SXin Li                                if (cRow=rows[cRowIndex]){
762*9c5db199SXin Li                                        do {
763*9c5db199SXin Li                                                tb.appendChild(cRow[1]);
764*9c5db199SXin Li                                        } while (cRow=rows[++cRowIndex])
765*9c5db199SXin Li                                }
766*9c5db199SXin Li                        }
767*9c5db199SXin Li                }
768*9c5db199SXin Li
769*9c5db199SXin Li                // If paging is enabled on the table, then we need to re-page because the order of rows has changed!
770*9c5db199SXin Li                if (tdata.pagesize) {
771*9c5db199SXin Li                        this.page(t); // This will internally do the striping
772*9c5db199SXin Li                }
773*9c5db199SXin Li                else {
774*9c5db199SXin Li                        // Re-stripe if a class name was supplied
775*9c5db199SXin Li                        if (tdata.stripeclass) {
776*9c5db199SXin Li                                this.stripe(t,tdata.stripeclass,!!tdata.ignorehiddenrows);
777*9c5db199SXin Li                        }
778*9c5db199SXin Li                }
779*9c5db199SXin Li        };
780*9c5db199SXin Li
781*9c5db199SXin Li        /**
782*9c5db199SXin Li        * Apply a filter to rows in a table and hide those that do not match.
783*9c5db199SXin Li        */
784*9c5db199SXin Li        table.filter = function(o,filters,args) {
785*9c5db199SXin Li                var cell;
786*9c5db199SXin Li                args = args || {};
787*9c5db199SXin Li
788*9c5db199SXin Li                var t = this.resolve(o,args);
789*9c5db199SXin Li                var tdata = this.tabledata[t.id];
790*9c5db199SXin Li
791*9c5db199SXin Li                // If new filters were passed in, apply them to the table's list of filters
792*9c5db199SXin Li                if (!filters) {
793*9c5db199SXin Li                        // If a null or blank value was sent in for 'filters' then that means reset the table to no filters
794*9c5db199SXin Li                        tdata.filters = null;
795*9c5db199SXin Li                }
796*9c5db199SXin Li                else {
797*9c5db199SXin Li                        // Allow for passing a select list in as the filter, since this is common design
798*9c5db199SXin Li                        if (filters.nodeName=="SELECT" && filters.type=="select-one" && filters.selectedIndex>-1) {
799*9c5db199SXin Li                                filters={ 'filter':filters.options[filters.selectedIndex].value };
800*9c5db199SXin Li                        }
801*9c5db199SXin Li                        // Also allow for a regular input
802*9c5db199SXin Li                        if (filters.nodeName=="INPUT" && filters.type=="text") {
803*9c5db199SXin Li                                filters={ 'filter':"/"+filters.value+"/" };
804*9c5db199SXin Li                        }
805*9c5db199SXin Li                        // Force filters to be an array
806*9c5db199SXin Li                        if (typeof(filters)=="object" && !filters.length) {
807*9c5db199SXin Li                                filters = [filters];
808*9c5db199SXin Li                        }
809*9c5db199SXin Li
810*9c5db199SXin Li                        // Convert regular expression strings to RegExp objects and function strings to function objects
811*9c5db199SXin Li                        for (var i=0,L=filters.length; i<L; i++) {
812*9c5db199SXin Li                                var filter = filters[i];
813*9c5db199SXin Li                                if (typeof(filter.filter)=="string") {
814*9c5db199SXin Li                                        // If a filter string is like "/expr/" then turn it into a Regex
815*9c5db199SXin Li                                        if (filter.filter.match(/^\/(.*)\/$/)) {
816*9c5db199SXin Li                                                filter.filter = new RegExp(RegExp.$1);
817*9c5db199SXin Li                                                filter.filter.regex=true;
818*9c5db199SXin Li                                        }
819*9c5db199SXin Li                                        // If filter string is like "function (x) { ... }" then turn it into a function
820*9c5db199SXin Li                                        else if (filter.filter.match(/^function\s*\(([^\)]*)\)\s*\{(.*)}\s*$/)) {
821*9c5db199SXin Li                                                filter.filter = Function(RegExp.$1,RegExp.$2);
822*9c5db199SXin Li                                        }
823*9c5db199SXin Li                                }
824*9c5db199SXin Li                                // If some non-table object was passed in rather than a 'col' value, resolve it
825*9c5db199SXin Li                                // and assign it's column index to the filter if it doesn't have one. This way,
826*9c5db199SXin Li                                // passing in a cell reference or a select object etc instead of a table object
827*9c5db199SXin Li                                // will automatically set the correct column to filter.
828*9c5db199SXin Li                                if (filter && !def(filter.col) && (cell=getParent(o,"TD","TH"))) {
829*9c5db199SXin Li                                        filter.col = this.getCellIndex(cell);
830*9c5db199SXin Li                                }
831*9c5db199SXin Li
832*9c5db199SXin Li                                // Apply the passed-in filters to the existing list of filters for the table, removing those that have a filter of null or ""
833*9c5db199SXin Li                                if ((!filter || !filter.filter) && tdata.filters) {
834*9c5db199SXin Li                                        delete tdata.filters[filter.col];
835*9c5db199SXin Li                                }
836*9c5db199SXin Li                                else {
837*9c5db199SXin Li                                        tdata.filters = tdata.filters || {};
838*9c5db199SXin Li                                        tdata.filters[filter.col] = filter.filter;
839*9c5db199SXin Li                                }
840*9c5db199SXin Li                        }
841*9c5db199SXin Li                        // If no more filters are left, then make sure to empty out the filters object
842*9c5db199SXin Li                        for (var j in tdata.filters) { var keep = true; }
843*9c5db199SXin Li                        if (!keep) {
844*9c5db199SXin Li                                tdata.filters = null;
845*9c5db199SXin Li                        }
846*9c5db199SXin Li                }
847*9c5db199SXin Li                // Everything's been setup, so now scrape the table rows
848*9c5db199SXin Li                return table.scrape(o);
849*9c5db199SXin Li        };
850*9c5db199SXin Li
851*9c5db199SXin Li        /**
852*9c5db199SXin Li         * "Page" a table by showing only a subset of the rows
853*9c5db199SXin Li         */
854*9c5db199SXin Li        table.page = function(t,page,args) {
855*9c5db199SXin Li                args = args || {};
856*9c5db199SXin Li                if (def(page)) { args.page = page; }
857*9c5db199SXin Li                return table.scrape(t,args);
858*9c5db199SXin Li        };
859*9c5db199SXin Li
860*9c5db199SXin Li        /**
861*9c5db199SXin Li         * Jump forward or back any number of pages
862*9c5db199SXin Li         */
863*9c5db199SXin Li        table.pageJump = function(t,count,args) {
864*9c5db199SXin Li                t = this.resolve(t,args);
865*9c5db199SXin Li                return this.page(t,(table.tabledata[t.id].page||0)+count,args);
866*9c5db199SXin Li        };
867*9c5db199SXin Li
868*9c5db199SXin Li        /**
869*9c5db199SXin Li         * Go to the next page of a paged table
870*9c5db199SXin Li         */
871*9c5db199SXin Li        table.pageNext = function(t,args) {
872*9c5db199SXin Li                return this.pageJump(t,1,args);
873*9c5db199SXin Li        };
874*9c5db199SXin Li
875*9c5db199SXin Li        /**
876*9c5db199SXin Li         * Go to the previous page of a paged table
877*9c5db199SXin Li         */
878*9c5db199SXin Li        table.pagePrevious = function(t,args) {
879*9c5db199SXin Li                return this.pageJump(t,-1,args);
880*9c5db199SXin Li        };
881*9c5db199SXin Li
882*9c5db199SXin Li        /**
883*9c5db199SXin Li        * Scrape a table to either hide or show each row based on filters and paging
884*9c5db199SXin Li        */
885*9c5db199SXin Li        table.scrape = function(o,args) {
886*9c5db199SXin Li                var col,cell,filterList,filterReset=false,filter;
887*9c5db199SXin Li                var page,pagesize,pagestart,pageend;
888*9c5db199SXin Li                var unfilteredrows=[],unfilteredrowcount=0,totalrows=0;
889*9c5db199SXin Li                var t,tdata,row,hideRow;
890*9c5db199SXin Li                args = args || {};
891*9c5db199SXin Li
892*9c5db199SXin Li                // Resolve the table object
893*9c5db199SXin Li                t = this.resolve(o,args);
894*9c5db199SXin Li                tdata = this.tabledata[t.id];
895*9c5db199SXin Li
896*9c5db199SXin Li                // Setup for Paging
897*9c5db199SXin Li                var page = tdata.page;
898*9c5db199SXin Li                if (def(page)) {
899*9c5db199SXin Li                        // Don't let the page go before the beginning
900*9c5db199SXin Li                        if (page<0) { tdata.page=page=0; }
901*9c5db199SXin Li                        pagesize = tdata.pagesize || 25; // 25=arbitrary default
902*9c5db199SXin Li                        pagestart = page*pagesize+1;
903*9c5db199SXin Li                        pageend = pagestart + pagesize - 1;
904*9c5db199SXin Li                }
905*9c5db199SXin Li
906*9c5db199SXin Li                // Scrape each row of each tbody
907*9c5db199SXin Li                var bodies = t.tBodies;
908*9c5db199SXin Li                if (bodies==null || bodies.length==0) { return; }
909*9c5db199SXin Li                for (var i=0,L=bodies.length; i<L; i++) {
910*9c5db199SXin Li                        var tb = bodies[i];
911*9c5db199SXin Li                        for (var j=0,L2=tb.rows.length; j<L2; j++) {
912*9c5db199SXin Li                                row = tb.rows[j];
913*9c5db199SXin Li                                hideRow = false;
914*9c5db199SXin Li
915*9c5db199SXin Li                                // Test if filters will hide the row
916*9c5db199SXin Li                                if (tdata.filters && row.cells) {
917*9c5db199SXin Li                                        var cells = row.cells;
918*9c5db199SXin Li                                        var cellsLength = cells.length;
919*9c5db199SXin Li                                        // Test each filter
920*9c5db199SXin Li                                        for (col in tdata.filters) {
921*9c5db199SXin Li                                                if (!hideRow) {
922*9c5db199SXin Li                                                        filter = tdata.filters[col];
923*9c5db199SXin Li                                                        if (filter && col<cellsLength) {
924*9c5db199SXin Li                                                                var val = this.getCellValue(cells[col]);
925*9c5db199SXin Li                                                                if (filter.regex && val.search) {
926*9c5db199SXin Li                                                                        hideRow=(val.search(filter)<0);
927*9c5db199SXin Li                                                                }
928*9c5db199SXin Li                                                                else if (typeof(filter)=="function") {
929*9c5db199SXin Li                                                                        hideRow=!filter(val,cells[col]);
930*9c5db199SXin Li                                                                }
931*9c5db199SXin Li                                                                else {
932*9c5db199SXin Li                                                                        hideRow = (val!=filter);
933*9c5db199SXin Li                                                                }
934*9c5db199SXin Li                                                        }
935*9c5db199SXin Li                                                }
936*9c5db199SXin Li                                        }
937*9c5db199SXin Li                                }
938*9c5db199SXin Li
939*9c5db199SXin Li                                // Keep track of the total rows scanned and the total runs _not_ filtered out
940*9c5db199SXin Li                                totalrows++;
941*9c5db199SXin Li                                if (!hideRow) {
942*9c5db199SXin Li                                        unfilteredrowcount++;
943*9c5db199SXin Li                                        if (def(page)) {
944*9c5db199SXin Li                                                // Temporarily keep an array of unfiltered rows in case the page we're on goes past
945*9c5db199SXin Li                                                // the last page and we need to back up. Don't want to filter again!
946*9c5db199SXin Li                                                unfilteredrows.push(row);
947*9c5db199SXin Li                                                if (unfilteredrowcount<pagestart || unfilteredrowcount>pageend) {
948*9c5db199SXin Li                                                        hideRow = true;
949*9c5db199SXin Li                                                }
950*9c5db199SXin Li                                        }
951*9c5db199SXin Li                                }
952*9c5db199SXin Li
953*9c5db199SXin Li                                row.style.display = hideRow?"none":"";
954*9c5db199SXin Li                        }
955*9c5db199SXin Li                }
956*9c5db199SXin Li
957*9c5db199SXin Li                if (def(page)) {
958*9c5db199SXin Li                        // Check to see if filtering has put us past the requested page index. If it has,
959*9c5db199SXin Li                        // then go back to the last page and show it.
960*9c5db199SXin Li                        if (pagestart>=unfilteredrowcount) {
961*9c5db199SXin Li                                pagestart = unfilteredrowcount-(unfilteredrowcount%pagesize);
962*9c5db199SXin Li                                tdata.page = page = pagestart/pagesize;
963*9c5db199SXin Li                                for (var i=pagestart,L=unfilteredrows.length; i<L; i++) {
964*9c5db199SXin Li                                        unfilteredrows[i].style.display="";
965*9c5db199SXin Li                                }
966*9c5db199SXin Li                        }
967*9c5db199SXin Li                }
968*9c5db199SXin Li
969*9c5db199SXin Li                // Loop through all THEADs and add/remove filtered class names
970*9c5db199SXin Li                this.processTableCells(t,"THEAD",
971*9c5db199SXin Li                        function(c) {
972*9c5db199SXin Li                                ((tdata.filters && def(tdata.filters[table.getCellIndex(c)]) && hasClass(c,table.FilterableClassName))?addClass:removeClass)(c,table.FilteredClassName);
973*9c5db199SXin Li                        }
974*9c5db199SXin Li                );
975*9c5db199SXin Li
976*9c5db199SXin Li                // Stripe the table if necessary
977*9c5db199SXin Li                if (tdata.stripeclass) {
978*9c5db199SXin Li                        this.stripe(t);
979*9c5db199SXin Li                }
980*9c5db199SXin Li
981*9c5db199SXin Li                // Calculate some values to be returned for info and updating purposes
982*9c5db199SXin Li                var pagecount = Math.floor(unfilteredrowcount/pagesize)+1;
983*9c5db199SXin Li                if (def(page)) {
984*9c5db199SXin Li                        // Update the page number/total containers if they exist
985*9c5db199SXin Li                        if (tdata.container_number) {
986*9c5db199SXin Li                                tdata.container_number.innerHTML = page+1;
987*9c5db199SXin Li                        }
988*9c5db199SXin Li                        if (tdata.container_count) {
989*9c5db199SXin Li                                tdata.container_count.innerHTML = pagecount;
990*9c5db199SXin Li                        }
991*9c5db199SXin Li                }
992*9c5db199SXin Li
993*9c5db199SXin Li                // Update the row count containers if they exist
994*9c5db199SXin Li                if (tdata.container_filtered_count) {
995*9c5db199SXin Li                        tdata.container_filtered_count.innerHTML = unfilteredrowcount;
996*9c5db199SXin Li                }
997*9c5db199SXin Li                if (tdata.container_all_count) {
998*9c5db199SXin Li                        tdata.container_all_count.innerHTML = totalrows;
999*9c5db199SXin Li                }
1000*9c5db199SXin Li                return { 'data':tdata, 'unfilteredcount':unfilteredrowcount, 'total':totalrows, 'pagecount':pagecount, 'page':page, 'pagesize':pagesize };
1001*9c5db199SXin Li        };
1002*9c5db199SXin Li
1003*9c5db199SXin Li        /**
1004*9c5db199SXin Li         * Shade alternate rows, aka Stripe the table.
1005*9c5db199SXin Li         */
1006*9c5db199SXin Li        table.stripe = function(t,className,args) {
1007*9c5db199SXin Li                args = args || {};
1008*9c5db199SXin Li                args.stripeclass = className;
1009*9c5db199SXin Li
1010*9c5db199SXin Li                t = this.resolve(t,args);
1011*9c5db199SXin Li                var tdata = this.tabledata[t.id];
1012*9c5db199SXin Li
1013*9c5db199SXin Li                var bodies = t.tBodies;
1014*9c5db199SXin Li                if (bodies==null || bodies.length==0) {
1015*9c5db199SXin Li                        return;
1016*9c5db199SXin Li                }
1017*9c5db199SXin Li
1018*9c5db199SXin Li                className = tdata.stripeclass;
1019*9c5db199SXin Li                // Cache a shorter, quicker reference to either the remove or add class methods
1020*9c5db199SXin Li                var f=[removeClass,addClass];
1021*9c5db199SXin Li                for (var i=0,L=bodies.length; i<L; i++) {
1022*9c5db199SXin Li                        var tb = bodies[i], tbrows = tb.rows, cRowIndex=0, cRow, displayedCount=0;
1023*9c5db199SXin Li                        if (cRow=tbrows[cRowIndex]){
1024*9c5db199SXin Li                                // The ignorehiddenrows test is pulled out of the loop for a slight speed increase.
1025*9c5db199SXin Li                                // Makes a bigger difference in FF than in IE.
1026*9c5db199SXin Li                                // In this case, speed always wins over brevity!
1027*9c5db199SXin Li                                if (tdata.ignoreHiddenRows) {
1028*9c5db199SXin Li                                        do {
1029*9c5db199SXin Li                                                f[displayedCount++%2](cRow,className);
1030*9c5db199SXin Li                                        } while (cRow=tbrows[++cRowIndex])
1031*9c5db199SXin Li                                }
1032*9c5db199SXin Li                                else {
1033*9c5db199SXin Li                                        do {
1034*9c5db199SXin Li                                                if (!isHidden(cRow)) {
1035*9c5db199SXin Li                                                        f[displayedCount++%2](cRow,className);
1036*9c5db199SXin Li                                                }
1037*9c5db199SXin Li                                        } while (cRow=tbrows[++cRowIndex])
1038*9c5db199SXin Li                                }
1039*9c5db199SXin Li                        }
1040*9c5db199SXin Li                }
1041*9c5db199SXin Li        };
1042*9c5db199SXin Li
1043*9c5db199SXin Li        /**
1044*9c5db199SXin Li         * Build up a list of unique values in a table column
1045*9c5db199SXin Li         */
1046*9c5db199SXin Li        table.getUniqueColValues = function(t,col) {
1047*9c5db199SXin Li                var values={}, bodies = this.resolve(t).tBodies;
1048*9c5db199SXin Li                for (var i=0,L=bodies.length; i<L; i++) {
1049*9c5db199SXin Li                        var tbody = bodies[i];
1050*9c5db199SXin Li                        for (var r=0,L2=tbody.rows.length; r<L2; r++) {
1051*9c5db199SXin Li                                values[this.getCellValue(tbody.rows[r].cells[col])] = true;
1052*9c5db199SXin Li                        }
1053*9c5db199SXin Li                }
1054*9c5db199SXin Li                var valArray = [];
1055*9c5db199SXin Li                for (var val in values) {
1056*9c5db199SXin Li                        valArray.push(val);
1057*9c5db199SXin Li                }
1058*9c5db199SXin Li                return valArray.sort();
1059*9c5db199SXin Li        };
1060*9c5db199SXin Li
1061*9c5db199SXin Li        /**
1062*9c5db199SXin Li         * Scan the document on load and add sorting, filtering, paging etc ability automatically
1063*9c5db199SXin Li         * based on existence of class names on the table and cells.
1064*9c5db199SXin Li         */
1065*9c5db199SXin Li        table.auto = function(args) {
1066*9c5db199SXin Li                var cells = [], tables = document.getElementsByTagName("TABLE");
1067*9c5db199SXin Li                var val,tdata;
1068*9c5db199SXin Li                if (tables!=null) {
1069*9c5db199SXin Li                        for (var i=0,L=tables.length; i<L; i++) {
1070*9c5db199SXin Li                                var t = table.resolve(tables[i]);
1071*9c5db199SXin Li                                tdata = table.tabledata[t.id];
1072*9c5db199SXin Li                                if (val=classValue(t,table.StripeClassNamePrefix)) {
1073*9c5db199SXin Li                                        tdata.stripeclass=val;
1074*9c5db199SXin Li                                }
1075*9c5db199SXin Li                                // Do auto-filter if necessary
1076*9c5db199SXin Li                                if (hasClass(t,table.AutoFilterClassName)) {
1077*9c5db199SXin Li                                        table.autofilter(t);
1078*9c5db199SXin Li                                }
1079*9c5db199SXin Li                                // Do auto-page if necessary
1080*9c5db199SXin Li                                if (val = classValue(t,table.AutoPageSizePrefix)) {
1081*9c5db199SXin Li                                        table.autopage(t,{'pagesize':+val});
1082*9c5db199SXin Li                                }
1083*9c5db199SXin Li                                // Do auto-sort if necessary
1084*9c5db199SXin Li                                if ((val = classValue(t,table.AutoSortColumnPrefix)) || (hasClass(t,table.AutoSortClassName))) {
1085*9c5db199SXin Li                                        table.autosort(t,{'col':(val==null)?null:+val});
1086*9c5db199SXin Li                                }
1087*9c5db199SXin Li                                // Do auto-stripe if necessary
1088*9c5db199SXin Li                                if (tdata.stripeclass && hasClass(t,table.AutoStripeClassName)) {
1089*9c5db199SXin Li                                        table.stripe(t);
1090*9c5db199SXin Li                                }
1091*9c5db199SXin Li                        }
1092*9c5db199SXin Li                }
1093*9c5db199SXin Li        };
1094*9c5db199SXin Li
1095*9c5db199SXin Li        /**
1096*9c5db199SXin Li         * Add sorting functionality to a table header cell
1097*9c5db199SXin Li         */
1098*9c5db199SXin Li        table.autosort = function(t,args) {
1099*9c5db199SXin Li                t = this.resolve(t,args);
1100*9c5db199SXin Li                var tdata = this.tabledata[t.id];
1101*9c5db199SXin Li                this.processTableCells(t, "THEAD", function(c) {
1102*9c5db199SXin Li                        var type = classValue(c,table.SortableColumnPrefix);
1103*9c5db199SXin Li                        if (type!=null) {
1104*9c5db199SXin Li                                type = type || "default";
1105*9c5db199SXin Li                                c.title =c.title || table.AutoSortTitle;
1106*9c5db199SXin Li                                addClass(c,table.SortableClassName);
1107*9c5db199SXin Li                                c.onclick = Function("","Table.sort(this,{'sorttype':Sort['"+type+"']})");
1108*9c5db199SXin Li                                // If we are going to auto sort on a column, we need to keep track of what kind of sort it will be
1109*9c5db199SXin Li                                if (args.col!=null) {
1110*9c5db199SXin Li                                        if (args.col==table.getActualCellIndex(c)) {
1111*9c5db199SXin Li                                                tdata.sorttype=Sort['"+type+"'];
1112*9c5db199SXin Li                                        }
1113*9c5db199SXin Li                                }
1114*9c5db199SXin Li                        }
1115*9c5db199SXin Li                } );
1116*9c5db199SXin Li                if (args.col!=null) {
1117*9c5db199SXin Li                        table.sort(t,args);
1118*9c5db199SXin Li                }
1119*9c5db199SXin Li        };
1120*9c5db199SXin Li
1121*9c5db199SXin Li        /**
1122*9c5db199SXin Li         * Add paging functionality to a table
1123*9c5db199SXin Li         */
1124*9c5db199SXin Li        table.autopage = function(t,args) {
1125*9c5db199SXin Li                t = this.resolve(t,args);
1126*9c5db199SXin Li                var tdata = this.tabledata[t.id];
1127*9c5db199SXin Li                if (tdata.pagesize) {
1128*9c5db199SXin Li                        this.processTableCells(t, "THEAD,TFOOT", function(c) {
1129*9c5db199SXin Li                                var type = classValue(c,table.AutoPageJumpPrefix);
1130*9c5db199SXin Li                                if (type=="next") { type = 1; }
1131*9c5db199SXin Li                                else if (type=="previous") { type = -1; }
1132*9c5db199SXin Li                                if (type!=null) {
1133*9c5db199SXin Li                                        c.onclick = Function("","Table.pageJump(this,"+type+")");
1134*9c5db199SXin Li                                }
1135*9c5db199SXin Li                        } );
1136*9c5db199SXin Li                        if (val = classValue(t,table.PageNumberPrefix)) {
1137*9c5db199SXin Li                                tdata.container_number = document.getElementById(val);
1138*9c5db199SXin Li                        }
1139*9c5db199SXin Li                        if (val = classValue(t,table.PageCountPrefix)) {
1140*9c5db199SXin Li                                tdata.container_count = document.getElementById(val);
1141*9c5db199SXin Li                        }
1142*9c5db199SXin Li                        return table.page(t,0,args);
1143*9c5db199SXin Li                }
1144*9c5db199SXin Li        };
1145*9c5db199SXin Li
1146*9c5db199SXin Li        /**
1147*9c5db199SXin Li         * A util function to cancel bubbling of clicks on filter dropdowns
1148*9c5db199SXin Li         */
1149*9c5db199SXin Li        table.cancelBubble = function(e) {
1150*9c5db199SXin Li                e = e || window.event;
1151*9c5db199SXin Li                if (typeof(e.stopPropagation)=="function") { e.stopPropagation(); }
1152*9c5db199SXin Li                if (def(e.cancelBubble)) { e.cancelBubble = true; }
1153*9c5db199SXin Li        };
1154*9c5db199SXin Li
1155*9c5db199SXin Li        /**
1156*9c5db199SXin Li         * Auto-filter a table
1157*9c5db199SXin Li         */
1158*9c5db199SXin Li        table.autofilter = function(t,args) {
1159*9c5db199SXin Li                args = args || {};
1160*9c5db199SXin Li                t = this.resolve(t,args);
1161*9c5db199SXin Li                var tdata = this.tabledata[t.id],val;
1162*9c5db199SXin Li                table.processTableCells(t, "THEAD", function(cell) {
1163*9c5db199SXin Li                        if (hasClass(cell,table.FilterableClassName)) {
1164*9c5db199SXin Li                                var cellIndex = table.getCellIndex(cell);
1165*9c5db199SXin Li                                var colValues = table.getUniqueColValues(t,cellIndex);
1166*9c5db199SXin Li                                if (colValues.length>0) {
1167*9c5db199SXin Li                                        if (typeof(args.insert)=="function") {
1168*9c5db199SXin Li                                                func.insert(cell,colValues);
1169*9c5db199SXin Li                                        }
1170*9c5db199SXin Li                                        else {
1171*9c5db199SXin Li                                                var sel = '<select onchange="Table.filter(this,this)" onclick="Table.cancelBubble(event)" class="'+table.AutoFilterClassName+'"><option value="">'+table.FilterAllLabel+'</option>';
1172*9c5db199SXin Li                                                for (var i=0; i<colValues.length; i++) {
1173*9c5db199SXin Li                                                        sel += '<option value="'+colValues[i]+'">'+colValues[i]+'</option>';
1174*9c5db199SXin Li                                                }
1175*9c5db199SXin Li                                                sel += '</select>';
1176*9c5db199SXin Li                                                cell.innerHTML += "<br>"+sel;
1177*9c5db199SXin Li                                        }
1178*9c5db199SXin Li                                }
1179*9c5db199SXin Li                        }
1180*9c5db199SXin Li                });
1181*9c5db199SXin Li                if (val = classValue(t,table.FilteredRowcountPrefix)) {
1182*9c5db199SXin Li                        tdata.container_filtered_count = document.getElementById(val);
1183*9c5db199SXin Li                }
1184*9c5db199SXin Li                if (val = classValue(t,table.RowcountPrefix)) {
1185*9c5db199SXin Li                        tdata.container_all_count = document.getElementById(val);
1186*9c5db199SXin Li                }
1187*9c5db199SXin Li        };
1188*9c5db199SXin Li
1189*9c5db199SXin Li        /**
1190*9c5db199SXin Li         * Attach the auto event so it happens on load.
1191*9c5db199SXin Li         * use jQuery's ready() function if available
1192*9c5db199SXin Li         */
1193*9c5db199SXin Li        if (typeof(jQuery)!="undefined") {
1194*9c5db199SXin Li                jQuery(table.auto);
1195*9c5db199SXin Li        }
1196*9c5db199SXin Li        else if (window.addEventListener) {
1197*9c5db199SXin Li                window.addEventListener( "load", table.auto, false );
1198*9c5db199SXin Li        }
1199*9c5db199SXin Li        else if (window.attachEvent) {
1200*9c5db199SXin Li                window.attachEvent( "onload", table.auto );
1201*9c5db199SXin Li        }
1202*9c5db199SXin Li
1203*9c5db199SXin Li        return table;
1204*9c5db199SXin Li})();
1205*9c5db199SXin Li"""
1206*9c5db199SXin Li
1207*9c5db199SXin Li
1208*9c5db199SXin Limaketree_js = """/**
1209*9c5db199SXin Li * Copyright (c)2005-2007 Matt Kruse (javascripttoolbox.com)
1210*9c5db199SXin Li *
1211*9c5db199SXin Li * Dual licensed under the MIT and GPL licenses.
1212*9c5db199SXin Li * This basically means you can use this code however you want for
1213*9c5db199SXin Li * free, but don't claim to have written it yourself!
1214*9c5db199SXin Li * Donations always accepted: http://www.JavascriptToolbox.com/donate/
1215*9c5db199SXin Li *
1216*9c5db199SXin Li * Please do not link to the .js files on javascripttoolbox.com from
1217*9c5db199SXin Li * your site. Copy the files locally to your server instead.
1218*9c5db199SXin Li *
1219*9c5db199SXin Li */
1220*9c5db199SXin Li/*
1221*9c5db199SXin LiThis code is inspired by and extended from Stuart Langridge's aqlist code:
1222*9c5db199SXin Li    http://www.kryogenix.org/code/browser/aqlists/
1223*9c5db199SXin Li    Stuart Langridge, November 2002
1224*9c5db199SXin Li    [email protected]
1225*9c5db199SXin Li    Inspired by Aaron's labels.js (http://youngpup.net/demos/labels/)
1226*9c5db199SXin Li    and Dave Lindquist's menuDropDown.js (http://www.gazingus.org/dhtml/?id=109)
1227*9c5db199SXin Li*/
1228*9c5db199SXin Li
1229*9c5db199SXin Li// Automatically attach a listener to the window onload, to convert the trees
1230*9c5db199SXin LiaddEvent(window,"load",convertTrees);
1231*9c5db199SXin Li
1232*9c5db199SXin Li// Utility function to add an event listener
1233*9c5db199SXin Lifunction addEvent(o,e,f){
1234*9c5db199SXin Li  if (o.addEventListener){ o.addEventListener(e,f,false); return true; }
1235*9c5db199SXin Li  else if (o.attachEvent){ return o.attachEvent("on"+e,f); }
1236*9c5db199SXin Li  else { return false; }
1237*9c5db199SXin Li}
1238*9c5db199SXin Li
1239*9c5db199SXin Li// utility function to set a global variable if it is not already set
1240*9c5db199SXin Lifunction setDefault(name,val) {
1241*9c5db199SXin Li  if (typeof(window[name])=="undefined" || window[name]==null) {
1242*9c5db199SXin Li    window[name]=val;
1243*9c5db199SXin Li  }
1244*9c5db199SXin Li}
1245*9c5db199SXin Li
1246*9c5db199SXin Li// Full expands a tree with a given ID
1247*9c5db199SXin Lifunction expandTree(treeId) {
1248*9c5db199SXin Li  var ul = document.getElementById(treeId);
1249*9c5db199SXin Li  if (ul == null) { return false; }
1250*9c5db199SXin Li  expandCollapseList(ul,nodeOpenClass);
1251*9c5db199SXin Li}
1252*9c5db199SXin Li
1253*9c5db199SXin Li// Fully collapses a tree with a given ID
1254*9c5db199SXin Lifunction collapseTree(treeId) {
1255*9c5db199SXin Li  var ul = document.getElementById(treeId);
1256*9c5db199SXin Li  if (ul == null) { return false; }
1257*9c5db199SXin Li  expandCollapseList(ul,nodeClosedClass);
1258*9c5db199SXin Li}
1259*9c5db199SXin Li
1260*9c5db199SXin Li// Expands enough nodes to expose an LI with a given ID
1261*9c5db199SXin Lifunction expandToItem(treeId,itemId) {
1262*9c5db199SXin Li  var ul = document.getElementById(treeId);
1263*9c5db199SXin Li  if (ul == null) { return false; }
1264*9c5db199SXin Li  var ret = expandCollapseList(ul,nodeOpenClass,itemId);
1265*9c5db199SXin Li  if (ret) {
1266*9c5db199SXin Li    var o = document.getElementById(itemId);
1267*9c5db199SXin Li    if (o.scrollIntoView) {
1268*9c5db199SXin Li      o.scrollIntoView(false);
1269*9c5db199SXin Li    }
1270*9c5db199SXin Li  }
1271*9c5db199SXin Li}
1272*9c5db199SXin Li
1273*9c5db199SXin Li// Performs 3 functions:
1274*9c5db199SXin Li// a) Expand all nodes
1275*9c5db199SXin Li// b) Collapse all nodes
1276*9c5db199SXin Li// c) Expand all nodes to reach a certain ID
1277*9c5db199SXin Lifunction expandCollapseList(ul,cName,itemId) {
1278*9c5db199SXin Li  if (!ul.childNodes || ul.childNodes.length==0) { return false; }
1279*9c5db199SXin Li  // Iterate LIs
1280*9c5db199SXin Li  for (var itemi=0;itemi<ul.childNodes.length;itemi++) {
1281*9c5db199SXin Li    var item = ul.childNodes[itemi];
1282*9c5db199SXin Li    if (itemId!=null && item.id==itemId) { return true; }
1283*9c5db199SXin Li    if (item.nodeName == "LI") {
1284*9c5db199SXin Li      // Iterate things in this LI
1285*9c5db199SXin Li      var subLists = false;
1286*9c5db199SXin Li      for (var sitemi=0;sitemi<item.childNodes.length;sitemi++) {
1287*9c5db199SXin Li        var sitem = item.childNodes[sitemi];
1288*9c5db199SXin Li        if (sitem.nodeName=="UL") {
1289*9c5db199SXin Li          subLists = true;
1290*9c5db199SXin Li          var ret = expandCollapseList(sitem,cName,itemId);
1291*9c5db199SXin Li          if (itemId!=null && ret) {
1292*9c5db199SXin Li            item.className=cName;
1293*9c5db199SXin Li            return true;
1294*9c5db199SXin Li          }
1295*9c5db199SXin Li        }
1296*9c5db199SXin Li      }
1297*9c5db199SXin Li      if (subLists && itemId==null) {
1298*9c5db199SXin Li        item.className = cName;
1299*9c5db199SXin Li      }
1300*9c5db199SXin Li    }
1301*9c5db199SXin Li  }
1302*9c5db199SXin Li}
1303*9c5db199SXin Li
1304*9c5db199SXin Li// Search the document for UL elements with the correct CLASS name, then process them
1305*9c5db199SXin Lifunction convertTrees() {
1306*9c5db199SXin Li  setDefault("treeClass","mktree");
1307*9c5db199SXin Li  setDefault("nodeClosedClass","liClosed");
1308*9c5db199SXin Li  setDefault("nodeOpenClass","liOpen");
1309*9c5db199SXin Li  setDefault("nodeBulletClass","liBullet");
1310*9c5db199SXin Li  setDefault("nodeLinkClass","bullet");
1311*9c5db199SXin Li  setDefault("preProcessTrees",true);
1312*9c5db199SXin Li  if (preProcessTrees) {
1313*9c5db199SXin Li    if (!document.createElement) { return; } // Without createElement, we can't do anything
1314*9c5db199SXin Li    var uls = document.getElementsByTagName("ul");
1315*9c5db199SXin Li    if (uls==null) { return; }
1316*9c5db199SXin Li    var uls_length = uls.length;
1317*9c5db199SXin Li    for (var uli=0;uli<uls_length;uli++) {
1318*9c5db199SXin Li      var ul=uls[uli];
1319*9c5db199SXin Li      if (ul.nodeName=="UL" && ul.className==treeClass) {
1320*9c5db199SXin Li        processList(ul);
1321*9c5db199SXin Li      }
1322*9c5db199SXin Li    }
1323*9c5db199SXin Li  }
1324*9c5db199SXin Li}
1325*9c5db199SXin Li
1326*9c5db199SXin Lifunction treeNodeOnclick() {
1327*9c5db199SXin Li  this.parentNode.className = (this.parentNode.className==nodeOpenClass) ? nodeClosedClass : nodeOpenClass;
1328*9c5db199SXin Li  return false;
1329*9c5db199SXin Li}
1330*9c5db199SXin Lifunction retFalse() {
1331*9c5db199SXin Li  return false;
1332*9c5db199SXin Li}
1333*9c5db199SXin Li// Process a UL tag and all its children, to convert to a tree
1334*9c5db199SXin Lifunction processList(ul) {
1335*9c5db199SXin Li  if (!ul.childNodes || ul.childNodes.length==0) { return; }
1336*9c5db199SXin Li  // Iterate LIs
1337*9c5db199SXin Li  var childNodesLength = ul.childNodes.length;
1338*9c5db199SXin Li  for (var itemi=0;itemi<childNodesLength;itemi++) {
1339*9c5db199SXin Li    var item = ul.childNodes[itemi];
1340*9c5db199SXin Li    if (item.nodeName == "LI") {
1341*9c5db199SXin Li      // Iterate things in this LI
1342*9c5db199SXin Li      var subLists = false;
1343*9c5db199SXin Li      var itemChildNodesLength = item.childNodes.length;
1344*9c5db199SXin Li      for (var sitemi=0;sitemi<itemChildNodesLength;sitemi++) {
1345*9c5db199SXin Li        var sitem = item.childNodes[sitemi];
1346*9c5db199SXin Li        if (sitem.nodeName=="UL") {
1347*9c5db199SXin Li          subLists = true;
1348*9c5db199SXin Li          processList(sitem);
1349*9c5db199SXin Li        }
1350*9c5db199SXin Li      }
1351*9c5db199SXin Li      var s= document.createElement("SPAN");
1352*9c5db199SXin Li      var t= '\u00A0'; // &nbsp;
1353*9c5db199SXin Li      s.className = nodeLinkClass;
1354*9c5db199SXin Li      if (subLists) {
1355*9c5db199SXin Li        // This LI has UL's in it, so it's a +/- node
1356*9c5db199SXin Li        if (item.className==null || item.className=="") {
1357*9c5db199SXin Li          item.className = nodeClosedClass;
1358*9c5db199SXin Li        }
1359*9c5db199SXin Li        // If it's just text, make the text work as the link also
1360*9c5db199SXin Li        if (item.firstChild.nodeName=="#text") {
1361*9c5db199SXin Li          t = t+item.firstChild.nodeValue;
1362*9c5db199SXin Li          item.removeChild(item.firstChild);
1363*9c5db199SXin Li        }
1364*9c5db199SXin Li        s.onclick = treeNodeOnclick;
1365*9c5db199SXin Li      }
1366*9c5db199SXin Li      else {
1367*9c5db199SXin Li        // No sublists, so it's just a bullet node
1368*9c5db199SXin Li        item.className = nodeBulletClass;
1369*9c5db199SXin Li        s.onclick = retFalse;
1370*9c5db199SXin Li      }
1371*9c5db199SXin Li      s.appendChild(document.createTextNode(t));
1372*9c5db199SXin Li      item.insertBefore(s,item.firstChild);
1373*9c5db199SXin Li    }
1374*9c5db199SXin Li  }
1375*9c5db199SXin Li}
1376*9c5db199SXin Li"""
1377*9c5db199SXin Li
1378*9c5db199SXin Li
1379*9c5db199SXin Li
1380*9c5db199SXin Li
1381*9c5db199SXin Lidef make_html_file(metadata, results, tag, host, output_file_name, dirname):
1382*9c5db199SXin Li    """
1383*9c5db199SXin Li    Create HTML file contents for the job report, to stdout or filesystem.
1384*9c5db199SXin Li
1385*9c5db199SXin Li    @param metadata: Dictionary with Job metadata (tests, exec time, etc).
1386*9c5db199SXin Li    @param results: List with testcase results.
1387*9c5db199SXin Li    @param tag: Job tag.
1388*9c5db199SXin Li    @param host: Client hostname.
1389*9c5db199SXin Li    @param output_file_name: Output file name. If empty string, prints to
1390*9c5db199SXin Li            stdout.
1391*9c5db199SXin Li    @param dirname: Prefix for HTML links. If empty string, the HTML links
1392*9c5db199SXin Li            will be relative to the results dir.
1393*9c5db199SXin Li    """
1394*9c5db199SXin Li    html_prefix = """
1395*9c5db199SXin Li<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1396*9c5db199SXin Li<html>
1397*9c5db199SXin Li<head>
1398*9c5db199SXin Li<title>Autotest job execution results</title>
1399*9c5db199SXin Li<style type="text/css">
1400*9c5db199SXin Li%s
1401*9c5db199SXin Li</style>
1402*9c5db199SXin Li<script type="text/javascript">
1403*9c5db199SXin Li%s
1404*9c5db199SXin Li%s
1405*9c5db199SXin Lifunction popup(tag,text) {
1406*9c5db199SXin Livar w = window.open('', tag, 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes, copyhistory=no,width=600,height=300,top=20,left=100');
1407*9c5db199SXin Liw.document.open("text/html", "replace");
1408*9c5db199SXin Liw.document.write(text);
1409*9c5db199SXin Liw.document.close();
1410*9c5db199SXin Lireturn true;
1411*9c5db199SXin Li}
1412*9c5db199SXin Li</script>
1413*9c5db199SXin Li</head>
1414*9c5db199SXin Li<body>
1415*9c5db199SXin Li""" % (format_css, table_js, maketree_js)
1416*9c5db199SXin Li
1417*9c5db199SXin Li    if output_file_name:
1418*9c5db199SXin Li        output = open(output_file_name, "w")
1419*9c5db199SXin Li    else:   #if no output file defined, print html file to console
1420*9c5db199SXin Li        output = sys.stdout
1421*9c5db199SXin Li    # create html page
1422*9c5db199SXin Li    print(html_prefix, file=output)
1423*9c5db199SXin Li    print('<h2 id=\"page_title\">Autotest job execution report</h2>', file=output)
1424*9c5db199SXin Li
1425*9c5db199SXin Li    # formating date and time to print
1426*9c5db199SXin Li    t = datetime.datetime.now()
1427*9c5db199SXin Li
1428*9c5db199SXin Li    epoch_sec = time.mktime(t.timetuple())
1429*9c5db199SXin Li    now = datetime.datetime.fromtimestamp(epoch_sec)
1430*9c5db199SXin Li
1431*9c5db199SXin Li    # basic statistics
1432*9c5db199SXin Li    total_executed = 0
1433*9c5db199SXin Li    total_failed = 0
1434*9c5db199SXin Li    total_passed = 0
1435*9c5db199SXin Li    for res in results:
1436*9c5db199SXin Li        if results[res][2] != None:
1437*9c5db199SXin Li            total_executed += 1
1438*9c5db199SXin Li            if results[res][2]['status'] == 'GOOD':
1439*9c5db199SXin Li                total_passed += 1
1440*9c5db199SXin Li            else:
1441*9c5db199SXin Li                total_failed += 1
1442*9c5db199SXin Li    stat_str = 'No test cases executed'
1443*9c5db199SXin Li    if total_executed > 0:
1444*9c5db199SXin Li        failed_perct = int(float(total_failed)/float(total_executed)*100)
1445*9c5db199SXin Li        stat_str = ('From %d tests executed, %d have passed (%d%% failures)' %
1446*9c5db199SXin Li                    (total_executed, total_passed, failed_perct))
1447*9c5db199SXin Li
1448*9c5db199SXin Li    kvm_ver_str = metadata.get('kvmver', None)
1449*9c5db199SXin Li
1450*9c5db199SXin Li    print('<table class="stats2">', file=output)
1451*9c5db199SXin Li    print('<tr><td>HOST</td><td>:</td><td>%s</td></tr>' % host, file=output)
1452*9c5db199SXin Li    print('<tr><td>RESULTS DIR</td><td>:</td><td>%s</td></tr>'  % tag, file=output)
1453*9c5db199SXin Li    print('<tr><td>DATE</td><td>:</td><td>%s</td></tr>' % now.ctime(), file=output)
1454*9c5db199SXin Li    print('<tr><td>STATS</td><td>:</td><td>%s</td></tr>'% stat_str, file=output)
1455*9c5db199SXin Li    print('<tr><td></td><td></td><td></td></tr>', file=output)
1456*9c5db199SXin Li    if kvm_ver_str is not None:
1457*9c5db199SXin Li        print('<tr><td>KVM VERSION</td><td>:</td><td>%s</td></tr>' % kvm_ver_str, file=output)
1458*9c5db199SXin Li    print('</table>', file=output)
1459*9c5db199SXin Li
1460*9c5db199SXin Li    ## print test results
1461*9c5db199SXin Li    print('<br>', file=output)
1462*9c5db199SXin Li    print('<h2 id=\"page_sub_title\">Test Results</h2>', file=output)
1463*9c5db199SXin Li    print('<h2 id=\"comment\">click on table headers to asc/desc sort</h2>', file=output)
1464*9c5db199SXin Li    result_table_prefix = """<table
1465*9c5db199SXin Liid="t1" class="stats table-autosort:4 table-autofilter table-stripeclass:alternate table-page-number:t1page table-page-count:t1pages table-filtered-rowcount:t1filtercount table-rowcount:t1allcount">
1466*9c5db199SXin Li<thead class="th table-sorted-asc table-sorted-desc">
1467*9c5db199SXin Li<tr>
1468*9c5db199SXin Li<th align="left" class="table-sortable:alphanumeric">Date/Time</th>
1469*9c5db199SXin Li<th align="left" class="filterable table-sortable:alphanumeric">Test Case<br><input name="tc_filter" size="10" onkeyup="Table.filter(this,this)" onclick="Table.cancelBubble(event)"></th>
1470*9c5db199SXin Li<th align="left" class="table-filterable table-sortable:alphanumeric">Status</th>
1471*9c5db199SXin Li<th align="left">Time (sec)</th>
1472*9c5db199SXin Li<th align="left">Info</th>
1473*9c5db199SXin Li<th align="left">Debug</th>
1474*9c5db199SXin Li</tr></thead>
1475*9c5db199SXin Li<tbody>
1476*9c5db199SXin Li"""
1477*9c5db199SXin Li    print(result_table_prefix, file=output)
1478*9c5db199SXin Li    def print_result(result, indent):
1479*9c5db199SXin Li        while result != []:
1480*9c5db199SXin Li            r = result.pop(0)
1481*9c5db199SXin Li            res = results[r][2]
1482*9c5db199SXin Li            print('<tr>', file=output)
1483*9c5db199SXin Li            print('<td align="left">%s</td>' % res['time'], file=output)
1484*9c5db199SXin Li            print('<td align="left" style="padding-left:%dpx">%s</td>' % (indent * 20, res['title']), file=output)
1485*9c5db199SXin Li            if res['status'] == 'GOOD':
1486*9c5db199SXin Li                print('<td align=\"left\"><b><font color="#00CC00">PASS</font></b></td>', file=output)
1487*9c5db199SXin Li            elif res['status'] == 'FAIL':
1488*9c5db199SXin Li                print('<td align=\"left\"><b><font color="red">FAIL</font></b></td>', file=output)
1489*9c5db199SXin Li            elif res['status'] == 'ERROR':
1490*9c5db199SXin Li                print('<td align=\"left\"><b><font color="red">ERROR!</font></b></td>', file=output)
1491*9c5db199SXin Li            else:
1492*9c5db199SXin Li                print('<td align=\"left\">%s</td>' % res['status'], file=output)
1493*9c5db199SXin Li            # print exec time (seconds)
1494*9c5db199SXin Li            print('<td align="left">%s</td>' % res['exec_time_sec'], file=output)
1495*9c5db199SXin Li            # print log only if test failed..
1496*9c5db199SXin Li            if res['log']:
1497*9c5db199SXin Li                #chop all '\n' from log text (to prevent html errors)
1498*9c5db199SXin Li                rx1 = re.compile('(\s+)')
1499*9c5db199SXin Li                log_text = rx1.sub(' ', res['log'])
1500*9c5db199SXin Li
1501*9c5db199SXin Li                # allow only a-zA-Z0-9_ in html title name
1502*9c5db199SXin Li                # (due to bug in MS-explorer)
1503*9c5db199SXin Li                rx2 = re.compile('([^a-zA-Z_0-9])')
1504*9c5db199SXin Li                updated_tag = rx2.sub('_', res['title'])
1505*9c5db199SXin Li
1506*9c5db199SXin Li                html_body_text = '<html><head><title>%s</title></head><body>%s</body></html>' % (str(updated_tag), log_text)
1507*9c5db199SXin Li                print('<td align=\"left\"><A HREF=\"#\" onClick=\"popup(\'%s\',\'%s\')\">Info</A></td>' % (str(updated_tag), str(html_body_text)), file=output)
1508*9c5db199SXin Li            else:
1509*9c5db199SXin Li                print('<td align=\"left\"></td>', file=output)
1510*9c5db199SXin Li            # print execution time
1511*9c5db199SXin Li            print('<td align="left"><A HREF=\"%s\">Debug</A></td>' % os.path.join(dirname, res['subdir'], "debug"), file=output)
1512*9c5db199SXin Li
1513*9c5db199SXin Li            print('</tr>', file=output)
1514*9c5db199SXin Li            print_result(results[r][1], indent + 1)
1515*9c5db199SXin Li
1516*9c5db199SXin Li    print_result(results[""][1], 0)
1517*9c5db199SXin Li    print("</tbody></table>", file=output)
1518*9c5db199SXin Li
1519*9c5db199SXin Li
1520*9c5db199SXin Li    print('<h2 id=\"page_sub_title\">Host Info</h2>', file=output)
1521*9c5db199SXin Li    print('<h2 id=\"comment\">click on each item to expend/collapse</h2>', file=output)
1522*9c5db199SXin Li    ## Meta list comes here..
1523*9c5db199SXin Li    print('<p>', file=output)
1524*9c5db199SXin Li    print('<A href="#" class="button" onClick="expandTree(\'meta_tree\');return false;">Expand All</A>', file=output)
1525*9c5db199SXin Li    print('&nbsp;&nbsp;&nbsp', file=output)
1526*9c5db199SXin Li    print('<A class="button" href="#" onClick="collapseTree(\'meta_tree\'); return false;">Collapse All</A>', file=output)
1527*9c5db199SXin Li    print('</p>', file=output)
1528*9c5db199SXin Li
1529*9c5db199SXin Li    print('<ul class="mktree" id="meta_tree">', file=output)
1530*9c5db199SXin Li    counter = 0
1531*9c5db199SXin Li    keys = list(metadata.keys())
1532*9c5db199SXin Li    keys.sort()
1533*9c5db199SXin Li    for key in keys:
1534*9c5db199SXin Li        val = metadata[key]
1535*9c5db199SXin Li        print('<li id=\"meta_headline\">%s' % key, file=output)
1536*9c5db199SXin Li        print('<ul><table class="meta_table"><tr><td align="left">%s</td></tr></table></ul></li>' % val, file=output)
1537*9c5db199SXin Li    print('</ul>', file=output)
1538*9c5db199SXin Li
1539*9c5db199SXin Li    print("</body></html>", file=output)
1540*9c5db199SXin Li    if output_file_name:
1541*9c5db199SXin Li        output.close()
1542*9c5db199SXin Li
1543*9c5db199SXin Li
1544*9c5db199SXin Lidef parse_result(dirname, line, results_data):
1545*9c5db199SXin Li    """
1546*9c5db199SXin Li    Parse job status log line.
1547*9c5db199SXin Li
1548*9c5db199SXin Li    @param dirname: Job results dir
1549*9c5db199SXin Li    @param line: Status log line.
1550*9c5db199SXin Li    @param results_data: Dictionary with for results.
1551*9c5db199SXin Li    """
1552*9c5db199SXin Li    parts = line.split()
1553*9c5db199SXin Li    if len(parts) < 4:
1554*9c5db199SXin Li        return None
1555*9c5db199SXin Li    global tests
1556*9c5db199SXin Li    if parts[0] == 'START':
1557*9c5db199SXin Li        pair = parts[3].split('=')
1558*9c5db199SXin Li        stime = int(pair[1])
1559*9c5db199SXin Li        results_data[parts[1]] = [stime, [], None]
1560*9c5db199SXin Li        try:
1561*9c5db199SXin Li            parent_test = re.findall(r".*/", parts[1])[0][:-1]
1562*9c5db199SXin Li            results_data[parent_test][1].append(parts[1])
1563*9c5db199SXin Li        except IndexError:
1564*9c5db199SXin Li            results_data[""][1].append(parts[1])
1565*9c5db199SXin Li
1566*9c5db199SXin Li    elif (parts[0] == 'END'):
1567*9c5db199SXin Li        result = {}
1568*9c5db199SXin Li        exec_time = ''
1569*9c5db199SXin Li        # fetch time stamp
1570*9c5db199SXin Li        if len(parts) > 7:
1571*9c5db199SXin Li            temp = parts[5].split('=')
1572*9c5db199SXin Li            exec_time = temp[1] + ' ' + parts[6] + ' ' + parts[7]
1573*9c5db199SXin Li        # assign default values
1574*9c5db199SXin Li        result['time'] = exec_time
1575*9c5db199SXin Li        result['testcase'] = 'na'
1576*9c5db199SXin Li        result['status'] = 'na'
1577*9c5db199SXin Li        result['log'] = None
1578*9c5db199SXin Li        result['exec_time_sec'] = 'na'
1579*9c5db199SXin Li        tag = parts[3]
1580*9c5db199SXin Li
1581*9c5db199SXin Li        result['subdir'] = parts[2]
1582*9c5db199SXin Li        # assign actual values
1583*9c5db199SXin Li        rx = re.compile('^(\w+)\.(.*)$')
1584*9c5db199SXin Li        m1 = rx.findall(parts[3])
1585*9c5db199SXin Li        if len(m1):
1586*9c5db199SXin Li            result['testcase'] = m1[0][1]
1587*9c5db199SXin Li        else:
1588*9c5db199SXin Li            result['testcase'] = parts[3]
1589*9c5db199SXin Li        result['title'] = str(tag)
1590*9c5db199SXin Li        result['status'] = parts[1]
1591*9c5db199SXin Li        if result['status'] != 'GOOD':
1592*9c5db199SXin Li            result['log'] = get_exec_log(dirname, tag)
1593*9c5db199SXin Li        if len(results_data)>0:
1594*9c5db199SXin Li            pair = parts[4].split('=')
1595*9c5db199SXin Li            etime = int(pair[1])
1596*9c5db199SXin Li            stime = results_data[parts[2]][0]
1597*9c5db199SXin Li            total_exec_time_sec = etime - stime
1598*9c5db199SXin Li            result['exec_time_sec'] = total_exec_time_sec
1599*9c5db199SXin Li        results_data[parts[2]][2] = result
1600*9c5db199SXin Li    return None
1601*9c5db199SXin Li
1602*9c5db199SXin Li
1603*9c5db199SXin Lidef get_exec_log(resdir, tag):
1604*9c5db199SXin Li    """
1605*9c5db199SXin Li    Get job execution summary.
1606*9c5db199SXin Li
1607*9c5db199SXin Li    @param resdir: Job results dir.
1608*9c5db199SXin Li    @param tag: Job tag.
1609*9c5db199SXin Li    """
1610*9c5db199SXin Li    stdout_file = os.path.join(resdir, tag, 'debug', 'stdout')
1611*9c5db199SXin Li    stderr_file = os.path.join(resdir, tag, 'debug', 'stderr')
1612*9c5db199SXin Li    status_file = os.path.join(resdir, tag, 'status')
1613*9c5db199SXin Li    dmesg_file = os.path.join(resdir, tag, 'sysinfo', 'dmesg')
1614*9c5db199SXin Li    log = ''
1615*9c5db199SXin Li    log += '<br><b>STDERR:</b><br>'
1616*9c5db199SXin Li    log += get_info_file(stderr_file)
1617*9c5db199SXin Li    log += '<br><b>STDOUT:</b><br>'
1618*9c5db199SXin Li    log += get_info_file(stdout_file)
1619*9c5db199SXin Li    log += '<br><b>STATUS:</b><br>'
1620*9c5db199SXin Li    log += get_info_file(status_file)
1621*9c5db199SXin Li    log += '<br><b>DMESG:</b><br>'
1622*9c5db199SXin Li    log += get_info_file(dmesg_file)
1623*9c5db199SXin Li    return log
1624*9c5db199SXin Li
1625*9c5db199SXin Li
1626*9c5db199SXin Lidef get_info_file(filename):
1627*9c5db199SXin Li    """
1628*9c5db199SXin Li    Gets the contents of an autotest info file.
1629*9c5db199SXin Li
1630*9c5db199SXin Li    It also and highlights the file contents with possible problems.
1631*9c5db199SXin Li
1632*9c5db199SXin Li    @param filename: Info file path.
1633*9c5db199SXin Li    """
1634*9c5db199SXin Li    data = ''
1635*9c5db199SXin Li    errors = re.compile(r"\b(error|fail|failed)\b", re.IGNORECASE)
1636*9c5db199SXin Li    if os.path.isfile(filename):
1637*9c5db199SXin Li        f = open('%s' % filename, "r")
1638*9c5db199SXin Li        lines = f.readlines()
1639*9c5db199SXin Li        f.close()
1640*9c5db199SXin Li        rx = re.compile('(\'|\")')
1641*9c5db199SXin Li        for line in lines:
1642*9c5db199SXin Li            new_line = rx.sub('', line)
1643*9c5db199SXin Li            errors_found = errors.findall(new_line)
1644*9c5db199SXin Li            if len(errors_found) > 0:
1645*9c5db199SXin Li                data += '<font color=red>%s</font><br>' % str(new_line)
1646*9c5db199SXin Li            else:
1647*9c5db199SXin Li                data += '%s<br>' % str(new_line)
1648*9c5db199SXin Li        if not data:
1649*9c5db199SXin Li            data = 'No Information Found.<br>'
1650*9c5db199SXin Li    else:
1651*9c5db199SXin Li        data = 'File not found.<br>'
1652*9c5db199SXin Li    return data
1653*9c5db199SXin Li
1654*9c5db199SXin Li
1655*9c5db199SXin Lidef usage():
1656*9c5db199SXin Li    """
1657*9c5db199SXin Li    Print stand alone program usage.
1658*9c5db199SXin Li    """
1659*9c5db199SXin Li    print('usage:',)
1660*9c5db199SXin Li    print('make_html_report.py -r <result_directory> [-f output_file] [-R]')
1661*9c5db199SXin Li    print('(e.g. make_html_reporter.py -r '\
1662*9c5db199SXin Li          '/usr/local/autotest/client/results/default -f /tmp/myreport.html)')
1663*9c5db199SXin Li    print('add "-R" for an html report with relative-paths (relative '
1664*9c5db199SXin Li          'to results directory)')
1665*9c5db199SXin Li    print('')
1666*9c5db199SXin Li    sys.exit(1)
1667*9c5db199SXin Li
1668*9c5db199SXin Li
1669*9c5db199SXin Lidef get_keyval_value(result_dir, key):
1670*9c5db199SXin Li    """
1671*9c5db199SXin Li    Return the value of the first appearance of key in any keyval file in
1672*9c5db199SXin Li    result_dir. If no appropriate line is found, return 'Unknown'.
1673*9c5db199SXin Li
1674*9c5db199SXin Li    @param result_dir: Path that holds the keyval files.
1675*9c5db199SXin Li    @param key: Specific key we're retrieving.
1676*9c5db199SXin Li    """
1677*9c5db199SXin Li    keyval_pattern = os.path.join(result_dir, "kvm.*", "keyval")
1678*9c5db199SXin Li    keyval_lines = subprocess.getoutput(r"grep -h '\b%s\b.*=' %s"
1679*9c5db199SXin Li                                      % (key, keyval_pattern))
1680*9c5db199SXin Li    if not keyval_lines:
1681*9c5db199SXin Li        return "Unknown"
1682*9c5db199SXin Li    keyval_line = keyval_lines.splitlines()[0]
1683*9c5db199SXin Li    if key in keyval_line and "=" in keyval_line:
1684*9c5db199SXin Li        return keyval_line.split("=")[1].strip()
1685*9c5db199SXin Li    else:
1686*9c5db199SXin Li        return "Unknown"
1687*9c5db199SXin Li
1688*9c5db199SXin Li
1689*9c5db199SXin Lidef get_kvm_version(result_dir):
1690*9c5db199SXin Li    """
1691*9c5db199SXin Li    Return an HTML string describing the KVM version.
1692*9c5db199SXin Li
1693*9c5db199SXin Li    @param result_dir: An Autotest job result dir.
1694*9c5db199SXin Li    """
1695*9c5db199SXin Li    kvm_version = get_keyval_value(result_dir, "kvm_version")
1696*9c5db199SXin Li    kvm_userspace_version = get_keyval_value(result_dir,
1697*9c5db199SXin Li                                             "kvm_userspace_version")
1698*9c5db199SXin Li    if kvm_version == "Unknown" or kvm_userspace_version == "Unknown":
1699*9c5db199SXin Li        return None
1700*9c5db199SXin Li    return "Kernel: %s<br>Userspace: %s" % (kvm_version, kvm_userspace_version)
1701*9c5db199SXin Li
1702*9c5db199SXin Li
1703*9c5db199SXin Lidef create_report(dirname, html_path='', output_file_name=None):
1704*9c5db199SXin Li    """
1705*9c5db199SXin Li    Create an HTML report with info about an autotest client job.
1706*9c5db199SXin Li
1707*9c5db199SXin Li    If no relative path (html_path) or output file name provided, an HTML
1708*9c5db199SXin Li    file in the toplevel job results dir called 'job_report.html' will be
1709*9c5db199SXin Li    created, with relative links.
1710*9c5db199SXin Li
1711*9c5db199SXin Li    @param html_path: Prefix for the HTML links. Useful to specify absolute
1712*9c5db199SXin Li            in the report (not wanted most of the time).
1713*9c5db199SXin Li    @param output_file_name: Path to the report file.
1714*9c5db199SXin Li    """
1715*9c5db199SXin Li    res_dir = os.path.abspath(dirname)
1716*9c5db199SXin Li    tag = res_dir
1717*9c5db199SXin Li    status_file_name = os.path.join(dirname, 'status')
1718*9c5db199SXin Li    sysinfo_dir = os.path.join(dirname, 'sysinfo')
1719*9c5db199SXin Li    host = get_info_file(os.path.join(sysinfo_dir, 'hostname'))
1720*9c5db199SXin Li    rx = re.compile('^\s+[END|START].*$')
1721*9c5db199SXin Li    # create the results set dict
1722*9c5db199SXin Li    results_data = {}
1723*9c5db199SXin Li    results_data[""] = [0, [], None]
1724*9c5db199SXin Li    if os.path.exists(status_file_name):
1725*9c5db199SXin Li        f = open(status_file_name, "r")
1726*9c5db199SXin Li        lines = f.readlines()
1727*9c5db199SXin Li        f.close()
1728*9c5db199SXin Li        for line in lines:
1729*9c5db199SXin Li            if rx.match(line):
1730*9c5db199SXin Li                parse_result(dirname, line, results_data)
1731*9c5db199SXin Li    # create the meta info dict
1732*9c5db199SXin Li    metalist = {
1733*9c5db199SXin Li                'uname': get_info_file(os.path.join(sysinfo_dir, 'uname')),
1734*9c5db199SXin Li                'cpuinfo':get_info_file(os.path.join(sysinfo_dir, 'cpuinfo')),
1735*9c5db199SXin Li                'meminfo':get_info_file(os.path.join(sysinfo_dir, 'meminfo')),
1736*9c5db199SXin Li                'df':get_info_file(os.path.join(sysinfo_dir, 'df')),
1737*9c5db199SXin Li                'modules':get_info_file(os.path.join(sysinfo_dir, 'modules')),
1738*9c5db199SXin Li                'gcc':get_info_file(os.path.join(sysinfo_dir, 'gcc_--version')),
1739*9c5db199SXin Li                'dmidecode':get_info_file(os.path.join(sysinfo_dir, 'dmidecode')),
1740*9c5db199SXin Li                'dmesg':get_info_file(os.path.join(sysinfo_dir, 'dmesg')),
1741*9c5db199SXin Li    }
1742*9c5db199SXin Li    if get_kvm_version(dirname) is not None:
1743*9c5db199SXin Li        metalist['kvm_ver'] = get_kvm_version(dirname)
1744*9c5db199SXin Li
1745*9c5db199SXin Li    if output_file_name is None:
1746*9c5db199SXin Li        output_file_name = os.path.join(dirname, 'job_report.html')
1747*9c5db199SXin Li    make_html_file(metalist, results_data, tag, host, output_file_name,
1748*9c5db199SXin Li                   html_path)
1749*9c5db199SXin Li
1750*9c5db199SXin Li
1751*9c5db199SXin Lidef main(argv):
1752*9c5db199SXin Li    """
1753*9c5db199SXin Li    Parses the arguments and executes the stand alone program.
1754*9c5db199SXin Li    """
1755*9c5db199SXin Li    dirname = None
1756*9c5db199SXin Li    output_file_name = None
1757*9c5db199SXin Li    relative_path = False
1758*9c5db199SXin Li    try:
1759*9c5db199SXin Li        opts, args = getopt.getopt(argv, "r:f:h:R", ['help'])
1760*9c5db199SXin Li    except getopt.GetoptError:
1761*9c5db199SXin Li        usage()
1762*9c5db199SXin Li        sys.exit(2)
1763*9c5db199SXin Li    for opt, arg in opts:
1764*9c5db199SXin Li        if opt in ("-h", "--help"):
1765*9c5db199SXin Li            usage()
1766*9c5db199SXin Li            sys.exit()
1767*9c5db199SXin Li        elif opt == '-r':
1768*9c5db199SXin Li            dirname =  arg
1769*9c5db199SXin Li        elif opt == '-f':
1770*9c5db199SXin Li            output_file_name =  arg
1771*9c5db199SXin Li        elif opt == '-R':
1772*9c5db199SXin Li            relative_path = True
1773*9c5db199SXin Li        else:
1774*9c5db199SXin Li            usage()
1775*9c5db199SXin Li            sys.exit(1)
1776*9c5db199SXin Li
1777*9c5db199SXin Li    html_path = dirname
1778*9c5db199SXin Li    # don't use absolute path in html output if relative flag passed
1779*9c5db199SXin Li    if relative_path:
1780*9c5db199SXin Li        html_path = ''
1781*9c5db199SXin Li
1782*9c5db199SXin Li    if dirname:
1783*9c5db199SXin Li        if os.path.isdir(dirname): # TBD: replace it with a validation of
1784*9c5db199SXin Li                                   # autotest result dir
1785*9c5db199SXin Li            create_report(dirname, html_path, output_file_name)
1786*9c5db199SXin Li            sys.exit(0)
1787*9c5db199SXin Li        else:
1788*9c5db199SXin Li            print('Invalid result directory <%s>' % dirname)
1789*9c5db199SXin Li            sys.exit(1)
1790*9c5db199SXin Li    else:
1791*9c5db199SXin Li        usage()
1792*9c5db199SXin Li        sys.exit(1)
1793*9c5db199SXin Li
1794*9c5db199SXin Li
1795*9c5db199SXin Liif __name__ == "__main__":
1796*9c5db199SXin Li    main(sys.argv[1:])
1797