2 * SearchHighlight plugin for jQuery
4 * Thanks to Scott Yang <http://scott.yang.id.au/>
5 * for the original idea and some code
7 * @author Renato Formato <rformato@gmail.com>
9 * @version 0.37 (9/1/2009)
12 * - exact (string, default:"exact")
13 * "exact" : find and highlight the exact words.
14 * "whole" : find partial matches but highlight whole words
15 * "partial": find and highlight partial matches
17 * - style_name (string, default:'hilite')
18 * The class given to the span wrapping the matched words.
20 * - style_name_suffix (boolean, default:true)
21 * If true a different number is added to style_name for every different matched word.
23 * - debug_referrer (string, default:null)
24 * Set a referrer for debugging purpose.
26 * - engines (array of regex, default:null)
27 * Add a new search engine regex to highlight searches coming from new search engines.
28 * The first element is the regex to match the domain.
29 * The second element is the regex to match the query string.
30 * Ex: [/^http:\/\/my\.site\.net/i,/search=([^&]+)/i]
32 * - highlight (string, default:null)
33 * A jQuery selector or object to set the elements enabled for highlight.
34 * If null or no elements are found, all the document is enabled for highlight.
36 * - nohighlight (string, default:null)
37 * A jQuery selector or object to set the elements not enabled for highlight.
38 * This option has priority on highlight.
40 * - keys (string, default:null)
41 * Disable the analisys of the referrer and search for the words given as argument
43 * - min_length (number, default:null)
44 * Set the minimun length of a key
50 jQuery
.fn
.SearchHighlight = function(options
) {
51 var ref
= options
.debug_referrer
|| document
.referrer
;
52 if(!ref
&& options
.keys
==undefined) return this;
54 SearchHighlight
.options
= $.extend({exact
:"exact",style_name
:'hilite',style_name_suffix
:true},options
);
56 if(options
.engines
) SearchHighlight
.engines
.unshift(options
.engines
);
57 var q
= SearchHighlight
.splitKeywords(options
.keys
!=undefined?options
.keys
.toLowerCase():SearchHighlight
.decodeURL(ref
,SearchHighlight
.engines
));
59 SearchHighlight
.buildReplaceTools(q
);
60 if(!SearchHighlight
.regex
) return this;
61 return this.each(function(){
63 if(el
==document
) el
= $("body")[0];
64 SearchHighlight
.hiliteElement(el
);
69 var SearchHighlight
= {
73 [/^http:\/\/(www\.)?google\./i, /q=([^&]+)/i], // Google
74 [/^http:\/\/(www\.)?search\.yahoo\./i, /p=([^&]+)/i], // Yahoo
75 [/^http:\/\/(www\.)?search\.msn\./i, /q=([^&]+)/i], // MSN
76 [/^http:\/\/(www\.)?search\.live\./i, /query=([^&]+)/i], // MSN Live
77 [/^http:\/\/(www\.)?search\.aol\./i, /userQuery=([^&]+)/i], // AOL
78 [/^http:\/\/(www\.)?ask\.com/i, /q=([^&]+)/i], // Ask.com
79 [/^http:\/\/(www\.)?altavista\./i, /q=([^&]+)/i], // AltaVista
80 [/^http:\/\/(www\.)?feedster\./i, /q=([^&]+)/i], // Feedster
81 [/^http:\/\/(www\.)?search\.lycos\./i, /q=([^&]+)/i], // Lycos
82 [/^http:\/\/(www\.)?alltheweb\./i, /q=([^&]+)/i], // AllTheWeb
83 [/^http:\/\/(www\.)?technorati\.com/i, /([^\?\/]+)(?:\?.*)$/i] // Technorati
86 decodeURL: function(URL
,reg
) {
87 //try to properly escape not UTF-8 URI encoded chars
89 URL
= decodeURIComponent(URL
);
94 $.each(reg
,function(i
,n
){
96 var match
= URL
.match(n
[1]);
98 query
= match
[1].toLowerCase();
106 splitKeywords: function(query
) {
108 //do not split keywords enclosed by "
109 var m
= query
.match(/"([^"]*)"/g);
111 for(var i
=0, ml
=m
.length
;i
<ml
;i
++) {
112 var regex
= new RegExp(m
[i
]);
113 query
= query
.replace(regex
,'@@@'+i
+'@@@');
115 query
= query
.split(/[\s,\+]+/);
117 for(var i
=0,l
= query
.length
;i
<l
;i
++) {
118 for(var j
=0, ml
=m
.length
;j
<ml
;j
++) {
119 var regex
= new RegExp("@@@"+j
+"@@@");
120 query
[i
] = query
[i
].replace(regex
,m
[j
].substring(1,m
[j
].length
-1))
128 [/[\xC0-\xC5\u0100-\u0105]/ig,'a'],
129 [/[\xC7\u0106-\u010D]/ig,'c'],
130 [/[\xC8-\xCB]/ig,'e'],
131 [/[\xCC-\xCF]/ig,'i'],
134 [/[\xD2-\xD6\xD8]/ig,'o'],
135 [/[\u015A-\u0161]/ig,'s'],
136 [/[\u0162-\u0167]/ig,'t'],
137 [/[\xD9-\xDC]/ig,'u'],
139 [/[\x91\x92\u2018\u2019]/ig,'\'']
141 matchAccent
: /[\x91\x92\xC0-\xC5\xC7-\xCF\xD1-\xD6\xD8-\xDC\xFF\u0100-\u010D\u0141\u015A-\u0167\u2018\u2019]/ig,
142 replaceAccent: function(q
) {
143 SearchHighlight
.matchAccent
.lastIndex
= 0;
144 if(SearchHighlight
.matchAccent
.test(q
)) {
145 for(var i
=0,l
=SearchHighlight
.regexAccent
.length
;i
<l
;i
++)
146 q
= q
.replace(SearchHighlight
.regexAccent
[i
][0],SearchHighlight
.regexAccent
[i
][1]);
150 escapeRegEx
: /((?:\\{2})*)([[\]{}*?|])/g, //the special chars . and + are already gone at this point because they are considered split chars
151 buildReplaceTools : function(query
) {
153 $.each(query
,function(i
,n
){
154 if(!SearchHighlight
.options
.min_length
|| n
.length
>=SearchHighlight
.options
.min_length
)
155 if(n
= SearchHighlight
.replaceAccent(n
).replace(SearchHighlight
.escapeRegEx
,"$1\\$2"))
159 if(!re
.length
) return;
160 regex
= re
.join("|");
161 switch(SearchHighlight
.options
.exact
) {
163 regex
= '\\b(?:'+regex
+')\\b';
166 regex
= '\\b\\w*('+regex
+')\\w*\\b';
169 SearchHighlight
.regex
= new RegExp(regex
, "gi");
171 $.each(re
,function(i
,n
){
172 SearchHighlight
.subs
[n
] = SearchHighlight
.options
.style_name
+
173 (SearchHighlight
.options
.style_name_suffix
?i
+1:'');
176 nosearch
: /s(?:cript|tyle)|textarea/i,
177 hiliteElement: function(el
) {
178 var opt
= SearchHighlight
.options
, elHighlight
, noHighlight
;
179 elHighlight
= opt
.highlight
?$(opt
.highlight
):$("body");
180 if(!elHighlight
.length
) elHighlight
= $("body");
181 noHighlight
= opt
.nohighlight
?$(opt
.nohighlight
):$([]);
183 elHighlight
.each(function(){
184 SearchHighlight
.hiliteTree(this,noHighlight
);
187 hiliteTree : function(el
,noHighlight
) {
188 if(noHighlight
.index(el
)!=-1) return;
189 var matchIndex
= SearchHighlight
.options
.exact
=="whole"?1:0;
190 for(var startIndex
=0,endIndex
=el
.childNodes
.length
;startIndex
<endIndex
;startIndex
++) {
191 var item
= el
.childNodes
[startIndex
];
192 if ( item
.nodeType
!= 8 ) {//comment node
194 if(item
.nodeType
==3) {
195 var text
= item
.data
, textNoAcc
= SearchHighlight
.replaceAccent(text
);
196 var newtext
="",match
,index
=0;
197 SearchHighlight
.regex
.lastIndex
= 0;
198 while(match
= SearchHighlight
.regex
.exec(textNoAcc
)) {
199 newtext
+= SearchHighlight
.fixTags(text
.substr(index
,match
.index
-index
))+'<span class="'+
200 SearchHighlight
.subs
[match
[matchIndex
].toLowerCase()]+'">'+SearchHighlight
.fixTags(text
.substr(match
.index
,match
[0].length
))+"</span>";
201 index
= match
.index
+match
[0].length
;
204 //add the last part of the text
205 newtext
+= SearchHighlight
.fixTags(text
.substring(index
));
206 var repl
= $.merge([],$("<span>"+newtext
+"</span>")[0].childNodes
);
207 endIndex
+= repl
.length
-1;
208 startIndex
+= repl
.length
-1;
209 $(item
).before(repl
).remove();
212 if(item
.nodeType
==1 && item
.nodeName
.search(SearchHighlight
.nosearch
)==-1)
213 SearchHighlight
.hiliteTree(item
,noHighlight
);
218 fixTags : function(text
) {
219 return text
.replace("<","<").replace(">",">");