Google Keyword Highlight

By kuy Last update May 13, 2010 — Installed 2,357 times.

There are 1 previous version of this script.

// ==UserScript==
// @name           Google Keyword Highlight
// @namespace      http://endflow.net/
// @description    Highlight search keywords in the result page of Google search.
// @version        0.1.6
// @include        http://*.google.tld/*
// @include        http://google.tld/*
// @include        https://*.google.tld/*
// @include        https://google.tld/*
// @exclude        http://mail.google.tld/*
// @exclude        https://mail.google.tld/*
// @exclude        http://docs.google.tld/*
// @exclude        https://docs.google.tld/*
// ==/UserScript==
// @author         Yuki KODAMA (Twitter: kuy, Skype: netkuy)
// @history        [2009-06-12] 0.1.0 initial version
//                 [2009-06-13] 0.1.1 add 'mode' option
//                 [2009-06-17] 0.1.2 add color previews
//                 [2009-06-19] 0.1.3 invert foreground colors
//                 [2009-06-20] 0.1.4 support AutoPagerize
//                 [2009-06-20] 0.1.5 change script name (old: Google Colored Keywords)
//                 [2010-05-13] 0.1.6 catch up new Google search

(function(){
//// Config
var cfg = {
    mode: 'smart', // 'strict', 'smart', 'verbose'
    limit: 2       // limit num of chars for 'smart' mode
}

//// CSS
GM_addStyle(<><![CDATA[
#gkh_hl {font-size: medium; margin-bottom: 6px;}
]]></>);

//// Colors
var colors = [
    'rgb(255, 255, 102)', 'rgb(160, 255, 255)', 'rgb(153, 255, 153)',
    'rgb(255, 153, 153)', 'rgb(255, 102, 255)', ['rgb(136, 0, 0)', 'white'],
    ['rgb(0, 170, 0)', 'white'], ['rgb(136, 104, 0)', 'white'],
    ['rgb(0, 70, 153)', 'white'], ['rgb(153, 0, 153)', 'white']
];
var clen = colors.length;

//// AutoPagerize
var APSupport = function(){
    this.handlers = {doc: [], node: []};
}

APSupport.prototype.init = function(onInit, thisObj){
//    log('APSupport: init');
    var scope = this;
    (function(callback, count){
        count = count || 5;
//        log('APSupport: remain: ' + count);
        if('AutoPagerize' in window){
//            log('APSupport: found');
            var scp = this;
            window.AutoPagerize.addFilter(function()callback.call(scp, 'node', arguments));
            window.AutoPagerize.addDocumentFilter(function()callback.call(scp, 'doc', arguments));
            onInit.call(thisObj);
        }else if(1 < count){
//            log('APSupport: retry');
            setTimeout(arguments.callee, 1000, callback, count - 1);
        }else{
//            log('APSupport: failed');
        }
    })(function()scope._onFetch.apply(scope, arguments));
}

APSupport.prototype._onFetch = function(id, args){
//    log('APSupport: _onFetch: ' + id);
    var list = [];
    switch(id){
        case 'node': list = this.handlers.node; break;
        case 'doc': list = this.handlers.doc; break;
    }
    for(var i = 0; i < list.length; i++)
        list[i][0].apply(list[i][1], args);
}

APSupport.prototype.addNodeHandler = function(handler, thisObj){
//    log('APSupport: addNodeHandler');
    this.handlers.node.push([handler, thisObj]);
}

APSupport.prototype.addDocHandler = function(handler, thisObj){
//    log('APSupport: addDocHandler');
    this.handlers.doc.push([handler, thisObj]);
}

/// Silimar Engine
// http://www.nihonbunka.com/eigodaigaku/archives/000034.html
var singularize = [
    [/^(.*)ies$/i, 'y'], [/^(.*)ves$/i, 'fe'],
    [/^(.*(x|sh|s|z|ch|o))es$/i, ''], [/^(.*o)s$/i, '']
];
var pluralize = [
    [/^(.*[^aiueo])y$/i, 'ies'], [/^(.*)fe?$/i, 'ves'],
    [/^(.*(x|sh|s|z|ch|o))$/i, 'es'], [/^(.*o)$/i, 's']
];

var getSimilars = function(keyword){
    var gen = function(output, pat){
        var m = keyword.match(pat[0]);
        if(m && m.length > 1)
            output.push(m[1] + pat[1]);
    }

    // irregular singularize
    var swords = [];
    for(var i = 0, l = singularize.length; i < l; i++)
        gen(swords, singularize[i]);

    // regular singularize
    if(swords.length == 0)
        gen(swords, [/^(.*)s$/i, '']);

    // irregular pluralize
    var pwords = [];
    if(swords.length == 0)
        for(var i = 0; i < pluralize.length; i++)
            gen(pwords, pluralize[i]);

    // regular pluralize
    if(swords.length == 0 && pwords.length == 0)
        gen(pwords, [/^(.*)$/i, 's']);

    // special variants
    var spvar = function(k){
        var m = k.match(/^(.*)'s$/);
        if(m && m.length == 2)
            swords.push(m[1]);
        else
            swords.push(k + "'s");
    }
    if(0 < swords.length)
        for(var i = 0, l = swords.length; i < l; i++)
            spvar(swords[i]);
    else if(0 < pwords.length)
        spvar(keyword);

    return swords.concat(pwords);
}

var assignColor = function(keyword){
    keyword = keyword.toLowerCase();
    callee = arguments.callee;
    var assign = function(k){
        var next = callee.nextIndex || 0;
        var color = colors[(next + clen) % clen];
        if(color.constructor == ''.constructor)
            color = [color, 'black'];
        cmap[k] = color;
        callee.nextIndex = next + 1;
    }
    if(cfg.mode === 'smart' && cfg.limit < keyword.length){
        var sims = getSimilars(keyword);
//        log(keyword, sims);
        for(var i = 0, l = sims.length; i < l; i++){
            if(hasColor(sims[i])){
                cmap[keyword] = hasColor.$;
                break;
            }
        }
        if(typeof(cmap[keyword]) === 'undefined')
            assign(keyword);
        for(var i = 0, l = sims.length; i < l; i++)
            cmap[sims[i]] = cmap[keyword];
    }else
        assign(keyword);
    return cmap[keyword];
}

var hasColor = function(keyword){
    keyword = keyword.toLowerCase();
    arguments.callee.$ = cmap[keyword];
    return typeof(arguments.callee.$) !== 'undefined';
}

var getColor = function(keyword){
    // ignore num of WS
    keyword = keyword.replace(/\s+/g, ' ');
    if(hasColor(keyword))
        return hasColor.$;
    var color = ['white', 'black'];
    if(cfg.mode !== 'strict')
        color = assignColor(keyword);
    return color;
}

//// Main
var cmap = {};
var main = function(){
    var loaded = $x('//body')[0].getAttribute('gkh_loaded') == 'true';
    var tsf = unsafeWindow.document.forms['tsf'];
    var stats = document.getElementById('resultStats');
    if(loaded || !tsf || !stats){
        if(!loaded)
            setTimeout(main, 100);
        return;
    }
    $x('//body')[0].setAttribute('gkh_loaded', 'true');

    // generates keywords
    var keywords = tsf.q.value;
    keywords = keywords
        .split(keywords.indexOf('"') != -1 ? '"' : ' ')
        .map(function(k)k.replace(/(^\s+|\s+$)/g, ''))
        .filter(function(k)0 < k.length);

//    log(keywords);

    // associates colors and keywords
    for(var i = 0, l = keywords.length; i < l; i++)
        assignColor(keywords[i]);

    // coloring page keywords
    var highlight = function(doc){
        doc = doc || null;
        var ems = $x('//li[contains(concat(" ", @class, " "), " g ")]//em', doc);
        for(var i = 0, l = ems.length; i < l; i++){
            if(!ems[i]) break;
            var color = getColor(ems[i].innerHTML);
            ems[i].style.color = color[1];
            ems[i].style.backgroundColor = color[0];
        }
    }
    highlight();

    // coloring keywords
    stats.parentNode.innerHTML = '<div id="gkh_hl">' + keywords.map(function(k){
        var color = getColor(k);
        return '<span style="color: ' + color[1] + '; background-color: ' + color[0] + ';">' + k + '</span>';
    }).join(' ') + '</div>' + stats.parentNode.innerHTML;

//    log(cmap);

    // add AutoPagerize handler
    var ap = new APSupport();
    ap.init(function(){
        ap.addDocHandler(function(doc){
            highlight(doc)
        }, this);
    }, this);
}

main();

//// Utils
function $x(x,c){c=c||document;var res=c.evaluate(x,c,null,4,null);
for(var i,nodes=[];i=res.iterateNext();nodes.push(i));return nodes}
function log(){unsafeWindow.console.log.apply(unsafeWindow.console.log,
arguments)}
})();