
// Page Information Console
// version 0.4.20070208 BETA!
// 2007-02-07
// Copyright (c) 2007, Aphex (apekstvinas[at]gmail.com)
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.  To install it, you need
// Greasemonkey 0.3 or later: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "Hello World", and click Uninstall.
//
// --------------------------------------------------------------------
// 
// Bugs:
// 1. Some of the styles are inherited from the page you're viewing.
//    May cause unexpected visual results.
// 2. Could not reach this.validatorURI variable in xmlhttp request
//    callback function.
// 
// --------------------------------------------------------------------
// 
// Suggestions:
// 
// Any suggestions? Feel free to contact
// me by email apekstvinas[at]gmail.com
//
// Tag grouping using labels is flexible, so there are lot of custom
// sorting and grouping patterns.
// 
// --------------------------------------------------------------------
//
// ==UserScript==
// @name          Page Information Console
// @namespace     http://aphex.wordpress.com
// @description   Script gets basic information about page source
// @include       *
// @exclude       http://localhost/*
// @exclude       http://validator.w3.org/*
// @exclude       http://*.google.*/*
// ==/UserScript==

window.showPageInfo = function ()
{
   // Console stylesheet
	this.CSS = '#pageInfoContainer { border:1px solid silver; padding:10px; margin:0px; font-size: 10pt; background:white; position:fixed; z-index:99998; right:0; bottom:0; left:0;}';
	this.CSS += '#pageInfoContainer * { font-family:"Lucida Grande", Tahoma, Verdana, Arial, Helvetica, sans-serif; color:black; text-align:left; background:white; }';
	this.CSS += '#pageInfoContainer h1 { font-size:15pt; margin:5px 0 0 0; padding:0; border:1px dotted silver; border-width:0 0 1px 0 }';
	this.CSS += '#pageInfoContainer h2 { font-size:12pt; margin:5px 0; padding:0 ; border:1px dotted silver; border-width:0 0 1px 0}';
	this.CSS += '#pageInfoContainer a.valid {color:green}';
	this.CSS += '#pageInfoContainer a.invalid {color:red}';
	this.CSS += '#pageInfoContainer ul {margin:5px; list-style:disc; padding:0 5px}';
	this.CSS += '#pageInfoContainer li {margin:0 5px; padding:0}';
	this.CSS += '#pageInfoContainer .hint { font-size:8pt; color:grey}';
	this.CSS += '#pageInfoContainerControll, #pageInfoContainerControll a {font-weight:normal; border:1px solid silver; padding:2px 3px; margin:0px; font-size: 8pt; background:white; position:fixed; z-index:99999; right:0; bottom:0; color:black; width:30px; text-align:center; text-decoration:none}';
    
	// List of tags with labels. Feel free to modify, create custom groups and list items. Each tag supports caption and array of labels.
	this.tags =
    [
        {caption:'Tables',tag:'table',              label:['bad']},
        {caption:'Font',tag:'font',                 label:['bad']},
        {caption:'Applets',tag:'applet',            label:['bad']},
        {caption:'Basefont',tag:'basefont',         label:['bad']},
        {caption:'Blockquote',tag:'blockquote',     label:['bad']},
        {caption:'Center',tag:'center',             label:['bad']},
        {caption:'Dir',tag:'dir',                   label:['bad']},
        {caption:'Em',tag:'em',                     label:['bad','semantic']},
        {caption:'Isindex',tag:'isindex',           label:['bad']},
        {caption:'Listing',tag:'listing',           label:['bad']},
        {caption:'Menu',tag:'menu',                 label:['bad']},
        {caption:'S',tag:'s',                       label:['bad']},
        {caption:'Strike',tag:'strike',             label:['bad']},
        {caption:'Strong',tag:'strong',             label:['semantic']},
        {caption:'U',tag:'u',                       label:['bad']},
        {caption:'Xmp',tag:'xmp',                   label:['bad']},     
        {caption:'Frames',tag:'frame',              label:['bad']},  
        
        {caption:'Divs',tag:'div',                  label:['ok']},
        {caption:'Spans',tag:'span',                label:['ok']},
        {caption:'UL',tag:'ul',                     label:['ok']},
        {caption:'OL',tag:'ol',                     label:['ok']},
        {caption:'Objects',tag:'object',            label:['ok']},
        {caption:'Head 1',tag:'h1',                 label:['ok','semantic']},
        {caption:'Head 2',tag:'h2',                 label:['ok','semantic']},
        {caption:'Head 3',tag:'h3',                 label:['ok','semantic']},
        {caption:'Iframe',tag:'iframe',             label:['other']},
        {caption:'Scripts',tag:'script',            label:['other']},
        {caption:'Styles',tag:'style',              label:['other']},
    ],
	
	// Captions and hints for tag labels
    this.tagLabels = 
    {
        bad:{		caption:'Depreciated tags',		hint:'table, font, applet, basefont, blockquote, center, dir, em, isindex, listing, menu, s, strike, u, xmp, frame'},
        ok:{		caption:'Apreciated tags', 		hint:'div, span, ul, ol, object, h1, h2, h3'},
        semantic:{	caption:'Semantic', 			hint:'Good for google: h1, h2, h3, strong, em'},
        other:{		caption:'Other tags', 			hint:'iframe, script, style'},
    },
    
	/* w3 html validator uri used to validate page. 
	*  Couldn't figure out a way to reach it in the xmlhttp callback function, therefore don't forget to change this uri in isValid_cb 
	*/
	this.validatorURI = 'http://validator.w3.org/check?uri=';
}

