[SPIP] +spip v3.0.17
[lhc/web/clavette_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 * - tag_name (string, default:'span')
18 * The tag that is used to wrap the matched words
19 *
20 * - style_name (string, default:'hilite')
21 * The class given to the tag wrapping the matched words.
22 *
23 * - style_name_suffix (boolean, default:true)
24 * If true a different number is added to style_name for every different matched word.
25 *
26 * - debug_referrer (string, default:null)
27 * Set a referrer for debugging purpose.
28 *
29 * - engines (array of regex, default:null)
30 * Add a new search engine regex to highlight searches coming from new search engines.
31 * The first element is the regex to match the domain.
32 * The second element is the regex to match the query string.
33 * Ex: [/^http:\/\/my\.site\.net/i,/search=([^&]+)/i]
34 *
35 * - highlight (string, default:null)
36 * A jQuery selector or object to set the elements enabled for highlight.
37 * If null or no elements are found, all the document is enabled for highlight.
38 *
39 * - nohighlight (string, default:null)
40 * A jQuery selector or object to set the elements not enabled for highlight.
41 * This option has priority on highlight.
42 *
43 * - keys (string, default:null)
44 * Disable the analisys of the referrer and search for the words given as argument
45 *
46 * - min_length (number, default:null)
47 * Set the minimun length of a key
48 *
49 */
50
51 if (window.jQuery)
52 (function($){
53 jQuery.fn.SearchHighlight = function(options) {
54 var ref = options.debug_referrer || document.referrer;
55 if(!ref && options.keys==undefined) return this;
56
57 SearchHighlight.options = $.extend({exact:"exact",tag_name:'span',style_name:'hilite',style_name_suffix:true},options);
58
59 if(options.engines) SearchHighlight.engines.unshift(options.engines);
60 var q = SearchHighlight.splitKeywords(options.keys!=undefined?options.keys.toLowerCase():SearchHighlight.decodeURL(ref,SearchHighlight.engines));
61 if(q && q.join("")) {
62 SearchHighlight.buildReplaceTools(q);
63 if(!SearchHighlight.regex) return this;
64 return this.each(function(){
65 var el = this;
66 if(el==document) el = $("body")[0];
67 SearchHighlight.hiliteElement(el);
68 })
69 } else return this;
70 };
71
72 var SearchHighlight = {
73 options: {},
74 regex: null,
75 engines: [
76 [/^http:\/\/(www\.)?google\./i, /q=([^&]+)/i], // Google
77 [/^http:\/\/(www\.)?search\.yahoo\./i, /p=([^&]+)/i], // Yahoo
78 [/^http:\/\/(www\.)?search\.msn\./i, /q=([^&]+)/i], // MSN
79 [/^http:\/\/(www\.)?search\.live\./i, /query=([^&]+)/i], // MSN Live
80 [/^http:\/\/(www\.)?search\.aol\./i, /userQuery=([^&]+)/i], // AOL
81 [/^http:\/\/(www\.)?ask\.com/i, /q=([^&]+)/i], // Ask.com
82 [/^http:\/\/(www\.)?altavista\./i, /q=([^&]+)/i], // AltaVista
83 [/^http:\/\/(www\.)?feedster\./i, /q=([^&]+)/i], // Feedster
84 [/^http:\/\/(www\.)?search\.lycos\./i, /q=([^&]+)/i], // Lycos
85 [/^http:\/\/(www\.)?alltheweb\./i, /q=([^&]+)/i], // AllTheWeb
86 [/^http:\/\/(www\.)?technorati\.com/i, /([^\?\/]+)(?:\?.*)$/i] // Technorati
87 ],
88 subs: {},
89 decodeURL: function(URL,reg) {
90 //try to properly escape not UTF-8 URI encoded chars
91 try {
92 URL = decodeURIComponent(URL);
93 } catch (e) {
94 URL = unescape(URL);
95 }
96 var query = null;
97 $.each(reg,function(i,n){
98 if(n[0].test(URL)) {
99 var match = URL.match(n[1]);
100 if(match) {
101 query = match[1].toLowerCase();
102 return false;
103 }
104 }
105 });
106
107 return query;
108 },
109 splitKeywords: function(query) {
110 if(query) {
111 //do not split keywords enclosed by "
112 var m = query.match(/"([^"]*)"/g);
113 if(m)
114 for(var i=0, ml=m.length;i<ml;i++) {
115 var i = query.indexOf(m[i]);
116 query = query.substring(0,i)+'@@@'+i+'@@@'+query.substring(i+m[i].length)
117 m[i] = decodeURI(m[i]);
118 m[i] = m[i].split("+").join(' ');
119 }
120 query = query.split(/[\s,\+]+/);
121 if(m)
122 for(var i=0,l = query.length;i<l;i++) {
123 for(var j=0, ml=m.length;j<ml;j++) {
124 var regex = new RegExp("@@@"+j+"@@@");
125 query[i] = query[i].replace(regex,m[j].substring(1,m[j].length-1))
126 }
127 }
128 };
129 return query;
130 },
131
132 regexAccent : [
133 [/[\xC0-\xC5\u0100-\u0105]/ig,'a'],
134 [/[\xC7\u0106-\u010D]/ig,'c'],
135 [/[\xC8-\xCB]/ig,'e'],
136 [/[\xCC-\xCF]/ig,'i'],
137 [/[\u0141]/ig,'l'],
138 [/\xD1/ig,'n'],
139 [/[\xD2-\xD6\xD8]/ig,'o'],
140 [/[\u015A-\u0161]/ig,'s'],
141 [/[\u0162-\u0167]/ig,'t'],
142 [/[\xD9-\xDC]/ig,'u'],
143 [/\xFF/ig,'y'],
144 [/[\x91\x92\u2018\u2019]/ig,'\'']
145 ],
146 matchAccent : /[\x91\x92\xC0-\xC5\xC7-\xCF\xD1-\xD6\xD8-\xDC\xFF\u0100-\u010D\u0141\u015A-\u0167\u2018\u2019]/ig,
147 replaceAccent: function(q) {
148 SearchHighlight.matchAccent.lastIndex = 0;
149 if(SearchHighlight.matchAccent.test(q)) {
150 for(var i=0,l=SearchHighlight.regexAccent.length;i<l;i++)
151 q = q.replace(SearchHighlight.regexAccent[i][0],SearchHighlight.regexAccent[i][1]);
152 }
153 return q;
154 },
155 escapeRegEx : /((?:\\{2})*)([[\]{}*?|])/g, //the special chars . and + are already gone at this point because they are considered split chars
156 buildReplaceTools : function(query) {
157 var re = [], regex;
158 $.each(query,function(i,n){
159 if(!SearchHighlight.options.min_length || n.length>=SearchHighlight.options.min_length)
160 if(n = SearchHighlight.replaceAccent(n).replace(SearchHighlight.escapeRegEx,"$1\\$2"))
161 re.push(n);
162 });
163
164 if(!re.length) return;
165 regex = re.join("|");
166 switch(SearchHighlight.options.exact) {
167 case "exact":
168 regex = '\\b(?:'+regex+')\\b';
169 break;
170 case "whole":
171 regex = '\\b\\w*('+regex+')\\w*\\b';
172 break;
173 }
174 SearchHighlight.regex = new RegExp(regex, "gi");
175
176 $.each(re,function(i,n){
177 SearchHighlight.subs[n] = SearchHighlight.options.style_name+
178 (SearchHighlight.options.style_name_suffix?i+1:'');
179 });
180 },
181 nosearch: /s(?:cript|tyle)|textarea/i,
182 hiliteElement: function(el) {
183 var opt = SearchHighlight.options, elHighlight, noHighlight;
184 elHighlight = opt.highlight?$(opt.highlight):$("body");
185 if(!elHighlight.length) elHighlight = $("body");
186 noHighlight = opt.nohighlight?$(opt.nohighlight):$([]);
187
188 elHighlight.each(function(){
189 SearchHighlight.hiliteTree(this,noHighlight);
190 });
191 },
192 hiliteTree : function(el,noHighlight) {
193 if(noHighlight.index(el)!=-1) return;
194 var matchIndex = SearchHighlight.options.exact=="whole"?1:0;
195 for(var startIndex=0,endIndex=el.childNodes.length;startIndex<endIndex;startIndex++) {
196 var item = el.childNodes[startIndex];
197 if ( item.nodeType != 8 ) {//comment node
198 //text node
199 if(item.nodeType==3) {
200 var text = item.data, textNoAcc = SearchHighlight.replaceAccent(text);
201 var newtext="",match,index=0;
202 SearchHighlight.regex.lastIndex = 0;
203 while(match = SearchHighlight.regex.exec(textNoAcc)) {
204 newtext += SearchHighlight.fixTags(text.substr(index,match.index-index))+'<'+SearchHighlight.options.tag_name+' class="'+
205 SearchHighlight.subs[match[matchIndex].toLowerCase()]+'">'+SearchHighlight.fixTags(text.substr(match.index,match[0].length))+"</"+SearchHighlight.options.tag_name+">";
206 index = match.index+match[0].length;
207 }
208 if(newtext) {
209 //add the last part of the text
210 newtext += SearchHighlight.fixTags(text.substring(index));
211 var repl = $.merge([],$("<"+SearchHighlight.options.tag_name+">"+newtext+"</"+SearchHighlight.options.tag_name+">")[0].childNodes);
212 endIndex += repl.length-1;
213 startIndex += repl.length-1;
214 $(item).before(repl).remove();
215 }
216 } else {
217 if(item.nodeType==1 && item.nodeName.search(SearchHighlight.nosearch)==-1)
218 SearchHighlight.hiliteTree(item,noHighlight);
219 }
220 }
221 }
222 },
223 fixTags : function(text) {
224 return text.replace("<","&lt;").replace(">","&gt;");
225 }
226 };
227 })(jQuery)