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'; // 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('  ', 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