window.showPageInfo.prototype=
{
    /**
    * Counts tags in document
    * 
    * @param	sTag		The name of tag to count
    * @return				Number of tags in document
    */
    countTags:function(sTag)
    {        
        return document.getElementsByTagName(sTag.toUpperCase()).length;
    },
    /**
    * Adds global style
    * 
    * @param	css        The Stylesheet string to add
    */
    addGlobalStyle:function (css) {
        var head, style;
        head = document.getElementsByTagName('head')[0];
        if (!head) { return; }
        style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = css;
        head.appendChild(style);
    },
    /**
    * Gets all unique labels used the tag list
    * 
    * @return				label list
    */
    getAllLabels:function()
    {
        var list;
        
        list=[];
        
        for(var i in  this.tags)
        {            
           list = list.concat(this.tags[i].label);            
        }
        
        list = this.getUniqueArrayValues(list);
        
        return list;       
    },
    /**
    * Gets all tags that have been labeled with specific label
    * 
    * @param	sLabel		Label name
    * @return				Array of tag objects
    */
    getTagsByLabel:function(sLabel)
    {
        var labelTags=[];
       
        for(var i in this.tags)
        {
            if (this.tags[i].label.indexOf(sLabel)!=-1) labelTags = labelTags.concat(this.tags[i]);
        }
        
        return labelTags;        
    },
    /**
    * Gets unique array valuews
    * 
    * @param	array		Array of any values
    * @return				Array of unique values
    */
    getUniqueArrayValues:function(array)
    {
        var uniList=[];
        
        for (var i in array)
        {            
            if(uniList.indexOf(array[i])==-1) uniList = uniList.concat(array[i]);
        }
        
        return uniList;
    },    
    /**
    * Sends XML http request to W3 html validator webservice
    */
    isValid:function  ()
    {
        try
        {
             GM_xmlhttpRequest({
                method: 'GET',
                url: this.validatorURI+encodeURIComponent(window.location.href)+'&output=soap12',
                headers: {
                    'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
                    'Accept': 'application/atom+xml,application/xml,text/xml',
                },
                onload: this.isValid_cb
            });
        }
        catch(err)
        {
            document.getElementById('showPageInfoValidity').innerHTML='<ul class="validity"><li>Html: <a href="http://validator.w3.org/check?uri='+encodeURIComponent(window.location.href)+'" target="_blank" class="invalid">Validation error</a></li></ul>';
        }
       
        
        
    },
    /**
    * Callback function for Validator xmlhttprequest
    * 
    * @param	oHttpRequest	Request object
    */
    isValid_cb:function (oHttpRequest)
    {
        var parser, dom, validity, valid, errorcount, exception, message;
        parser = new DOMParser();        
        dom = parser.parseFromString(oHttpRequest.responseText,"application/xml");
        validity = dom.getElementsByTagName('validity');
        errorcount = dom.getElementsByTagName('errorcount');
        errorcount = (errorcount.length>0)?errorcount.item(0).firstChild.nodeValue:0;
       
        exception = (validity.length==0)?true:false;       
        if(!exception) valid = (validity.item(0).firstChild.nodeValue=='true')?true:false;
        else valid=false;
        
        if(exception) message='Validation error';
        else if(!exception && valid) message='Valid';
        else if(!exception && !valid) message='Invalid';
        
        document.getElementById('showPageInfoValidity').innerHTML='<ul class="validity"><li>Html: <a href="http://validator.w3.org/check?uri='+encodeURIComponent(window.location.href)+'" target="_blank" class="'+((valid)?'valid':'invalid')+'">'+message+'</a> '+((errorcount>0)?'('+errorcount+')':'')+'</li></ul>';
    },
    /**
    * Get all tags of the DOM with attributes having a value of {@param attrValue}
    * 
    * @param	tag			Specific tag
	* @param	attribute	Name of attribute
	* @param	attrValue	Value of attribute
    * @return				Array of tags with attribute and attribute value
    */
    getTagsByAttributes:function (tag,attribute,attrValue)
    {
        var taglist, resultlist;
        
        taglist = document.getElementsByTagName(tag);
        resultlist = [];
        
        for(var i=0; taglist.item.length>i; i++)
        {
            if (taglist.item(i).getAttribute(attribute)==attrValue) resultlist=resultlist.concat(taglist.item(i));
        }
        
        return resultlist;
    },
    /**
    * Creates console window, data structures and outputs all the data. Console is created with style.display="none" attribute.
    * 
    * @return				Console Html object or false o n error
    */
    createConsole:function()
    {
        var container, allLabels, out, labeledTags, tmpOut, numberOfTags, addSection, imagelist1, imagelist2;
        
        out = '<h1>Page Information Console</h1><br><div class="hint">Page uri: '+window.location.href+'</div><br>';
        tmpOut = '';
        addSection = false;
        
        this.addGlobalStyle(this.CSS);
        
        container = document.createElement('div');
        container.setAttribute('id','pageInfoContainer'); 
        container.setAttribute('style','display:none');         
        
		out+='<h2>W3 Validity</h2><div class="hint">Checks html validity using W3C validator service</div><div id="showPageInfoValidity"><ul><li>Checking...</li></ul></div>';
		
        allLabels = this.getAllLabels();
               
        for (var i in allLabels)
        {
             
            labeledTags=this.getTagsByLabel(allLabels[i]);
            
            tmpOut='<h2>'+((typeof(this.tagLabels[allLabels[i]])=='undefined' && typeof(this.tagLabels[allLabels[i]].caption)=='undefined')?allLabels[i]:this.tagLabels[allLabels[i]].caption)+'</h2>';
            if (typeof(this.tagLabels[allLabels[i]])!='undefined' && typeof(this.tagLabels[allLabels[i]].hint)!='undefined') tmpOut+='<div class="hint">'+this.tagLabels[allLabels[i]].hint+'</div>';
            
            tmpOut+='<ul>';
            
            addSection = false;
            for(var ii in labeledTags)
            {
                numberOfTags = this.countTags(labeledTags[ii].tag);
                if (numberOfTags>0) {
                    tmpOut+='<li>'+labeledTags[ii].caption+': '+numberOfTags+'</li>';
                    addSection=true;
                }
            }
            tmpOut+='</ul>';
            
            if(addSection) out+=tmpOut;
                        
        }      
    
        imagelist1=this.getTagsByAttributes('img','alt', null).length;
        imagelist2=this.getTagsByAttributes('img','alt', '').length;
        
        if(imagelist1>0 || imagelist2>0) out+='<h2>Unanotated images</h2><div class="hint">Images that have no or empty alt attribute</div><ul><li>No alt attribute: '+imagelist1+'</li><li>Empty alt attribute: '+imagelist2+'</li></ul>';
        
        container.innerHTML = out;         
        
        try
        {
            
            document.body.insertBefore(container,document.body.firstChild);        
            this.isValid();  
            return container;
            
        }catch(err)
        {
            return false;        
        }
        
    },
    /**
    * Displays console toggle button and calls to create the console.
    */
    init:function()
    {
        var controlContainer, console;
        
        this.createConsole();
        
        controlContainer = document.createElement('div');
        controlContainer.setAttribute('id','pageInfoContainerControll');
        controlContainer.innerHTML = '<a href="#" onClick="p=document.getElementById(\'pageInfoContainer\'); p.style.display=(p.style.display==\'none\')?\'block\':\'none\'; return false;">p.i.c</a>'; 
        document.body.insertBefore(controlContainer,document.body.firstChild);
    }
   
};

window.afx_showPageInfo=new window.showPageInfo();
window.afx_showPageInfo.init();


