JWS-120 Fixes the exception raised by the ChunkHolder method in GenericMetadataServic...
[jabaws.git] / website / docs / _static / searchtools.js
1 /*
2  * searchtools.js_t
3  * ~~~~~~~~~~~~~~~~
4  *
5  * Sphinx JavaScript utilities for the full-text search.
6  *
7  * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
8  * :license: BSD, see LICENSE for details.
9  *
10  */
11
12
13 /* Non-minified version JS is _stemmer.js if file is provided */ 
14 /**
15  * Porter Stemmer
16  */
17 var Stemmer = function() {
18
19   var step2list = {
20     ational: 'ate',
21     tional: 'tion',
22     enci: 'ence',
23     anci: 'ance',
24     izer: 'ize',
25     bli: 'ble',
26     alli: 'al',
27     entli: 'ent',
28     eli: 'e',
29     ousli: 'ous',
30     ization: 'ize',
31     ation: 'ate',
32     ator: 'ate',
33     alism: 'al',
34     iveness: 'ive',
35     fulness: 'ful',
36     ousness: 'ous',
37     aliti: 'al',
38     iviti: 'ive',
39     biliti: 'ble',
40     logi: 'log'
41   };
42
43   var step3list = {
44     icate: 'ic',
45     ative: '',
46     alize: 'al',
47     iciti: 'ic',
48     ical: 'ic',
49     ful: '',
50     ness: ''
51   };
52
53   var c = "[^aeiou]";          // consonant
54   var v = "[aeiouy]";          // vowel
55   var C = c + "[^aeiouy]*";    // consonant sequence
56   var V = v + "[aeiou]*";      // vowel sequence
57
58   var mgr0 = "^(" + C + ")?" + V + C;                      // [C]VC... is m>0
59   var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$";    // [C]VC[V] is m=1
60   var mgr1 = "^(" + C + ")?" + V + C + V + C;              // [C]VCVC... is m>1
61   var s_v   = "^(" + C + ")?" + v;                         // vowel in stem
62
63   this.stemWord = function (w) {
64     var stem;
65     var suffix;
66     var firstch;
67     var origword = w;
68
69     if (w.length < 3)
70       return w;
71
72     var re;
73     var re2;
74     var re3;
75     var re4;
76
77     firstch = w.substr(0,1);
78     if (firstch == "y")
79       w = firstch.toUpperCase() + w.substr(1);
80
81     // Step 1a
82     re = /^(.+?)(ss|i)es$/;
83     re2 = /^(.+?)([^s])s$/;
84
85     if (re.test(w))
86       w = w.replace(re,"$1$2");
87     else if (re2.test(w))
88       w = w.replace(re2,"$1$2");
89
90     // Step 1b
91     re = /^(.+?)eed$/;
92     re2 = /^(.+?)(ed|ing)$/;
93     if (re.test(w)) {
94       var fp = re.exec(w);
95       re = new RegExp(mgr0);
96       if (re.test(fp[1])) {
97         re = /.$/;
98         w = w.replace(re,"");
99       }
100     }
101     else if (re2.test(w)) {
102       var fp = re2.exec(w);
103       stem = fp[1];
104       re2 = new RegExp(s_v);
105       if (re2.test(stem)) {
106         w = stem;
107         re2 = /(at|bl|iz)$/;
108         re3 = new RegExp("([^aeiouylsz])\\1$");
109         re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
110         if (re2.test(w))
111           w = w + "e";
112         else if (re3.test(w)) {
113           re = /.$/;
114           w = w.replace(re,"");
115         }
116         else if (re4.test(w))
117           w = w + "e";
118       }
119     }
120
121     // Step 1c
122     re = /^(.+?)y$/;
123     if (re.test(w)) {
124       var fp = re.exec(w);
125       stem = fp[1];
126       re = new RegExp(s_v);
127       if (re.test(stem))
128         w = stem + "i";
129     }
130
131     // Step 2
132     re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
133     if (re.test(w)) {
134       var fp = re.exec(w);
135       stem = fp[1];
136       suffix = fp[2];
137       re = new RegExp(mgr0);
138       if (re.test(stem))
139         w = stem + step2list[suffix];
140     }
141
142     // Step 3
143     re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
144     if (re.test(w)) {
145       var fp = re.exec(w);
146       stem = fp[1];
147       suffix = fp[2];
148       re = new RegExp(mgr0);
149       if (re.test(stem))
150         w = stem + step3list[suffix];
151     }
152
153     // Step 4
154     re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
155     re2 = /^(.+?)(s|t)(ion)$/;
156     if (re.test(w)) {
157       var fp = re.exec(w);
158       stem = fp[1];
159       re = new RegExp(mgr1);
160       if (re.test(stem))
161         w = stem;
162     }
163     else if (re2.test(w)) {
164       var fp = re2.exec(w);
165       stem = fp[1] + fp[2];
166       re2 = new RegExp(mgr1);
167       if (re2.test(stem))
168         w = stem;
169     }
170
171     // Step 5
172     re = /^(.+?)e$/;
173     if (re.test(w)) {
174       var fp = re.exec(w);
175       stem = fp[1];
176       re = new RegExp(mgr1);
177       re2 = new RegExp(meq1);
178       re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
179       if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
180         w = stem;
181     }
182     re = /ll$/;
183     re2 = new RegExp(mgr1);
184     if (re.test(w) && re2.test(w)) {
185       re = /.$/;
186       w = w.replace(re,"");
187     }
188
189     // and turn initial Y back to y
190     if (firstch == "y")
191       w = firstch.toLowerCase() + w.substr(1);
192     return w;
193   }
194 }
195
196
197
198 /**
199  * Simple result scoring code.
200  */
201 var Scorer = {
202   // Implement the following function to further tweak the score for each result
203   // The function takes a result array [filename, title, anchor, descr, score]
204   // and returns the new score.
205   /*
206   score: function(result) {
207     return result[4];
208   },
209   */
210
211   // query matches the full name of an object
212   objNameMatch: 11,
213   // or matches in the last dotted part of the object name
214   objPartialMatch: 6,
215   // Additive scores depending on the priority of the object
216   objPrio: {0:  15,   // used to be importantResults
217             1:  5,   // used to be objectResults
218             2: -5},  // used to be unimportantResults
219   //  Used when the priority is not in the mapping.
220   objPrioDefault: 0,
221
222   // query found in title
223   title: 15,
224   // query found in terms
225   term: 5
226 };
227
228
229
230
231
232 var splitChars = (function() {
233     var result = {};
234     var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648,
235          1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702,
236          2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971,
237          2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345,
238          3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761,
239          3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823,
240          4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125,
241          8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695,
242          11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587,
243          43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141];
244     var i, j, start, end;
245     for (i = 0; i < singles.length; i++) {
246         result[singles[i]] = true;
247     }
248     var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709],
249          [722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161],
250          [1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568],
251          [1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807],
252          [1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047],
253          [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383],
254          [2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450],
255          [2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547],
256          [2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673],
257          [2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820],
258          [2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946],
259          [2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023],
260          [3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173],
261          [3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332],
262          [3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481],
263          [3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718],
264          [3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791],
265          [3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095],
266          [4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205],
267          [4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687],
268          [4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968],
269          [4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869],
270          [5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102],
271          [6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271],
272          [6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592],
273          [6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822],
274          [6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167],
275          [7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959],
276          [7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143],
277          [8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318],
278          [8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483],
279          [8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101],
280          [10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567],
281          [11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292],
282          [12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444],
283          [12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783],
284          [12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311],
285          [19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511],
286          [42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774],
287          [42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071],
288          [43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263],
289          [43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519],
290          [43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647],
291          [43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967],
292          [44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295],
293          [57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274],
294          [64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007],
295          [65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381],
296          [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]];
297     for (i = 0; i < ranges.length; i++) {
298         start = ranges[i][0];
299         end = ranges[i][1];
300         for (j = start; j <= end; j++) {
301             result[j] = true;
302         }
303     }
304     return result;
305 })();
306
307 function splitQuery(query) {
308     var result = [];
309     var start = -1;
310     for (var i = 0; i < query.length; i++) {
311         if (splitChars[query.charCodeAt(i)]) {
312             if (start !== -1) {
313                 result.push(query.slice(start, i));
314                 start = -1;
315             }
316         } else if (start === -1) {
317             start = i;
318         }
319     }
320     if (start !== -1) {
321         result.push(query.slice(start));
322     }
323     return result;
324 }
325
326
327
328
329 /**
330  * Search Module
331  */
332 var Search = {
333
334   _index : null,
335   _queued_query : null,
336   _pulse_status : -1,
337
338   init : function() {
339       var params = $.getQueryParameters();
340       if (params.q) {
341           var query = params.q[0];
342           $('input[name="q"]')[0].value = query;
343           this.performSearch(query);
344       }
345   },
346
347   loadIndex : function(url) {
348     $.ajax({type: "GET", url: url, data: null,
349             dataType: "script", cache: true,
350             complete: function(jqxhr, textstatus) {
351               if (textstatus != "success") {
352                 document.getElementById("searchindexloader").src = url;
353               }
354             }});
355   },
356
357   setIndex : function(index) {
358     var q;
359     this._index = index;
360     if ((q = this._queued_query) !== null) {
361       this._queued_query = null;
362       Search.query(q);
363     }
364   },
365
366   hasIndex : function() {
367       return this._index !== null;
368   },
369
370   deferQuery : function(query) {
371       this._queued_query = query;
372   },
373
374   stopPulse : function() {
375       this._pulse_status = 0;
376   },
377
378   startPulse : function() {
379     if (this._pulse_status >= 0)
380         return;
381     function pulse() {
382       var i;
383       Search._pulse_status = (Search._pulse_status + 1) % 4;
384       var dotString = '';
385       for (i = 0; i < Search._pulse_status; i++)
386         dotString += '.';
387       Search.dots.text(dotString);
388       if (Search._pulse_status > -1)
389         window.setTimeout(pulse, 500);
390     }
391     pulse();
392   },
393
394   /**
395    * perform a search for something (or wait until index is loaded)
396    */
397   performSearch : function(query) {
398     // create the required interface elements
399     this.out = $('#search-results');
400     this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
401     this.dots = $('<span></span>').appendTo(this.title);
402     this.status = $('<p style="display: none"></p>').appendTo(this.out);
403     this.output = $('<ul class="search"/>').appendTo(this.out);
404
405     $('#search-progress').text(_('Preparing search...'));
406     this.startPulse();
407
408     // index already loaded, the browser was quick!
409     if (this.hasIndex())
410       this.query(query);
411     else
412       this.deferQuery(query);
413   },
414
415   /**
416    * execute search (requires search index to be loaded)
417    */
418   query : function(query) {
419     var i;
420     var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"];
421
422     // stem the searchterms and add them to the correct list
423     var stemmer = new Stemmer();
424     var searchterms = [];
425     var excluded = [];
426     var hlterms = [];
427     var tmp = splitQuery(query);
428     var objectterms = [];
429     for (i = 0; i < tmp.length; i++) {
430       if (tmp[i] !== "") {
431           objectterms.push(tmp[i].toLowerCase());
432       }
433
434       if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||
435           tmp[i] === "") {
436         // skip this "word"
437         continue;
438       }
439       // stem the word
440       var word = stemmer.stemWord(tmp[i].toLowerCase());
441       // prevent stemmer from cutting word smaller than two chars
442       if(word.length < 3 && tmp[i].length >= 3) {
443         word = tmp[i];
444       }
445       var toAppend;
446       // select the correct list
447       if (word[0] == '-') {
448         toAppend = excluded;
449         word = word.substr(1);
450       }
451       else {
452         toAppend = searchterms;
453         hlterms.push(tmp[i].toLowerCase());
454       }
455       // only add if not already in the list
456       if (!$u.contains(toAppend, word))
457         toAppend.push(word);
458     }
459     var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
460
461     // console.debug('SEARCH: searching for:');
462     // console.info('required: ', searchterms);
463     // console.info('excluded: ', excluded);
464
465     // prepare search
466     var terms = this._index.terms;
467     var titleterms = this._index.titleterms;
468
469     // array of [filename, title, anchor, descr, score]
470     var results = [];
471     $('#search-progress').empty();
472
473     // lookup as object
474     for (i = 0; i < objectterms.length; i++) {
475       var others = [].concat(objectterms.slice(0, i),
476                              objectterms.slice(i+1, objectterms.length));
477       results = results.concat(this.performObjectSearch(objectterms[i], others));
478     }
479
480     // lookup as search terms in fulltext
481     results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms));
482
483     // let the scorer override scores with a custom scoring function
484     if (Scorer.score) {
485       for (i = 0; i < results.length; i++)
486         results[i][4] = Scorer.score(results[i]);
487     }
488
489     // now sort the results by score (in opposite order of appearance, since the
490     // display function below uses pop() to retrieve items) and then
491     // alphabetically
492     results.sort(function(a, b) {
493       var left = a[4];
494       var right = b[4];
495       if (left > right) {
496         return 1;
497       } else if (left < right) {
498         return -1;
499       } else {
500         // same score: sort alphabetically
501         left = a[1].toLowerCase();
502         right = b[1].toLowerCase();
503         return (left > right) ? -1 : ((left < right) ? 1 : 0);
504       }
505     });
506
507     // for debugging
508     //Search.lastresults = results.slice();  // a copy
509     //console.info('search results:', Search.lastresults);
510
511     // print the results
512     var resultCount = results.length;
513     function displayNextItem() {
514       // results left, load the summary and display it
515       if (results.length) {
516         var item = results.pop();
517         var listItem = $('<li style="display:none"></li>');
518         if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {
519           // dirhtml builder
520           var dirname = item[0] + '/';
521           if (dirname.match(/\/index\/$/)) {
522             dirname = dirname.substring(0, dirname.length-6);
523           } else if (dirname == 'index/') {
524             dirname = '';
525           }
526           listItem.append($('<a/>').attr('href',
527             DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
528             highlightstring + item[2]).html(item[1]));
529         } else {
530           // normal html builders
531           listItem.append($('<a/>').attr('href',
532             item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
533             highlightstring + item[2]).html(item[1]));
534         }
535         if (item[3]) {
536           listItem.append($('<span> (' + item[3] + ')</span>'));
537           Search.output.append(listItem);
538           listItem.slideDown(5, function() {
539             displayNextItem();
540           });
541         } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
542           var suffix = DOCUMENTATION_OPTIONS.SOURCELINK_SUFFIX;
543           $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[5] + (item[5].slice(-suffix.length) === suffix ? '' : suffix),
544                   dataType: "text",
545                   complete: function(jqxhr, textstatus) {
546                     var data = jqxhr.responseText;
547                     if (data !== '' && data !== undefined) {
548                       listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
549                     }
550                     Search.output.append(listItem);
551                     listItem.slideDown(5, function() {
552                       displayNextItem();
553                     });
554                   }});
555         } else {
556           // no source available, just display title
557           Search.output.append(listItem);
558           listItem.slideDown(5, function() {
559             displayNextItem();
560           });
561         }
562       }
563       // search finished, update title and status message
564       else {
565         Search.stopPulse();
566         Search.title.text(_('Search Results'));
567         if (!resultCount)
568           Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
569         else
570             Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
571         Search.status.fadeIn(500);
572       }
573     }
574     displayNextItem();
575   },
576
577   /**
578    * search for object names
579    */
580   performObjectSearch : function(object, otherterms) {
581     var filenames = this._index.filenames;
582     var docnames = this._index.docnames;
583     var objects = this._index.objects;
584     var objnames = this._index.objnames;
585     var titles = this._index.titles;
586
587     var i;
588     var results = [];
589
590     for (var prefix in objects) {
591       for (var name in objects[prefix]) {
592         var fullname = (prefix ? prefix + '.' : '') + name;
593         if (fullname.toLowerCase().indexOf(object) > -1) {
594           var score = 0;
595           var parts = fullname.split('.');
596           // check for different match types: exact matches of full name or
597           // "last name" (i.e. last dotted part)
598           if (fullname == object || parts[parts.length - 1] == object) {
599             score += Scorer.objNameMatch;
600           // matches in last name
601           } else if (parts[parts.length - 1].indexOf(object) > -1) {
602             score += Scorer.objPartialMatch;
603           }
604           var match = objects[prefix][name];
605           var objname = objnames[match[1]][2];
606           var title = titles[match[0]];
607           // If more than one term searched for, we require other words to be
608           // found in the name/title/description
609           if (otherterms.length > 0) {
610             var haystack = (prefix + ' ' + name + ' ' +
611                             objname + ' ' + title).toLowerCase();
612             var allfound = true;
613             for (i = 0; i < otherterms.length; i++) {
614               if (haystack.indexOf(otherterms[i]) == -1) {
615                 allfound = false;
616                 break;
617               }
618             }
619             if (!allfound) {
620               continue;
621             }
622           }
623           var descr = objname + _(', in ') + title;
624
625           var anchor = match[3];
626           if (anchor === '')
627             anchor = fullname;
628           else if (anchor == '-')
629             anchor = objnames[match[1]][1] + '-' + fullname;
630           // add custom score for some objects according to scorer
631           if (Scorer.objPrio.hasOwnProperty(match[2])) {
632             score += Scorer.objPrio[match[2]];
633           } else {
634             score += Scorer.objPrioDefault;
635           }
636           results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]);
637         }
638       }
639     }
640
641     return results;
642   },
643
644   /**
645    * search for full-text terms in the index
646    */
647   performTermsSearch : function(searchterms, excluded, terms, titleterms) {
648     var docnames = this._index.docnames;
649     var filenames = this._index.filenames;
650     var titles = this._index.titles;
651
652     var i, j, file;
653     var fileMap = {};
654     var scoreMap = {};
655     var results = [];
656
657     // perform the search on the required terms
658     for (i = 0; i < searchterms.length; i++) {
659       var word = searchterms[i];
660       var files = [];
661       var _o = [
662         {files: terms[word], score: Scorer.term},
663         {files: titleterms[word], score: Scorer.title}
664       ];
665
666       // no match but word was a required one
667       if ($u.every(_o, function(o){return o.files === undefined;})) {
668         break;
669       }
670       // found search word in contents
671       $u.each(_o, function(o) {
672         var _files = o.files;
673         if (_files === undefined)
674           return
675
676         if (_files.length === undefined)
677           _files = [_files];
678         files = files.concat(_files);
679
680         // set score for the word in each file to Scorer.term
681         for (j = 0; j < _files.length; j++) {
682           file = _files[j];
683           if (!(file in scoreMap))
684             scoreMap[file] = {}
685           scoreMap[file][word] = o.score;
686         }
687       });
688
689       // create the mapping
690       for (j = 0; j < files.length; j++) {
691         file = files[j];
692         if (file in fileMap)
693           fileMap[file].push(word);
694         else
695           fileMap[file] = [word];
696       }
697     }
698
699     // now check if the files don't contain excluded terms
700     for (file in fileMap) {
701       var valid = true;
702
703       // check if all requirements are matched
704       if (fileMap[file].length != searchterms.length)
705           continue;
706
707       // ensure that none of the excluded terms is in the search result
708       for (i = 0; i < excluded.length; i++) {
709         if (terms[excluded[i]] == file ||
710             titleterms[excluded[i]] == file ||
711             $u.contains(terms[excluded[i]] || [], file) ||
712             $u.contains(titleterms[excluded[i]] || [], file)) {
713           valid = false;
714           break;
715         }
716       }
717
718       // if we have still a valid result we can add it to the result list
719       if (valid) {
720         // select one (max) score for the file.
721         // for better ranking, we should calculate ranking by using words statistics like basic tf-idf...
722         var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]}));
723         results.push([docnames[file], titles[file], '', null, score, filenames[file]]);
724       }
725     }
726     return results;
727   },
728
729   /**
730    * helper function to return a node containing the
731    * search summary for a given text. keywords is a list
732    * of stemmed words, hlwords is the list of normal, unstemmed
733    * words. the first one is used to find the occurrence, the
734    * latter for highlighting it.
735    */
736   makeSearchSummary : function(text, keywords, hlwords) {
737     var textLower = text.toLowerCase();
738     var start = 0;
739     $.each(keywords, function() {
740       var i = textLower.indexOf(this.toLowerCase());
741       if (i > -1)
742         start = i;
743     });
744     start = Math.max(start - 120, 0);
745     var excerpt = ((start > 0) ? '...' : '') +
746       $.trim(text.substr(start, 240)) +
747       ((start + 240 - text.length) ? '...' : '');
748     var rv = $('<div class="context"></div>').text(excerpt);
749     $.each(hlwords, function() {
750       rv = rv.highlightText(this, 'highlighted');
751     });
752     return rv;
753   }
754 };
755
756 $(document).ready(function() {
757   Search.init();
758 });