/** * Search Engine Keyword Highlight (http://fucoder.com/code/se-hilite/) * * This module can be imported by any HTML page, and it would analyse the * referrer for search engine keywords, and then highlight those keywords on * the page, by wrapping them around ... tags. * Document can then define styles else where to provide visual feedbacks. * * Usage: * * In HTML. Add the following line towards the end of the document. * * * * In CSS, define the following style: * * .hilite { background-color: #ff0; } * * If Hilite.style_name_suffix is true, then define the follow styles: * * .hilite1 { background-color: #ff0; } * .hilite2 { background-color: #f0f; } * .hilite3 { background-color: #0ff; } * .hilite4 ... * * @author Scott Yang * @version 1.5 */ // Configuration: Hilite = { /** * Element ID to be highlighted. If set, then only content inside this DOM * element will be highlighted, otherwise everything inside document.body * will be searched. */ elementid: 'content', /** * Whether we are matching an exact word. For example, searching for * "highlight" will only match "highlight" but not "highlighting" if exact * is set to true. */ exact: true, /** * Maximum number of DOM nodes to test, before handing the control back to * the GUI thread. This prevents locking up the UI when parsing and * replacing inside a large document. */ max_nodes: 1000, /** * Whether to automatically hilite a section of the HTML document, by * binding the "Hilite.hilite()" to window.onload() event. If this * attribute is set to false, you can still manually trigger the hilite by * calling Hilite.hilite() in Javascript after document has been fully * loaded. */ onload: true, /** * Name of the style to be used. Default to 'hilite'. */ style_name: 'hilite', /** * Whether to use different style names for different search keywords by * appending a number starting from 1, i.e. hilite1, hilite2, etc. */ style_name_suffix: true, /** * Set it to override the document.referrer string. Used for debugging * only. */ debug_referrer: '' }; Hilite.search_engines = [ ['google\\.', 'q'], // Google ['search\\.yahoo\\.', 'p'], // Yahoo ['search\\.msn\\.', 'q'], // MSN ['search\\.live\\.', 'query'], // MSN Live ['search\\.aol\\.', 'userQuery'], // AOL ['ask\\.com', 'q'], // Ask.com ['altavista\\.', 'q'], // AltaVista ['feedster\\.', 'q'], // Feedster ['search\\.lycos\\.', 'q'], // Lycos ['alltheweb\\.', 'q'], // AllTheWeb ['technorati\\.com/search/([^\\?/]+)', 1], // Technorati ['dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)', 1, true] // DogPile ]; /** * Decode the referrer string and return a list of search keywords. */ Hilite.decodeReferrer = function(referrer) { var query = null; var regex = new RegExp(''); for (var i = 0; i < Hilite.search_engines.length; i ++) { var se = Hilite.search_engines[i]; regex.compile('^http://(www\\.)?' + se[0], 'i'); var match = referrer.match(regex); if (match) { var result; if (isNaN(se[1])) { result = Hilite.decodeReferrerQS(referrer, se[1]); } else { result = match[se[1] + 1]; } if (result) { result = decodeURIComponent(result); // XXX: DogPile's URI requires decoding twice. if (se.length > 2 && se[2]) result = decodeURIComponent(result); result = result.replace(/\'|"/g, ''); result = result.split(/[\s,\+\.]+/); return result; } break; } } return null; }; Hilite.decodeReferrerQS = function(referrer, match) { var idx = referrer.indexOf('?'); var idx2; if (idx >= 0) { var qs = new String(referrer.substring(idx + 1)); idx = 0; idx2 = 0; while ((idx >= 0) && ((idx2 = qs.indexOf('=', idx)) >= 0)) { var key, val; key = qs.substring(idx, idx2); idx = qs.indexOf('&', idx2) + 1; if (key == match) { if (idx <= 0) { return qs.substring(idx2+1); } else { return qs.substring(idx2+1, idx - 1); } } else if (idx <=0) { return null; } } } return null; }; /** * Highlight a DOM element with a list of keywords. */ Hilite.hiliteElement = function(elm, query) { if (!query || elm.childNodes.length == 0) return; var qre = new Array(); for (var i = 0; i < query.length; i ++) { query[i] = query[i].toLowerCase(); if (Hilite.exact) qre.push('\\b'+query[i]+'\\b'); else qre.push(query[i]); } qre = new RegExp(qre.join("|"), "i"); var stylemapper = {}; for (var i = 0; i < query.length; i ++) { if (Hilite.style_name_suffix) stylemapper[query[i]] = Hilite.style_name+(i+1); else stylemapper[query[i]] = Hilite.style_name; } var textproc = function(node) { var match = qre.exec(node.data); if (match) { var val = match[0]; var k = ''; var node2 = node.splitText(match.index); var node3 = node2.splitText(val.length); var span = node.ownerDocument.createElement('SPAN'); node.parentNode.replaceChild(span, node2); span.className = stylemapper[val.toLowerCase()]; span.appendChild(node2); return span; } else { return node; } }; Hilite.walkElements(elm.childNodes[0], 1, textproc); }; /** * Highlight a HTML document using keywords extracted from document.referrer. * This is the main function to be called to perform search engine highlight * on a document. * * Currently it would check for DOM element 'content', element 'container' and * then document.body in that order, so it only highlights appropriate section * on WordPress and Movable Type pages. */ Hilite.hilite = function() { // If 'debug_referrer' then we will use that as our referrer string // instead. var q = Hilite.debug_referrer ? Hilite.debug_referrer : document.referrer; var e = null; q = Hilite.decodeReferrer(q); if (q && ((Hilite.elementid && (e = document.getElementById(Hilite.elementid))) || (e = document.body))) { Hilite.hiliteElement(e, q); } }; Hilite.walkElements = function(node, depth, textproc) { var skipre = /^(script|style|textarea)/i; var count = 0; while (node && depth > 0) { count ++; if (count >= Hilite.max_nodes) { var handler = function() { Hilite.walkElements(node, depth, textproc); }; setTimeout(handler, 50); return; } if (node.nodeType == 1) { // ELEMENT_NODE if (!skipre.test(node.tagName) && node.childNodes.length > 0) { node = node.childNodes[0]; depth ++; continue; } } else if (node.nodeType == 3) { // TEXT_NODE node = textproc(node); } if (node.nextSibling) { node = node.nextSibling; } else { while (depth > 0) { node = node.parentNode; depth --; if (node.nextSibling) { node = node.nextSibling; break; } } } } }; // Trigger the highlight using the onload handler. if (Hilite.onload) { if (window.attachEvent) { window.attachEvent('onload', Hilite.hilite); } else if (window.addEventListener) { window.addEventListener('load', Hilite.hilite, false); } else { var __onload = window.onload; window.onload = function() { Hilite.hilite(); __onload(); }; } }