[SPIP] +2.1.12
[velocampus/web/www.git] / www / prive / javascript / SearchHighlight.js
1 /**
2 * SearchHighlight plugin for jQuery
3 *
4 * Thanks to Scott Yang <http://scott.yang.id.au/>
5 * for the original idea and some code
6 *
7 * @author Renato Formato <rformato@gmail.com>
8 *
9 * @version 0.37 (9/1/2009)
10 *
11 * Options
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
16 *
17 * - style_name (string, default:'hilite')
18 * The class given to the span wrapping the matched words.
19 *
20 * - style_name_suffix (boolean, default:true)
21 * If true a different number is added to style_name for every different matched word.
22 *
23 * - debug_referrer (string, default:null)
24 * Set a referrer for debugging purpose.
25 *
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]
31 *
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.
35 *
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.
39 *
40 * - keys (string, default:null)
41 * Disable the analisys of the referrer and search for the words given as argument
42 *
43 * - min_length (number, default:null)
44 * Set the minimun length of a key
45 *
46 */
47
48 if (window.jQuery)
49 (function($){
50 jQuery.fn.SearchHighlight = function(options) {
51 var ref = options.debug_referrer || document.referrer;
52 if(!ref && options.keys==undefined) return this;
53
54 SearchHighlight.options = $.extend({exact:"exact",style_name:'hilite',style_name_suffix:true},options);
55
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));
58 if(q && q.join("")) {
59 SearchHighlight.buildReplaceTools(q);
60 if(!SearchHighlight.regex) return this;
61 return this.each(function(){
62 var el = this;
63 if(el==document) el = $("body")[0];
64 SearchHighlight.hiliteElement(el);
65 })
66 } else return this;
67 };
68
69 var SearchHighlight = {
70 options: {},
71 regex: null,
72 engines: [
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
84 ],
85 subs: {},
86 decodeURL: function(URL,reg) {
87 //try to properly escape not UTF-8 URI encoded chars
88 try {
89 URL = decodeURIComponent(URL);
90 } catch (e) {
91 URL = unescape(URL);
92 }
93 var query = null;
94 $.each(reg,function(i,n){
95 if(n[0].test(URL)) {
96 var match = URL.match(n[1]);
97 if(match) {
98 query = match[1].toLowerCase();
99 return false;
100 }
101 }
102 });
103
104 return query;
105 },
106 splitKeywords: function(query) {
107 if(query) {
108 //do not split keywords enclosed by "
109 var m = query.match(/"([^"]*)"/g);
110 if(m)
111 for(var i=0, ml=m.length;i<ml;i++) {
112 var regex = new RegExp(m[i]);
113 query = query.replace(regex,'@@@'+i+'@@@');
114 }
115 query = query.split(/[\s,\+]+/);
116 if(m)
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))
121 }
122 }
123 };
124 return query;
125 },
126
127 regexAccent : [
128 [/[\xC0-\xC5\u0100-\u0105]/ig,'a'],
129 [/[\xC7\u0106-\u010D]/ig,'c'],
130 [/[\xC8-\xCB]/ig,'e'],
131 [/[\xCC-\xCF]/ig,'i'],
132 [/[\u0141]/ig,'l'],
133 [/\xD1/ig,'n'],
134 [/[\xD2-\xD6\xD8]/ig,'o'],
135 [/[\u015A-\u0161]/ig,'s'],
136 [/[\u0162-\u0167]/ig,'t'],
137 [/[\xD9-\xDC]/ig,'u'],
138 [/\xFF/ig,'y'],
139 [/[\x91\x92\u2018\u2019]/ig,'\'']
140 ],
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]);
147 }
148 return q;
149 },
150 escapeRegEx : /((?:\\{2})*)([[\]{}*?|])/g, //the special chars . and + are already gone at this point because they are considered split chars
151 buildReplaceTools : function(query) {
152 var re = [], regex;
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"))
156 re.push(n);
157 });
158
159 if(!re.length) return;
160 regex = re.join("|");
161 switch(SearchHighlight.options.exact) {
162 case "exact":
163 regex = '\\b(?:'+regex+')\\b';
164 break;
165 case "whole":
166 regex = '\\b\\w*('+regex+')\\w*\\b';
167 break;
168 }
169 SearchHighlight.regex = new RegExp(regex, "gi");
170
171 $.each(re,function(i,n){
172 SearchHighlight.subs[n] = SearchHighlight.options.style_name+
173 (SearchHighlight.options.style_name_suffix?i+1:'');
174 });
175 },
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):$([]);
182
183 elHighlight.each(function(){
184 SearchHighlight.hiliteTree(this,noHighlight);
185 });
186 },
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
193 //text 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;
202 }
203 if(newtext) {
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();
210 }
211 } else {
212 if(item.nodeType==1 && item.nodeName.search(SearchHighlight.nosearch)==-1)
213 SearchHighlight.hiliteTree(item,noHighlight);
214 }
215 }
216 }
217 },
218 fixTags : function(text) {
219 return text.replace("<","&lt;").replace(">","&gt;");
220 }
221 };
222 })(jQuery)